diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 8ba2f4b0ba..cc007e121c 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -2,7 +2,13 @@ import { quotas } from "@budibase/pro" import * as internal from "./internal" import * as external from "./external" import { isExternalTable } from "../../../integrations/utils" -import { Ctx, SearchResponse } from "@budibase/types" +import { + Ctx, + SearchResponse, + SortOrder, + SortType, + ViewV2, +} from "@budibase/types" import * as utils from "./utils" import { gridSocket } from "../../../websockets" import sdk from "../../../sdk" @@ -146,6 +152,45 @@ export async function search(ctx: any) { }) } +function getSortOptions( + ctx: Ctx, + view: ViewV2 +): + | { + sort: string + sortOrder?: SortOrder + sortType?: SortType + } + | undefined { + const { sort_column, sort_order, sort_type } = ctx.query + if (Array.isArray(sort_column)) { + ctx.throw(400, "sort_column cannot be an array") + } + if (Array.isArray(sort_order)) { + ctx.throw(400, "sort_order cannot be an array") + } + if (Array.isArray(sort_type)) { + ctx.throw(400, "sort_type cannot be an array") + } + + if (sort_column) { + return { + sort: sort_column, + sortOrder: sort_order as SortOrder, + sortType: sort_type as SortType, + } + } + if (view.sort) { + return { + sort: view.sort.field, + sortOrder: view.sort.order, + sortType: view.sort.type, + } + } + + return +} + export async function searchView(ctx: Ctx) { const { viewId } = ctx.params @@ -160,9 +205,7 @@ export async function searchView(ctx: Ctx) { sdk.rows.search({ tableId: view.tableId, query: view.query || {}, - sort: view.sort?.field, - sortOrder: view.sort?.order, - sortType: view.sort?.type, + ...getSortOptions(ctx, view), }), { datasourceId: view.tableId, diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 2890085c88..ec55ecd8a7 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -755,7 +755,14 @@ describe("/rows", () => { }) }) - it.each([ + const sortTestOptions: [ + { + field: string + order?: SortOrder + type?: SortType + }, + string[] + ][] = [ [ { field: "name", @@ -815,31 +822,76 @@ describe("/rows", () => { }, ["Bob", "Charly", "Alice", "Danny"], ], - ])("allow sorting (%s)", async (sortParams, expected) => { - await config.createTable(userTable()) - const users = [ - { name: "Alice", age: 25 }, - { name: "Bob", age: 30 }, - { name: "Charly", age: 27 }, - { name: "Danny", age: 15 }, - ] - for (const user of users) { - await config.createRow({ - tableId: config.table!._id, - ...user, + ] + + it.each(sortTestOptions)( + "allow sorting (%s)", + async (sortParams, expected) => { + await config.createTable(userTable()) + const users = [ + { name: "Alice", age: 25 }, + { name: "Bob", age: 30 }, + { name: "Charly", age: 27 }, + { name: "Danny", age: 15 }, + ] + for (const user of users) { + await config.createRow({ + tableId: config.table!._id, + ...user, + }) + } + + const createViewResponse = await config.api.viewV2.create({ + sort: sortParams, + }) + + const response = await config.api.viewV2.search(createViewResponse.id) + + expect(response.body.rows).toHaveLength(4) + expect(response.body).toEqual({ + rows: expected.map(name => expect.objectContaining({ name })), }) } + ) - const createViewResponse = await config.api.viewV2.create({ - sort: sortParams, - }) + it.each(sortTestOptions)( + "allow override the default view sorting (%s)", + async (sortParams, expected) => { + await config.createTable(userTable()) + const users = [ + { name: "Alice", age: 25 }, + { name: "Bob", age: 30 }, + { name: "Charly", age: 27 }, + { name: "Danny", age: 15 }, + ] + for (const user of users) { + await config.createRow({ + tableId: config.table!._id, + ...user, + }) + } - const response = await config.api.viewV2.search(createViewResponse.id) + const createViewResponse = await config.api.viewV2.create({ + sort: { + field: "name", + order: SortOrder.ASCENDING, + type: SortType.STRING, + }, + }) - expect(response.body.rows).toHaveLength(4) - expect(response.body).toEqual({ - rows: expected.map(name => expect.objectContaining({ name })), - }) - }) + const response = await config.api.viewV2.search(createViewResponse.id, { + sort: { + column: sortParams.field, + order: sortParams.order, + type: sortParams.type, + }, + }) + + expect(response.body.rows).toHaveLength(4) + expect(response.body).toEqual({ + rows: expected.map(name => expect.objectContaining({ name })), + }) + } + ) }) }) diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index d79f324d85..15111ad977 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -1,4 +1,4 @@ -import { ViewV2 } from "@budibase/types" +import { SortOrder, SortType, ViewV2 } from "@budibase/types" import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" import { generator } from "@budibase/backend-core/tests" @@ -38,9 +38,33 @@ export class ViewV2API extends TestAPI { .expect(expectStatus) } - search = async (viewId: string, { expectStatus } = { expectStatus: 200 }) => { + search = async ( + viewId: string, + options?: { + sort: { + column: string + order?: SortOrder + type?: SortType + } + }, + { expectStatus } = { expectStatus: 200 } + ) => { + const qs: [string, any][] = [] + if (options?.sort.column) { + qs.push(["sort_column", options.sort.column]) + } + if (options?.sort.order) { + qs.push(["sort_order", options.sort.order]) + } + if (options?.sort.type) { + qs.push(["sort_type", options.sort.type]) + } + let url = `/api/v2/views/${viewId}/search` + if (qs.length) { + url += "?" + qs.map(q => q.join("=")).join("&") + } return this.request - .get(`/api/v2/views/${viewId}/search`) + .get(url) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(expectStatus)