Adding all response capabilities.

This commit is contained in:
mike12345567 2021-12-06 17:39:51 +00:00
parent 65d6ca9249
commit ba9854d121
18 changed files with 212 additions and 189 deletions

View File

@ -8,6 +8,7 @@
export let error = null export let error = null
export let id = null export let id = null
export let height = null export let height = null
export let minHeight = null
export const getCaretPosition = () => ({ export const getCaretPosition = () => ({
start: textarea.selectionStart, start: textarea.selectionStart,
end: textarea.selectionEnd, end: textarea.selectionEnd,
@ -23,7 +24,8 @@
</script> </script>
<div <div
style={height ? `height: ${height}px;` : ""} style={(height ? `height: ${height}px;` : "") +
(minHeight ? `min-height: ${minHeight}px` : "")}
class="spectrum-Textfield spectrum-Textfield--multiline" class="spectrum-Textfield spectrum-Textfield--multiline"
class:is-invalid={!!error} class:is-invalid={!!error}
class:is-disabled={disabled} class:is-disabled={disabled}

View File

@ -11,6 +11,7 @@
export let error = null export let error = null
export let getCaretPosition = null export let getCaretPosition = null
export let height = null export let height = null
export let minHeight = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -27,6 +28,7 @@
{value} {value}
{placeholder} {placeholder}
{height} {height}
{minHeight}
on:change={onChange} on:change={onChange}
/> />
</Field> </Field>

View File

@ -1,5 +1,5 @@
import { datasources, tables } from "../stores/backend" import { datasources, tables } from "../stores/backend"
import { IntegrationNames } from "../constants" import { IntegrationNames } from "../constants/backend"
import analytics, { Events } from "../analytics" import analytics, { Events } from "../analytics"
import { get } from "svelte/store" import { get } from "svelte/store"
import cloneDeep from "lodash/cloneDeepWith" import cloneDeep from "lodash/cloneDeepWith"

View File

@ -39,7 +39,7 @@
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow $: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
$: { $: {
UNSORTABLE_TYPES.forEach(type => { UNSORTABLE_TYPES.forEach(type => {
Object.values(schema).forEach(col => { Object.values(schema || {}).forEach(col => {
if (col.type === type) { if (col.type === type) {
col.sortable = false col.sortable = false
} }
@ -113,16 +113,16 @@
<Layout noPadding gap="S"> <Layout noPadding gap="S">
<div> <div>
<div class="table-title"> {#if title}
{#if title} <div class="table-title">
<Heading size="S">{title}</Heading> <Heading size="S">{title}</Heading>
{/if} {#if loading}
{#if loading} <div transition:fade|local>
<div transition:fade|local> <Spinner size="10" />
<Spinner size="10" /> </div>
</div> {/if}
{/if} </div>
</div> {/if}
<div class="popovers"> <div class="popovers">
<slot /> <slot />
{#if !isUsersTable && selectedRows.length > 0} {#if !isUsersTable && selectedRows.length > 0}

View File

@ -9,7 +9,7 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { capitalise } from "helpers" import { capitalise } from "helpers"
import { IntegrationTypes } from "constants" import { IntegrationTypes } from "constants/backend"
export let datasource export let datasource
export let schema export let schema

View File

@ -3,7 +3,7 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import ICONS from "../icons" import ICONS from "../icons"
import api from "builderStore/api" import api from "builderStore/api"
import { IntegrationNames, IntegrationTypes } from "constants" import { IntegrationNames, IntegrationTypes } from "constants/backend"
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte" import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
import DatasourceConfigModal from "components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte" import DatasourceConfigModal from "components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte"
import { createRestDatasource } from "builderStore/datasource" import { createRestDatasource } from "builderStore/datasource"

View File

@ -2,7 +2,7 @@
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { ModalContent, notifications, Body, Layout } from "@budibase/bbui" import { ModalContent, notifications, Body, Layout } from "@budibase/bbui"
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte" import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
import { IntegrationNames } from "constants" import { IntegrationNames } from "constants/backend"
import cloneDeep from "lodash/cloneDeepWith" import cloneDeep from "lodash/cloneDeepWith"
import { saveDatasource as save } from "builderStore/datasource" import { saveDatasource as save } from "builderStore/datasource"

View File

@ -0,0 +1,11 @@
<script>
import { TextArea } from "@budibase/bbui"
export let data
export let height
export let minHeight = "120"
$: string = JSON.stringify(data || {}, null, 2)
</script>
<TextArea disabled value={string} {height} {minHeight} />

View File

@ -1,5 +1,12 @@
<script> <script>
import { Icon, ActionButton, Input, Label, Toggle } from "@budibase/bbui" import {
Icon,
ActionButton,
Input,
Label,
Toggle,
Select,
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { lowercase } from "helpers" import { lowercase } from "helpers"
@ -12,6 +19,7 @@
export let name export let name
export let headings = false export let headings = false
export let activity = false export let activity = false
export let options
let fields = Object.entries(object).map(([name, value]) => ({ name, value })) let fields = Object.entries(object).map(([name, value]) => ({ name, value }))
@ -50,7 +58,15 @@
<div class="container" class:container-active={activity} class:readOnly> <div class="container" class:container-active={activity} class:readOnly>
{#each fields as field, idx} {#each fields as field, idx}
<Input placeholder="Key" bind:value={field.name} on:change={changed} /> <Input placeholder="Key" bind:value={field.name} on:change={changed} />
<Input placeholder="Value" bind:value={field.value} on:change={changed} /> {#if options}
<Select bind:value={field.value} on:change={changed} {options} />
{:else}
<Input
placeholder="Value"
bind:value={field.value}
on:change={changed}
/>
{/if}
{#if activity} {#if activity}
<Toggle /> <Toggle />
{/if} {/if}

View File

@ -14,7 +14,6 @@
Tab, Tab,
} from "@budibase/bbui" } from "@budibase/bbui"
import { notifications, Divider } from "@budibase/bbui" import { notifications, Divider } from "@budibase/bbui"
import api from "builderStore/api"
import ExtraQueryConfig from "./ExtraQueryConfig.svelte" import ExtraQueryConfig from "./ExtraQueryConfig.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte" import IntegrationQueryEditor from "components/integration/index.svelte"
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte" import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
@ -28,23 +27,20 @@
} from "stores/backend" } from "stores/backend"
import { capitalise } from "../../helpers" import { capitalise } from "../../helpers"
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte" import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
import { Roles } from "constants/backend" import JSONPreview from "./JSONPreview.svelte"
import { Roles, SchemaTypeOptions } from "constants/backend"
import { onMount } from "svelte" import { onMount } from "svelte"
import KeyValueBuilder from "./KeyValueBuilder.svelte"
import { fieldsToSchema, schemaToFields } from "helpers/data/utils"
export let query export let query
let fields = query.schema ? schemaToFields(query.schema) : [] let fields = query?.schema ? schemaToFields(query.schema) : []
let parameters let parameters
let data = [] let data = []
let roleId let roleId
const transformerDocs = const transformerDocs =
"https://docs.budibase.com/building-apps/data/transformers" "https://docs.budibase.com/building-apps/data/transformers"
const typeOptions = [
{ label: "Text", value: "string" },
{ label: "Number", value: "number" },
{ label: "Boolean", value: "boolean" },
{ label: "Datetime", value: "datetime" },
]
$: datasource = $datasources.list.find(ds => ds._id === query.datasourceId) $: datasource = $datasources.list.find(ds => ds._id === query.datasourceId)
$: query.schema = fieldsToSchema(fields) $: query.schema = fieldsToSchema(fields)
@ -60,15 +56,6 @@
query.transformer = "return data" query.transformer = "return data"
} }
function newField() {
fields = [...fields, {}]
}
function deleteField(idx) {
fields.splice(idx, 1)
fields = fields
}
function resetDependentFields() { function resetDependentFields() {
if (query.fields.extra) { if (query.fields.extra) {
query.fields.extra = {} query.fields.extra = {}
@ -94,43 +81,18 @@
async function previewQuery() { async function previewQuery() {
try { try {
const response = await api.post(`/api/queries/preview`, { const response = await queries.preview(query)
fields: query.fields, if (response.rows.length === 0) {
queryVerb: query.queryVerb,
transformer: query.transformer,
parameters: query.parameters.reduce(
(acc, next) => ({
...acc,
[next.name]: next.default,
}),
{}
),
datasourceId: datasource._id,
})
const json = await response.json()
if (response.status !== 200) throw new Error(json.message)
data = json.rows || []
if (data.length === 0) {
notifications.info( notifications.info(
"Query results empty. Please execute a query with results to create your schema." "Query results empty. Please execute a query with results to create your schema."
) )
return return
} }
data = response.rows
fields = response.schema
notifications.success("Query executed successfully.") notifications.success("Query executed successfully.")
// Assume all the fields are strings and create a basic schema from the
// unique fields returned by the server
fields = json.schemaFields.map(field => ({
name: field,
type: "string",
}))
} catch (err) { } catch (err) {
notifications.error(`Query Error: ${err.message}`) notifications.error(err)
console.error(err)
} }
} }
@ -146,26 +108,6 @@
} }
} }
function schemaToFields(schema) {
return Object.keys(schema).map(key => ({
name: key,
type: query.schema[key].type,
}))
}
function fieldsToSchema(fieldsToConvert) {
return fieldsToConvert.reduce(
(acc, next) => ({
...acc,
[next.name]: {
name: next.name,
type: next.type,
},
}),
{}
)
}
onMount(async () => { onMount(async () => {
if (!query || !query._id) { if (!query || !query._id) {
roleId = Roles.BASIC roleId = Roles.BASIC
@ -271,29 +213,15 @@
{#if data} {#if data}
<Tabs selected="JSON"> <Tabs selected="JSON">
<Tab title="JSON"> <Tab title="JSON">
<pre <JSONPreview data={data[0]} minHeight="120" />
class="preview">
<!-- prettier-ignore -->
{#if !data[0]}
Please run your query to fetch some data.
{:else}
{JSON.stringify(data[0], undefined, 2)}
{/if}
</pre>
</Tab> </Tab>
<Tab title="Schema"> <Tab title="Schema">
<Layout gap="S"> <KeyValueBuilder
{#each fields as field, idx} bind:object={fields}
<div class="field"> name="field"
<Input placeholder="Field Name" bind:value={field.name} /> headings
<Select bind:value={field.type} options={typeOptions} /> options={SchemaTypeOptions}
<Icon name="bleClose" on:click={() => deleteField(idx)} /> />
</div>
{/each}
<div>
<Button secondary on:click={newField}>Add Field</Button>
</div>
</Layout>
</Tab> </Tab>
<Tab title="Preview"> <Tab title="Preview">
<ExternalDataSourceTable {query} {data} /> <ExternalDataSourceTable {query} {data} />
@ -322,29 +250,11 @@
justify-content: space-between; justify-content: space-between;
} }
.field {
display: grid;
grid-template-columns: 1fr 1fr 5%;
gap: var(--spacing-l);
}
.viewer { .viewer {
min-height: 200px; min-height: 200px;
width: 640px; width: 640px;
} }
.preview {
height: 100%;
min-height: 120px;
overflow-y: auto;
overflow-wrap: break-word;
white-space: pre-wrap;
background-color: var(--grey-2);
padding: var(--spacing-m);
border-radius: 8px;
color: var(--ink);
}
.viewer-controls { .viewer-controls {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -154,3 +154,58 @@ export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
export const SWITCHABLE_TYPES = ALLOWABLE_NUMBER_TYPES.concat( export const SWITCHABLE_TYPES = ALLOWABLE_NUMBER_TYPES.concat(
ALLOWABLE_STRING_TYPES ALLOWABLE_STRING_TYPES
) )
export const IntegrationTypes = {
POSTGRES: "POSTGRES",
MONGODB: "MONGODB",
COUCHDB: "COUCHDB",
S3: "S3",
MYSQL: "MYSQL",
REST: "REST",
DYNAMODB: "DYNAMODB",
ELASTICSEARCH: "ELASTICSEARCH",
SQL_SERVER: "SQL_SERVER",
AIRTABLE: "AIRTABLE",
ARANGODB: "ARANGODB",
ORACLE: "ORACLE",
INTERNAL: "INTERNAL",
}
export const IntegrationNames = {
[IntegrationTypes.POSTGRES]: "PostgreSQL",
[IntegrationTypes.MONGODB]: "MongoDB",
[IntegrationTypes.COUCHDB]: "CouchDB",
[IntegrationTypes.S3]: "S3",
[IntegrationTypes.MYSQL]: "MySQL",
[IntegrationTypes.REST]: "REST",
[IntegrationTypes.DYNAMODB]: "DynamoDB",
[IntegrationTypes.ELASTICSEARCH]: "ElasticSearch",
[IntegrationTypes.SQL_SERVER]: "SQL Server",
[IntegrationTypes.AIRTABLE]: "Airtable",
[IntegrationTypes.ARANGODB]: "ArangoDB",
[IntegrationTypes.ORACLE]: "Oracle",
[IntegrationTypes.INTERNAL]: "Internal",
}
export const SchemaTypeOptions = [
{ label: "Text", value: "string" },
{ label: "Number", value: "number" },
{ label: "Boolean", value: "boolean" },
{ label: "Datetime", value: "datetime" },
]
export const RawRestBodyTypes = {
NONE: "none",
FORM: "form",
ENCODED: "encoded",
JSON: "json",
TEXT: "text",
}
export const RestBodyTypes = [
{ name: "none", value: "none" },
{ name: "form-data", value: "form" },
{ name: "x-www-form-encoded", value: "encoded" },
{ name: "raw (JSON)", value: "json" },
{ name: "raw (Text)", value: "text" },
]

View File

@ -15,38 +15,6 @@ export const AppStatus = {
DEPLOYED: "published", DEPLOYED: "published",
} }
export const IntegrationTypes = {
POSTGRES: "POSTGRES",
MONGODB: "MONGODB",
COUCHDB: "COUCHDB",
S3: "S3",
MYSQL: "MYSQL",
REST: "REST",
DYNAMODB: "DYNAMODB",
ELASTICSEARCH: "ELASTICSEARCH",
SQL_SERVER: "SQL_SERVER",
AIRTABLE: "AIRTABLE",
ARANGODB: "ARANGODB",
ORACLE: "ORACLE",
INTERNAL: "INTERNAL",
}
export const IntegrationNames = {
[IntegrationTypes.POSTGRES]: "PostgreSQL",
[IntegrationTypes.MONGODB]: "MongoDB",
[IntegrationTypes.COUCHDB]: "CouchDB",
[IntegrationTypes.S3]: "S3",
[IntegrationTypes.MYSQL]: "MySQL",
[IntegrationTypes.REST]: "REST",
[IntegrationTypes.DYNAMODB]: "DynamoDB",
[IntegrationTypes.ELASTICSEARCH]: "ElasticSearch",
[IntegrationTypes.SQL_SERVER]: "SQL Server",
[IntegrationTypes.AIRTABLE]: "Airtable",
[IntegrationTypes.ARANGODB]: "ArangoDB",
[IntegrationTypes.ORACLE]: "Oracle",
[IntegrationTypes.INTERNAL]: "Internal",
}
// fields on the user table that cannot be edited // fields on the user table that cannot be edited
export const UNEDITABLE_USER_FIELDS = [ export const UNEDITABLE_USER_FIELDS = [
"email", "email",
@ -66,22 +34,6 @@ export const LAYOUT_NAMES = {
}, },
} }
export const RawRestBodyTypes = {
NONE: "none",
FORM: "form",
ENCODED: "encoded",
JSON: "json",
TEXT: "text",
}
export const RestBodyTypes = [
{ name: "none", value: "none" },
{ name: "form-data", value: "form" },
{ name: "x-www-form-encoded", value: "encoded" },
{ name: "raw (JSON)", value: "json" },
{ name: "raw (Text)", value: "text" },
]
export const BUDIBASE_INTERNAL_DB = "bb_internal" export const BUDIBASE_INTERNAL_DB = "bb_internal"
export const APP_NAME_REGEX = /^[\w\s]+$/ export const APP_NAME_REGEX = /^[\w\s]+$/

View File

@ -0,0 +1,19 @@
export function schemaToFields(schema) {
const response = {}
if (schema && typeof schema === "object") {
for (let [field, value] of Object.entries(schema)) {
response[field] = value?.type || "string"
}
}
return response
}
export function fieldsToSchema(fields) {
const response = {}
if (fields && typeof fields === "object") {
for (let [name, type] of Object.entries(fields)) {
response[name] = { name, type }
}
}
return response
}

View File

@ -1,7 +1,7 @@
<script> <script>
import { params } from "@roxi/routify" import { params } from "@roxi/routify"
import { queries, datasources } from "stores/backend" import { queries, datasources } from "stores/backend"
import { IntegrationTypes } from "constants" import { IntegrationTypes } from "constants/backend"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
if ($params.query) { if ($params.query) {
@ -13,7 +13,7 @@
const datasource = $datasources.list.find( const datasource = $datasources.list.find(
ds => ds._id === $datasources.selected ds => ds._id === $datasources.selected
) )
if (datasource.source === IntegrationTypes.REST) { if (datasource?.source === IntegrationTypes.REST) {
$goto("../rest") $goto("../rest")
} }
</script> </script>

View File

@ -1,6 +1,6 @@
<script> <script>
import { Body } from "@budibase/bbui" import { Body } from "@budibase/bbui"
import { RawRestBodyTypes } from "constants" import { RawRestBodyTypes } from "constants/backend"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import CodeMirrorEditor, { import CodeMirrorEditor, {
EditorModes, EditorModes,

View File

@ -15,7 +15,7 @@
import PlusConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte" import PlusConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte"
import ICONS from "components/backend/DatasourceNavigator/icons" import ICONS from "components/backend/DatasourceNavigator/icons"
import VerbRenderer from "./_components/VerbRenderer.svelte" import VerbRenderer from "./_components/VerbRenderer.svelte"
import { IntegrationTypes } from "constants" import { IntegrationTypes } from "constants/backend"
import { isEqual } from "lodash" import { isEqual } from "lodash"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"

View File

@ -15,6 +15,7 @@
Label, Label,
TextArea, TextArea,
Table, Table,
notifications,
} from "@budibase/bbui" } from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import EditableLabel from "components/common/inputs/EditableLabel.svelte" import EditableLabel from "components/common/inputs/EditableLabel.svelte"
@ -24,13 +25,19 @@
import RestBodyInput from "../_components/RestBodyInput.svelte" import RestBodyInput from "../_components/RestBodyInput.svelte"
import { capitalise } from "helpers" import { capitalise } from "helpers"
import { onMount } from "svelte" import { onMount } from "svelte"
import { RestBodyTypes as bodyTypes } from "constants" import { fieldsToSchema, schemaToFields } from "helpers/data/utils"
import {
RestBodyTypes as bodyTypes,
SchemaTypeOptions,
} from "constants/backend"
import JSONPreview from "components/integration/JSONPreview.svelte"
let query let query
let breakQs = {} let breakQs = {}
let url = "" let url = ""
// test - { info: { code: 500, time: "455ms", size: "2.09KB" }} // test - { info: { code: 500, time: "455ms", size: "2.09KB" }}
let response let response
let schema
$: datasource = $datasources.list.find(ds => ds._id === query?.datasourceId) $: datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
$: datasourceType = datasource?.source $: datasourceType = datasource?.source
@ -102,15 +109,38 @@
function learnMoreBanner() {} function learnMoreBanner() {}
function saveQuery() {} function buildQuery() {
const newQuery = { ...query }
const queryString = buildQueryString(breakQs)
newQuery.fields.path = url.split("?")[0]
newQuery.fields.queryString = queryString
return newQuery
}
function sendQuery() {} function saveQuery() {
query.schema = fieldsToSchema(schema)
}
async function runQuery() {
try {
response = await queries.preview(buildQuery(query))
if (response.rows.length === 0) {
notifications.info("Request did not return any data.")
} else {
response.info = response.info || { code: 200 }
notifications.success("Request sent successfully.")
}
} catch (err) {
notifications.error(err)
}
}
onMount(() => { onMount(() => {
query = getSelectedQuery() query = getSelectedQuery()
const qs = query?.fields.queryString const qs = query?.fields.queryString
breakQs = breakQueryString(qs) breakQs = breakQueryString(qs)
url = buildUrl(query.fields.path, qs) url = buildUrl(query.fields.path, qs)
schema = schemaToFields(query.schema)
if (query && !query.transformer) { if (query && !query.transformer) {
query.transformer = "return data" query.transformer = "return data"
} }
@ -149,7 +179,7 @@
<div class="url"> <div class="url">
<Input bind:value={url} /> <Input bind:value={url} />
</div> </div>
<Button cta disabled={!url} on:click={sendQuery}>Send</Button> <Button cta disabled={!url} on:click={runQuery}>Send</Button>
</div> </div>
<Tabs selected="Params" quiet noPadding noHorizPadding> <Tabs selected="Params" quiet noPadding noHorizPadding>
<Tab title="Params"> <Tab title="Params">
@ -203,20 +233,17 @@
{:else} {:else}
<Tabs selected="JSON" quiet noPadding noHorizPadding> <Tabs selected="JSON" quiet noPadding noHorizPadding>
<Tab title="JSON"> <Tab title="JSON">
<CodeMirrorEditor <div>
height={300} <JSONPreview height="300" data={response.rows[0]} />
value={response.text} </div>
resize="vertical"
readonly
on:change={e => (query.transformer = e.detail)}
/>
</Tab> </Tab>
<Tab title="Schema"> <Tab title="Schema">
<KeyValueBuilder <KeyValueBuilder
bind:object={response.schemaFields} bind:object={response.schema}
name="header" name="header"
headings headings
activity activity
options={SchemaTypeOptions}
/> />
</Tab> </Tab>
<Tab title="Raw"> <Tab title="Raw">
@ -225,7 +252,7 @@
<Tab title="Preview"> <Tab title="Preview">
{#if response} {#if response}
<Table <Table
schema={response?.schemaFields} schema={response?.schema}
data={response?.rows} data={response?.rows}
allowEditColumns={false} allowEditColumns={false}
allowEditRows={false} allowEditRows={false}

View File

@ -62,6 +62,35 @@ export function createQueriesStore() {
unselect: () => { unselect: () => {
update(state => ({ ...state, selected: null })) update(state => ({ ...state, selected: null }))
}, },
preview: async query => {
const response = await api.post("/api/queries/preview", {
fields: query.fields,
queryVerb: query.queryVerb,
transformer: query.transformer,
parameters: query.parameters.reduce(
(acc, next) => ({
...acc,
[next.name]: next.default,
}),
{}
),
datasourceId: query.datasourceId,
})
if (response.status !== 200) {
const error = await response.text()
throw `Query error: ${error}`
}
const json = await response.json()
// Assume all the fields are strings and create a basic schema from the
// unique fields returned by the server
const schema = {}
for (let field of json.schemaFields) {
schema[field] = "string"
}
return { ...json, schema, rows: json.rows || [] }
},
delete: async query => { delete: async query => {
const response = await api.delete( const response = await api.delete(
`/api/queries/${query._id}/${query._rev}` `/api/queries/${query._id}/${query._rev}`