diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 93a342a1a1..c8bc31f41d 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -264,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 }} @@ -284,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 9298ac99ba..a47fb09af6 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.11.19", + "version": "2.11.26", "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/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} {name} handleChange(name, e)} - options={typeOptions} + options={Object.values(typeOptions)} placeholder={null} getOptionLabel={option => option.label} getOptionValue={option => option.value} diff --git a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte index 0fd30b82bb..0fe1cb90d2 100644 --- a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte @@ -20,7 +20,6 @@ import { FieldType } from "@budibase/types" import { createEventDispatcher, onMount } from "svelte" import FilterUsers from "./FilterUsers.svelte" - import { RelationshipType } from "constants/backend" export let schemaFields export let filters = [] @@ -126,6 +125,7 @@ // Update type based on field const fieldSchema = enrichedSchemaFields.find(x => x.name === filter.field) filter.type = fieldSchema?.type + filter.subtype = fieldSchema?.subtype // Update external type based on field filter.externalType = getSchema(filter)?.externalType @@ -196,7 +196,7 @@ } return LuceneUtils.getValidOperatorsForType( - filter.type, + { type: filter.type, subtype: filter.subtype }, filter.field, datasource ) @@ -301,9 +301,10 @@ {:else if filter.type === FieldType.BB_REFERENCE} {:else} diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 8b76207822..a81b33c2d3 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -1,7 +1,9 @@ +import { FieldType, FieldSubtype } from "@budibase/types" + export const FIELDS = { STRING: { name: "Text", - type: "string", + type: FieldType.STRING, icon: "Text", constraints: { type: "string", @@ -11,7 +13,7 @@ export const FIELDS = { }, BARCODEQR: { name: "Barcode/QR", - type: "barcodeqr", + type: FieldType.BARCODEQR, icon: "Camera", constraints: { type: "string", @@ -21,7 +23,7 @@ export const FIELDS = { }, LONGFORM: { name: "Long Form Text", - type: "longform", + type: FieldType.LONGFORM, icon: "TextAlignLeft", constraints: { type: "string", @@ -31,7 +33,7 @@ export const FIELDS = { }, OPTIONS: { name: "Options", - type: "options", + type: FieldType.OPTIONS, icon: "Dropdown", constraints: { type: "string", @@ -41,7 +43,7 @@ export const FIELDS = { }, ARRAY: { name: "Multi-select", - type: "array", + type: FieldType.ARRAY, icon: "Duplicate", constraints: { type: "array", @@ -51,7 +53,7 @@ export const FIELDS = { }, NUMBER: { name: "Number", - type: "number", + type: FieldType.NUMBER, icon: "123", constraints: { type: "number", @@ -61,12 +63,12 @@ export const FIELDS = { }, BIGINT: { name: "BigInt", - type: "bigint", + type: FieldType.BIGINT, icon: "TagBold", }, BOOLEAN: { name: "Boolean", - type: "boolean", + type: FieldType.BOOLEAN, icon: "Boolean", constraints: { type: "boolean", @@ -75,7 +77,7 @@ export const FIELDS = { }, DATETIME: { name: "Date/Time", - type: "datetime", + type: FieldType.DATETIME, icon: "Calendar", constraints: { type: "string", @@ -89,7 +91,7 @@ export const FIELDS = { }, ATTACHMENT: { name: "Attachment", - type: "attachment", + type: FieldType.ATTACHMENT, icon: "Folder", constraints: { type: "array", @@ -98,7 +100,7 @@ export const FIELDS = { }, LINK: { name: "Relationship", - type: "link", + type: FieldType.LINK, icon: "Link", constraints: { type: "array", @@ -107,26 +109,34 @@ export const FIELDS = { }, FORMULA: { name: "Formula", - type: "formula", + type: FieldType.FORMULA, icon: "Calculator", constraints: {}, }, JSON: { name: "JSON", - type: "json", + type: FieldType.JSON, icon: "Brackets", constraints: { type: "object", presence: false, }, }, - BB_REFERENCE_USER: { + USER: { name: "User", - type: "bb_reference", - subtype: "user", - compositeType: "bb_reference_user", // Used for working with the subtype on CreateEditColumn as is it was a primary type + type: FieldType.BB_REFERENCE, + subtype: FieldSubtype.USER, icon: "User", }, + USERS: { + name: "Users", + type: FieldType.BB_REFERENCE, + subtype: FieldSubtype.USERS, + icon: "User", + constraints: { + type: "array", + }, + }, } export const AUTO_COLUMN_SUB_TYPES = { diff --git a/packages/builder/src/helpers/duplicate.js b/packages/builder/src/helpers/duplicate.js index e84a600138..1547fcd4d1 100644 --- a/packages/builder/src/helpers/duplicate.js +++ b/packages/builder/src/helpers/duplicate.js @@ -3,16 +3,17 @@ * e.g. * name all names result * ------ ----------- -------- - * ("foo") ["foo"] "foo (1)" - * ("foo") ["foo", "foo (1)"] "foo (2)" - * ("foo (1)") ["foo", "foo (1)"] "foo (2)" - * ("foo") ["foo", "foo (2)"] "foo (1)" + * ("foo") ["foo"] "foo 1" + * ("foo") ["foo", "foo 1"] "foo 2" + * ("foo 1") ["foo", "foo 1"] "foo 2" + * ("foo") ["foo", "foo 2"] "foo 1" * * Repl */ export const duplicateName = (name, allNames) => { - const baseName = name.split(" (")[0] - const isDuplicate = new RegExp(`${baseName}\\s\\((\\d+)\\)$`) + const duplicatePattern = new RegExp(`\\s(\\d+)$`) + const baseName = name.split(duplicatePattern)[0] + const isDuplicate = new RegExp(`${baseName}\\s(\\d+)$`) // get the sequence from matched names const sequence = [] @@ -28,7 +29,6 @@ export const duplicateName = (name, allNames) => { return false }) sequence.sort((a, b) => a - b) - // get the next number in the sequence let number if (sequence.length === 0) { @@ -46,5 +46,5 @@ export const duplicateName = (name, allNames) => { } } - return `${baseName} (${number})` + return `${baseName} ${number}` } diff --git a/packages/builder/src/helpers/tests/duplicate.test.js b/packages/builder/src/helpers/tests/duplicate.test.js index a571054e0c..400abed0aa 100644 --- a/packages/builder/src/helpers/tests/duplicate.test.js +++ b/packages/builder/src/helpers/tests/duplicate.test.js @@ -9,34 +9,34 @@ describe("duplicate", () => { const duplicate = duplicateName(name, names) - expect(duplicate).toBe("foo (1)") + expect(duplicate).toBe("foo 1") }) it("with multiple existing", async () => { - const names = ["foo", "foo (1)", "foo (2)"] + const names = ["foo", "foo 1", "foo 2"] const name = "foo" const duplicate = duplicateName(name, names) - expect(duplicate).toBe("foo (3)") + expect(duplicate).toBe("foo 3") }) it("with mixed multiple existing", async () => { - const names = ["foo", "foo (1)", "foo (2)", "bar", "bar (1)", "bar (2)"] + const names = ["foo", "foo 1", "foo 2", "bar", "bar 1", "bar 2"] const name = "foo" const duplicate = duplicateName(name, names) - expect(duplicate).toBe("foo (3)") + expect(duplicate).toBe("foo 3") }) it("with incomplete sequence", async () => { - const names = ["foo", "foo (2)", "foo (3)"] + const names = ["foo", "foo 2", "foo 3"] const name = "foo" const duplicate = duplicateName(name, names) - expect(duplicate).toBe("foo (1)") + expect(duplicate).toBe("foo 1") }) }) }) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte index d21b4799a1..ddbc8b004e 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ConditionalUIDrawer.svelte @@ -118,7 +118,7 @@ } const getOperatorOptions = condition => { - return LuceneUtils.getValidOperatorsForType(condition.valueType) + return LuceneUtils.getValidOperatorsForType({ type: condition.valueType }) } const onOperatorChange = (condition, newOperator) => { @@ -137,9 +137,9 @@ condition.referenceValue = null // Ensure a valid operator is set - const validOperators = LuceneUtils.getValidOperatorsForType(newType).map( - x => x.value - ) + const validOperators = LuceneUtils.getValidOperatorsForType({ + type: newType, + }).map(x => x.value) if (!validOperators.includes(condition.operator)) { condition.operator = validOperators[0] ?? Constants.OperatorOptions.Equals.value diff --git a/packages/client/manifest.json b/packages/client/manifest.json index bf32b98ff6..0dc82cadd2 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5688,11 +5688,6 @@ "label": "Validation", "key": "validation" }, - { - "type": "filter/relationship", - "label": "Filtering", - "key": "filter" - }, { "type": "boolean", "label": "Search", diff --git a/packages/client/src/components/app/dynamic-filter/FilterModal.svelte b/packages/client/src/components/app/dynamic-filter/FilterModal.svelte index 513c19126d..0adf508dfa 100644 --- a/packages/client/src/components/app/dynamic-filter/FilterModal.svelte +++ b/packages/client/src/components/app/dynamic-filter/FilterModal.svelte @@ -63,7 +63,7 @@ // Ensure a valid operator is set const validOperators = LuceneUtils.getValidOperatorsForType( - expression.type, + { type: expression.type }, expression.field, datasource ).map(x => x.value) @@ -125,7 +125,7 @@