diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte index 1b9fdcdf108..f5028b1eead 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte @@ -31,6 +31,11 @@ import IntegrationQueryEditor from "@/components/integration/index.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import { findAllComponents } from "@/helpers/components" + import { + extractFields, + extractJSONArrayFields, + extractRelationships, + } from "@/helpers/bindings" import ClientBindingPanel from "@/components/common/bindings/ClientBindingPanel.svelte" import DataSourceCategory from "@/components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte" import { API } from "@/api" @@ -81,67 +86,9 @@ value: `{{ literal ${safe(provider._id)} }}`, type: "provider", })) - $: links = bindings - // Get only link bindings - .filter(x => x.fieldSchema?.type === "link") - // Filter out bindings provided by forms - .filter(x => !x.component?.endsWith("/form")) - .map(binding => { - const { providerId, readableBinding, fieldSchema } = binding || {} - const { name, tableId } = fieldSchema || {} - const safeProviderId = safe(providerId) - return { - 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: `{{ ${safeProviderId}.${safe("_id")} }}`, - rowTableId: `{{ ${safeProviderId}.${safe("tableId")} }}`, - } - }) - $: fields = bindings - .filter( - x => - x.fieldSchema?.type === "attachment" || - (x.fieldSchema?.type === "array" && x.tableId) - ) - .map(binding => { - const { providerId, readableBinding, runtimeBinding } = binding - const { name, type, tableId } = binding.fieldSchema - return { - providerId, - label: readableBinding, - fieldName: name, - fieldType: type, - tableId, - type: "field", - value: `{{ literal ${runtimeBinding} }}`, - } - }) - $: jsonArrays = bindings - .filter( - x => - x.fieldSchema?.type === "jsonarray" || - (x.fieldSchema?.type === "json" && x.fieldSchema?.subtype === "array") - ) - .map(binding => { - const { providerId, readableBinding, runtimeBinding, tableId } = binding - const { name, type, prefixKeys, subtype } = binding.fieldSchema - return { - providerId, - label: readableBinding, - fieldName: name, - fieldType: type, - tableId, - prefixKeys, - type: type === "jsonarray" ? "jsonarray" : "queryarray", - subtype, - value: `{{ literal ${runtimeBinding} }}`, - } - }) + $: links = extractRelationships(bindings) + $: fields = extractFields(bindings) + $: jsonArrays = extractJSONArrayFields(bindings) $: custom = { type: "custom", label: "JSON / CSV", diff --git a/packages/builder/src/helpers/bindings.ts b/packages/builder/src/helpers/bindings.ts new file mode 100644 index 00000000000..66a13d9ba30 --- /dev/null +++ b/packages/builder/src/helpers/bindings.ts @@ -0,0 +1,74 @@ +import { makePropSafe } from "@budibase/string-templates" +import { UIBinding } from "@budibase/types" + +export function extractRelationships(bindings: UIBinding[]) { + return ( + bindings + // Get only link bindings + .filter(x => x.fieldSchema?.type === "link") + // Filter out bindings provided by forms + .filter(x => !x.component?.endsWith("/form")) + .map(binding => { + const { providerId, readableBinding, fieldSchema } = binding || {} + const { name, tableId } = fieldSchema || {} + const safeProviderId = makePropSafe(providerId) + return { + 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: `{{ ${safeProviderId}.${makePropSafe("_id")} }}`, + rowTableId: `{{ ${safeProviderId}.${makePropSafe("tableId")} }}`, + } + }) + ) +} + +export function extractFields(bindings: UIBinding[]) { + return bindings + .filter( + x => + x.fieldSchema?.type === "attachment" || + (x.fieldSchema?.type === "array" && x.tableId) + ) + .map(binding => { + const { providerId, readableBinding, runtimeBinding } = binding + const { name, type, tableId } = binding.fieldSchema! + return { + providerId, + label: readableBinding, + fieldName: name, + fieldType: type, + tableId, + type: "field", + value: `{{ literal ${runtimeBinding} }}`, + } + }) +} + +export function extractJSONArrayFields(bindings: UIBinding[]) { + return bindings + .filter( + x => + x.fieldSchema?.type === "jsonarray" || + (x.fieldSchema?.type === "json" && x.fieldSchema?.subtype === "array") + ) + .map(binding => { + const { providerId, readableBinding, runtimeBinding, tableId } = binding + const { name, type, prefixKeys, subtype } = binding.fieldSchema! + return { + providerId, + label: readableBinding, + fieldName: name, + fieldType: type, + tableId, + prefixKeys, + type: type === "jsonarray" ? "jsonarray" : "queryarray", + subtype, + value: `{{ literal ${runtimeBinding} }}`, + } + }) +} diff --git a/packages/builder/src/helpers/index.ts b/packages/builder/src/helpers/index.ts index 81afc696a30..0e61eeb9c67 100644 --- a/packages/builder/src/helpers/index.ts +++ b/packages/builder/src/helpers/index.ts @@ -10,3 +10,4 @@ export { isBuilderInputFocused, } from "./helpers" export * as featureFlag from "./featureFlags" +export * as bindings from "./bindings" diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index d8169fdedb0..3afb96994ff 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -6,12 +6,13 @@ import { findComponentsBySettingsType } from "@/helpers/screen" import { UIDatasourceType, Screen } from "@budibase/types" import { queries } from "./queries" import { views } from "./views" -import { featureFlag } from "@/helpers" +import { bindings, featureFlag } from "@/helpers" +import { getBindableProperties } from "@/dataBinding" function reduceBy( key: TKey, list: TItem[] -) { +): Record { return list.reduce( (result, item) => ({ ...result, @@ -31,6 +32,7 @@ const validationKeyByType: Record = { viewV2: "id", query: "_id", custom: null, + link: "rowId", } export const screenComponentErrors = derived( @@ -59,8 +61,21 @@ export const screenComponentErrors = derived( if (!validationKey) { continue } + + const componentBindings = getBindableProperties( + $selectedScreen, + component._id + ) + + const componentDatasources = { + ...reduceBy( + "rowId", + bindings.extractRelationships(componentBindings) + ), + } + const resourceId = componentSettings[validationKey] - if (!datasources[resourceId]) { + if (!{ ...datasources, ...componentDatasources }[resourceId]) { const friendlyTypeName = friendlyNameByType[type] ?? type result[component._id!] = [ `The ${friendlyTypeName} named "${label}" could not be found`, diff --git a/packages/types/src/ui/bindings/binding.ts b/packages/types/src/ui/bindings/binding.ts index 2cfb23ed2d1..a770a25a3ee 100644 --- a/packages/types/src/ui/bindings/binding.ts +++ b/packages/types/src/ui/bindings/binding.ts @@ -24,3 +24,18 @@ export type InsertAtPositionFn = (_: { value: string cursor?: { anchor: number } }) => void + +export interface UIBinding { + tableId?: string + fieldSchema?: { + name: string + tableId: string + type: string + subtype?: string + prefixKeys?: string + } + component?: string + providerId: string + readableBinding?: string + runtimeBinding?: string +} diff --git a/packages/types/src/ui/datasource.ts b/packages/types/src/ui/datasource.ts index 53740e8c4d2..a121d929c81 100644 --- a/packages/types/src/ui/datasource.ts +++ b/packages/types/src/ui/datasource.ts @@ -1 +1,7 @@ -export type UIDatasourceType = "table" | "view" | "viewV2" | "query" | "custom" +export type UIDatasourceType = + | "table" + | "view" + | "viewV2" + | "query" + | "custom" + | "link"