diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 14809c1118..c8bc31f41d 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -26,13 +26,6 @@ jobs: lint: runs-on: ubuntu-latest steps: - - name: Maximize build space - uses: easimon/maximize-build-space@master - with: - root-reserve-mb: 35000 - swap-size-mb: 1024 - remove-android: "true" - remove-dotnet: "true" - name: Checkout repo and submodules uses: actions/checkout@v3 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase' @@ -271,18 +264,23 @@ jobs: if [[ $branch == "master" ]]; then base_commit=$(git rev-parse origin/master) - else + elif [[ $branch == "develop" ]]; then base_commit=$(git rev-parse origin/develop) fi - echo "target_branch=$branch" - echo "target_branch=$branch" >> "$GITHUB_OUTPUT" - echo "pro_commit=$pro_commit" - echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT" - echo "base_commit=$base_commit" - echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT" + if [[ ! -z $base_commit ]]; then + echo "target_branch=$branch" + echo "target_branch=$branch" >> "$GITHUB_OUTPUT" + echo "pro_commit=$pro_commit" + echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT" + echo "base_commit=$base_commit" + echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT" + else + echo "Nothing to do - branch to branch merge." + fi - - name: Check submodule merged to develop + - name: Check submodule merged to base branch + if: ${{ steps.get_pro_commits.outputs.base_commit != '' }} uses: actions/github-script@v4 with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -291,7 +289,7 @@ jobs: const baseCommit = '${{ steps.get_pro_commits.outputs.base_commit }}'; if (submoduleCommit !== baseCommit) { - console.error('Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}"" branch.'); + console.error('Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}" branch.'); console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md') process.exit(1); } else { diff --git a/.github/workflows/check_unreleased_changes.yml b/.github/workflows/check_unreleased_changes.yml deleted file mode 100644 index d558330545..0000000000 --- a/.github/workflows/check_unreleased_changes.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: check_unreleased_changes - -on: - pull_request: - branches: - - master - -jobs: - check_unreleased: - runs-on: ubuntu-latest - steps: - - name: Check for unreleased changes - env: - REPO: "Budibase/budibase" - TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - RELEASE_TIMESTAMP=$(curl -s -H "Authorization: token $TOKEN" \ - "https://api.github.com/repos/$REPO/releases/latest" | \ - jq -r .published_at) - COMMIT_TIMESTAMP=$(curl -s -H "Authorization: token $TOKEN" \ - "https://api.github.com/repos/$REPO/commits/master" | \ - jq -r .commit.committer.date) - RELEASE_SECONDS=$(date --date="$RELEASE_TIMESTAMP" "+%s") - COMMIT_SECONDS=$(date --date="$COMMIT_TIMESTAMP" "+%s") - if (( COMMIT_SECONDS > RELEASE_SECONDS )); then - echo "There are unreleased changes. Please release these changes before merging." - exit 1 - fi - echo "No unreleased changes detected." diff --git a/lerna.json b/lerna.json index 7302cd4555..8bd6248ef1 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.11.15-alpha.2", + "version": "2.11.27-alpha.0", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index e5b6554fca..c38ef76e17 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "build:docker:dependencies": "docker build -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest ./hosting", "publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.2.1 --push ./hosting/couchdb", "publish:docker:dependencies": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest -t budibase/dependencies:v3.2.1 --push ./hosting", - "build:docs": "lerna run --stream build:docs", "release:helm": "node scripts/releaseHelmChart", "env:multi:enable": "lerna run --stream env:multi:enable", "env:multi:disable": "lerna run --stream env:multi:disable", diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index b1dced660c..e9eb77df7c 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -62,7 +62,7 @@ "@trendyol/jest-testcontainers": "^2.1.1", "@types/chance": "1.1.3", "@types/cookies": "0.7.8", - "@types/jest": "29.5.3", + "@types/jest": "29.5.5", "@types/lodash": "4.14.180", "@types/node": "18.17.0", "@types/node-fetch": "2.6.4", diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index f918dcc352..b33b4835a9 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -1,5 +1,10 @@ import { prefixed, DocumentType } from "@budibase/types" -export { SEPARATOR, UNICODE_MAX, DocumentType } from "@budibase/types" +export { + SEPARATOR, + UNICODE_MAX, + DocumentType, + InternalTable, +} from "@budibase/types" /** * Can be used to create a few different forms of querying a view. @@ -30,10 +35,6 @@ export const DeprecatedViews = { ], } -export enum InternalTable { - USER_METADATA = "ta_users", -} - export const StaticDatabases = { GLOBAL: { name: "global-db", diff --git a/packages/backend-core/src/docIds/ids.ts b/packages/backend-core/src/docIds/ids.ts index e0ac85b3df..4c9eb713c8 100644 --- a/packages/backend-core/src/docIds/ids.ts +++ b/packages/backend-core/src/docIds/ids.ts @@ -45,6 +45,11 @@ export function generateGlobalUserID(id?: any) { return `${DocumentType.USER}${SEPARATOR}${id || newid()}` } +const isGlobalUserIDRegex = new RegExp(`^${DocumentType.USER}${SEPARATOR}.+`) +export function isGlobalUserID(id: string) { + return isGlobalUserIDRegex.test(id) +} + /** * Generates a new user ID based on the passed in global ID. * @param {string} globalId The ID of the global user. diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 386b47105d..8445bf9e6d 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -948,12 +948,15 @@ export const buildFormSchema = (component, asset) => { if (component._component.endsWith("formblock")) { let schema = {} - const datasource = getDatasourceForProvider(asset, component) const info = getSchemaForDatasource(component, datasource) + if (!info?.schema) { + return schema + } + if (!component.fields) { - Object.values(info?.schema) + Object.values(info.schema) .filter( ({ autocolumn, name }) => !autocolumn && !["_rev", "_id"].includes(name) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 6c029ddff3..a567caf87f 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -64,6 +64,7 @@ const INITIAL_FRONTEND_STATE = { }, features: { componentValidation: false, + disableUserMetadata: false, }, errors: [], hasAppPackage: false, diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 2b7cde9201..5fee849afb 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -4,6 +4,7 @@ import { TableNames } from "constants" import { Grid } from "@budibase/frontend-core" import { API } from "api" + import { store } from "builderStore" import GridAddColumnModal from "components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte" import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte" import GridEditUserModal from "components/backend/DataTable/modals/grid/GridEditUserModal.svelte" @@ -17,11 +18,11 @@ import GridUsersTableButton from "components/backend/DataTable/modals/grid/GridUsersTableButton.svelte" const userSchemaOverrides = { - firstName: { displayName: "First name" }, - lastName: { displayName: "Last name" }, - email: { displayName: "Email" }, - roleId: { displayName: "Role" }, - status: { displayName: "Status" }, + firstName: { displayName: "First name", disabled: true }, + lastName: { displayName: "Last name", disabled: true }, + email: { displayName: "Email", disabled: true }, + roleId: { displayName: "Role", disabled: true }, + status: { displayName: "Status", disabled: true }, } $: id = $tables.selected?._id @@ -60,14 +61,14 @@ datasource={gridDatasource} canAddRows={!isUsersTable} canDeleteRows={!isUsersTable} - canEditRows={!isUsersTable} - canEditColumns={!isUsersTable} + canEditRows={!isUsersTable || !$store.features.disableUserMetadata} + canEditColumns={!isUsersTable || !$store.features.disableUserMetadata} schemaOverrides={isUsersTable ? userSchemaOverrides : null} showAvatars={false} on:updatedatasource={handleGridTableUpdate} > - {#if isUsersTable} + {#if isUsersTable && $store.features.disableUserMetadata} {/if} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 8233278e58..ba61ede746 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -33,7 +33,7 @@ import { getBindings } from "components/backend/DataTable/formula" import JSONSchemaModal from "./JSONSchemaModal.svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" - import { FieldType } from "@budibase/types" + import { FieldType, FieldSubtype, SourceName } from "@budibase/types" import RelationshipSelector from "components/common/RelationshipSelector.svelte" const AUTO_TYPE = "auto" @@ -43,7 +43,6 @@ const NUMBER_TYPE = FIELDS.NUMBER.type const JSON_TYPE = FIELDS.JSON.type const DATE_TYPE = FIELDS.DATETIME.type - const USER_REFRENCE_TYPE = FIELDS.BB_REFERENCE_USER.compositeType const dispatch = createEventDispatcher() const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] @@ -52,7 +51,19 @@ export let field let mounted = false - let fieldDefinitions = cloneDeep(FIELDS) + const fieldDefinitions = Object.values(FIELDS).reduce( + // Storing the fields by complex field id + (acc, field) => ({ + ...acc, + [makeFieldId(field.type, field.subtype)]: field, + }), + {} + ) + + function makeFieldId(type, subtype) { + return `${type}${subtype || ""}`.toUpperCase() + } + let originalName let linkEditDisabled let primaryDisplay @@ -72,8 +83,8 @@ let jsonSchemaModal let allowedTypes = [] let editableColumn = { - type: fieldDefinitions.STRING.type, - constraints: fieldDefinitions.STRING.constraints, + type: FIELDS.STRING.type, + constraints: FIELDS.STRING.constraints, // Initial value for column name in other table for linked records fieldName: $tables.selected.name, } @@ -139,9 +150,6 @@ $tables.selected.primaryDisplay == null || $tables.selected.primaryDisplay === editableColumn.name - if (editableColumn.type === FieldType.BB_REFERENCE) { - editableColumn.type = `${editableColumn.type}_${editableColumn.subtype}` - } // Here we are setting the relationship values based on the editableColumn // This part of the code is used when viewing an existing field hence the check // for the tableId @@ -172,7 +180,17 @@ } } - allowedTypes = getAllowedTypes() + if (!savingColumn) { + editableColumn.fieldId = makeFieldId( + editableColumn.type, + editableColumn.subtype + ) + + allowedTypes = getAllowedTypes().map(t => ({ + fieldId: makeFieldId(t.type, t.subtype), + ...t, + })) + } } $: initialiseField(field, savingColumn) @@ -249,13 +267,7 @@ let saveColumn = cloneDeep(editableColumn) - // Handle types on composite types - const definition = fieldDefinitions[saveColumn.type.toUpperCase()] - if (definition && saveColumn.type === definition.compositeType) { - saveColumn.type = definition.type - saveColumn.subtype = definition.subtype - delete saveColumn.compositeType - } + delete saveColumn.fieldId if (saveColumn.type === AUTO_TYPE) { saveColumn = buildAutoColumn( @@ -320,27 +332,33 @@ } } - function handleTypeChange(event) { + function onHandleTypeChange(event) { + handleTypeChange(event.detail) + } + + function handleTypeChange(type) { // remove any extra fields that may not be related to this type delete editableColumn.autocolumn delete editableColumn.subtype delete editableColumn.tableId delete editableColumn.relationshipType delete editableColumn.formulaType + delete editableColumn.constraints // Add in defaults and initial definition - const definition = fieldDefinitions[event.detail?.toUpperCase()] + const definition = fieldDefinitions[type?.toUpperCase()] if (definition?.constraints) { editableColumn.constraints = definition.constraints } + editableColumn.type = definition.type + editableColumn.subtype = definition.subtype + // Default relationships many to many if (editableColumn.type === LINK_TYPE) { editableColumn.relationshipType = RelationshipType.MANY_TO_MANY } else if (editableColumn.type === FORMULA_TYPE) { editableColumn.formulaType = "dynamic" - } else if (editableColumn.type === USER_REFRENCE_TYPE) { - editableColumn.relationshipType = RelationshipType.ONE_TO_MANY } } @@ -381,9 +399,26 @@ return ALLOWABLE_NUMBER_OPTIONS } + const isUsers = + editableColumn.type === FieldType.BB_REFERENCE && + editableColumn.subtype === FieldSubtype.USERS + if (!external) { return [ - ...Object.values(fieldDefinitions), + FIELDS.STRING, + FIELDS.BARCODEQR, + FIELDS.LONGFORM, + FIELDS.OPTIONS, + FIELDS.ARRAY, + FIELDS.NUMBER, + FIELDS.BIGINT, + FIELDS.BOOLEAN, + FIELDS.DATETIME, + FIELDS.ATTACHMENT, + FIELDS.LINK, + FIELDS.FORMULA, + FIELDS.JSON, + isUsers ? FIELDS.USERS : FIELDS.USER, { name: "Auto Column", type: AUTO_TYPE }, ] } else { @@ -397,7 +432,7 @@ FIELDS.BOOLEAN, FIELDS.FORMULA, FIELDS.BIGINT, - FIELDS.BB_REFERENCE_USER, + isUsers ? FIELDS.USERS : FIELDS.USER, ] // no-sql or a spreadsheet if (!external || table.sql) { @@ -472,6 +507,13 @@ return newError } + function isUsersColumn(column) { + return ( + column.type === FieldType.BB_REFERENCE && + [FieldSubtype.USER, FieldSubtype.USERS].includes(column.subtype) + ) + } + onMount(() => { mounted = true }) @@ -489,11 +531,11 @@ {/if} ({ label: table.name, value: table._id, + name: table.name, + _id: table._id, })) + + $: { + // Determine the relationship type based on the selected values of both parts + relationshipType = Object.entries(relationshipMap).find( + ([_, parts]) => + parts.part1 === relationshipPart1 && parts.part2 === relationshipPart2 + )?.[0] + + changed(() => { + hasValidated = false + }) + } $: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet(relationshipType) $: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY @@ -338,33 +363,34 @@ onConfirm={saveRelationship} disabled={!valid} > - - changed(() => { - const table = plusTables.find(tbl => tbl._id === e.detail) - fromColumn = table?.name || "" - fromPrimary = table?.primary?.[0] - })} - /> - {/if} + + + changed(() => { + const table = plusTables.find(tbl => tbl._id === e.detail) + fromColumn = table?.name || "" + fromPrimary = table?.primary?.[0] + })} + secondaryTableChanged={e => + changed(() => { + const table = plusTables.find(tbl => tbl._id === e.detail) + toColumn = table.name || "" + fromForeign = null + })} + /> + {#if isManyToOne && fromId} - changed(() => { - const table = plusTables.find(tbl => tbl._id === e.detail) - toColumn = table.name || "" - fromForeign = null - })} - /> {#if isManyToMany} option.label} diff --git a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte index 7bf2bbbbab..5bc3b9e728 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte @@ -3,6 +3,7 @@ import { FIELDS } from "constants/backend" import { API } from "api" import { parseFile } from "./utils" + import { canBeDisplayColumn } from "@budibase/shared-core" export let rows = [] export let schema = {} @@ -10,36 +11,82 @@ export let displayColumn = null export let promptUpload = false - const typeOptions = [ - { + const typeOptions = { + [FIELDS.STRING.type]: { label: "Text", value: FIELDS.STRING.type, + config: { + type: FIELDS.STRING.type, + constraints: FIELDS.STRING.constraints, + }, }, - { + [FIELDS.NUMBER.type]: { label: "Number", value: FIELDS.NUMBER.type, + config: { + type: FIELDS.NUMBER.type, + constraints: FIELDS.NUMBER.constraints, + }, }, - { + [FIELDS.DATETIME.type]: { label: "Date", value: FIELDS.DATETIME.type, + config: { + type: FIELDS.DATETIME.type, + constraints: FIELDS.DATETIME.constraints, + }, }, - { + [FIELDS.OPTIONS.type]: { label: "Options", value: FIELDS.OPTIONS.type, + config: { + type: FIELDS.OPTIONS.type, + constraints: FIELDS.OPTIONS.constraints, + }, }, - { + [FIELDS.ARRAY.type]: { label: "Multi-select", value: FIELDS.ARRAY.type, + config: { + type: FIELDS.ARRAY.type, + constraints: FIELDS.ARRAY.constraints, + }, }, - { + [FIELDS.BARCODEQR.type]: { label: "Barcode/QR", value: FIELDS.BARCODEQR.type, + config: { + type: FIELDS.BARCODEQR.type, + constraints: FIELDS.BARCODEQR.constraints, + }, }, - { + [FIELDS.LONGFORM.type]: { label: "Long Form Text", value: FIELDS.LONGFORM.type, + config: { + type: FIELDS.LONGFORM.type, + constraints: FIELDS.LONGFORM.constraints, + }, }, - ] + user: { + label: "User", + value: "user", + config: { + type: FIELDS.USER.type, + subtype: FIELDS.USER.subtype, + constraints: FIELDS.USER.constraints, + }, + }, + users: { + label: "Users", + value: "users", + config: { + type: FIELDS.USERS.type, + subtype: FIELDS.USERS.subtype, + constraints: FIELDS.USERS.constraints, + }, + }, + } let fileInput let error = null @@ -48,10 +95,16 @@ let validation = {} let validateHash = "" let errors = {} + let selectedColumnTypes = {} $: displayColumnOptions = Object.keys(schema || {}).filter(column => { - return validation[column] + return validation[column] && canBeDisplayColumn(schema[column].type) }) + + $: if (displayColumn && !canBeDisplayColumn(schema[displayColumn].type)) { + displayColumn = null + } + $: { // binding in consumer is causing double renders here const newValidateHash = JSON.stringify(rows) + JSON.stringify(schema) @@ -72,6 +125,13 @@ rows = response.rows schema = response.schema fileName = response.fileName + selectedColumnTypes = Object.entries(response.schema).reduce( + (acc, [colName, fieldConfig]) => ({ + ...acc, + [colName]: fieldConfig.type, + }), + {} + ) } catch (e) { loading = false error = e @@ -98,8 +158,10 @@ } const handleChange = (name, e) => { - schema[name].type = e.detail - schema[name].constraints = FIELDS[e.detail.toUpperCase()].constraints + const { config } = typeOptions[e.detail] + schema[name].type = config.type + schema[name].subtype = config.subtype + schema[name].constraints = config.constraints } const openFileUpload = (promptUpload, fileInput) => { @@ -142,9 +204,9 @@
{column.name} table.name} getOptionValue={table => table._id} bind:value={relationshipTableIdPrimary} + on:change={primaryTableChanged} + bind:error={errors.fromTable} />
@@ -46,20 +52,24 @@ +{#if editableColumn} + +{/if}