2020-12-18 19:19:43 +01:00
|
|
|
<script>
|
2021-03-17 12:40:24 +01:00
|
|
|
import { goto } from "@roxi/routify"
|
2020-12-18 19:19:43 +01:00
|
|
|
import {
|
2021-04-23 11:55:27 +02:00
|
|
|
Icon,
|
2020-12-18 19:19:43 +01:00
|
|
|
Select,
|
|
|
|
Button,
|
2021-04-28 15:46:11 +02:00
|
|
|
ButtonGroup,
|
2021-02-18 17:58:10 +01:00
|
|
|
Body,
|
2020-12-18 19:19:43 +01:00
|
|
|
Label,
|
2021-04-27 15:26:03 +02:00
|
|
|
Layout,
|
2020-12-18 19:19:43 +01:00
|
|
|
Input,
|
|
|
|
Heading,
|
2021-04-21 13:41:44 +02:00
|
|
|
Tabs,
|
2021-04-23 11:55:27 +02:00
|
|
|
Tab,
|
2020-12-18 19:19:43 +01:00
|
|
|
} from "@budibase/bbui"
|
2021-04-20 12:53:19 +02:00
|
|
|
import { notifications, Divider } from "@budibase/bbui"
|
2020-12-18 19:19:43 +01:00
|
|
|
import api from "builderStore/api"
|
2021-07-08 14:38:49 +02:00
|
|
|
import ExtraQueryConfig from "./ExtraQueryConfig.svelte"
|
2020-12-18 19:19:43 +01:00
|
|
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
2021-01-07 14:13:46 +01:00
|
|
|
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
|
2021-02-18 17:58:10 +01:00
|
|
|
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
2021-11-09 17:25:23 +01:00
|
|
|
import {
|
|
|
|
datasources,
|
|
|
|
integrations,
|
|
|
|
queries,
|
|
|
|
roles,
|
|
|
|
permissions,
|
|
|
|
} from "stores/backend"
|
2021-04-20 12:53:19 +02:00
|
|
|
import { capitalise } from "../../helpers"
|
2021-10-12 19:45:13 +02:00
|
|
|
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
|
2021-11-09 17:25:23 +01:00
|
|
|
import { Roles } from "constants/backend"
|
|
|
|
import { onMount } from "svelte"
|
2020-12-18 19:19:43 +01:00
|
|
|
|
|
|
|
export let query
|
|
|
|
export let fields = []
|
|
|
|
|
2021-01-07 14:13:46 +01:00
|
|
|
let parameters
|
2021-01-15 18:29:46 +01:00
|
|
|
let data = []
|
2021-11-09 17:25:23 +01:00
|
|
|
let roleId
|
2021-10-13 17:42:07 +02:00
|
|
|
const transformerDocs =
|
|
|
|
"https://docs.budibase.com/building-apps/data/transformers"
|
2021-04-20 12:53:19 +02:00
|
|
|
const typeOptions = [
|
|
|
|
{ label: "Text", value: "STRING" },
|
|
|
|
{ label: "Number", value: "NUMBER" },
|
|
|
|
{ label: "Boolean", value: "BOOLEAN" },
|
|
|
|
{ label: "Datetime", value: "DATETIME" },
|
|
|
|
]
|
2020-12-18 19:19:43 +01:00
|
|
|
|
2021-06-08 18:14:46 +02:00
|
|
|
$: datasource = $datasources.list.find(ds => ds._id === query.datasourceId)
|
2020-12-18 19:19:43 +01:00
|
|
|
$: query.schema = fields.reduce(
|
|
|
|
(acc, next) => ({
|
|
|
|
...acc,
|
|
|
|
[next.name]: {
|
|
|
|
name: next.name,
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
)
|
2021-01-14 15:22:24 +01:00
|
|
|
$: datasourceType = datasource?.source
|
2021-03-22 13:23:36 +01:00
|
|
|
$: integrationInfo = $integrations[datasourceType]
|
2021-02-18 17:58:10 +01:00
|
|
|
$: queryConfig = integrationInfo?.query
|
2021-02-18 19:55:08 +01:00
|
|
|
$: shouldShowQueryConfig = queryConfig && query.queryVerb
|
2021-06-08 15:26:06 +02:00
|
|
|
$: readQuery = query.queryVerb === "read" || query.readable
|
2021-06-08 18:14:46 +02:00
|
|
|
$: queryInvalid = !query.name || (readQuery && data.length === 0)
|
2021-01-13 15:11:53 +01:00
|
|
|
|
2021-10-12 19:45:13 +02:00
|
|
|
// seed the transformer
|
|
|
|
if (query && !query.transformer) {
|
|
|
|
query.transformer = "return data"
|
|
|
|
}
|
|
|
|
|
2020-12-18 19:19:43 +01:00
|
|
|
function newField() {
|
|
|
|
fields = [...fields, {}]
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteField(idx) {
|
|
|
|
fields.splice(idx, 1)
|
|
|
|
fields = fields
|
|
|
|
}
|
|
|
|
|
2021-07-08 14:38:49 +02:00
|
|
|
function resetDependentFields() {
|
2021-11-09 17:25:23 +01:00
|
|
|
if (query.fields.extra) {
|
|
|
|
query.fields.extra = {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updateRole(role, id = null) {
|
|
|
|
roleId = role
|
|
|
|
if (query?._id || id) {
|
|
|
|
for (let level of ["read", "write"]) {
|
|
|
|
await permissions.save({
|
|
|
|
level,
|
|
|
|
role,
|
|
|
|
resource: query?._id || id,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-07-08 14:38:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function populateExtraQuery(extraQueryFields) {
|
|
|
|
query.fields.extra = extraQueryFields
|
|
|
|
}
|
|
|
|
|
2020-12-18 19:19:43 +01:00
|
|
|
async function previewQuery() {
|
|
|
|
try {
|
2021-01-06 13:28:51 +01:00
|
|
|
const response = await api.post(`/api/queries/preview`, {
|
2021-01-13 15:11:53 +01:00
|
|
|
fields: query.fields,
|
2021-01-12 17:49:11 +01:00
|
|
|
queryVerb: query.queryVerb,
|
2021-10-12 19:45:13 +02:00
|
|
|
transformer: query.transformer,
|
2021-01-08 13:06:37 +01:00
|
|
|
parameters: query.parameters.reduce(
|
|
|
|
(acc, next) => ({
|
|
|
|
...acc,
|
|
|
|
[next.name]: next.default,
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
),
|
2021-01-06 13:28:51 +01:00
|
|
|
datasourceId: datasource._id,
|
2020-12-18 19:19:43 +01:00
|
|
|
})
|
|
|
|
const json = await response.json()
|
2021-01-07 14:13:46 +01:00
|
|
|
|
|
|
|
if (response.status !== 200) throw new Error(json.message)
|
|
|
|
|
2021-02-22 18:41:02 +01:00
|
|
|
data = json.rows || []
|
2020-12-18 19:19:43 +01:00
|
|
|
|
2021-01-15 18:29:46 +01:00
|
|
|
if (data.length === 0) {
|
2021-04-09 12:02:53 +02:00
|
|
|
notifications.info(
|
2021-01-18 16:40:26 +01:00
|
|
|
"Query results empty. Please execute a query with results to create your schema."
|
|
|
|
)
|
2021-01-15 18:29:46 +01:00
|
|
|
return
|
2021-01-18 16:40:26 +01:00
|
|
|
}
|
2021-01-15 18:29:46 +01:00
|
|
|
|
2021-04-09 12:02:53 +02:00
|
|
|
notifications.success("Query executed successfully.")
|
2021-01-15 18:29:46 +01:00
|
|
|
|
2021-02-22 18:41:02 +01:00
|
|
|
// Assume all the fields are strings and create a basic schema from the
|
|
|
|
// unique fields returned by the server
|
2021-05-04 12:32:22 +02:00
|
|
|
fields = json.schemaFields.map(field => ({
|
2020-12-18 19:19:43 +01:00
|
|
|
name: field,
|
|
|
|
type: "STRING",
|
|
|
|
}))
|
|
|
|
} catch (err) {
|
2021-04-09 12:02:53 +02:00
|
|
|
notifications.error(`Query Error: ${err.message}`)
|
2020-12-18 19:19:43 +01:00
|
|
|
console.error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-07 14:13:46 +01:00
|
|
|
async function saveQuery() {
|
|
|
|
try {
|
2021-04-01 11:29:47 +02:00
|
|
|
const { _id } = await queries.save(query.datasourceId, query)
|
2021-11-09 17:25:23 +01:00
|
|
|
await updateRole(roleId, _id)
|
2021-04-09 12:02:53 +02:00
|
|
|
notifications.success(`Query saved successfully.`)
|
2021-04-20 12:53:19 +02:00
|
|
|
$goto(`../${_id}`)
|
2021-01-07 14:13:46 +01:00
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
2021-04-09 12:02:53 +02:00
|
|
|
notifications.error(`Error creating query. ${err.message}`)
|
2021-01-07 14:13:46 +01:00
|
|
|
}
|
|
|
|
}
|
2021-11-09 17:25:23 +01:00
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
if (!query || !query._id) {
|
2021-11-11 13:11:09 +01:00
|
|
|
roleId = Roles.BASIC
|
2021-11-09 17:25:23 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
roleId = (await permissions.forResource(query._id))["read"]
|
|
|
|
} catch (err) {
|
|
|
|
roleId = Roles.BASIC
|
|
|
|
}
|
|
|
|
})
|
2020-12-18 19:19:43 +01:00
|
|
|
</script>
|
|
|
|
|
2021-04-27 15:26:03 +02:00
|
|
|
<Layout gap="S" noPadding>
|
2021-04-30 13:38:06 +02:00
|
|
|
<Heading size="M">Query {integrationInfo?.friendlyName}</Heading>
|
2021-04-20 12:53:19 +02:00
|
|
|
<Divider />
|
2021-04-30 13:38:06 +02:00
|
|
|
<Heading size="S">Config</Heading>
|
2021-04-27 15:26:03 +02:00
|
|
|
<div class="config">
|
2021-02-18 17:58:10 +01:00
|
|
|
<div class="config-field">
|
2021-04-27 15:26:03 +02:00
|
|
|
<Label>Query Name</Label>
|
|
|
|
<Input bind:value={query.name} />
|
2021-01-22 17:49:22 +01:00
|
|
|
</div>
|
2021-04-27 15:26:03 +02:00
|
|
|
{#if queryConfig}
|
|
|
|
<div class="config-field">
|
|
|
|
<Label>Function</Label>
|
|
|
|
<Select
|
|
|
|
bind:value={query.queryVerb}
|
2021-07-08 14:38:49 +02:00
|
|
|
on:change={resetDependentFields}
|
2021-04-27 15:26:03 +02:00
|
|
|
options={Object.keys(queryConfig)}
|
2021-05-04 12:32:22 +02:00
|
|
|
getOptionLabel={verb =>
|
2021-04-27 15:26:03 +02:00
|
|
|
queryConfig[verb]?.displayName || capitalise(verb)}
|
|
|
|
/>
|
|
|
|
</div>
|
2021-11-09 17:25:23 +01:00
|
|
|
<div class="config-field">
|
|
|
|
<Label>Access level</Label>
|
|
|
|
<Select
|
|
|
|
value={roleId}
|
|
|
|
on:change={e => updateRole(e.detail)}
|
|
|
|
options={$roles}
|
|
|
|
getOptionLabel={x => x.name}
|
|
|
|
getOptionValue={x => x._id}
|
|
|
|
/>
|
|
|
|
</div>
|
2021-07-08 14:38:49 +02:00
|
|
|
{#if integrationInfo?.extra && query.queryVerb}
|
|
|
|
<ExtraQueryConfig
|
|
|
|
{query}
|
|
|
|
{populateExtraQuery}
|
|
|
|
config={integrationInfo.extra}
|
|
|
|
/>
|
|
|
|
{/if}
|
2021-04-27 15:26:03 +02:00
|
|
|
<ParameterBuilder bind:parameters={query.parameters} bindable={false} />
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
{#if shouldShowQueryConfig}
|
2021-04-20 12:53:19 +02:00
|
|
|
<Divider />
|
2021-01-12 17:49:11 +01:00
|
|
|
<div class="config">
|
2021-04-30 13:38:06 +02:00
|
|
|
<Heading size="S">Fields</Heading>
|
2021-04-30 13:31:45 +02:00
|
|
|
<Body size="S">Fill in the fields specific to this query.</Body>
|
2021-01-13 15:11:53 +01:00
|
|
|
<IntegrationQueryEditor
|
2021-02-18 19:55:08 +01:00
|
|
|
{datasource}
|
2021-01-13 15:11:53 +01:00
|
|
|
{query}
|
2021-10-12 19:45:13 +02:00
|
|
|
height={200}
|
2021-02-18 19:55:08 +01:00
|
|
|
schema={queryConfig[query.queryVerb]}
|
2021-04-23 11:55:27 +02:00
|
|
|
bind:parameters
|
|
|
|
/>
|
2021-04-20 12:53:19 +02:00
|
|
|
<Divider />
|
2021-04-27 15:26:03 +02:00
|
|
|
</div>
|
2021-10-12 19:45:13 +02:00
|
|
|
<div class="config">
|
2021-10-13 17:42:07 +02:00
|
|
|
<div class="help-heading">
|
|
|
|
<Heading size="S">Transformer</Heading>
|
|
|
|
<Icon
|
|
|
|
on:click={() => window.open(transformerDocs)}
|
|
|
|
hoverable
|
|
|
|
name="Help"
|
|
|
|
size="L"
|
|
|
|
/>
|
|
|
|
</div>
|
2021-10-12 19:45:13 +02:00
|
|
|
<Body size="S"
|
2021-10-13 17:42:07 +02:00
|
|
|
>Add a JavaScript function to transform the query result.</Body
|
2021-10-12 19:45:13 +02:00
|
|
|
>
|
|
|
|
<CodeMirrorEditor
|
|
|
|
height={200}
|
|
|
|
label="Transformer"
|
|
|
|
value={query.transformer}
|
2021-10-13 17:42:07 +02:00
|
|
|
resize="vertical"
|
2021-10-12 19:45:13 +02:00
|
|
|
on:change={e => (query.transformer = e.detail)}
|
|
|
|
/>
|
|
|
|
<Divider />
|
|
|
|
</div>
|
2021-04-27 15:26:03 +02:00
|
|
|
<div class="viewer-controls">
|
2021-04-30 13:38:06 +02:00
|
|
|
<Heading size="S">Results</Heading>
|
2021-04-28 15:46:11 +02:00
|
|
|
<ButtonGroup>
|
2021-06-08 18:14:46 +02:00
|
|
|
<Button cta disabled={queryInvalid} on:click={saveQuery}>
|
2021-04-27 15:26:03 +02:00
|
|
|
Save Query
|
|
|
|
</Button>
|
2021-04-28 15:46:11 +02:00
|
|
|
<Button secondary on:click={previewQuery}>Run Query</Button>
|
|
|
|
</ButtonGroup>
|
2021-04-27 15:26:03 +02:00
|
|
|
</div>
|
2021-04-30 13:31:45 +02:00
|
|
|
<Body size="S">
|
2021-04-28 15:46:11 +02:00
|
|
|
Below, you can preview the results from your query and change the schema.
|
|
|
|
</Body>
|
|
|
|
<section class="viewer">
|
|
|
|
{#if data}
|
|
|
|
<Tabs selected="JSON">
|
|
|
|
<Tab title="JSON">
|
|
|
|
<pre
|
|
|
|
class="preview">
|
2021-02-22 16:53:49 +01:00
|
|
|
<!-- prettier-ignore -->
|
2021-02-22 15:01:02 +01:00
|
|
|
{#if !data[0]}
|
|
|
|
Please run your query to fetch some data.
|
2021-02-18 17:58:10 +01:00
|
|
|
{:else}
|
|
|
|
{JSON.stringify(data[0], undefined, 2)}
|
|
|
|
{/if}
|
2021-04-21 13:41:44 +02:00
|
|
|
</pre>
|
2021-04-28 15:46:11 +02:00
|
|
|
</Tab>
|
|
|
|
<Tab title="Schema">
|
|
|
|
<Layout gap="S">
|
2021-01-12 17:49:11 +01:00
|
|
|
{#each fields as field, idx}
|
|
|
|
<div class="field">
|
2021-04-20 12:53:19 +02:00
|
|
|
<Input placeholder="Field Name" bind:value={field.name} />
|
|
|
|
<Select bind:value={field.type} options={typeOptions} />
|
2021-04-23 11:55:27 +02:00
|
|
|
<Icon name="bleClose" on:click={() => deleteField(idx)} />
|
2021-01-12 17:49:11 +01:00
|
|
|
</div>
|
|
|
|
{/each}
|
2021-04-28 15:46:11 +02:00
|
|
|
<div>
|
|
|
|
<Button secondary on:click={newField}>Add Field</Button>
|
|
|
|
</div>
|
|
|
|
</Layout>
|
|
|
|
</Tab>
|
|
|
|
<Tab title="Preview">
|
|
|
|
<ExternalDataSourceTable {query} {data} />
|
|
|
|
</Tab>
|
|
|
|
</Tabs>
|
|
|
|
{/if}
|
|
|
|
</section>
|
2021-04-27 15:26:03 +02:00
|
|
|
{/if}
|
|
|
|
</Layout>
|
2020-12-18 19:19:43 +01:00
|
|
|
|
|
|
|
<style>
|
2021-04-27 15:26:03 +02:00
|
|
|
.config {
|
|
|
|
display: grid;
|
|
|
|
grid-gap: var(--spacing-s);
|
|
|
|
}
|
2021-10-13 17:42:07 +02:00
|
|
|
|
2021-02-18 17:58:10 +01:00
|
|
|
.config-field {
|
|
|
|
display: grid;
|
|
|
|
grid-template-columns: 20% 1fr;
|
|
|
|
grid-gap: var(--spacing-l);
|
2021-01-22 17:49:22 +01:00
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
2021-10-13 17:42:07 +02:00
|
|
|
.help-heading {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
}
|
|
|
|
|
2020-12-18 19:19:43 +01:00
|
|
|
.field {
|
|
|
|
display: grid;
|
2021-02-18 17:58:10 +01:00
|
|
|
grid-template-columns: 1fr 1fr 5%;
|
2021-01-22 17:49:22 +01:00
|
|
|
gap: var(--spacing-l);
|
2020-12-18 19:19:43 +01:00
|
|
|
}
|
|
|
|
|
2021-02-22 10:01:40 +01:00
|
|
|
.viewer {
|
|
|
|
min-height: 200px;
|
2021-08-23 17:32:27 +02:00
|
|
|
width: 640px;
|
2021-02-22 10:01:40 +01:00
|
|
|
}
|
|
|
|
|
2020-12-18 19:19:43 +01:00
|
|
|
.preview {
|
2021-01-07 14:13:46 +01:00
|
|
|
height: 100%;
|
2021-02-22 10:01:40 +01:00
|
|
|
min-height: 120px;
|
2021-01-07 14:13:46 +01:00
|
|
|
overflow-y: auto;
|
2020-12-18 19:19:43 +01:00
|
|
|
overflow-wrap: break-word;
|
|
|
|
white-space: pre-wrap;
|
2021-04-20 12:53:19 +02:00
|
|
|
background-color: var(--grey-2);
|
2021-02-22 10:01:40 +01:00
|
|
|
padding: var(--spacing-m);
|
|
|
|
border-radius: 8px;
|
2021-04-20 12:53:19 +02:00
|
|
|
color: var(--ink);
|
2020-12-18 19:19:43 +01:00
|
|
|
}
|
2021-01-07 14:13:46 +01:00
|
|
|
|
2021-01-22 17:49:22 +01:00
|
|
|
.viewer-controls {
|
2021-01-12 17:49:11 +01:00
|
|
|
display: flex;
|
2021-01-22 17:49:22 +01:00
|
|
|
flex-direction: row;
|
2021-02-18 19:55:08 +01:00
|
|
|
justify-content: space-between;
|
2021-01-22 17:49:22 +01:00
|
|
|
gap: var(--spacing-m);
|
|
|
|
min-width: 150px;
|
2021-02-19 15:31:07 +01:00
|
|
|
align-items: center;
|
2021-02-05 11:32:10 +01:00
|
|
|
}
|
2020-12-18 19:19:43 +01:00
|
|
|
</style>
|