From e0c88597a790c4aa0f38991d9456794a15401172 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 12 Nov 2021 13:42:55 +0000 Subject: [PATCH] Enable data providers to use array and attachment fields as their source --- .../builder/src/builderStore/dataBinding.js | 27 ++- .../DataProviderSelect.svelte | 5 +- .../PropertyControls/DataSourceSelect.svelte | 181 +++++++++++------- packages/client/src/api/datasources.js | 20 ++ .../src/components/app/DataProvider.svelte | 11 +- 5 files changed, 164 insertions(+), 80 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 9cf00be3d4..d4d535a532 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -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] } /** diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte index d27a542f47..979443a403 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte @@ -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(() => { diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte index d86f13e100..f12710e70d 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte @@ -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) + }
@@ -127,11 +146,10 @@ - {#if value.parameters.length > 0} + {#if getQueryParams(value._id).length > 0} 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} /> @@ -159,52 +175,71 @@
  • handleSelected(table)}>{table.label}
  • {/each} - -
    - Views -
    -
      - {#each views as view} -
    • handleSelected(view)}>{view.label}
    • - {/each} -
    - -
    - Relationships -
    -
      - {#each links as link} -
    • handleSelected(link)}>{link.label}
    • - {/each} -
    - -
    - Queries -
    -
      - {#each queries as query} -
    • handleSelected(query)} - > - {query.label} -
    • - {/each} -
    - -
    - Data Providers -
    -
      - {#each dataProviders as provider} -
    • handleSelected(provider)} - > - {provider.label} -
    • - {/each} -
    + {#if views?.length} + +
    + Views +
    +
      + {#each views as view} +
    • handleSelected(view)}>{view.label}
    • + {/each} +
    + {/if} + {#if queries?.length} + +
    + Queries +
    +
      + {#each queries as query} +
    • handleSelected(query)} + > + {query.label} +
    • + {/each} +
    + {/if} + {#if links?.length} + +
    + Relationships +
    +
      + {#each links as link} +
    • handleSelected(link)}>{link.label}
    • + {/each} +
    + {/if} + {#if fields?.length} + +
    + Fields +
    +
      + {#each fields as field} +
    • handleSelected(field)}>{field.label}
    • + {/each} +
    + {/if} + {#if dataProviders?.length} + +
    + Data Providers +
    +
      + {#each dataProviders as provider} +
    • handleSelected(provider)} + > + {provider.label} +
    • + {/each} +
    + {/if} {#if otherSources?.length}
    diff --git a/packages/client/src/api/datasources.js b/packages/client/src/api/datasources.js index d2b05899cb..cea4ae0f3b 100644 --- a/packages/client/src/api/datasources.js +++ b/packages/client/src/api/datasources.js @@ -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") && diff --git a/packages/client/src/components/app/DataProvider.svelte b/packages/client/src/components/app/DataProvider.svelte index a8e53f4906..0b9cbac4dd 100644 --- a/packages/client/src/components/app/DataProvider.svelte +++ b/packages/client/src/components/app/DataProvider.svelte @@ -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