custom fields in queries

This commit is contained in:
Martin McKeaveney 2021-01-13 14:11:53 +00:00
parent 7a1212ff36
commit 45a02507f7
10 changed files with 223 additions and 79 deletions

View File

@ -0,0 +1,69 @@
<script>
import {
Button,
TextArea,
Label,
Input,
Heading,
Spacer,
Select
} from "@budibase/bbui"
export let fields = {}
export let schema
let customSchema = {}
let draftField = {}
function addField() {
// Add the new field to custom fields for the query
customSchema[draftField.name] = {
type: draftField.type
}
// reset the draft field
draftField = {}
}
</script>
<form on:submit|preventDefault>
{#each Object.keys(schema.fields) as field}
<Label extraSmall grey>{field}</Label>
<Input
type={schema.fields[field]?.type}
required={schema.fields[field]?.required}
bind:value={fields[field]} />
<Spacer medium />
{/each}
{#if schema.customisable}
<Label>Add Custom Field</Label>
{#each Object.keys(customSchema) as field}
<Label extraSmall grey>{field}</Label>
<Input
thin
type={customSchema[field]?.type}
bind:value={fields[field]}
/>
<Spacer medium />
{/each}
<div class="new-field">
<Label extraSmall grey>Name</Label>
<Label extraSmall grey>Type</Label>
<Input thin bind:value={draftField.name} />
<Select thin secondary bind:value={draftField.name}>
<option value={"text"}>String</option>
<option value={"number"}>Number</option>
</Select>
</div>
<Button small thin primary on:click={addField}>Add Field</Button>
{/if}
</form>
<style>
.new-field {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: var(--spacing-m);
margin-top: var(--spacing-m);
margin-bottom: var(--spacing-m);
}
</style>

View File

@ -37,7 +37,7 @@
export let query
export let fields = []
let config = {}
let config
let tab = "JSON"
let parameters
let data
@ -60,6 +60,8 @@
$: datasourceType = datasource.source
$: datasourceType && fetchQueryConfig()
$: shouldShowQueryConfig = config && query.queryVerb && query.queryType
function newField() {
fields = [...fields, {}]
}
@ -75,8 +77,7 @@
const json = await response.json()
config = json.query
} catch (err) {
// TODO: Error fetching integration config
// notifier.danger()
notifier.danger("Error fetching integration configuration.")
console.error(err)
}
}
@ -84,6 +85,7 @@
async function previewQuery() {
try {
const response = await api.post(`/api/queries/preview`, {
fields: query.fields,
queryVerb: query.queryVerb,
parameters: query.parameters.reduce(
(acc, next) => ({
@ -93,7 +95,6 @@
{}
),
datasourceId: datasource._id,
query: query.queryString,
})
const json = await response.json()
@ -107,6 +108,7 @@
name: field,
type: "STRING",
}))
notifier.success("Query executed successfully.")
} catch (err) {
notifier.danger(`Query Error: ${err.message}`)
console.error(err)
@ -138,17 +140,19 @@
</div>
{/each}
</div>
<Select thin secondary bind:value={query.queryType}>
<option value={''}>Select an option</option>
{#each Object.keys(config) as queryType}
<option value={config[queryType].type}>{queryType}</option>
{/each}
</Select>
{#if config && query.queryVerb}
<Select thin secondary bind:value={query.queryType}>
<option value={''}>Select an option</option>
{#each Object.keys(config[query.queryVerb]) as queryType}
<option value={queryType}>{queryType}</option>
{/each}
</Select>
{/if}
</header>
<Spacer large />
{#if query.queryVerb && query.queryType}
{#if shouldShowQueryConfig}
<section>
<div class="config">
<Label extraSmall grey>Query Name</Label>
@ -156,7 +160,10 @@
<Spacer medium />
<IntegrationQueryEditor {query} bind:parameters />
<IntegrationQueryEditor
schema={config[query.queryVerb][query.queryType]}
{query}
bind:parameters />
<Spacer medium />

View File

@ -2,6 +2,7 @@
import { TextArea, Label, Input, Heading, Spacer } from "@budibase/bbui"
import Editor from "./SvelteEditor.svelte"
import ParameterBuilder from "./QueryParameterBuilder.svelte"
import FieldsBuilder from "./QueryFieldsBuilder.svelte"
const QueryTypes = {
SQL: "sql",
@ -10,9 +11,10 @@
}
export let query
export let schema
function updateQuery({ detail }) {
query.queryString = detail.value
query.fields.sql = detail.value
}
</script>
@ -22,20 +24,19 @@
<Heading extraSmall black>Query</Heading>
<Spacer large />
{#if query.queryType === QueryTypes.SQL}
<!-- <TextArea bind:value={query.queryString} /> -->
{#if schema.type === QueryTypes.SQL}
<Editor
label="Query"
mode="sql"
on:change={updateQuery}
value={query.queryString} />
{:else if query.queryType === QueryTypes.JSON}
value={query.fields.sql} />
{:else if schema.type === QueryTypes.JSON}
<Spacer large />
<Editor
label="Query"
mode="json"
on:change={updateQuery}
value={query.queryString} />
{:else if query.queryType === QueryTypes.FIELDS}
<!-- {#each Object.keys()} -->
value={query.fields.json} />
{:else if schema.type === QueryTypes.FIELDS}
<FieldsBuilder bind:fields={query.fields} {schema} />
{/if}

View File

@ -57,7 +57,9 @@
bind:customParams={parameters.queryParams}
parameters={query.parameters}
bindings={bindableProperties} />
<pre>{query.queryString}</pre>
{#if query.fields.sql}
<pre>{query.fields.queryString}</pre>
{/if}
{/if}
</div>

View File

@ -26,6 +26,7 @@
datasourceId: $params.selectedDatasource,
name: "New Query",
parameters: [],
fields: {},
}
}
}

View File

@ -29,18 +29,20 @@ exports.save = async function(ctx) {
ctx.message = `Query ${query.name} saved successfully.`
}
exports.preview = async function(ctx) {
const { query, datasourceId, parameters, queryVerb } = ctx.request.body
let parsedQuery = ""
if (query) {
const queryTemplate = handlebars.compile(query)
parsedQuery = queryTemplate(parameters)
function enrichQueryFields(fields, parameters) {
const enrichedQuery = {}
// enrich the fields with dynamic parameters
for (let key in fields) {
const template = handlebars.compile(fields[key])
enrichedQuery[key] = template(parameters)
}
return enrichedQuery
}
exports.preview = async function(ctx) {
const db = new CouchDB(ctx.user.appId)
const datasource = await db.get(datasourceId)
const datasource = await db.get(ctx.request.body.datasourceId)
const Integration = integrations[datasource.source]
@ -49,7 +51,11 @@ exports.preview = async function(ctx) {
return
}
ctx.body = await new Integration(datasource.config, parsedQuery)[queryVerb]()
const { fields, parameters, queryVerb } = ctx.request.body
const enrichedQuery = enrichQueryFields(fields, parameters)
ctx.body = await new Integration(datasource.config)[queryVerb](enrichedQuery)
}
exports.execute = async function(ctx) {
@ -58,10 +64,6 @@ exports.execute = async function(ctx) {
const query = await db.get(ctx.params.queryId)
const datasource = await db.get(query.datasourceId)
const queryTemplate = handlebars.compile(query.queryString)
const parsedQuery = queryTemplate(ctx.request.body.parameters)
const Integration = integrations[datasource.source]
if (!Integration) {
@ -69,10 +71,15 @@ exports.execute = async function(ctx) {
return
}
const enrichedQuery = enrichQueryFields(
query.fields,
ctx.request.body.parameters
)
// call the relevant CRUD method on the integration class
const response = await new Integration(datasource.config, parsedQuery)[
query.queryVerb
]()
const response = await new Integration(datasource.config)[query.queryVerb](
enrichedQuery
)
ctx.body = response
}

View File

@ -24,7 +24,7 @@ function generateQueryValidation() {
_id: Joi.string(),
_rev: Joi.string(),
name: Joi.string().required(),
queryString: Joi.string().required(),
fields: Joi.object().required(),
datasourceId: Joi.string().required(),
parameters: Joi.array().items(Joi.object({
name: Joi.string(),
@ -39,7 +39,7 @@ function generateQueryValidation() {
function generateQueryPreviewValidation() {
// prettier-ignore
return joiValidator.body(Joi.object({
query: Joi.string(),
fields: Joi.object().required(),
queryVerb: Joi.string().allow(...Object.values(QueryVerb)).required(),
datasourceId: Joi.string().required(),
parameters: Joi.object({}).required().unknown(true)

View File

@ -0,0 +1,7 @@
class Field {
constructor(type, defaultValue, required) {
this.type = type
this.default = defaultValue
this.required = required
}
}

View File

@ -12,19 +12,51 @@ const SCHEMA = {
default: "mybase",
required: true,
},
table: {
type: "string",
default: "mytable",
required: true,
},
},
query: {
Custom: {
type: "fields",
custom: true,
create: {
"Airtable Record": {
type: "fields",
customisable: true,
fields: {
table: {
type: "string",
required: true,
},
},
},
},
"Airtable Ids": {
type: "list",
read: {
Table: {
type: "fields",
fields: {
table: {
type: "string",
required: true,
},
view: {
type: "string",
required: true,
},
},
},
},
update: {
Fields: {
type: "fields",
customisable: true,
fields: {
id: {
type: "string",
required: true,
},
},
},
},
delete: {
"Airtable Ids": {
type: "list",
},
},
},
}
@ -35,11 +67,13 @@ class AirtableIntegration {
this.client = new Airtable(config).base(config.base)
}
async create(record) {
async create(query) {
const { table, ...rest } = query
try {
const records = await this.client(this.config.table).create([
const records = await this.client(table).create([
{
fields: record,
fields: rest,
},
])
return records
@ -49,18 +83,23 @@ class AirtableIntegration {
}
}
async read() {
const records = await this.client(this.config.table)
.select({ maxRecords: this.query.records, view: this.query.view })
.firstPage()
return records.map(({ fields }) => fields)
async read(query) {
try {
const records = await this.client(query.table)
.select({ maxRecords: 10, view: query.view })
.firstPage()
return records.map(({ fields }) => fields)
} catch (err) {
console.error("Error writing to airtable", err)
return []
}
}
async update(document) {
const { id, ...rest } = document
async update(query) {
const { table, id, ...rest } = query
try {
const records = await this.client(this.config.table).update([
const records = await this.client(table).update([
{
id,
fields: rest,
@ -73,9 +112,9 @@ class AirtableIntegration {
}
}
async delete(id) {
async delete(query) {
try {
const records = await this.client(this.config.table).destroy([id])
const records = await this.client(query.table).destroy(query.ids)
return records
} catch (err) {
console.error("Error writing to airtable", err)

View File

@ -29,46 +29,57 @@ const SCHEMA = {
},
},
query: {
SQL: {
type: "sql",
create: {
SQL: {
type: "sql",
},
},
read: {
SQL: {
type: "sql",
},
},
update: {
SQL: {
type: "sql",
},
},
delete: {
SQL: {
type: "sql",
},
},
},
}
class PostgresIntegration {
constructor(config, query) {
constructor(config) {
this.config = config
this.queryString = this.buildQuery(query)
this.client = new Client(config)
this.connect()
}
buildQuery(query) {
// TODO: account for different types
return query
}
async connect() {
return this.client.connect()
}
async create() {
const response = await this.client.query(this.queryString)
async create({ sql }) {
const response = await this.client.query(sql)
return response.rows
}
async read() {
const response = await this.client.query(this.queryString)
async read({ sql }) {
const response = await this.client.query(sql)
return response.rows
}
async update() {
const response = await this.client.query(this.queryString)
async update({ sql }) {
const response = await this.client.query(sql)
return response.rows
}
async delete() {
const response = await this.client.query(this.queryString)
async delete({ sql }) {
const response = await this.client.query(sql)
return response.rows
}
}