Enable data providers to use array and attachment fields as their source

This commit is contained in:
Andrew Kingston 2021-11-12 13:42:55 +00:00
parent 5bb7ed004c
commit 3db35d3af9
5 changed files with 164 additions and 80 deletions

View File

@ -333,8 +333,11 @@ const getUrlBindings = asset => {
*/
export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
let schema, table
if (datasource) {
const { type } = datasource
// Determine the source table from the datasource type
if (type === "provider") {
const component = findComponent(asset.props, datasource.providerId)
const source = getDatasourceForProvider(asset, component)
@ -342,11 +345,31 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
} else if (type === "query") {
const queries = get(queriesStores).list
table = queries.find(query => query._id === datasource._id)
} else if (type === "field") {
const { fieldType } = datasource
if (fieldType === "attachment") {
schema = {
url: {
type: "string",
},
name: {
type: "string",
},
}
} else if (fieldType === "array") {
schema = {
value: {
type: "string",
},
}
}
} else {
const tables = get(tablesStore).list
table = tables.find(table => table._id === datasource.tableId)
}
if (table) {
// Determine the schema from the table if not already determined
if (table && !schema) {
if (type === "view") {
schema = cloneDeep(table.views?.[datasource.name]?.schema)
} else if (type === "query" && isForm) {
@ -525,7 +548,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
* {{ literal [componentId] }}
*/
function extractLiteralHandlebarsID(value) {
return value?.match(/{{\s*literal[\s[]+([a-fA-F0-9]+)[\s\]]*}}/)?.[1]
return value?.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
}
/**

View File

@ -11,10 +11,7 @@
const getValue = component => `{{ literal ${makePropSafe(component._id)} }}`
$: path = findComponentPath($currentAsset.props, $store.selectedComponentId)
$: providers = path.filter(
component =>
component._component === "@budibase/standard-components/dataprovider"
)
$: providers = path.filter(c => c._component?.endsWith("/dataprovider"))
// Set initial value to closest data provider
onMount(() => {

View File

@ -20,16 +20,18 @@
import { notifications } from "@budibase/bbui"
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
const dispatch = createEventDispatcher()
let anchorRight, dropdownRight
let drawer
import { makePropSafe as safe } from "@budibase/string-templates"
export let value = {}
export let otherSources
export let showAllQueries
export let bindings = []
const dispatch = createEventDispatcher()
const arrayTypes = ["attachment", "array"]
let anchorRight, dropdownRight
let drawer
$: text = value?.label ?? "Choose an option"
$: tables = $tablesStore.list.map(m => ({
label: m.name,
@ -54,8 +56,6 @@
name: query.name,
tableId: query._id,
...query,
schema: query.schema,
parameters: query.parameters,
type: "query",
}))
$: dataProviders = getDataProviderComponents(
@ -65,29 +65,40 @@
label: provider._instanceName,
name: provider._instanceName,
providerId: provider._id,
value: `{{ literal [${provider._id}] }}`,
value: `{{ literal ${safe(provider._id)} }}`,
type: "provider",
schema: provider.schema,
}))
$: queryBindableProperties = bindings.map(property => ({
...property,
category: property.type === "instance" ? "Component" : "Table",
label: property.readableBinding,
path: property.readableBinding,
}))
$: links = bindings
.filter(x => x.fieldSchema?.type === "link")
.map(property => {
.map(binding => {
const { providerId, readableBinding, fieldSchema } = binding || {}
const { name, tableId } = fieldSchema || {}
const safeProviderId = safe(providerId)
return {
providerId: property.providerId,
label: property.readableBinding,
fieldName: property.fieldSchema.name,
tableId: property.fieldSchema.tableId,
providerId,
label: readableBinding,
fieldName: name,
tableId,
type: "link",
// These properties will be enriched by the client library and provide
// details of the parent row of the relationship field, from context
rowId: `{{ ${property.providerId}._id }}`,
rowTableId: `{{ ${property.providerId}.tableId }}`,
rowId: `{{ ${safeProviderId}.${safe("_id")} }}`,
rowTableId: `{{ ${safeProviderId}.${safe("tableId")} }}`,
}
})
$: fields = bindings
.filter(x => arrayTypes.includes(x.fieldSchema?.type))
.map(binding => {
const { providerId, readableBinding, fieldSchema } = binding || {}
const { name, type, tableId } = fieldSchema || {}
return {
providerId,
label: readableBinding,
fieldName: name,
fieldType: type,
tableId,
type: "field",
value: `{{ literal ${safe(providerId)}.${safe(name)} }}`,
}
})
@ -102,6 +113,14 @@
).source
return $integrations[source].query[query.queryVerb]
}
const getQueryParams = query => {
return $queriesStore.list.find(q => q._id === query?._id)?.parameters || []
}
const getQueryDatasource = query => {
return $datasources.list.find(ds => ds._id === query?.datasourceId)
}
</script>
<div class="container" bind:this={anchorRight}>
@ -127,11 +146,10 @@
</Button>
<DrawerContent slot="body">
<Layout noPadding>
{#if value.parameters.length > 0}
{#if getQueryParams(value._id).length > 0}
<ParameterBuilder
bind:customParams={value.queryParams}
parameters={queries.find(query => query._id === value._id)
.parameters}
parameters={getQueryParams(value)}
{bindings}
/>
{/if}
@ -139,9 +157,7 @@
height={200}
query={value}
schema={fetchQueryDefinition(value)}
datasource={$datasources.list.find(
ds => ds._id === value.datasourceId
)}
datasource={getQueryDatasource(value)}
editable={false}
/>
</Layout>
@ -159,52 +175,71 @@
<li on:click={() => handleSelected(table)}>{table.label}</li>
{/each}
</ul>
<Divider size="S" />
<div class="title">
<Heading size="XS">Views</Heading>
</div>
<ul>
{#each views as view}
<li on:click={() => handleSelected(view)}>{view.label}</li>
{/each}
</ul>
<Divider size="S" />
<div class="title">
<Heading size="XS">Relationships</Heading>
</div>
<ul>
{#each links as link}
<li on:click={() => handleSelected(link)}>{link.label}</li>
{/each}
</ul>
<Divider size="S" />
<div class="title">
<Heading size="XS">Queries</Heading>
</div>
<ul>
{#each queries as query}
<li
class:selected={value === query}
on:click={() => handleSelected(query)}
>
{query.label}
</li>
{/each}
</ul>
<Divider size="S" />
<div class="title">
<Heading size="XS">Data Providers</Heading>
</div>
<ul>
{#each dataProviders as provider}
<li
class:selected={value === provider}
on:click={() => handleSelected(provider)}
>
{provider.label}
</li>
{/each}
</ul>
{#if views?.length}
<Divider size="S" />
<div class="title">
<Heading size="XS">Views</Heading>
</div>
<ul>
{#each views as view}
<li on:click={() => handleSelected(view)}>{view.label}</li>
{/each}
</ul>
{/if}
{#if queries?.length}
<Divider size="S" />
<div class="title">
<Heading size="XS">Queries</Heading>
</div>
<ul>
{#each queries as query}
<li
class:selected={value === query}
on:click={() => handleSelected(query)}
>
{query.label}
</li>
{/each}
</ul>
{/if}
{#if links?.length}
<Divider size="S" />
<div class="title">
<Heading size="XS">Relationships</Heading>
</div>
<ul>
{#each links as link}
<li on:click={() => handleSelected(link)}>{link.label}</li>
{/each}
</ul>
{/if}
{#if fields?.length}
<Divider size="S" />
<div class="title">
<Heading size="XS">Fields</Heading>
</div>
<ul>
{#each fields as field}
<li on:click={() => handleSelected(field)}>{field.label}</li>
{/each}
</ul>
{/if}
{#if dataProviders?.length}
<Divider size="S" />
<div class="title">
<Heading size="XS">Data Providers</Heading>
</div>
<ul>
{#each dataProviders as provider}
<li
class:selected={value === provider}
on:click={() => handleSelected(provider)}
>
{provider.label}
</li>
{/each}
</ul>
{/if}
{#if otherSources?.length}
<Divider size="S" />
<div class="title">

View File

@ -55,6 +55,26 @@ export const fetchDatasourceSchema = async dataSource => {
return dataSource.value?.schema
}
// Field sources will have their schema statically defined by the builder
if (type === "field") {
if (dataSource.fieldType === "attachment") {
return {
url: {
type: "string",
},
name: {
type: "string",
},
}
} else if (dataSource.fieldType === "array") {
return {
value: {
type: "string",
},
}
}
}
// Tables, views and links can be fetched by table ID
if (
(type === "table" || type === "view" || type === "link") &&

View File

@ -183,7 +183,16 @@
} else if (dataSource?.type === "provider") {
// For providers referencing another provider, just use the rows it
// provides
allRows = dataSource?.value?.rows ?? []
allRows = dataSource?.value?.rows || []
} else if (dataSource?.type === "field") {
// Field sources will be available from context.
// Enrich non object elements into object to ensure a valid schema.
const data = dataSource?.value || []
if (data[0] && typeof data[0] !== "object") {
allRows = data.map(value => ({ value }))
} else {
allRows = data
}
} else {
// For other data sources like queries or views, fetch all rows from the
// server