From b8d38159d09659b4b7d04077385e1cbb4832b1ce Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 24 Jan 2025 14:48:27 +0100 Subject: [PATCH 1/8] Extract binding utils --- .../DataSourceSelect/DataSourceSelect.svelte | 43 ++-------------- packages/builder/src/helpers/bindings.ts | 50 +++++++++++++++++++ packages/builder/src/helpers/index.ts | 1 + packages/types/src/ui/bindings/binding.ts | 13 +++++ 4 files changed, 67 insertions(+), 40 deletions(-) create mode 100644 packages/builder/src/helpers/bindings.ts 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 1b9fdcdf10..6ccde7fede 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,7 @@ import IntegrationQueryEditor from "@/components/integration/index.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import { findAllComponents } from "@/helpers/components" + import { extractFields, 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,46 +82,8 @@ 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} }}`, - } - }) + $: links = extractRelationships(bindings) + $: fields = extractFields(bindings) $: jsonArrays = bindings .filter( x => diff --git a/packages/builder/src/helpers/bindings.ts b/packages/builder/src/helpers/bindings.ts new file mode 100644 index 0000000000..2d1410c76a --- /dev/null +++ b/packages/builder/src/helpers/bindings.ts @@ -0,0 +1,50 @@ +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} }}`, + } + }) +} diff --git a/packages/builder/src/helpers/index.ts b/packages/builder/src/helpers/index.ts index 81afc696a3..0e61eeb9c6 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/types/src/ui/bindings/binding.ts b/packages/types/src/ui/bindings/binding.ts index 2cfb23ed2d..10e6968409 100644 --- a/packages/types/src/ui/bindings/binding.ts +++ b/packages/types/src/ui/bindings/binding.ts @@ -24,3 +24,16 @@ export type InsertAtPositionFn = (_: { value: string cursor?: { anchor: number } }) => void + +export interface UIBinding { + tableId?: string + fieldSchema?: { + name: string + tableId: string + type: string + } + component?: string + providerId: string + readableBinding?: string + runtimeBinding?: string +} From 5f3aaf458b593f0ecbab53553713249b4e60f7fc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 24 Jan 2025 14:50:06 +0100 Subject: [PATCH 2/8] Extract binding utils --- .../DataSourceSelect/DataSourceSelect.svelte | 28 ++++--------------- packages/builder/src/helpers/bindings.ts | 24 ++++++++++++++++ packages/types/src/ui/bindings/binding.ts | 2 ++ 3 files changed, 32 insertions(+), 22 deletions(-) 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 6ccde7fede..f5028b1eea 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceSelect.svelte @@ -31,7 +31,11 @@ import IntegrationQueryEditor from "@/components/integration/index.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import { findAllComponents } from "@/helpers/components" - import { extractFields, extractRelationships } from "@/helpers/bindings" + 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" @@ -84,27 +88,7 @@ })) $: links = extractRelationships(bindings) $: fields = extractFields(bindings) - $: 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} }}`, - } - }) + $: 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 index 2d1410c76a..66a13d9ba3 100644 --- a/packages/builder/src/helpers/bindings.ts +++ b/packages/builder/src/helpers/bindings.ts @@ -48,3 +48,27 @@ export function extractFields(bindings: UIBinding[]) { } }) } + +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/types/src/ui/bindings/binding.ts b/packages/types/src/ui/bindings/binding.ts index 10e6968409..a770a25a3e 100644 --- a/packages/types/src/ui/bindings/binding.ts +++ b/packages/types/src/ui/bindings/binding.ts @@ -31,6 +31,8 @@ export interface UIBinding { name: string tableId: string type: string + subtype?: string + prefixKeys?: string } component?: string providerId: string From 08c4cfcec0a4c5ce3691b5dc87b243048d6a65e4 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 10:21:31 +0100 Subject: [PATCH 3/8] Validate links --- .../src/stores/builder/screenComponent.ts | 32 +++++++++++++++---- packages/types/src/ui/datasource.ts | 8 ++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index d8169fdedb..aa6a854f77 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -6,7 +6,8 @@ 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 { screenComponentBindableProperties } from "./bindings" function reduceBy( key: TKey, @@ -31,14 +32,26 @@ const validationKeyByType: Record = { viewV2: "id", query: "_id", custom: null, + link: "rowId", } export const screenComponentErrors = derived( - [selectedScreen, tables, views, viewsV2, queries], - ([$selectedScreen, $tables, $views, $viewsV2, $queries]): Record< - string, - string[] - > => { + [ + selectedScreen, + tables, + views, + viewsV2, + queries, + screenComponentBindableProperties, + ], + ([ + $selectedScreen, + $tables, + $views, + $viewsV2, + $queries, + $screenComponentBindableProperties, + ]): Record => { if (!featureFlag.isEnabled("CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS")) { return {} } @@ -56,6 +69,9 @@ export const screenComponentErrors = derived( const type = componentSettings.type as UIDatasourceType const validationKey = validationKeyByType[type] + if (type === "link") { + debugger + } if (!validationKey) { continue } @@ -76,6 +92,10 @@ export const screenComponentErrors = derived( ...reduceBy("name", $views.list), ...reduceBy("id", $viewsV2.list), ...reduceBy("_id", $queries.list), + ...reduceBy( + "rowId", + bindings.extractRelationships($screenComponentBindableProperties) + ), } return getInvalidDatasources($selectedScreen, datasources) diff --git a/packages/types/src/ui/datasource.ts b/packages/types/src/ui/datasource.ts index 53740e8c4d..a121d929c8 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" From 52330a30b84e8c231e92d03a020f05f5fc984eb9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 10:23:26 +0100 Subject: [PATCH 4/8] Remove debugger --- packages/builder/src/stores/builder/screenComponent.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index aa6a854f77..1af10e8a80 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -69,9 +69,6 @@ export const screenComponentErrors = derived( const type = componentSettings.type as UIDatasourceType const validationKey = validationKeyByType[type] - if (type === "link") { - debugger - } if (!validationKey) { continue } From 92606c6129458bd499320d6994b7ddae7f72cabc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 10:28:54 +0100 Subject: [PATCH 5/8] Validate in all screen --- .../src/stores/builder/screenComponent.ts | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 1af10e8a80..3afb96994f 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -7,12 +7,12 @@ import { UIDatasourceType, Screen } from "@budibase/types" import { queries } from "./queries" import { views } from "./views" import { bindings, featureFlag } from "@/helpers" -import { screenComponentBindableProperties } from "./bindings" +import { getBindableProperties } from "@/dataBinding" function reduceBy( key: TKey, list: TItem[] -) { +): Record { return list.reduce( (result, item) => ({ ...result, @@ -36,22 +36,11 @@ const validationKeyByType: Record = { } export const screenComponentErrors = derived( - [ - selectedScreen, - tables, - views, - viewsV2, - queries, - screenComponentBindableProperties, - ], - ([ - $selectedScreen, - $tables, - $views, - $viewsV2, - $queries, - $screenComponentBindableProperties, - ]): Record => { + [selectedScreen, tables, views, viewsV2, queries], + ([$selectedScreen, $tables, $views, $viewsV2, $queries]): Record< + string, + string[] + > => { if (!featureFlag.isEnabled("CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS")) { return {} } @@ -72,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`, @@ -89,10 +91,6 @@ export const screenComponentErrors = derived( ...reduceBy("name", $views.list), ...reduceBy("id", $viewsV2.list), ...reduceBy("_id", $queries.list), - ...reduceBy( - "rowId", - bindings.extractRelationships($screenComponentBindableProperties) - ), } return getInvalidDatasources($selectedScreen, datasources) From f666b35bd181a9aa8f311f58e236344f3f91caef Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 11:48:00 +0100 Subject: [PATCH 6/8] Validate by field --- packages/builder/src/stores/builder/screenComponent.ts | 2 ++ packages/types/src/ui/datasource.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 3afb96994f..aecc27c4e5 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -33,6 +33,7 @@ const validationKeyByType: Record = { query: "_id", custom: null, link: "rowId", + field: "label", } export const screenComponentErrors = derived( @@ -72,6 +73,7 @@ export const screenComponentErrors = derived( "rowId", bindings.extractRelationships(componentBindings) ), + ...reduceBy("label", bindings.extractFields(componentBindings)), } const resourceId = componentSettings[validationKey] diff --git a/packages/types/src/ui/datasource.ts b/packages/types/src/ui/datasource.ts index a121d929c8..c6b1ed01d1 100644 --- a/packages/types/src/ui/datasource.ts +++ b/packages/types/src/ui/datasource.ts @@ -5,3 +5,4 @@ export type UIDatasourceType = | "query" | "custom" | "link" + | "field" From 1557b026826911ba2e87ec978698bbb04d91f5db Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 12:17:25 +0100 Subject: [PATCH 7/8] Use proper field --- packages/builder/src/stores/builder/screenComponent.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index aecc27c4e5..1c1070ad17 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -33,7 +33,7 @@ const validationKeyByType: Record = { query: "_id", custom: null, link: "rowId", - field: "label", + field: "value", } export const screenComponentErrors = derived( @@ -73,7 +73,7 @@ export const screenComponentErrors = derived( "rowId", bindings.extractRelationships(componentBindings) ), - ...reduceBy("label", bindings.extractFields(componentBindings)), + ...reduceBy("value", bindings.extractFields(componentBindings)), } const resourceId = componentSettings[validationKey] From f9dadf83a2914da94ea793a9fadf09b056d894e0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 27 Jan 2025 12:16:07 +0100 Subject: [PATCH 8/8] Validate json arrays --- packages/builder/src/stores/builder/screenComponent.ts | 5 +++++ packages/types/src/ui/datasource.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 1c1070ad17..4100e1e1fc 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -34,6 +34,7 @@ const validationKeyByType: Record = { custom: null, link: "rowId", field: "value", + jsonarray: "value", } export const screenComponentErrors = derived( @@ -74,6 +75,10 @@ export const screenComponentErrors = derived( bindings.extractRelationships(componentBindings) ), ...reduceBy("value", bindings.extractFields(componentBindings)), + ...reduceBy( + "value", + bindings.extractJSONArrayFields(componentBindings) + ), } const resourceId = componentSettings[validationKey] diff --git a/packages/types/src/ui/datasource.ts b/packages/types/src/ui/datasource.ts index c6b1ed01d1..8cc9b4ff94 100644 --- a/packages/types/src/ui/datasource.ts +++ b/packages/types/src/ui/datasource.ts @@ -6,3 +6,4 @@ export type UIDatasourceType = | "custom" | "link" | "field" + | "jsonarray"