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 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/*", 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/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index afdc3d2f30..6313681f36 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -27,10 +27,7 @@ let candidateIndex let lastSearchId let searching = false - let container let anchor - let relationshipAnchor - let relationshipFields $: fieldValue = parseValue(value) $: oneRowOnly = schema?.relationshipType === "one-to-many" @@ -57,13 +54,6 @@ return acc }, {}) - $: showRelationshipFields = - relationshipAnchor && - relationshipFields && - Object.keys(relationshipFields).length && - focused && - !isOpen - const parseValue = value => { if (Array.isArray(value) && value.every(x => x?._id)) { return value @@ -245,16 +235,6 @@ return value } - const displayRelationshipFields = (e, relationship) => { - relationshipAnchor = e.target - relationshipFields = relationFields[relationship._id] - } - - const hideRelationshipFields = () => { - relationshipAnchor = null - relationshipFields = undefined - } - onMount(() => { api = { focus: open, @@ -274,7 +254,7 @@ style="--color:{color};" bind:this={anchor} > -
+
1} @@ -286,9 +266,7 @@
displayRelationshipFields(e, relationship)} on:focus={() => {}} - on:mouseleave={() => hideRelationshipFields()} > {readable( @@ -363,26 +341,6 @@ {/if} -{#if showRelationshipFields} - -
- {#each Object.entries(relationshipFields) as [fieldName, fieldValue]} -
- {fieldName} -
-
- {fieldValue} -
- {/each} -
-
-{/if} - 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 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}`) } } 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],