custom fields in queries
This commit is contained in:
parent
7a1212ff36
commit
45a02507f7
|
@ -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>
|
|
@ -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 />
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
datasourceId: $params.selectedDatasource,
|
||||
name: "New Query",
|
||||
parameters: [],
|
||||
fields: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
class Field {
|
||||
constructor(type, defaultValue, required) {
|
||||
this.type = type
|
||||
this.default = defaultValue
|
||||
this.required = required
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue