From b4a8f22b2ee54914294eadb0259fde5f1006879a Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 11 Oct 2023 10:09:14 +0100 Subject: [PATCH 001/134] Initial commit --- .../design/settings/componentSettings.js | 2 + .../ButtonConfiguration.svelte | 104 ++++++++++ .../ButtonConfiguration/ButtonSetting.svelte | 61 ++++++ .../controls/FieldConfiguration.svelte | 91 --------- .../EditFieldPopover.svelte | 38 ++-- .../FieldConfiguration.svelte | 4 +- .../new/_components/componentStructure.json | 1 + packages/client/manifest.json | 183 ++++++++++++++++++ .../components/app/forms/ButtonGroup.svelte | 38 ++++ .../client/src/components/app/forms/index.js | 1 + 10 files changed, 412 insertions(+), 111 deletions(-) create mode 100644 packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte create mode 100644 packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte delete mode 100644 packages/builder/src/components/design/settings/controls/FieldConfiguration.svelte create mode 100644 packages/client/src/components/app/forms/ButtonGroup.svelte diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index 4c49587372..232b4bef31 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -23,6 +23,7 @@ import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte" import GridColumnEditor from "./controls/ColumnEditor/GridColumnEditor.svelte" import BarButtonList from "./controls/BarButtonList.svelte" import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte" +import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte" import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte" const componentMap = { @@ -48,6 +49,7 @@ const componentMap = { "filter/relationship": RelationshipFilterEditor, url: URLSelect, fieldConfiguration: FieldConfiguration, + buttonConfiguration: ButtonConfiguration, columns: ColumnEditor, "columns/basic": BasicColumnEditor, "columns/grid": GridColumnEditor, diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte new file mode 100644 index 0000000000..baa8ad733c --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte @@ -0,0 +1,104 @@ + + +
+ {#if buttonList?.length} + + {/if} +
+ + diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte new file mode 100644 index 0000000000..851cbe289c --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte @@ -0,0 +1,61 @@ + + +
+
+ +
{item.text}
+
+
+ {console.log("REMOVE ME")}} + /> +
+
+ + diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration.svelte deleted file mode 100644 index 80f4829d71..0000000000 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - -Configure columns - - - Configure the columns in your {subject.toLowerCase()}. - - - - diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte index 7d2eaae478..72e7784727 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte @@ -61,6 +61,25 @@ dispatch("change", update) } + + const customPositionHandler = (anchorBounds, eleBounds, cfg) => { + let { left, top } = cfg + let percentageOffset = 30 + // left-outside + left = anchorBounds.left - eleBounds.width - 18 + + // shift up from the anchor, if space allows + let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset + let defaultTop = anchorBounds.top - offsetPos + + if (window.innerHeight - defaultTop < eleBounds.height) { + top = window.innerHeight - eleBounds.height - 5 + } else { + top = anchorBounds.top - offsetPos + } + + return { ...cfg, left, top } + } 0} maxHeight={600} - handlePostionUpdate={(anchorBounds, eleBounds, cfg) => { - let { left, top } = cfg - let percentageOffset = 30 - // left-outside - left = anchorBounds.left - eleBounds.width - 18 - - // shift up from the anchor, if space allows - let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset - let defaultTop = anchorBounds.top - offsetPos - - if (window.innerHeight - defaultTop < eleBounds.height) { - top = window.innerHeight - eleBounds.height - 5 - } else { - top = anchorBounds.top - offsetPos - } - - return { ...cfg, left, top } - }} + handlePostionUpdate={customPositionHandler} > diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index 4169cb7d3d..42651a4d84 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -50,7 +50,7 @@ updateSanitsedFields(sanitisedValue) unconfigured = buildUnconfiguredOptions(schema, sanitisedFields) fieldList = [...sanitisedFields, ...unconfigured] - .map(buildSudoInstance) + .map(buildPseudoInstance) .filter(x => x != null) } @@ -104,7 +104,7 @@ }) } - const buildSudoInstance = instance => { + const buildPseudoInstance = instance => { if (instance._component) { return instance } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json index 11a130490a..dd129be11e 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json @@ -36,6 +36,7 @@ "heading", "text", "button", + "buttongroup", "tag", "spectrumcard", "cardstat", diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 4e56ca758d..27e56d94db 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -258,6 +258,189 @@ "description": "Contains your app screens", "static": true }, + + "buttongroup": { + "name": "Button group", + "icon": "Button", + "hasChildren": false, + "settings": [ + { + "section": true, + "name": "Buttons", + "settings": [ + { + "type": "buttonConfiguration", + "key": "buttons", + "nested": true, + "defaultValue" : [{ + "component" : "button", + "props" : { + "type": "cta" + } + },{ + "component" : "button", + "props" : { + "type" : "primary" + } + }] + } + ] + }, + { + "section": true, + "name": "Layout", + "settings": [ + { + "type": "select", + "label": "Direction", + "key": "direction", + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "Column", + "value": "column", + "barIcon": "ViewColumn", + "barTitle": "Column layout" + }, + { + "label": "Row", + "value": "row", + "barIcon": "ViewRow", + "barTitle": "Row layout" + } + ], + "defaultValue": "column" + }, + { + "type": "select", + "label": "Horiz. align", + "key": "hAlign", + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "Left", + "value": "left", + "barIcon": "AlignLeft", + "barTitle": "Align left" + }, + { + "label": "Center", + "value": "center", + "barIcon": "AlignCenter", + "barTitle": "Align center" + }, + { + "label": "Right", + "value": "right", + "barIcon": "AlignRight", + "barTitle": "Align right" + }, + { + "label": "Stretch", + "value": "stretch", + "barIcon": "MoveLeftRight", + "barTitle": "Align stretched horizontally" + } + ], + "defaultValue": "left" + }, + { + "type": "select", + "label": "Vert. align", + "key": "vAlign", + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "Top", + "value": "top", + "barIcon": "AlignTop", + "barTitle": "Align top" + }, + { + "label": "Middle", + "value": "middle", + "barIcon": "AlignMiddle", + "barTitle": "Align middle" + }, + { + "label": "Bottom", + "value": "bottom", + "barIcon": "AlignBottom", + "barTitle": "Align bottom" + }, + { + "label": "Stretch", + "value": "stretch", + "barIcon": "MoveUpDown", + "barTitle": "Align stretched vertically" + } + ], + "defaultValue": "top" + }, + { + "type": "select", + "label": "Size", + "key": "size", + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "Shrink", + "value": "shrink", + "barIcon": "Minimize", + "barTitle": "Shrink container" + }, + { + "label": "Grow", + "value": "grow", + "barIcon": "Maximize", + "barTitle": "Grow container" + } + ], + "defaultValue": "shrink" + }, + { + "type": "select", + "label": "Gap", + "key": "gap", + "showInBar": true, + "barStyle": "picker", + "options": [ + { + "label": "None", + "value": "N" + }, + { + "label": "Small", + "value": "S" + }, + { + "label": "Medium", + "value": "M" + }, + { + "label": "Large", + "value": "L" + } + ], + "defaultValue": "M" + }, + { + "type": "boolean", + "label": "Wrap", + "key": "wrap", + "showInBar": true, + "barIcon": "ModernGridView", + "barTitle": "Wrap" + } + ] + } + ] + }, + "button": { "name": "Button", "description": "A basic html button that is ready for styling", diff --git a/packages/client/src/components/app/forms/ButtonGroup.svelte b/packages/client/src/components/app/forms/ButtonGroup.svelte new file mode 100644 index 0000000000..222e91a55f --- /dev/null +++ b/packages/client/src/components/app/forms/ButtonGroup.svelte @@ -0,0 +1,38 @@ + + + + + + {#each buttons as { text, type, quiet, disabled, onClick, size }} + + {/each} + + + diff --git a/packages/client/src/components/app/forms/index.js b/packages/client/src/components/app/forms/index.js index 5804d3a79d..24d7f11c0c 100644 --- a/packages/client/src/components/app/forms/index.js +++ b/packages/client/src/components/app/forms/index.js @@ -16,3 +16,4 @@ export { default as jsonfield } from "./JSONField.svelte" export { default as s3upload } from "./S3Upload.svelte" export { default as codescanner } from "./CodeScannerField.svelte" export { default as bbreferencefield } from "./BBReferenceField.svelte" +export { default as buttongroup } from "./ButtonGroup.svelte" \ No newline at end of file From b8c87224f76b0ec77e65072fd78df917bff7a7bf Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 13 Oct 2023 14:53:39 +0100 Subject: [PATCH 002/134] Add/Remove button behaviour, binding label support and default text behaviour. Refactoring and renaming of EditComponentPopoever component --- .../ButtonConfiguration.svelte | 112 ++++++++++++++---- .../ButtonConfiguration/ButtonSetting.svelte | 33 +++--- .../{ => DraggableList}/DraggableList.svelte | 32 ++++- .../controls/DraggableList/drag-handle.svelte | 31 +++++ ...ver.svelte => EditComponentPopover.svelte} | 65 ++++------ .../FieldConfiguration.svelte | 2 +- .../FieldConfiguration/FieldSetting.svelte | 56 ++++++++- packages/client/manifest.json | 22 ++-- .../src/components/app/ButtonGroup.svelte | 37 ++++++ .../components/app/forms/ButtonGroup.svelte | 38 ------ .../client/src/components/app/forms/index.js | 3 +- packages/client/src/components/app/index.js | 1 + 12 files changed, 281 insertions(+), 151 deletions(-) rename packages/builder/src/components/design/settings/controls/{ => DraggableList}/DraggableList.svelte (82%) create mode 100644 packages/builder/src/components/design/settings/controls/DraggableList/drag-handle.svelte rename packages/builder/src/components/design/settings/controls/{FieldConfiguration/EditFieldPopover.svelte => EditComponentPopover.svelte} (63%) create mode 100644 packages/client/src/components/app/ButtonGroup.svelte delete mode 100644 packages/client/src/components/app/forms/ButtonGroup.svelte diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte index baa8ad733c..fde888d17b 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte @@ -1,5 +1,5 @@
- -
{item.text}
+
{readableText || "Button"}
- {console.log("REMOVE ME")}} - /> + removeButton(item._id)} + />
diff --git a/packages/builder/src/components/design/settings/controls/DraggableList.svelte b/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte similarity index 82% rename from packages/builder/src/components/design/settings/controls/DraggableList.svelte rename to packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte index c8395b2a1f..1992299e90 100644 --- a/packages/builder/src/components/design/settings/controls/DraggableList.svelte +++ b/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte @@ -1,10 +1,10 @@
- -
{item.label || item.field}
+ > +
+ + {item.field} +
+ +
{readableText}
@@ -53,4 +81,20 @@ .list-item-body { justify-content: space-between; } + .type-icon { + display: flex; + gap: var(--spacing-m); + margin: var(--spacing-xl); + margin-bottom: 0px; + height: var(--spectrum-alias-item-height-m); + padding: 0px var(--spectrum-alias-item-padding-m); + border-width: var(--spectrum-actionbutton-border-size); + border-radius: var(--spectrum-alias-border-radius-regular); + border: 1px solid + var( + --spectrum-actionbutton-m-border-color, + var(--spectrum-alias-border-color) + ); + align-items: center; + } diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 27e56d94db..a8559d4f79 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -258,7 +258,6 @@ "description": "Contains your app screens", "static": true }, - "buttongroup": { "name": "Button group", "icon": "Button", @@ -272,17 +271,14 @@ "type": "buttonConfiguration", "key": "buttons", "nested": true, - "defaultValue" : [{ - "component" : "button", - "props" : { + "defaultValue": [ + { "type": "cta" + }, + { + "type": "primary" } - },{ - "component" : "button", - "props" : { - "type" : "primary" - } - }] + ] } ] }, @@ -310,7 +306,7 @@ "barTitle": "Row layout" } ], - "defaultValue": "column" + "defaultValue": "row" }, { "type": "select", @@ -440,7 +436,6 @@ } ] }, - "button": { "name": "Button", "description": "A basic html button that is ready for styling", @@ -2592,7 +2587,6 @@ "key": "disabled", "defaultValue": false }, - { "type": "text", "label": "Initial form step", @@ -5875,4 +5869,4 @@ } ] } -} +} \ No newline at end of file diff --git a/packages/client/src/components/app/ButtonGroup.svelte b/packages/client/src/components/app/ButtonGroup.svelte new file mode 100644 index 0000000000..8d6cacd7a5 --- /dev/null +++ b/packages/client/src/components/app/ButtonGroup.svelte @@ -0,0 +1,37 @@ + + + + + {#each buttons as { text, type, quiet, disabled, onClick, size }, idx} + + {/each} + + diff --git a/packages/client/src/components/app/forms/ButtonGroup.svelte b/packages/client/src/components/app/forms/ButtonGroup.svelte deleted file mode 100644 index 222e91a55f..0000000000 --- a/packages/client/src/components/app/forms/ButtonGroup.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - {#each buttons as { text, type, quiet, disabled, onClick, size }} - - {/each} - - - diff --git a/packages/client/src/components/app/forms/index.js b/packages/client/src/components/app/forms/index.js index 24d7f11c0c..38372f32d4 100644 --- a/packages/client/src/components/app/forms/index.js +++ b/packages/client/src/components/app/forms/index.js @@ -15,5 +15,4 @@ export { default as formstep } from "./FormStep.svelte" export { default as jsonfield } from "./JSONField.svelte" export { default as s3upload } from "./S3Upload.svelte" export { default as codescanner } from "./CodeScannerField.svelte" -export { default as bbreferencefield } from "./BBReferenceField.svelte" -export { default as buttongroup } from "./ButtonGroup.svelte" \ No newline at end of file +export { default as bbreferencefield } from "./BBReferenceField.svelte" \ No newline at end of file diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js index 060c15a857..97df3741e1 100644 --- a/packages/client/src/components/app/index.js +++ b/packages/client/src/components/app/index.js @@ -19,6 +19,7 @@ export { default as dataprovider } from "./DataProvider.svelte" export { default as divider } from "./Divider.svelte" export { default as screenslot } from "./ScreenSlot.svelte" export { default as button } from "./Button.svelte" +export { default as buttongroup } from "./ButtonGroup.svelte" export { default as repeater } from "./Repeater.svelte" export { default as text } from "./Text.svelte" export { default as layout } from "./Layout.svelte" From 166c517ff82d4644f37f5e6fe3b4ff2fef2b0d94 Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 13 Oct 2023 15:02:18 +0100 Subject: [PATCH 003/134] Fix Block import locations --- packages/client/src/components/app/ButtonGroup.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/app/ButtonGroup.svelte b/packages/client/src/components/app/ButtonGroup.svelte index 8d6cacd7a5..0af16e6950 100644 --- a/packages/client/src/components/app/ButtonGroup.svelte +++ b/packages/client/src/components/app/ButtonGroup.svelte @@ -1,6 +1,6 @@
- {#if buttonList?.length} + {#if buttonCount} - {#each buttons as { text, type, quiet, disabled, onClick, size }, idx} + {#each buttons as { text, type, quiet, disabled, onClick, size }} Date: Wed, 18 Oct 2023 10:31:36 +0100 Subject: [PATCH 006/134] Create endpoint and controller function for user column migration. --- packages/server/src/api/controllers/table/index.ts | 2 ++ packages/server/src/api/routes/table.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 44f673f284..2780185ed7 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -158,3 +158,5 @@ export async function validateExistingTableImport(ctx: UserCtx) { ctx.status = 422 } } + +export async function migrate(ctx: UserCtx) {} diff --git a/packages/server/src/api/routes/table.ts b/packages/server/src/api/routes/table.ts index 7ffa5acb3e..1aa23257fb 100644 --- a/packages/server/src/api/routes/table.ts +++ b/packages/server/src/api/routes/table.ts @@ -167,4 +167,11 @@ router tableController.bulkImport ) + .post( + "/api/tables/:tableId/migrate", + paramResource("tableId"), + authorized(BUILDER), + tableController.migrate + ) + export default router From 5747f30b5f5cccd7ea5b78a6565e56edfc919400 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 18 Oct 2023 12:04:50 +0100 Subject: [PATCH 007/134] Precondition checks to make sure the migration is from the right column to the right column. --- .../server/src/api/controllers/table/index.ts | 56 ++++++++++++++++++- packages/types/src/api/web/app/table.ts | 11 ++++ .../types/src/documents/app/table/schema.ts | 30 ++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 2780185ed7..36dee4811e 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -11,11 +11,16 @@ import { BulkImportRequest, BulkImportResponse, FetchTablesResponse, + InternalTable, + MigrateRequest, + MigrateResponse, SaveTableRequest, SaveTableResponse, Table, TableResponse, UserCtx, + isBBReferenceField, + isRelationshipField, } from "@budibase/types" import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" @@ -159,4 +164,53 @@ export async function validateExistingTableImport(ctx: UserCtx) { } } -export async function migrate(ctx: UserCtx) {} +function error(ctx: UserCtx, message: string, status = 400) { + ctx.status = status + ctx.body = { message } +} + +export async function migrate(ctx: UserCtx) { + const { tableId, oldColumn, newColumn } = ctx.request.body + + // For now we're only supporting migrations of user relationships to user + // columns in internal tables. In future we may want to support other + // migrations but for now return an error if we aren't migrating a user + // relationship. + if (isExternalTable(tableId)) { + return error(ctx, "External tables cannot be migrated") + } + + const table = await sdk.tables.getTable(tableId) + + if (!(oldColumn.name in table.schema)) { + return error( + ctx, + `Column "${oldColumn.name}" does not exist on table "${table.name}"` + ) + } + + if (newColumn.name in table.schema) { + return error( + ctx, + `Column "${newColumn.name}" already exists on table "${table.name}"` + ) + } + + if (!isBBReferenceField(newColumn)) { + return error(ctx, `Column "${newColumn.name}" is not a user column`) + } + + if (newColumn.subtype !== "user" && newColumn.subtype !== "users") { + return error(ctx, `Column "${newColumn.name}" is not a user column`) + } + + if (!isRelationshipField(oldColumn)) { + return error(ctx, `Column "${oldColumn.name}" is not a user relationship`) + } + + if (oldColumn.tableId !== InternalTable.USER_METADATA) { + return error(ctx, `Column "${oldColumn.name}" is not a user relationship`) + } + + let rows = await sdk.rows.fetch(tableId) +} diff --git a/packages/types/src/api/web/app/table.ts b/packages/types/src/api/web/app/table.ts index cb5faaa9ea..61da12d041 100644 --- a/packages/types/src/api/web/app/table.ts +++ b/packages/types/src/api/web/app/table.ts @@ -1,4 +1,5 @@ import { + FieldSchema, Row, Table, TableRequest, @@ -33,3 +34,13 @@ export interface BulkImportRequest { export interface BulkImportResponse { message: string } + +export interface MigrateRequest { + tableId: string + oldColumn: FieldSchema + newColumn: FieldSchema +} + +export interface MigrateResponse { + message: string +} diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index e529a8e8b7..755ccf61e7 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -164,3 +164,33 @@ export type FieldSchema = export interface TableSchema { [key: string]: FieldSchema } + +export function isRelationshipField( + field: FieldSchema +): field is RelationshipFieldMetadata { + return field.type === FieldType.LINK +} + +export function isManyToMany( + field: RelationshipFieldMetadata +): field is ManyToManyRelationshipFieldMetadata { + return field.relationshipType === RelationshipType.MANY_TO_MANY +} + +export function isOneToMany( + field: RelationshipFieldMetadata +): field is OneToManyRelationshipFieldMetadata { + return field.relationshipType === RelationshipType.ONE_TO_MANY +} + +export function isManyToOne( + field: RelationshipFieldMetadata +): field is ManyToOneRelationshipFieldMetadata { + return field.relationshipType === RelationshipType.MANY_TO_ONE +} + +export function isBBReferenceField( + field: FieldSchema +): field is BBReferenceFieldMetadata { + return field.type === FieldType.BB_REFERENCE +} From 0d67f000f03e1c27692e92dcf866cd36b55435cd Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 18 Oct 2023 12:19:24 +0100 Subject: [PATCH 008/134] Initial reflow of the block settings --- packages/client/manifest.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 8d0a4e456f..d5fca15fd7 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5288,17 +5288,17 @@ }, "settings": [ { - "type": "select", + "type": "table", + "label": "Data", + "key": "dataSource" + }, + { + "type": "radio", "label": "Type", "key": "actionType", "options": ["Create", "Update", "View"], "defaultValue": "Create" }, - { - "type": "table", - "label": "Data", - "key": "dataSource" - }, { "type": "text", "label": "Title", @@ -5323,7 +5323,7 @@ }, { "type": "text", - "label": "Empty text", + "label": "No rows found", "key": "noRowsMessage", "defaultValue": "We couldn't find a row to display", "nested": true From c25de74e174c9e2fa895f5aa8e2a5cc51f9968bf Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 18 Oct 2023 15:14:34 +0100 Subject: [PATCH 009/134] Action Michael's feedback about the structure of this feature. --- .../server/src/api/controllers/table/index.ts | 50 +---------- packages/server/src/sdk/app/tables/index.ts | 12 +++ .../server/src/sdk/app/tables/migration.ts | 84 +++++++++++++++++++ 3 files changed, 97 insertions(+), 49 deletions(-) create mode 100644 packages/server/src/sdk/app/tables/migration.ts diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 36dee4811e..132bae3d81 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -11,7 +11,6 @@ import { BulkImportRequest, BulkImportResponse, FetchTablesResponse, - InternalTable, MigrateRequest, MigrateResponse, SaveTableRequest, @@ -19,8 +18,6 @@ import { Table, TableResponse, UserCtx, - isBBReferenceField, - isRelationshipField, } from "@budibase/types" import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" @@ -164,53 +161,8 @@ export async function validateExistingTableImport(ctx: UserCtx) { } } -function error(ctx: UserCtx, message: string, status = 400) { - ctx.status = status - ctx.body = { message } -} - export async function migrate(ctx: UserCtx) { const { tableId, oldColumn, newColumn } = ctx.request.body - - // For now we're only supporting migrations of user relationships to user - // columns in internal tables. In future we may want to support other - // migrations but for now return an error if we aren't migrating a user - // relationship. - if (isExternalTable(tableId)) { - return error(ctx, "External tables cannot be migrated") - } - const table = await sdk.tables.getTable(tableId) - - if (!(oldColumn.name in table.schema)) { - return error( - ctx, - `Column "${oldColumn.name}" does not exist on table "${table.name}"` - ) - } - - if (newColumn.name in table.schema) { - return error( - ctx, - `Column "${newColumn.name}" already exists on table "${table.name}"` - ) - } - - if (!isBBReferenceField(newColumn)) { - return error(ctx, `Column "${newColumn.name}" is not a user column`) - } - - if (newColumn.subtype !== "user" && newColumn.subtype !== "users") { - return error(ctx, `Column "${newColumn.name}" is not a user column`) - } - - if (!isRelationshipField(oldColumn)) { - return error(ctx, `Column "${oldColumn.name}" is not a user relationship`) - } - - if (oldColumn.tableId !== InternalTable.USER_METADATA) { - return error(ctx, `Column "${oldColumn.name}" is not a user relationship`) - } - - let rows = await sdk.rows.fetch(tableId) + await sdk.tables.migrate(table, oldColumn, newColumn) } diff --git a/packages/server/src/sdk/app/tables/index.ts b/packages/server/src/sdk/app/tables/index.ts index 64fcde4bff..e4884b2ec4 100644 --- a/packages/server/src/sdk/app/tables/index.ts +++ b/packages/server/src/sdk/app/tables/index.ts @@ -7,6 +7,7 @@ import { } from "../../../integrations/utils" import { Database, + FieldSchema, Table, TableResponse, TableViewsResponse, @@ -14,6 +15,7 @@ import { import datasources from "../datasources" import { populateExternalTableSchemas } from "./validation" import sdk from "../../../sdk" +import { migrate } from "./migration" async function getAllInternalTables(db?: Database): Promise { if (!db) { @@ -84,6 +86,14 @@ async function saveTable(table: Table) { } } +async function addColumn(table: Table, newColumn: FieldSchema) { + if (newColumn.name in table.schema) { + throw `Column "${newColumn.name}" already exists on table "${table.name}"` + } + table.schema[newColumn.name] = newColumn + await saveTable(table) +} + export default { getAllInternalTables, getAllExternalTables, @@ -92,4 +102,6 @@ export default { populateExternalTableSchemas, enrichViewSchemas, saveTable, + addColumn, + migrate, } diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts new file mode 100644 index 0000000000..f727b5ea9d --- /dev/null +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -0,0 +1,84 @@ +import { BadRequestError } from "@budibase/backend-core" +import { + BBReferenceFieldMetadata, + FieldSchema, + InternalTable, + RelationshipFieldMetadata, + Table, + isBBReferenceField, + isRelationshipField, +} from "@budibase/types" +import { isExternalTable } from "src/integrations/utils" +import sdk from "../../../sdk" + +export async function migrate( + table: Table, + oldColumn: FieldSchema, + newColumn: FieldSchema +) { + let migrator = getColumnMigrator(table, oldColumn, newColumn) + + await sdk.tables.addColumn(table, newColumn) + + migrator.doMigration() +} + +interface ColumnMigrator { + doMigration(): Promise +} + +function getColumnMigrator( + table: Table, + oldColumn: FieldSchema, + newColumn: FieldSchema +): ColumnMigrator { + // For now we're only supporting migrations of user relationships to user + // columns in internal tables. In future we may want to support other + // migrations but for now return an error if we aren't migrating a user + // relationship. + if (isExternalTable(table._id!)) { + throw new BadRequestError("External tables cannot be migrated") + } + + if (!(oldColumn.name in table.schema)) { + throw new BadRequestError(`Column "${oldColumn.name}" does not exist`) + } + + if (newColumn.name in table.schema) { + throw new BadRequestError(`Column "${newColumn.name}" already exists`) + } + + if (!isBBReferenceField(newColumn)) { + throw new BadRequestError(`Column "${newColumn.name}" is not a user column`) + } + + if (newColumn.subtype !== "user" && newColumn.subtype !== "users") { + throw new BadRequestError(`Column "${newColumn.name}" is not a user column`) + } + + if (!isRelationshipField(oldColumn)) { + throw new BadRequestError( + `Column "${oldColumn.name}" is not a user relationship` + ) + } + + if (oldColumn.tableId !== InternalTable.USER_METADATA) { + throw new BadRequestError( + `Column "${oldColumn.name}" is not a user relationship` + ) + } + + return new UserColumnMigrator(table, oldColumn, newColumn) +} + +class UserColumnMigrator implements ColumnMigrator { + constructor( + private table: Table, + private oldColumn: RelationshipFieldMetadata, + private newColumn: BBReferenceFieldMetadata + ) {} + + async doMigration() { + let rows = await sdk.rows.fetch(this.table._id!) + } +} From 6ae5451fdf46db2dce172047a310b64ebbf596a7 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 18 Oct 2023 16:56:55 +0100 Subject: [PATCH 010/134] Create failing test. --- .../server/src/api/controllers/table/index.ts | 6 ++- .../server/src/api/routes/tests/table.spec.ts | 44 +++++++++++++++++++ packages/server/src/sdk/app/links/index.ts | 5 +++ packages/server/src/sdk/app/links/links.ts | 32 ++++++++++++++ packages/server/src/sdk/app/rows/search.ts | 6 +-- .../server/src/sdk/app/tables/migration.ts | 3 +- packages/server/src/sdk/index.ts | 2 + .../server/src/tests/utilities/api/table.ts | 22 +++++++++- packages/types/src/api/web/app/table.ts | 1 - 9 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 packages/server/src/sdk/app/links/index.ts create mode 100644 packages/server/src/sdk/app/links/links.ts diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 132bae3d81..75532dbfdf 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -162,7 +162,11 @@ export async function validateExistingTableImport(ctx: UserCtx) { } export async function migrate(ctx: UserCtx) { - const { tableId, oldColumn, newColumn } = ctx.request.body + const { oldColumn, newColumn } = ctx.request.body + let tableId = ctx.params.tableId as string const table = await sdk.tables.getTable(tableId) await sdk.tables.migrate(table, oldColumn, newColumn) + + ctx.status = 200 + ctx.body = { message: `Column ${oldColumn.name} migrated.` } } diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index ded54729b9..eaf3757ea8 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -6,6 +6,8 @@ import { Table, ViewCalculation, AutoFieldSubTypes, + InternalTable, + FieldSubtype, } from "@budibase/types" import { checkBuilderEndpoint } from "./utilities/TestFunctions" import * as setup from "./utilities" @@ -417,4 +419,46 @@ describe("/tables", () => { }) }) }) + + describe("migrate", () => { + it("should successfully migrate a user relationship to a user column", async () => { + const users = await config.api.row.fetch(InternalTable.USER_METADATA) + const table = await config.api.table.create({ + name: "table", + type: "table", + schema: { + "user relationship": { + type: FieldType.LINK, + fieldName: "test", + name: "user relationship", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.ONE_TO_MANY, + tableId: InternalTable.USER_METADATA, + }, + }, + }) + + await config.api.row.save(table._id!, { + "user relationship": users, + }) + + await config.api.table.migrate(table._id!, { + oldColumn: table.schema["user relationship"], + newColumn: { + name: "user column", + type: FieldType.BB_REFERENCE, + subtype: FieldSubtype.USER, + }, + }) + + const migratedTable = await config.api.table.get(table._id!) + expect(migratedTable.schema["user column"]).toBeDefined() + + const rows = await config.api.row.fetch(table._id!) + expect(rows[0]["user column"]).toBeDefined() + }) + }) }) diff --git a/packages/server/src/sdk/app/links/index.ts b/packages/server/src/sdk/app/links/index.ts new file mode 100644 index 0000000000..6655a76656 --- /dev/null +++ b/packages/server/src/sdk/app/links/index.ts @@ -0,0 +1,5 @@ +import * as links from "./links" + +export default { + ...links, +} diff --git a/packages/server/src/sdk/app/links/links.ts b/packages/server/src/sdk/app/links/links.ts new file mode 100644 index 0000000000..7f3dcbd4ac --- /dev/null +++ b/packages/server/src/sdk/app/links/links.ts @@ -0,0 +1,32 @@ +import { context } from "@budibase/backend-core" +import { isTableId } from "@budibase/backend-core/src/docIds" +import { LinkDocument, LinkDocumentValue } from "@budibase/types" +import { ViewName, getQueryIndex } from "../../../../src/db/utils" + +export async function fetch(tableId: string): Promise { + if (!isTableId(tableId)) { + throw new Error(`Invalid tableId: ${tableId}`) + } + + const db = context.getAppDB() + const params: any = { startkey: [tableId], endkey: [tableId, {}] } + const linkRows = (await db.query(getQueryIndex(ViewName.LINK), params)).rows + return linkRows.map(row => row.value as LinkDocumentValue) +} + +export async function fetchWithDocument( + tableId: string +): Promise { + if (!isTableId(tableId)) { + throw new Error(`Invalid tableId: ${tableId}`) + } + + const db = context.getAppDB() + const params: any = { + startkey: [tableId], + endkey: [tableId, {}], + include_docs: true, + } + const linkRows = (await db.query(getQueryIndex(ViewName.LINK), params)).rows + return linkRows.map(row => row.doc as LinkDocument) +} diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index ced35db9be..f75bd07437 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -1,4 +1,4 @@ -import { SearchFilters, SearchParams, Row } from "@budibase/types" +import { SearchFilters, SearchParams } from "@budibase/types" import { isExternalTable } from "../../../integrations/utils" import * as internal from "./search/internal" import * as external from "./search/external" @@ -45,7 +45,7 @@ export async function exportRows( return pickApi(options.tableId).exportRows(options) } -export async function fetch(tableId: string): Promise { +export async function fetch(tableId: string) { return pickApi(tableId).fetch(tableId) } @@ -53,6 +53,6 @@ export async function fetchView( tableId: string, viewName: string, params: ViewParams -): Promise { +) { return pickApi(tableId).fetchView(viewName, params) } diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index f727b5ea9d..ac7f3a3765 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -8,8 +8,8 @@ import { isBBReferenceField, isRelationshipField, } from "@budibase/types" -import { isExternalTable } from "src/integrations/utils" import sdk from "../../../sdk" +import { isExternalTable } from "../../../../src/integrations/utils" export async function migrate( table: Table, @@ -80,5 +80,6 @@ class UserColumnMigrator implements ColumnMigrator { async doMigration() { let rows = await sdk.rows.fetch(this.table._id!) + let links = await sdk.links.fetchWithDocument(this.table._id!) } } diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts index 24eb1ebf3c..c3057e3d4f 100644 --- a/packages/server/src/sdk/index.ts +++ b/packages/server/src/sdk/index.ts @@ -5,6 +5,7 @@ import { default as applications } from "./app/applications" import { default as datasources } from "./app/datasources" import { default as queries } from "./app/queries" import { default as rows } from "./app/rows" +import { default as links } from "./app/links" import { default as users } from "./users" import { default as plugins } from "./plugins" import * as views from "./app/views" @@ -22,6 +23,7 @@ const sdk = { plugins, views, permissions, + links, } // default export for TS diff --git a/packages/server/src/tests/utilities/api/table.ts b/packages/server/src/tests/utilities/api/table.ts index 04432a788a..501841f6e7 100644 --- a/packages/server/src/tests/utilities/api/table.ts +++ b/packages/server/src/tests/utilities/api/table.ts @@ -1,4 +1,10 @@ -import { SaveTableRequest, SaveTableResponse, Table } from "@budibase/types" +import { + MigrateRequest, + MigrateResponse, + SaveTableRequest, + SaveTableResponse, + Table, +} from "@budibase/types" import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" @@ -42,4 +48,18 @@ export class TableAPI extends TestAPI { .expect(expectStatus) return res.body } + + migrate = async ( + tableId: string, + data: MigrateRequest, + { expectStatus } = { expectStatus: 200 } + ): Promise => { + const res = await this.request + .post(`/api/tables/${tableId}/migrate`) + .send(data) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(expectStatus) + return res.body + } } diff --git a/packages/types/src/api/web/app/table.ts b/packages/types/src/api/web/app/table.ts index 61da12d041..f4d6720516 100644 --- a/packages/types/src/api/web/app/table.ts +++ b/packages/types/src/api/web/app/table.ts @@ -36,7 +36,6 @@ export interface BulkImportResponse { } export interface MigrateRequest { - tableId: string oldColumn: FieldSchema newColumn: FieldSchema } From 1771b5905a37e544261a2fb8b2deca58786aeabc Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 18 Oct 2023 18:02:10 +0100 Subject: [PATCH 011/134] Most of the way to getting my first test passing. --- .../server/src/api/routes/tests/table.spec.ts | 2 + packages/server/src/sdk/app/tables/index.ts | 17 ++++++--- .../server/src/sdk/app/tables/migration.ts | 38 +++++++++++++++++-- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index eaf3757ea8..dcf1704ed0 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -456,9 +456,11 @@ describe("/tables", () => { const migratedTable = await config.api.table.get(table._id!) expect(migratedTable.schema["user column"]).toBeDefined() + expect(migratedTable.schema["user relationship"]).not.toBeDefined() const rows = await config.api.row.fetch(table._id!) expect(rows[0]["user column"]).toBeDefined() + expect(rows[0]["user relationship"]).not.toBeDefined() }) }) }) diff --git a/packages/server/src/sdk/app/tables/index.ts b/packages/server/src/sdk/app/tables/index.ts index e4884b2ec4..fab8c7d198 100644 --- a/packages/server/src/sdk/app/tables/index.ts +++ b/packages/server/src/sdk/app/tables/index.ts @@ -16,6 +16,8 @@ import datasources from "../datasources" import { populateExternalTableSchemas } from "./validation" import sdk from "../../../sdk" import { migrate } from "./migration" +import { DocumentInsertResponse } from "@budibase/nano" +import { cloneDeep } from "lodash" async function getAllInternalTables(db?: Database): Promise { if (!db) { @@ -75,23 +77,28 @@ function enrichViewSchemas(table: Table): TableResponse { } } -async function saveTable(table: Table) { +async function saveTable(table: Table): Promise { const db = context.getAppDB() + let resp: DocumentInsertResponse if (isExternalTable(table._id!)) { const datasource = await sdk.datasources.get(table.sourceId!) datasource.entities![table.name] = table - await db.put(datasource) + resp = await db.put(datasource) } else { - await db.put(table) + resp = await db.put(table) } + + let tableClone = cloneDeep(table) + tableClone._rev = resp.rev + return tableClone } -async function addColumn(table: Table, newColumn: FieldSchema) { +async function addColumn(table: Table, newColumn: FieldSchema): Promise
{ if (newColumn.name in table.schema) { throw `Column "${newColumn.name}" already exists on table "${table.name}"` } table.schema[newColumn.name] = newColumn - await saveTable(table) + return await saveTable(table) } export default { diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index ac7f3a3765..a08b56826d 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -1,15 +1,19 @@ -import { BadRequestError } from "@budibase/backend-core" +import { BadRequestError, context } from "@budibase/backend-core" import { BBReferenceFieldMetadata, FieldSchema, InternalTable, RelationshipFieldMetadata, + Row, Table, isBBReferenceField, isRelationshipField, } from "@budibase/types" import sdk from "../../../sdk" import { isExternalTable } from "../../../../src/integrations/utils" +import { db as dbCore } from "@budibase/backend-core" +import { EventType, updateLinks } from "../../../../src/db/linkedRows" +import { cloneDeep } from "lodash" export async function migrate( table: Table, @@ -17,10 +21,15 @@ export async function migrate( newColumn: FieldSchema ) { let migrator = getColumnMigrator(table, oldColumn, newColumn) + let oldTable = cloneDeep(table) - await sdk.tables.addColumn(table, newColumn) + table = await sdk.tables.addColumn(table, newColumn) - migrator.doMigration() + await migrator.doMigration() + + delete table.schema[oldColumn.name] + await sdk.tables.saveTable(table) + await updateLinks({ eventType: EventType.TABLE_UPDATED, table, oldTable }) } interface ColumnMigrator { @@ -80,6 +89,29 @@ class UserColumnMigrator implements ColumnMigrator { async doMigration() { let rows = await sdk.rows.fetch(this.table._id!) + let rowsById = rows.reduce((acc, row) => { + acc[row._id!] = row + return acc + }, {} as Record) + let links = await sdk.links.fetchWithDocument(this.table._id!) + for (let link of links) { + if (link.doc1.tableId !== this.table._id) { + continue + } + if (link.doc1.fieldName !== this.oldColumn.name) { + continue + } + if (link.doc2.tableId !== InternalTable.USER_METADATA) { + continue + } + + let userId = dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId) + let row = rowsById[link.doc1.rowId] + row[this.newColumn.name] = userId + } + + let db = context.getAppDB() + await db.bulkDocs(rows) } } From 77729737bce3cceabcef7290bbf46321939bccd4 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 19 Oct 2023 09:47:50 +0100 Subject: [PATCH 012/134] First test passes! --- packages/server/src/sdk/app/rows/external.ts | 2 +- packages/server/src/sdk/app/rows/search.ts | 8 ++++++-- packages/server/src/sdk/app/rows/search/external.ts | 6 ++++++ packages/server/src/sdk/app/rows/search/internal.ts | 9 ++++----- packages/server/src/sdk/app/tables/migration.ts | 4 ++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/server/src/sdk/app/rows/external.ts b/packages/server/src/sdk/app/rows/external.ts index 8bcf89a3f5..beae02e134 100644 --- a/packages/server/src/sdk/app/rows/external.ts +++ b/packages/server/src/sdk/app/rows/external.ts @@ -1,4 +1,4 @@ -import { IncludeRelationship, Operation, Row } from "@budibase/types" +import { IncludeRelationship, Operation } from "@budibase/types" import { handleRequest } from "../../../api/controllers/row/external" import { breakRowIdField } from "../../../integrations/utils" diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index f75bd07437..221aa6486c 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -1,4 +1,4 @@ -import { SearchFilters, SearchParams } from "@budibase/types" +import { Row, SearchFilters, SearchParams } from "@budibase/types" import { isExternalTable } from "../../../integrations/utils" import * as internal from "./search/internal" import * as external from "./search/external" @@ -45,10 +45,14 @@ export async function exportRows( return pickApi(options.tableId).exportRows(options) } -export async function fetch(tableId: string) { +export async function fetch(tableId: string): Promise { return pickApi(tableId).fetch(tableId) } +export async function fetchRaw(tableId: string): Promise { + return pickApi(tableId).fetchRaw(tableId) +} + export async function fetchView( tableId: string, viewName: string, diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index c41efad171..981ae1bf8d 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -186,6 +186,12 @@ export async function fetch(tableId: string): Promise { }) } +export async function fetchRaw(tableId: string): Promise { + return await handleRequest(Operation.READ, tableId, { + includeSqlRelationships: IncludeRelationship.INCLUDE, + }) +} + export async function fetchView(viewName: string) { // there are no views in external datasources, shouldn't ever be called // for now just fetch diff --git a/packages/server/src/sdk/app/rows/search/internal.ts b/packages/server/src/sdk/app/rows/search/internal.ts index 779ff5f777..58611c8849 100644 --- a/packages/server/src/sdk/app/rows/search/internal.ts +++ b/packages/server/src/sdk/app/rows/search/internal.ts @@ -140,14 +140,13 @@ export async function exportRows( } export async function fetch(tableId: string): Promise { - const db = context.getAppDB() - const table = await sdk.tables.getTable(tableId) - const rows = await getRawTableData(db, tableId) + const rows = await fetchRaw(tableId) return await outputProcessing(table, rows) } -async function getRawTableData(db: Database, tableId: string) { +export async function fetchRaw(tableId: string): Promise { + const db = context.getAppDB() let rows if (tableId === InternalTables.USER_METADATA) { rows = await sdk.users.fetchMetadata() @@ -182,7 +181,7 @@ export async function fetchView( }) } else { const tableId = viewInfo.meta.tableId - const data = await getRawTableData(db, tableId) + const data = await fetchRaw(tableId) response = await inMemoryViews.runView( viewInfo, calculation as string, diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index a08b56826d..cea15114a1 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -28,7 +28,7 @@ export async function migrate( await migrator.doMigration() delete table.schema[oldColumn.name] - await sdk.tables.saveTable(table) + table = await sdk.tables.saveTable(table) await updateLinks({ eventType: EventType.TABLE_UPDATED, table, oldTable }) } @@ -88,7 +88,7 @@ class UserColumnMigrator implements ColumnMigrator { ) {} async doMigration() { - let rows = await sdk.rows.fetch(this.table._id!) + let rows = await sdk.rows.fetchRaw(this.table._id!) let rowsById = rows.reduce((acc, row) => { acc[row._id!] = row return acc From ad54b31667cffa18c9ccf7971fc1af8085a9e233 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 19 Oct 2023 12:00:47 +0100 Subject: [PATCH 013/134] Moved label align and button size into styles section of form block. Added tagged component settings section to the style section. General settings and sections now have a tag property for filtering. --- .../Component/ComponentSettingsPanel.svelte | 7 +- .../Component/ComponentSettingsSection.svelte | 20 ++++-- .../Component/DesignSection.svelte | 15 +++++ packages/client/manifest.json | 66 ++++++++++--------- 4 files changed, 70 insertions(+), 38 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte index afcada4138..8b9ff0ae2c 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte @@ -65,7 +65,12 @@ /> {/if} {#if section == "styles"} - + { const settings = definition?.settings ?? [] - const generalSettings = settings.filter(setting => !setting.section) - const customSections = settings.filter(setting => setting.section) + const generalSettings = settings.filter( + setting => !setting.section && setting.tag === tag + ) + const customSections = settings.filter( + setting => setting.section && setting.tag === tag + ) let sections = [ { name: "General", @@ -131,7 +141,7 @@ - {:else if idx === 0 && section.name === "General" && componentDefinition.info} + {:else if idx === 0 && section.name === "General" && componentDefinition.info && !tag} {/if} {/each} -{#if componentDefinition?.block} +{#if componentDefinition?.block && !tag} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/DesignSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/DesignSection.svelte index 444ded7e1f..def1fcf24b 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/DesignSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/DesignSection.svelte @@ -1,10 +1,12 @@ + + + {#if styles?.length > 0} {#each styles as style} Date: Thu, 19 Oct 2023 12:28:28 +0100 Subject: [PATCH 014/134] Moved buttons section below fields --- packages/client/manifest.json | 102 +++++++++++++++++----------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index d5fca15fd7..1919f4ccd2 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5330,57 +5330,6 @@ } ] }, - { - "section": true, - "name": "Buttons", - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - }, - "settings": [ - { - "type": "text", - "key": "saveButtonLabel", - "label": "Save button", - "nested": true, - "defaultValue": "Save" - }, - { - "type": "text", - "key": "deleteButtonLabel", - "label": "Delete button", - "nested": true, - "defaultValue": "Delete", - "dependsOn": { - "setting": "actionType", - "value": "Update" - } - }, - { - "type": "url", - "label": "Navigate after button press", - "key": "actionUrl", - "placeholder": "Choose a screen", - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - } - }, - { - "type": "boolean", - "label": "Hide notifications", - "key": "notificationOverride", - "defaultValue": false, - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - } - } - ] - }, { "section": true, "name": "Fields", @@ -5436,6 +5385,57 @@ } } ] + }, + { + "section": true, + "name": "Buttons", + "dependsOn": { + "setting": "actionType", + "value": "View", + "invert": true + }, + "settings": [ + { + "type": "text", + "key": "saveButtonLabel", + "label": "Save button", + "nested": true, + "defaultValue": "Save" + }, + { + "type": "text", + "key": "deleteButtonLabel", + "label": "Delete button", + "nested": true, + "defaultValue": "Delete", + "dependsOn": { + "setting": "actionType", + "value": "Update" + } + }, + { + "type": "url", + "label": "Navigate after button press", + "key": "actionUrl", + "placeholder": "Choose a screen", + "dependsOn": { + "setting": "actionType", + "value": "View", + "invert": true + } + }, + { + "type": "boolean", + "label": "Hide notifications", + "key": "notificationOverride", + "defaultValue": false, + "dependsOn": { + "setting": "actionType", + "value": "View", + "invert": true + } + } + ] } ], "context": [ From a3ad8780deb6089bc96d1a66d21b0ed9ebf30c2e Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 19 Oct 2023 17:28:55 +0100 Subject: [PATCH 015/134] Implement many-to-many user column migrations. --- .../server/src/api/routes/tests/table.spec.ts | 95 +++++++++++++++++-- packages/server/src/sdk/app/rows/search.ts | 2 +- .../server/src/sdk/app/tables/migration.ts | 66 ++++++++++++- .../src/tests/utilities/TestConfiguration.ts | 3 +- .../server/src/tests/utilities/api/index.ts | 1 + 5 files changed, 154 insertions(+), 13 deletions(-) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index dcf1704ed0..907b020246 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -8,6 +8,7 @@ import { AutoFieldSubTypes, InternalTable, FieldSubtype, + Row, } from "@budibase/types" import { checkBuilderEndpoint } from "./utilities/TestFunctions" import * as setup from "./utilities" @@ -421,8 +422,13 @@ describe("/tables", () => { }) describe("migrate", () => { - it("should successfully migrate a user relationship to a user column", async () => { - const users = await config.api.row.fetch(InternalTable.USER_METADATA) + it("should successfully migrate a one-to-many user relationship to a user column", async () => { + const users = await Promise.all([ + config.createUser({ email: "1@example.com" }), + config.createUser({ email: "2@example.com" }), + config.createUser({ email: "3@example.com" }), + ]) + const table = await config.api.table.create({ name: "table", type: "table", @@ -441,9 +447,11 @@ describe("/tables", () => { }, }) - await config.api.row.save(table._id!, { - "user relationship": users, - }) + const rows = await Promise.all( + users.map(u => + config.api.row.save(table._id!, { "user relationship": [u] }) + ) + ) await config.api.table.migrate(table._id!, { oldColumn: table.schema["user relationship"], @@ -458,9 +466,80 @@ describe("/tables", () => { expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined() - const rows = await config.api.row.fetch(table._id!) - expect(rows[0]["user column"]).toBeDefined() - expect(rows[0]["user relationship"]).not.toBeDefined() + const migratedRows = await config.api.row.fetch(table._id!) + + rows.sort((a, b) => a._id!.localeCompare(b._id!)) + migratedRows.sort((a, b) => a._id!.localeCompare(b._id!)) + + for (const [i, row] of rows.entries()) { + const migratedRow = migratedRows[i] + expect(migratedRow["user column"]).toBeDefined() + expect(migratedRow["user relationship"]).not.toBeDefined() + expect(row["user relationship"][0]._id).toEqual( + migratedRow["user column"][0]._id + ) + } + }) + + it("should successfully migrate a many-to-many user relationship to a users column", async () => { + const users = await Promise.all([ + config.createUser({ email: "1@example.com" }), + config.createUser({ email: "2@example.com" }), + config.createUser({ email: "3@example.com" }), + ]) + + const table = await config.api.table.create({ + name: "table", + type: "table", + schema: { + "user relationship": { + type: FieldType.LINK, + fieldName: "test", + name: "user relationship", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.MANY_TO_MANY, + tableId: InternalTable.USER_METADATA, + }, + }, + }) + + const row1 = await config.api.row.save(table._id!, { + "user relationship": [users[0], users[1]], + }) + + const row2 = await config.api.row.save(table._id!, { + "user relationship": [users[2]], + }) + + await config.api.table.migrate(table._id!, { + oldColumn: table.schema["user relationship"], + newColumn: { + name: "user column", + type: FieldType.BB_REFERENCE, + subtype: FieldSubtype.USERS, + }, + }) + + const migratedTable = await config.api.table.get(table._id!) + expect(migratedTable.schema["user column"]).toBeDefined() + expect(migratedTable.schema["user relationship"]).not.toBeDefined() + + const row1Migrated = (await config.api.row.get(table._id!, row1._id!)) + .body as Row + expect(row1Migrated["user relationship"]).not.toBeDefined() + expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual( + expect.arrayContaining([users[0]._id, users[1]._id]) + ) + + const row2Migrated = (await config.api.row.get(table._id!, row2._id!)) + .body as Row + expect(row2Migrated["user relationship"]).not.toBeDefined() + expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual([ + users[2]._id, + ]) }) }) }) diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 221aa6486c..caf9ffea02 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -57,6 +57,6 @@ export async function fetchView( tableId: string, viewName: string, params: ViewParams -) { +): Promise { return pickApi(tableId).fetchView(viewName, params) } diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index cea15114a1..e3f817f893 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -2,8 +2,12 @@ import { BadRequestError, context } from "@budibase/backend-core" import { BBReferenceFieldMetadata, FieldSchema, + FieldSubtype, InternalTable, + ManyToManyRelationshipFieldMetadata, + OneToManyRelationshipFieldMetadata, RelationshipFieldMetadata, + RelationshipType, Row, Table, isBBReferenceField, @@ -77,13 +81,30 @@ function getColumnMigrator( ) } - return new UserColumnMigrator(table, oldColumn, newColumn) + if (oldColumn.relationshipType === RelationshipType.ONE_TO_MANY) { + if (newColumn.subtype !== FieldSubtype.USER) { + throw new BadRequestError( + `Column "${oldColumn.name}" is a one-to-many column but "${newColumn.name}" is not a single user column` + ) + } + return new OneToManyUserColumnMigrator(table, oldColumn, newColumn) + } + if (oldColumn.relationshipType === RelationshipType.MANY_TO_MANY) { + if (newColumn.subtype !== FieldSubtype.USERS) { + throw new BadRequestError( + `Column "${oldColumn.name}" is a many-to-many column but "${newColumn.name}" is not a multi user column` + ) + } + return new ManyToManyUserColumnMigrator(table, oldColumn, newColumn) + } + + throw new BadRequestError(`Unknown migration type`) } -class UserColumnMigrator implements ColumnMigrator { +class OneToManyUserColumnMigrator implements ColumnMigrator { constructor( private table: Table, - private oldColumn: RelationshipFieldMetadata, + private oldColumn: OneToManyRelationshipFieldMetadata, private newColumn: BBReferenceFieldMetadata ) {} @@ -115,3 +136,42 @@ class UserColumnMigrator implements ColumnMigrator { await db.bulkDocs(rows) } } + +class ManyToManyUserColumnMigrator implements ColumnMigrator { + constructor( + private table: Table, + private oldColumn: ManyToManyRelationshipFieldMetadata, + private newColumn: BBReferenceFieldMetadata + ) {} + + async doMigration() { + let rows = await sdk.rows.fetchRaw(this.table._id!) + let rowsById = rows.reduce((acc, row) => { + acc[row._id!] = row + return acc + }, {} as Record) + + let links = await sdk.links.fetchWithDocument(this.table._id!) + for (let link of links) { + if (link.doc1.tableId !== this.table._id) { + continue + } + if (link.doc1.fieldName !== this.oldColumn.name) { + continue + } + if (link.doc2.tableId !== InternalTable.USER_METADATA) { + continue + } + + let userId = dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId) + let row = rowsById[link.doc1.rowId] + if (!row[this.newColumn.name]) { + row[this.newColumn.name] = [] + } + row[this.newColumn.name].push(userId) + } + + let db = context.getAppDB() + await db.bulkDocs(rows) + } +} diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index cec8c8aa12..c00772ef63 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -55,6 +55,7 @@ import { RelationshipType, CreateViewRequest, RelationshipFieldMetadata, + User, } from "@budibase/types" import API from "./api" @@ -254,7 +255,7 @@ class TestConfiguration { } catch (err) { existing = { email } } - const user = { + const user: User = { _id: id, ...existing, roles: roles || {}, diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index fce8237760..1352874914 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -27,5 +27,6 @@ export default class API { this.datasource = new DatasourceAPI(config) this.screen = new ScreenAPI(config) this.application = new ApplicationAPI(config) + this.global = new GlobalAPI(config) } } From 3e51557281a82383450e28e875ecb518852e8727 Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 20 Oct 2023 10:38:34 +0100 Subject: [PATCH 016/134] Feedback update, add tag to getsections fn definition --- .../_components/Component/ComponentSettingsSection.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index f2d6c9cb52..6eb4b4c440 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -26,7 +26,7 @@ tag ) - const getSections = (instance, definition, isScreen) => { + const getSections = (instance, definition, isScreen, tag) => { const settings = definition?.settings ?? [] const generalSettings = settings.filter( setting => !setting.section && setting.tag === tag From 871891c2a2f2e25cb1e821a9dfe8b7c19c199aaf Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 20 Oct 2023 15:35:16 +0100 Subject: [PATCH 017/134] Parse view v1 schema to fix builder crashed caused by 'groupedby' views --- .../components/backend/DataTable/Table.svelte | 44 ++++++++++--------- .../backend/DataTable/ViewDataTable.svelte | 16 ++++++- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/Table.svelte b/packages/builder/src/components/backend/DataTable/Table.svelte index f8087d8a39..45e440eca5 100644 --- a/packages/builder/src/components/backend/DataTable/Table.svelte +++ b/packages/builder/src/components/backend/DataTable/Table.svelte @@ -24,17 +24,21 @@ let selectedRows = [] let customRenderers = [] + let parsedSchema = {} + + $: if (schema) { + parsedSchema = Object.keys(schema).reduce((acc, key) => { + acc[key] = { ...schema[key] } + if (!canBeSortColumn(acc[key].type)) { + acc[key].sortable = false + } + return acc + }, {}) + } $: selectedRows, dispatch("selectionUpdated", selectedRows) $: isUsersTable = tableId === TableNames.USERS $: data && resetSelectedRows() - $: { - Object.values(schema || {}).forEach(col => { - if (!canBeSortColumn(col.type)) { - col.sortable = false - } - }) - } $: { if (isUsersTable) { customRenderers = [ @@ -44,24 +48,24 @@ }, ] UNEDITABLE_USER_FIELDS.forEach(field => { - if (schema[field]) { - schema[field].editable = false + if (parsedSchema[field]) { + parsedSchema[field].editable = false } }) - if (schema.email) { - schema.email.displayName = "Email" + if (parsedSchema.email) { + parsedSchema.email.displayName = "Email" } - if (schema.roleId) { - schema.roleId.displayName = "Role" + if (parsedSchema.roleId) { + parsedSchema.roleId.displayName = "Role" } - if (schema.firstName) { - schema.firstName.displayName = "First Name" + if (parsedSchema.firstName) { + parsedSchema.firstName.displayName = "First Name" } - if (schema.lastName) { - schema.lastName.displayName = "Last Name" + if (parsedSchema.lastName) { + parsedSchema.lastName.displayName = "Last Name" } - if (schema.status) { - schema.status.displayName = "Status" + if (parsedSchema.status) { + parsedSchema.status.displayName = "Status" } } } @@ -97,7 +101,7 @@
{ + if (!view.groupBy) { + return view.schema + } + const { group, field, value } = view.schema + return { + group, + field: { type: field }, + value: { type: value }, + } + } + $: name = view.name + $: schema = parseSchema(view) $: calculation = view.calculation $: supportedFormats = Object.values(ROW_EXPORT_FORMATS).filter(key => { @@ -61,7 +75,7 @@
Date: Mon, 23 Oct 2023 09:49:57 +0100 Subject: [PATCH 018/134] Fix tests after merge. --- packages/server/src/sdk/app/tables/index.ts | 2 ++ packages/server/src/sdk/app/tables/migration.ts | 3 ++- packages/server/src/sdk/app/tables/update.ts | 13 ++++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/server/src/sdk/app/tables/index.ts b/packages/server/src/sdk/app/tables/index.ts index 8542250517..ed71498d44 100644 --- a/packages/server/src/sdk/app/tables/index.ts +++ b/packages/server/src/sdk/app/tables/index.ts @@ -2,10 +2,12 @@ import { populateExternalTableSchemas } from "./validation" import * as getters from "./getters" import * as updates from "./update" import * as utils from "./utils" +import { migrate } from "./migration" export default { populateExternalTableSchemas, ...updates, ...getters, ...utils, + migrate, } diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index cea15114a1..76b824959c 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -23,7 +23,8 @@ export async function migrate( let migrator = getColumnMigrator(table, oldColumn, newColumn) let oldTable = cloneDeep(table) - table = await sdk.tables.addColumn(table, newColumn) + table.schema[newColumn.name] = newColumn + table = await sdk.tables.saveTable(table) await migrator.doMigration() diff --git a/packages/server/src/sdk/app/tables/update.ts b/packages/server/src/sdk/app/tables/update.ts index 9bba4a967e..c59f8a8a42 100644 --- a/packages/server/src/sdk/app/tables/update.ts +++ b/packages/server/src/sdk/app/tables/update.ts @@ -3,21 +3,28 @@ import { isExternalTable } from "../../../integrations/utils" import sdk from "../../index" import { context } from "@budibase/backend-core" import { isExternal } from "./utils" +import { DocumentInsertResponse } from "@budibase/nano" import * as external from "./external" import * as internal from "./internal" +import { cloneDeep } from "lodash" export * as external from "./external" export * as internal from "./internal" -export async function saveTable(table: Table) { +export async function saveTable(table: Table): Promise
{ const db = context.getAppDB() + let resp: DocumentInsertResponse if (isExternalTable(table._id!)) { const datasource = await sdk.datasources.get(table.sourceId!) datasource.entities![table.name] = table - await db.put(datasource) + resp = await db.put(datasource) } else { - await db.put(table) + resp = await db.put(table) } + + let tableClone = cloneDeep(table) + tableClone._rev = resp.rev + return tableClone } export async function update(table: Table, renaming?: RenameColumn) { From 9dd16381a7e1c83eb3339f1790289af14b7b8774 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 09:52:17 +0100 Subject: [PATCH 019/134] Merge base branch. --- packages/server/src/tests/utilities/api/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index 1352874914..fce8237760 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -27,6 +27,5 @@ export default class API { this.datasource = new DatasourceAPI(config) this.screen = new ScreenAPI(config) this.application = new ApplicationAPI(config) - this.global = new GlobalAPI(config) } } From 06f0a8da1ae5ee15f4e583a526e81f8c93eb6fef Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 09:59:11 +0100 Subject: [PATCH 020/134] Update pro submodule. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 570d14aa44..56d968bfe6 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 570d14aa44aa88f4d053856322210f0008ba5c76 +Subproject commit 56d968bfe6998e1077d3fce4eb1c9e483d1d6fc9 From 2e0b528331cbc08f9cac96da2dbb19d0548283f5 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 09:59:27 +0100 Subject: [PATCH 021/134] Update pro submodule. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 570d14aa44..56d968bfe6 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 570d14aa44aa88f4d053856322210f0008ba5c76 +Subproject commit 56d968bfe6998e1077d3fce4eb1c9e483d1d6fc9 From 9e279be4c334c5e613066a59fe0ab8b42a842e0c Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 10:35:41 +0100 Subject: [PATCH 022/134] Put pro back to where it was. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 56d968bfe6..570d14aa44 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 56d968bfe6998e1077d3fce4eb1c9e483d1d6fc9 +Subproject commit 570d14aa44aa88f4d053856322210f0008ba5c76 From 3fc2ff2c9b1bf30eea4a2cf96c217935a283c0eb Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 10:36:05 +0100 Subject: [PATCH 023/134] Put pro back to where it was. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 56d968bfe6..570d14aa44 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 56d968bfe6998e1077d3fce4eb1c9e483d1d6fc9 +Subproject commit 570d14aa44aa88f4d053856322210f0008ba5c76 From febfab092717f106ef13f9827221e67d74c244b8 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 10:48:10 +0100 Subject: [PATCH 024/134] Fix tests/types. --- packages/server/src/sdk/users/tests/utils.spec.ts | 14 +++++++------- .../src/tests/utilities/TestConfiguration.ts | 7 ++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/server/src/sdk/users/tests/utils.spec.ts b/packages/server/src/sdk/users/tests/utils.spec.ts index 5c6777df59..f7c9413ebd 100644 --- a/packages/server/src/sdk/users/tests/utils.spec.ts +++ b/packages/server/src/sdk/users/tests/utils.spec.ts @@ -39,12 +39,12 @@ describe("syncGlobalUsers", () => { expect(metadata).toHaveLength(3) expect(metadata).toContainEqual( expect.objectContaining({ - _id: db.generateUserMetadataID(user1._id), + _id: db.generateUserMetadataID(user1._id!), }) ) expect(metadata).toContainEqual( expect.objectContaining({ - _id: db.generateUserMetadataID(user2._id), + _id: db.generateUserMetadataID(user2._id!), }) ) }) @@ -59,7 +59,7 @@ describe("syncGlobalUsers", () => { expect(metadata).toHaveLength(1) expect(metadata).not.toContainEqual( expect.objectContaining({ - _id: db.generateUserMetadataID(user._id), + _id: db.generateUserMetadataID(user._id!), }) ) }) @@ -70,7 +70,7 @@ describe("syncGlobalUsers", () => { const group = await proSdk.groups.save(structures.userGroups.userGroup()) const user1 = await config.createUser({ admin: false, builder: false }) const user2 = await config.createUser({ admin: false, builder: false }) - await proSdk.groups.addUsers(group.id, [user1._id, user2._id]) + await proSdk.groups.addUsers(group.id, [user1._id!, user2._id!]) await config.doInContext(config.appId, async () => { await syncGlobalUsers() @@ -87,12 +87,12 @@ describe("syncGlobalUsers", () => { expect(metadata).toHaveLength(3) expect(metadata).toContainEqual( expect.objectContaining({ - _id: db.generateUserMetadataID(user1._id), + _id: db.generateUserMetadataID(user1._id!), }) ) expect(metadata).toContainEqual( expect.objectContaining({ - _id: db.generateUserMetadataID(user2._id), + _id: db.generateUserMetadataID(user2._id!), }) ) }) @@ -109,7 +109,7 @@ describe("syncGlobalUsers", () => { { appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC }, ], }) - await proSdk.groups.addUsers(group.id, [user1._id, user2._id]) + await proSdk.groups.addUsers(group.id, [user1._id!, user2._id!]) await config.doInContext(config.appId, async () => { await syncGlobalUsers() diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index c00772ef63..5f5b211975 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -295,7 +295,7 @@ class TestConfiguration { admin?: boolean roles?: UserRoles } = {} - ) { + ): Promise { let { id, firstName, lastName, email, builder, admin, roles } = user firstName = firstName || this.defaultUserValues.firstName lastName = lastName || this.defaultUserValues.lastName @@ -315,10 +315,7 @@ class TestConfiguration { roles, }) await cache.user.invalidateUser(globalId) - return { - ...resp, - globalId, - } + return resp } async createGroup(roleId: string = roles.BUILTIN_ROLE_IDS.BASIC) { From 5e6ed0fd670f762fb5b4ec4f084697baa1264135 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 11:54:27 +0100 Subject: [PATCH 025/134] Implement many-to-one user column migration. --- .../server/src/api/routes/tests/table.spec.ts | 61 +++++++++++++++++++ .../server/src/sdk/app/tables/migration.ts | 21 ++++--- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 907b020246..420717f7f0 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -510,6 +510,67 @@ describe("/tables", () => { "user relationship": [users[0], users[1]], }) + const row2 = await config.api.row.save(table._id!, { + "user relationship": [users[1], users[2]], + }) + + await config.api.table.migrate(table._id!, { + oldColumn: table.schema["user relationship"], + newColumn: { + name: "user column", + type: FieldType.BB_REFERENCE, + subtype: FieldSubtype.USERS, + }, + }) + + const migratedTable = await config.api.table.get(table._id!) + expect(migratedTable.schema["user column"]).toBeDefined() + expect(migratedTable.schema["user relationship"]).not.toBeDefined() + + const row1Migrated = (await config.api.row.get(table._id!, row1._id!)) + .body as Row + expect(row1Migrated["user relationship"]).not.toBeDefined() + expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual( + expect.arrayContaining([users[0]._id, users[1]._id]) + ) + + const row2Migrated = (await config.api.row.get(table._id!, row2._id!)) + .body as Row + expect(row2Migrated["user relationship"]).not.toBeDefined() + expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual( + expect.arrayContaining([users[1]._id, users[2]._id]) + ) + }) + + it("should successfully migrate a many-to-one user relationship to a users column", async () => { + const users = await Promise.all([ + config.createUser({ email: "1@example.com" }), + config.createUser({ email: "2@example.com" }), + config.createUser({ email: "3@example.com" }), + ]) + + const table = await config.api.table.create({ + name: "table", + type: "table", + schema: { + "user relationship": { + type: FieldType.LINK, + fieldName: "test", + name: "user relationship", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.MANY_TO_ONE, + tableId: InternalTable.USER_METADATA, + }, + }, + }) + + const row1 = await config.api.row.save(table._id!, { + "user relationship": [users[0], users[1]], + }) + const row2 = await config.api.row.save(table._id!, { "user relationship": [users[2]], }) diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index 94b8841136..08c06b28b9 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -5,8 +5,8 @@ import { FieldSubtype, InternalTable, ManyToManyRelationshipFieldMetadata, + ManyToOneRelationshipFieldMetadata, OneToManyRelationshipFieldMetadata, - RelationshipFieldMetadata, RelationshipType, Row, Table, @@ -88,21 +88,24 @@ function getColumnMigrator( `Column "${oldColumn.name}" is a one-to-many column but "${newColumn.name}" is not a single user column` ) } - return new OneToManyUserColumnMigrator(table, oldColumn, newColumn) + return new SingleUserColumnMigrator(table, oldColumn, newColumn) } - if (oldColumn.relationshipType === RelationshipType.MANY_TO_MANY) { + if ( + oldColumn.relationshipType === RelationshipType.MANY_TO_MANY || + oldColumn.relationshipType === RelationshipType.MANY_TO_ONE + ) { if (newColumn.subtype !== FieldSubtype.USERS) { throw new BadRequestError( - `Column "${oldColumn.name}" is a many-to-many column but "${newColumn.name}" is not a multi user column` + `Column "${oldColumn.name}" is a ${oldColumn.relationshipType} column but "${newColumn.name}" is not a multi user column` ) } - return new ManyToManyUserColumnMigrator(table, oldColumn, newColumn) + return new MultiUserColumnMigrator(table, oldColumn, newColumn) } throw new BadRequestError(`Unknown migration type`) } -class OneToManyUserColumnMigrator implements ColumnMigrator { +class SingleUserColumnMigrator implements ColumnMigrator { constructor( private table: Table, private oldColumn: OneToManyRelationshipFieldMetadata, @@ -138,10 +141,12 @@ class OneToManyUserColumnMigrator implements ColumnMigrator { } } -class ManyToManyUserColumnMigrator implements ColumnMigrator { +class MultiUserColumnMigrator implements ColumnMigrator { constructor( private table: Table, - private oldColumn: ManyToManyRelationshipFieldMetadata, + private oldColumn: + | ManyToManyRelationshipFieldMetadata + | ManyToOneRelationshipFieldMetadata, private newColumn: BBReferenceFieldMetadata ) {} From 08a22f1525af1fc7225f4792a0183b7e07d3365c Mon Sep 17 00:00:00 2001 From: Dean Date: Mon, 23 Oct 2023 16:47:05 +0100 Subject: [PATCH 026/134] Show 'Creator' instead of 'Admin' for the global user role picker --- .../src/components/common/RoleSelect.svelte | 15 ++++++++++++--- .../_components/BuilderSidePanel.svelte | 11 +++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/common/RoleSelect.svelte b/packages/builder/src/components/common/RoleSelect.svelte index 82752554d5..2df61926e1 100644 --- a/packages/builder/src/components/common/RoleSelect.svelte +++ b/packages/builder/src/components/common/RoleSelect.svelte @@ -39,7 +39,15 @@ allowCreator ) => { if (allowedRoles?.length) { - return roles.filter(role => allowedRoles.includes(role._id)) + const filteredRoles = roles.filter(role => + allowedRoles.includes(role._id) + ) + return [ + ...filteredRoles, + ...(allowedRoles.includes(Constants.Roles.CREATOR) + ? [{ _id: Constants.Roles.CREATOR, name: "Creator", enabled: false }] + : []), + ] } let newRoles = [...roles] @@ -129,8 +137,9 @@ getOptionColour={getColor} getOptionIcon={getIcon} isOptionEnabled={option => - option._id !== Constants.Roles.CREATOR || - $licensing.perAppBuildersEnabled} + (option._id !== Constants.Roles.CREATOR || + $licensing.perAppBuildersEnabled) && + option.enabled !== false} {placeholder} {error} /> diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index a7d9584330..f9a40b09a6 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -516,6 +516,13 @@ } return null } + + const parseRole = user => { + if (user.isAdminOrGlobalBuilder) { + return Constants.Roles.CREATOR + } + return user.role + } @@ -725,7 +732,7 @@ From a701933f48ad5f4f141cd85d18f9f86938cbe410 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 23 Oct 2023 17:57:25 +0100 Subject: [PATCH 027/134] Frontend changes for the user column migration work. --- packages/frontend-core/src/api/tables.js | 9 +++++++ .../components/grid/cells/HeaderCell.svelte | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/frontend-core/src/api/tables.js b/packages/frontend-core/src/api/tables.js index a08e35d3d8..34d2371e1a 100644 --- a/packages/frontend-core/src/api/tables.js +++ b/packages/frontend-core/src/api/tables.js @@ -140,4 +140,13 @@ export const buildTableEndpoints = API => ({ }, }) }, + migrateColumn: async ({ tableId, oldColumn, newColumn }) => { + return await API.post({ + url: `/api/tables/${tableId}/migrate`, + body: { + oldColumn, + newColumn, + }, + }) + }, }) diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte index d6cbcb582d..01c759a15c 100644 --- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte @@ -10,6 +10,7 @@ export let orderable = true const { + API, reorder, isReordering, isResizing, @@ -114,6 +115,24 @@ open = false } + const migrateUserColumn = async () => { + let subtype = "users" + if (column.schema.relationshipType === "one-to-many") { + subtype = "user" + } + + await API.migrateColumn({ + tableId: $definition._id, + oldColumn: column.schema, + newColumn: { + name: `${column.schema.name} migrated`, + type: "bb_reference", + subtype, + }, + }) + open = false + } + const duplicateColumn = async () => { open = false @@ -262,6 +281,11 @@ > Hide column + {#if column.schema.type === "link" && column.schema.tableId === "ta_users"} + + Migrate to user column + + {/if} {/if} From 2f0a40e9bb8c8e8893517d6dfcca1433ff088173 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 24 Oct 2023 15:18:46 +0100 Subject: [PATCH 028/134] Introduce modal to show warning to users, and toast to show success. --- .../backend/DataTable/TableDataTable.svelte | 6 +++ .../components/grid/cells/HeaderCell.svelte | 42 +++++++++---------- .../grid/controls/MigrationModal.svelte | 42 +++++++++++++++++++ 3 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 packages/frontend-core/src/components/grid/controls/MigrationModal.svelte diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 5fee849afb..22deacbe03 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -53,6 +53,11 @@ await datasources.fetch() } } + + const refreshDefinitions = async () => { + await tables.fetch() + await datasources.fetch() + }
@@ -66,6 +71,7 @@ schemaOverrides={isUsersTable ? userSchemaOverrides : null} showAvatars={false} on:updatedatasource={handleGridTableUpdate} + on:refreshdefinitions={refreshDefinitions} > {#if isUsersTable && $store.features.disableUserMetadata} diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte index 01c759a15c..9b05f9ef79 100644 --- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte @@ -1,9 +1,17 @@ + + + +
Hide column - {#if column.schema.type === "link" && column.schema.tableId === "ta_users"} - + {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === "ta_users"} + Migrate to user column {/if} diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte new file mode 100644 index 0000000000..b262f93797 --- /dev/null +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -0,0 +1,42 @@ + + + + TODO: copy here + From cea1c04b73c508d96f1a1a828b964ec07b2858b5 Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Tue, 24 Oct 2023 17:16:44 +0200 Subject: [PATCH 029/134] Creators count functionality --- .../backend-core/src/cache/writethrough.ts | 4 +- packages/backend-core/src/users/db.ts | 110 ++++++++++-------- packages/backend-core/src/users/users.ts | 2 +- .../tests/core/users/users.spec.js | 54 +++++++++ .../core/utilities/structures/licenses.ts | 8 ++ packages/types/src/sdk/featureFlag.ts | 3 + packages/types/src/sdk/licensing/billing.ts | 7 ++ packages/types/src/sdk/licensing/plan.ts | 4 + 8 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 packages/backend-core/tests/core/users/users.spec.js diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts index e64c116663..c331d791a6 100644 --- a/packages/backend-core/src/cache/writethrough.ts +++ b/packages/backend-core/src/cache/writethrough.ts @@ -119,8 +119,8 @@ export class Writethrough { this.writeRateMs = writeRateMs } - async put(doc: any) { - return put(this.db, doc, this.writeRateMs) + async put(doc: any, writeRateMs: number = this.writeRateMs) { + return put(this.db, doc, writeRateMs) } async get(id: string) { diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index a2539e836e..daa09bee6f 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -25,12 +25,17 @@ import { import { getAccountHolderFromUserIds, isAdmin, + isCreator, validateUniqueUser, } from "./utils" import { searchExistingEmails } from "./lookup" import { hash } from "../utils" -type QuotaUpdateFn = (change: number, cb?: () => Promise) => Promise +type QuotaUpdateFn = ( + change: number, + creatorsChange: number, + cb?: () => Promise +) => Promise type GroupUpdateFn = (groupId: string, userIds: string[]) => Promise type FeatureFn = () => Promise type GroupGetFn = (ids: string[]) => Promise @@ -245,7 +250,8 @@ export class UserDB { } const change = dbUser ? 0 : 1 // no change if there is existing user - return UserDB.quotas.addUsers(change, async () => { + const creatorsChange = isCreator(dbUser) !== isCreator(user) ? 1 : 0 + return UserDB.quotas.addUsers(change, creatorsChange, async () => { await validateUniqueUser(email, tenantId) let builtUser = await UserDB.buildUser(user, opts, tenantId, dbUser) @@ -307,6 +313,7 @@ export class UserDB { let usersToSave: any[] = [] let newUsers: any[] = [] + let newCreators: any[] = [] const emails = newUsersRequested.map((user: User) => user.email) const existingEmails = await searchExistingEmails(emails) @@ -327,59 +334,66 @@ export class UserDB { } newUser.userGroups = groups newUsers.push(newUser) + if (isCreator(newUser)) { + newCreators.push(newUser) + } } const account = await accountSdk.getAccountByTenantId(tenantId) - return UserDB.quotas.addUsers(newUsers.length, async () => { - // create the promises array that will be called by bulkDocs - newUsers.forEach((user: any) => { - usersToSave.push( - UserDB.buildUser( - user, - { - hashPassword: true, - requirePassword: user.requirePassword, - }, - tenantId, - undefined, // no dbUser - account + return UserDB.quotas.addUsers( + newUsers.length, + newCreators.length, + async () => { + // create the promises array that will be called by bulkDocs + newUsers.forEach((user: any) => { + usersToSave.push( + UserDB.buildUser( + user, + { + hashPassword: true, + requirePassword: user.requirePassword, + }, + tenantId, + undefined, // no dbUser + account + ) ) - ) - }) + }) - const usersToBulkSave = await Promise.all(usersToSave) - await usersCore.bulkUpdateGlobalUsers(usersToBulkSave) + const usersToBulkSave = await Promise.all(usersToSave) + await usersCore.bulkUpdateGlobalUsers(usersToBulkSave) - // Post-processing of bulk added users, e.g. events and cache operations - for (const user of usersToBulkSave) { - // TODO: Refactor to bulk insert users into the info db - // instead of relying on looping tenant creation - await platform.users.addUser(tenantId, user._id, user.email) - await eventHelpers.handleSaveEvents(user, undefined) - } + // Post-processing of bulk added users, e.g. events and cache operations + for (const user of usersToBulkSave) { + // TODO: Refactor to bulk insert users into the info db + // instead of relying on looping tenant creation + await platform.users.addUser(tenantId, user._id, user.email) + await eventHelpers.handleSaveEvents(user, undefined) + } + + const saved = usersToBulkSave.map(user => { + return { + _id: user._id, + email: user.email, + } + }) + + // now update the groups + if (Array.isArray(saved) && groups) { + const groupPromises = [] + const createdUserIds = saved.map(user => user._id) + for (let groupId of groups) { + groupPromises.push(UserDB.groups.addUsers(groupId, createdUserIds)) + } + await Promise.all(groupPromises) + } - const saved = usersToBulkSave.map(user => { return { - _id: user._id, - email: user.email, + successful: saved, + unsuccessful, } - }) - - // now update the groups - if (Array.isArray(saved) && groups) { - const groupPromises = [] - const createdUserIds = saved.map(user => user._id) - for (let groupId of groups) { - groupPromises.push(UserDB.groups.addUsers(groupId, createdUserIds)) - } - await Promise.all(groupPromises) } - - return { - successful: saved, - unsuccessful, - } - }) + ) } static async bulkDelete(userIds: string[]): Promise { @@ -419,11 +433,12 @@ export class UserDB { _deleted: true, })) const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete) + const creatorsToDelete = usersToDelete.filter(isCreator) - await UserDB.quotas.removeUsers(toDelete.length) for (let user of usersToDelete) { await bulkDeleteProcessing(user) } + await UserDB.quotas.removeUsers(toDelete.length, creatorsToDelete.length) // Build Response // index users by id @@ -472,7 +487,8 @@ export class UserDB { await db.remove(userId, dbUser._rev) - await UserDB.quotas.removeUsers(1) + const creatorsToDelete = isCreator(dbUser) ? 1 : 0 + await UserDB.quotas.removeUsers(1, creatorsToDelete) await eventHelpers.handleDeleteEvents(dbUser) await cache.user.invalidateUser(userId) await sessions.invalidateSessions(userId, { reason: "deletion" }) diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts index 6237c23972..bad108ab84 100644 --- a/packages/backend-core/src/users/users.ts +++ b/packages/backend-core/src/users/users.ts @@ -14,11 +14,11 @@ import { } from "../db" import { BulkDocsResponse, - ContextUser, SearchQuery, SearchQueryOperators, SearchUsersRequest, User, + ContextUser, DatabaseQueryOpts, } from "@budibase/types" import { getGlobalDB } from "../context" diff --git a/packages/backend-core/tests/core/users/users.spec.js b/packages/backend-core/tests/core/users/users.spec.js new file mode 100644 index 0000000000..ae7109344a --- /dev/null +++ b/packages/backend-core/tests/core/users/users.spec.js @@ -0,0 +1,54 @@ +const _ = require('lodash/fp') +const {structures} = require("../../../tests") + +jest.mock("../../../src/context") +jest.mock("../../../src/db") + +const context = require("../../../src/context") +const db = require("../../../src/db") + +const {getCreatorCount} = require('../../../src/users/users') + +describe("Users", () => { + + let getGlobalDBMock + let getGlobalUserParamsMock + let paginationMock + + beforeEach(() => { + jest.resetAllMocks() + + getGlobalDBMock = jest.spyOn(context, "getGlobalDB") + getGlobalUserParamsMock = jest.spyOn(db, "getGlobalUserParams") + paginationMock = jest.spyOn(db, "pagination") + }) + + it("Retrieves the number of creators", async () => { + const getUsers = (offset, limit, creators = false) => { + const range = _.range(offset, limit) + const opts = creators ? {builder: {global: true}} : undefined + return range.map(() => structures.users.user(opts)) + } + const page1Data = getUsers(0, 8) + const page2Data = getUsers(8, 12, true) + getGlobalDBMock.mockImplementation(() => ({ + name : "fake-db", + allDocs: () => ({ + rows: [...page1Data, ...page2Data] + }) + })) + paginationMock.mockImplementationOnce(() => ({ + data: page1Data, + hasNextPage: true, + nextPage: "1" + })) + paginationMock.mockImplementation(() => ({ + data: page2Data, + hasNextPage: false, + nextPage: undefined + })) + const creatorsCount = await getCreatorCount() + expect(creatorsCount).toBe(4) + expect(paginationMock).toHaveBeenCalledTimes(2) + }) +}) diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index 0e34f2e9bb..bb452f9ad5 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -123,6 +123,10 @@ export function customer(): Customer { export function subscription(): Subscription { return { amount: 10000, + amounts: { + user: 10000, + creator: 0, + }, cancelAt: undefined, currency: "usd", currentPeriodEnd: 0, @@ -131,6 +135,10 @@ export function subscription(): Subscription { duration: PriceDuration.MONTHLY, pastDueAt: undefined, quantity: 0, + quantities: { + user: 0, + creator: 0, + }, status: "active", } } diff --git a/packages/types/src/sdk/featureFlag.ts b/packages/types/src/sdk/featureFlag.ts index 53aa4842c4..e3935bc7ee 100644 --- a/packages/types/src/sdk/featureFlag.ts +++ b/packages/types/src/sdk/featureFlag.ts @@ -1,5 +1,8 @@ export enum FeatureFlag { LICENSING = "LICENSING", + // Feature IDs in Posthog + PER_CREATOR_PER_USER_PRICE = "18873", + PER_CREATOR_PER_USER_PRICE_ALERT = "18530", } export interface TenantFeatureFlags { diff --git a/packages/types/src/sdk/licensing/billing.ts b/packages/types/src/sdk/licensing/billing.ts index 35f366c811..bcbc7abd18 100644 --- a/packages/types/src/sdk/licensing/billing.ts +++ b/packages/types/src/sdk/licensing/billing.ts @@ -5,10 +5,17 @@ export interface Customer { currency: string | null | undefined } +export interface SubscriptionItems { + user: number | undefined + creator: number | undefined +} + export interface Subscription { amount: number + amounts: SubscriptionItems | undefined currency: string quantity: number + quantities: SubscriptionItems | undefined duration: PriceDuration cancelAt: number | null | undefined currentPeriodStart: number diff --git a/packages/types/src/sdk/licensing/plan.ts b/packages/types/src/sdk/licensing/plan.ts index 3e214a01ff..1604dfb8af 100644 --- a/packages/types/src/sdk/licensing/plan.ts +++ b/packages/types/src/sdk/licensing/plan.ts @@ -4,7 +4,9 @@ export enum PlanType { PRO = "pro", /** @deprecated */ TEAM = "team", + /** @deprecated */ PREMIUM = "premium", + PREMIUM_PLUS = "premium_plus", BUSINESS = "business", ENTERPRISE = "enterprise", } @@ -26,10 +28,12 @@ export interface AvailablePrice { currency: string duration: PriceDuration priceId: string + type?: string } export enum PlanModel { PER_USER = "perUser", + PER_CREATOR_PER_USER = "per_creator_per_user", DAY_PASS = "dayPass", } From 102a0824844f58a51ba784290271ed7e1fcdd10c Mon Sep 17 00:00:00 2001 From: jvcalderon Date: Tue, 24 Oct 2023 17:18:13 +0200 Subject: [PATCH 030/134] Update pro submodule --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index d24c0dc3a3..39bff12817 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit d24c0dc3a30014cbe61860252aa48104cad36376 +Subproject commit 39bff1281715c647f5d0c1db9bbf1d53c9fd4fc6 From d3670ddf21a43552aee52602c3a40acd7eea7f38 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 24 Oct 2023 17:22:49 +0100 Subject: [PATCH 031/134] Add an input to allow the user to choose the new column name. --- .../backend/DataTable/TableDataTable.svelte | 1 + .../grid/controls/MigrationModal.svelte | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 22deacbe03..40bcf2b74e 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -55,6 +55,7 @@ } const refreshDefinitions = async () => { + console.log("woot") await tables.fetch() await datasources.fetch() } diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index b262f93797..e329038217 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -1,11 +1,18 @@
@@ -71,7 +66,6 @@ schemaOverrides={isUsersTable ? userSchemaOverrides : null} showAvatars={false} on:updatedatasource={handleGridTableUpdate} - on:refreshdefinitions={refreshDefinitions} > {#if isUsersTable && $store.features.disableUserMetadata} diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index ee7fd7bfbe..111ff86170 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -45,7 +45,6 @@ notifications.error(`Failed to migrate: ${e.message}`) } await rows.actions.refreshData() - dispatch("refreshdefintions") } From 7acce7b7c0b0a8ca4363263a8d728007c0eecfb8 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 16:01:08 +0100 Subject: [PATCH 043/134] Remove unused dispatch import. --- .../src/components/grid/controls/MigrationModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index 111ff86170..ec2972a03e 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -8,7 +8,7 @@ import { getContext } from "svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" - const { API, dispatch, definition, rows } = getContext("grid") + const { API, definition, rows } = getContext("grid") export let column From 2c5dd99da202a45dfb97a5cb87351862ecc3627e Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 16:37:15 +0100 Subject: [PATCH 044/134] Use FieldSubtype enum instead of raw strings. --- .../src/components/grid/controls/MigrationModal.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index ec2972a03e..0f173da8b9 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -7,6 +7,7 @@ } from "@budibase/bbui" import { getContext } from "svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" + import { FieldSubtype } from "@budibase/types" const { API, definition, rows } = getContext("grid") @@ -25,9 +26,9 @@ } const migrateUserColumn = async () => { - let subtype = "users" + let subtype = FieldSubtype.USERS if (column.schema.relationshipType === "one-to-many") { - subtype = "user" + subtype = FieldSubtype.USER } try { From ef84e96f98c7a38a6145fb47beee4bdbf5237a73 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 16:38:14 +0100 Subject: [PATCH 045/134] Use RelationshipType enum instead of raw string. --- .../src/components/grid/controls/MigrationModal.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index 0f173da8b9..c6a49c6a48 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -7,7 +7,7 @@ } from "@budibase/bbui" import { getContext } from "svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" - import { FieldSubtype } from "@budibase/types" + import { FieldSubtype, RelationshipType } from "@budibase/types" const { API, definition, rows } = getContext("grid") @@ -27,7 +27,7 @@ const migrateUserColumn = async () => { let subtype = FieldSubtype.USERS - if (column.schema.relationshipType === "one-to-many") { + if (column.schema.relationshipType === RelationshipType.ONE_TO_MANY) { subtype = FieldSubtype.USER } From c5097487e22438a0e4fbc961f2cb422c07329a00 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 16:38:55 +0100 Subject: [PATCH 046/134] Use FieldType constant instead of raw string. --- .../src/components/grid/controls/MigrationModal.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index c6a49c6a48..73ea32408b 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -7,7 +7,7 @@ } from "@budibase/bbui" import { getContext } from "svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" - import { FieldSubtype, RelationshipType } from "@budibase/types" + import { FieldSubtype, FieldType, RelationshipType } from "@budibase/types" const { API, definition, rows } = getContext("grid") @@ -37,7 +37,7 @@ oldColumn: column.schema, newColumn: { name: newColumnName, - type: "bb_reference", + type: FieldType.BB_REFERENCE, subtype, }, }) From e03b1be9d1b2c7c3e8c3ded8f0b3e304a23446bc Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 16:41:37 +0100 Subject: [PATCH 047/134] Make sure new column name cannot be the same as an existing column name. --- .../src/components/grid/controls/MigrationModal.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index 73ea32408b..1957c3259f 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -17,8 +17,8 @@ $: error = checkNewColumnName(newColumnName) const checkNewColumnName = newColumnName => { - if (column.schema.name === newColumnName) { - return "New column name can't be the same as the existing column name." + if (newColumnName in $definition.schema) { + return "New column name can't be the same as an existing column name." } if (newColumnName.match(ValidColumnNameRegex) === null) { return "Illegal character; must be alpha-numeric." From 4a00649f7f7c01d1280a08ff35cedd4963779021 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 16:46:14 +0100 Subject: [PATCH 048/134] Simplify the function signature of processInternalTables --- packages/server/src/sdk/app/tables/getters.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/server/src/sdk/app/tables/getters.ts b/packages/server/src/sdk/app/tables/getters.ts index 34cddc8dc7..47da0beb40 100644 --- a/packages/server/src/sdk/app/tables/getters.ts +++ b/packages/server/src/sdk/app/tables/getters.ts @@ -19,8 +19,8 @@ import { import datasources from "../datasources" import sdk from "../../../sdk" -function processInternalTables(docs: AllDocsResponse): Table[] { - return docs.rows.map(tableDoc => processInternalTable(tableDoc.doc)) +function processInternalTables(tables: Table[]): Table[] { + return tables.map(processInternalTable) } export function processInternalTable(table: Table): Table { @@ -40,7 +40,7 @@ export async function getAllInternalTables(db?: Database): Promise { include_docs: true, }) ) - return processInternalTables(internalTables) + return processInternalTables(internalTables.rows.map(row => row.doc!)) } async function getAllExternalTables(): Promise { @@ -110,7 +110,9 @@ export async function getTables(tableIds: string[]): Promise { const internalTableDocs = await db.allDocs( getMultiIDParams(internalTableIds) ) - tables = tables.concat(processInternalTables(internalTableDocs)) + tables = tables.concat( + processInternalTables(internalTableDocs.rows.map(row => row.doc!)) + ) } return tables } From 6c3b535863878c521bb312606218b89fa2f0f787 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 16:49:29 +0100 Subject: [PATCH 049/134] Simplify try-catch in the migrate function. --- packages/server/src/sdk/app/tables/migration.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index b678b64318..293f9184d6 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -34,10 +34,8 @@ export async function migrate( table = await sdk.tables.saveTable(table) let migrator = getColumnMigrator(table, oldColumn, newColumn) - let result: MigrationResult - try { - result = await migrator.doMigration() + return await migrator.doMigration() } catch (e) { // If the migration fails then we need to roll back the table schema // change. @@ -45,8 +43,6 @@ export async function migrate( await sdk.tables.saveTable(table) throw e } - - return result } interface ColumnMigrator { From 455b26bac95917be3b5d15176248498fd55635e9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 25 Oct 2023 19:00:25 +0100 Subject: [PATCH 050/134] Making sure the source ID is always set when creating a table - the frontend expects this to be set for every table so making the type represent this correctly. --- .../server/src/api/controllers/datasource.ts | 3 +- .../server/src/api/controllers/table/index.ts | 3 +- .../server/src/api/routes/tests/row.spec.ts | 6 +- .../server/src/api/routes/tests/table.spec.ts | 4 ++ .../src/api/routes/tests/viewV2.spec.ts | 2 + packages/server/src/constants/index.ts | 14 ++--- packages/server/src/db/utils.ts | 3 +- .../server/src/integrations/googlesheets.ts | 8 +-- .../src/integrations/microsoftSqlServer.ts | 6 +- packages/server/src/integrations/mysql.ts | 6 +- packages/server/src/integrations/oracle.ts | 6 +- packages/server/src/integrations/postgres.ts | 6 +- .../integrations/tests/googlesheets.spec.ts | 2 + packages/server/src/integrations/utils.ts | 9 ++- .../middleware/tests/trimViewRowInfo.spec.ts | 9 ++- .../src/sdk/app/rows/search/internal.ts | 6 +- .../app/rows/search/tests/external.spec.ts | 62 ++++++++++--------- .../app/rows/search/tests/internal.spec.ts | 9 ++- .../sdk/app/rows/search/tests/utils.spec.ts | 3 + .../src/sdk/app/tables/external/index.ts | 2 +- .../src/sdk/app/tables/external/utils.ts | 4 +- packages/server/src/sdk/app/tables/getters.ts | 10 +-- .../src/sdk/app/tables/tests/tables.spec.ts | 8 ++- .../sdk/app/tables/tests/validation.spec.ts | 41 ++++++++---- .../src/sdk/app/views/tests/views.spec.ts | 2 + .../src/tests/utilities/TestConfiguration.ts | 27 ++++++-- .../server/src/tests/utilities/structures.ts | 2 + .../tests/inputProcessing.spec.ts | 11 +++- .../tests/outputProcessing.spec.ts | 4 ++ packages/server/src/websockets/builder.ts | 2 +- .../types/src/documents/app/table/table.ts | 8 +-- packages/types/src/sdk/datasources.ts | 6 +- 32 files changed, 185 insertions(+), 109 deletions(-) diff --git a/packages/server/src/api/controllers/datasource.ts b/packages/server/src/api/controllers/datasource.ts index b50c2464f0..5d024d51b6 100644 --- a/packages/server/src/api/controllers/datasource.ts +++ b/packages/server/src/api/controllers/datasource.ts @@ -12,7 +12,6 @@ import { CreateDatasourceResponse, Datasource, DatasourcePlus, - ExternalTable, FetchDatasourceInfoRequest, FetchDatasourceInfoResponse, IntegrationBase, @@ -59,7 +58,7 @@ async function buildSchemaHelper(datasource: Datasource): Promise { const connector = (await getConnector(datasource)) as DatasourcePlus return await connector.buildSchema( datasource._id!, - datasource.entities! as Record + datasource.entities! as Record ) } diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index d2ad63c13e..c814a37ad9 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -24,7 +24,6 @@ import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" import { builderSocket } from "../../../websockets" import { cloneDeep, isEqual } from "lodash" -import { processInternalTable } from "../../../sdk/app/tables/getters" function pickApi({ tableId, table }: { tableId?: string; table?: Table }) { if (table && !tableId) { @@ -50,7 +49,7 @@ export async function fetch(ctx: UserCtx) { return Object.values(entities).map
((entity: Table) => ({ ...entity, type: "external", - sourceId: datasource._id, + sourceId: datasource._id!, sql: isSQL(datasource), })) } else { diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 4c2e7a7494..e5a24c27e0 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -21,6 +21,7 @@ import { SortType, StaticQuotaName, Table, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" import { expectAnyExternalColsAttributes, @@ -65,6 +66,7 @@ describe.each([ type: "table", primary: ["id"], primaryDisplay: "name", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { id: { type: FieldType.AUTO, @@ -880,6 +882,7 @@ describe.each([ async function userTable(): Promise
{ return { name: `users_${generator.word()}`, + sourceId: INTERNAL_TABLE_SOURCE_ID, type: "table", primary: ["id"], schema: { @@ -1062,6 +1065,7 @@ describe.each([ async function userTable(): Promise
{ return { name: `users_${generator.word()}`, + sourceId: INTERNAL_TABLE_SOURCE_ID, type: "table", primary: ["id"], schema: { @@ -1597,7 +1601,7 @@ describe.each([ const tableConfig = generateTableConfig() if (config.datasource) { - tableConfig.sourceId = config.datasource._id + tableConfig.sourceId = config.datasource._id! if (config.datasource.plus) { tableConfig.type = "external" } diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 420717f7f0..f988b44e0f 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -9,6 +9,7 @@ import { InternalTable, FieldSubtype, Row, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" import { checkBuilderEndpoint } from "./utilities/TestFunctions" import * as setup from "./utilities" @@ -432,6 +433,7 @@ describe("/tables", () => { const table = await config.api.table.create({ name: "table", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { "user relationship": { type: FieldType.LINK, @@ -491,6 +493,7 @@ describe("/tables", () => { const table = await config.api.table.create({ name: "table", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { "user relationship": { type: FieldType.LINK, @@ -552,6 +555,7 @@ describe("/tables", () => { const table = await config.api.table.create({ name: "table", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { "user relationship": { type: FieldType.LINK, diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 40060aef48..a1ce5ec10e 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -10,6 +10,7 @@ import { UIFieldMetadata, UpdateViewRequest, ViewV2, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" import { generator } from "@budibase/backend-core/tests" import { generateDatasourceID } from "../../../db/utils" @@ -18,6 +19,7 @@ function priceTable(): Table { return { name: "table", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { Price: { type: FieldType.NUMBER, diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index b37a4b36c1..fe69b3c9c8 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -1,5 +1,9 @@ import { objectStore, roles, constants } from "@budibase/backend-core" -import { FieldType as FieldTypes } from "@budibase/types" +import { + FieldType as FieldTypes, + Table, + INTERNAL_TABLE_SOURCE_ID, +} from "@budibase/types" export { FieldType as FieldTypes, RelationshipType, @@ -70,9 +74,10 @@ export enum SortDirection { DESCENDING = "DESCENDING", } -export const USERS_TABLE_SCHEMA = { +export const USERS_TABLE_SCHEMA: Table = { _id: "ta_users", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, views: {}, name: "Users", // TODO: ADMIN PANEL - when implemented this doesn't need to be carried out @@ -87,12 +92,10 @@ export const USERS_TABLE_SCHEMA = { }, presence: true, }, - fieldName: "email", name: "email", }, firstName: { name: "firstName", - fieldName: "firstName", type: FieldTypes.STRING, constraints: { type: FieldTypes.STRING, @@ -101,7 +104,6 @@ export const USERS_TABLE_SCHEMA = { }, lastName: { name: "lastName", - fieldName: "lastName", type: FieldTypes.STRING, constraints: { type: FieldTypes.STRING, @@ -109,7 +111,6 @@ export const USERS_TABLE_SCHEMA = { }, }, roleId: { - fieldName: "roleId", name: "roleId", type: FieldTypes.OPTIONS, constraints: { @@ -119,7 +120,6 @@ export const USERS_TABLE_SCHEMA = { }, }, status: { - fieldName: "status", name: "status", type: FieldTypes.OPTIONS, constraints: { diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 2c07bd8d22..d532d8a8b2 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -5,6 +5,7 @@ import { FieldSchema, RelationshipFieldMetadata, VirtualDocumentType, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" import { FieldTypes } from "../constants" export { DocumentType, VirtualDocumentType } from "@budibase/types" @@ -18,7 +19,7 @@ export const enum AppStatus { } export const BudibaseInternalDB = { - _id: "bb_internal", + _id: INTERNAL_TABLE_SOURCE_ID, type: dbCore.BUDIBASE_DATASOURCE_TYPE, name: "Budibase DB", source: "BUDIBASE", diff --git a/packages/server/src/integrations/googlesheets.ts b/packages/server/src/integrations/googlesheets.ts index 57b6682cc8..4433f45863 100644 --- a/packages/server/src/integrations/googlesheets.ts +++ b/packages/server/src/integrations/googlesheets.ts @@ -12,7 +12,7 @@ import { Row, SearchFilters, SortJson, - ExternalTable, + Table, TableRequest, Schema, } from "@budibase/types" @@ -262,7 +262,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { id?: string ) { // base table - const table: ExternalTable = { + const table: Table = { name: title, primary: [GOOGLE_SHEETS_PRIMARY_KEY], schema: {}, @@ -283,7 +283,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { async buildSchema( datasourceId: string, - entities: Record + entities: Record ): Promise { // not fully configured yet if (!this.config.auth) { @@ -291,7 +291,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { } await this.connect() const sheets = this.client.sheetsByIndex - const tables: Record = {} + const tables: Record = {} let errors: Record = {} await utils.parallelForeach( sheets, diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index ff68026369..b86286756c 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -2,7 +2,7 @@ import { DatasourceFieldType, Integration, Operation, - ExternalTable, + Table, TableSchema, QueryJson, QueryType, @@ -380,7 +380,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { */ async buildSchema( datasourceId: string, - entities: Record + entities: Record ): Promise { await this.connect() let tableInfo: MSSQLTablesResponse[] = await this.runSQL(this.TABLES_SQL) @@ -394,7 +394,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { .map((record: any) => record.TABLE_NAME) .filter((name: string) => this.MASTER_TABLES.indexOf(name) === -1) - const tables: Record = {} + const tables: Record = {} for (let tableName of tableNames) { // get the column definition (type) const definition = await this.runSQL( diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 3a954da9bd..fe7eae51be 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -4,7 +4,7 @@ import { QueryType, QueryJson, SqlQuery, - ExternalTable, + Table, TableSchema, DatasourcePlus, DatasourceFeature, @@ -278,9 +278,9 @@ class MySQLIntegration extends Sql implements DatasourcePlus { async buildSchema( datasourceId: string, - entities: Record + entities: Record ): Promise { - const tables: { [key: string]: ExternalTable } = {} + const tables: { [key: string]: Table } = {} await this.connect() try { diff --git a/packages/server/src/integrations/oracle.ts b/packages/server/src/integrations/oracle.ts index b3936320ac..5fde565180 100644 --- a/packages/server/src/integrations/oracle.ts +++ b/packages/server/src/integrations/oracle.ts @@ -5,7 +5,7 @@ import { QueryJson, QueryType, SqlQuery, - ExternalTable, + Table, DatasourcePlus, DatasourceFeature, ConnectionInfo, @@ -263,14 +263,14 @@ class OracleIntegration extends Sql implements DatasourcePlus { */ async buildSchema( datasourceId: string, - entities: Record + entities: Record ): Promise { const columnsResponse = await this.internalQuery({ sql: this.COLUMNS_SQL, }) const oracleTables = this.mapColumns(columnsResponse) - const tables: { [key: string]: ExternalTable } = {} + const tables: { [key: string]: Table } = {} // iterate each table Object.values(oracleTables).forEach(oracleTable => { diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index 8479cd05d8..38339499b2 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -5,7 +5,7 @@ import { QueryType, QueryJson, SqlQuery, - ExternalTable, + Table, DatasourcePlus, DatasourceFeature, ConnectionInfo, @@ -273,7 +273,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus { */ async buildSchema( datasourceId: string, - entities: Record + entities: Record ): Promise { let tableKeys: { [key: string]: string[] } = {} await this.openConnection() @@ -300,7 +300,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus { const columnsResponse: { rows: PostgresColumn[] } = await this.client.query(this.COLUMNS_SQL) - const tables: { [key: string]: ExternalTable } = {} + const tables: { [key: string]: Table } = {} for (let column of columnsResponse.rows) { const tableName: string = column.table_name diff --git a/packages/server/src/integrations/tests/googlesheets.spec.ts b/packages/server/src/integrations/tests/googlesheets.spec.ts index 748baddc39..842b681867 100644 --- a/packages/server/src/integrations/tests/googlesheets.spec.ts +++ b/packages/server/src/integrations/tests/googlesheets.spec.ts @@ -31,6 +31,7 @@ import { structures } from "@budibase/backend-core/tests" import TestConfiguration from "../../tests/utilities/TestConfiguration" import GoogleSheetsIntegration from "../googlesheets" import { FieldType, Table, TableSchema } from "@budibase/types" +import { generateDatasourceID } from "../../db/utils" describe("Google Sheets Integration", () => { let integration: any, @@ -61,6 +62,7 @@ describe("Google Sheets Integration", () => { function createBasicTable(name: string, columns: string[]): Table { return { name, + sourceId: generateDatasourceID(), schema: { ...columns.reduce((p, c) => { p[c] = { diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index b37fe9f0ed..9e6c5ca3af 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -4,7 +4,6 @@ import { SearchFilters, Datasource, FieldType, - ExternalTable, } from "@budibase/types" import { DocumentType, SEPARATOR } from "../db/utils" import { InvalidColumns, NoEmptyFilterStrings } from "../constants" @@ -301,9 +300,9 @@ function copyExistingPropsOver( * @param entities The old list of tables, if there was any to look for definitions in. */ export function finaliseExternalTables( - tables: Record, - entities: Record -): Record { + tables: Record, + entities: Record +): Record { let finalTables: Record = {} const tableIds = Object.values(tables).map(table => table._id!) for (let [name, table] of Object.entries(tables)) { @@ -316,7 +315,7 @@ export function finaliseExternalTables( } export function checkExternalTables( - tables: Record + tables: Record ): Record { const invalidColumns = Object.values(InvalidColumns) as string[] const errors: Record = {} diff --git a/packages/server/src/middleware/tests/trimViewRowInfo.spec.ts b/packages/server/src/middleware/tests/trimViewRowInfo.spec.ts index bf717d5828..106129f6c9 100644 --- a/packages/server/src/middleware/tests/trimViewRowInfo.spec.ts +++ b/packages/server/src/middleware/tests/trimViewRowInfo.spec.ts @@ -1,5 +1,11 @@ import { generator } from "@budibase/backend-core/tests" -import { BBRequest, FieldType, Row, Table } from "@budibase/types" +import { + BBRequest, + FieldType, + Row, + Table, + INTERNAL_TABLE_SOURCE_ID, +} from "@budibase/types" import * as utils from "../../db/utils" import trimViewRowInfoMiddleware from "../trimViewRowInfo" @@ -73,6 +79,7 @@ describe("trimViewRowInfo middleware", () => { const table: Table = { _id: tableId, name: generator.word(), + sourceId: INTERNAL_TABLE_SOURCE_ID, type: "table", schema: { name: { diff --git a/packages/server/src/sdk/app/rows/search/internal.ts b/packages/server/src/sdk/app/rows/search/internal.ts index 58611c8849..1aec8a321e 100644 --- a/packages/server/src/sdk/app/rows/search/internal.ts +++ b/packages/server/src/sdk/app/rows/search/internal.ts @@ -197,11 +197,7 @@ export async function fetchView( try { table = await sdk.tables.getTable(viewInfo.meta.tableId) } catch (err) { - /* istanbul ignore next */ - table = { - name: "", - schema: {}, - } + throw new Error("Unable to retrieve view table.") } rows = await outputProcessing(table, response.rows) } diff --git a/packages/server/src/sdk/app/rows/search/tests/external.spec.ts b/packages/server/src/sdk/app/rows/search/tests/external.spec.ts index b3bddfbc97..1afdca35fa 100644 --- a/packages/server/src/sdk/app/rows/search/tests/external.spec.ts +++ b/packages/server/src/sdk/app/rows/search/tests/external.spec.ts @@ -15,6 +15,7 @@ import { expectAnyExternalColsAttributes, generator, } from "@budibase/backend-core/tests" +import datasource from "../../../../../api/routes/datasource" jest.unmock("mysql2/promise") @@ -23,36 +24,7 @@ jest.setTimeout(30000) describe.skip("external", () => { const config = new TestConfiguration() - let externalDatasource: Datasource - - const tableData: Table = { - name: generator.word(), - type: "external", - primary: ["id"], - schema: { - id: { - name: "id", - type: FieldType.AUTO, - autocolumn: true, - }, - name: { - name: "name", - type: FieldType.STRING, - }, - surname: { - name: "surname", - type: FieldType.STRING, - }, - age: { - name: "age", - type: FieldType.NUMBER, - }, - address: { - name: "address", - type: FieldType.STRING, - }, - }, - } + let externalDatasource: Datasource, tableData: Table beforeAll(async () => { const container = await new GenericContainer("mysql") @@ -84,6 +56,36 @@ describe.skip("external", () => { }, }, }) + + tableData = { + name: generator.word(), + type: "external", + primary: ["id"], + sourceId: externalDatasource._id!, + schema: { + id: { + name: "id", + type: FieldType.AUTO, + autocolumn: true, + }, + name: { + name: "name", + type: FieldType.STRING, + }, + surname: { + name: "surname", + type: FieldType.STRING, + }, + age: { + name: "age", + type: FieldType.NUMBER, + }, + address: { + name: "address", + type: FieldType.STRING, + }, + }, + } }) describe("search", () => { diff --git a/packages/server/src/sdk/app/rows/search/tests/internal.spec.ts b/packages/server/src/sdk/app/rows/search/tests/internal.spec.ts index b3e98a1149..b3cac2321e 100644 --- a/packages/server/src/sdk/app/rows/search/tests/internal.spec.ts +++ b/packages/server/src/sdk/app/rows/search/tests/internal.spec.ts @@ -1,4 +1,10 @@ -import { FieldType, Row, Table, SearchParams } from "@budibase/types" +import { + FieldType, + Row, + Table, + SearchParams, + INTERNAL_TABLE_SOURCE_ID, +} from "@budibase/types" import TestConfiguration from "../../../../../tests/utilities/TestConfiguration" import { search } from "../internal" import { @@ -12,6 +18,7 @@ describe("internal", () => { const tableData: Table = { name: generator.word(), type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { name: "name", diff --git a/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts index d946eea432..428c57be64 100644 --- a/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts +++ b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts @@ -5,12 +5,14 @@ import { FieldTypeSubtypes, Table, SearchParams, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" const tableId = "ta_a" const tableWithUserCol: Table = { _id: tableId, name: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { user: { name: "user", @@ -23,6 +25,7 @@ const tableWithUserCol: Table = { const tableWithUsersCol: Table = { _id: tableId, name: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { user: { name: "user", diff --git a/packages/server/src/sdk/app/tables/external/index.ts b/packages/server/src/sdk/app/tables/external/index.ts index 402baada78..f445fcaf08 100644 --- a/packages/server/src/sdk/app/tables/external/index.ts +++ b/packages/server/src/sdk/app/tables/external/index.ts @@ -35,10 +35,10 @@ export async function save( opts?: { tableId?: string; renaming?: RenameColumn } ) { let tableToSave: TableRequest = { + ...update, type: "table", _id: buildExternalTableId(datasourceId, update.name), sourceId: datasourceId, - ...update, } const tableId = opts?.tableId || update._id diff --git a/packages/server/src/sdk/app/tables/external/utils.ts b/packages/server/src/sdk/app/tables/external/utils.ts index 10c755a7d6..a60667f44f 100644 --- a/packages/server/src/sdk/app/tables/external/utils.ts +++ b/packages/server/src/sdk/app/tables/external/utils.ts @@ -76,12 +76,14 @@ export function generateManyLinkSchema( const primary = table.name + table.primary[0] const relatedPrimary = relatedTable.name + relatedTable.primary[0] const jcTblName = generateJunctionTableName(column, table, relatedTable) + const datasourceId = datasource._id! // first create the new table const junctionTable = { - _id: buildExternalTableId(datasource._id!, jcTblName), + _id: buildExternalTableId(datasourceId, jcTblName), name: jcTblName, primary: [primary, relatedPrimary], constrained: [primary, relatedPrimary], + sourceId: datasourceId, schema: { [primary]: foreignKeyStructure(primary, { toTable: table.name, diff --git a/packages/server/src/sdk/app/tables/getters.ts b/packages/server/src/sdk/app/tables/getters.ts index 47da0beb40..af779bcc2b 100644 --- a/packages/server/src/sdk/app/tables/getters.ts +++ b/packages/server/src/sdk/app/tables/getters.ts @@ -1,20 +1,16 @@ import { context } from "@budibase/backend-core" -import { - BudibaseInternalDB, - getMultiIDParams, - getTableParams, -} from "../../../db/utils" +import { getMultiIDParams, getTableParams } from "../../../db/utils" import { breakExternalTableId, isExternalTable, isSQL, } from "../../../integrations/utils" import { - AllDocsResponse, Database, Table, TableResponse, TableViewsResponse, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" import datasources from "../datasources" import sdk from "../../../sdk" @@ -27,7 +23,7 @@ export function processInternalTable(table: Table): Table { return { ...table, type: "internal", - sourceId: table.sourceId || BudibaseInternalDB._id, + sourceId: table.sourceId || INTERNAL_TABLE_SOURCE_ID, } } diff --git a/packages/server/src/sdk/app/tables/tests/tables.spec.ts b/packages/server/src/sdk/app/tables/tests/tables.spec.ts index 78ebe59f01..ab45eeb8bb 100644 --- a/packages/server/src/sdk/app/tables/tests/tables.spec.ts +++ b/packages/server/src/sdk/app/tables/tests/tables.spec.ts @@ -1,4 +1,9 @@ -import { FieldType, Table, ViewV2 } from "@budibase/types" +import { + FieldType, + INTERNAL_TABLE_SOURCE_ID, + Table, + ViewV2, +} from "@budibase/types" import { generator } from "@budibase/backend-core/tests" import sdk from "../../.." @@ -13,6 +18,7 @@ describe("table sdk", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, diff --git a/packages/server/src/sdk/app/tables/tests/validation.spec.ts b/packages/server/src/sdk/app/tables/tests/validation.spec.ts index 5347eede90..62a9c61dbb 100644 --- a/packages/server/src/sdk/app/tables/tests/validation.spec.ts +++ b/packages/server/src/sdk/app/tables/tests/validation.spec.ts @@ -1,36 +1,50 @@ import { populateExternalTableSchemas } from "../validation" import { cloneDeep } from "lodash/fp" -import { AutoReason, Datasource, Table } from "@budibase/types" +import { + AutoReason, + Datasource, + FieldType, + RelationshipType, + SourceName, + Table, +} from "@budibase/types" import { isEqual } from "lodash" +import { generateDatasourceID } from "../../../../db/utils" -const SCHEMA = { +const datasourceId = generateDatasourceID() + +const SCHEMA: Datasource = { + source: SourceName.POSTGRES, + type: "datasource", + _id: datasourceId, entities: { client: { _id: "tableA", name: "client", primary: ["idC"], primaryDisplay: "Name", + sourceId: datasourceId, schema: { idC: { autocolumn: true, externalType: "int unsigned", name: "idC", - type: "number", + type: FieldType.NUMBER, }, Name: { autocolumn: false, externalType: "varchar(255)", name: "Name", - type: "string", + type: FieldType.STRING, }, project: { fieldName: "idC", foreignKey: "idC", main: true, name: "project", - relationshipType: "many-to-one", + relationshipType: RelationshipType.MANY_TO_ONE, tableId: "tableB", - type: "link", + type: FieldType.LINK, }, }, }, @@ -39,31 +53,32 @@ const SCHEMA = { name: "project", primary: ["idP"], primaryDisplay: "Name", + sourceId: datasourceId, schema: { idC: { externalType: "int unsigned", name: "idC", - type: "number", + type: FieldType.NUMBER, }, idP: { autocolumn: true, externalType: "int unsigned", name: "idProject", - type: "number", + type: FieldType.NUMBER, }, Name: { autocolumn: false, externalType: "varchar(255)", name: "Name", - type: "string", + type: FieldType.STRING, }, client: { fieldName: "idC", foreignKey: "idC", name: "client", - relationshipType: "one-to-many", + relationshipType: RelationshipType.ONE_TO_MANY, tableId: "tableA", - type: "link", + type: FieldType.LINK, }, }, sql: true, @@ -95,12 +110,12 @@ describe("validation and update of external table schemas", () => { function noOtherTableChanges(response: any) { checkOtherColumns( response.entities!.client!, - SCHEMA.entities.client as Table, + SCHEMA.entities!.client, OTHER_CLIENT_COLS ) checkOtherColumns( response.entities!.project!, - SCHEMA.entities.project as Table, + SCHEMA.entities!.project, OTHER_PROJECT_COLS ) } diff --git a/packages/server/src/sdk/app/views/tests/views.spec.ts b/packages/server/src/sdk/app/views/tests/views.spec.ts index 8fcc6405ef..2068eda4c3 100644 --- a/packages/server/src/sdk/app/views/tests/views.spec.ts +++ b/packages/server/src/sdk/app/views/tests/views.spec.ts @@ -2,6 +2,7 @@ import _ from "lodash" import { FieldSchema, FieldType, + INTERNAL_TABLE_SOURCE_ID, Table, TableSchema, ViewV2, @@ -14,6 +15,7 @@ describe("table sdk", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 5f5b211975..3d4046b91b 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -56,6 +56,7 @@ import { CreateViewRequest, RelationshipFieldMetadata, User, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" import API from "./api" @@ -68,6 +69,10 @@ type DefaultUserValues = { csrfToken: string } +interface TableToBuild extends Omit { + sourceId?: string +} + class TestConfiguration { server: any request: supertest.SuperTest | undefined @@ -538,10 +543,13 @@ class TestConfiguration { // TABLE async updateTable( - config?: Table, + config?: TableToBuild, { skipReassigning } = { skipReassigning: false } ): Promise
{ config = config || basicTable() + if (!config.sourceId) { + config.sourceId = INTERNAL_TABLE_SOURCE_ID + } const response = await this._req(config, null, controllers.table.save) if (!skipReassigning) { this.table = response @@ -549,13 +557,19 @@ class TestConfiguration { return response } - async createTable(config?: Table, options = { skipReassigning: false }) { + async createTable( + config?: TableToBuild, + options = { skipReassigning: false } + ) { if (config != null && config._id) { delete config._id } config = config || basicTable() + if (!config.sourceId) { + config.sourceId = INTERNAL_TABLE_SOURCE_ID + } if (this.datasource && !config.sourceId) { - config.sourceId = this.datasource._id + config.sourceId = this.datasource._id || INTERNAL_TABLE_SOURCE_ID if (this.datasource.plus) { config.type = "external" } @@ -572,12 +586,15 @@ class TestConfiguration { async createLinkedTable( relationshipType = RelationshipType.ONE_TO_MANY, links: any = ["link"], - config?: Table + config?: TableToBuild ) { if (!this.table) { throw "Must have created a table first." } const tableConfig = config || basicTable() + if (!tableConfig.sourceId) { + tableConfig.sourceId = INTERNAL_TABLE_SOURCE_ID + } tableConfig.primaryDisplay = "name" for (let link of links) { tableConfig.schema[link] = { @@ -590,7 +607,7 @@ class TestConfiguration { } if (this.datasource && !tableConfig.sourceId) { - tableConfig.sourceId = this.datasource._id + tableConfig.sourceId = this.datasource._id || INTERNAL_TABLE_SOURCE_ID if (this.datasource.plus) { tableConfig.type = "external" } diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts index d3e92ea34d..52f3450454 100644 --- a/packages/server/src/tests/utilities/structures.ts +++ b/packages/server/src/tests/utilities/structures.ts @@ -19,12 +19,14 @@ import { FieldType, SourceName, Table, + INTERNAL_TABLE_SOURCE_ID, } from "@budibase/types" export function basicTable(): Table { return { name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, diff --git a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts index 18d5128986..10274ed4d9 100644 --- a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts +++ b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts @@ -1,6 +1,11 @@ import { inputProcessing } from ".." import { generator, structures } from "@budibase/backend-core/tests" -import { FieldType, FieldTypeSubtypes, Table } from "@budibase/types" +import { + FieldType, + FieldTypeSubtypes, + INTERNAL_TABLE_SOURCE_ID, + Table, +} from "@budibase/types" import * as bbReferenceProcessor from "../bbReferenceProcessor" jest.mock("../bbReferenceProcessor", (): typeof bbReferenceProcessor => ({ @@ -20,6 +25,7 @@ describe("rowProcessor - inputProcessing", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, @@ -70,6 +76,7 @@ describe("rowProcessor - inputProcessing", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, @@ -110,6 +117,7 @@ describe("rowProcessor - inputProcessing", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, @@ -150,6 +158,7 @@ describe("rowProcessor - inputProcessing", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, diff --git a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts index ecb8856c88..56db285f8b 100644 --- a/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts +++ b/packages/server/src/utilities/rowProcessor/tests/outputProcessing.spec.ts @@ -2,6 +2,7 @@ import { FieldSubtype, FieldType, FieldTypeSubtypes, + INTERNAL_TABLE_SOURCE_ID, Table, } from "@budibase/types" import { outputProcessing } from ".." @@ -26,6 +27,7 @@ describe("rowProcessor - outputProcessing", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, @@ -71,6 +73,7 @@ describe("rowProcessor - outputProcessing", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, @@ -108,6 +111,7 @@ describe("rowProcessor - outputProcessing", () => { _id: generator.guid(), name: "TestTable", type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { name: { type: FieldType.STRING, diff --git a/packages/server/src/websockets/builder.ts b/packages/server/src/websockets/builder.ts index 2cff24e635..0428561bfe 100644 --- a/packages/server/src/websockets/builder.ts +++ b/packages/server/src/websockets/builder.ts @@ -17,7 +17,7 @@ import { clearLock, updateLock } from "../utilities/redis" import { Socket } from "socket.io" import { BuilderSocketEvent } from "@budibase/shared-core" import { processInternalTable } from "../sdk/app/tables/getters" -import { isExternalTable, isInternalTable } from "../integrations/utils" +import { isInternalTable } from "../integrations/utils" export default class BuilderSocket extends BaseSocket { constructor(app: Koa, server: http.Server) { diff --git a/packages/types/src/documents/app/table/table.ts b/packages/types/src/documents/app/table/table.ts index 5174ec608f..df17351c12 100644 --- a/packages/types/src/documents/app/table/table.ts +++ b/packages/types/src/documents/app/table/table.ts @@ -3,14 +3,16 @@ import { View, ViewV2 } from "../view" import { RenameColumn } from "../../../sdk" import { TableSchema } from "./schema" +export const INTERNAL_TABLE_SOURCE_ID = "bb_internal" + export interface Table extends Document { type?: string views?: { [key: string]: View | ViewV2 } name: string + sourceId: string primary?: string[] schema: TableSchema primaryDisplay?: string - sourceId?: string relatedFormula?: string[] constrained?: string[] sql?: boolean @@ -19,10 +21,6 @@ export interface Table extends Document { rowHeight?: number } -export interface ExternalTable extends Table { - sourceId: string -} - export interface TableRequest extends Table { _rename?: RenameColumn created?: boolean diff --git a/packages/types/src/sdk/datasources.ts b/packages/types/src/sdk/datasources.ts index 39a10961de..7a335eb3b9 100644 --- a/packages/types/src/sdk/datasources.ts +++ b/packages/types/src/sdk/datasources.ts @@ -1,4 +1,4 @@ -import { ExternalTable, Table } from "../documents" +import { Table } from "../documents" export const PASSWORD_REPLACEMENT = "--secret-value--" @@ -176,7 +176,7 @@ export interface IntegrationBase { } export interface Schema { - tables: Record + tables: Record errors: Record } @@ -187,7 +187,7 @@ export interface DatasourcePlus extends IntegrationBase { getStringConcat(parts: string[]): string buildSchema( datasourceId: string, - entities: Record + entities: Record ): Promise getTableNames(): Promise } From fd0d8f17f2a3ac4c4413c5b64ed0c730d3f75bcd Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 25 Oct 2023 19:07:51 +0100 Subject: [PATCH 051/134] Making sure single table get also includes sourceId. --- packages/server/src/sdk/app/tables/getters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/sdk/app/tables/getters.ts b/packages/server/src/sdk/app/tables/getters.ts index af779bcc2b..b9c67c573b 100644 --- a/packages/server/src/sdk/app/tables/getters.ts +++ b/packages/server/src/sdk/app/tables/getters.ts @@ -67,7 +67,7 @@ export async function getTable(tableId: string): Promise
{ const table = await getExternalTable(datasourceId!, tableName!) return { ...table, sql: isSQL(datasource) } } else { - return db.get(tableId) + return processInternalTable(await db.get
(tableId)) } } From 7ddaf6479f934af59832269f06bc2f4c144aad17 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 26 Oct 2023 09:54:38 +0200 Subject: [PATCH 052/134] Clean unused pipelines --- .../workflows/release-singleimage-test.yml | 72 ------------------- package.json | 1 - 2 files changed, 73 deletions(-) delete mode 100644 .github/workflows/release-singleimage-test.yml diff --git a/.github/workflows/release-singleimage-test.yml b/.github/workflows/release-singleimage-test.yml deleted file mode 100644 index c3a14226ce..0000000000 --- a/.github/workflows/release-singleimage-test.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Test - -on: - workflow_dispatch: - -env: - CI: true - PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - REGISTRY_URL: registry.hub.docker.com - NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} -jobs: - build: - name: "build" - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18.x] - steps: - - name: "Checkout" - uses: actions/checkout@v4 - with: - submodules: true - token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: "yarn" - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - - name: Setup Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - name: Run Yarn - run: yarn - - name: Run Yarn Build - run: yarn build --scope @budibase/server --scope @budibase/worker - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_API_KEY }} - - name: Get the latest release version - id: version - run: | - release_version=$(cat lerna.json | jq -r '.version') - echo $release_version - echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV - - name: Tag and release Budibase service docker image - uses: docker/build-push-action@v5 - with: - context: . - push: true - pull: true - platforms: linux/amd64,linux/arm64 - build-args: BUDIBASE_VERSION=0.0.0+test - tags: budibase/budibase-test:test - file: ./hosting/single/Dockerfile.v2 - cache-from: type=registry,ref=budibase/budibase-test:test - cache-to: type=inline - - name: Tag and release Budibase Azure App Service docker image - uses: docker/build-push-action@v2 - with: - context: . - push: true - platforms: linux/amd64 - build-args: | - TARGETBUILD=aas - BUDIBASE_VERSION=0.0.0+test - tags: budibase/budibase-test:aas - file: ./hosting/single/Dockerfile.v2 diff --git a/package.json b/package.json index 100a306a35..d3f4903e6c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "build:sdk": "lerna run --stream build:sdk", "deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular", "release": "lerna publish from-package --yes --force-publish --no-git-tag-version --no-push --no-git-reset", - "release:develop": "yarn release --dist-tag develop", "restore": "yarn run clean && yarn && yarn run build", "nuke": "yarn run nuke:packages && yarn run nuke:docker", "nuke:packages": "yarn run restore", From 389848efbefe4e37d3bf397d16fda5ad04e9abdc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 26 Oct 2023 11:05:07 +0200 Subject: [PATCH 053/134] Add update versions v2 --- scripts/updateWorkepaceVersions.V2.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 scripts/updateWorkepaceVersions.V2.sh diff --git a/scripts/updateWorkepaceVersions.V2.sh b/scripts/updateWorkepaceVersions.V2.sh new file mode 100755 index 0000000000..634bcbcfb0 --- /dev/null +++ b/scripts/updateWorkepaceVersions.V2.sh @@ -0,0 +1,8 @@ +#!/bin/bash +version=$1 +echo "Setting version $version" +yarn lerna exec "yarn version --no-git-tag-version --new-version=$version" +echo "Updating dependencies" +node scripts/syncLocalDependencies.js $version +echo "Syncing yarn workspace" +yarn From 6032216e2f4b9f21fb640f554fe1a68c731cce9e Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 26 Oct 2023 10:13:22 +0100 Subject: [PATCH 054/134] Added fix for scenario where componentdefinition was queried befor it existed. Fix to ensure an empty general settings block is not shown --- .../Component/ComponentSettingsSection.svelte | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index cd91e974ce..6dc9078f2c 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -34,10 +34,14 @@ setting => setting.section && setting.tag === tag ) let sections = [ - { - name: "General", - settings: generalSettings, - }, + ...(generalSettings?.length + ? [ + { + name: "General", + settings: generalSettings, + }, + ] + : []), ...(customSections || []), ] @@ -142,7 +146,7 @@ - {:else if idx === 0 && section.name === "General" && componentDefinition.info && !tag} + {:else if idx === 0 && section.name === "General" && componentDefinition?.info && !tag} Date: Thu, 26 Oct 2023 11:24:02 +0200 Subject: [PATCH 055/134] Fix typo --- ...pdateWorkepaceVersions.V2.sh => updateWorkspaceVersions.V2.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{updateWorkepaceVersions.V2.sh => updateWorkspaceVersions.V2.sh} (100%) diff --git a/scripts/updateWorkepaceVersions.V2.sh b/scripts/updateWorkspaceVersions.V2.sh similarity index 100% rename from scripts/updateWorkepaceVersions.V2.sh rename to scripts/updateWorkspaceVersions.V2.sh From 5909b4a7c723c8e13fc735e7844fcf00331f8b0b Mon Sep 17 00:00:00 2001 From: Gerard Burns Date: Thu, 26 Oct 2023 11:24:43 +0100 Subject: [PATCH 056/134] Add template for grid blocks (#12086) * wip * linting --------- Co-authored-by: Mihail Hadzhiev <102024164+MihailHadzhiev2022@users.noreply.github.com> --- .../store/screenTemplates/rowListScreen.js | 22 ++++++++++++++---- .../NewScreen/CreateScreenModal.svelte | 8 +++++-- .../NewScreen/DatasourceModal.svelte | 6 ++++- .../design/_components/NewScreen/grid.png | Bin 0 -> 24819 bytes .../design/_components/NewScreen/index.svelte | 11 +++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js index b17bd99e10..59bcd0d5e8 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js @@ -2,14 +2,14 @@ import sanitizeUrl from "./utils/sanitizeUrl" import { Screen } from "./utils/Screen" import { Component } from "./utils/Component" -export default function (datasources) { +export default function (datasources, mode = "table") { if (!Array.isArray(datasources)) { return [] } return datasources.map(datasource => { return { name: `${datasource.label} - List`, - create: () => createScreen(datasource), + create: () => createScreen(datasource, mode), id: ROW_LIST_TEMPLATE, resourceId: datasource.resourceId, } @@ -40,10 +40,24 @@ const generateTableBlock = datasource => { return tableBlock } -const createScreen = datasource => { +const generateGridBlock = datasource => { + const gridBlock = new Component("@budibase/standard-components/gridblock") + gridBlock + .customProps({ + table: datasource, + }) + .instanceName(`${datasource.label} - Grid block`) + return gridBlock +} + +const createScreen = (datasource, mode) => { return new Screen() .route(rowListUrl(datasource)) .instanceName(`${datasource.label} - List`) - .addChild(generateTableBlock(datasource)) + .addChild( + mode === "table" + ? generateTableBlock(datasource) + : generateGridBlock(datasource) + ) .json() } diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index 9a96242b30..92ed3dcfc7 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -12,6 +12,7 @@ import { capitalise } from "helpers" import { goto } from "@roxi/routify" + let mode let pendingScreen // Modal refs @@ -100,14 +101,15 @@ } // Handler for NewScreenModal - export const show = mode => { + export const show = newMode => { + mode = newMode selectedTemplates = null blankScreenUrl = null screenMode = mode pendingScreen = null screenAccessRole = Roles.BASIC - if (mode === "table") { + if (mode === "table" || mode === "grid") { datasourceModal.show() } else if (mode === "blank") { let templates = getTemplates($tables.list) @@ -123,6 +125,7 @@ // Handler for DatasourceModal confirmation, move to screen access select const confirmScreenDatasources = async ({ templates }) => { + console.log(templates) selectedTemplates = templates screenAccessRoleModal.show() } @@ -177,6 +180,7 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte index a866cd23d4..731c60a406 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte @@ -7,6 +7,7 @@ import rowListScreen from "builderStore/store/screenTemplates/rowListScreen" import DatasourceTemplateRow from "./DatasourceTemplateRow.svelte" + export let mode export let onCancel export let onConfirm export let initialScreens = [] @@ -24,7 +25,10 @@ screen => screen.resourceId !== resourceId ) } else { - selectedScreens = [...selectedScreens, rowListScreen([datasource])[0]] + selectedScreens = [ + ...selectedScreens, + rowListScreen([datasource], mode)[0], + ] } } diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..c3efa30a67d733434fade938263e4aba9fd7cdee GIT binary patch literal 24819 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

K!sjL$Lv!Wkn)K>4}0WK}T!0MY-`Gz%=`-%HF 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 y4eyTF0000View, edit and delete rows on a table + +

createScreenModal.show("grid")}> +
+ +
+
+ Grid + View and manipulate rows on a grid +
+
From 8db0ff79a6c6d8b5061b1e55a5aa30a6ab0af07e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 26 Oct 2023 12:44:36 +0200 Subject: [PATCH 057/134] Increase yarn timeouts --- packages/server/Dockerfile.v2 | 4 ++-- packages/worker/Dockerfile.v2 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/Dockerfile.v2 b/packages/server/Dockerfile.v2 index 881c21299e..f737570fcd 100644 --- a/packages/server/Dockerfile.v2 +++ b/packages/server/Dockerfile.v2 @@ -44,7 +44,7 @@ RUN chmod +x ./scripts/removeWorkspaceDependencies.sh WORKDIR /string-templates COPY packages/string-templates/package.json package.json RUN ../scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 COPY packages/string-templates . @@ -57,7 +57,7 @@ COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies. RUN chmod +x ./scripts/removeWorkspaceDependencies.sh RUN ./scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true \ +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 \ # Remove unneeded data from file system to reduce image size && yarn cache clean && apt-get remove -y --purge --auto-remove g++ make python jq \ && rm -rf /tmp/* /root/.node-gyp /usr/local/lib/node_modules/npm/node_modules/node-gyp diff --git a/packages/worker/Dockerfile.v2 b/packages/worker/Dockerfile.v2 index a8be432827..4706ca155a 100644 --- a/packages/worker/Dockerfile.v2 +++ b/packages/worker/Dockerfile.v2 @@ -19,7 +19,7 @@ RUN chmod +x ./scripts/removeWorkspaceDependencies.sh WORKDIR /string-templates COPY packages/string-templates/package.json package.json RUN ../scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 COPY packages/string-templates . @@ -30,7 +30,7 @@ RUN cd ../string-templates && yarn link && cd - && yarn link @budibase/string-te RUN ../scripts/removeWorkspaceDependencies.sh package.json -RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 # Remove unneeded data from file system to reduce image size RUN apk del .gyp \ && yarn cache clean From 44f9c64ed72b87c8a62e6b4754f107e1545bf07a Mon Sep 17 00:00:00 2001 From: Gerard Burns Date: Thu, 26 Oct 2023 13:06:25 +0100 Subject: [PATCH 058/134] Prevent the key user being used in rest queries (#12072) * Add warning about unusable user binding * linting * remove unnecessary safe nav operators * change regex to capture property access of user binding --- .../integration/RestQueryViewer.svelte | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/builder/src/components/integration/RestQueryViewer.svelte b/packages/builder/src/components/integration/RestQueryViewer.svelte index 254f65fcaf..e6913b0953 100644 --- a/packages/builder/src/components/integration/RestQueryViewer.svelte +++ b/packages/builder/src/components/integration/RestQueryViewer.svelte @@ -196,8 +196,36 @@ } } + const validateQuery = async () => { + const forbiddenBindings = /{{\s?user(\.(\w|\$)*\s?|\s?)}}/g + const bindingError = new Error( + "'user' is a protected binding and cannot be used" + ) + + if (forbiddenBindings.test(url)) { + throw bindingError + } + + if (forbiddenBindings.test(query.fields.requestBody ?? "")) { + throw bindingError + } + + Object.values(requestBindings).forEach(bindingValue => { + if (forbiddenBindings.test(bindingValue)) { + throw bindingError + } + }) + + Object.values(query.fields.headers).forEach(headerValue => { + if (forbiddenBindings.test(headerValue)) { + throw bindingError + } + }) + } + async function runQuery() { try { + await validateQuery() response = await queries.preview(buildQuery()) if (response.rows.length === 0) { notifications.info("Request did not return any data") From ed0670a00863e27e6ef4f1306792ec5a9ea7cb42 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 26 Oct 2023 13:19:09 +0100 Subject: [PATCH 059/134] Major update to make the table.type always 'table' and then adding a new sourceType which states what source the table came from, external or internal. Don't want to keep using a type that should be static as two different things. --- .../src/builderStore/store/frontend.js | 6 +- .../DataTable/RelationshipDataTable.svelte | 3 +- .../backend/DataTable/TableDataTable.svelte | 8 +- .../backend/DataTable/ViewDataTable.svelte | 2 - .../buttons/grid/GridImportButton.svelte | 2 +- .../DataTable/modals/CreateEditColumn.svelte | 14 ++-- .../ExistingTableDataImport.svelte | 4 +- .../modals/CreateTableModal.svelte | 4 +- .../popovers/EditTablePopover.svelte | 9 ++- .../QueryViewerSidePanel/PreviewPanel.svelte | 2 +- .../Tables/CreateExternalTableModal.svelte | 4 +- .../data/datasource/bb_internal/index.svelte | 4 +- .../index.svelte | 5 +- .../server/src/api/controllers/table/index.ts | 7 +- .../src/api/controllers/table/internal.ts | 6 +- .../server/src/api/routes/tests/row.spec.ts | 9 ++- .../server/src/api/routes/tests/table.spec.ts | 23 +++--- .../src/api/routes/tests/viewV2.spec.ts | 6 +- packages/server/src/constants/index.ts | 7 +- .../db/defaultData/datasource_bb_default.ts | 21 ++++-- .../src/integration-test/postgres.spec.ts | 14 ++-- .../server/src/integrations/googlesheets.ts | 5 +- .../src/integrations/microsoftSqlServer.ts | 3 + packages/server/src/integrations/mysql.ts | 3 + packages/server/src/integrations/oracle.ts | 3 + packages/server/src/integrations/postgres.ts | 3 + .../integrations/tests/googlesheets.spec.ts | 4 +- .../middleware/tests/trimViewRowInfo.spec.ts | 2 + .../server/src/middleware/trimViewRowInfo.ts | 1 - .../app/rows/search/tests/external.spec.ts | 4 +- .../app/rows/search/tests/internal.spec.ts | 2 + .../sdk/app/rows/search/tests/utils.spec.ts | 9 ++- .../src/sdk/app/tables/external/utils.ts | 5 +- packages/server/src/sdk/app/tables/getters.ts | 55 +++++++++----- .../src/sdk/app/tables/tests/tables.spec.ts | 2 + .../sdk/app/tables/tests/validation.spec.ts | 6 +- packages/server/src/sdk/app/tables/utils.ts | 4 +- .../src/sdk/app/views/tests/views.spec.ts | 2 + .../src/tests/utilities/TestConfiguration.ts | 73 ++++++++++--------- .../server/src/tests/utilities/structures.ts | 2 + .../tests/inputProcessing.spec.ts | 5 ++ .../tests/outputProcessing.spec.ts | 4 + packages/server/src/websockets/builder.ts | 7 +- .../types/src/documents/app/table/table.ts | 8 +- 44 files changed, 236 insertions(+), 136 deletions(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index a567caf87f..a4729b4a8a 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -580,7 +580,7 @@ export const getFrontendStore = () => { let table = validTables.find(table => { return ( table.sourceId !== BUDIBASE_INTERNAL_DB_ID && - table.type === DB_TYPE_INTERNAL + table.sourceType === DB_TYPE_INTERNAL ) }) if (table) { @@ -591,7 +591,7 @@ export const getFrontendStore = () => { table = validTables.find(table => { return ( table.sourceId === BUDIBASE_INTERNAL_DB_ID && - table.type === DB_TYPE_INTERNAL + table.sourceType === DB_TYPE_INTERNAL ) }) if (table) { @@ -599,7 +599,7 @@ export const getFrontendStore = () => { } // Finally try an external table - return validTables.find(table => table.type === DB_TYPE_EXTERNAL) + return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL) }, enrichEmptySettings: (component, opts) => { if (!component?._component) { diff --git a/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte b/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte index 8ef870caca..4e67a92443 100644 --- a/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte @@ -16,7 +16,6 @@ $: linkedTable = $tables.list.find(table => table._id === linkedTableId) $: schema = linkedTable?.schema $: table = $tables.list.find(table => table._id === tableId) - $: type = table?.type $: fetchData(tableId, rowId) $: { let rowLabel = row?.[table?.primaryDisplay] @@ -41,5 +40,5 @@ {#if row && row._id === rowId} -
+
{/if} diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 5fee849afb..c2932d3b10 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -16,6 +16,7 @@ import GridRelationshipButton from "components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte" import GridEditColumnModal from "components/backend/DataTable/modals/grid/GridEditColumnModal.svelte" import GridUsersTableButton from "components/backend/DataTable/modals/grid/GridUsersTableButton.svelte" + import { DB_TYPE_EXTERNAL } from "constants/backend" const userSchemaOverrides = { firstName: { displayName: "First name", disabled: true }, @@ -27,7 +28,7 @@ $: id = $tables.selected?._id $: isUsersTable = id === TableNames.USERS - $: isInternal = $tables.selected?.type !== "external" + $: isInternal = $tables.selected?.sourceType !== DB_TYPE_EXTERNAL $: gridDatasource = { type: "table", tableId: id, @@ -46,10 +47,7 @@ tables.replaceTable(id, e.detail) // We need to refresh datasources when an external table changes. - // Type "external" may exist - sometimes type is "table" and sometimes it - // is "external" - it has different meanings in different endpoints. - // If we check both these then we hopefully catch all external tables. - if (e.detail?.type === "external" || e.detail?.sql) { + if (e.detail?.sourceType === DB_TYPE_EXTERNAL || e.detail?.sql) { await datasources.fetch() } } diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte index f6160e3caa..bdf62f2959 100644 --- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte @@ -17,7 +17,6 @@ let hideAutocolumns = true let data = [] let loading = false - let type = "internal" $: name = view.name $: calculation = view.calculation @@ -65,7 +64,6 @@ tableId={view.tableId} {data} {loading} - {type} rowCount={10} allowEditing={false} bind:hideAutocolumns diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte index 71d971891c..74e255cf7e 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte @@ -10,6 +10,6 @@ diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 7b51e6c839..8c9b425708 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -26,6 +26,7 @@ ALLOWABLE_NUMBER_TYPES, SWITCHABLE_TYPES, PrettyRelationshipDefinitions, + DB_TYPE_EXTERNAL, } from "constants/backend" import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils" import ConfirmDialog from "components/common/ConfirmDialog.svelte" @@ -254,10 +255,11 @@ !uneditable && editableColumn?.type !== AUTO_TYPE && !editableColumn.autocolumn - $: external = table.type === "external" + $: externalTable = table.sourceType === DB_TYPE_EXTERNAL // in the case of internal tables the sourceId will just be undefined $: tableOptions = $tables.list.filter( - opt => opt.type === table.type && table.sourceId === opt.sourceId + opt => + opt.sourceType === table.sourceType && table.sourceId === opt.sourceId ) $: typeEnabled = !originalName || @@ -409,7 +411,7 @@ editableColumn.type === FieldType.BB_REFERENCE && editableColumn.subtype === FieldSubtype.USERS - if (!external) { + if (!externalTable) { return [ FIELDS.STRING, FIELDS.BARCODEQR, @@ -441,7 +443,7 @@ isUsers ? FIELDS.USERS : FIELDS.USER, ] // no-sql or a spreadsheet - if (!external || table.sql) { + if (!externalTable || table.sql) { fields = [...fields, FIELDS.LINK, FIELDS.ARRAY] } return fields @@ -486,7 +488,7 @@ }) } const newError = {} - if (!external && fieldInfo.name?.startsWith("_")) { + if (!externalTable && fieldInfo.name?.startsWith("_")) { newError.name = `Column name cannot start with an underscore.` } else if (fieldInfo.name && !fieldInfo.name.match(ValidColumnNameRegex)) { newError.name = `Illegal character; must be alpha-numeric.` @@ -498,7 +500,7 @@ newError.name = `Column name already in use.` } - if (fieldInfo.type == "auto" && !fieldInfo.subtype) { + if (fieldInfo.type === "auto" && !fieldInfo.subtype) { newError.subtype = `Auto Column requires a type` } diff --git a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte index 43751ad944..eb1e7bc7ff 100644 --- a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte @@ -1,6 +1,6 @@
-
+