From 40966731c9663828f0c82fa3bdd07f41f92dff8f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 10 Oct 2024 16:56:52 +0200 Subject: [PATCH 01/15] Remove related fields popover --- .../grid/cells/RelationshipCell.svelte | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index 73c8a99cc2..7532ee8b0e 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -56,12 +56,6 @@ return acc }, {}) - $: showRelationshipFields = - relationshipFields && - Object.keys(relationshipFields).length && - focused && - !isOpen - const parseValue = value => { if (Array.isArray(value) && value.every(x => x?._id)) { return value @@ -358,21 +352,6 @@ {/if} -{#if showRelationshipFields} - -
- {#each Object.entries(relationshipFields) as [fieldName, fieldValue]} -
- {fieldName} -
-
- {fieldValue} -
- {/each} -
-
-{/if} - From 56aaacb437551752834a7578bb4da9a85805e6c9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 10 Oct 2024 17:12:53 +0200 Subject: [PATCH 02/15] Lint --- .../components/grid/cells/RelationshipCell.svelte | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index 7532ee8b0e..8f773d3f90 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -27,9 +27,7 @@ let candidateIndex let lastSearchId let searching = false - let container let anchor - let relationshipFields $: fieldValue = parseValue(value) $: oneRowOnly = schema?.relationshipType === "one-to-many" @@ -236,14 +234,6 @@ return value } - const displayRelationshipFields = relationship => { - relationshipFields = relationFields[relationship._id] - } - - const hideRelationshipFields = () => { - relationshipFields = undefined - } - onMount(() => { api = { focus: open, @@ -263,7 +253,7 @@ style="--color:{color};" bind:this={anchor} > -
+
1} @@ -275,9 +265,7 @@
displayRelationshipFields(relationship)} on:focus={() => {}} - on:mouseleave={() => hideRelationshipFields()} > {readable( From 964f8222babbdb7867ed1b3df834ac693d687851 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 10 Oct 2024 17:10:07 +0100 Subject: [PATCH 03/15] Allow sorting by calculation fields. --- packages/backend-core/src/sql/sql.ts | 24 +++- .../src/api/routes/tests/viewV2.spec.ts | 118 ++++++++++++++++++ .../src/sdk/app/rows/search/internal/sqs.ts | 27 ++-- 3 files changed, 156 insertions(+), 13 deletions(-) diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index f122ad1c41..382eca3f76 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -273,6 +273,7 @@ class InternalBuilder { const col = parts.pop()! const schema = this.table.schema[col] let identifier = this.quotedIdentifier(field) + if ( schema.type === FieldType.STRING || schema.type === FieldType.LONGFORM || @@ -957,6 +958,13 @@ class InternalBuilder { return query } + isAggregateField(field: string): boolean { + const found = this.query.resource?.aggregations?.find( + aggregation => aggregation.name === field + ) + return !!found + } + addSorting(query: Knex.QueryBuilder): Knex.QueryBuilder { let { sort, resource } = this.query const primaryKey = this.table.primary @@ -979,13 +987,17 @@ class InternalBuilder { nulls = value.direction === SortOrder.ASCENDING ? "first" : "last" } - let composite = `${aliased}.${key}` - if (this.client === SqlClient.ORACLE) { - query = query.orderByRaw( - `${this.convertClobs(composite)} ${direction} nulls ${nulls}` - ) + if (this.isAggregateField(key)) { + query = query.orderBy(key, direction, nulls) } else { - query = query.orderBy(composite, direction, nulls) + let composite = `${aliased}.${key}` + if (this.client === SqlClient.ORACLE) { + query = query.orderByRaw( + `${this.convertClobs(composite)} ${direction} nulls ${nulls}` + ) + } else { + query = query.orderBy(composite, direction, nulls) + } } } } diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 0b4237406f..eae429d49d 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -3381,6 +3381,124 @@ describe.each([ expect(rows).toHaveLength(1) expect(rows[0].sum).toEqual(3) }) + + it("should be able to sort by group by field", async () => { + const table = await config.api.table.save( + saveTableRequest({ + schema: { + quantity: { + type: FieldType.NUMBER, + name: "quantity", + }, + price: { + type: FieldType.NUMBER, + name: "price", + }, + }, + }) + ) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + type: ViewV2Type.CALCULATION, + schema: { + quantity: { visible: true }, + sum: { + visible: true, + calculationType: CalculationType.SUM, + field: "price", + }, + }, + }) + + await config.api.row.bulkImport(table._id!, { + rows: [ + { + quantity: 1, + price: 1, + }, + { + quantity: 1, + price: 2, + }, + { + quantity: 2, + price: 10, + }, + ], + }) + + const { rows } = await config.api.viewV2.search(view.id, { + query: {}, + sort: "quantity", + sortOrder: SortOrder.DESCENDING, + }) + + expect(rows).toEqual([ + expect.objectContaining({ quantity: 2, sum: 10 }), + expect.objectContaining({ quantity: 1, sum: 3 }), + ]) + }) + + it("should be able to sort by a calculation", async () => { + const table = await config.api.table.save( + saveTableRequest({ + schema: { + quantity: { + type: FieldType.NUMBER, + name: "quantity", + }, + price: { + type: FieldType.NUMBER, + name: "price", + }, + }, + }) + ) + + await config.api.row.bulkImport(table._id!, { + rows: [ + { + quantity: 1, + price: 1, + }, + { + quantity: 1, + price: 2, + }, + { + quantity: 2, + price: 10, + }, + ], + }) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + type: ViewV2Type.CALCULATION, + schema: { + quantity: { visible: true }, + sum: { + visible: true, + calculationType: CalculationType.SUM, + field: "price", + }, + }, + }) + + const { rows } = await config.api.viewV2.search(view.id, { + query: {}, + sort: "sum", + sortOrder: SortOrder.DESCENDING, + }) + + expect(rows).toEqual([ + expect.objectContaining({ quantity: 2, sum: 10 }), + expect.objectContaining({ quantity: 1, sum: 3 }), + ]) + }) }) !isLucene && diff --git a/packages/server/src/sdk/app/rows/search/internal/sqs.ts b/packages/server/src/sdk/app/rows/search/internal/sqs.ts index ad323a8c12..916e20957b 100644 --- a/packages/server/src/sdk/app/rows/search/internal/sqs.ts +++ b/packages/server/src/sdk/app/rows/search/internal/sqs.ts @@ -418,13 +418,26 @@ export async function search( if (params.sort) { const sortField = table.schema[params.sort] - const sortType = - sortField.type === FieldType.NUMBER ? SortType.NUMBER : SortType.STRING - request.sort = { - [mapToUserColumn(sortField.name)]: { - direction: params.sortOrder || SortOrder.ASCENDING, - type: sortType as SortType, - }, + const isAggregateField = aggregations.some(agg => agg.name === params.sort) + + if (isAggregateField) { + request.sort = { + [params.sort]: { + direction: params.sortOrder || SortOrder.ASCENDING, + type: SortType.NUMBER, + }, + } + } else if (sortField) { + const sortType = + sortField.type === FieldType.NUMBER ? SortType.NUMBER : SortType.STRING + request.sort = { + [mapToUserColumn(sortField.name)]: { + direction: params.sortOrder || SortOrder.ASCENDING, + type: sortType as SortType, + }, + } + } else { + throw new Error(`Unable to sort by ${params.sort}`) } } From ecddd46fc6aee7a0551870e513c6cf3c7c068da8 Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:59:01 +0100 Subject: [PATCH 04/15] Update pro (#14769) --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index aca9828117..fc4c7f4925 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit aca9828117bb97f54f40ee359f1a3f6e259174e7 +Subproject commit fc4c7f4925139af078480217965c3d6338dc0a7f From 1bcd2590d3285f404e4f84c48bae8e8cf6992589 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 11 Oct 2024 12:28:20 +0100 Subject: [PATCH 05/15] Allow BB_REFERNCE_SINGLE to have default values. --- .../server/src/api/routes/tests/row.spec.ts | 32 +++++++++++++++++++ packages/shared-core/src/table.ts | 2 +- .../types/src/documents/app/table/schema.ts | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index b86ec38d08..61031a79b4 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -14,6 +14,7 @@ import { InternalTable, tenancy, features, + utils, } from "@budibase/backend-core" import { quotas } from "@budibase/pro" import { @@ -757,6 +758,37 @@ describe.each([ }) }) + describe.only("user column", () => { + beforeAll(async () => { + table = await config.api.table.save( + saveTableRequest({ + schema: { + user: { + name: "user", + type: FieldType.BB_REFERENCE_SINGLE, + subtype: BBReferenceFieldSubType.USER, + default: "{{ [Current User]._id }}", + }, + }, + }) + ) + }) + + it("creates a new row with a default value successfully", async () => { + const row = await config.api.row.save(table._id!, {}) + expect(row.user._id).toEqual(config.getUser()._id) + }) + + it("does not use default value if value specified", async () => { + const id = `us_${utils.newid()}` + await config.createUser({ _id: id }) + const row = await config.api.row.save(table._id!, { + user: id, + }) + expect(row.user._id).toEqual(id) + }) + }) + describe("bindings", () => { describe("string column", () => { beforeAll(async () => { diff --git a/packages/shared-core/src/table.ts b/packages/shared-core/src/table.ts index 57f6854604..677b1e2357 100644 --- a/packages/shared-core/src/table.ts +++ b/packages/shared-core/src/table.ts @@ -67,7 +67,7 @@ const allowDefaultColumnByType: Record = { [FieldType.SIGNATURE_SINGLE]: false, [FieldType.LINK]: false, [FieldType.BB_REFERENCE]: false, - [FieldType.BB_REFERENCE_SINGLE]: false, + [FieldType.BB_REFERENCE_SINGLE]: true, } export function canBeDisplayColumn(type: FieldType): boolean { diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index f9d1a4c012..f5bb081fd5 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -126,6 +126,7 @@ export interface BBReferenceSingleFieldMetadata extends Omit { type: FieldType.BB_REFERENCE_SINGLE subtype: Exclude + default?: string } export interface AttachmentFieldMetadata extends BaseFieldSchema { From abb01ba98a5538fd295d4acadff60472d0fc1ab0 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 11 Oct 2024 11:58:51 +0000 Subject: [PATCH 06/15] Bump version to 2.32.17 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index ba3db109d0..13530e9aee 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "2.32.16", + "version": "2.32.17", "npmClient": "yarn", "packages": [ "packages/*", From c0e89c47adedc16ed62aa7d7ebcb5a12b0db2143 Mon Sep 17 00:00:00 2001 From: Christos Alexiou Date: Fri, 11 Oct 2024 17:29:09 +0300 Subject: [PATCH 07/15] Extend available labels --- .github/workflows/deploy-featurebranch.yml | 40 +++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-featurebranch.yml b/.github/workflows/deploy-featurebranch.yml index d86d301507..0e19f0649f 100644 --- a/.github/workflows/deploy-featurebranch.yml +++ b/.github/workflows/deploy-featurebranch.yml @@ -3,26 +3,50 @@ name: deploy-featurebranch on: pull_request: types: [ - labeled, - # default types below (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) - opened, - synchronize, - reopened, - ] + labeled, + # default types below (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) + opened, + synchronize, + reopened, + ] jobs: release: if: | (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase') && - contains(github.event.pull_request.labels.*.name, 'feature-branch') + ( + contains(github.event.pull_request.labels.*.name, 'feature-branch') || + contains(github.event.pull_request.labels.*.name, 'feature-branch-pro') || + contains(github.event.pull_request.labels.*.name, 'feature-branch-team') || + contains(github.event.pull_request.labels.*.name, 'feature-branch-business') || + contains(github.event.pull_request.labels.*.name, 'feature-branch-enterprise') + ) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + + - name: Set PAYLOAD_LICENSE_TYPE + id: set_license_type + run: | + if [[ "${{ contains(github.event.pull_request.labels.*.name, 'feature-branch') }}" == "true" ]]; then + echo "PAYLOAD_LICENSE_TYPE=free" >> $GITHUB_ENV + elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'feature-branch-pro') }}" == "true" ]]; then + echo "PAYLOAD_LICENSE_TYPE=pro" >> $GITHUB_ENV + elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'feature-branch-team') }}" == "true" ]]; then + echo "PAYLOAD_LICENSE_TYPE=team" >> $GITHUB_ENV + elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'feature-branch-business') }}" == "true" ]]; then + echo "PAYLOAD_LICENSE_TYPE=business" >> $GITHUB_ENV + elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'feature-branch-enterprise') }}" == "true" ]]; then + echo "PAYLOAD_LICENSE_TYPE=enterprise" >> $GITHUB_ENV + else + echo "PAYLOAD_LICENSE_TYPE=free" >> $GITHUB_ENV + fi + - uses: passeidireto/trigger-external-workflow-action@main env: PAYLOAD_BRANCH: ${{ github.head_ref }} PAYLOAD_PR_NUMBER: ${{ github.event.pull_request.number }} - PAYLOAD_LICENSE_TYPE: "free" + PAYLOAD_LICENSE_TYPE: ${{ env.PAYLOAD_LICENSE_TYPE }} with: repository: budibase/budibase-deploys event: featurebranch-qa-deploy From 33f7792522906e3dd326bafd59ee669cb0771ff8 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 11 Oct 2024 15:33:33 +0100 Subject: [PATCH 08/15] Potential fix for view search problem. --- .../server/src/api/controllers/row/views.ts | 1 + packages/server/src/sdk/app/rows/search.ts | 19 +++---- .../server/src/sdk/app/rows/search/utils.ts | 54 ++++++++++--------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index 622688deb6..9163c401df 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -8,6 +8,7 @@ import { } from "@budibase/types" import sdk from "../../../sdk" import { context } from "@budibase/backend-core" +import * as utils from "./utils" export async function searchView( ctx: UserCtx diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index f68f78f0bc..ee40f6f4e6 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -16,7 +16,7 @@ import * as external from "./search/external" import { ExportRowsParams, ExportRowsResult } from "./search/types" import { dataFilters } from "@budibase/shared-core" import sdk from "../../index" -import { searchInputMapping } from "./search/utils" +import { checkFilters, searchInputMapping } from "./search/utils" import { db, features } from "@budibase/backend-core" import tracer from "dd-trace" import { getQueryableFields, removeInvalidFilters } from "./queryUtils" @@ -92,8 +92,10 @@ export async function search( // Enrich saved query with ephemeral query params. // We prevent searching on any fields that are saved as part of the query, as // that could let users find rows they should not be allowed to access. - let viewQuery = dataFilters.buildQueryLegacy(view.query) || {} - delete viewQuery?.onEmptyFilter + let query = await enrichSearchContext(view.query || {}, context) + query = dataFilters.buildQueryLegacy(query) || {} + query = checkFilters(table, query) + delete query?.onEmptyFilter const sqsEnabled = await features.flags.isEnabled("SQS") const supportsLogicalOperators = @@ -113,26 +115,25 @@ export async function search( ?.filter(filter => filter.field) .map(filter => db.removeKeyNumbering(filter.field)) || [] - viewQuery ??= {} // Carry over filters for unused fields Object.keys(options.query).forEach(key => { const operator = key as Exclude Object.keys(options.query[operator] || {}).forEach(field => { if (!existingFields.includes(db.removeKeyNumbering(field))) { - viewQuery![operator]![field] = options.query[operator]![field] + query[operator]![field] = options.query[operator]![field] } }) }) - options.query = viewQuery + options.query = query } else { - const conditions = viewQuery ? [viewQuery] : [] + const conditions = query ? [query] : [] options.query = { $and: { conditions: [...conditions, options.query], }, } - if (viewQuery.onEmptyFilter) { - options.query.onEmptyFilter = viewQuery.onEmptyFilter + if (query.onEmptyFilter) { + options.query.onEmptyFilter = query.onEmptyFilter } } } diff --git a/packages/server/src/sdk/app/rows/search/utils.ts b/packages/server/src/sdk/app/rows/search/utils.ts index 90303a6ca7..c7de7eef37 100644 --- a/packages/server/src/sdk/app/rows/search/utils.ts +++ b/packages/server/src/sdk/app/rows/search/utils.ts @@ -80,35 +80,41 @@ function userColumnMapping(column: string, filters: SearchFilters) { }) } +export function checkFilters( + table: Table, + filters: SearchFilters +): SearchFilters { + for (let [key, column] of Object.entries(table.schema || {})) { + switch (column.type) { + case FieldType.BB_REFERENCE_SINGLE: { + const subtype = column.subtype + switch (subtype) { + case BBReferenceFieldSubType.USER: + userColumnMapping(key, filters) + break + + default: + utils.unreachable(subtype) + } + break + } + case FieldType.BB_REFERENCE: { + userColumnMapping(key, filters) + break + } + } + } + return dataFilters.recurseLogicalOperators(filters, filters => + checkFilters(table, filters) + ) +} + // maps through the search parameters to check if any of the inputs are invalid // based on the table schema, converts them to something that is valid. export function searchInputMapping(table: Table, options: RowSearchParams) { // need an internal function to loop over filters, because this takes the full options - function checkFilters(filters: SearchFilters) { - for (let [key, column] of Object.entries(table.schema || {})) { - switch (column.type) { - case FieldType.BB_REFERENCE_SINGLE: { - const subtype = column.subtype - switch (subtype) { - case BBReferenceFieldSubType.USER: - userColumnMapping(key, filters) - break - - default: - utils.unreachable(subtype) - } - break - } - case FieldType.BB_REFERENCE: { - userColumnMapping(key, filters) - break - } - } - } - return dataFilters.recurseLogicalOperators(filters, checkFilters) - } if (options.query) { - options.query = checkFilters(options.query) + options.query = checkFilters(table, options.query) } return options } From 64d4aef87df03fd8fc2575fc56f149da7b006913 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 11 Oct 2024 16:41:51 +0200 Subject: [PATCH 09/15] Fix all or property --- packages/shared-core/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-core/src/utils.ts b/packages/shared-core/src/utils.ts index 4847e911e9..bead5961f0 100644 --- a/packages/shared-core/src/utils.ts +++ b/packages/shared-core/src/utils.ts @@ -172,7 +172,7 @@ export const processSearchFilters = ( .sort((a, b) => { return a.localeCompare(b) }) - .filter(key => key in filter) + .filter(key => filter[key]) if (filterPropertyKeys.length == 1) { const key = filterPropertyKeys[0], From e967e62f1dd9dd2ecf60ed6acb15847f46fbeb6f Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 11 Oct 2024 15:55:25 +0100 Subject: [PATCH 10/15] Add tests. --- .../src/api/routes/tests/viewV2.spec.ts | 44 +++++++++++++++++++ packages/server/src/sdk/app/rows/search.ts | 9 ++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 0b4237406f..abb2a4c83c 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -3428,6 +3428,50 @@ describe.each([ expect(response.rows).toHaveLength(1) expect(response.rows[0].sum).toEqual(61) }) + + it("should be able to filter on a single user field in both the view query and search query", async () => { + const table = await config.api.table.save( + saveTableRequest({ + schema: { + user: { + name: "user", + type: FieldType.BB_REFERENCE_SINGLE, + subtype: BBReferenceFieldSubType.USER, + }, + }, + }) + ) + + await config.api.row.save(table._id!, { + user: config.getUser()._id, + }) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + query: { + equal: { + user: "{{ [user].[_id] }}", + }, + }, + schema: { + user: { + visible: true, + }, + }, + }) + + const { rows } = await config.api.viewV2.search(view.id, { + query: { + equal: { + user: "{{ [user].[_id] }}", + }, + }, + }) + + expect(rows).toHaveLength(1) + expect(rows[0].user._id).toEqual(config.getUser()._id) + }) }) describe("permissions", () => { diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index ee40f6f4e6..94bab9f7f3 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -81,6 +81,10 @@ export async function search( options.query = {} } + if (context) { + options.query = await enrichSearchContext(options.query, context) + } + // need to make sure filters in correct shape before checking for view options = searchInputMapping(table, options) @@ -100,6 +104,7 @@ export async function search( const sqsEnabled = await features.flags.isEnabled("SQS") const supportsLogicalOperators = isExternalTableID(view.tableId) || sqsEnabled + if (!supportsLogicalOperators) { // In the unlikely event that a Grouped Filter is in a non-SQS environment // It needs to be ignored entirely @@ -138,10 +143,6 @@ export async function search( } } - if (context) { - options.query = await enrichSearchContext(options.query, context) - } - options.query = dataFilters.cleanupQuery(options.query) options.query = dataFilters.fixupFilterArrays(options.query) From 1ad371cd7a61380d4f4fdd50a92fcca9c25be709 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 11 Oct 2024 15:58:04 +0100 Subject: [PATCH 11/15] Fix lint. --- packages/server/src/api/controllers/row/views.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index 9163c401df..622688deb6 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -8,7 +8,6 @@ import { } from "@budibase/types" import sdk from "../../../sdk" import { context } from "@budibase/backend-core" -import * as utils from "./utils" export async function searchView( ctx: UserCtx From b24c3378342d44e39b1773a4a885476f577d7257 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 11 Oct 2024 16:22:04 +0100 Subject: [PATCH 12/15] Respond to PR comment. --- packages/server/src/sdk/app/rows/search.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 94bab9f7f3..7ac3bb8ead 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -96,10 +96,10 @@ export async function search( // Enrich saved query with ephemeral query params. // We prevent searching on any fields that are saved as part of the query, as // that could let users find rows they should not be allowed to access. - let query = await enrichSearchContext(view.query || {}, context) - query = dataFilters.buildQueryLegacy(query) || {} - query = checkFilters(table, query) - delete query?.onEmptyFilter + let viewQuery = await enrichSearchContext(view.query || {}, context) + viewQuery = dataFilters.buildQueryLegacy(viewQuery) || {} + viewQuery = checkFilters(table, viewQuery) + delete viewQuery?.onEmptyFilter const sqsEnabled = await features.flags.isEnabled("SQS") const supportsLogicalOperators = @@ -125,20 +125,20 @@ export async function search( const operator = key as Exclude Object.keys(options.query[operator] || {}).forEach(field => { if (!existingFields.includes(db.removeKeyNumbering(field))) { - query[operator]![field] = options.query[operator]![field] + viewQuery[operator]![field] = options.query[operator]![field] } }) }) - options.query = query + options.query = viewQuery } else { - const conditions = query ? [query] : [] + const conditions = viewQuery ? [viewQuery] : [] options.query = { $and: { conditions: [...conditions, options.query], }, } - if (query.onEmptyFilter) { - options.query.onEmptyFilter = query.onEmptyFilter + if (viewQuery.onEmptyFilter) { + options.query.onEmptyFilter = viewQuery.onEmptyFilter } } } From 158e0950450fb66742d0819a64460f979f528b9b Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 11 Oct 2024 16:24:03 +0100 Subject: [PATCH 13/15] Fix lint. --- packages/server/src/api/routes/tests/row.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 61031a79b4..6490b4770a 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -758,7 +758,7 @@ describe.each([ }) }) - describe.only("user column", () => { + describe("user column", () => { beforeAll(async () => { table = await config.api.table.save( saveTableRequest({ From bdbcf2110ea3aa6d3a8ec761806bcdf3b657ceb6 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 11 Oct 2024 17:04:29 +0100 Subject: [PATCH 14/15] Fixing an issue with screen creation - wrong base permissions were being detected for external DBs. --- packages/server/src/sdk/app/permissions/index.ts | 3 +-- packages/server/src/utilities/security.ts | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts index 97af9ccc83..7c385864f4 100644 --- a/packages/server/src/sdk/app/permissions/index.ts +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -73,8 +73,7 @@ export async function getResourcePerms( p[level] = { role, type: PermissionSource.BASE } return p }, {}) - const result = Object.assign(basePermissions, permissions) - return result + return Object.assign(basePermissions, permissions) } export async function getDependantResources( diff --git a/packages/server/src/utilities/security.ts b/packages/server/src/utilities/security.ts index 4f93c33ee4..7cfbeb197c 100644 --- a/packages/server/src/utilities/security.ts +++ b/packages/server/src/utilities/security.ts @@ -1,5 +1,5 @@ -import { permissions, roles } from "@budibase/backend-core" -import { DocumentType, VirtualDocumentType } from "../db/utils" +import { DocumentType, permissions, roles } from "@budibase/backend-core" +import { VirtualDocumentType } from "../db/utils" import { getDocumentType, getVirtualDocumentType } from "@budibase/types" export const CURRENTLY_SUPPORTED_LEVELS: string[] = [ @@ -19,6 +19,7 @@ export function getPermissionType(resourceId: string) { switch (docType) { case DocumentType.TABLE: case DocumentType.ROW: + case DocumentType.DATASOURCE_PLUS: return permissions.PermissionType.TABLE case DocumentType.AUTOMATION: return permissions.PermissionType.AUTOMATION From d0cb8733826ac31f4b15634233d8038dde6485bf Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 11 Oct 2024 17:55:59 +0100 Subject: [PATCH 15/15] Adding test case for the fix. --- .../server/src/api/routes/tests/table.spec.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index d6c1651ef0..f7fe6a66d4 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -21,6 +21,7 @@ import { ViewCalculation, ViewV2Enriched, RowExportFormat, + PermissionLevel, } from "@budibase/types" import { checkBuilderEndpoint } from "./utilities/TestFunctions" import * as setup from "./utilities" @@ -191,6 +192,55 @@ describe.each([ ) }) + describe("permissions", () => { + it("get the base permissions for the table", async () => { + const table = await config.api.table.save( + tableForDatasource(datasource, { + schema: { + name: { + type: FieldType.STRING, + name: "name", + }, + }, + }) + ) + + // get the explicit permissions + const { permissions } = await config.api.permission.get(table._id!, { + status: 200, + }) + const explicitPermissions = { + role: "ADMIN", + permissionType: "EXPLICIT", + } + expect(permissions.write).toEqual(explicitPermissions) + expect(permissions.read).toEqual(explicitPermissions) + + // revoke the explicit permissions + for (let level of [PermissionLevel.WRITE, PermissionLevel.READ]) { + await config.api.permission.revoke( + { + roleId: permissions[level].role, + resourceId: table._id!, + level, + }, + { status: 200 } + ) + } + + // check base permissions + const { permissions: basePermissions } = await config.api.permission.get( + table._id!, + { + status: 200, + } + ) + const basePerms = { role: "BASIC", permissionType: "BASE" } + expect(basePermissions.write).toEqual(basePerms) + expect(basePermissions.read).toEqual(basePerms) + }) + }) + describe("update", () => { it("updates a table", async () => { const table = await config.api.table.save(