From 81ca81222c43fa0bdeb4679d0b11a90f71bf5c7d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 13:08:46 +0200 Subject: [PATCH 01/19] Process bb_references on external --- packages/server/src/api/controllers/row/external.ts | 8 ++++++-- packages/server/src/sdk/app/rows/search/external.ts | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index 52dfcfd97a..a16892296d 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -18,7 +18,10 @@ import { import sdk from "../../../sdk" import * as utils from "./utils" import { dataFilters } from "@budibase/shared-core" -import { inputProcessing } from "../../../utilities/rowProcessor" +import { + inputProcessing, + outputProcessing, +} from "../../../utilities/rowProcessor" import { cloneDeep, isEqual } from "lodash" export async function handleRequest( @@ -121,7 +124,8 @@ export async function find(ctx: UserCtx): Promise { ctx.throw(404) } - return row + const table = await sdk.tables.getTable(tableId) + return await outputProcessing(table, row) } export async function destroy(ctx: UserCtx) { diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index 447d1d7d16..14ef4ec4fd 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -17,6 +17,7 @@ import { utils } from "@budibase/shared-core" import { ExportRowsParams, ExportRowsResult } from "../search" import { HTTPError, db } from "@budibase/backend-core" import pick from "lodash/pick" +import { outputProcessing } from "../../../../utilities/rowProcessor" export async function search(options: SearchParams) { const { tableId } = options @@ -75,6 +76,9 @@ export async function search(options: SearchParams) { rows = rows.map((r: any) => pick(r, fields)) } + const table = await sdk.tables.getTable(tableId) + rows = await outputProcessing(table, rows) + // need wrapper object for bookmarks etc when paginating return { rows, hasNextPage, bookmark: bookmark && bookmark + 1 } } catch (err: any) { From d0fcb5d7e686494abbb168f36915d9f6baec0ea8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 15:34:22 +0200 Subject: [PATCH 02/19] Fix patch on external ds --- packages/server/src/api/controllers/row/external.ts | 13 +++++++++++-- packages/server/src/sdk/app/rows/external.ts | 8 +++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index a16892296d..ec9a3e8fb0 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -56,14 +56,21 @@ export async function patch(ctx: UserCtx) { if (!validateResult.valid) { throw { validation: validateResult.errors } } + + const table = await sdk.tables.getTable(tableId) + const { row: dataToUpdate } = await inputProcessing( + ctx.user?._id, + cloneDeep(table), + rowData + ) + const response = await handleRequest(Operation.UPDATE, tableId, { id: breakRowIdField(_id), - row: rowData, + row: dataToUpdate, }) const row = await sdk.rows.external.getRow(tableId, _id, { relationships: true, }) - const table = await sdk.tables.getTable(tableId) return { ...response, row, @@ -107,9 +114,11 @@ export async function save(ctx: UserCtx) { return { ...response, row, + // row: await outputProcessing(table, row), } } else { return response + // return outputProcessing(table, response) } } diff --git a/packages/server/src/sdk/app/rows/external.ts b/packages/server/src/sdk/app/rows/external.ts index 568bd07e9d..6652be3a89 100644 --- a/packages/server/src/sdk/app/rows/external.ts +++ b/packages/server/src/sdk/app/rows/external.ts @@ -1,6 +1,8 @@ import { IncludeRelationship, Operation, Row } from "@budibase/types" import { handleRequest } from "../../../api/controllers/row/external" import { breakRowIdField } from "../../../integrations/utils" +import { outputProcessing } from "../../../utilities/rowProcessor" +import sdk from "../../../sdk" export async function getRow( tableId: string, @@ -13,5 +15,9 @@ export async function getRow( ? IncludeRelationship.INCLUDE : IncludeRelationship.EXCLUDE, })) as Row[] - return response ? response[0] : response + const row = response ? response[0] : response + + const table = await sdk.tables.getTable(tableId) + const enrichedRow = await outputProcessing(table, row) + return enrichedRow } From ab97093a0629aac4ef85a6680586342a7c52bdf4 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 15:48:06 +0200 Subject: [PATCH 03/19] Remove user logic from CreateEditColumn --- .../DataTable/modals/CreateEditColumn.svelte | 69 +++++-------------- .../builder/src/constants/backend/index.js | 3 +- 2 files changed, 18 insertions(+), 54 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 7d77942815..0ade6ea2ab 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 { FieldSubtype, FieldType } from "@budibase/types" + import { FieldType } from "@budibase/types" import RelationshipSelector from "components/common/RelationshipSelector.svelte" const AUTO_TYPE = "auto" @@ -43,11 +43,7 @@ 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 USER_REFRENCE_TYPE = FIELDS.BB_REFERENCE_USER.compositeType const dispatch = createEventDispatcher() const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] @@ -80,33 +76,6 @@ fieldName: $tables.selected.name, } - 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 } } @@ -149,12 +118,8 @@ $tables.selected.primaryDisplay == null || $tables.selected.primaryDisplay === editableColumn.name - const mapped = Object.entries(bbRefTypeMapping).find( - ([_, v]) => v.type === field.type && v.subtype === field.subtype - ) - if (mapped) { - editableColumn.type = mapped[0] - delete editableColumn.subtype + if (editableColumn.type === FieldType.BB_REFERENCE) { + editableColumn.type = `${editableColumn.type}_${editableColumn.subtype}` } } else if (!savingColumn) { let highestNumber = 0 @@ -188,8 +153,6 @@ $: initialiseField(field, savingColumn) - $: isBBReference = !!bbRefTypeMapping[editableColumn.type] - $: checkConstraints(editableColumn) $: required = !!editableColumn?.constraints?.presence || primaryDisplay $: uneditable = @@ -265,11 +228,12 @@ let saveColumn = cloneDeep(editableColumn) - if (bbRefTypeMapping[saveColumn.type]) { - saveColumn = { - ...saveColumn, - ...bbRefTypeMapping[saveColumn.type], - } + // 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 } if (saveColumn.type === AUTO_TYPE) { @@ -352,7 +316,7 @@ editableColumn.relationshipType = RelationshipType.MANY_TO_MANY } else if (editableColumn.type === FORMULA_TYPE) { editableColumn.formulaType = "dynamic" - } else if (editableColumn.type === BB_USER_REFERENCE_TYPE) { + } else if (editableColumn.type === USER_REFRENCE_TYPE) { editableColumn.relationshipType = RelationshipType.ONE_TO_MANY } } @@ -410,14 +374,12 @@ FIELDS.BOOLEAN, FIELDS.FORMULA, FIELDS.BIGINT, + FIELDS.BB_REFERENCE_USER, ] // no-sql or a spreadsheet if (!external || table.sql) { fields = [...fields, FIELDS.LINK, FIELDS.ARRAY] } - if (fieldDefinitions.USER) { - fields.push(fieldDefinitions.USER) - } return fields } } @@ -426,8 +388,9 @@ if (!fieldToCheck) { return } + // most types need this, just make sure its always present - if (fieldToCheck && !fieldToCheck.constraints) { + if (!fieldToCheck.constraints) { fieldToCheck.constraints = {} } // some string types may have been built by server, may not always have constraints @@ -507,7 +470,7 @@ on:change={handleTypeChange} options={allowedTypes} getOptionLabel={field => field.name} - getOptionValue={field => field.type} + getOptionValue={field => field.compositeType || field.type} getOptionIcon={field => field.icon} isOptionEnabled={option => { if (option.type == AUTO_TYPE) { @@ -671,7 +634,7 @@ - {:else if isBBReference} + {:else if editableColumn.type === USER_REFRENCE_TYPE} diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 047152eeed..8b76207822 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -120,10 +120,11 @@ export const FIELDS = { presence: false, }, }, - USER: { + BB_REFERENCE_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 icon: "User", }, } From 1fb3c4b3c3d8462344296b9933bcffcc2d28838c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 16:20:41 +0200 Subject: [PATCH 04/19] Fix external save/update --- .../src/api/controllers/row/external.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index ec9a3e8fb0..7bb9eed6cc 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -49,14 +49,6 @@ export async function patch(ctx: UserCtx) { const tableId = utils.getTableId(ctx) const { _id, ...rowData } = ctx.request.body - const validateResult = await sdk.rows.utils.validate({ - row: rowData, - tableId, - }) - if (!validateResult.valid) { - throw { validation: validateResult.errors } - } - const table = await sdk.tables.getTable(tableId) const { row: dataToUpdate } = await inputProcessing( ctx.user?._id, @@ -64,6 +56,14 @@ export async function patch(ctx: UserCtx) { rowData ) + const validateResult = await sdk.rows.utils.validate({ + row: dataToUpdate, + tableId, + }) + if (!validateResult.valid) { + throw { validation: validateResult.errors } + } + const response = await handleRequest(Operation.UPDATE, tableId, { id: breakRowIdField(_id), row: dataToUpdate, @@ -81,13 +81,6 @@ export async function patch(ctx: UserCtx) { export async function save(ctx: UserCtx) { const inputs = ctx.request.body const tableId = utils.getTableId(ctx) - const validateResult = await sdk.rows.utils.validate({ - row: inputs, - tableId, - }) - if (!validateResult.valid) { - throw { validation: validateResult.errors } - } const table = await sdk.tables.getTable(tableId) const { table: updatedTable, row } = await inputProcessing( @@ -96,6 +89,14 @@ export async function save(ctx: UserCtx) { inputs ) + const validateResult = await sdk.rows.utils.validate({ + row, + tableId, + }) + if (!validateResult.valid) { + throw { validation: validateResult.errors } + } + const response = await handleRequest(Operation.CREATE, tableId, { row, }) From 9afa33450124a2f95abfeed539f149a226ee9332 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 16:20:47 +0200 Subject: [PATCH 05/19] Fix deletions --- .../server/src/utilities/rowProcessor/bbReferenceProcessor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts index b9b91b6789..5409ed925c 100644 --- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts +++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts @@ -6,7 +6,7 @@ import { InvalidBBRefError } from "./errors" export async function processInputBBReferences( value: string | string[] | { _id: string } | { _id: string }[], subtype: FieldSubtype -): Promise { +): Promise { const referenceIds: string[] = [] if (Array.isArray(value)) { @@ -39,7 +39,7 @@ export async function processInputBBReferences( throw utils.unreachable(subtype) } - return referenceIds.join(",") || undefined + return referenceIds.join(",") || null } export async function processOutputBBReferences( From fdb6474868d74dae0e86dbd443a636ad8dd10149 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 16:22:00 +0200 Subject: [PATCH 06/19] Fix tests --- .../rowProcessor/tests/bbReferenceProcessor.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts index 67a44a86f2..d0932b399c 100644 --- a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts +++ b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts @@ -139,20 +139,20 @@ describe("bbReferenceProcessor", () => { expect(cacheGetUsersSpy).toBeCalledWith(userIds) }) - it("empty strings will return undefined", async () => { + it("empty strings will return null", async () => { const result = await config.doInTenant(() => processInputBBReferences("", FieldSubtype.USER) ) - expect(result).toEqual(undefined) + expect(result).toEqual(null) }) - it("empty arrays will return undefined", async () => { + it("empty arrays will return null", async () => { const result = await config.doInTenant(() => processInputBBReferences([], FieldSubtype.USER) ) - expect(result).toEqual(undefined) + expect(result).toEqual(null) }) }) }) From bf975ac039f4402a3bb0fc4735cf3c03c71c0c4f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 16:51:15 +0200 Subject: [PATCH 07/19] Fix tests --- packages/server/src/sdk/app/rows/external.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/server/src/sdk/app/rows/external.ts b/packages/server/src/sdk/app/rows/external.ts index 6652be3a89..568bd07e9d 100644 --- a/packages/server/src/sdk/app/rows/external.ts +++ b/packages/server/src/sdk/app/rows/external.ts @@ -1,8 +1,6 @@ import { IncludeRelationship, Operation, Row } from "@budibase/types" import { handleRequest } from "../../../api/controllers/row/external" import { breakRowIdField } from "../../../integrations/utils" -import { outputProcessing } from "../../../utilities/rowProcessor" -import sdk from "../../../sdk" export async function getRow( tableId: string, @@ -15,9 +13,5 @@ export async function getRow( ? IncludeRelationship.INCLUDE : IncludeRelationship.EXCLUDE, })) as Row[] - const row = response ? response[0] : response - - const table = await sdk.tables.getTable(tableId) - const enrichedRow = await outputProcessing(table, row) - return enrichedRow + return response ? response[0] : response } From 3c0a033c8b1b4860dbc4a9fab9f40fd0da0928e2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 16:51:42 +0200 Subject: [PATCH 08/19] Add new tests --- .../server/src/api/routes/tests/row.spec.ts | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 6a021460ac..3fee697327 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -7,6 +7,7 @@ import { context, InternalTable, roles, tenancy } from "@budibase/backend-core" import { quotas } from "@budibase/pro" import { FieldType, + FieldTypeSubtypes, MonthlyQuotaName, PermissionLevel, QuotaUsageType, @@ -25,6 +26,7 @@ import { mocks, structures, } from "@budibase/backend-core/tests" +import { async } from "validate.js" const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() tk.freeze(timestamp) @@ -1511,4 +1513,104 @@ describe.each([ }) }) }) + + describe("bb reference fields", () => { + let tableId: string + beforeAll(async () => { + const tableConfig = generateTableConfig() + const table = await config.api.table.create({ + ...tableConfig, + schema: { + ...tableConfig.schema, + user: { + name: "user", + type: FieldType.BB_REFERENCE, + subtype: FieldTypeSubtypes.BB_REFERENCE.USER, + relationshipType: RelationshipType.ONE_TO_MANY, + }, + users: { + name: "users", + type: FieldType.BB_REFERENCE, + subtype: FieldTypeSubtypes.BB_REFERENCE.USER, + relationshipType: RelationshipType.MANY_TO_MANY, + }, + }, + }) + tableId = table._id! + }) + + it("can save and retrieve when BB reference fields are empty", async () => { + const rowData = { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + } + const row = await config.api.row.save(tableId, rowData) + + expect(row).toEqual({ + name: rowData.name, + description: rowData.description, + type: "row", + tableId, + _id: expect.any(String), + _rev: expect.any(String), + }) + }) + + it("can save and retrieve a row with a single BB reference field", async () => { + const user = await config.createUser() + const rowData = { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + user: user, + } + const row = await config.api.row.save(tableId, rowData) + + expect(row).toEqual({ + name: rowData.name, + description: rowData.description, + type: "row", + tableId, + user: [ + { + _id: user._id, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + primaryDisplay: user.email, + }, + ], + _id: expect.any(String), + _rev: expect.any(String), + }) + }) + + it("can save and retrieve a row with a multiple BB reference field", async () => { + const users = [await config.createUser(), await config.createUser()] + const rowData = { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + user: users, + } + const row = await config.api.row.save(tableId, rowData) + + expect(row).toEqual({ + name: rowData.name, + description: rowData.description, + type: "row", + tableId, + user: users.map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + _id: expect.any(String), + _rev: expect.any(String), + }) + }) + }) }) From a87e3dd0fc54903f56750cd69115aa2755378dd9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:03:16 +0200 Subject: [PATCH 09/19] Extra tests --- .../server/src/api/routes/tests/row.spec.ts | 56 +++++++++++++++++-- .../server/src/tests/utilities/api/row.ts | 4 +- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 3fee697327..38aa1294f4 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1539,7 +1539,7 @@ describe.each([ tableId = table._id! }) - it("can save and retrieve when BB reference fields are empty", async () => { + it("can save a row when BB reference fields are empty", async () => { const rowData = { ...basicRow(tableId), name: generator.name(), @@ -1557,7 +1557,7 @@ describe.each([ }) }) - it("can save and retrieve a row with a single BB reference field", async () => { + it("can save a row with a single BB reference field", async () => { const user = await config.createUser() const rowData = { ...basicRow(tableId), @@ -1586,13 +1586,13 @@ describe.each([ }) }) - it("can save and retrieve a row with a multiple BB reference field", async () => { + it("can save a row with a multiple BB reference field", async () => { const users = [await config.createUser(), await config.createUser()] const rowData = { ...basicRow(tableId), name: generator.name(), description: generator.name(), - user: users, + users: users, } const row = await config.api.row.save(tableId, rowData) @@ -1601,7 +1601,7 @@ describe.each([ description: rowData.description, type: "row", tableId, - user: users.map(u => ({ + users: users.map(u => ({ _id: u._id, email: u.email, firstName: u.firstName, @@ -1612,5 +1612,51 @@ describe.each([ _rev: expect.any(String), }) }) + + it("can update an existing populated row", async () => { + const [user1, user2, user3] = [ + await config.createUser(), + await config.createUser(), + await config.createUser(), + ] + + const rowData = { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + users: [user1, user2], + } + const row = await config.api.row.save(tableId, rowData) + + const updatedRow = await config.api.row.save(tableId, { + ...row, + user: [user3], + users: [user3, user2], + }) + expect(updatedRow).toEqual({ + name: rowData.name, + description: rowData.description, + type: "row", + tableId, + user: [ + { + _id: user3._id, + email: user3.email, + firstName: user3.firstName, + lastName: user3.lastName, + primaryDisplay: user3.email, + }, + ], + users: [user3, user2].map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + _id: row._id, + _rev: expect.any(String), + }) + }) }) }) diff --git a/packages/server/src/tests/utilities/api/row.ts b/packages/server/src/tests/utilities/api/row.ts index 686c8c031b..2db5169e3d 100644 --- a/packages/server/src/tests/utilities/api/row.ts +++ b/packages/server/src/tests/utilities/api/row.ts @@ -44,12 +44,12 @@ export class RowAPI extends TestAPI { } save = async ( - sourceId: string, + tableId: string, row: SaveRowRequest, { expectStatus } = { expectStatus: 200 } ): Promise => { const resp = await this.request - .post(`/api/${sourceId}/rows`) + .post(`/api/${tableId}/rows`) .send(row) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) From 8a9e87ea6f9dbe4142a6c6e41fffc6ad2fa3174b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:08:29 +0200 Subject: [PATCH 10/19] Add extra tests --- .../server/src/api/routes/tests/row.spec.ts | 32 ++++++++++++++++++- 1 file changed, 31 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 38aa1294f4..41571a51d9 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -26,7 +26,6 @@ import { mocks, structures, } from "@budibase/backend-core/tests" -import { async } from "validate.js" const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() tk.freeze(timestamp) @@ -1658,5 +1657,36 @@ describe.each([ _rev: expect.any(String), }) }) + + it("can wipe an existing populated BB references in row", async () => { + const [user1, user2] = [ + await config.createUser(), + await config.createUser(), + ] + + const rowData = { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + users: [user1, user2], + } + const row = await config.api.row.save(tableId, rowData) + + const updatedRow = await config.api.row.save(tableId, { + ...row, + user: null, + users: null, + }) + expect(updatedRow).toEqual({ + name: rowData.name, + description: rowData.description, + type: "row", + tableId, + user: null, + users: null, + _id: row._id, + _rev: expect.any(String), + }) + }) }) }) From 01057baa9667d98242bd82b5e037f939089d4414 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:16:37 +0200 Subject: [PATCH 11/19] Add extra tests --- .../server/src/api/routes/tests/row.spec.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 41571a51d9..a56678e5cf 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1612,6 +1612,71 @@ describe.each([ }) }) + it("can retrieve rows with no populated BB references", async () => { + const rowData = { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + } + const row = await config.api.row.save(tableId, rowData) + + const { body: retrieved } = await config.api.row.get(tableId, row._id!) + expect(retrieved).toEqual({ + name: rowData.name, + description: rowData.description, + type: "row", + tableId, + user: undefined, + users: undefined, + _id: row._id, + _rev: expect.any(String), + createdAt: timestamp, + updatedAt: timestamp, + }) + }) + + it("can retrieve rows with populated BB references", async () => { + const [user1, user2] = [ + await config.createUser(), + await config.createUser(), + ] + + const rowData = { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + users: [user1, user2], + user: [user1], + } + const row = await config.api.row.save(tableId, rowData) + + const { body: retrieved } = await config.api.row.get(tableId, row._id!) + expect(retrieved).toEqual({ + name: rowData.name, + description: rowData.description, + type: "row", + tableId, + user: [user1].map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + users: [user1, user2].map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + _id: row._id, + _rev: expect.any(String), + createdAt: timestamp, + updatedAt: timestamp, + }) + }) + it("can update an existing populated row", async () => { const [user1, user2, user3] = [ await config.createUser(), From 0e3cacd19caa4e6da1a55dd1cef04774a51a371b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:25:20 +0200 Subject: [PATCH 12/19] Clean --- packages/server/src/api/controllers/row/external.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index 7bb9eed6cc..688501dd38 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -115,11 +115,9 @@ export async function save(ctx: UserCtx) { return { ...response, row, - // row: await outputProcessing(table, row), } } else { return response - // return outputProcessing(table, response) } } From 049a91debb632c2bbfd0214a570470eb522a755f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:26:32 +0200 Subject: [PATCH 13/19] Clean --- packages/server/src/sdk/app/rows/search/external.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index 14ef4ec4fd..447d1d7d16 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -17,7 +17,6 @@ import { utils } from "@budibase/shared-core" import { ExportRowsParams, ExportRowsResult } from "../search" import { HTTPError, db } from "@budibase/backend-core" import pick from "lodash/pick" -import { outputProcessing } from "../../../../utilities/rowProcessor" export async function search(options: SearchParams) { const { tableId } = options @@ -76,9 +75,6 @@ export async function search(options: SearchParams) { rows = rows.map((r: any) => pick(r, fields)) } - const table = await sdk.tables.getTable(tableId) - rows = await outputProcessing(table, rows) - // need wrapper object for bookmarks etc when paginating return { rows, hasNextPage, bookmark: bookmark && bookmark + 1 } } catch (err: any) { From d7cbd2dcbcce2d132a64d5bafafdcdc2c7b42ad9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:29:38 +0200 Subject: [PATCH 14/19] Fix --- packages/server/src/api/controllers/row/external.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index 688501dd38..1b0a4762b7 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -132,8 +132,7 @@ export async function find(ctx: UserCtx): Promise { ctx.throw(404) } - const table = await sdk.tables.getTable(tableId) - return await outputProcessing(table, row) + return row } export async function destroy(ctx: UserCtx) { From e22c770bad746ecb8203ca2e7a1d782762c3d304 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:35:16 +0200 Subject: [PATCH 15/19] Clean tests --- .../server/src/api/routes/tests/row.spec.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index a56678e5cf..f1dc8f19d7 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -18,6 +18,7 @@ import { SortType, StaticQuotaName, Table, + User, } from "@budibase/types" import { expectAnyExternalColsAttributes, @@ -26,6 +27,7 @@ import { mocks, structures, } from "@budibase/backend-core/tests" +import _ from "lodash" const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() tk.freeze(timestamp) @@ -35,7 +37,7 @@ const { basicRow } = setup.structures describe.each([ ["internal", undefined], ["postgres", databaseTestProviders.postgres], -])("/rows (%s)", (_, dsProvider) => { +])("/rows (%s)", (__, dsProvider) => { const isInternal = !dsProvider const request = setup.getRequest() @@ -1515,6 +1517,7 @@ describe.each([ describe("bb reference fields", () => { let tableId: string + let users: User[] beforeAll(async () => { const tableConfig = generateTableConfig() const table = await config.api.table.create({ @@ -1536,6 +1539,13 @@ describe.each([ }, }) tableId = table._id! + + users = [ + await config.createUser(), + await config.createUser(), + await config.createUser(), + await config.createUser(), + ] }) it("can save a row when BB reference fields are empty", async () => { @@ -1557,7 +1567,7 @@ describe.each([ }) it("can save a row with a single BB reference field", async () => { - const user = await config.createUser() + const user = _.sample(users)! const rowData = { ...basicRow(tableId), name: generator.name(), @@ -1586,12 +1596,12 @@ describe.each([ }) it("can save a row with a multiple BB reference field", async () => { - const users = [await config.createUser(), await config.createUser()] + const selectedUsers = _.sampleSize(users, 2) const rowData = { ...basicRow(tableId), name: generator.name(), description: generator.name(), - users: users, + users: selectedUsers, } const row = await config.api.row.save(tableId, rowData) @@ -1600,7 +1610,7 @@ describe.each([ description: rowData.description, type: "row", tableId, - users: users.map(u => ({ + users: selectedUsers.map(u => ({ _id: u._id, email: u.email, firstName: u.firstName, @@ -1636,10 +1646,7 @@ describe.each([ }) it("can retrieve rows with populated BB references", async () => { - const [user1, user2] = [ - await config.createUser(), - await config.createUser(), - ] + const [user1, user2] = _.sampleSize(users, 2) const rowData = { ...basicRow(tableId), @@ -1678,11 +1685,7 @@ describe.each([ }) it("can update an existing populated row", async () => { - const [user1, user2, user3] = [ - await config.createUser(), - await config.createUser(), - await config.createUser(), - ] + const [user1, user2, user3] = _.sampleSize(users, 3) const rowData = { ...basicRow(tableId), @@ -1724,10 +1727,7 @@ describe.each([ }) it("can wipe an existing populated BB references in row", async () => { - const [user1, user2] = [ - await config.createUser(), - await config.createUser(), - ] + const [user1, user2] = _.sampleSize(users, 2) const rowData = { ...basicRow(tableId), From 8c99ac3f9b240a594e0714a662ac8a4a5c63efab Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 17:48:30 +0200 Subject: [PATCH 16/19] Test search --- .../server/src/api/routes/tests/row.spec.ts | 134 ++++++++++++++++++ .../server/src/tests/utilities/api/row.ts | 12 ++ 2 files changed, 146 insertions(+) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index f1dc8f19d7..a34669de03 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1753,5 +1753,139 @@ describe.each([ _rev: expect.any(String), }) }) + + it("fetch all will populate the BB references", async () => { + const [user1, user2, user3] = _.sampleSize(users, 3) + + const rows: { + name: string + description: string + user?: User[] + users?: User[] + tableId: string + }[] = [ + { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + users: [user1, user2], + }, + { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + user: [user1], + users: [user1, user3], + }, + { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + users: [user3], + }, + ] + + await config.api.row.save(tableId, rows[0]) + await config.api.row.save(tableId, rows[1]) + await config.api.row.save(tableId, rows[2]) + + const res = await config.api.row.fetch(tableId) + + expect(res).toEqual( + expect.arrayContaining( + rows.map(r => ({ + name: r.name, + description: r.description, + type: "row", + tableId, + user: r.user?.map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + users: r.users?.map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + _id: expect.any(String), + _rev: expect.any(String), + createdAt: timestamp, + updatedAt: timestamp, + })) + ) + ) + }) + + it("search all will populate the BB references", async () => { + const [user1, user2, user3] = _.sampleSize(users, 3) + + const rows: { + name: string + description: string + user?: User[] + users?: User[] + tableId: string + }[] = [ + { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + users: [user1, user2], + }, + { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + user: [user1], + users: [user1, user3], + }, + { + ...basicRow(tableId), + name: generator.name(), + description: generator.name(), + users: [user3], + }, + ] + + await config.api.row.save(tableId, rows[0]) + await config.api.row.save(tableId, rows[1]) + await config.api.row.save(tableId, rows[2]) + + const res = await config.api.row.search(tableId) + + expect(res).toEqual({ + rows: expect.arrayContaining( + rows.map(r => ({ + name: r.name, + description: r.description, + type: "row", + tableId, + user: r.user?.map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + users: r.users?.map(u => ({ + _id: u._id, + email: u.email, + firstName: u.firstName, + lastName: u.lastName, + primaryDisplay: u.email, + })), + _id: expect.any(String), + _rev: expect.any(String), + createdAt: timestamp, + updatedAt: timestamp, + })) + ), + }) + }) }) }) diff --git a/packages/server/src/tests/utilities/api/row.ts b/packages/server/src/tests/utilities/api/row.ts index 2db5169e3d..adeb96a593 100644 --- a/packages/server/src/tests/utilities/api/row.ts +++ b/packages/server/src/tests/utilities/api/row.ts @@ -122,4 +122,16 @@ export class RowAPI extends TestAPI { .expect(expectStatus) return request } + + search = async ( + sourceId: string, + { expectStatus } = { expectStatus: 200 } + ): Promise => { + const request = this.request + .post(`/api/${sourceId}/search`) + .set(this.config.defaultHeaders()) + .expect(expectStatus) + + return (await request).body + } } From 0cec0269323aeb14fcad22d090a75003460eb8bc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 18:32:18 +0200 Subject: [PATCH 17/19] Datasource usage on tests --- .../src/api/controllers/row/external.ts | 7 +-- .../server/src/api/routes/tests/row.spec.ts | 53 ++++++++++++------- .../src/sdk/app/rows/search/external.ts | 8 ++- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index 1b0a4762b7..57b808a85f 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -73,7 +73,7 @@ export async function patch(ctx: UserCtx) { }) return { ...response, - row, + row: await outputProcessing(table, row), table, } } @@ -114,7 +114,7 @@ export async function save(ctx: UserCtx) { }) return { ...response, - row, + row: await outputProcessing(table, row), } } else { return response @@ -132,7 +132,8 @@ export async function find(ctx: UserCtx): Promise { ctx.throw(404) } - return row + const table = await sdk.tables.getTable(tableId) + return await outputProcessing(table, row) } export async function destroy(ctx: UserCtx) { diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index a34669de03..b4a33efdde 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1518,8 +1518,16 @@ describe.each([ describe("bb reference fields", () => { let tableId: string let users: User[] + beforeAll(async () => { const tableConfig = generateTableConfig() + + if (config.datasource) { + tableConfig.sourceId = config.datasource._id + if (config.datasource.plus) { + tableConfig.type = "external" + } + } const table = await config.api.table.create({ ...tableConfig, schema: { @@ -1559,10 +1567,11 @@ describe.each([ expect(row).toEqual({ name: rowData.name, description: rowData.description, - type: "row", tableId, _id: expect.any(String), _rev: expect.any(String), + id: isInternal ? undefined : expect.any(Number), + type: isInternal ? "row" : undefined, }) }) @@ -1579,7 +1588,6 @@ describe.each([ expect(row).toEqual({ name: rowData.name, description: rowData.description, - type: "row", tableId, user: [ { @@ -1592,6 +1600,8 @@ describe.each([ ], _id: expect.any(String), _rev: expect.any(String), + id: isInternal ? undefined : expect.any(Number), + type: isInternal ? "row" : undefined, }) }) @@ -1608,7 +1618,6 @@ describe.each([ expect(row).toEqual({ name: rowData.name, description: rowData.description, - type: "row", tableId, users: selectedUsers.map(u => ({ _id: u._id, @@ -1619,6 +1628,8 @@ describe.each([ })), _id: expect.any(String), _rev: expect.any(String), + id: isInternal ? undefined : expect.any(Number), + type: isInternal ? "row" : undefined, }) }) @@ -1634,14 +1645,13 @@ describe.each([ expect(retrieved).toEqual({ name: rowData.name, description: rowData.description, - type: "row", tableId, user: undefined, users: undefined, _id: row._id, _rev: expect.any(String), - createdAt: timestamp, - updatedAt: timestamp, + id: isInternal ? undefined : expect.any(Number), + ...defaultRowFields, }) }) @@ -1661,7 +1671,6 @@ describe.each([ expect(retrieved).toEqual({ name: rowData.name, description: rowData.description, - type: "row", tableId, user: [user1].map(u => ({ _id: u._id, @@ -1679,8 +1688,8 @@ describe.each([ })), _id: row._id, _rev: expect.any(String), - createdAt: timestamp, - updatedAt: timestamp, + id: isInternal ? undefined : expect.any(Number), + ...defaultRowFields, }) }) @@ -1703,7 +1712,6 @@ describe.each([ expect(updatedRow).toEqual({ name: rowData.name, description: rowData.description, - type: "row", tableId, user: [ { @@ -1723,6 +1731,8 @@ describe.each([ })), _id: row._id, _rev: expect.any(String), + id: isInternal ? undefined : expect.any(Number), + type: isInternal ? "row" : undefined, }) }) @@ -1745,12 +1755,13 @@ describe.each([ expect(updatedRow).toEqual({ name: rowData.name, description: rowData.description, - type: "row", tableId, - user: null, - users: null, + user: isInternal ? null : undefined, + users: isInternal ? null : undefined, _id: row._id, _rev: expect.any(String), + id: isInternal ? undefined : expect.any(Number), + type: isInternal ? "row" : undefined, }) }) @@ -1796,7 +1807,6 @@ describe.each([ rows.map(r => ({ name: r.name, description: r.description, - type: "row", tableId, user: r.user?.map(u => ({ _id: u._id, @@ -1814,8 +1824,8 @@ describe.each([ })), _id: expect.any(String), _rev: expect.any(String), - createdAt: timestamp, - updatedAt: timestamp, + id: isInternal ? undefined : expect.any(Number), + ...defaultRowFields, })) ) ) @@ -1863,7 +1873,6 @@ describe.each([ rows.map(r => ({ name: r.name, description: r.description, - type: "row", tableId, user: r.user?.map(u => ({ _id: u._id, @@ -1881,10 +1890,16 @@ describe.each([ })), _id: expect.any(String), _rev: expect.any(String), - createdAt: timestamp, - updatedAt: timestamp, + id: isInternal ? undefined : expect.any(Number), + ...defaultRowFields, })) ), + ...(isInternal + ? {} + : { + hasNextPage: false, + bookmark: null, + }), }) }) }) diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index 447d1d7d16..817bfce33d 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -17,6 +17,7 @@ import { utils } from "@budibase/shared-core" import { ExportRowsParams, ExportRowsResult } from "../search" import { HTTPError, db } from "@budibase/backend-core" import pick from "lodash/pick" +import { outputProcessing } from "../../../../utilities/rowProcessor" export async function search(options: SearchParams) { const { tableId } = options @@ -75,6 +76,9 @@ export async function search(options: SearchParams) { rows = rows.map((r: any) => pick(r, fields)) } + const table = await sdk.tables.getTable(tableId) + rows = await outputProcessing(table, rows) + // need wrapper object for bookmarks etc when paginating return { rows, hasNextPage, bookmark: bookmark && bookmark + 1 } } catch (err: any) { @@ -166,9 +170,11 @@ export async function exportRows( } export async function fetch(tableId: string) { - return handleRequest(Operation.READ, tableId, { + const response = await handleRequest(Operation.READ, tableId, { includeSqlRelationships: IncludeRelationship.INCLUDE, }) + const table = await sdk.tables.getTable(tableId) + return await outputProcessing(table, response) } export async function fetchView(viewName: string) { From fad4f12e26fef89699617b46cc680b0a11dcd543 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 18:46:13 +0200 Subject: [PATCH 18/19] Fix test --- packages/server/src/api/controllers/row/external.ts | 6 +++++- packages/server/src/utilities/rowProcessor/index.ts | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index 57b808a85f..1e57416cd1 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -133,7 +133,11 @@ export async function find(ctx: UserCtx): Promise { } const table = await sdk.tables.getTable(tableId) - return await outputProcessing(table, row) + // Preserving links, as the outputProcessing does not support external rows yet and we don't need it in this use case + return await outputProcessing(table, row, { + squash: false, + preserveLinks: true, + }) } export async function destroy(ctx: UserCtx) { diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index 933e9bd2f5..773b54dd6a 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -200,7 +200,10 @@ export async function inputProcessing( export async function outputProcessing( table: Table, rows: T, - opts = { squash: true } + opts: { squash?: boolean; preserveLinks?: boolean } = { + squash: true, + preserveLinks: false, + } ): Promise { let safeRows: Row[] let wasArray = true @@ -211,7 +214,9 @@ export async function outputProcessing( safeRows = rows } // attach any linked row information - let enriched = await linkRows.attachFullLinkedDocs(table, safeRows) + let enriched = !opts.preserveLinks + ? await linkRows.attachFullLinkedDocs(table, safeRows) + : safeRows // process formulas enriched = processFormulas(table, enriched, { dynamic: true }) as Row[] From 317da512a57a640dc14f99cb12860d0997ab295a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 27 Sep 2023 20:04:53 +0200 Subject: [PATCH 19/19] Fix tests --- packages/server/src/sdk/tests/rows/row.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/server/src/sdk/tests/rows/row.spec.ts b/packages/server/src/sdk/tests/rows/row.spec.ts index 08c5746f2e..af3d405e15 100644 --- a/packages/server/src/sdk/tests/rows/row.spec.ts +++ b/packages/server/src/sdk/tests/rows/row.spec.ts @@ -7,9 +7,14 @@ import { HTTPError } from "@budibase/backend-core" import { Operation } from "@budibase/types" const mockDatasourcesGet = jest.fn() +const mockTableGet = jest.fn() sdk.datasources.get = mockDatasourcesGet +sdk.tables.getTable = mockTableGet jest.mock("../../../api/controllers/row/ExternalRequest") +jest.mock("../../../utilities/rowProcessor", () => ({ + outputProcessing: jest.fn((_, rows) => rows), +})) jest.mock("../../../api/controllers/view/exporters", () => ({ ...jest.requireActual("../../../api/controllers/view/exporters"),