diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index fc35575ec6..9d1131ed7f 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -30,8 +30,8 @@ jobs: with: root-reserve-mb: 35000 swap-size-mb: 1024 - remove-android: 'true' - remove-dotnet: 'true' + 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' @@ -121,7 +121,7 @@ jobs: name: codecov-umbrella verbose: true - test-services: + test-worker: runs-on: ubuntu-latest steps: - name: Checkout repo and submodules @@ -143,12 +143,48 @@ jobs: node-version: 18.x cache: "yarn" - run: yarn --frozen-lockfile - - name: Test worker and server + - name: Test worker run: | if ${{ env.USE_NX_AFFECTED }}; then - yarn test --scope=@budibase/worker --scope=@budibase/server --since=${{ env.NX_BASE_BRANCH }} + yarn test --scope=@budibase/worker --since=${{ env.NX_BASE_BRANCH }} else - yarn test --scope=@budibase/worker --scope=@budibase/server + yarn test --scope=@budibase/worker + fi + + - uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN || github.token }} # not required for public repos + name: codecov-umbrella + verbose: true + + test-server: + runs-on: ubuntu-latest + steps: + - 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' + with: + submodules: true + token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} + fetch-depth: 0 + - name: Checkout repo only + uses: actions/checkout@v3 + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase' + with: + fetch-depth: 0 + + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: "yarn" + - run: yarn --frozen-lockfile + - name: Test server + run: | + if ${{ env.USE_NX_AFFECTED }}; then + yarn test --scope=@budibase/server --since=${{ env.NX_BASE_BRANCH }} + else + yarn test --scope=@budibase/server fi - uses: codecov/codecov-action@v3 @@ -259,4 +295,4 @@ jobs: process.exit(1); } else { console.log('All good, the submodule had been merged and setup correctly!') - } \ No newline at end of file + } diff --git a/lerna.json b/lerna.json index 0283ac132c..c4d2219e21 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.10.12-alpha.17", + "version": "2.10.12-alpha.23", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/src/cache/user.ts b/packages/backend-core/src/cache/user.ts index b3fd7c08cd..481d3691e4 100644 --- a/packages/backend-core/src/cache/user.ts +++ b/packages/backend-core/src/cache/user.ts @@ -120,7 +120,7 @@ export async function getUsers( ): Promise<{ users: User[]; notFoundIds?: string[] }> { const client = await redis.getUserClient() // try cache - let usersFromCache = await client.bulkGet(userIds) + let usersFromCache = await client.bulkGet(userIds) const missingUsersFromCache = userIds.filter(uid => !usersFromCache[uid]) const users = Object.values(usersFromCache) let notFoundIds diff --git a/packages/backend-core/src/redis/redis.ts b/packages/backend-core/src/redis/redis.ts index 78817d0aa0..e7755f275d 100644 --- a/packages/backend-core/src/redis/redis.ts +++ b/packages/backend-core/src/redis/redis.ts @@ -242,7 +242,7 @@ class RedisWrapper { } } - async bulkGet(keys: string[]) { + async bulkGet(keys: string[]) { const db = this._db if (keys.length === 0) { return {} @@ -250,7 +250,7 @@ class RedisWrapper { const prefixedKeys = keys.map(key => addDbPrefix(db, key)) let response = await this.getClient().mget(prefixedKeys) if (Array.isArray(response)) { - let final: Record = {} + let final: Record = {} let count = 0 for (let result of response) { if (result) { diff --git a/packages/backend-core/tests/extra/DBTestConfiguration.ts b/packages/backend-core/tests/extra/DBTestConfiguration.ts index a2550a6e24..99a5bcba46 100644 --- a/packages/backend-core/tests/extra/DBTestConfiguration.ts +++ b/packages/backend-core/tests/extra/DBTestConfiguration.ts @@ -18,7 +18,7 @@ class DBTestConfiguration { // TENANCY - doInTenant(task: any) { + doInTenant(task: () => Promise) { return context.doInTenant(this.tenantId, () => { return task() }) diff --git a/packages/backend-core/tests/index.ts b/packages/backend-core/tests/index.ts index 50fc1dc431..cdbacc12d8 100644 --- a/packages/backend-core/tests/index.ts +++ b/packages/backend-core/tests/index.ts @@ -1 +1,2 @@ export * from "./core/utilities" +export * from "./extra" diff --git a/packages/bbui/src/Form/Core/TextField.svelte b/packages/bbui/src/Form/Core/TextField.svelte index b3b0865c64..7afd8f86c3 100644 --- a/packages/bbui/src/Form/Core/TextField.svelte +++ b/packages/bbui/src/Form/Core/TextField.svelte @@ -96,8 +96,8 @@ {disabled} {readonly} {id} - value={value || ""} - placeholder={placeholder || ""} + value={value ?? ""} + placeholder={placeholder ?? ""} on:click on:blur on:focus diff --git a/packages/bbui/src/Table/CellRenderer.svelte b/packages/bbui/src/Table/CellRenderer.svelte index c64b975884..4ad6e22d7e 100644 --- a/packages/bbui/src/Table/CellRenderer.svelte +++ b/packages/bbui/src/Table/CellRenderer.svelte @@ -25,6 +25,7 @@ longform: StringRenderer, array: ArrayRenderer, internal: InternalRenderer, + bb_reference: RelationshipRenderer, } $: type = getType(schema) $: customRenderer = customRenderers?.find(x => x.column === schema?.name) diff --git a/packages/builder/src/components/backend/DataTable/ExternalDataSourceTable.svelte b/packages/builder/src/components/backend/DataTable/ExternalDataSourceTable.svelte deleted file mode 100644 index bc18bf1b41..0000000000 --- a/packages/builder/src/components/backend/DataTable/ExternalDataSourceTable.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - -{#if error} -
{error}
-{/if} - - - diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index d63cc4a286..cabbe6b9df 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -33,6 +33,7 @@ import { getBindings } from "components/backend/DataTable/formula" import JSONSchemaModal from "./JSONSchemaModal.svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" + import { FieldSubtype, FieldType } from "@budibase/types" import RelationshipSelector from "components/common/RelationshipSelector.svelte" const AUTO_TYPE = "auto" @@ -42,6 +43,11 @@ const NUMBER_TYPE = FIELDS.NUMBER.type const JSON_TYPE = FIELDS.JSON.type const DATE_TYPE = FIELDS.DATETIME.type + const BB_REFERENCE_TYPE = FieldType.BB_REFERENCE + const BB_USER_REFERENCE_TYPE = composeType( + BB_REFERENCE_TYPE, + FieldSubtype.USER + ) const dispatch = createEventDispatcher() const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] @@ -70,7 +76,7 @@ let jsonSchemaModal let allowedTypes = [] let editableColumn = { - type: "string", + type: fieldDefinitions.STRING.type, constraints: fieldDefinitions.STRING.constraints, // Initial value for column name in other table for linked records fieldName: $tables.selected.name, @@ -78,6 +84,33 @@ let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions) let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions) + const bbRefTypeMapping = {} + + function composeType(fieldType, subtype) { + return `${fieldType}_${subtype}` + } + + // Handling fields with subtypes + fieldDefinitions = Object.entries(fieldDefinitions).reduce( + (p, [key, field]) => { + if (field.type === BB_REFERENCE_TYPE) { + const composedType = composeType(field.type, field.subtype) + p[key] = { + ...field, + type: composedType, + } + bbRefTypeMapping[composedType] = { + type: field.type, + subtype: field.subtype, + } + } else { + p[key] = field + } + return p + }, + {} + ) + $: if (primaryDisplay) { editableColumn.constraints.presence = { allowEmpty: false } } @@ -128,6 +161,7 @@ } const initialiseField = (field, savingColumn) => { isCreating = !field + if (field && !savingColumn) { editableColumn = cloneDeep(field) originalName = editableColumn.name ? editableColumn.name + "" : null @@ -148,6 +182,13 @@ relationshipPart1 = part1 relationshipPart2 = part2 } + + const mapped = Object.entries(bbRefTypeMapping).find( + ([_, v]) => v.type === field.type && v.subtype === field.subtype + ) + if (mapped) { + editableColumn.type = mapped[0] + delete editableColumn.subtype } } else if (!savingColumn) { let highestNumber = 0 @@ -165,11 +206,14 @@ editableColumn.name = "Column 01" } } + allowedTypes = getAllowedTypes() } $: initialiseField(field, savingColumn) + $: isBBReference = !!bbRefTypeMapping[editableColumn.type] + $: checkConstraints(editableColumn) $: required = !!editableColumn?.constraints?.presence || primaryDisplay $: uneditable = @@ -242,6 +286,13 @@ let saveColumn = cloneDeep(editableColumn) + if (bbRefTypeMapping[saveColumn.type]) { + saveColumn = { + ...saveColumn, + ...bbRefTypeMapping[saveColumn.type], + } + } + if (saveColumn.type === AUTO_TYPE) { saveColumn = buildAutoColumn( $tables.selected.name, @@ -320,9 +371,10 @@ // Default relationships many to many if (editableColumn.type === LINK_TYPE) { editableColumn.relationshipType = RelationshipType.MANY_TO_MANY - } - if (editableColumn.type === FORMULA_TYPE) { + } else if (editableColumn.type === FORMULA_TYPE) { editableColumn.formulaType = "dynamic" + } else if (editableColumn.type === BB_USER_REFERENCE_TYPE) { + editableColumn.relationshipType = RelationshipType.ONE_TO_MANY } } @@ -361,7 +413,9 @@ ALLOWABLE_NUMBER_TYPES.indexOf(editableColumn.type) !== -1 ) { return ALLOWABLE_NUMBER_OPTIONS - } else if (!external) { + } + + if (!external) { return [ ...Object.values(fieldDefinitions), { name: "Auto Column", type: AUTO_TYPE }, @@ -382,6 +436,9 @@ if (!external || table.sql) { fields = [...fields, FIELDS.LINK, FIELDS.ARRAY] } + if (fieldDefinitions.USER) { + fields.push(fieldDefinitions.USER) + } return fields } } @@ -637,6 +694,17 @@ + {:else if isBBReference} + + (editableColumn.relationshipType = e.detail + ? RelationshipType.MANY_TO_MANY + : RelationshipType.ONE_TO_MANY)} + disabled={!isCreating} + thin + text="Allow multiple users" + /> {/if} {#if editableColumn.type === AUTO_TYPE || editableColumn.autocolumn} populateExtraQuery(extraQueryFields)} - bind:value={extraQueryFields[key]} - /> - {/if} + + {#if type === "string"} + populateExtraQuery(extraQueryFields)} + bind:value={extraQueryFields[key]} + /> + {/if} - {#if type === "list"} - populateExtraQuery(extraQueryFields)} + bind:value={extraQueryFields[key]} + options={config[key].data[query.queryVerb]} + getOptionLabel={current => current} + /> + {/if} {/each} - - diff --git a/packages/builder/src/components/integration/KeyValueBuilder.svelte b/packages/builder/src/components/integration/KeyValueBuilder.svelte index 5d35498cfe..096d5c0f71 100644 --- a/packages/builder/src/components/integration/KeyValueBuilder.svelte +++ b/packages/builder/src/components/integration/KeyValueBuilder.svelte @@ -34,6 +34,7 @@ export let bindings = [] export let bindingDrawerLeft export let allowHelpers = true + export let customButtonText = null let fields = Object.entries(object || {}).map(([name, value]) => ({ name, @@ -158,9 +159,13 @@ {/if} {#if !readOnly && !noAddButton}
- Add{name ? ` ${lowercase(name)}` : ""} + + {#if customButtonText} + {customButtonText} + {:else} + {`Add${name ? ` ${lowercase(name)}` : ""}`} + {/if} +
{/if} diff --git a/packages/builder/src/components/integration/QueryViewer.svelte b/packages/builder/src/components/integration/QueryViewer.svelte index 4683bc6335..6e7ee52268 100644 --- a/packages/builder/src/components/integration/QueryViewer.svelte +++ b/packages/builder/src/components/integration/QueryViewer.svelte @@ -1,364 +1,443 @@ - { - navigateTo = null - }} -> - { - await saveQuery() - override = true - resumeNavigation() - }} - onCancel={async () => { - override = true - resumeNavigation() - }} - > - Leaving this section will mean losing and changes to your query - - - -
- - Query {integrationInfo?.friendlyName} - - Config -
-
- - { - let newValue = e.target.value || "" - if (newValue.match(ValidQueryNameRegex)) { - query.name = newValue.trim() - nameError = null - } else { - nameError = "Invalid query name" - } - }} - error={nameError} - /> -
- {#if queryConfig} -
- - { + let newValue = e.target.value || "" + if (newValue.match(ValidQueryNameRegex)) { + newQuery.name = newValue.trim() + nameError = null + } else { + nameError = "Invalid query name" + } + }} + error={nameError} + /> + {#if integration.query} + +