From 5f353a85f7f9668d59e6cd9fa1e01a0a8e7891ff Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Wed, 8 Jan 2025 10:55:11 +0000 Subject: [PATCH 01/73] permissions store --- .../builder/src/stores/builder/permissions.js | 27 ----------- .../builder/src/stores/builder/permissions.ts | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+), 27 deletions(-) delete mode 100644 packages/builder/src/stores/builder/permissions.js create mode 100644 packages/builder/src/stores/builder/permissions.ts diff --git a/packages/builder/src/stores/builder/permissions.js b/packages/builder/src/stores/builder/permissions.js deleted file mode 100644 index a303cd713b..0000000000 --- a/packages/builder/src/stores/builder/permissions.js +++ /dev/null @@ -1,27 +0,0 @@ -import { writable } from "svelte/store" -import { API } from "@/api" - -export function createPermissionStore() { - const { subscribe } = writable([]) - - return { - subscribe, - save: async ({ level, role, resource }) => { - return await API.updatePermissionForResource(resource, role, level) - }, - remove: async ({ level, role, resource }) => { - return await API.removePermissionFromResource(resource, role, level) - }, - forResource: async resourceId => { - return (await API.getPermissionForResource(resourceId)).permissions - }, - forResourceDetailed: async resourceId => { - return await API.getPermissionForResource(resourceId) - }, - getDependantsInfo: async resourceId => { - return await API.getDependants(resourceId) - }, - } -} - -export const permissions = createPermissionStore() diff --git a/packages/builder/src/stores/builder/permissions.ts b/packages/builder/src/stores/builder/permissions.ts new file mode 100644 index 0000000000..002b73893e --- /dev/null +++ b/packages/builder/src/stores/builder/permissions.ts @@ -0,0 +1,47 @@ +import { BudiStore } from "../BudiStore" +import { API } from "@/api" +import { + PermissionLevel, + GetResourcePermsResponse, + GetDependantResourcesResponse, +} from "@budibase/types" + +interface Permission { + level: PermissionLevel + role: string + resource: string +} + +export class PermissionStore extends BudiStore { + constructor() { + super([]) + } + + save = async (permission: Permission) => { + const { level, role, resource } = permission + return await API.updatePermissionForResource(resource, role, level) + } + + remove = async (permission: Permission) => { + const { level, role, resource } = permission + return await API.removePermissionFromResource(resource, role, level) + } + + forResource = async (resourceId: string): Promise => { + return (await API.getPermissionForResource(resourceId)).permissions + } + + forResourceDetailed = async ( + resourceId: string + ): Promise => { + return await API.getPermissionForResource(resourceId) + } + + getDependantsInfo = async ( + resourceId: string + ): Promise => { + return await API.getDependants(resourceId) + } +} + +export const permissions = new PermissionStore() From a983292865fee9980342d23af16a8d9ed20c6a64 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Wed, 8 Jan 2025 11:14:56 +0000 Subject: [PATCH 02/73] fix return type --- packages/builder/src/stores/builder/permissions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/stores/builder/permissions.ts b/packages/builder/src/stores/builder/permissions.ts index 002b73893e..6056449150 100644 --- a/packages/builder/src/stores/builder/permissions.ts +++ b/packages/builder/src/stores/builder/permissions.ts @@ -4,6 +4,7 @@ import { PermissionLevel, GetResourcePermsResponse, GetDependantResourcesResponse, + ResourcePermissionInfo, } from "@budibase/types" interface Permission { @@ -27,7 +28,9 @@ export class PermissionStore extends BudiStore { return await API.removePermissionFromResource(resource, role, level) } - forResource = async (resourceId: string): Promise => { + forResource = async ( + resourceId: string + ): Promise> => { return (await API.getPermissionForResource(resourceId)).permissions } From b6418c333cf422857f64db84f963f96e12a061d4 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 9 Jan 2025 09:20:16 +0000 Subject: [PATCH 03/73] views v1 --- packages/builder/src/stores/builder/views.js | 67 -------------- packages/builder/src/stores/builder/views.ts | 94 ++++++++++++++++++++ 2 files changed, 94 insertions(+), 67 deletions(-) delete mode 100644 packages/builder/src/stores/builder/views.js create mode 100644 packages/builder/src/stores/builder/views.ts diff --git a/packages/builder/src/stores/builder/views.js b/packages/builder/src/stores/builder/views.js deleted file mode 100644 index 07c356f56d..0000000000 --- a/packages/builder/src/stores/builder/views.js +++ /dev/null @@ -1,67 +0,0 @@ -import { writable, derived } from "svelte/store" -import { tables } from "./tables" -import { API } from "@/api" - -export function createViewsStore() { - const store = writable({ - selectedViewName: null, - }) - const derivedStore = derived([store, tables], ([$store, $tables]) => { - let list = [] - $tables.list?.forEach(table => { - const views = Object.values(table?.views || {}).filter(view => { - return view.version !== 2 - }) - list = list.concat(views) - }) - return { - ...$store, - list, - selected: list.find(view => view.name === $store.selectedViewName), - } - }) - - const select = name => { - store.update(state => ({ - ...state, - selectedViewName: name, - })) - } - - const deleteView = async view => { - await API.deleteView(view.name) - - // Update tables - tables.update(state => { - const table = state.list.find(table => table._id === view.tableId) - delete table.views[view.name] - return { ...state } - }) - } - - const save = async view => { - const savedView = await API.saveView(view) - select(view.name) - - // Update tables - tables.update(state => { - const table = state.list.find(table => table._id === view.tableId) - if (table) { - if (view.originalName) { - delete table.views[view.originalName] - } - table.views[view.name] = savedView - } - return { ...state } - }) - } - - return { - subscribe: derivedStore.subscribe, - select, - delete: deleteView, - save, - } -} - -export const views = createViewsStore() diff --git a/packages/builder/src/stores/builder/views.ts b/packages/builder/src/stores/builder/views.ts new file mode 100644 index 0000000000..4e309e180d --- /dev/null +++ b/packages/builder/src/stores/builder/views.ts @@ -0,0 +1,94 @@ +import { DerivedBudiStore } from "../BudiStore" +import { tables } from "./tables" +import { API } from "@/api" +import { View } from "@budibase/types" +import { helpers } from "@budibase/shared-core" +import { derived, Writable } from "svelte/store" + +interface BuilderViewStore { + selectedViewName: string | null +} + +interface DerivedViewStore extends BuilderViewStore { + list: View[] + selected?: View +} + +export class ViewsStore extends DerivedBudiStore< + BuilderViewStore, + DerivedViewStore +> { + constructor() { + const makeDerivedStore = (store: Writable) => { + return derived([store, tables], ([$store, $tables]): DerivedViewStore => { + let list: View[] = [] + $tables.list?.forEach(table => { + const views = Object.values(table?.views || {}).filter( + (view): view is View => !helpers.views.isV2(view) + ) + list = list.concat(views) + }) + return { + selectedViewName: $store.selectedViewName, + list, + selected: list.find(view => view.name === $store.selectedViewName), + } + }) + } + + super( + { + selectedViewName: null, + }, + makeDerivedStore + ) + + this.select = this.select.bind(this) + } + + select = (name: string) => { + this.store.update(state => ({ + ...state, + selectedViewName: name, + })) + } + + delete = async (view: View) => { + if (!view.name) { + return + } + await API.deleteView(view.name) + + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + if (table?.views && view.name) { + delete table.views[view.name] + } + return { ...state } + }) + } + + save = async (view: View & { originalName?: string }) => { + if (!view.name) { + return + } + + const savedView = await API.saveView(view) + this.select(view.name) + + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + if (table?.views && view.name) { + if (view.originalName) { + delete table.views[view.originalName] + } + table.views[view.name] = savedView + } + return { ...state } + }) + } +} + +export const views = new ViewsStore() From d06b22d4b8eaded1b62c855f04dafc8cb6fbbb1c Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 13 Jan 2025 11:56:33 +0000 Subject: [PATCH 04/73] Add sorting tests for dateonly fields. --- .../src/api/routes/tests/search.spec.ts | 318 +++++++++++------- 1 file changed, 192 insertions(+), 126 deletions(-) diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index 4de92f21e5..18221f9c12 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -1690,138 +1690,204 @@ if (descriptions.length) { describe.each([true, false])( "search with timestamp: %s", searchWithTimestamp => { - const SAVE_SUFFIX = saveWithTimestamp - ? "T00:00:00.000Z" - : "" - const SEARCH_SUFFIX = searchWithTimestamp - ? "T00:00:00.000Z" - : "" + describe.each(["/", "-"])( + "date separator: %s", + separator => { + const SAVE_SUFFIX = saveWithTimestamp + ? "T00:00:00.000Z" + : "" + const SEARCH_SUFFIX = searchWithTimestamp + ? "T00:00:00.000Z" + : "" - const JAN_1ST = `2020-01-01` - const JAN_10TH = `2020-01-10` - const JAN_30TH = `2020-01-30` - const UNEXISTING_DATE = `2020-01-03` - const NULL_DATE__ID = `null_date__id` + const JAN_1ST = `2020-01-01` + const JAN_10TH = `2020-01-10` + const JAN_30TH = `2020-01-30` + const UNEXISTING_DATE = `2020-01-03` + const NULL_DATE__ID = `null_date__id` - beforeAll(async () => { - tableOrViewId = await createTableOrView({ - dateid: { name: "dateid", type: FieldType.STRING }, - date: { - name: "date", - type: FieldType.DATETIME, - dateOnly: true, - }, - }) - - await createRows([ - { dateid: NULL_DATE__ID, date: null }, - { date: `${JAN_1ST}${SAVE_SUFFIX}` }, - { date: `${JAN_10TH}${SAVE_SUFFIX}` }, - ]) - }) - - describe("equal", () => { - it("successfully finds a row", async () => { - await expectQuery({ - equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("successfully finds an ISO8601 row", async () => { - await expectQuery({ - equal: { date: `${JAN_10TH}${SEARCH_SUFFIX}` }, - }).toContainExactly([{ date: JAN_10TH }]) - }) - - it("finds a row with ISO8601 timestamp", async () => { - await expectQuery({ - equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("fails to find nonexistent row", async () => { - await expectQuery({ - equal: { - date: `${UNEXISTING_DATE}${SEARCH_SUFFIX}`, - }, - }).toFindNothing() - }) - }) - - describe("notEqual", () => { - it("successfully finds a row", async () => { - await expectQuery({ - notEqual: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, - }).toContainExactly([ - { date: JAN_10TH }, - { dateid: NULL_DATE__ID }, - ]) - }) - - it("fails to find nonexistent row", async () => { - await expectQuery({ - notEqual: { date: `${JAN_30TH}${SEARCH_SUFFIX}` }, - }).toContainExactly([ - { date: JAN_1ST }, - { date: JAN_10TH }, - { dateid: NULL_DATE__ID }, - ]) - }) - }) - - describe("oneOf", () => { - it("successfully finds a row", async () => { - await expectQuery({ - oneOf: { date: [`${JAN_1ST}${SEARCH_SUFFIX}`] }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("fails to find nonexistent row", async () => { - await expectQuery({ - oneOf: { - date: [`${UNEXISTING_DATE}${SEARCH_SUFFIX}`], - }, - }).toFindNothing() - }) - }) - - describe("range", () => { - it("successfully finds a row", async () => { - await expectQuery({ - range: { - date: { - low: `${JAN_1ST}${SEARCH_SUFFIX}`, - high: `${JAN_1ST}${SEARCH_SUFFIX}`, + beforeAll(async () => { + tableOrViewId = await createTableOrView({ + dateid: { + name: "dateid", + type: FieldType.STRING, }, - }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("successfully finds multiple rows", async () => { - await expectQuery({ - range: { date: { - low: `${JAN_1ST}${SEARCH_SUFFIX}`, - high: `${JAN_10TH}${SEARCH_SUFFIX}`, + name: "date", + type: FieldType.DATETIME, + dateOnly: true, }, - }, - }).toContainExactly([ - { date: JAN_1ST }, - { date: JAN_10TH }, - ]) - }) + }) - it("successfully finds no rows", async () => { - await expectQuery({ - range: { - date: { - low: `${JAN_30TH}${SEARCH_SUFFIX}`, - high: `${JAN_30TH}${SEARCH_SUFFIX}`, - }, - }, - }).toFindNothing() - }) - }) + await createRows([ + { dateid: NULL_DATE__ID, date: null }, + { date: `${JAN_1ST}${SAVE_SUFFIX}` }, + { date: `${JAN_10TH}${SAVE_SUFFIX}` }, + ]) + }) + + describe("equal", () => { + it("successfully finds a row", async () => { + await expectQuery({ + equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, + }).toContainExactly([{ date: JAN_1ST }]) + }) + + it("successfully finds an ISO8601 row", async () => { + await expectQuery({ + equal: { date: `${JAN_10TH}${SEARCH_SUFFIX}` }, + }).toContainExactly([{ date: JAN_10TH }]) + }) + + it("finds a row with ISO8601 timestamp", async () => { + await expectQuery({ + equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, + }).toContainExactly([{ date: JAN_1ST }]) + }) + + it("fails to find nonexistent row", async () => { + await expectQuery({ + equal: { + date: `${UNEXISTING_DATE}${SEARCH_SUFFIX}`, + }, + }).toFindNothing() + }) + }) + + describe("notEqual", () => { + it("successfully finds a row", async () => { + await expectQuery({ + notEqual: { + date: `${JAN_1ST}${SEARCH_SUFFIX}`, + }, + }).toContainExactly([ + { date: JAN_10TH }, + { dateid: NULL_DATE__ID }, + ]) + }) + + it("fails to find nonexistent row", async () => { + await expectQuery({ + notEqual: { + date: `${JAN_30TH}${SEARCH_SUFFIX}`, + }, + }).toContainExactly([ + { date: JAN_1ST }, + { date: JAN_10TH }, + { dateid: NULL_DATE__ID }, + ]) + }) + }) + + describe("oneOf", () => { + it("successfully finds a row", async () => { + await expectQuery({ + oneOf: { date: [`${JAN_1ST}${SEARCH_SUFFIX}`] }, + }).toContainExactly([{ date: JAN_1ST }]) + }) + + it("fails to find nonexistent row", async () => { + await expectQuery({ + oneOf: { + date: [`${UNEXISTING_DATE}${SEARCH_SUFFIX}`], + }, + }).toFindNothing() + }) + }) + + describe("range", () => { + it("successfully finds a row", async () => { + await expectQuery({ + range: { + date: { + low: `${JAN_1ST}${SEARCH_SUFFIX}`, + high: `${JAN_1ST}${SEARCH_SUFFIX}`, + }, + }, + }).toContainExactly([{ date: JAN_1ST }]) + }) + + it("successfully finds multiple rows", async () => { + await expectQuery({ + range: { + date: { + low: `${JAN_1ST}${SEARCH_SUFFIX}`, + high: `${JAN_10TH}${SEARCH_SUFFIX}`, + }, + }, + }).toContainExactly([ + { date: JAN_1ST }, + { date: JAN_10TH }, + ]) + }) + + it("successfully finds no rows", async () => { + await expectQuery({ + range: { + date: { + low: `${JAN_30TH}${SEARCH_SUFFIX}`, + high: `${JAN_30TH}${SEARCH_SUFFIX}`, + }, + }, + }).toFindNothing() + }) + }) + + describe.only("sort", () => { + it("sorts ascending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortOrder: SortOrder.ASCENDING, + }).toMatchExactly([ + { dateid: NULL_DATE__ID }, + { date: JAN_1ST }, + { date: JAN_10TH }, + ]) + }) + + it("sorts descending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortOrder: SortOrder.DESCENDING, + }).toMatchExactly([ + { date: JAN_10TH }, + { date: JAN_1ST }, + { dateid: NULL_DATE__ID }, + ]) + }) + + describe("sortType STRING", () => { + it("sorts ascending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortType: SortType.STRING, + sortOrder: SortOrder.ASCENDING, + }).toMatchExactly([ + { dateid: NULL_DATE__ID }, + { date: JAN_1ST }, + { date: JAN_10TH }, + ]) + }) + + it("sorts descending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortType: SortType.STRING, + sortOrder: SortOrder.DESCENDING, + }).toMatchExactly([ + { date: JAN_10TH }, + { date: JAN_1ST }, + { dateid: NULL_DATE__ID }, + ]) + }) + }) + }) + } + ) } ) } From 831df35c5eacdeb076f46cf58c5ba82195067498 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 10 Jan 2025 11:40:19 +0100 Subject: [PATCH 05/73] Initial blocks conversion --- packages/client/src/components/app/blocks/CardsBlock.svelte | 2 +- packages/client/src/components/app/deprecated/TableBlock.svelte | 2 +- packages/client/src/utils/{blocks.js => blocks.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/client/src/utils/{blocks.js => blocks.ts} (100%) diff --git a/packages/client/src/components/app/blocks/CardsBlock.svelte b/packages/client/src/components/app/blocks/CardsBlock.svelte index 0368a27e93..34707c394d 100644 --- a/packages/client/src/components/app/blocks/CardsBlock.svelte +++ b/packages/client/src/components/app/blocks/CardsBlock.svelte @@ -3,7 +3,7 @@ import Block from "components/Block.svelte" import BlockComponent from "components/BlockComponent.svelte" import { makePropSafe as safe } from "@budibase/string-templates" - import { enrichSearchColumns, enrichFilter } from "utils/blocks.js" + import { enrichSearchColumns, enrichFilter } from "utils/blocks" import { get } from "svelte/store" export let title diff --git a/packages/client/src/components/app/deprecated/TableBlock.svelte b/packages/client/src/components/app/deprecated/TableBlock.svelte index e67dc5b061..cdb45cfed7 100644 --- a/packages/client/src/components/app/deprecated/TableBlock.svelte +++ b/packages/client/src/components/app/deprecated/TableBlock.svelte @@ -5,7 +5,7 @@ import Block from "components/Block.svelte" import BlockComponent from "components/BlockComponent.svelte" import { makePropSafe as safe } from "@budibase/string-templates" - import { enrichSearchColumns, enrichFilter } from "utils/blocks.js" + import { enrichSearchColumns, enrichFilter } from "utils/blocks" import { Utils } from "@budibase/frontend-core" export let title diff --git a/packages/client/src/utils/blocks.js b/packages/client/src/utils/blocks.ts similarity index 100% rename from packages/client/src/utils/blocks.js rename to packages/client/src/utils/blocks.ts From c4ba33cf5bdc1fdb1f71ee53df1ed26fe2ec9cf0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 10 Jan 2025 11:43:00 +0100 Subject: [PATCH 06/73] Fix types --- packages/client/src/utils/blocks.ts | 31 +++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/client/src/utils/blocks.ts b/packages/client/src/utils/blocks.ts index 88cf4b095b..58d79aa1aa 100644 --- a/packages/client/src/utils/blocks.ts +++ b/packages/client/src/utils/blocks.ts @@ -1,10 +1,16 @@ import { makePropSafe as safe } from "@budibase/string-templates" import { API } from "../api/index.js" -import { UILogicalOperator } from "@budibase/types" +import { + BasicOperator, + LegacyFilter, + UIColumn, + UILogicalOperator, + UISearchFilter, +} from "@budibase/types" import { Constants } from "@budibase/frontend-core" // Map of data types to component types for search fields inside blocks -const schemaComponentMap = { +const schemaComponentMap: Record = { string: "stringfield", options: "optionsfield", number: "numberfield", @@ -19,7 +25,16 @@ const schemaComponentMap = { * @param searchColumns the search columns to use * @param schema the datasource schema */ -export const enrichSearchColumns = async (searchColumns, schema) => { +export const enrichSearchColumns = async ( + searchColumns: string[], + schema: Record< + string, + { + tableId: string + type: string + } + > +) => { if (!searchColumns?.length || !schema) { return [] } @@ -61,12 +76,16 @@ export const enrichSearchColumns = async (searchColumns, schema) => { * @param columns the enriched search column structure * @param formId the ID of the form containing the search fields */ -export const enrichFilter = (filter, columns, formId) => { +export const enrichFilter = ( + filter: UISearchFilter, + columns: UIColumn[], + formId: string +) => { if (!columns?.length) { return filter } - let newFilters = [] + const newFilters: LegacyFilter[] = [] columns?.forEach(column => { const safePath = column.name.split(".").map(safe).join(".") const stringType = column.type === "string" || column.type === "formula" @@ -99,7 +118,7 @@ export const enrichFilter = (filter, columns, formId) => { newFilters.push({ field: column.name, type: column.type, - operator: stringType ? "string" : "equal", + operator: stringType ? BasicOperator.STRING : BasicOperator.EQUAL, valueType: "Binding", value: `{{ ${binding} }}`, }) From 558b7c88a7fe013454614ab0dba916dc5b82edf8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 10 Jan 2025 11:46:58 +0100 Subject: [PATCH 07/73] Basic grid conversion --- .../client/src/utils/{grid.js => grid.ts} | 28 ++++++++++--------- packages/client/tsconfig.json | 1 + 2 files changed, 16 insertions(+), 13 deletions(-) rename packages/client/src/utils/{grid.js => grid.ts} (89%) diff --git a/packages/client/src/utils/grid.js b/packages/client/src/utils/grid.ts similarity index 89% rename from packages/client/src/utils/grid.js rename to packages/client/src/utils/grid.ts index 142a7ed55a..eec2c9c173 100644 --- a/packages/client/src/utils/grid.js +++ b/packages/client/src/utils/grid.ts @@ -1,4 +1,4 @@ -import { GridSpacing, GridRowHeight } from "constants" +import { GridSpacing, GridRowHeight } from "@/constants" import { builderStore } from "stores" import { buildStyleString } from "utils/styleable.js" @@ -44,10 +44,11 @@ export const GridDragModes = { } // Builds a CSS variable name for a certain piece of grid metadata -export const getGridVar = (device, param) => `--grid-${device}-${param}` +export const getGridVar = (device: string, param: string) => + `--grid-${device}-${param}` // Determines whether a JS event originated from immediately within a grid -export const isGridEvent = e => { +export const isGridEvent = (e: any) => { return ( e.target.dataset?.indicator === "true" || e.target @@ -59,11 +60,11 @@ export const isGridEvent = e => { // Svelte action to apply required class names and styles to our component // wrappers -export const gridLayout = (node, metadata) => { - let selectComponent +export const gridLayout = (node: HTMLDivElement, metadata: any) => { + let selectComponent: any // Applies the required listeners, CSS and classes to a component DOM node - const applyMetadata = metadata => { + const applyMetadata = (metadata: any) => { const { id, styles, @@ -86,7 +87,7 @@ export const gridLayout = (node, metadata) => { } // Callback to select the component when clicking on the wrapper - selectComponent = e => { + selectComponent = (e: Event) => { e.stopPropagation() builderStore.actions.selectComponent(id) } @@ -100,7 +101,7 @@ export const gridLayout = (node, metadata) => { } width += 2 * GridSpacing height += 2 * GridSpacing - let vars = { + let vars: any = { "--default-width": width, "--default-height": height, } @@ -135,7 +136,7 @@ export const gridLayout = (node, metadata) => { } // Apply some metadata to data attributes to speed up lookups - const addDataTag = (tagName, device, param) => { + const addDataTag = (tagName: string, device: string, param: string) => { const val = `${vars[getGridVar(device, param)]}` if (node.dataset[tagName] !== val) { node.dataset[tagName] = val @@ -147,11 +148,12 @@ export const gridLayout = (node, metadata) => { addDataTag("gridMobileHAlign", Devices.Mobile, GridParams.HAlign) addDataTag("gridDesktopVAlign", Devices.Desktop, GridParams.VAlign) addDataTag("gridMobileVAlign", Devices.Mobile, GridParams.VAlign) - if (node.dataset.insideGrid !== true) { - node.dataset.insideGrid = true + if (node.dataset.insideGrid !== "true") { + node.dataset.insideGrid = "true" } // Apply all CSS variables to the wrapper + // @ts-expect-error TODO node.style = buildStyleString(vars) // Add a listener to select this node on click @@ -160,7 +162,7 @@ export const gridLayout = (node, metadata) => { } // Add draggable attribute - node.setAttribute("draggable", !!draggable) + node.setAttribute("draggable", (!!draggable).toString()) } // Removes the previously set up listeners @@ -176,7 +178,7 @@ export const gridLayout = (node, metadata) => { applyMetadata(metadata) return { - update(newMetadata) { + update(newMetadata: any) { removeListeners() applyMetadata(newMetadata) }, diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 740b129738..ab07343525 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -14,6 +14,7 @@ "../*", "../../node_modules/@budibase/*" ], + "@/*": ["./src/*"], "*": ["./src/*"] } } From ec0de61cf71d7924b68b2cc8d7308cd2b89cfcc7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 10 Jan 2025 11:57:06 +0100 Subject: [PATCH 08/73] Types --- packages/client/src/components/Component.svelte | 2 +- packages/client/src/utils/grid.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 7a1341c67c..79b4ca6f68 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -39,7 +39,7 @@ getActionContextKey, getActionDependentContextKeys, } from "../utils/buttonActions.js" - import { gridLayout } from "utils/grid.js" + import { gridLayout } from "utils/grid" export let instance = {} export let parent = null diff --git a/packages/client/src/utils/grid.ts b/packages/client/src/utils/grid.ts index eec2c9c173..519f47f342 100644 --- a/packages/client/src/utils/grid.ts +++ b/packages/client/src/utils/grid.ts @@ -48,11 +48,13 @@ export const getGridVar = (device: string, param: string) => `--grid-${device}-${param}` // Determines whether a JS event originated from immediately within a grid -export const isGridEvent = (e: any) => { +export const isGridEvent = (e: Event & { target: HTMLElement }): boolean => { return ( e.target.dataset?.indicator === "true" || + // @ts-expect-error: api is not properly typed e.target .closest?.(".component") + // @ts-expect-error ?.parentNode.closest(".component") ?.childNodes[0]?.classList?.contains("grid") ) @@ -101,7 +103,7 @@ export const gridLayout = (node: HTMLDivElement, metadata: any) => { } width += 2 * GridSpacing height += 2 * GridSpacing - let vars: any = { + const vars: Record = { "--default-width": width, "--default-height": height, } From d88ba716bba1bf355f88cf907833bbb0473f9812 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 10 Jan 2025 12:43:32 +0100 Subject: [PATCH 09/73] Type grid --- packages/client/src/utils/grid.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/client/src/utils/grid.ts b/packages/client/src/utils/grid.ts index 519f47f342..119d008375 100644 --- a/packages/client/src/utils/grid.ts +++ b/packages/client/src/utils/grid.ts @@ -2,6 +2,26 @@ import { GridSpacing, GridRowHeight } from "@/constants" import { builderStore } from "stores" import { buildStyleString } from "utils/styleable.js" +interface GridMetadata { + id: string + styles: Record & { + "--default-width"?: number + "--default-height"?: number + } + interactive: boolean + errored: boolean + definition?: { + size?: { + width: number + height: number + } + grid?: { hAlign: string; vAlign: string } + } + draggable: boolean + insideGrid: boolean + ignoresLayout: boolean +} + /** * We use CSS variables on components to control positioning and layout of * components inside grids. @@ -62,11 +82,11 @@ export const isGridEvent = (e: Event & { target: HTMLElement }): boolean => { // Svelte action to apply required class names and styles to our component // wrappers -export const gridLayout = (node: HTMLDivElement, metadata: any) => { +export const gridLayout = (node: HTMLDivElement, metadata: GridMetadata) => { let selectComponent: any // Applies the required listeners, CSS and classes to a component DOM node - const applyMetadata = (metadata: any) => { + const applyMetadata = (metadata: GridMetadata) => { const { id, styles, @@ -103,7 +123,7 @@ export const gridLayout = (node: HTMLDivElement, metadata: any) => { } width += 2 * GridSpacing height += 2 * GridSpacing - const vars: Record = { + const vars: Record = { "--default-width": width, "--default-height": height, } @@ -180,7 +200,7 @@ export const gridLayout = (node: HTMLDivElement, metadata: any) => { applyMetadata(metadata) return { - update(newMetadata: any) { + update(newMetadata: GridMetadata) { removeListeners() applyMetadata(newMetadata) }, From a566b49ec38fddb2ea1da590056798048fa58540 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 10 Jan 2025 12:45:02 +0100 Subject: [PATCH 10/73] Fix @/constants usages --- packages/client/vite.config.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client/vite.config.mjs b/packages/client/vite.config.mjs index 22f451fadd..7c163181ae 100644 --- a/packages/client/vite.config.mjs +++ b/packages/client/vite.config.mjs @@ -67,6 +67,10 @@ export default defineConfig(({ mode }) => { find: "constants", replacement: path.resolve("./src/constants"), }, + { + find: "@/constants", + replacement: path.resolve("./src/constants"), + }, { find: "sdk", replacement: path.resolve("./src/sdk"), From c38d694727a873b44d825b489dce0f3a1c3fd374 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 10 Jan 2025 12:46:20 +0100 Subject: [PATCH 11/73] Convert linkable --- packages/client/src/utils/{linkable.js => linkable.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename packages/client/src/utils/{linkable.js => linkable.ts} (61%) diff --git a/packages/client/src/utils/linkable.js b/packages/client/src/utils/linkable.ts similarity index 61% rename from packages/client/src/utils/linkable.js rename to packages/client/src/utils/linkable.ts index d82a53474b..2b6cd0ebfd 100644 --- a/packages/client/src/utils/linkable.js +++ b/packages/client/src/utils/linkable.ts @@ -1,8 +1,8 @@ import { get } from "svelte/store" -import { link } from "svelte-spa-router" +import { link, LinkActionOpts } from "svelte-spa-router" import { builderStore } from "stores" -export const linkable = (node, href) => { +export const linkable = (node: HTMLElement, href?: LinkActionOpts) => { if (get(builderStore).inBuilder) { node.onclick = e => { e.preventDefault() From 92a1b0b4acb9ab8bba9243d09419923db9318394 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 12:17:03 +0100 Subject: [PATCH 12/73] Type --- packages/client/src/utils/grid.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/utils/grid.ts b/packages/client/src/utils/grid.ts index 119d008375..69797e9120 100644 --- a/packages/client/src/utils/grid.ts +++ b/packages/client/src/utils/grid.ts @@ -83,7 +83,7 @@ export const isGridEvent = (e: Event & { target: HTMLElement }): boolean => { // Svelte action to apply required class names and styles to our component // wrappers export const gridLayout = (node: HTMLDivElement, metadata: GridMetadata) => { - let selectComponent: any + let selectComponent: ((e: Event) => void) | null // Applies the required listeners, CSS and classes to a component DOM node const applyMetadata = (metadata: GridMetadata) => { From 3f6dcb569ee406af0fc56cfe2038ca77c0e7e93a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 12:56:24 +0100 Subject: [PATCH 13/73] Add comment --- packages/client/src/sdk.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index 68d75d2806..c641d339d5 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -74,6 +74,7 @@ export default { fetchData, QueryUtils, ContextScopes: Constants.ContextScopes, + // This is not used internally but exposed to users to be used in plugins getAPIKey, enrichButtonActions, processStringSync, From e55874a6988e552086530db60e22972d898be44e Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 13 Jan 2025 12:51:13 +0000 Subject: [PATCH 14/73] More validation around datetime columns and bulk importing. --- .../server/src/api/routes/tests/row.spec.ts | 98 +++++ .../src/api/routes/tests/search.spec.ts | 373 +++++++++--------- packages/server/src/utilities/schema.ts | 24 +- 3 files changed, 302 insertions(+), 193 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index e5cd54e5a5..737ac2863a 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -2043,6 +2043,104 @@ if (descriptions.length) { expect(rows[0].name).toEqual("Clare updated") expect(rows[1].name).toEqual("Jeff updated") }) + + it("should reject bulkImport date only fields with wrong format", async () => { + const table = await config.api.table.save( + saveTableRequest({ + schema: { + date: { + type: FieldType.DATETIME, + dateOnly: true, + name: "date", + }, + }, + }) + ) + + await config.api.row.bulkImport( + table._id!, + { + rows: [ + { + date: "01.02.2024", + }, + ], + }, + { + status: 400, + body: { + message: + 'Invalid format for field "date": "01.02.2024". Date-only fields must be in the format "YYYY-MM-DD".', + }, + } + ) + }) + + it("should reject bulkImport date time fields with wrong format", async () => { + const table = await config.api.table.save( + saveTableRequest({ + schema: { + date: { + type: FieldType.DATETIME, + name: "date", + }, + }, + }) + ) + + await config.api.row.bulkImport( + table._id!, + { + rows: [ + { + date: "01.02.2024", + }, + ], + }, + { + status: 400, + body: { + message: + 'Invalid format for field "date": "01.02.2024". Datetime fields must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SSZ".', + }, + } + ) + }) + + it("should reject bulkImport time fields with wrong format", async () => { + const table = await config.api.table.save( + saveTableRequest({ + schema: { + time: { + type: FieldType.DATETIME, + timeOnly: true, + name: "time", + }, + }, + }) + ) + + await config.api.row.bulkImport( + table._id!, + { + rows: [ + { + time: "3pm", + }, + ], + }, + { + // This isn't ideal atm because it doesn't line up with datetime + // and date only error messages, but there's a check earlier in + // the stack than when those errors happen that produces this one, + // and it's not easy to bypass. The key is that this fails. + status: 500, + body: { + message: 'Invalid date value: "3pm"', + }, + } + ) + }) }) describe("enrich", () => { diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index 18221f9c12..c3b274d5f4 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -1690,204 +1690,199 @@ if (descriptions.length) { describe.each([true, false])( "search with timestamp: %s", searchWithTimestamp => { - describe.each(["/", "-"])( - "date separator: %s", - separator => { - const SAVE_SUFFIX = saveWithTimestamp - ? "T00:00:00.000Z" - : "" - const SEARCH_SUFFIX = searchWithTimestamp - ? "T00:00:00.000Z" - : "" + const SAVE_SUFFIX = saveWithTimestamp + ? "T00:00:00.000Z" + : "" + const SEARCH_SUFFIX = searchWithTimestamp + ? "T00:00:00.000Z" + : "" - const JAN_1ST = `2020-01-01` - const JAN_10TH = `2020-01-10` - const JAN_30TH = `2020-01-30` - const UNEXISTING_DATE = `2020-01-03` - const NULL_DATE__ID = `null_date__id` + const JAN_1ST = `2020-01-01` + const JAN_10TH = `2020-01-10` + const JAN_30TH = `2020-01-30` + const UNEXISTING_DATE = `2020-01-03` + const NULL_DATE__ID = `null_date__id` - beforeAll(async () => { - tableOrViewId = await createTableOrView({ - dateid: { - name: "dateid", - type: FieldType.STRING, - }, + beforeAll(async () => { + tableOrViewId = await createTableOrView({ + dateid: { + name: "dateid", + type: FieldType.STRING, + }, + date: { + name: "date", + type: FieldType.DATETIME, + dateOnly: true, + }, + }) + + await createRows([ + { dateid: NULL_DATE__ID, date: null }, + { date: `${JAN_1ST}${SAVE_SUFFIX}` }, + { date: `${JAN_10TH}${SAVE_SUFFIX}` }, + ]) + }) + + describe("equal", () => { + it("successfully finds a row", async () => { + await expectQuery({ + equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, + }).toContainExactly([{ date: JAN_1ST }]) + }) + + it("successfully finds an ISO8601 row", async () => { + await expectQuery({ + equal: { date: `${JAN_10TH}${SEARCH_SUFFIX}` }, + }).toContainExactly([{ date: JAN_10TH }]) + }) + + it("finds a row with ISO8601 timestamp", async () => { + await expectQuery({ + equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, + }).toContainExactly([{ date: JAN_1ST }]) + }) + + it("fails to find nonexistent row", async () => { + await expectQuery({ + equal: { + date: `${UNEXISTING_DATE}${SEARCH_SUFFIX}`, + }, + }).toFindNothing() + }) + }) + + describe("notEqual", () => { + it("successfully finds a row", async () => { + await expectQuery({ + notEqual: { + date: `${JAN_1ST}${SEARCH_SUFFIX}`, + }, + }).toContainExactly([ + { date: JAN_10TH }, + { dateid: NULL_DATE__ID }, + ]) + }) + + it("fails to find nonexistent row", async () => { + await expectQuery({ + notEqual: { + date: `${JAN_30TH}${SEARCH_SUFFIX}`, + }, + }).toContainExactly([ + { date: JAN_1ST }, + { date: JAN_10TH }, + { dateid: NULL_DATE__ID }, + ]) + }) + }) + + describe("oneOf", () => { + it("successfully finds a row", async () => { + await expectQuery({ + oneOf: { date: [`${JAN_1ST}${SEARCH_SUFFIX}`] }, + }).toContainExactly([{ date: JAN_1ST }]) + }) + + it("fails to find nonexistent row", async () => { + await expectQuery({ + oneOf: { + date: [`${UNEXISTING_DATE}${SEARCH_SUFFIX}`], + }, + }).toFindNothing() + }) + }) + + describe("range", () => { + it("successfully finds a row", async () => { + await expectQuery({ + range: { date: { - name: "date", - type: FieldType.DATETIME, - dateOnly: true, + low: `${JAN_1ST}${SEARCH_SUFFIX}`, + high: `${JAN_1ST}${SEARCH_SUFFIX}`, }, - }) + }, + }).toContainExactly([{ date: JAN_1ST }]) + }) - await createRows([ - { dateid: NULL_DATE__ID, date: null }, - { date: `${JAN_1ST}${SAVE_SUFFIX}` }, - { date: `${JAN_10TH}${SAVE_SUFFIX}` }, + it("successfully finds multiple rows", async () => { + await expectQuery({ + range: { + date: { + low: `${JAN_1ST}${SEARCH_SUFFIX}`, + high: `${JAN_10TH}${SEARCH_SUFFIX}`, + }, + }, + }).toContainExactly([ + { date: JAN_1ST }, + { date: JAN_10TH }, + ]) + }) + + it("successfully finds no rows", async () => { + await expectQuery({ + range: { + date: { + low: `${JAN_30TH}${SEARCH_SUFFIX}`, + high: `${JAN_30TH}${SEARCH_SUFFIX}`, + }, + }, + }).toFindNothing() + }) + }) + + describe("sort", () => { + it("sorts ascending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortOrder: SortOrder.ASCENDING, + }).toMatchExactly([ + { dateid: NULL_DATE__ID }, + { date: JAN_1ST }, + { date: JAN_10TH }, + ]) + }) + + it("sorts descending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortOrder: SortOrder.DESCENDING, + }).toMatchExactly([ + { date: JAN_10TH }, + { date: JAN_1ST }, + { dateid: NULL_DATE__ID }, + ]) + }) + + describe("sortType STRING", () => { + it("sorts ascending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortType: SortType.STRING, + sortOrder: SortOrder.ASCENDING, + }).toMatchExactly([ + { dateid: NULL_DATE__ID }, + { date: JAN_1ST }, + { date: JAN_10TH }, ]) }) - describe("equal", () => { - it("successfully finds a row", async () => { - await expectQuery({ - equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("successfully finds an ISO8601 row", async () => { - await expectQuery({ - equal: { date: `${JAN_10TH}${SEARCH_SUFFIX}` }, - }).toContainExactly([{ date: JAN_10TH }]) - }) - - it("finds a row with ISO8601 timestamp", async () => { - await expectQuery({ - equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("fails to find nonexistent row", async () => { - await expectQuery({ - equal: { - date: `${UNEXISTING_DATE}${SEARCH_SUFFIX}`, - }, - }).toFindNothing() - }) + it("sorts descending", async () => { + await expectSearch({ + query: {}, + sort: "date", + sortType: SortType.STRING, + sortOrder: SortOrder.DESCENDING, + }).toMatchExactly([ + { date: JAN_10TH }, + { date: JAN_1ST }, + { dateid: NULL_DATE__ID }, + ]) }) - - describe("notEqual", () => { - it("successfully finds a row", async () => { - await expectQuery({ - notEqual: { - date: `${JAN_1ST}${SEARCH_SUFFIX}`, - }, - }).toContainExactly([ - { date: JAN_10TH }, - { dateid: NULL_DATE__ID }, - ]) - }) - - it("fails to find nonexistent row", async () => { - await expectQuery({ - notEqual: { - date: `${JAN_30TH}${SEARCH_SUFFIX}`, - }, - }).toContainExactly([ - { date: JAN_1ST }, - { date: JAN_10TH }, - { dateid: NULL_DATE__ID }, - ]) - }) - }) - - describe("oneOf", () => { - it("successfully finds a row", async () => { - await expectQuery({ - oneOf: { date: [`${JAN_1ST}${SEARCH_SUFFIX}`] }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("fails to find nonexistent row", async () => { - await expectQuery({ - oneOf: { - date: [`${UNEXISTING_DATE}${SEARCH_SUFFIX}`], - }, - }).toFindNothing() - }) - }) - - describe("range", () => { - it("successfully finds a row", async () => { - await expectQuery({ - range: { - date: { - low: `${JAN_1ST}${SEARCH_SUFFIX}`, - high: `${JAN_1ST}${SEARCH_SUFFIX}`, - }, - }, - }).toContainExactly([{ date: JAN_1ST }]) - }) - - it("successfully finds multiple rows", async () => { - await expectQuery({ - range: { - date: { - low: `${JAN_1ST}${SEARCH_SUFFIX}`, - high: `${JAN_10TH}${SEARCH_SUFFIX}`, - }, - }, - }).toContainExactly([ - { date: JAN_1ST }, - { date: JAN_10TH }, - ]) - }) - - it("successfully finds no rows", async () => { - await expectQuery({ - range: { - date: { - low: `${JAN_30TH}${SEARCH_SUFFIX}`, - high: `${JAN_30TH}${SEARCH_SUFFIX}`, - }, - }, - }).toFindNothing() - }) - }) - - describe.only("sort", () => { - it("sorts ascending", async () => { - await expectSearch({ - query: {}, - sort: "date", - sortOrder: SortOrder.ASCENDING, - }).toMatchExactly([ - { dateid: NULL_DATE__ID }, - { date: JAN_1ST }, - { date: JAN_10TH }, - ]) - }) - - it("sorts descending", async () => { - await expectSearch({ - query: {}, - sort: "date", - sortOrder: SortOrder.DESCENDING, - }).toMatchExactly([ - { date: JAN_10TH }, - { date: JAN_1ST }, - { dateid: NULL_DATE__ID }, - ]) - }) - - describe("sortType STRING", () => { - it("sorts ascending", async () => { - await expectSearch({ - query: {}, - sort: "date", - sortType: SortType.STRING, - sortOrder: SortOrder.ASCENDING, - }).toMatchExactly([ - { dateid: NULL_DATE__ID }, - { date: JAN_1ST }, - { date: JAN_10TH }, - ]) - }) - - it("sorts descending", async () => { - await expectSearch({ - query: {}, - sort: "date", - sortType: SortType.STRING, - sortOrder: SortOrder.DESCENDING, - }).toMatchExactly([ - { date: JAN_10TH }, - { date: JAN_1ST }, - { dateid: NULL_DATE__ID }, - ]) - }) - }) - }) - } - ) + }) + }) } ) } diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts index cfdd0d753a..6a30bb5688 100644 --- a/packages/server/src/utilities/schema.ts +++ b/packages/server/src/utilities/schema.ts @@ -7,7 +7,7 @@ import { Table, } from "@budibase/types" import { ValidColumnNameRegex, helpers, utils } from "@budibase/shared-core" -import { db } from "@budibase/backend-core" +import { db, HTTPError, sql } from "@budibase/backend-core" type Rows = Array @@ -180,10 +180,26 @@ export function parse(rows: Rows, table: Table): Rows { !columnSchema.timeOnly && !columnSchema.dateOnly ) { - // If provided must be a valid date + if (columnData && !columnSchema.timeOnly) { + if (!sql.utils.isValidISODateString(columnData)) { + let message = `Invalid format for field "${columnName}": "${columnData}".` + if (columnSchema.dateOnly) { + message += ` Date-only fields must be in the format "YYYY-MM-DD".` + } else { + message += ` Datetime fields must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SSZ".` + } + throw new HTTPError(message, 400) + } + } + if (columnData && columnSchema.timeOnly) { + if (!sql.utils.isValidTime(columnData)) { + throw new HTTPError( + `Invalid format for field "${columnName}": "${columnData}". Time-only fields must be in the format "HH:MM:SS".`, + 400 + ) + } + } parsedRow[columnName] = columnData - ? new Date(columnData).toISOString() - : columnData } else if ( columnType === FieldType.JSON && typeof columnData === "string" From 378e611222c569d6656865574faa615b380adba6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 17:23:12 +0100 Subject: [PATCH 15/73] Remove DEFAULT_VALUES flag check --- .../DataTable/modals/CreateEditColumn.svelte | 91 +++++++++---------- packages/builder/src/helpers/utils.js | 17 ++-- 2 files changed, 51 insertions(+), 57 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index db5b8a7d49..60243b1dab 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -168,7 +168,6 @@ // used to select what different options can be displayed for column type $: canBeDisplay = canBeDisplayColumn(editableColumn) && !editableColumn.autocolumn - $: defaultValuesEnabled = isEnabled("DEFAULT_VALUES") $: canHaveDefault = !required && canHaveDefaultColumn(editableColumn.type) $: canBeRequired = editableColumn?.type !== FieldType.LINK && @@ -300,7 +299,7 @@ } // Ensure we don't have a default value if we can't have one - if (!canHaveDefault || !defaultValuesEnabled) { + if (!canHaveDefault) { delete saveColumn.default } @@ -848,51 +847,49 @@ {/if} - {#if defaultValuesEnabled} - {#if editableColumn.type === FieldType.OPTIONS} - (editableColumn.default = e.detail)} + placeholder="None" + /> + {:else if editableColumn.type === FieldType.ARRAY} + + (editableColumn.default = e.detail?.length ? e.detail : undefined)} + placeholder="None" + /> + {:else if editableColumn.subtype === BBReferenceFieldSubType.USER} + {@const defaultValue = + editableColumn.type === FieldType.BB_REFERENCE_SINGLE + ? SingleUserDefault + : MultiUserDefault} + + (editableColumn.default = e.detail ? defaultValue : undefined)} + /> + {:else} + (editableColumn.default = e.detail)} + bindings={defaultValueBindings} + allowJS + /> {/if} diff --git a/packages/builder/src/helpers/utils.js b/packages/builder/src/helpers/utils.js index 27f459d6f2..7af7d4ad22 100644 --- a/packages/builder/src/helpers/utils.js +++ b/packages/builder/src/helpers/utils.js @@ -7,22 +7,19 @@ import { FIELDS, isAutoColumnUserRelationship, } from "@/constants/backend" -import { isEnabled } from "@/helpers/featureFlags" export function getAutoColumnInformation(enabled = true) { let info = {} for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) { // Because it's possible to replicate the functionality of CREATED_AT and - // CREATED_BY columns, we disable their creation when the DEFAULT_VALUES - // feature flag is enabled. - if (isEnabled("DEFAULT_VALUES")) { - if ( - subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT || - subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY - ) { - continue - } + // CREATED_BY columns with user column default values, we disable their creation + if ( + subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT || + subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY + ) { + continue } + info[subtype] = { enabled, name: AUTO_COLUMN_DISPLAY_NAMES[key] } } return info From 29dcdba7c58f887942d3ca734e3fa84aad676759 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 17:29:06 +0100 Subject: [PATCH 16/73] Lint --- .../components/backend/DataTable/modals/CreateEditColumn.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 60243b1dab..55ac8474a6 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -49,7 +49,6 @@ import { RowUtils, canBeDisplayColumn } from "@budibase/frontend-core" import ServerBindingPanel from "@/components/common/bindings/ServerBindingPanel.svelte" import OptionsEditor from "./OptionsEditor.svelte" - import { isEnabled } from "@/helpers/featureFlags" import { getUserBindings } from "@/dataBinding" export let field From 0bc1e33ef3e79c3edc9485c0017674f87e2f6d16 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Mon, 13 Jan 2025 18:20:22 +0000 Subject: [PATCH 17/73] pr comments --- packages/builder/src/stores/builder/views.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/stores/builder/views.ts b/packages/builder/src/stores/builder/views.ts index 4e309e180d..81085fcb42 100644 --- a/packages/builder/src/stores/builder/views.ts +++ b/packages/builder/src/stores/builder/views.ts @@ -55,7 +55,7 @@ export class ViewsStore extends DerivedBudiStore< delete = async (view: View) => { if (!view.name) { - return + throw new Error("View name is required") } await API.deleteView(view.name) @@ -71,7 +71,7 @@ export class ViewsStore extends DerivedBudiStore< save = async (view: View & { originalName?: string }) => { if (!view.name) { - return + throw new Error("View name is required") } const savedView = await API.saveView(view) From 1fb530dd8e23a32383731c0bfd09617a2a204804 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 14 Jan 2025 09:01:25 +0000 Subject: [PATCH 18/73] Bump version to 3.2.41 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index f1edc49261..e8cd581c98 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.2.40", + "version": "3.2.41", "npmClient": "yarn", "concurrency": 20, "command": { From d9eb7eaa552523a46d09c38f6fc379dcec347a5c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 13:34:09 +0100 Subject: [PATCH 19/73] Type constants --- .../src/constants/backend/{automations.js => automations.ts} | 0 .../builder/src/constants/backend/{backups.js => backups.ts} | 0 packages/builder/src/constants/backend/{index.js => index.ts} | 3 ++- 3 files changed, 2 insertions(+), 1 deletion(-) rename packages/builder/src/constants/backend/{automations.js => automations.ts} (100%) rename packages/builder/src/constants/backend/{backups.js => backups.ts} (100%) rename packages/builder/src/constants/backend/{index.js => index.ts} (98%) diff --git a/packages/builder/src/constants/backend/automations.js b/packages/builder/src/constants/backend/automations.ts similarity index 100% rename from packages/builder/src/constants/backend/automations.js rename to packages/builder/src/constants/backend/automations.ts diff --git a/packages/builder/src/constants/backend/backups.js b/packages/builder/src/constants/backend/backups.ts similarity index 100% rename from packages/builder/src/constants/backend/backups.js rename to packages/builder/src/constants/backend/backups.ts diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.ts similarity index 98% rename from packages/builder/src/constants/backend/index.js rename to packages/builder/src/constants/backend/index.ts index 6ddf4c2138..c28416ac3e 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.ts @@ -4,6 +4,7 @@ import { INTERNAL_TABLE_SOURCE_ID, AutoFieldSubType, Hosting, + FieldSubType, } from "@budibase/types" import { Constants } from "@budibase/frontend-core" @@ -209,7 +210,7 @@ export const Roles = { BUILDER: "BUILDER", } -export function isAutoColumnUserRelationship(subtype) { +export function isAutoColumnUserRelationship(subtype: FieldSubType) { return ( subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY || subtype === AUTO_COLUMN_SUB_TYPES.UPDATED_BY From 1bf99d31de180ac35479b1b3866eb781c836207c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 13:34:46 +0100 Subject: [PATCH 20/73] Type constants --- packages/builder/src/constants/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/builder/src/constants/{index.js => index.ts} (100%) diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.ts similarity index 100% rename from packages/builder/src/constants/index.js rename to packages/builder/src/constants/index.ts From 2fd120c656e6d605a69b186b174b2e50d66156c8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 13:36:25 +0100 Subject: [PATCH 21/73] Convert utils --- packages/builder/src/helpers/keyUtils.js | 7 ------- packages/builder/src/helpers/keyUtils.ts | 7 +++++++ packages/builder/src/helpers/{utils.js => utils.ts} | 0 3 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 packages/builder/src/helpers/keyUtils.js create mode 100644 packages/builder/src/helpers/keyUtils.ts rename packages/builder/src/helpers/{utils.js => utils.ts} (100%) diff --git a/packages/builder/src/helpers/keyUtils.js b/packages/builder/src/helpers/keyUtils.js deleted file mode 100644 index 8d6dfb06dc..0000000000 --- a/packages/builder/src/helpers/keyUtils.js +++ /dev/null @@ -1,7 +0,0 @@ -function handleEnter(fnc) { - return e => e.key === "Enter" && fnc() -} - -export const keyUtils = { - handleEnter, -} diff --git a/packages/builder/src/helpers/keyUtils.ts b/packages/builder/src/helpers/keyUtils.ts new file mode 100644 index 0000000000..f78b05ec2b --- /dev/null +++ b/packages/builder/src/helpers/keyUtils.ts @@ -0,0 +1,7 @@ +function handleEnter(fnc: () => void) { + return (e: KeyboardEvent) => e.key === "Enter" && fnc() +} + +export const keyUtils = { + handleEnter, +} diff --git a/packages/builder/src/helpers/utils.js b/packages/builder/src/helpers/utils.ts similarity index 100% rename from packages/builder/src/helpers/utils.js rename to packages/builder/src/helpers/utils.ts From 819f6f52e3cbb71a3e90ff0f07a4699ca14ff7dd Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 13:48:20 +0100 Subject: [PATCH 22/73] Typings --- .../builder/src/constants/backend/index.ts | 5 +++- packages/builder/src/helpers/utils.ts | 27 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/builder/src/constants/backend/index.ts b/packages/builder/src/constants/backend/index.ts index c28416ac3e..910823a553 100644 --- a/packages/builder/src/constants/backend/index.ts +++ b/packages/builder/src/constants/backend/index.ts @@ -17,7 +17,10 @@ export { export const AUTO_COLUMN_SUB_TYPES = AutoFieldSubType -export const AUTO_COLUMN_DISPLAY_NAMES = { +export const AUTO_COLUMN_DISPLAY_NAMES: Record< + keyof typeof AUTO_COLUMN_SUB_TYPES, + string +> = { AUTO_ID: "Auto ID", CREATED_BY: "Created By", CREATED_AT: "Created At", diff --git a/packages/builder/src/helpers/utils.ts b/packages/builder/src/helpers/utils.ts index 7af7d4ad22..988f4cbb99 100644 --- a/packages/builder/src/helpers/utils.ts +++ b/packages/builder/src/helpers/utils.ts @@ -1,4 +1,4 @@ -import { FieldType } from "@budibase/types" +import { AutoFieldSubType, Automation, FieldType } from "@budibase/types" import { ActionStepID } from "@/constants/backend/automations" import { TableNames } from "@/constants" import { @@ -8,8 +8,14 @@ import { isAutoColumnUserRelationship, } from "@/constants/backend" -export function getAutoColumnInformation(enabled = true) { - let info = {} +type AutoColumnInformation = Partial< + Record +> + +export function getAutoColumnInformation( + enabled = true +): AutoColumnInformation { + const info: AutoColumnInformation = {} for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) { // Because it's possible to replicate the functionality of CREATED_AT and // CREATED_BY columns with user column default values, we disable their creation @@ -19,13 +25,20 @@ export function getAutoColumnInformation(enabled = true) { ) { continue } - - info[subtype] = { enabled, name: AUTO_COLUMN_DISPLAY_NAMES[key] } + const typedKey = key as keyof typeof AUTO_COLUMN_SUB_TYPES + info[subtype] = { + enabled, + name: AUTO_COLUMN_DISPLAY_NAMES[typedKey], + } } return info } -export function buildAutoColumn(tableName, name, subtype) { +export function buildAutoColumn( + tableName: string, + name: string, + subtype: AutoFieldSubType +) { let type, constraints switch (subtype) { case AUTO_COLUMN_SUB_TYPES.UPDATED_BY: @@ -65,7 +78,7 @@ export function buildAutoColumn(tableName, name, subtype) { return base } -export function checkForCollectStep(automation) { +export function checkForCollectStep(automation: Automation) { return automation.definition.steps.some( step => step.stepId === ActionStepID.COLLECT ) From d97c492d36bac6004b92ce29f0eaa59e5b16345e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 14:06:24 +0100 Subject: [PATCH 23/73] Typing utils --- .../builder/src/constants/backend/index.ts | 8 -- packages/builder/src/helpers/utils.ts | 75 +++++++++++-------- .../types/src/documents/app/table/schema.ts | 1 + 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/packages/builder/src/constants/backend/index.ts b/packages/builder/src/constants/backend/index.ts index 910823a553..b7d3f584be 100644 --- a/packages/builder/src/constants/backend/index.ts +++ b/packages/builder/src/constants/backend/index.ts @@ -4,7 +4,6 @@ import { INTERNAL_TABLE_SOURCE_ID, AutoFieldSubType, Hosting, - FieldSubType, } from "@budibase/types" import { Constants } from "@budibase/frontend-core" @@ -213,13 +212,6 @@ export const Roles = { BUILDER: "BUILDER", } -export function isAutoColumnUserRelationship(subtype: FieldSubType) { - return ( - subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY || - subtype === AUTO_COLUMN_SUB_TYPES.UPDATED_BY - ) -} - export const PrettyRelationshipDefinitions = { MANY: "Many rows", ONE: "One row", diff --git a/packages/builder/src/helpers/utils.ts b/packages/builder/src/helpers/utils.ts index 988f4cbb99..140daaef3b 100644 --- a/packages/builder/src/helpers/utils.ts +++ b/packages/builder/src/helpers/utils.ts @@ -1,12 +1,20 @@ -import { AutoFieldSubType, Automation, FieldType } from "@budibase/types" +import { + AutoFieldSubType, + Automation, + DateFieldMetadata, + FieldType, + NumberFieldMetadata, + RelationshipFieldMetadata, + RelationshipType, +} from "@budibase/types" import { ActionStepID } from "@/constants/backend/automations" import { TableNames } from "@/constants" import { AUTO_COLUMN_DISPLAY_NAMES, AUTO_COLUMN_SUB_TYPES, FIELDS, - isAutoColumnUserRelationship, } from "@/constants/backend" +import { utils } from "@budibase/shared-core" type AutoColumnInformation = Partial< Record @@ -38,44 +46,47 @@ export function buildAutoColumn( tableName: string, name: string, subtype: AutoFieldSubType -) { - let type, constraints +): RelationshipFieldMetadata | NumberFieldMetadata | DateFieldMetadata { + const base = { + name, + icon: "ri-magic-line", + autocolumn: true, + } + switch (subtype) { case AUTO_COLUMN_SUB_TYPES.UPDATED_BY: case AUTO_COLUMN_SUB_TYPES.CREATED_BY: - type = FieldType.LINK - constraints = FIELDS.LINK.constraints - break + return { + ...base, + type: FieldType.LINK, + subtype, + constraints: FIELDS.LINK.constraints, + tableId: TableNames.USERS, + fieldName: `${tableName}-${name}`, + relationshipType: RelationshipType.MANY_TO_ONE, + } + case AUTO_COLUMN_SUB_TYPES.AUTO_ID: - type = FieldType.NUMBER - constraints = FIELDS.NUMBER.constraints - break + return { + ...base, + type: FieldType.NUMBER, + subtype, + constraints: FIELDS.NUMBER.constraints, + } case AUTO_COLUMN_SUB_TYPES.UPDATED_AT: case AUTO_COLUMN_SUB_TYPES.CREATED_AT: - type = FieldType.DATETIME - constraints = FIELDS.DATETIME.constraints - break + return { + ...base, + type: FieldType.DATETIME, + subtype, + constraints: FIELDS.DATETIME.constraints, + } + default: - type = FieldType.STRING - constraints = FIELDS.STRING.constraints - break + throw utils.unreachable(subtype, { + message: "Cannot build auto column with supplied subtype", + }) } - if (Object.values(AUTO_COLUMN_SUB_TYPES).indexOf(subtype) === -1) { - throw "Cannot build auto column with supplied subtype" - } - const base = { - name, - type, - subtype, - icon: "ri-magic-line", - autocolumn: true, - constraints, - } - if (isAutoColumnUserRelationship(subtype)) { - base.tableId = TableNames.USERS - base.fieldName = `${tableName}-${name}` - } - return base } export function checkForCollectStep(automation: Automation) { diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index 551b1f16a8..13eecec3a4 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -109,6 +109,7 @@ export interface LongFormFieldMetadata extends BaseFieldSchema { export interface StringFieldMetadata extends BaseFieldSchema { type: FieldType.STRING default?: string + subtype?: never } export interface FormulaFieldMetadata extends BaseFieldSchema { From bdd0d10d0d5af482ab7de2a5b1cf2fdc28c761c8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 14:07:54 +0100 Subject: [PATCH 24/73] Extra typings --- packages/builder/src/helpers/{planTitle.js => planTitle.ts} | 4 ++-- .../builder/src/helpers/{sanitizeUrl.js => sanitizeUrl.ts} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename packages/builder/src/helpers/{planTitle.js => planTitle.ts} (86%) rename packages/builder/src/helpers/{sanitizeUrl.js => sanitizeUrl.ts} (88%) diff --git a/packages/builder/src/helpers/planTitle.js b/packages/builder/src/helpers/planTitle.ts similarity index 86% rename from packages/builder/src/helpers/planTitle.js rename to packages/builder/src/helpers/planTitle.ts index c08b8bf3fe..ab342b4d93 100644 --- a/packages/builder/src/helpers/planTitle.js +++ b/packages/builder/src/helpers/planTitle.ts @@ -1,6 +1,6 @@ import { PlanType } from "@budibase/types" -export function getFormattedPlanName(userPlanType) { +export function getFormattedPlanName(userPlanType: PlanType) { let planName switch (userPlanType) { case PlanType.PRO: @@ -29,6 +29,6 @@ export function getFormattedPlanName(userPlanType) { return `${planName} Plan` } -export function isPremiumOrAbove(userPlanType) { +export function isPremiumOrAbove(userPlanType: PlanType) { return ![PlanType.PRO, PlanType.TEAM, PlanType.FREE].includes(userPlanType) } diff --git a/packages/builder/src/helpers/sanitizeUrl.js b/packages/builder/src/helpers/sanitizeUrl.ts similarity index 88% rename from packages/builder/src/helpers/sanitizeUrl.js rename to packages/builder/src/helpers/sanitizeUrl.ts index 4d00c503fb..5b76930037 100644 --- a/packages/builder/src/helpers/sanitizeUrl.js +++ b/packages/builder/src/helpers/sanitizeUrl.ts @@ -1,4 +1,4 @@ -export default function (url) { +export default function (url: string) { return url .split("/") .map(part => { From b3d18b16cf5841b24452d3a5d0d8b97d412b59de Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 14:11:39 +0100 Subject: [PATCH 25/73] Type pagination --- .../src/helpers/{pagination.js => pagination.ts} | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) rename packages/builder/src/helpers/{pagination.js => pagination.ts} (76%) diff --git a/packages/builder/src/helpers/pagination.js b/packages/builder/src/helpers/pagination.ts similarity index 76% rename from packages/builder/src/helpers/pagination.js rename to packages/builder/src/helpers/pagination.ts index 122973f1a1..0280cd1052 100644 --- a/packages/builder/src/helpers/pagination.js +++ b/packages/builder/src/helpers/pagination.ts @@ -1,6 +1,16 @@ import { writable } from "svelte/store" -function defaultValue() { +interface PaginationStore { + nextPage: string | null | undefined + page: string | null | undefined + hasPrevPage: boolean + hasNextPage: boolean + loading: boolean + pageNumber: number + pages: string[] +} + +function defaultValue(): PaginationStore { return { nextPage: null, page: undefined, @@ -29,13 +39,13 @@ export function createPaginationStore() { update(state => { state.pageNumber++ state.page = state.nextPage - state.pages.push(state.page) + state.pages.push(state.page!) state.hasPrevPage = state.pageNumber > 1 return state }) } - function fetched(hasNextPage, nextPage) { + function fetched(hasNextPage: boolean, nextPage: string) { update(state => { state.hasNextPage = hasNextPage state.nextPage = nextPage From 8f282d54d2830e268d80ae27a12b49da60d64553 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 14:13:20 +0100 Subject: [PATCH 26/73] Type duplicates --- .../builder/src/helpers/{duplicate.js => duplicate.ts} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename packages/builder/src/helpers/{duplicate.js => duplicate.ts} (92%) diff --git a/packages/builder/src/helpers/duplicate.js b/packages/builder/src/helpers/duplicate.ts similarity index 92% rename from packages/builder/src/helpers/duplicate.js rename to packages/builder/src/helpers/duplicate.ts index 361e1faa25..4816bcfd7b 100644 --- a/packages/builder/src/helpers/duplicate.js +++ b/packages/builder/src/helpers/duplicate.ts @@ -10,13 +10,13 @@ * * Repl */ -export const duplicateName = (name, allNames) => { +export const duplicateName = (name: string, allNames: string[]) => { const duplicatePattern = new RegExp(`\\s(\\d+)$`) const baseName = name.split(duplicatePattern)[0] const isDuplicate = new RegExp(`${baseName}\\s(\\d+)$`) // get the sequence from matched names - const sequence = [] + const sequence: number[] = [] allNames.filter(n => { if (n === baseName) { return true @@ -71,9 +71,9 @@ export const duplicateName = (name, allNames) => { * flat array of strings */ export const getSequentialName = ( - items, - prefix, - { getName = x => x, numberFirstItem = false } = {} + items: string[], + prefix: string, + { getName = (x: string) => x, numberFirstItem = false } = {} ) => { if (!prefix?.length || !getName) { return null From 2a853d3992c67e59ba00fbc3c083051080e4b2c1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 14:20:10 +0100 Subject: [PATCH 27/73] Type helpers --- packages/builder/src/helpers/helpers.js | 46 ----------------- packages/builder/src/helpers/helpers.ts | 50 +++++++++++++++++++ .../src/helpers/{index.js => index.ts} | 0 3 files changed, 50 insertions(+), 46 deletions(-) delete mode 100644 packages/builder/src/helpers/helpers.js create mode 100644 packages/builder/src/helpers/helpers.ts rename packages/builder/src/helpers/{index.js => index.ts} (100%) diff --git a/packages/builder/src/helpers/helpers.js b/packages/builder/src/helpers/helpers.js deleted file mode 100644 index 99483d40e2..0000000000 --- a/packages/builder/src/helpers/helpers.js +++ /dev/null @@ -1,46 +0,0 @@ -import { last, flow } from "lodash/fp" - -export const buildStyle = styles => { - let str = "" - for (let s in styles) { - if (styles[s]) { - let key = convertCamel(s) - str += `${key}: ${styles[s]}; ` - } - } - return str -} - -export const convertCamel = str => { - return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) -} - -export const pipe = (arg, funcs) => flow(funcs)(arg) - -export const capitalise = s => { - if (!s) { - return s - } - return s.substring(0, 1).toUpperCase() + s.substring(1) -} - -export const lowercase = s => s.substring(0, 1).toLowerCase() + s.substring(1) - -export const lowercaseExceptFirst = s => - s.charAt(0) + s.substring(1).toLowerCase() - -export const get_name = s => (!s ? "" : last(s.split("/"))) - -export const get_capitalised_name = name => pipe(name, [get_name, capitalise]) - -export const isBuilderInputFocused = e => { - const activeTag = document.activeElement?.tagName.toLowerCase() - const inCodeEditor = document.activeElement?.classList?.contains("cm-content") - if ( - (inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) && - e.key !== "Escape" - ) { - return true - } - return false -} diff --git a/packages/builder/src/helpers/helpers.ts b/packages/builder/src/helpers/helpers.ts new file mode 100644 index 0000000000..f767e6f59f --- /dev/null +++ b/packages/builder/src/helpers/helpers.ts @@ -0,0 +1,50 @@ +import type { Many } from "lodash" +import { last, flow } from "lodash/fp" + +export const buildStyle = (styles: Record) => { + let str = "" + for (let s in styles) { + if (styles[s]) { + let key = convertCamel(s) + str += `${key}: ${styles[s]}; ` + } + } + return str +} + +export const convertCamel = (str: string) => { + return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) +} + +export const pipe = (arg: string, funcs: Many<(...args: any[]) => any>) => + flow(funcs)(arg) + +export const capitalise = (s: string) => { + if (!s) { + return s + } + return s.substring(0, 1).toUpperCase() + s.substring(1) +} + +export const lowercase = (s: string) => + s.substring(0, 1).toLowerCase() + s.substring(1) + +export const lowercaseExceptFirst = (s: string) => + s.charAt(0) + s.substring(1).toLowerCase() + +export const get_name = (s: string) => (!s ? "" : last(s.split("/"))) + +export const get_capitalised_name = (name: string) => + pipe(name, [get_name, capitalise]) + +export const isBuilderInputFocused = (e: KeyboardEvent) => { + const activeTag = document.activeElement?.tagName.toLowerCase() + const inCodeEditor = document.activeElement?.classList?.contains("cm-content") + if ( + (inCodeEditor || ["input", "textarea"].indexOf(activeTag!) !== -1) && + e.key !== "Escape" + ) { + return true + } + return false +} diff --git a/packages/builder/src/helpers/index.js b/packages/builder/src/helpers/index.ts similarity index 100% rename from packages/builder/src/helpers/index.js rename to packages/builder/src/helpers/index.ts From a8d7a97bd678d73621d65e2fd9c69082d7ea52a7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 17:08:37 +0100 Subject: [PATCH 28/73] Type warnings --- packages/builder/src/helpers/{warnings.js => warnings.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/builder/src/helpers/{warnings.js => warnings.ts} (85%) diff --git a/packages/builder/src/helpers/warnings.js b/packages/builder/src/helpers/warnings.ts similarity index 85% rename from packages/builder/src/helpers/warnings.js rename to packages/builder/src/helpers/warnings.ts index ad943a8578..ae6c65666c 100644 --- a/packages/builder/src/helpers/warnings.js +++ b/packages/builder/src/helpers/warnings.ts @@ -1,4 +1,4 @@ -export const suppressWarnings = warnings => { +export const suppressWarnings = (warnings: string[]) => { if (!warnings?.length) { return } From d912e95849295e03e4b363423e792cc9b42eb580 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 17:10:40 +0100 Subject: [PATCH 29/73] Type fetchData --- .../src/helpers/{fetchData.js => fetchData.ts} | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) rename packages/builder/src/helpers/{fetchData.js => fetchData.ts} (57%) diff --git a/packages/builder/src/helpers/fetchData.js b/packages/builder/src/helpers/fetchData.ts similarity index 57% rename from packages/builder/src/helpers/fetchData.js rename to packages/builder/src/helpers/fetchData.ts index 085d0ae5b4..22b82c070e 100644 --- a/packages/builder/src/helpers/fetchData.js +++ b/packages/builder/src/helpers/fetchData.ts @@ -1,13 +1,21 @@ import { writable } from "svelte/store" import { API } from "@/api" -export default function (url) { - const store = writable({ status: "LOADING", data: {}, error: {} }) +export default function (url: string) { + const store = writable<{ + status: "LOADING" | "SUCCESS" | "ERROR" + data: object + error?: unknown + }>({ + status: "LOADING", + data: {}, + error: {}, + }) async function get() { store.update(u => ({ ...u, status: "LOADING" })) try { - const data = await API.get({ url }) + const data = await API.get({ url }) store.set({ data, status: "SUCCESS" }) } catch (e) { store.set({ data: {}, error: e, status: "ERROR" }) From 2558bd3e6ad2e71bb355ac1c97c2dbc25287a6c2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 17:12:20 +0100 Subject: [PATCH 30/73] Type feature flags --- .../builder/src/helpers/{featureFlags.js => featureFlags.ts} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename packages/builder/src/helpers/{featureFlags.js => featureFlags.ts} (58%) diff --git a/packages/builder/src/helpers/featureFlags.js b/packages/builder/src/helpers/featureFlags.ts similarity index 58% rename from packages/builder/src/helpers/featureFlags.js rename to packages/builder/src/helpers/featureFlags.ts index e9054e8a9c..625468f06b 100644 --- a/packages/builder/src/helpers/featureFlags.js +++ b/packages/builder/src/helpers/featureFlags.ts @@ -1,7 +1,8 @@ +import { FeatureFlag } from "@budibase/types" import { auth } from "../stores/portal" import { get } from "svelte/store" -export const isEnabled = featureFlag => { +export const isEnabled = (featureFlag: FeatureFlag) => { const user = get(auth).user return !!user?.flags?.[featureFlag] } From 8281ec4e3ee5f47293e3cba7c49c01a2178b0d5c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 13 Jan 2025 17:13:50 +0100 Subject: [PATCH 31/73] Allow feature flags as strings --- packages/builder/src/helpers/featureFlags.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/helpers/featureFlags.ts b/packages/builder/src/helpers/featureFlags.ts index 625468f06b..faa57892e8 100644 --- a/packages/builder/src/helpers/featureFlags.ts +++ b/packages/builder/src/helpers/featureFlags.ts @@ -2,7 +2,7 @@ import { FeatureFlag } from "@budibase/types" import { auth } from "../stores/portal" import { get } from "svelte/store" -export const isEnabled = (featureFlag: FeatureFlag) => { +export const isEnabled = (featureFlag: FeatureFlag | `${FeatureFlag}`) => { const user = get(auth).user return !!user?.flags?.[featureFlag] } From ca1015baf6b6bc5942c7a5cbd0caf4cd499665ee Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 10:07:50 +0100 Subject: [PATCH 32/73] Remove unexpected never field --- packages/types/src/documents/app/table/schema.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index 13eecec3a4..551b1f16a8 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -109,7 +109,6 @@ export interface LongFormFieldMetadata extends BaseFieldSchema { export interface StringFieldMetadata extends BaseFieldSchema { type: FieldType.STRING default?: string - subtype?: never } export interface FormulaFieldMetadata extends BaseFieldSchema { From 41c8ab976fd4c30661b4e75927c5bdbe4ec7a74e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 10:09:10 +0100 Subject: [PATCH 33/73] Type --- packages/builder/src/helpers/fetchData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/helpers/fetchData.ts b/packages/builder/src/helpers/fetchData.ts index 22b82c070e..cffb8f4d7d 100644 --- a/packages/builder/src/helpers/fetchData.ts +++ b/packages/builder/src/helpers/fetchData.ts @@ -15,7 +15,7 @@ export default function (url: string) { async function get() { store.update(u => ({ ...u, status: "LOADING" })) try { - const data = await API.get({ url }) + const data = await API.get({ url }) store.set({ data, status: "SUCCESS" }) } catch (e) { store.set({ data: {}, error: e, status: "ERROR" }) From 30c942c852e17ed03b51c758641e2aaeda239eac Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 10:45:08 +0100 Subject: [PATCH 34/73] Type options --- packages/frontend-core/src/fetch/DataFetch.ts | 27 +++++++++++-------- packages/frontend-core/src/fetch/index.ts | 10 ++++++- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/frontend-core/src/fetch/DataFetch.ts b/packages/frontend-core/src/fetch/DataFetch.ts index b10a8b0a69..ad8a632480 100644 --- a/packages/frontend-core/src/fetch/DataFetch.ts +++ b/packages/frontend-core/src/fetch/DataFetch.ts @@ -51,7 +51,19 @@ export interface DataFetchParams< API: APIClient datasource: TDatasource query: TQuery - options?: {} + options?: Partial> +} + +export interface DataFetchOptions { + // Search config + filter: UISearchFilter | LegacyFilter[] | null + query: TQuery + // Sorting config + sortColumn: string | null + sortOrder: SortOrder + // Pagination config + limit: number + paginate: boolean } /** @@ -73,18 +85,11 @@ export default abstract class DataFetch< supportsSort: boolean supportsPagination: boolean } - options: { + options: DataFetchOptions & { datasource: TDatasource - limit: number - // Search config - filter: UISearchFilter | LegacyFilter[] | null - query: TQuery - // Sorting config - sortColumn: string | null - sortOrder: SortOrder + sortType: SortType | null - // Pagination config - paginate: boolean + // Client side feature customisation clientSideSearching: boolean clientSideSorting: boolean diff --git a/packages/frontend-core/src/fetch/index.ts b/packages/frontend-core/src/fetch/index.ts index d80aa10df6..4b9de01dae 100644 --- a/packages/frontend-core/src/fetch/index.ts +++ b/packages/frontend-core/src/fetch/index.ts @@ -32,7 +32,15 @@ export const DataFetchMap = { } // Constructs a new fetch model for a certain datasource -export const fetchData = ({ API, datasource, options }: any) => { +export const fetchData = ({ + API, + datasource, + options, +}: { + API: APIClient + datasource: TDatasource + options: any +}) => { const Fetch = DataFetchMap[datasource?.type as DataFetchType] || TableFetch const fetch = new Fetch({ API, datasource, ...options }) From c1981aaa29d537e55980837983a1dbfe2c5021f7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 10:48:50 +0100 Subject: [PATCH 35/73] Rename DataFetch to BaseDataFetch --- packages/frontend-core/src/fetch/CustomFetch.ts | 4 ++-- packages/frontend-core/src/fetch/DataFetch.ts | 2 +- packages/frontend-core/src/fetch/FieldFetch.ts | 4 ++-- packages/frontend-core/src/fetch/GroupUserFetch.ts | 4 ++-- packages/frontend-core/src/fetch/NestedProviderFetch.ts | 4 ++-- packages/frontend-core/src/fetch/QueryFetch.ts | 4 ++-- packages/frontend-core/src/fetch/RelationshipFetch.ts | 4 ++-- packages/frontend-core/src/fetch/TableFetch.ts | 4 ++-- packages/frontend-core/src/fetch/UserFetch.ts | 4 ++-- packages/frontend-core/src/fetch/ViewFetch.ts | 4 ++-- packages/frontend-core/src/fetch/ViewV2Fetch.ts | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/frontend-core/src/fetch/CustomFetch.ts b/packages/frontend-core/src/fetch/CustomFetch.ts index dfd29c4a02..60ce1f50ec 100644 --- a/packages/frontend-core/src/fetch/CustomFetch.ts +++ b/packages/frontend-core/src/fetch/CustomFetch.ts @@ -1,4 +1,4 @@ -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" interface CustomDatasource { type: "custom" @@ -7,7 +7,7 @@ interface CustomDatasource { type CustomDefinition = Record -export default class CustomFetch extends DataFetch< +export default class CustomFetch extends BaseDataFetch< CustomDatasource, CustomDefinition > { diff --git a/packages/frontend-core/src/fetch/DataFetch.ts b/packages/frontend-core/src/fetch/DataFetch.ts index ad8a632480..75c454e2ac 100644 --- a/packages/frontend-core/src/fetch/DataFetch.ts +++ b/packages/frontend-core/src/fetch/DataFetch.ts @@ -71,7 +71,7 @@ export interface DataFetchOptions { * internal table or datasource plus. * For other types of datasource, this class is overridden and extended. */ -export default abstract class DataFetch< +export default abstract class BaseDataFetch< TDatasource extends { type: DataFetchType }, TDefinition extends { schema?: Record | null diff --git a/packages/frontend-core/src/fetch/FieldFetch.ts b/packages/frontend-core/src/fetch/FieldFetch.ts index 694443a5dc..5cc903d87e 100644 --- a/packages/frontend-core/src/fetch/FieldFetch.ts +++ b/packages/frontend-core/src/fetch/FieldFetch.ts @@ -1,5 +1,5 @@ import { Row } from "@budibase/types" -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" type Types = "field" | "queryarray" | "jsonarray" @@ -18,7 +18,7 @@ function isArrayOfStrings(value: string[] | Row[]): value is string[] { return Array.isArray(value) && !!value[0] && typeof value[0] !== "object" } -export default class FieldFetch extends DataFetch< +export default class FieldFetch extends BaseDataFetch< FieldDatasource, FieldDefinition > { diff --git a/packages/frontend-core/src/fetch/GroupUserFetch.ts b/packages/frontend-core/src/fetch/GroupUserFetch.ts index e07e5331d4..f3fdcf5ebb 100644 --- a/packages/frontend-core/src/fetch/GroupUserFetch.ts +++ b/packages/frontend-core/src/fetch/GroupUserFetch.ts @@ -1,5 +1,5 @@ import { get } from "svelte/store" -import DataFetch, { DataFetchParams } from "./DataFetch" +import BaseDataFetch, { DataFetchParams } from "./DataFetch" import { TableNames } from "../constants" interface GroupUserQuery { @@ -12,7 +12,7 @@ interface GroupUserDatasource { tableId: TableNames.USERS } -export default class GroupUserFetch extends DataFetch< +export default class GroupUserFetch extends BaseDataFetch< GroupUserDatasource, {}, GroupUserQuery diff --git a/packages/frontend-core/src/fetch/NestedProviderFetch.ts b/packages/frontend-core/src/fetch/NestedProviderFetch.ts index af121fcef8..b4274e690f 100644 --- a/packages/frontend-core/src/fetch/NestedProviderFetch.ts +++ b/packages/frontend-core/src/fetch/NestedProviderFetch.ts @@ -1,5 +1,5 @@ import { Row, TableSchema } from "@budibase/types" -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" interface NestedProviderDatasource { type: "provider" @@ -14,7 +14,7 @@ interface NestedProviderDefinition { schema?: TableSchema primaryDisplay?: string } -export default class NestedProviderFetch extends DataFetch< +export default class NestedProviderFetch extends BaseDataFetch< NestedProviderDatasource, NestedProviderDefinition > { diff --git a/packages/frontend-core/src/fetch/QueryFetch.ts b/packages/frontend-core/src/fetch/QueryFetch.ts index 09dde86cbd..179deb1a66 100644 --- a/packages/frontend-core/src/fetch/QueryFetch.ts +++ b/packages/frontend-core/src/fetch/QueryFetch.ts @@ -1,4 +1,4 @@ -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" import { Helpers } from "@budibase/bbui" import { ExecuteQueryRequest, Query } from "@budibase/types" import { get } from "svelte/store" @@ -17,7 +17,7 @@ interface QueryDatasource { parameters: { name: string; default: string }[] } -export default class QueryFetch extends DataFetch { +export default class QueryFetch extends BaseDataFetch { async determineFeatureFlags() { const definition = await this.getDefinition() const supportsPagination = diff --git a/packages/frontend-core/src/fetch/RelationshipFetch.ts b/packages/frontend-core/src/fetch/RelationshipFetch.ts index 89a85ab0e4..c84c38e77c 100644 --- a/packages/frontend-core/src/fetch/RelationshipFetch.ts +++ b/packages/frontend-core/src/fetch/RelationshipFetch.ts @@ -1,5 +1,5 @@ import { Table } from "@budibase/types" -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" interface RelationshipDatasource { type: "link" @@ -9,7 +9,7 @@ interface RelationshipDatasource { fieldName: string } -export default class RelationshipFetch extends DataFetch< +export default class RelationshipFetch extends BaseDataFetch< RelationshipDatasource, Table > { diff --git a/packages/frontend-core/src/fetch/TableFetch.ts b/packages/frontend-core/src/fetch/TableFetch.ts index 67cac6b6a7..c39c9341fc 100644 --- a/packages/frontend-core/src/fetch/TableFetch.ts +++ b/packages/frontend-core/src/fetch/TableFetch.ts @@ -1,5 +1,5 @@ import { get } from "svelte/store" -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" import { SortOrder, Table } from "@budibase/types" interface TableDatasource { @@ -7,7 +7,7 @@ interface TableDatasource { tableId: string } -export default class TableFetch extends DataFetch { +export default class TableFetch extends BaseDataFetch { async determineFeatureFlags() { return { supportsSearch: true, diff --git a/packages/frontend-core/src/fetch/UserFetch.ts b/packages/frontend-core/src/fetch/UserFetch.ts index 36aebac506..2632337d64 100644 --- a/packages/frontend-core/src/fetch/UserFetch.ts +++ b/packages/frontend-core/src/fetch/UserFetch.ts @@ -1,5 +1,5 @@ import { get } from "svelte/store" -import DataFetch, { DataFetchParams } from "./DataFetch" +import BaseDataFetch, { DataFetchParams } from "./DataFetch" import { TableNames } from "../constants" import { utils } from "@budibase/shared-core" import { SearchFilters, SearchUsersRequest } from "@budibase/types" @@ -16,7 +16,7 @@ interface UserDatasource { interface UserDefinition {} -export default class UserFetch extends DataFetch< +export default class UserFetch extends BaseDataFetch< UserDatasource, UserDefinition, UserFetchQuery diff --git a/packages/frontend-core/src/fetch/ViewFetch.ts b/packages/frontend-core/src/fetch/ViewFetch.ts index df00b9bbfc..219f17ee2c 100644 --- a/packages/frontend-core/src/fetch/ViewFetch.ts +++ b/packages/frontend-core/src/fetch/ViewFetch.ts @@ -1,5 +1,5 @@ import { Table } from "@budibase/types" -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" type ViewV1Datasource = { type: "view" @@ -10,7 +10,7 @@ type ViewV1Datasource = { groupBy: string } -export default class ViewFetch extends DataFetch { +export default class ViewFetch extends BaseDataFetch { async getDefinition() { const { datasource } = this.options diff --git a/packages/frontend-core/src/fetch/ViewV2Fetch.ts b/packages/frontend-core/src/fetch/ViewV2Fetch.ts index aa5fbd60a2..029047c5fb 100644 --- a/packages/frontend-core/src/fetch/ViewV2Fetch.ts +++ b/packages/frontend-core/src/fetch/ViewV2Fetch.ts @@ -1,5 +1,5 @@ import { SortOrder, ViewV2Enriched, ViewV2Type } from "@budibase/types" -import DataFetch from "./DataFetch" +import BaseDataFetch from "./DataFetch" import { get } from "svelte/store" import { helpers } from "@budibase/shared-core" @@ -8,7 +8,7 @@ interface ViewDatasource { id: string } -export default class ViewV2Fetch extends DataFetch< +export default class ViewV2Fetch extends BaseDataFetch< ViewDatasource, ViewV2Enriched > { From 79039a1c30fe4a59a11fc8edaaf5f8aa1bfc5051 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 10:52:08 +0100 Subject: [PATCH 36/73] Use union type --- .../src/components/grid/stores/rows.ts | 5 ++--- packages/frontend-core/src/fetch/index.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/rows.ts b/packages/frontend-core/src/components/grid/stores/rows.ts index 07fbf02134..159a01a86f 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.ts +++ b/packages/frontend-core/src/components/grid/stores/rows.ts @@ -1,5 +1,5 @@ import { writable, derived, get, Writable, Readable } from "svelte/store" -import { fetchData } from "../../../fetch" +import { DataFetch, fetchData } from "../../../fetch" import { NewRowID, RowPageSize } from "../lib/constants" import { generateRowID, @@ -13,7 +13,6 @@ import { sleep } from "../../../utils/utils" import { FieldType, Row, UIRow } from "@budibase/types" import { getRelatedTableValues } from "../../../utils" import { Store as StoreContext } from "." -import DataFetch from "../../../fetch/DataFetch" interface IndexedUIRow extends UIRow { __idx: number @@ -21,7 +20,7 @@ interface IndexedUIRow extends UIRow { interface RowStore { rows: Writable - fetch: Writable | null> // TODO: type this properly, having a union of all the possible options + fetch: Writable loaded: Writable refreshing: Writable loading: Writable diff --git a/packages/frontend-core/src/fetch/index.ts b/packages/frontend-core/src/fetch/index.ts index 4b9de01dae..1c1d6671e6 100644 --- a/packages/frontend-core/src/fetch/index.ts +++ b/packages/frontend-core/src/fetch/index.ts @@ -31,6 +31,20 @@ export const DataFetchMap = { queryarray: QueryArrayFetch, } +export type DataFetch = + | TableFetch + | ViewFetch + | ViewV2Fetch + | QueryFetch + | RelationshipFetch + | UserFetch + | GroupUserFetch + | CustomFetch + | NestedProviderFetch + | FieldFetch<"field"> + | JSONArrayFetch + | QueryArrayFetch + // Constructs a new fetch model for a certain datasource export const fetchData = ({ API, From bd1a04ff1bcdb49c01b14b9ffa41353bd223f7d8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:01:41 +0100 Subject: [PATCH 37/73] Type UIDatasource --- packages/types/src/ui/stores/grid/datasource.ts | 2 +- packages/types/src/ui/stores/grid/table.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/types/src/ui/stores/grid/datasource.ts b/packages/types/src/ui/stores/grid/datasource.ts index 9927518133..f4210f6725 100644 --- a/packages/types/src/ui/stores/grid/datasource.ts +++ b/packages/types/src/ui/stores/grid/datasource.ts @@ -1,6 +1,6 @@ import { UITable, UIView } from "@budibase/types" -export type UIDatasource = UITable | UIView +export type UIDatasource = UITable | (Omit & { type: "viewV2" }) export interface UIFieldMutation { visible?: boolean diff --git a/packages/types/src/ui/stores/grid/table.ts b/packages/types/src/ui/stores/grid/table.ts index e69f9fd38f..7b6d659e4c 100644 --- a/packages/types/src/ui/stores/grid/table.ts +++ b/packages/types/src/ui/stores/grid/table.ts @@ -8,10 +8,9 @@ import { UISearchFilter, } from "@budibase/types" -export interface UITable extends Omit { +export interface UITable extends Table { name: string id: string - type: string tableId: string primaryDisplay?: string sort?: { From 8624997f4c371fb853159605a5fc81a4289baa99 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:14:30 +0100 Subject: [PATCH 38/73] Fix types --- packages/builder/src/helpers/duplicate.ts | 12 +++++++++--- packages/builder/src/stores/builder/rowActions.ts | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/helpers/duplicate.ts b/packages/builder/src/helpers/duplicate.ts index 4816bcfd7b..35beb4df38 100644 --- a/packages/builder/src/helpers/duplicate.ts +++ b/packages/builder/src/helpers/duplicate.ts @@ -70,10 +70,16 @@ export const duplicateName = (name: string, allNames: string[]) => { * @param getName optional function to extract the name for an item, if not a * flat array of strings */ -export const getSequentialName = ( - items: string[], +export const getSequentialName = ( + items: T[], prefix: string, - { getName = (x: string) => x, numberFirstItem = false } = {} + { + getName, + numberFirstItem, + }: { + getName: (item: T) => string + numberFirstItem?: boolean + } ) => { if (!prefix?.length || !getName) { return null diff --git a/packages/builder/src/stores/builder/rowActions.ts b/packages/builder/src/stores/builder/rowActions.ts index 9576eccd1b..2b3077926e 100644 --- a/packages/builder/src/stores/builder/rowActions.ts +++ b/packages/builder/src/stores/builder/rowActions.ts @@ -62,7 +62,7 @@ export class RowActionStore extends BudiStore { const existingRowActions = get(this)[tableId] || [] name = getSequentialName(existingRowActions, "New row action ", { getName: x => x.name, - }) + })! } if (!name) { From 06013929e44cd822cc19c146f944303fa2a36e90 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:14:34 +0100 Subject: [PATCH 39/73] Fix usage --- .../src/components/common/bindings/SnippetDrawer.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index 90ca6ffd9f..f10c44f81f 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -28,7 +28,9 @@ let loading = false let deleteConfirmationDialog - $: defaultName = getSequentialName($snippets, "MySnippet", x => x.name) + $: defaultName = getSequentialName($snippets, "MySnippet", { + getName: x => x.name, + }) $: key = snippet?.name $: name = snippet?.name || defaultName $: code = snippet?.code ? encodeJSBinding(snippet.code) : "" From bcbf16a69b6b91c415bb6a4b8e0119d9f06e35a7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:25:26 +0100 Subject: [PATCH 40/73] Typing defititions --- .../src/components/grid/stores/datasource.ts | 34 +++++++++++++------ packages/frontend-core/src/fetch/index.ts | 9 +++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/frontend-core/src/components/grid/stores/datasource.ts b/packages/frontend-core/src/components/grid/stores/datasource.ts index 805ace5a8f..588f373152 100644 --- a/packages/frontend-core/src/components/grid/stores/datasource.ts +++ b/packages/frontend-core/src/components/grid/stores/datasource.ts @@ -1,7 +1,11 @@ // TODO: datasource and defitions are unions of the different implementations. At this point, the datasource does not know what type is being used, and the assignations will cause TS exceptions. Casting it "as any" for now. This should be fixed improving the type usages. import { derived, get, Readable, Writable } from "svelte/store" -import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch" +import { + DataFetchDefinition, + getDatasourceDefinition, + getDatasourceSchema, +} from "../../../fetch" import { enrichSchemaWithRelColumns, memo } from "../../../utils" import { cloneDeep } from "lodash" import { @@ -18,7 +22,7 @@ import { Store as StoreContext, BaseStoreProps } from "." import { DatasourceActions } from "./datasources" interface DatasourceStore { - definition: Writable + definition: Writable schemaMutations: Writable> subSchemaMutations: Writable>> } @@ -131,11 +135,17 @@ export const deriveStores = (context: StoreContext): DerivedDatasourceStore => { [datasource, definition], ([$datasource, $definition]) => { let type = $datasource?.type + // @ts-expect-error if (type === "provider") { type = ($datasource as any).value?.datasource?.type // TODO: see line 1 } // Handle calculation views - if (type === "viewV2" && $definition?.type === ViewV2Type.CALCULATION) { + if ( + type === "viewV2" && + $definition && + "type" in $definition && + $definition.type === ViewV2Type.CALCULATION + ) { return false } return !!type && ["table", "viewV2", "link"].includes(type) @@ -197,7 +207,7 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => { ) => { // Update local state const originalDefinition = get(definition) - definition.set(newDefinition as UIDatasource) + definition.set(newDefinition) // Update server if (get(config).canSaveSchema) { @@ -225,13 +235,15 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => { // Update primary display newDefinition.primaryDisplay = column - // Sanitise schema to ensure field is required and has no default value - if (!newDefinition.schema[column].constraints) { - newDefinition.schema[column].constraints = {} - } - newDefinition.schema[column].constraints.presence = { allowEmpty: false } - if ("default" in newDefinition.schema[column]) { - delete newDefinition.schema[column].default + if (newDefinition.schema) { + // Sanitise schema to ensure field is required and has no default value + if (!newDefinition.schema[column].constraints) { + newDefinition.schema[column].constraints = {} + } + newDefinition.schema[column].constraints.presence = { allowEmpty: false } + if ("default" in newDefinition.schema[column]) { + delete newDefinition.schema[column].default + } } return await saveDefinition(newDefinition as any) // TODO: see line 1 } diff --git a/packages/frontend-core/src/fetch/index.ts b/packages/frontend-core/src/fetch/index.ts index 1c1d6671e6..dee0c9dbf2 100644 --- a/packages/frontend-core/src/fetch/index.ts +++ b/packages/frontend-core/src/fetch/index.ts @@ -11,6 +11,7 @@ import GroupUserFetch from "./GroupUserFetch" import CustomFetch from "./CustomFetch" import QueryArrayFetch from "./QueryArrayFetch" import { APIClient } from "../api/types" +import { Table, ViewV2Enriched } from "@budibase/types" export type DataFetchType = keyof typeof DataFetchMap @@ -45,6 +46,14 @@ export type DataFetch = | JSONArrayFetch | QueryArrayFetch +export type DataFetchDefinition = + | Table + | ViewV2Enriched + | { + schema?: Record | null + primaryDisplay?: string + } + // Constructs a new fetch model for a certain datasource export const fetchData = ({ API, From 9f5f0d468e7b3c5d2f1769975aca28e6c2dfc47e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:26:37 +0100 Subject: [PATCH 41/73] Remove TODO --- packages/frontend-core/src/components/grid/stores/rows.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-core/src/components/grid/stores/rows.ts b/packages/frontend-core/src/components/grid/stores/rows.ts index 159a01a86f..7e58808327 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.ts +++ b/packages/frontend-core/src/components/grid/stores/rows.ts @@ -253,7 +253,7 @@ export const createActions = (context: StoreContext): RowActionStore => { // Reset state properties when dataset changes if (!$instanceLoaded || resetRows) { - definition.set($fetch.definition as any) // TODO: datasource and defitions are unions of the different implementations. At this point, the datasource does not know what type is being used, and the assignations will cause TS exceptions. Casting it "as any" for now. This should be fixed improving the type usages. + definition.set($fetch.definition ?? null) } // Reset scroll state when data changes From 35d5e51898a94054284239b5e061557dfdc8c9b9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:35:43 +0100 Subject: [PATCH 42/73] Convert and fix tests --- packages/builder/src/helpers/duplicate.ts | 14 +++++++------- .../tests/{duplicate.test.js => duplicate.test.ts} | 0 2 files changed, 7 insertions(+), 7 deletions(-) rename packages/builder/src/helpers/tests/{duplicate.test.js => duplicate.test.ts} (100%) diff --git a/packages/builder/src/helpers/duplicate.ts b/packages/builder/src/helpers/duplicate.ts index 35beb4df38..b4740b3e52 100644 --- a/packages/builder/src/helpers/duplicate.ts +++ b/packages/builder/src/helpers/duplicate.ts @@ -70,18 +70,18 @@ export const duplicateName = (name: string, allNames: string[]) => { * @param getName optional function to extract the name for an item, if not a * flat array of strings */ -export const getSequentialName = ( - items: T[], - prefix: string, +export const getSequentialName = ( + items: T[] | null, + prefix: string | null, { getName, numberFirstItem, }: { - getName: (item: T) => string + getName?: (item: T) => string numberFirstItem?: boolean - } + } = {} ) => { - if (!prefix?.length || !getName) { + if (!prefix?.length) { return null } const trimmedPrefix = prefix.trim() @@ -91,7 +91,7 @@ export const getSequentialName = ( } let max = 0 items.forEach(item => { - const name = getName(item) + const name = getName?.(item) ?? item if (typeof name !== "string" || !name.startsWith(trimmedPrefix)) { return } diff --git a/packages/builder/src/helpers/tests/duplicate.test.js b/packages/builder/src/helpers/tests/duplicate.test.ts similarity index 100% rename from packages/builder/src/helpers/tests/duplicate.test.js rename to packages/builder/src/helpers/tests/duplicate.test.ts From 0665ff1307a95d6e5b5dd0c50a4b3ab13f206771 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:37:14 +0100 Subject: [PATCH 43/73] Convert test --- .../helpers/tests/{nameHelpers.spec.js => nameHelpers.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/builder/src/helpers/tests/{nameHelpers.spec.js => nameHelpers.spec.ts} (100%) diff --git a/packages/builder/src/helpers/tests/nameHelpers.spec.js b/packages/builder/src/helpers/tests/nameHelpers.spec.ts similarity index 100% rename from packages/builder/src/helpers/tests/nameHelpers.spec.js rename to packages/builder/src/helpers/tests/nameHelpers.spec.ts From 4afaa85b846453636c537fa301061255fed49ce7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:46:58 +0100 Subject: [PATCH 44/73] Types --- packages/client/src/{constants.js => constants.ts} | 0 packages/client/src/sdk.ts | 9 +++++++++ 2 files changed, 9 insertions(+) rename packages/client/src/{constants.js => constants.ts} (100%) create mode 100644 packages/client/src/sdk.ts diff --git a/packages/client/src/constants.js b/packages/client/src/constants.ts similarity index 100% rename from packages/client/src/constants.js rename to packages/client/src/constants.ts diff --git a/packages/client/src/sdk.ts b/packages/client/src/sdk.ts new file mode 100644 index 0000000000..8c9413fb37 --- /dev/null +++ b/packages/client/src/sdk.ts @@ -0,0 +1,9 @@ +import { APIClient } from "@budibase/frontend-core" +import type { ActionTypes } from "./constants" + +export interface SDK { + API: APIClient + styleable: unknown + Provider: unknown + ActionTypes: typeof ActionTypes +} From f1680f494197105c5034f404869979a2af77ac96 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:47:46 +0100 Subject: [PATCH 45/73] Fix imports --- packages/client/src/stores/components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/stores/components.js b/packages/client/src/stores/components.js index d4afa6c7f1..48ade99321 100644 --- a/packages/client/src/stores/components.js +++ b/packages/client/src/stores/components.js @@ -6,7 +6,7 @@ import { screenStore } from "./screens" import { builderStore } from "./builder" import Router from "../components/Router.svelte" import * as AppComponents from "../components/app/index.js" -import { ScreenslotType } from "../constants.js" +import { ScreenslotType } from "../constants" export const BudibasePrefix = "@budibase/standard-components/" From 85ab9e3ce3335f609c89630e1bb13a777b806677 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:51:19 +0100 Subject: [PATCH 46/73] Type and export --- packages/frontend-core/src/fetch/DataFetch.ts | 2 +- packages/frontend-core/src/fetch/index.ts | 1 + packages/frontend-core/src/index.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/fetch/DataFetch.ts b/packages/frontend-core/src/fetch/DataFetch.ts index 75c454e2ac..b82cac2d5e 100644 --- a/packages/frontend-core/src/fetch/DataFetch.ts +++ b/packages/frontend-core/src/fetch/DataFetch.ts @@ -435,7 +435,7 @@ export default abstract class BaseDataFetch< * Resets the data set and updates options * @param newOptions any new options */ - async update(newOptions: any) { + async update(newOptions: DataFetchOptions) { // Check if any settings have actually changed let refresh = false for (const [key, value] of Object.entries(newOptions || {})) { diff --git a/packages/frontend-core/src/fetch/index.ts b/packages/frontend-core/src/fetch/index.ts index dee0c9dbf2..2bb6e09000 100644 --- a/packages/frontend-core/src/fetch/index.ts +++ b/packages/frontend-core/src/fetch/index.ts @@ -14,6 +14,7 @@ import { APIClient } from "../api/types" import { Table, ViewV2Enriched } from "@budibase/types" export type DataFetchType = keyof typeof DataFetchMap +export type { DataFetchOptions } from "./DataFetch" export const DataFetchMap = { table: TableFetch, diff --git a/packages/frontend-core/src/index.ts b/packages/frontend-core/src/index.ts index fd5595e04d..11354be49d 100644 --- a/packages/frontend-core/src/index.ts +++ b/packages/frontend-core/src/index.ts @@ -1,7 +1,7 @@ export { createAPIClient } from "./api" export type { APIClient } from "./api" export { fetchData, DataFetchMap } from "./fetch" -export type { DataFetchType } from "./fetch" +export type { DataFetchType, DataFetchOptions } from "./fetch" export * as Constants from "./constants" export * from "./stores" export * from "./utils" From 7d284ae50e062d943c141d2fcf8279b8909dfeaf Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 14 Jan 2025 11:57:20 +0100 Subject: [PATCH 47/73] Type DataProvider --- .../src/components/app/DataProvider.svelte | 40 +++++++++++++------ packages/client/src/{sdk.ts => index.ts} | 5 +++ packages/frontend-core/src/fetch/DataFetch.ts | 2 +- 3 files changed, 34 insertions(+), 13 deletions(-) rename packages/client/src/{sdk.ts => index.ts} (70%) diff --git a/packages/client/src/components/app/DataProvider.svelte b/packages/client/src/components/app/DataProvider.svelte index e6629aa3f3..8b06190727 100644 --- a/packages/client/src/components/app/DataProvider.svelte +++ b/packages/client/src/components/app/DataProvider.svelte @@ -1,8 +1,18 @@ -