From 7dbf71d4777789765c476a010c2a80c9e177a2f8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 12:28:37 +0200 Subject: [PATCH 01/89] Namespace --- .../server/src/api/controllers/view/index.ts | 196 +----------------- .../src/api/controllers/view/legacyViews.ts | 195 +++++++++++++++++ packages/server/src/api/routes/view.ts | 8 +- 3 files changed, 200 insertions(+), 199 deletions(-) create mode 100644 packages/server/src/api/controllers/view/legacyViews.ts diff --git a/packages/server/src/api/controllers/view/index.ts b/packages/server/src/api/controllers/view/index.ts index 99c4224c62..83bc2c3c8d 100644 --- a/packages/server/src/api/controllers/view/index.ts +++ b/packages/server/src/api/controllers/view/index.ts @@ -1,195 +1 @@ -import viewTemplate from "./viewBuilder" -import { apiFileReturn } from "../../../utilities/fileSystem" -import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters" -import { deleteView, getView, getViews, saveView } from "./utils" -import { fetchView } from "../row" -import { context, events } from "@budibase/backend-core" -import { DocumentType } from "../../../db/utils" -import sdk from "../../../sdk" -import { FieldTypes } from "../../../constants" -import { - Ctx, - Row, - Table, - TableExportFormat, - TableSchema, - View, -} from "@budibase/types" -import { builderSocket } from "../../../websockets" - -const { cloneDeep, isEqual } = require("lodash") - -export async function fetch(ctx: Ctx) { - ctx.body = await getViews() -} - -export async function save(ctx: Ctx) { - const db = context.getAppDB() - const { originalName, ...viewToSave } = ctx.request.body - - const existingTable = await sdk.tables.getTable(ctx.request.body.tableId) - existingTable.views ??= {} - const table = cloneDeep(existingTable) - - const groupByField: any = Object.values(table.schema).find( - (field: any) => field.name == viewToSave.groupBy - ) - - const view = viewTemplate(viewToSave, groupByField?.type === FieldTypes.ARRAY) - const viewName = viewToSave.name - - if (!viewName) { - ctx.throw(400, "Cannot create view without a name") - } - - await saveView(originalName, viewName, view) - - // add views to table document - if (!table.views) table.views = {} - if (!view.meta.schema) { - view.meta.schema = table.schema - } - table.views[viewName] = { ...view.meta, name: viewName } - if (originalName) { - delete table.views[originalName] - existingTable.views[viewName] = existingTable.views[originalName] - } - await db.put(table) - await handleViewEvents(existingTable.views[viewName], table.views[viewName]) - - ctx.body = table.views[viewName] - builderSocket?.emitTableUpdate(ctx, table) -} - -export async function calculationEvents(existingView: View, newView: View) { - const existingCalculation = existingView && existingView.calculation - const newCalculation = newView && newView.calculation - - if (existingCalculation && !newCalculation) { - await events.view.calculationDeleted(existingView) - } - - if (!existingCalculation && newCalculation) { - await events.view.calculationCreated(newView) - } - - if ( - existingCalculation && - newCalculation && - existingCalculation !== newCalculation - ) { - await events.view.calculationUpdated(newView) - } -} - -export async function filterEvents(existingView: View, newView: View) { - const hasExistingFilters = !!( - existingView && - existingView.filters && - existingView.filters.length - ) - const hasNewFilters = !!(newView && newView.filters && newView.filters.length) - - if (hasExistingFilters && !hasNewFilters) { - await events.view.filterDeleted(newView) - } - - if (!hasExistingFilters && hasNewFilters) { - await events.view.filterCreated(newView) - } - - if ( - hasExistingFilters && - hasNewFilters && - !isEqual(existingView.filters, newView.filters) - ) { - await events.view.filterUpdated(newView) - } -} - -async function handleViewEvents(existingView: View, newView: View) { - if (!existingView) { - await events.view.created(newView) - } else { - await events.view.updated(newView) - } - await calculationEvents(existingView, newView) - await filterEvents(existingView, newView) -} - -export async function destroy(ctx: Ctx) { - const db = context.getAppDB() - const viewName = decodeURIComponent(ctx.params.viewName) - const view = await deleteView(viewName) - const table = await sdk.tables.getTable(view.meta.tableId) - delete table.views![viewName] - await db.put(table) - await events.view.deleted(view) - - ctx.body = view - builderSocket?.emitTableUpdate(ctx, table) -} - -export async function exportView(ctx: Ctx) { - const viewName = decodeURIComponent(ctx.query.view as string) - const view = await getView(viewName) - - const format = ctx.query.format as unknown - - if (!isFormat(format)) { - ctx.throw( - 400, - "Format must be specified, either csv, json or jsonWithSchema" - ) - } - - if (view) { - ctx.params.viewName = viewName - // Fetch view rows - ctx.query = { - group: view.meta.groupBy, - calculation: view.meta.calculation, - // @ts-ignore - stats: !!view.meta.field, - field: view.meta.field, - } - } else { - // table all_ view - /* istanbul ignore next */ - ctx.params.viewName = viewName - } - - await fetchView(ctx) - let rows = ctx.body as Row[] - - let schema: TableSchema = view && view.meta && view.meta.schema - const tableId = - ctx.params.tableId || - view?.meta?.tableId || - (viewName.startsWith(DocumentType.TABLE) && viewName) - const table: Table = await sdk.tables.getTable(tableId) - if (!schema) { - schema = table.schema - } - - let exportRows = sdk.rows.utils.cleanExportRows(rows, schema, format, []) - - if (format === Format.CSV) { - ctx.attachment(`${viewName}.csv`) - ctx.body = apiFileReturn(csv(Object.keys(schema), exportRows)) - } else if (format === Format.JSON) { - ctx.attachment(`${viewName}.json`) - ctx.body = apiFileReturn(json(exportRows)) - } else if (format === Format.JSON_WITH_SCHEMA) { - ctx.attachment(`${viewName}.json`) - ctx.body = apiFileReturn(jsonWithSchema(schema, exportRows)) - } else { - throw "Format not recognised" - } - - if (viewName.startsWith(DocumentType.TABLE)) { - await events.table.exported(table, format as TableExportFormat) - } else { - await events.view.exported(table, format as TableExportFormat) - } -} +export * as v1 from "./legacyViews" diff --git a/packages/server/src/api/controllers/view/legacyViews.ts b/packages/server/src/api/controllers/view/legacyViews.ts new file mode 100644 index 0000000000..99c4224c62 --- /dev/null +++ b/packages/server/src/api/controllers/view/legacyViews.ts @@ -0,0 +1,195 @@ +import viewTemplate from "./viewBuilder" +import { apiFileReturn } from "../../../utilities/fileSystem" +import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters" +import { deleteView, getView, getViews, saveView } from "./utils" +import { fetchView } from "../row" +import { context, events } from "@budibase/backend-core" +import { DocumentType } from "../../../db/utils" +import sdk from "../../../sdk" +import { FieldTypes } from "../../../constants" +import { + Ctx, + Row, + Table, + TableExportFormat, + TableSchema, + View, +} from "@budibase/types" +import { builderSocket } from "../../../websockets" + +const { cloneDeep, isEqual } = require("lodash") + +export async function fetch(ctx: Ctx) { + ctx.body = await getViews() +} + +export async function save(ctx: Ctx) { + const db = context.getAppDB() + const { originalName, ...viewToSave } = ctx.request.body + + const existingTable = await sdk.tables.getTable(ctx.request.body.tableId) + existingTable.views ??= {} + const table = cloneDeep(existingTable) + + const groupByField: any = Object.values(table.schema).find( + (field: any) => field.name == viewToSave.groupBy + ) + + const view = viewTemplate(viewToSave, groupByField?.type === FieldTypes.ARRAY) + const viewName = viewToSave.name + + if (!viewName) { + ctx.throw(400, "Cannot create view without a name") + } + + await saveView(originalName, viewName, view) + + // add views to table document + if (!table.views) table.views = {} + if (!view.meta.schema) { + view.meta.schema = table.schema + } + table.views[viewName] = { ...view.meta, name: viewName } + if (originalName) { + delete table.views[originalName] + existingTable.views[viewName] = existingTable.views[originalName] + } + await db.put(table) + await handleViewEvents(existingTable.views[viewName], table.views[viewName]) + + ctx.body = table.views[viewName] + builderSocket?.emitTableUpdate(ctx, table) +} + +export async function calculationEvents(existingView: View, newView: View) { + const existingCalculation = existingView && existingView.calculation + const newCalculation = newView && newView.calculation + + if (existingCalculation && !newCalculation) { + await events.view.calculationDeleted(existingView) + } + + if (!existingCalculation && newCalculation) { + await events.view.calculationCreated(newView) + } + + if ( + existingCalculation && + newCalculation && + existingCalculation !== newCalculation + ) { + await events.view.calculationUpdated(newView) + } +} + +export async function filterEvents(existingView: View, newView: View) { + const hasExistingFilters = !!( + existingView && + existingView.filters && + existingView.filters.length + ) + const hasNewFilters = !!(newView && newView.filters && newView.filters.length) + + if (hasExistingFilters && !hasNewFilters) { + await events.view.filterDeleted(newView) + } + + if (!hasExistingFilters && hasNewFilters) { + await events.view.filterCreated(newView) + } + + if ( + hasExistingFilters && + hasNewFilters && + !isEqual(existingView.filters, newView.filters) + ) { + await events.view.filterUpdated(newView) + } +} + +async function handleViewEvents(existingView: View, newView: View) { + if (!existingView) { + await events.view.created(newView) + } else { + await events.view.updated(newView) + } + await calculationEvents(existingView, newView) + await filterEvents(existingView, newView) +} + +export async function destroy(ctx: Ctx) { + const db = context.getAppDB() + const viewName = decodeURIComponent(ctx.params.viewName) + const view = await deleteView(viewName) + const table = await sdk.tables.getTable(view.meta.tableId) + delete table.views![viewName] + await db.put(table) + await events.view.deleted(view) + + ctx.body = view + builderSocket?.emitTableUpdate(ctx, table) +} + +export async function exportView(ctx: Ctx) { + const viewName = decodeURIComponent(ctx.query.view as string) + const view = await getView(viewName) + + const format = ctx.query.format as unknown + + if (!isFormat(format)) { + ctx.throw( + 400, + "Format must be specified, either csv, json or jsonWithSchema" + ) + } + + if (view) { + ctx.params.viewName = viewName + // Fetch view rows + ctx.query = { + group: view.meta.groupBy, + calculation: view.meta.calculation, + // @ts-ignore + stats: !!view.meta.field, + field: view.meta.field, + } + } else { + // table all_ view + /* istanbul ignore next */ + ctx.params.viewName = viewName + } + + await fetchView(ctx) + let rows = ctx.body as Row[] + + let schema: TableSchema = view && view.meta && view.meta.schema + const tableId = + ctx.params.tableId || + view?.meta?.tableId || + (viewName.startsWith(DocumentType.TABLE) && viewName) + const table: Table = await sdk.tables.getTable(tableId) + if (!schema) { + schema = table.schema + } + + let exportRows = sdk.rows.utils.cleanExportRows(rows, schema, format, []) + + if (format === Format.CSV) { + ctx.attachment(`${viewName}.csv`) + ctx.body = apiFileReturn(csv(Object.keys(schema), exportRows)) + } else if (format === Format.JSON) { + ctx.attachment(`${viewName}.json`) + ctx.body = apiFileReturn(json(exportRows)) + } else if (format === Format.JSON_WITH_SCHEMA) { + ctx.attachment(`${viewName}.json`) + ctx.body = apiFileReturn(jsonWithSchema(schema, exportRows)) + } else { + throw "Format not recognised" + } + + if (viewName.startsWith(DocumentType.TABLE)) { + await events.table.exported(table, format as TableExportFormat) + } else { + await events.view.exported(table, format as TableExportFormat) + } +} diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 8b366993c0..33fccd1e4b 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -11,7 +11,7 @@ router .get( "/api/views/export", authorized(permissions.BUILDER), - viewController.exportView + viewController.v1.exportView ) .get( "/api/views/:viewName", @@ -22,13 +22,13 @@ router ), rowController.fetchView ) - .get("/api/views", authorized(permissions.BUILDER), viewController.fetch) + .get("/api/views", authorized(permissions.BUILDER), viewController.v1.fetch) .delete( "/api/views/:viewName", paramResource("viewName"), authorized(permissions.BUILDER), - viewController.destroy + viewController.v1.destroy ) - .post("/api/views", authorized(permissions.BUILDER), viewController.save) + .post("/api/views", authorized(permissions.BUILDER), viewController.v1.save) export default router From 6ff5c23cd6ed138fecdbab5648bcf38a75f2fc5b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 12:48:16 +0200 Subject: [PATCH 02/89] Add controller scaffolding for views v2 --- packages/server/src/api/controllers/view/index.ts | 1 + packages/server/src/api/controllers/view/views.ts | 6 ++++++ packages/server/src/api/routes/view.ts | 5 +++++ packages/server/src/sdk/index.ts | 2 ++ packages/server/src/sdk/views/index.ts | 3 +++ 5 files changed, 17 insertions(+) create mode 100644 packages/server/src/api/controllers/view/views.ts create mode 100644 packages/server/src/sdk/views/index.ts diff --git a/packages/server/src/api/controllers/view/index.ts b/packages/server/src/api/controllers/view/index.ts index 83bc2c3c8d..95b4b2e143 100644 --- a/packages/server/src/api/controllers/view/index.ts +++ b/packages/server/src/api/controllers/view/index.ts @@ -1 +1,2 @@ export * as v1 from "./legacyViews" +export * as v2 from "./views" diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts new file mode 100644 index 0000000000..7d37c3e1ad --- /dev/null +++ b/packages/server/src/api/controllers/view/views.ts @@ -0,0 +1,6 @@ +import sdk from "../../../sdk" +import { Ctx } from "@budibase/types" + +export async function fetch(ctx: Ctx) { + ctx.body = await sdk.views.get() +} diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 33fccd1e4b..2879d55c26 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -8,6 +8,11 @@ import { permissions } from "@budibase/backend-core" const router: Router = new Router() router + .get( + "/api/views/v2", + authorized(permissions.BUILDER), + viewController.v2.fetch + ) .get( "/api/views/export", authorized(permissions.BUILDER), diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts index 1bf7d89604..d80ec2eb93 100644 --- a/packages/server/src/sdk/index.ts +++ b/packages/server/src/sdk/index.ts @@ -7,6 +7,7 @@ import { default as queries } from "./app/queries" import { default as rows } from "./app/rows" import { default as users } from "./users" import { default as plugins } from "./plugins" +import * as views from "./views" const sdk = { backups, @@ -18,6 +19,7 @@ const sdk = { datasources, queries, plugins, + views, } // default export for TS diff --git a/packages/server/src/sdk/views/index.ts b/packages/server/src/sdk/views/index.ts new file mode 100644 index 0000000000..1c2f27ec9e --- /dev/null +++ b/packages/server/src/sdk/views/index.ts @@ -0,0 +1,3 @@ +export async function get() { + return [] +} From 81e847daeb7868789c6e8034f8c260ded817d015 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 13:09:44 +0200 Subject: [PATCH 03/89] Rename tests --- .../{view.spec.js.snap => legacyView.spec.js.snap} | 0 .../src/api/routes/tests/{view.spec.js => legacyView.spec.js} | 0 packages/server/src/api/routes/view.ts | 2 ++ packages/server/src/tests/utilities/TestConfiguration.ts | 2 +- 4 files changed, 3 insertions(+), 1 deletion(-) rename packages/server/src/api/routes/tests/__snapshots__/{view.spec.js.snap => legacyView.spec.js.snap} (100%) rename packages/server/src/api/routes/tests/{view.spec.js => legacyView.spec.js} (100%) diff --git a/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap b/packages/server/src/api/routes/tests/__snapshots__/legacyView.spec.js.snap similarity index 100% rename from packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap rename to packages/server/src/api/routes/tests/__snapshots__/legacyView.spec.js.snap diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/legacyView.spec.js similarity index 100% rename from packages/server/src/api/routes/tests/view.spec.js rename to packages/server/src/api/routes/tests/legacyView.spec.js diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 2879d55c26..8830086c35 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -13,6 +13,8 @@ router authorized(permissions.BUILDER), viewController.v2.fetch ) + +router .get( "/api/views/export", authorized(permissions.BUILDER), diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index f0e3678099..496c232387 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -631,7 +631,7 @@ class TestConfiguration { tableId: this.table._id, name: "ViewTest", } - return this._req(view, null, controllers.view.save) + return this._req(view, null, controllers.view.v1.save) } // AUTOMATION From 899c8a14fbe49299f03b64836de5ae4a9c482cae Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 16:13:00 +0200 Subject: [PATCH 04/89] Implement and test create --- packages/backend-core/src/constants/db.ts | 1 + .../server/src/api/controllers/view/views.ts | 9 ++- .../server/src/api/routes/tests/view.spec.ts | 74 +++++++++++++++++++ packages/server/src/api/routes/view.ts | 5 ++ packages/server/src/db/utils.ts | 8 ++ packages/server/src/sdk/app/views/index.ts | 25 +++++++ packages/server/src/sdk/index.ts | 2 +- packages/server/src/sdk/views/index.ts | 3 - packages/types/src/documents/app/view.ts | 5 ++ 9 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 packages/server/src/api/routes/tests/view.spec.ts create mode 100644 packages/server/src/sdk/app/views/index.ts delete mode 100644 packages/server/src/sdk/views/index.ts diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index be49b9f261..ea21c04bc3 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -70,6 +70,7 @@ export enum DocumentType { USER_FLAG = "flag", AUTOMATION_METADATA = "meta_au", AUDIT_LOG = "al", + VIEW = "vi", } export const StaticDatabases = { diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 7d37c3e1ad..dc9ac000c5 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -1,6 +1,11 @@ import sdk from "../../../sdk" -import { Ctx } from "@budibase/types" +import { Ctx, ViewV2 } from "@budibase/types" export async function fetch(ctx: Ctx) { - ctx.body = await sdk.views.get() + ctx.body = await sdk.views.get(ctx.params.viewId) +} + +export async function save(ctx: Ctx) { + const view = ctx.request.body + ctx.body = await sdk.views.save(view) } diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts new file mode 100644 index 0000000000..97048a2fa6 --- /dev/null +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -0,0 +1,74 @@ +import * as setup from "./utilities" +import { FieldType, Table, ViewV2 } from "@budibase/types" +import { generator } from "@budibase/backend-core/tests" +import sdk from "../../../sdk" +import { context } from "@budibase/backend-core" + +function priceTable(): Table { + return { + name: "table", + type: "table", + schema: { + Price: { + type: FieldType.NUMBER, + name: "Price", + constraints: {}, + }, + Category: { + type: FieldType.STRING, + name: "Category", + constraints: { + type: "string", + }, + }, + }, + } +} + +describe("/views/v2", () => { + const request = setup.getRequest() + const config = setup.getConfig() + let table: Table + + afterAll(setup.afterAll) + + beforeAll(async () => { + await config.init() + }) + + beforeEach(async () => { + table = await config.createTable(priceTable()) + }) + + const saveView = async (view: ViewV2) => { + return request + .post(`/api/views/v2`) + .send(view) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + } + + describe("create", () => { + it("persist the view when the view is successfully created", async () => { + const view = { + name: generator.guid(), + tableId: table._id!, + } + const res = await saveView(view) + expect(res.status).toBe(200) + expect(res.body._id).toBeDefined() + + await context.doInAppContext(config.appId, async () => { + const persisted = await sdk.views.get(res.body._id) + expect(persisted).toEqual({ + _id: res.body._id, + _rev: res.body._rev, + ...view, + createdAt: expect.any(String), + updatedAt: expect.any(String), + }) + }) + }) + }) +}) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 8830086c35..e14c00ab6c 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -13,6 +13,11 @@ router authorized(permissions.BUILDER), viewController.v2.fetch ) + .post( + "/api/views/v2", + authorized(permissions.BUILDER), + viewController.v2.save + ) router .get( diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index e08392c3a1..1b8444b04b 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -271,3 +271,11 @@ export function getMultiIDParams(ids: string[]) { include_docs: true, } } + +/** + * Generates a new view ID. + * @returns {string} The new view ID which the view doc can be stored under. + */ +export function generateViewID(): string { + return `${DocumentType.VIEW}${SEPARATOR}${newid()}` +} diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts new file mode 100644 index 0000000000..71abe271fc --- /dev/null +++ b/packages/server/src/sdk/app/views/index.ts @@ -0,0 +1,25 @@ +import { context } from "@budibase/backend-core" +import { ViewV2 } from "@budibase/types" +import { generateViewID } from "../../../db/utils" + +export async function get(viewId: string) { + const db = context.getAppDB() + const result = await db.get(viewId) + return result +} + +export async function save(view: ViewV2) { + const db = context.getAppDB() + + const response = await db.put( + { + _id: generateViewID(), + ...view, + }, + {} + ) + return { + _id: response.id, + _rev: response.rev, + } +} diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts index d80ec2eb93..85ac483c05 100644 --- a/packages/server/src/sdk/index.ts +++ b/packages/server/src/sdk/index.ts @@ -7,7 +7,7 @@ import { default as queries } from "./app/queries" import { default as rows } from "./app/rows" import { default as users } from "./users" import { default as plugins } from "./plugins" -import * as views from "./views" +import * as views from "./app/views" const sdk = { backups, diff --git a/packages/server/src/sdk/views/index.ts b/packages/server/src/sdk/views/index.ts deleted file mode 100644 index 1c2f27ec9e..0000000000 --- a/packages/server/src/sdk/views/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function get() { - return [] -} diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index de0dfea7f5..d36271ba43 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -10,6 +10,11 @@ export interface View { meta?: Record } +export interface ViewV2 { + name: string + tableId: string +} + export type ViewSchema = ViewCountOrSumSchema | ViewStatisticsSchema export interface ViewCountOrSumSchema { From 4a5a3e2c33d9a8a6a0437ff03a45d8fee65bb370 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 16:33:06 +0200 Subject: [PATCH 05/89] Implement and test fetch --- .../server/src/api/controllers/view/views.ts | 2 +- .../server/src/api/routes/tests/view.spec.ts | 39 +++++++++++++++++-- packages/server/src/sdk/app/views/index.ts | 19 ++++++++- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index dc9ac000c5..9c5c8d582a 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -2,7 +2,7 @@ import sdk from "../../../sdk" import { Ctx, ViewV2 } from "@budibase/types" export async function fetch(ctx: Ctx) { - ctx.body = await sdk.views.get(ctx.params.viewId) + ctx.body = { views: await sdk.views.fetch() } } export async function save(ctx: Ctx) { diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index 97048a2fa6..e9c8c64505 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -49,12 +49,43 @@ describe("/views/v2", () => { .expect(200) } + function createView(): ViewV2 { + return { + name: generator.guid(), + tableId: table._id!, + } + } + + describe("fetch", () => { + const views = [] + + beforeAll(async () => { + table = await config.createTable(priceTable()) + + for (let id = 0; id < 10; id++) { + const view = createView() + const res = await saveView(view) + await context.doInAppContext(config.appId, async () => { + views.push(await sdk.views.get(res.body._id)) + }) + } + }) + + it("returns all views", async () => { + const res = await request + .get(`/api/views/v2`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(res.body.views.length).toBe(10) + expect(res.body.views).toEqual(expect.arrayContaining([])) + }) + }) + describe("create", () => { it("persist the view when the view is successfully created", async () => { - const view = { - name: generator.guid(), - tableId: table._id!, - } + const view = createView() const res = await saveView(view) expect(res.status).toBe(200) expect(res.body._id).toBeDefined() diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 71abe271fc..fa3813796d 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,7 +1,24 @@ -import { context } from "@budibase/backend-core" +import { + DocumentType, + SEPARATOR, + UNICODE_MAX, + context, +} from "@budibase/backend-core" import { ViewV2 } from "@budibase/types" import { generateViewID } from "../../../db/utils" +export async function fetch() { + const db = context.getAppDB() + + const startKey = `${DocumentType.VIEW}${SEPARATOR}` + const response = await db.allDocs({ + startkey: startKey, + endkey: `${startKey}${UNICODE_MAX}`, + }) + + return response.rows +} + export async function get(viewId: string) { const db = context.getAppDB() const result = await db.get(viewId) From f395b79cacbe225d0f876f8281e4caed6126df2f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 16:49:50 +0200 Subject: [PATCH 06/89] Fix fetch --- .../server/src/api/controllers/view/views.ts | 6 +++- .../server/src/api/routes/tests/view.spec.ts | 28 +++++++------------ packages/server/src/sdk/app/views/index.ts | 3 +- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 9c5c8d582a..092d016b4d 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -7,5 +7,9 @@ export async function fetch(ctx: Ctx) { export async function save(ctx: Ctx) { const view = ctx.request.body - ctx.body = await sdk.views.save(view) + const result = await sdk.views.save(view) + ctx.body = { + ...view, + ...result, + } } diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index e9c8c64505..3a64a21ad9 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -2,7 +2,6 @@ import * as setup from "./utilities" import { FieldType, Table, ViewV2 } from "@budibase/types" import { generator } from "@budibase/backend-core/tests" import sdk from "../../../sdk" -import { context } from "@budibase/backend-core" function priceTable(): Table { return { @@ -57,17 +56,13 @@ describe("/views/v2", () => { } describe("fetch", () => { - const views = [] + const views: any[] = [] beforeAll(async () => { table = await config.createTable(priceTable()) - for (let id = 0; id < 10; id++) { - const view = createView() - const res = await saveView(view) - await context.doInAppContext(config.appId, async () => { - views.push(await sdk.views.get(res.body._id)) - }) + const res = await saveView(createView()) + views.push(res.body) } }) @@ -79,7 +74,9 @@ describe("/views/v2", () => { .expect(200) expect(res.body.views.length).toBe(10) - expect(res.body.views).toEqual(expect.arrayContaining([])) + expect(res.body.views).toEqual( + expect.arrayContaining(views.map(v => expect.objectContaining(v))) + ) }) }) @@ -90,15 +87,10 @@ describe("/views/v2", () => { expect(res.status).toBe(200) expect(res.body._id).toBeDefined() - await context.doInAppContext(config.appId, async () => { - const persisted = await sdk.views.get(res.body._id) - expect(persisted).toEqual({ - _id: res.body._id, - _rev: res.body._rev, - ...view, - createdAt: expect.any(String), - updatedAt: expect.any(String), - }) + expect(res.body).toEqual({ + ...view, + _id: expect.any(String), + _rev: expect.any(String), }) }) }) diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index fa3813796d..0c3bcc5532 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -14,9 +14,10 @@ export async function fetch() { const response = await db.allDocs({ startkey: startKey, endkey: `${startKey}${UNICODE_MAX}`, + include_docs: true, }) - return response.rows + return response.rows.map(r => r.doc) } export async function get(viewId: string) { From e2c1a549def4cd29ff41fb255fa91170f1474f10 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 17:02:54 +0200 Subject: [PATCH 07/89] Find by table --- .../server/src/api/controllers/view/views.ts | 7 ++++ .../server/src/api/routes/tests/view.spec.ts | 34 +++++++++++++++---- packages/server/src/api/routes/view.ts | 7 +++- packages/server/src/db/utils.ts | 8 +++-- packages/server/src/sdk/app/views/index.ts | 17 ++++++++-- 5 files changed, 61 insertions(+), 12 deletions(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 092d016b4d..0380519480 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -1,3 +1,4 @@ +import { DocumentType, SEPARATOR } from "@budibase/backend-core" import sdk from "../../../sdk" import { Ctx, ViewV2 } from "@budibase/types" @@ -5,6 +6,12 @@ export async function fetch(ctx: Ctx) { ctx.body = { views: await sdk.views.fetch() } } +export async function findByTable(ctx: Ctx) { + const tableId = `${DocumentType.TABLE}${SEPARATOR}${ctx.params.tableId}` + console.error(tableId) + ctx.body = { views: await sdk.views.findByTable(tableId) } +} + export async function save(ctx: Ctx) { const view = ctx.request.body const result = await sdk.views.save(view) diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index 3a64a21ad9..c1ce023a00 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -33,9 +33,6 @@ describe("/views/v2", () => { beforeAll(async () => { await config.init() - }) - - beforeEach(async () => { table = await config.createTable(priceTable()) }) @@ -48,10 +45,10 @@ describe("/views/v2", () => { .expect(200) } - function createView(): ViewV2 { + function createView(tableId: string): ViewV2 { return { name: generator.guid(), - tableId: table._id!, + tableId, } } @@ -61,7 +58,7 @@ describe("/views/v2", () => { beforeAll(async () => { table = await config.createTable(priceTable()) for (let id = 0; id < 10; id++) { - const res = await saveView(createView()) + const res = await saveView(createView(table._id!)) views.push(res.body) } }) @@ -80,9 +77,32 @@ describe("/views/v2", () => { }) }) + describe("findByTable", () => { + const views: any[] = [] + + beforeAll(async () => { + table = await config.createTable(priceTable()) + for (let id = 0; id < 5; id++) { + const res = await saveView(createView(table._id!)) + views.push(res.body) + } + }) + + it("returns all views", async () => { + const res = await request + .get(`/api/views/v2/${table._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(res.body.views.length).toBe(5) + expect(res.body.views).toEqual(expect.arrayContaining([])) + }) + }) + describe("create", () => { it("persist the view when the view is successfully created", async () => { - const view = createView() + const view = createView(table._id!) const res = await saveView(view) expect(res.status).toBe(200) expect(res.body._id).toBeDefined() diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index e14c00ab6c..faaf06d742 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -3,7 +3,7 @@ import * as viewController from "../controllers/view" import * as rowController from "../controllers/row" import authorized from "../../middleware/authorized" import { paramResource } from "../../middleware/resourceId" -import { permissions } from "@budibase/backend-core" +import { DocumentType, SEPARATOR, permissions } from "@budibase/backend-core" const router: Router = new Router() @@ -13,6 +13,11 @@ router authorized(permissions.BUILDER), viewController.v2.fetch ) + .get( + `/api/views/v2/${DocumentType.TABLE}${SEPARATOR}:tableId`, + authorized(permissions.BUILDER), + viewController.v2.findByTable + ) .post( "/api/views/v2", authorized(permissions.BUILDER), diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 1b8444b04b..8854cbc824 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -276,6 +276,10 @@ export function getMultiIDParams(ids: string[]) { * Generates a new view ID. * @returns {string} The new view ID which the view doc can be stored under. */ -export function generateViewID(): string { - return `${DocumentType.VIEW}${SEPARATOR}${newid()}` +export function generateViewID(tableId: string) { + return `${viewIDPrefix(tableId)}${newid()}` +} + +export function viewIDPrefix(tableId: string) { + return `${DocumentType.VIEW}${SEPARATOR}${tableId}${SEPARATOR}` } diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 0c3bcc5532..da30585ab3 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -5,7 +5,7 @@ import { context, } from "@budibase/backend-core" import { ViewV2 } from "@budibase/types" -import { generateViewID } from "../../../db/utils" +import * as utils from "../../../db/utils" export async function fetch() { const db = context.getAppDB() @@ -20,6 +20,19 @@ export async function fetch() { return response.rows.map(r => r.doc) } +export async function findByTable(tableId: string) { + const db = context.getAppDB() + + const startKey = utils.viewIDPrefix(tableId) + const response = await db.allDocs({ + startkey: startKey, + endkey: `${startKey}${UNICODE_MAX}`, + include_docs: true, + }) + + return response.rows.map(r => r.doc) +} + export async function get(viewId: string) { const db = context.getAppDB() const result = await db.get(viewId) @@ -31,7 +44,7 @@ export async function save(view: ViewV2) { const response = await db.put( { - _id: generateViewID(), + _id: utils.generateViewID(view.tableId), ...view, }, {} From 1187bd8fb2c1e9e1a827fa40873a999cade1a6b3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 17:03:19 +0200 Subject: [PATCH 08/89] Clean --- packages/server/src/api/controllers/view/views.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 0380519480..9627c0a1dc 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -8,7 +8,6 @@ export async function fetch(ctx: Ctx) { export async function findByTable(ctx: Ctx) { const tableId = `${DocumentType.TABLE}${SEPARATOR}${ctx.params.tableId}` - console.error(tableId) ctx.body = { views: await sdk.views.findByTable(tableId) } } From 7155d759061712c4f7471997aedbe47bc7c45c9c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 17:48:11 +0200 Subject: [PATCH 09/89] Implement get view --- .../server/src/api/controllers/view/views.ts | 5 ++++ .../server/src/api/routes/tests/view.spec.ts | 29 +++++++++++++++++++ packages/server/src/api/routes/view.ts | 5 ++++ 3 files changed, 39 insertions(+) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 9627c0a1dc..b2aa389776 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -6,6 +6,11 @@ export async function fetch(ctx: Ctx) { ctx.body = { views: await sdk.views.fetch() } } +export async function find(ctx: Ctx) { + const viewId = `${DocumentType.VIEW}${SEPARATOR}${ctx.params.viewId}` + ctx.body = await sdk.views.get(viewId) +} + export async function findByTable(ctx: Ctx) { const tableId = `${DocumentType.TABLE}${SEPARATOR}${ctx.params.tableId}` ctx.body = { views: await sdk.views.findByTable(tableId) } diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index c1ce023a00..9691a418bc 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -45,6 +45,14 @@ describe("/views/v2", () => { .expect(200) } + const getView = async (viewId: string) => { + return request + .get(`/api/views/v2/${viewId}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + } + function createView(tableId: string): ViewV2 { return { name: generator.guid(), @@ -100,6 +108,27 @@ describe("/views/v2", () => { }) }) + describe("getView", () => { + let view: any + beforeAll(async () => { + view = (await saveView(createView(table._id!))).body + }) + + it("persist the view when the view is successfully created", async () => { + const res = await getView(view._id) + expect(res.status).toBe(200) + expect(res.body._id).toBeDefined() + + expect(res.body).toEqual({ + ...view, + _id: expect.any(String), + _rev: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), + }) + }) + }) + describe("create", () => { it("persist the view when the view is successfully created", async () => { const view = createView(table._id!) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index faaf06d742..837306b92b 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -18,6 +18,11 @@ router authorized(permissions.BUILDER), viewController.v2.findByTable ) + .get( + `/api/views/v2/${DocumentType.VIEW}${SEPARATOR}:viewId`, + authorized(permissions.BUILDER), + viewController.v2.find + ) .post( "/api/views/v2", authorized(permissions.BUILDER), From 4bbb1b0289b423ce5c6c2800813f61548d11aefe Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 18:01:46 +0200 Subject: [PATCH 10/89] Refactor paths --- .../server/src/api/controllers/view/views.ts | 11 +++++--- .../server/src/api/routes/tests/view.spec.ts | 27 ++++++++++++++----- packages/server/src/api/routes/view.ts | 4 +-- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index b2aa389776..772bb2140b 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -7,12 +7,17 @@ export async function fetch(ctx: Ctx) { } export async function find(ctx: Ctx) { - const viewId = `${DocumentType.VIEW}${SEPARATOR}${ctx.params.viewId}` - ctx.body = await sdk.views.get(viewId) + const { tableId, viewId } = ctx.params + + const result = await sdk.views.get(viewId) + if (result?.tableId !== tableId) { + ctx.throw(404) + } + ctx.body = result } export async function findByTable(ctx: Ctx) { - const tableId = `${DocumentType.TABLE}${SEPARATOR}${ctx.params.tableId}` + const { tableId } = ctx.params ctx.body = { views: await sdk.views.findByTable(tableId) } } diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index 9691a418bc..e6e841f850 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -1,6 +1,6 @@ import * as setup from "./utilities" import { FieldType, Table, ViewV2 } from "@budibase/types" -import { generator } from "@budibase/backend-core/tests" +import { generator, structures } from "@budibase/backend-core/tests" import sdk from "../../../sdk" function priceTable(): Table { @@ -45,12 +45,17 @@ describe("/views/v2", () => { .expect(200) } - const getView = async (viewId: string) => { + const getView = ({ + tableId, + viewId, + }: { + tableId: string + viewId: string + }) => { return request - .get(`/api/views/v2/${viewId}`) + .get(`/api/views/v2/${tableId}/${viewId}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) } function createView(tableId: string): ViewV2 { @@ -114,8 +119,11 @@ describe("/views/v2", () => { view = (await saveView(createView(table._id!))).body }) - it("persist the view when the view is successfully created", async () => { - const res = await getView(view._id) + it("can fetch the expected view", async () => { + const res = await getView({ + tableId: view.tableId, + viewId: view._id, + }).expect(200) expect(res.status).toBe(200) expect(res.body._id).toBeDefined() @@ -127,6 +135,13 @@ describe("/views/v2", () => { updatedAt: expect.any(String), }) }) + + it("will return 404 if the wrong table id is provided", async () => { + await getView({ + tableId: structures.generator.guid(), + viewId: view._id, + }).expect(404) + }) }) describe("create", () => { diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 837306b92b..4eb138e120 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -14,12 +14,12 @@ router viewController.v2.fetch ) .get( - `/api/views/v2/${DocumentType.TABLE}${SEPARATOR}:tableId`, + `/api/views/v2/:tableId`, authorized(permissions.BUILDER), viewController.v2.findByTable ) .get( - `/api/views/v2/${DocumentType.VIEW}${SEPARATOR}:viewId`, + `/api/views/v2/:tableId/:viewId`, authorized(permissions.BUILDER), viewController.v2.find ) From 4ca25ee06551c225111e61f21f78c75540ffcc1a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 12 Jul 2023 18:09:13 +0200 Subject: [PATCH 11/89] Implement deletes --- .../server/src/api/controllers/view/views.ts | 9 ++++++- .../server/src/api/routes/tests/view.spec.ts | 26 +++++++++++++++++++ packages/server/src/api/routes/view.ts | 5 ++++ packages/server/src/sdk/app/views/index.ts | 5 ++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 772bb2140b..7f74950a1f 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -1,4 +1,3 @@ -import { DocumentType, SEPARATOR } from "@budibase/backend-core" import sdk from "../../../sdk" import { Ctx, ViewV2 } from "@budibase/types" @@ -29,3 +28,11 @@ export async function save(ctx: Ctx) { ...result, } } + +export async function remove(ctx: Ctx) { + const { viewId } = ctx.params + const { _rev } = await sdk.views.get(viewId) + + await sdk.views.remove(viewId, _rev) + ctx.status = 204 +} diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index e6e841f850..b4a972622f 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -158,4 +158,30 @@ describe("/views/v2", () => { }) }) }) + + describe("delete", () => { + let view: any + + beforeAll(async () => { + table = await config.createTable(priceTable()) + view = (await saveView(createView(table._id!))).body + }) + + it("can delete an existing view", async () => { + await getView({ + tableId: view.tableId, + viewId: view._id, + }).expect(200) + + await request + .delete(`/api/views/v2/${view.tableId}/${view._id}`) + .set(config.defaultHeaders()) + .expect(204) + + await getView({ + tableId: view.tableId, + viewId: view._id, + }).expect(404) + }) + }) }) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 4eb138e120..9137302a3f 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -28,6 +28,11 @@ router authorized(permissions.BUILDER), viewController.v2.save ) + .delete( + `/api/views/v2/:tableId/:viewId`, + authorized(permissions.BUILDER), + viewController.v2.remove + ) router .get( diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index da30585ab3..c8f0abca7d 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -54,3 +54,8 @@ export async function save(view: ViewV2) { _rev: response.rev, } } + +export async function remove(viewId: string, rev: string) { + const db = context.getAppDB() + await db.remove(viewId, rev) +} From 02bc1d2cdc10b251cda4810a125b97d4c21acb33 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 13 Jul 2023 11:36:26 +0200 Subject: [PATCH 12/89] Simplifying endpoints --- .../server/src/api/controllers/view/views.ts | 22 +++--- .../server/src/api/routes/tests/view.spec.ts | 69 ++++++++----------- packages/server/src/api/routes/view.ts | 9 +-- 3 files changed, 43 insertions(+), 57 deletions(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 7f74950a1f..47f9d645a0 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -2,24 +2,26 @@ import sdk from "../../../sdk" import { Ctx, ViewV2 } from "@budibase/types" export async function fetch(ctx: Ctx) { - ctx.body = { views: await sdk.views.fetch() } + const { tableId } = ctx.query + + if (tableId && typeof tableId !== "string") { + ctx.throw(400, "tableId type is not valid") + } + + const views = tableId + ? await sdk.views.findByTable(tableId) + : await sdk.views.fetch() + + ctx.body = { views } } export async function find(ctx: Ctx) { - const { tableId, viewId } = ctx.params + const { viewId } = ctx.params const result = await sdk.views.get(viewId) - if (result?.tableId !== tableId) { - ctx.throw(404) - } ctx.body = result } -export async function findByTable(ctx: Ctx) { - const { tableId } = ctx.params - ctx.body = { views: await sdk.views.findByTable(tableId) } -} - export async function save(ctx: Ctx) { const view = ctx.request.body const result = await sdk.views.save(view) diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/view.spec.ts index b4a972622f..61b1ab07ad 100644 --- a/packages/server/src/api/routes/tests/view.spec.ts +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -1,7 +1,6 @@ import * as setup from "./utilities" import { FieldType, Table, ViewV2 } from "@budibase/types" import { generator, structures } from "@budibase/backend-core/tests" -import sdk from "../../../sdk" function priceTable(): Table { return { @@ -45,15 +44,9 @@ describe("/views/v2", () => { .expect(200) } - const getView = ({ - tableId, - viewId, - }: { - tableId: string - viewId: string - }) => { + const getView = (viewId: string) => { return request - .get(`/api/views/v2/${tableId}/${viewId}`) + .get(`/api/views/v2/${viewId}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) } @@ -88,28 +81,36 @@ describe("/views/v2", () => { expect.arrayContaining(views.map(v => expect.objectContaining(v))) ) }) - }) - describe("findByTable", () => { - const views: any[] = [] - - beforeAll(async () => { - table = await config.createTable(priceTable()) + it("can filter by table id", async () => { + const newTable = await config.createTable(priceTable()) + const newViews = [] for (let id = 0; id < 5; id++) { - const res = await saveView(createView(table._id!)) - views.push(res.body) + const res = await saveView(createView(newTable._id!)) + newViews.push(res.body) } - }) - - it("returns all views", async () => { const res = await request - .get(`/api/views/v2/${table._id}`) + .get(`/api/views/v2?tableId=${newTable._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) expect(res.body.views.length).toBe(5) - expect(res.body.views).toEqual(expect.arrayContaining([])) + expect(res.body.views).toEqual( + expect.arrayContaining(newViews.map(v => expect.objectContaining(v))) + ) + }) + + it("can not filter by multiple table ids", async () => { + const res = await request + .get( + `/api/views/v2?tableId=${structures.generator.guid()}&tableId=${structures.generator.guid()}` + ) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(400) + + expect(res.body.message).toBe("tableId type is not valid") }) }) @@ -120,10 +121,7 @@ describe("/views/v2", () => { }) it("can fetch the expected view", async () => { - const res = await getView({ - tableId: view.tableId, - viewId: view._id, - }).expect(200) + const res = await getView(view._id).expect(200) expect(res.status).toBe(200) expect(res.body._id).toBeDefined() @@ -136,11 +134,8 @@ describe("/views/v2", () => { }) }) - it("will return 404 if the wrong table id is provided", async () => { - await getView({ - tableId: structures.generator.guid(), - viewId: view._id, - }).expect(404) + it("will return 404 if the unnexisting id is provided", async () => { + await getView(structures.generator.guid()).expect(404) }) }) @@ -168,20 +163,14 @@ describe("/views/v2", () => { }) it("can delete an existing view", async () => { - await getView({ - tableId: view.tableId, - viewId: view._id, - }).expect(200) + await getView(view._id).expect(200) await request - .delete(`/api/views/v2/${view.tableId}/${view._id}`) + .delete(`/api/views/v2/${view._id}`) .set(config.defaultHeaders()) .expect(204) - await getView({ - tableId: view.tableId, - viewId: view._id, - }).expect(404) + await getView(view._id).expect(404) }) }) }) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 9137302a3f..92a7212975 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -14,12 +14,7 @@ router viewController.v2.fetch ) .get( - `/api/views/v2/:tableId`, - authorized(permissions.BUILDER), - viewController.v2.findByTable - ) - .get( - `/api/views/v2/:tableId/:viewId`, + `/api/views/v2/:viewId`, authorized(permissions.BUILDER), viewController.v2.find ) @@ -29,7 +24,7 @@ router viewController.v2.save ) .delete( - `/api/views/v2/:tableId/:viewId`, + `/api/views/v2/:viewId`, authorized(permissions.BUILDER), viewController.v2.remove ) From e3c493081957bc1402267fb5a8db1b173ee85597 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 17 Jul 2023 12:29:48 +0200 Subject: [PATCH 13/89] Undo renaming as lagacyViews --- .../server/src/api/controllers/view/index.ts | 4 +- .../src/api/controllers/view/legacyViews.ts | 195 ----------------- .../server/src/api/controllers/view/views.ts | 203 +++++++++++++++--- .../src/api/controllers/view/viewsV2.ts | 40 ++++ ...acyView.spec.js.snap => view.spec.js.snap} | 0 .../{legacyView.spec.js => view.spec.js} | 0 .../tests/{view.spec.ts => viewV2.spec.ts} | 0 7 files changed, 221 insertions(+), 221 deletions(-) delete mode 100644 packages/server/src/api/controllers/view/legacyViews.ts create mode 100644 packages/server/src/api/controllers/view/viewsV2.ts rename packages/server/src/api/routes/tests/__snapshots__/{legacyView.spec.js.snap => view.spec.js.snap} (100%) rename packages/server/src/api/routes/tests/{legacyView.spec.js => view.spec.js} (100%) rename packages/server/src/api/routes/tests/{view.spec.ts => viewV2.spec.ts} (100%) diff --git a/packages/server/src/api/controllers/view/index.ts b/packages/server/src/api/controllers/view/index.ts index 95b4b2e143..0d66969dd6 100644 --- a/packages/server/src/api/controllers/view/index.ts +++ b/packages/server/src/api/controllers/view/index.ts @@ -1,2 +1,2 @@ -export * as v1 from "./legacyViews" -export * as v2 from "./views" +export * as v1 from "./views" +export * as v2 from "./viewsV2" diff --git a/packages/server/src/api/controllers/view/legacyViews.ts b/packages/server/src/api/controllers/view/legacyViews.ts deleted file mode 100644 index 99c4224c62..0000000000 --- a/packages/server/src/api/controllers/view/legacyViews.ts +++ /dev/null @@ -1,195 +0,0 @@ -import viewTemplate from "./viewBuilder" -import { apiFileReturn } from "../../../utilities/fileSystem" -import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters" -import { deleteView, getView, getViews, saveView } from "./utils" -import { fetchView } from "../row" -import { context, events } from "@budibase/backend-core" -import { DocumentType } from "../../../db/utils" -import sdk from "../../../sdk" -import { FieldTypes } from "../../../constants" -import { - Ctx, - Row, - Table, - TableExportFormat, - TableSchema, - View, -} from "@budibase/types" -import { builderSocket } from "../../../websockets" - -const { cloneDeep, isEqual } = require("lodash") - -export async function fetch(ctx: Ctx) { - ctx.body = await getViews() -} - -export async function save(ctx: Ctx) { - const db = context.getAppDB() - const { originalName, ...viewToSave } = ctx.request.body - - const existingTable = await sdk.tables.getTable(ctx.request.body.tableId) - existingTable.views ??= {} - const table = cloneDeep(existingTable) - - const groupByField: any = Object.values(table.schema).find( - (field: any) => field.name == viewToSave.groupBy - ) - - const view = viewTemplate(viewToSave, groupByField?.type === FieldTypes.ARRAY) - const viewName = viewToSave.name - - if (!viewName) { - ctx.throw(400, "Cannot create view without a name") - } - - await saveView(originalName, viewName, view) - - // add views to table document - if (!table.views) table.views = {} - if (!view.meta.schema) { - view.meta.schema = table.schema - } - table.views[viewName] = { ...view.meta, name: viewName } - if (originalName) { - delete table.views[originalName] - existingTable.views[viewName] = existingTable.views[originalName] - } - await db.put(table) - await handleViewEvents(existingTable.views[viewName], table.views[viewName]) - - ctx.body = table.views[viewName] - builderSocket?.emitTableUpdate(ctx, table) -} - -export async function calculationEvents(existingView: View, newView: View) { - const existingCalculation = existingView && existingView.calculation - const newCalculation = newView && newView.calculation - - if (existingCalculation && !newCalculation) { - await events.view.calculationDeleted(existingView) - } - - if (!existingCalculation && newCalculation) { - await events.view.calculationCreated(newView) - } - - if ( - existingCalculation && - newCalculation && - existingCalculation !== newCalculation - ) { - await events.view.calculationUpdated(newView) - } -} - -export async function filterEvents(existingView: View, newView: View) { - const hasExistingFilters = !!( - existingView && - existingView.filters && - existingView.filters.length - ) - const hasNewFilters = !!(newView && newView.filters && newView.filters.length) - - if (hasExistingFilters && !hasNewFilters) { - await events.view.filterDeleted(newView) - } - - if (!hasExistingFilters && hasNewFilters) { - await events.view.filterCreated(newView) - } - - if ( - hasExistingFilters && - hasNewFilters && - !isEqual(existingView.filters, newView.filters) - ) { - await events.view.filterUpdated(newView) - } -} - -async function handleViewEvents(existingView: View, newView: View) { - if (!existingView) { - await events.view.created(newView) - } else { - await events.view.updated(newView) - } - await calculationEvents(existingView, newView) - await filterEvents(existingView, newView) -} - -export async function destroy(ctx: Ctx) { - const db = context.getAppDB() - const viewName = decodeURIComponent(ctx.params.viewName) - const view = await deleteView(viewName) - const table = await sdk.tables.getTable(view.meta.tableId) - delete table.views![viewName] - await db.put(table) - await events.view.deleted(view) - - ctx.body = view - builderSocket?.emitTableUpdate(ctx, table) -} - -export async function exportView(ctx: Ctx) { - const viewName = decodeURIComponent(ctx.query.view as string) - const view = await getView(viewName) - - const format = ctx.query.format as unknown - - if (!isFormat(format)) { - ctx.throw( - 400, - "Format must be specified, either csv, json or jsonWithSchema" - ) - } - - if (view) { - ctx.params.viewName = viewName - // Fetch view rows - ctx.query = { - group: view.meta.groupBy, - calculation: view.meta.calculation, - // @ts-ignore - stats: !!view.meta.field, - field: view.meta.field, - } - } else { - // table all_ view - /* istanbul ignore next */ - ctx.params.viewName = viewName - } - - await fetchView(ctx) - let rows = ctx.body as Row[] - - let schema: TableSchema = view && view.meta && view.meta.schema - const tableId = - ctx.params.tableId || - view?.meta?.tableId || - (viewName.startsWith(DocumentType.TABLE) && viewName) - const table: Table = await sdk.tables.getTable(tableId) - if (!schema) { - schema = table.schema - } - - let exportRows = sdk.rows.utils.cleanExportRows(rows, schema, format, []) - - if (format === Format.CSV) { - ctx.attachment(`${viewName}.csv`) - ctx.body = apiFileReturn(csv(Object.keys(schema), exportRows)) - } else if (format === Format.JSON) { - ctx.attachment(`${viewName}.json`) - ctx.body = apiFileReturn(json(exportRows)) - } else if (format === Format.JSON_WITH_SCHEMA) { - ctx.attachment(`${viewName}.json`) - ctx.body = apiFileReturn(jsonWithSchema(schema, exportRows)) - } else { - throw "Format not recognised" - } - - if (viewName.startsWith(DocumentType.TABLE)) { - await events.table.exported(table, format as TableExportFormat) - } else { - await events.view.exported(table, format as TableExportFormat) - } -} diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 47f9d645a0..99c4224c62 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -1,40 +1,195 @@ +import viewTemplate from "./viewBuilder" +import { apiFileReturn } from "../../../utilities/fileSystem" +import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters" +import { deleteView, getView, getViews, saveView } from "./utils" +import { fetchView } from "../row" +import { context, events } from "@budibase/backend-core" +import { DocumentType } from "../../../db/utils" import sdk from "../../../sdk" -import { Ctx, ViewV2 } from "@budibase/types" +import { FieldTypes } from "../../../constants" +import { + Ctx, + Row, + Table, + TableExportFormat, + TableSchema, + View, +} from "@budibase/types" +import { builderSocket } from "../../../websockets" + +const { cloneDeep, isEqual } = require("lodash") export async function fetch(ctx: Ctx) { - const { tableId } = ctx.query + ctx.body = await getViews() +} - if (tableId && typeof tableId !== "string") { - ctx.throw(400, "tableId type is not valid") +export async function save(ctx: Ctx) { + const db = context.getAppDB() + const { originalName, ...viewToSave } = ctx.request.body + + const existingTable = await sdk.tables.getTable(ctx.request.body.tableId) + existingTable.views ??= {} + const table = cloneDeep(existingTable) + + const groupByField: any = Object.values(table.schema).find( + (field: any) => field.name == viewToSave.groupBy + ) + + const view = viewTemplate(viewToSave, groupByField?.type === FieldTypes.ARRAY) + const viewName = viewToSave.name + + if (!viewName) { + ctx.throw(400, "Cannot create view without a name") } - const views = tableId - ? await sdk.views.findByTable(tableId) - : await sdk.views.fetch() + await saveView(originalName, viewName, view) - ctx.body = { views } + // add views to table document + if (!table.views) table.views = {} + if (!view.meta.schema) { + view.meta.schema = table.schema + } + table.views[viewName] = { ...view.meta, name: viewName } + if (originalName) { + delete table.views[originalName] + existingTable.views[viewName] = existingTable.views[originalName] + } + await db.put(table) + await handleViewEvents(existingTable.views[viewName], table.views[viewName]) + + ctx.body = table.views[viewName] + builderSocket?.emitTableUpdate(ctx, table) } -export async function find(ctx: Ctx) { - const { viewId } = ctx.params +export async function calculationEvents(existingView: View, newView: View) { + const existingCalculation = existingView && existingView.calculation + const newCalculation = newView && newView.calculation - const result = await sdk.views.get(viewId) - ctx.body = result -} + if (existingCalculation && !newCalculation) { + await events.view.calculationDeleted(existingView) + } -export async function save(ctx: Ctx) { - const view = ctx.request.body - const result = await sdk.views.save(view) - ctx.body = { - ...view, - ...result, + if (!existingCalculation && newCalculation) { + await events.view.calculationCreated(newView) + } + + if ( + existingCalculation && + newCalculation && + existingCalculation !== newCalculation + ) { + await events.view.calculationUpdated(newView) } } -export async function remove(ctx: Ctx) { - const { viewId } = ctx.params - const { _rev } = await sdk.views.get(viewId) +export async function filterEvents(existingView: View, newView: View) { + const hasExistingFilters = !!( + existingView && + existingView.filters && + existingView.filters.length + ) + const hasNewFilters = !!(newView && newView.filters && newView.filters.length) - await sdk.views.remove(viewId, _rev) - ctx.status = 204 + if (hasExistingFilters && !hasNewFilters) { + await events.view.filterDeleted(newView) + } + + if (!hasExistingFilters && hasNewFilters) { + await events.view.filterCreated(newView) + } + + if ( + hasExistingFilters && + hasNewFilters && + !isEqual(existingView.filters, newView.filters) + ) { + await events.view.filterUpdated(newView) + } +} + +async function handleViewEvents(existingView: View, newView: View) { + if (!existingView) { + await events.view.created(newView) + } else { + await events.view.updated(newView) + } + await calculationEvents(existingView, newView) + await filterEvents(existingView, newView) +} + +export async function destroy(ctx: Ctx) { + const db = context.getAppDB() + const viewName = decodeURIComponent(ctx.params.viewName) + const view = await deleteView(viewName) + const table = await sdk.tables.getTable(view.meta.tableId) + delete table.views![viewName] + await db.put(table) + await events.view.deleted(view) + + ctx.body = view + builderSocket?.emitTableUpdate(ctx, table) +} + +export async function exportView(ctx: Ctx) { + const viewName = decodeURIComponent(ctx.query.view as string) + const view = await getView(viewName) + + const format = ctx.query.format as unknown + + if (!isFormat(format)) { + ctx.throw( + 400, + "Format must be specified, either csv, json or jsonWithSchema" + ) + } + + if (view) { + ctx.params.viewName = viewName + // Fetch view rows + ctx.query = { + group: view.meta.groupBy, + calculation: view.meta.calculation, + // @ts-ignore + stats: !!view.meta.field, + field: view.meta.field, + } + } else { + // table all_ view + /* istanbul ignore next */ + ctx.params.viewName = viewName + } + + await fetchView(ctx) + let rows = ctx.body as Row[] + + let schema: TableSchema = view && view.meta && view.meta.schema + const tableId = + ctx.params.tableId || + view?.meta?.tableId || + (viewName.startsWith(DocumentType.TABLE) && viewName) + const table: Table = await sdk.tables.getTable(tableId) + if (!schema) { + schema = table.schema + } + + let exportRows = sdk.rows.utils.cleanExportRows(rows, schema, format, []) + + if (format === Format.CSV) { + ctx.attachment(`${viewName}.csv`) + ctx.body = apiFileReturn(csv(Object.keys(schema), exportRows)) + } else if (format === Format.JSON) { + ctx.attachment(`${viewName}.json`) + ctx.body = apiFileReturn(json(exportRows)) + } else if (format === Format.JSON_WITH_SCHEMA) { + ctx.attachment(`${viewName}.json`) + ctx.body = apiFileReturn(jsonWithSchema(schema, exportRows)) + } else { + throw "Format not recognised" + } + + if (viewName.startsWith(DocumentType.TABLE)) { + await events.table.exported(table, format as TableExportFormat) + } else { + await events.view.exported(table, format as TableExportFormat) + } } diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts new file mode 100644 index 0000000000..47f9d645a0 --- /dev/null +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -0,0 +1,40 @@ +import sdk from "../../../sdk" +import { Ctx, ViewV2 } from "@budibase/types" + +export async function fetch(ctx: Ctx) { + const { tableId } = ctx.query + + if (tableId && typeof tableId !== "string") { + ctx.throw(400, "tableId type is not valid") + } + + const views = tableId + ? await sdk.views.findByTable(tableId) + : await sdk.views.fetch() + + ctx.body = { views } +} + +export async function find(ctx: Ctx) { + const { viewId } = ctx.params + + const result = await sdk.views.get(viewId) + ctx.body = result +} + +export async function save(ctx: Ctx) { + const view = ctx.request.body + const result = await sdk.views.save(view) + ctx.body = { + ...view, + ...result, + } +} + +export async function remove(ctx: Ctx) { + const { viewId } = ctx.params + const { _rev } = await sdk.views.get(viewId) + + await sdk.views.remove(viewId, _rev) + ctx.status = 204 +} diff --git a/packages/server/src/api/routes/tests/__snapshots__/legacyView.spec.js.snap b/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap similarity index 100% rename from packages/server/src/api/routes/tests/__snapshots__/legacyView.spec.js.snap rename to packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap diff --git a/packages/server/src/api/routes/tests/legacyView.spec.js b/packages/server/src/api/routes/tests/view.spec.js similarity index 100% rename from packages/server/src/api/routes/tests/legacyView.spec.js rename to packages/server/src/api/routes/tests/view.spec.js diff --git a/packages/server/src/api/routes/tests/view.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts similarity index 100% rename from packages/server/src/api/routes/tests/view.spec.ts rename to packages/server/src/api/routes/tests/viewV2.spec.ts From 03f84170b815a25b1293b30943b676a7c67e0ef9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 17 Jul 2023 22:26:57 +0200 Subject: [PATCH 14/89] Use v2 as prefix --- .../server/src/api/routes/tests/viewV2.spec.ts | 14 +++++++------- packages/server/src/api/routes/view.ts | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 61b1ab07ad..db94f2972b 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -23,7 +23,7 @@ function priceTable(): Table { } } -describe("/views/v2", () => { +describe("/v2/views", () => { const request = setup.getRequest() const config = setup.getConfig() let table: Table @@ -37,7 +37,7 @@ describe("/views/v2", () => { const saveView = async (view: ViewV2) => { return request - .post(`/api/views/v2`) + .post(`/api/v2/views`) .send(view) .set(config.defaultHeaders()) .expect("Content-Type", /json/) @@ -46,7 +46,7 @@ describe("/views/v2", () => { const getView = (viewId: string) => { return request - .get(`/api/views/v2/${viewId}`) + .get(`/api/v2/views/${viewId}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) } @@ -71,7 +71,7 @@ describe("/views/v2", () => { it("returns all views", async () => { const res = await request - .get(`/api/views/v2`) + .get(`/api/v2/views`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -90,7 +90,7 @@ describe("/views/v2", () => { newViews.push(res.body) } const res = await request - .get(`/api/views/v2?tableId=${newTable._id}`) + .get(`/api/v2/views?tableId=${newTable._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -104,7 +104,7 @@ describe("/views/v2", () => { it("can not filter by multiple table ids", async () => { const res = await request .get( - `/api/views/v2?tableId=${structures.generator.guid()}&tableId=${structures.generator.guid()}` + `/api/v2/views?tableId=${structures.generator.guid()}&tableId=${structures.generator.guid()}` ) .set(config.defaultHeaders()) .expect("Content-Type", /json/) @@ -166,7 +166,7 @@ describe("/views/v2", () => { await getView(view._id).expect(200) await request - .delete(`/api/views/v2/${view._id}`) + .delete(`/api/v2/views/${view._id}`) .set(config.defaultHeaders()) .expect(204) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 92a7212975..5fa4fe17f0 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -9,22 +9,22 @@ const router: Router = new Router() router .get( - "/api/views/v2", + "/api/v2/views", authorized(permissions.BUILDER), viewController.v2.fetch ) .get( - `/api/views/v2/:viewId`, + `/api/v2/views/:viewId`, authorized(permissions.BUILDER), viewController.v2.find ) .post( - "/api/views/v2", + "/api/v2/views", authorized(permissions.BUILDER), viewController.v2.save ) .delete( - `/api/views/v2/:viewId`, + `/api/v2/views/:viewId`, authorized(permissions.BUILDER), viewController.v2.remove ) From 8282fbb99babf1ab79010ed44a3313620f9144aa Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 09:58:43 +0200 Subject: [PATCH 15/89] Type responses --- .../src/api/controllers/view/viewsV2.ts | 27 +++++++++++----- .../src/api/routes/tests/viewV2.spec.ts | 31 ++++++++++--------- packages/server/src/sdk/app/views/index.ts | 13 ++++---- packages/types/src/api/web/app/index.ts | 1 + packages/types/src/api/web/app/view.ts | 9 ++++++ packages/types/src/documents/app/view.ts | 4 ++- 6 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 packages/types/src/api/web/app/view.ts diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 47f9d645a0..65340682d8 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -1,7 +1,7 @@ import sdk from "../../../sdk" -import { Ctx, ViewV2 } from "@budibase/types" +import { Ctx, FetchViewResponse, ViewResponse, ViewV2 } from "@budibase/types" -export async function fetch(ctx: Ctx) { +export async function fetch(ctx: Ctx<{}, FetchViewResponse>) { const { tableId } = ctx.query if (tableId && typeof tableId !== "string") { @@ -15,11 +15,17 @@ export async function fetch(ctx: Ctx) { ctx.body = { views } } -export async function find(ctx: Ctx) { +export async function find(ctx: Ctx<{}, ViewResponse>) { const { viewId } = ctx.params - const result = await sdk.views.get(viewId) - ctx.body = result + const view = await sdk.views.get(viewId) + if (!view) { + ctx.throw(404) + } + + ctx.body = { + data: view, + } } export async function save(ctx: Ctx) { @@ -31,10 +37,15 @@ export async function save(ctx: Ctx) { } } -export async function remove(ctx: Ctx) { +export async function remove(ctx: Ctx<{}, {}>) { const { viewId } = ctx.params - const { _rev } = await sdk.views.get(viewId) + const doc = await sdk.views.get(viewId) + if (!doc) { + ctx.throw(404) + } - await sdk.views.remove(viewId, _rev) + const { _rev } = doc + + await sdk.views.remove(viewId, _rev!) ctx.status = 204 } diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index db94f2972b..4c45692b3a 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -59,7 +59,7 @@ describe("/v2/views", () => { } describe("fetch", () => { - const views: any[] = [] + const views: ViewV2[] = [] beforeAll(async () => { table = await config.createTable(priceTable()) @@ -115,22 +115,23 @@ describe("/v2/views", () => { }) describe("getView", () => { - let view: any + let view: ViewV2 beforeAll(async () => { view = (await saveView(createView(table._id!))).body }) it("can fetch the expected view", async () => { - const res = await getView(view._id).expect(200) + const res = await getView(view._id!).expect(200) expect(res.status).toBe(200) - expect(res.body._id).toBeDefined() expect(res.body).toEqual({ - ...view, - _id: expect.any(String), - _rev: expect.any(String), - createdAt: expect.any(String), - updatedAt: expect.any(String), + data: { + ...view, + _id: view._id, + _rev: view._rev, + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, }) }) @@ -141,13 +142,13 @@ describe("/v2/views", () => { describe("create", () => { it("persist the view when the view is successfully created", async () => { - const view = createView(table._id!) - const res = await saveView(view) + const newView = createView(table._id!) + const res = await saveView(newView) expect(res.status).toBe(200) expect(res.body._id).toBeDefined() expect(res.body).toEqual({ - ...view, + ...newView, _id: expect.any(String), _rev: expect.any(String), }) @@ -155,7 +156,7 @@ describe("/v2/views", () => { }) describe("delete", () => { - let view: any + let view: ViewV2 beforeAll(async () => { table = await config.createTable(priceTable()) @@ -163,14 +164,14 @@ describe("/v2/views", () => { }) it("can delete an existing view", async () => { - await getView(view._id).expect(200) + await getView(view._id!).expect(200) await request .delete(`/api/v2/views/${view._id}`) .set(config.defaultHeaders()) .expect(204) - await getView(view._id).expect(404) + await getView(view._id!).expect(404) }) }) }) diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index c8f0abca7d..a1cffeb2b5 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -7,7 +7,7 @@ import { import { ViewV2 } from "@budibase/types" import * as utils from "../../../db/utils" -export async function fetch() { +export async function fetch(): Promise { const db = context.getAppDB() const startKey = `${DocumentType.VIEW}${SEPARATOR}` @@ -20,7 +20,7 @@ export async function fetch() { return response.rows.map(r => r.doc) } -export async function findByTable(tableId: string) { +export async function findByTable(tableId: string): Promise { const db = context.getAppDB() const startKey = utils.viewIDPrefix(tableId) @@ -33,13 +33,13 @@ export async function findByTable(tableId: string) { return response.rows.map(r => r.doc) } -export async function get(viewId: string) { +export async function get(viewId: string): Promise { const db = context.getAppDB() - const result = await db.get(viewId) + const result = await db.get(viewId) return result } -export async function save(view: ViewV2) { +export async function save(view: ViewV2): Promise { const db = context.getAppDB() const response = await db.put( @@ -50,12 +50,13 @@ export async function save(view: ViewV2) { {} ) return { + ...view, _id: response.id, _rev: response.rev, } } -export async function remove(viewId: string, rev: string) { +export async function remove(viewId: string, rev: string): Promise { const db = context.getAppDB() await db.remove(viewId, rev) } diff --git a/packages/types/src/api/web/app/index.ts b/packages/types/src/api/web/app/index.ts index 9be15ecfe3..bb9eb09abc 100644 --- a/packages/types/src/api/web/app/index.ts +++ b/packages/types/src/api/web/app/index.ts @@ -1,2 +1,3 @@ export * from "./backup" export * from "./datasource" +export * from "./view" diff --git a/packages/types/src/api/web/app/view.ts b/packages/types/src/api/web/app/view.ts new file mode 100644 index 0000000000..524783d090 --- /dev/null +++ b/packages/types/src/api/web/app/view.ts @@ -0,0 +1,9 @@ +import { ViewV2 } from "../../../documents" + +export interface FetchViewResponse { + views: ViewV2[] +} + +export interface ViewResponse { + data: ViewV2 +} diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index d36271ba43..f3aad235cc 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,3 +1,5 @@ +import { Document } from "../document" + export interface View { name: string tableId: string @@ -10,7 +12,7 @@ export interface View { meta?: Record } -export interface ViewV2 { +export interface ViewV2 extends Document { name: string tableId: string } From 7f3de5d40e0b0070cbad53e058375835405e88c6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 10:14:13 +0200 Subject: [PATCH 16/89] Move view code to test config --- .../src/api/routes/tests/viewV2.spec.ts | 43 ++++++++----------- .../src/tests/utilities/TestConfiguration.ts | 25 ++++++++--- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 4c45692b3a..a532779fcf 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -35,15 +35,6 @@ describe("/v2/views", () => { table = await config.createTable(priceTable()) }) - const saveView = async (view: ViewV2) => { - return request - .post(`/api/v2/views`) - .send(view) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - } - const getView = (viewId: string) => { return request .get(`/api/v2/views/${viewId}`) @@ -51,21 +42,13 @@ describe("/v2/views", () => { .expect("Content-Type", /json/) } - function createView(tableId: string): ViewV2 { - return { - name: generator.guid(), - tableId, - } - } - describe("fetch", () => { const views: ViewV2[] = [] beforeAll(async () => { - table = await config.createTable(priceTable()) + await config.createTable(priceTable()) for (let id = 0; id < 10; id++) { - const res = await saveView(createView(table._id!)) - views.push(res.body) + views.push(await config.createViewV2()) } }) @@ -86,9 +69,9 @@ describe("/v2/views", () => { const newTable = await config.createTable(priceTable()) const newViews = [] for (let id = 0; id < 5; id++) { - const res = await saveView(createView(newTable._id!)) - newViews.push(res.body) + newViews.push(await config.createViewV2({ tableId: newTable._id })) } + const res = await request .get(`/api/v2/views?tableId=${newTable._id}`) .set(config.defaultHeaders()) @@ -117,7 +100,7 @@ describe("/v2/views", () => { describe("getView", () => { let view: ViewV2 beforeAll(async () => { - view = (await saveView(createView(table._id!))).body + view = await config.createViewV2() }) it("can fetch the expected view", async () => { @@ -142,8 +125,16 @@ describe("/v2/views", () => { describe("create", () => { it("persist the view when the view is successfully created", async () => { - const newView = createView(table._id!) - const res = await saveView(newView) + const newView: ViewV2 = { + name: generator.name(), + tableId: config.table!._id!, + } + const res = await request + .post(`/api/v2/views`) + .send(newView) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) expect(res.status).toBe(200) expect(res.body._id).toBeDefined() @@ -159,8 +150,8 @@ describe("/v2/views", () => { let view: ViewV2 beforeAll(async () => { - table = await config.createTable(priceTable()) - view = (await saveView(createView(table._id!))).body + await config.createTable(priceTable()) + view = await config.createViewV2() }) it("can delete an existing view", async () => { diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 496c232387..a02fd39462 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -50,6 +50,7 @@ import { SearchFilters, UserRoles, Automation, + ViewV2, } from "@budibase/types" import { BUILTIN_ROLE_IDS } from "@budibase/backend-core/src/security/roles" @@ -73,7 +74,7 @@ class TestConfiguration { user: any globalUserId: any userMetadataId: any - table: any + table?: Table linkedTable: any automation: any datasource: any @@ -525,7 +526,7 @@ class TestConfiguration { async updateTable(config?: any): Promise { config = config || basicTable() this.table = await this._req(config, null, controllers.table.save) - return this.table + return this.table! } async createTable(config?: Table) { @@ -536,7 +537,7 @@ class TestConfiguration { } async getTable(tableId?: string) { - tableId = tableId || this.table._id + tableId = tableId || this.table?._id return this._req(null, { tableId }, controllers.table.find) } @@ -577,7 +578,7 @@ class TestConfiguration { throw "Test requires table to be configured." } const tableId = (config && config.tableId) || this.table._id - config = config || basicRow(tableId) + config = config || basicRow(tableId!) return this._req(config, { tableId }, controllers.row.save) } @@ -587,14 +588,14 @@ class TestConfiguration { async getRows(tableId: string) { if (!tableId && this.table) { - tableId = this.table._id + tableId = this.table._id! } return this._req(null, { tableId }, controllers.row.fetch) } async searchRows(tableId: string, searchParams: SearchFilters = {}) { if (!tableId && this.table) { - tableId = this.table._id + tableId = this.table._id! } const body = { query: searchParams, @@ -634,6 +635,18 @@ class TestConfiguration { return this._req(view, null, controllers.view.v1.save) } + async createViewV2(config?: Partial) { + if (!this.table) { + throw "Test requires table to be configured." + } + const view = { + tableId: this.table._id, + name: generator.guid(), + ...config, + } + return this._req(view, null, controllers.view.v2.save) + } + // AUTOMATION async createAutomation(config?: any) { From f7452aa7fa1c78e630c1cdfba21d098bbddbd1e1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 10:26:53 +0200 Subject: [PATCH 17/89] Extract test utils --- .../server/src/api/routes/tests/viewV2.spec.ts | 18 +++++++++--------- .../src/tests/utilities/TestConfiguration.ts | 6 ++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index a532779fcf..31b843c597 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -35,13 +35,6 @@ describe("/v2/views", () => { table = await config.createTable(priceTable()) }) - const getView = (viewId: string) => { - return request - .get(`/api/v2/views/${viewId}`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - } - describe("fetch", () => { const views: ViewV2[] = [] @@ -98,6 +91,13 @@ describe("/v2/views", () => { }) describe("getView", () => { + const getView = (viewId: string) => { + return request + .get(`/api/v2/views/${viewId}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + } + let view: ViewV2 beforeAll(async () => { view = await config.createViewV2() @@ -155,14 +155,14 @@ describe("/v2/views", () => { }) it("can delete an existing view", async () => { - await getView(view._id!).expect(200) + await config.getViewV2(view._id!).expect(200) await request .delete(`/api/v2/views/${view._id}`) .set(config.defaultHeaders()) .expect(204) - await getView(view._id!).expect(404) + await config.getViewV2(view._id!).expect(404) }) }) }) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index a02fd39462..0ff3cf2fdf 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -647,6 +647,12 @@ class TestConfiguration { return this._req(view, null, controllers.view.v2.save) } + getViewV2(viewId: string): supertest.Test { + return this.request!.get(`/api/v2/views/${viewId}`) + .set(this.defaultHeaders()) + .expect("Content-Type", /json/) + } + // AUTOMATION async createAutomation(config?: any) { From 5731b260791e06b66ee1afcdae923b3a94e7f463 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 10:30:15 +0200 Subject: [PATCH 18/89] Namespacing --- .../src/api/routes/tests/viewV2.spec.ts | 12 +++---- .../src/tests/utilities/TestConfiguration.ts | 35 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 31b843c597..3ae6445393 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -41,7 +41,7 @@ describe("/v2/views", () => { beforeAll(async () => { await config.createTable(priceTable()) for (let id = 0; id < 10; id++) { - views.push(await config.createViewV2()) + views.push(await config.api.viewV2.create()) } }) @@ -62,7 +62,7 @@ describe("/v2/views", () => { const newTable = await config.createTable(priceTable()) const newViews = [] for (let id = 0; id < 5; id++) { - newViews.push(await config.createViewV2({ tableId: newTable._id })) + newViews.push(await config.api.viewV2.create({ tableId: newTable._id })) } const res = await request @@ -100,7 +100,7 @@ describe("/v2/views", () => { let view: ViewV2 beforeAll(async () => { - view = await config.createViewV2() + view = await config.api.viewV2.create() }) it("can fetch the expected view", async () => { @@ -151,18 +151,18 @@ describe("/v2/views", () => { beforeAll(async () => { await config.createTable(priceTable()) - view = await config.createViewV2() + view = await config.api.viewV2.create() }) it("can delete an existing view", async () => { - await config.getViewV2(view._id!).expect(200) + await config.api.viewV2.get(view._id!).expect(200) await request .delete(`/api/v2/views/${view._id}`) .set(config.defaultHeaders()) .expect(204) - await config.getViewV2(view._id!).expect(404) + await config.api.viewV2.get(view._id!).expect(404) }) }) }) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 0ff3cf2fdf..e4f3cf31d1 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -635,22 +635,25 @@ class TestConfiguration { return this._req(view, null, controllers.view.v1.save) } - async createViewV2(config?: Partial) { - if (!this.table) { - throw "Test requires table to be configured." - } - const view = { - tableId: this.table._id, - name: generator.guid(), - ...config, - } - return this._req(view, null, controllers.view.v2.save) - } - - getViewV2(viewId: string): supertest.Test { - return this.request!.get(`/api/v2/views/${viewId}`) - .set(this.defaultHeaders()) - .expect("Content-Type", /json/) + api = { + viewV2: { + create: async (config?: Partial) => { + if (!this.table) { + throw "Test requires table to be configured." + } + const view = { + tableId: this.table._id, + name: generator.guid(), + ...config, + } + return this._req(view, null, controllers.view.v2.save) + }, + get: (viewId: string): supertest.Test => { + return this.request!.get(`/api/v2/views/${viewId}`) + .set(this.defaultHeaders()) + .expect("Content-Type", /json/) + }, + }, } // AUTOMATION From 160d949423f69b3bf44dff06c827430428ad7fec Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 10:36:01 +0200 Subject: [PATCH 19/89] Catch 404s --- packages/server/src/sdk/app/views/index.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index a1cffeb2b5..60f886c2e2 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -35,8 +35,16 @@ export async function findByTable(tableId: string): Promise { export async function get(viewId: string): Promise { const db = context.getAppDB() - const result = await db.get(viewId) - return result + try { + const result = await db.get(viewId) + return result + } catch (err: any) { + if (err.status === 404) { + return undefined + } + + throw err + } } export async function save(view: ViewV2): Promise { From 7140df6ed34e4cc978aa4edc80ebb41831ae82fa Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 12:06:35 +0200 Subject: [PATCH 20/89] Better typing responses --- .../src/api/controllers/view/viewsV2.ts | 23 +++++++++++++------ .../src/api/routes/tests/viewV2.spec.ts | 10 ++++---- .../src/tests/utilities/TestConfiguration.ts | 3 ++- packages/types/src/api/web/app/view.ts | 5 ++++ 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 65340682d8..8cb0a14150 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -1,7 +1,13 @@ import sdk from "../../../sdk" -import { Ctx, FetchViewResponse, ViewResponse, ViewV2 } from "@budibase/types" +import { + CreateViewRequest, + Ctx, + FetchViewResponse, + ViewResponse, + ViewV2, +} from "@budibase/types" -export async function fetch(ctx: Ctx<{}, FetchViewResponse>) { +export async function fetch(ctx: Ctx) { const { tableId } = ctx.query if (tableId && typeof tableId !== "string") { @@ -15,7 +21,7 @@ export async function fetch(ctx: Ctx<{}, FetchViewResponse>) { ctx.body = { views } } -export async function find(ctx: Ctx<{}, ViewResponse>) { +export async function find(ctx: Ctx) { const { viewId } = ctx.params const view = await sdk.views.get(viewId) @@ -28,16 +34,19 @@ export async function find(ctx: Ctx<{}, ViewResponse>) { } } -export async function save(ctx: Ctx) { +export async function save(ctx: Ctx) { const view = ctx.request.body + const result = await sdk.views.save(view) ctx.body = { - ...view, - ...result, + data: { + ...view, + ...result, + }, } } -export async function remove(ctx: Ctx<{}, {}>) { +export async function remove(ctx: Ctx) { const { viewId } = ctx.params const doc = await sdk.views.get(viewId) if (!doc) { diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 3ae6445393..b66d28eee6 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -136,12 +136,14 @@ describe("/v2/views", () => { .expect("Content-Type", /json/) .expect(200) expect(res.status).toBe(200) - expect(res.body._id).toBeDefined() + expect(res.body.data._id).toBeDefined() expect(res.body).toEqual({ - ...newView, - _id: expect.any(String), - _rev: expect.any(String), + data: { + ...newView, + _id: expect.any(String), + _rev: expect.any(String), + }, }) }) }) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index e4f3cf31d1..a3340f094c 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -646,7 +646,8 @@ class TestConfiguration { name: generator.guid(), ...config, } - return this._req(view, null, controllers.view.v2.save) + const result = await this._req(view, null, controllers.view.v2.save) + return result.data }, get: (viewId: string): supertest.Test => { return this.request!.get(`/api/v2/views/${viewId}`) diff --git a/packages/types/src/api/web/app/view.ts b/packages/types/src/api/web/app/view.ts index 524783d090..6ebe8b8a36 100644 --- a/packages/types/src/api/web/app/view.ts +++ b/packages/types/src/api/web/app/view.ts @@ -7,3 +7,8 @@ export interface FetchViewResponse { export interface ViewResponse { data: ViewV2 } + +export type CreateViewRequest = Omit< + ViewV2, + "_id" | "_rev" | "createdAt" | "updatedAt" +> From 1e6a65d4e909fceadfc81d4a1e8443944efc5994 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 12:56:24 +0200 Subject: [PATCH 21/89] Extract viewapi to its own file --- .../src/api/routes/tests/viewV2.spec.ts | 3 +- .../src/tests/utilities/TestConfiguration.ts | 27 ++-------- .../server/src/tests/utilities/api/base.ts | 17 ++++++ .../server/src/tests/utilities/api/index.ts | 10 ++++ .../server/src/tests/utilities/api/viewV2.ts | 52 +++++++++++++++++++ 5 files changed, 85 insertions(+), 24 deletions(-) create mode 100644 packages/server/src/tests/utilities/api/base.ts create mode 100644 packages/server/src/tests/utilities/api/index.ts create mode 100644 packages/server/src/tests/utilities/api/viewV2.ts diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index b66d28eee6..52c4b3d55b 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -26,13 +26,12 @@ function priceTable(): Table { describe("/v2/views", () => { const request = setup.getRequest() const config = setup.getConfig() - let table: Table afterAll(setup.afterAll) beforeAll(async () => { await config.init() - table = await config.createTable(priceTable()) + await config.createTable(priceTable()) }) describe("fetch", () => { diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index a3340f094c..3aa70aae2f 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -54,6 +54,8 @@ import { } from "@budibase/types" import { BUILTIN_ROLE_IDS } from "@budibase/backend-core/src/security/roles" +import API from "./api" + type DefaultUserValues = { globalUserId: string email: string @@ -80,6 +82,7 @@ class TestConfiguration { datasource: any tenantId?: string defaultUserValues: DefaultUserValues + api: API constructor(openServer = true) { if (openServer) { @@ -95,6 +98,8 @@ class TestConfiguration { this.appId = null this.allApps = [] this.defaultUserValues = this.populateDefaultUserValues() + + this.api = new API(this) } populateDefaultUserValues(): DefaultUserValues { @@ -635,28 +640,6 @@ class TestConfiguration { return this._req(view, null, controllers.view.v1.save) } - api = { - viewV2: { - create: async (config?: Partial) => { - if (!this.table) { - throw "Test requires table to be configured." - } - const view = { - tableId: this.table._id, - name: generator.guid(), - ...config, - } - const result = await this._req(view, null, controllers.view.v2.save) - return result.data - }, - get: (viewId: string): supertest.Test => { - return this.request!.get(`/api/v2/views/${viewId}`) - .set(this.defaultHeaders()) - .expect("Content-Type", /json/) - }, - }, - } - // AUTOMATION async createAutomation(config?: any) { diff --git a/packages/server/src/tests/utilities/api/base.ts b/packages/server/src/tests/utilities/api/base.ts new file mode 100644 index 0000000000..34120277c0 --- /dev/null +++ b/packages/server/src/tests/utilities/api/base.ts @@ -0,0 +1,17 @@ +import TestConfiguration from "../TestConfiguration" +import { SuperTest, Test } from "supertest" + +export interface TestAPIOpts { + headers?: any + status?: number +} + +export abstract class TestAPI { + config: TestConfiguration + request: SuperTest + + protected constructor(config: TestConfiguration) { + this.config = config + this.request = config.request! + } +} diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts new file mode 100644 index 0000000000..f759abd47c --- /dev/null +++ b/packages/server/src/tests/utilities/api/index.ts @@ -0,0 +1,10 @@ +import TestConfiguration from "../TestConfiguration" +import { ViewV2API } from "./viewV2" + +export default class API { + viewV2: ViewV2API + + constructor(config: TestConfiguration) { + this.viewV2 = new ViewV2API(config) + } +} diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts new file mode 100644 index 0000000000..1927c1a8bc --- /dev/null +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -0,0 +1,52 @@ +import { Account, AccountMetadata, ViewV2 } from "@budibase/types" +import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" +import { generator } from "@budibase/backend-core/tests" +import supertest from "supertest" + +export class ViewV2API extends TestAPI { + constructor(config: TestConfiguration) { + super(config) + } + + create = async (viewData?: Partial) => { + if (!this.config.table) { + throw "Test requires table to be configured." + } + const view = { + tableId: this.config.table._id, + name: generator.guid(), + ...viewData, + } + const result = await this.request + .post(`/api/v2/views`) + .send(view) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + return result.body.data as ViewV2 + } + get = (viewId: string): supertest.Test => { + return this.request + .get(`/api/v2/views/${viewId}`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + } + // }, + // } + // saveMetadata = async (account: Account) => { + // const res = await this.request + // .put(`/api/system/accounts/${account.accountId}/metadata`) + // .send(account) + // .set(this.config.internalAPIHeaders()) + // .expect("Content-Type", /json/) + // .expect(200) + // return res.body as AccountMetadata + // } + + // destroyMetadata = (accountId: string) => { + // return this.request + // .del(`/api/system/accounts/${accountId}/metadata`) + // .set(this.config.internalAPIHeaders()) + // } +} From e71d883dfd2469bb470da2559077ffde4358926e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 12:58:48 +0200 Subject: [PATCH 22/89] Change status codes --- packages/server/src/api/controllers/view/viewsV2.ts | 6 +++--- packages/server/src/api/routes/tests/viewV2.spec.ts | 4 +--- packages/server/src/api/routes/view.ts | 2 +- packages/server/src/sdk/app/views/index.ts | 2 +- packages/server/src/tests/utilities/api/viewV2.ts | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 8cb0a14150..1b6b7a5f31 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -4,7 +4,6 @@ import { Ctx, FetchViewResponse, ViewResponse, - ViewV2, } from "@budibase/types" export async function fetch(ctx: Ctx) { @@ -34,10 +33,11 @@ export async function find(ctx: Ctx) { } } -export async function save(ctx: Ctx) { +export async function create(ctx: Ctx) { const view = ctx.request.body - const result = await sdk.views.save(view) + const result = await sdk.views.create(view) + ctx.status = 201 ctx.body = { data: { ...view, diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 52c4b3d55b..bc5c13297a 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -133,9 +133,7 @@ describe("/v2/views", () => { .send(newView) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(res.status).toBe(200) - expect(res.body.data._id).toBeDefined() + .expect(201) expect(res.body).toEqual({ data: { diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 5fa4fe17f0..155fce1aad 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -21,7 +21,7 @@ router .post( "/api/v2/views", authorized(permissions.BUILDER), - viewController.v2.save + viewController.v2.create ) .delete( `/api/v2/views/:viewId`, diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 60f886c2e2..fbac7b757a 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -47,7 +47,7 @@ export async function get(viewId: string): Promise { } } -export async function save(view: ViewV2): Promise { +export async function create(view: ViewV2): Promise { const db = context.getAppDB() const response = await db.put( diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 1927c1a8bc..5c838e50fb 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -23,7 +23,7 @@ export class ViewV2API extends TestAPI { .send(view) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(201) return result.body.data as ViewV2 } get = (viewId: string): supertest.Test => { From 97a538f5db99c44e29e4cc301dc271bea6f1116f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 14 Jul 2023 10:26:22 +0200 Subject: [PATCH 23/89] Basic search --- .../server/src/api/controllers/row/index.ts | 14 +++++ packages/server/src/api/routes/row.ts | 5 ++ .../server/src/api/routes/tests/row.spec.ts | 56 ++++++++++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index aa3aa3e21c..46c933da07 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -146,6 +146,20 @@ export async function search(ctx: any) { }) } +export async function searchView(ctx: any) { + const { viewId } = ctx.params + const view = await sdk.views.get(viewId) + const tableId = view.tableId + + ctx.status = 200 + ctx.body = await quotas.addQuery( + () => sdk.rows.search({ tableId, query: {} }), + { + datasourceId: tableId, + } + ) +} + export async function validate(ctx: Ctx) { const tableId = utils.getTableId(ctx) // external tables are hard to validate currently diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index 6d1cd206c6..6b393051c6 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -146,6 +146,11 @@ router authorized(PermissionType.TABLE, PermissionLevel.READ), rowController.search ) + .get( + "/api/views/v2/:viewId/search", + authorized(PermissionType.VIEW, PermissionLevel.READ), + rowController.searchView + ) /** * @api {post} /api/:tableId/rows Creates a new row * @apiName Creates a new row diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 8e99c30246..7a5b85bd83 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -14,8 +14,9 @@ import { Row, Table, FieldType, + ViewV2, } from "@budibase/types" -import { structures } from "@budibase/backend-core/tests" +import { generator, structures } from "@budibase/backend-core/tests" describe("/rows", () => { let request = setup.getRequest() @@ -685,4 +686,57 @@ describe("/rows", () => { expect(row._id).toEqual(existing._id) }) }) + + describe.only("view search", () => { + function priceTable(): Table { + return { + name: "table", + type: "table", + schema: { + Price: { + type: FieldType.NUMBER, + name: "Price", + constraints: {}, + }, + Category: { + type: FieldType.STRING, + name: "Category", + constraints: { + type: "string", + }, + }, + }, + } + } + + it("returns table rows from view", async () => { + const table = await config.createTable(priceTable()) + const rows = await Promise.all( + Array(10) + .fill({}) + .map(() => config.createRow({ tableId: table._id })) + ) + const view: ViewV2 = { + name: generator.guid(), + tableId: table._id!, + } + const createViewResponse = await request + .post(`/api/views/v2`) + .send(view) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + const response = await request + .get(`/api/views/v2/${createViewResponse.body._id}/search`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(response.body.rows).toHaveLength(10) + expect(response.body).toEqual({ + rows: expect.arrayContaining(rows.map(expect.objectContaining)), + }) + }) + }) }) From 9f36bdc6c18f74f7855b7b1083e27c2bc5f57ae2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 11:00:29 +0200 Subject: [PATCH 24/89] Reuse config --- .../server/src/api/routes/tests/row.spec.ts | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 7a5b85bd83..35dc453ed9 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -687,7 +687,7 @@ describe("/rows", () => { }) }) - describe.only("view search", () => { + describe("view search", () => { function priceTable(): Table { return { name: "table", @@ -711,24 +711,14 @@ describe("/rows", () => { it("returns table rows from view", async () => { const table = await config.createTable(priceTable()) - const rows = await Promise.all( - Array(10) - .fill({}) - .map(() => config.createRow({ tableId: table._id })) - ) - const view: ViewV2 = { - name: generator.guid(), - tableId: table._id!, - } - const createViewResponse = await request - .post(`/api/views/v2`) - .send(view) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + const rows = [] + for (let i = 0; i < 10; i++) + rows.push(await config.createRow({ tableId: table._id })) + + const createViewResponse = await config.api.viewV2.create() const response = await request - .get(`/api/views/v2/${createViewResponse.body._id}/search`) + .get(`/api/views/v2/${createViewResponse._id}/search`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) From a42f6753dec40d4e6fa00e36a236a6a53727046d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 13:09:58 +0200 Subject: [PATCH 25/89] Clean --- packages/server/src/tests/utilities/TestConfiguration.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 3aa70aae2f..7cf60e6cf7 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -50,7 +50,6 @@ import { SearchFilters, UserRoles, Automation, - ViewV2, } from "@budibase/types" import { BUILTIN_ROLE_IDS } from "@budibase/backend-core/src/security/roles" @@ -248,7 +247,7 @@ class TestConfiguration { const db = tenancy.getTenantDB(this.getTenantId()) let existing try { - existing = await db.get(id) + existing = await db.get(id) } catch (err) { existing = { email } } @@ -466,7 +465,7 @@ class TestConfiguration { async generateApiKey(userId = this.defaultUserValues.globalUserId) { const db = tenancy.getTenantDB(this.getTenantId()) const id = dbCore.generateDevInfoID(userId) - let devInfo + let devInfo: any try { devInfo = await db.get(id) } catch (err) { From 891ba4148bbc3e1a0855471e10ad5c6962af8e4e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 13:10:32 +0200 Subject: [PATCH 26/89] Clean --- packages/server/src/api/routes/tests/row.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 35dc453ed9..8b965570a3 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -14,9 +14,8 @@ import { Row, Table, FieldType, - ViewV2, } from "@budibase/types" -import { generator, structures } from "@budibase/backend-core/tests" +import { structures } from "@budibase/backend-core/tests" describe("/rows", () => { let request = setup.getRequest() From d981ad039d30f30d68a6a08450e1b03dbb3371b6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 13:40:34 +0200 Subject: [PATCH 27/89] Typings --- packages/server/src/api/controllers/row/external.ts | 2 ++ packages/server/src/api/controllers/row/index.ts | 11 +++++++---- packages/server/src/api/controllers/row/internal.ts | 3 +-- packages/server/src/sdk/app/rows/search.ts | 4 +++- packages/types/src/api/web/app/index.ts | 1 + packages/types/src/api/web/app/rows.ts | 3 +++ 6 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 packages/types/src/api/web/app/rows.ts diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index 2122ada068..6bae6afd48 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -73,9 +73,11 @@ export async function patch(ctx: UserCtx) { row: inputs, }) const row = await getRow(tableId, id, { relationships: true }) + const table = await sdk.tables.getTable(tableId) return { ...response, row, + table, } } diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 46c933da07..d3c4215182 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -2,7 +2,7 @@ import { quotas } from "@budibase/pro" import * as internal from "./internal" import * as external from "./external" import { isExternalTable } from "../../../integrations/utils" -import { Ctx } from "@budibase/types" +import { Ctx, SearchResponse } from "@budibase/types" import * as utils from "./utils" import { gridSocket } from "../../../websockets" import sdk from "../../../sdk" @@ -114,7 +114,7 @@ export async function destroy(ctx: any) { response = rows for (let row of rows) { ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row) - gridSocket?.emitRowDeletion(ctx, row._id) + gridSocket?.emitRowDeletion(ctx, row._id!) } } else { let resp = await quotas.addQuery(() => pickApi(tableId).destroy(ctx), { @@ -124,7 +124,7 @@ export async function destroy(ctx: any) { response = resp.response row = resp.row ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row) - gridSocket?.emitRowDeletion(ctx, row._id) + gridSocket?.emitRowDeletion(ctx, row._id!) } ctx.status = 200 // for automations include the row that was deleted @@ -146,9 +146,12 @@ export async function search(ctx: any) { }) } -export async function searchView(ctx: any) { +export async function searchView(ctx: Ctx) { const { viewId } = ctx.params const view = await sdk.views.get(viewId) + if (!view) { + ctx.throw(404) + } const tableId = view.tableId ctx.status = 200 diff --git a/packages/server/src/api/controllers/row/internal.ts b/packages/server/src/api/controllers/row/internal.ts index 0367346832..d56ba3f14a 100644 --- a/packages/server/src/api/controllers/row/internal.ts +++ b/packages/server/src/api/controllers/row/internal.ts @@ -19,7 +19,6 @@ import { UserCtx, LinkDocumentValue, Row, Table } from "@budibase/types" import sdk from "../../../sdk" export async function patch(ctx: UserCtx) { - const db = context.getAppDB() const inputs = ctx.request.body const tableId = inputs.tableId const isUserTable = tableId === InternalTables.USER_METADATA @@ -77,7 +76,7 @@ export async function patch(ctx: UserCtx) { // the row has been updated, need to put it into the ctx ctx.request.body = row await userController.updateMetadata(ctx) - return { row: ctx.body, table } + return { row: ctx.body as Row, table } } return finaliseRow(table, row, { diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 87a1662a54..0a4886278a 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -30,7 +30,9 @@ function pickApi(tableId: any) { return internal } -export async function search(options: SearchParams) { +export async function search(options: SearchParams): Promise<{ + rows: any[] +}> { return pickApi(options.tableId).search(options) } diff --git a/packages/types/src/api/web/app/index.ts b/packages/types/src/api/web/app/index.ts index bb9eb09abc..5d12a31903 100644 --- a/packages/types/src/api/web/app/index.ts +++ b/packages/types/src/api/web/app/index.ts @@ -1,3 +1,4 @@ export * from "./backup" export * from "./datasource" export * from "./view" +export * from "./rows" diff --git a/packages/types/src/api/web/app/rows.ts b/packages/types/src/api/web/app/rows.ts new file mode 100644 index 0000000000..d40d2ee15d --- /dev/null +++ b/packages/types/src/api/web/app/rows.ts @@ -0,0 +1,3 @@ +export interface SearchResponse { + rows: any[] +} From 63f181649af0fc70ead627bf5311b0e5725841e5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 00:17:41 +0100 Subject: [PATCH 28/89] Change url version path --- packages/server/src/api/routes/row.ts | 22 +- .../server/src/api/routes/tests/row.spec.ts | 544 +++++++++--------- 2 files changed, 283 insertions(+), 283 deletions(-) diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index 6b393051c6..e5969fc4a2 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -1,12 +1,12 @@ -import Router from "@koa/router" -import * as rowController from "../controllers/row" -import authorized from "../../middleware/authorized" -import { paramResource, paramSubResource } from "../../middleware/resourceId" -import { permissions } from "@budibase/backend-core" -import { internalSearchValidator } from "./utils/validators" -const { PermissionType, PermissionLevel } = permissions +import Router from "@koa/router"; +import * as rowController from "../controllers/row"; +import authorized from "../../middleware/authorized"; +import { paramResource, paramSubResource } from "../../middleware/resourceId"; +import { permissions } from "@budibase/backend-core"; +import { internalSearchValidator } from "./utils/validators"; +const { PermissionType, PermissionLevel } = permissions; -const router: Router = new Router() +const router: Router = new Router(); router /** @@ -147,7 +147,7 @@ router rowController.search ) .get( - "/api/views/v2/:viewId/search", + "/api/v2/views/:viewId/search", authorized(PermissionType.VIEW, PermissionLevel.READ), rowController.searchView ) @@ -266,6 +266,6 @@ router paramResource("tableId"), authorized(PermissionType.TABLE, PermissionLevel.WRITE), rowController.exportRows - ) + ); -export default router +export default router; diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 8b965570a3..f388782d2d 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1,12 +1,12 @@ -import tk from "timekeeper" -const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() -tk.freeze(timestamp) +import tk from "timekeeper"; +const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString(); +tk.freeze(timestamp); -import { outputProcessing } from "../../../utilities/rowProcessor" -import * as setup from "./utilities" -const { basicRow } = setup.structures -import { context, tenancy } from "@budibase/backend-core" -import { quotas } from "@budibase/pro" +import { outputProcessing } from "../../../utilities/rowProcessor"; +import * as setup from "./utilities"; +const { basicRow } = setup.structures; +import { context, tenancy } from "@budibase/backend-core"; +import { quotas } from "@budibase/pro"; import { QuotaUsageType, StaticQuotaName, @@ -14,39 +14,39 @@ import { Row, Table, FieldType, -} from "@budibase/types" -import { structures } from "@budibase/backend-core/tests" +} from "@budibase/types"; +import { structures } from "@budibase/backend-core/tests"; describe("/rows", () => { - let request = setup.getRequest() - let config = setup.getConfig() - let table: Table - let row: Row + let request = setup.getRequest(); + let config = setup.getConfig(); + let table: Table; + let row: Row; - afterAll(setup.afterAll) + afterAll(setup.afterAll); beforeAll(async () => { - await config.init() - }) + await config.init(); + }); beforeEach(async () => { - table = await config.createTable() - row = basicRow(table._id!) - }) + table = await config.createTable(); + row = basicRow(table._id!); + }); const loadRow = async (id: string, tbl_Id: string, status = 200) => await request .get(`/api/${tbl_Id}/rows/${id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(status) + .expect(status); const getRowUsage = async () => { const { total } = await config.doInContext(null, () => quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS) - ) - return total - } + ); + return total; + }; const getQueryUsage = async () => { const { total } = await config.doInContext(null, () => @@ -54,43 +54,43 @@ describe("/rows", () => { QuotaUsageType.MONTHLY, MonthlyQuotaName.QUERIES ) - ) - return total - } + ); + return total; + }; const assertRowUsage = async (expected: number) => { - const usage = await getRowUsage() - expect(usage).toBe(expected) - } + const usage = await getRowUsage(); + expect(usage).toBe(expected); + }; const assertQueryUsage = async (expected: number) => { - const usage = await getQueryUsage() - expect(usage).toBe(expected) - } + const usage = await getQueryUsage(); + expect(usage).toBe(expected); + }; describe("save, load, update", () => { it("returns a success message when the row is created", async () => { - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .post(`/api/${row.tableId}/rows`) .send(row) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); expect((res as any).res.statusMessage).toEqual( `${table.name} saved successfully` - ) - expect(res.body.name).toEqual("Test Contact") - expect(res.body._rev).toBeDefined() - await assertRowUsage(rowUsage + 1) - await assertQueryUsage(queryUsage + 1) - }) + ); + expect(res.body.name).toEqual("Test Contact"); + expect(res.body._rev).toBeDefined(); + await assertRowUsage(rowUsage + 1); + await assertQueryUsage(queryUsage + 1); + }); it("Increment row autoId per create row request", async () => { - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const newTable = await config.createTable({ name: "TestTableAuto", @@ -113,9 +113,9 @@ describe("/rows", () => { }, }, }, - }) + }); - const ids = [1, 2, 3] + const ids = [1, 2, 3]; // Performing several create row requests should increment the autoID fields accordingly const createRow = async (id: number) => { @@ -126,27 +126,27 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); expect((res as any).res.statusMessage).toEqual( `${newTable.name} saved successfully` - ) - expect(res.body.name).toEqual("row_" + id) - expect(res.body._rev).toBeDefined() - expect(res.body["Row ID"]).toEqual(id) - } + ); + expect(res.body.name).toEqual("row_" + id); + expect(res.body._rev).toBeDefined(); + expect(res.body["Row ID"]).toEqual(id); + }; for (let i = 0; i < ids.length; i++) { - await createRow(ids[i]) + await createRow(ids[i]); } - await assertRowUsage(rowUsage + ids.length) - await assertQueryUsage(queryUsage + ids.length) - }) + await assertRowUsage(rowUsage + ids.length); + await assertQueryUsage(queryUsage + ids.length); + }); it("updates a row successfully", async () => { - const existing = await config.createRow() - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const existing = await config.createRow(); + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .post(`/api/${table._id}/rows`) @@ -158,25 +158,25 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); expect((res as any).res.statusMessage).toEqual( `${table.name} updated successfully.` - ) - expect(res.body.name).toEqual("Updated Name") - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage + 1) - }) + ); + expect(res.body.name).toEqual("Updated Name"); + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage + 1); + }); it("should load a row", async () => { - const existing = await config.createRow() - const queryUsage = await getQueryUsage() + const existing = await config.createRow(); + const queryUsage = await getQueryUsage(); const res = await request .get(`/api/${table._id}/rows/${existing._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); expect(res.body).toEqual({ ...row, @@ -185,65 +185,65 @@ describe("/rows", () => { type: "row", createdAt: timestamp, updatedAt: timestamp, - }) - await assertQueryUsage(queryUsage + 1) - }) + }); + await assertQueryUsage(queryUsage + 1); + }); it("should list all rows for given tableId", async () => { const newRow = { tableId: table._id, name: "Second Contact", status: "new", - } - await config.createRow() - await config.createRow(newRow) - const queryUsage = await getQueryUsage() + }; + await config.createRow(); + await config.createRow(newRow); + const queryUsage = await getQueryUsage(); const res = await request .get(`/api/${table._id}/rows`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); - expect(res.body.length).toBe(2) - expect(res.body.find((r: Row) => r.name === newRow.name)).toBeDefined() - expect(res.body.find((r: Row) => r.name === row.name)).toBeDefined() - await assertQueryUsage(queryUsage + 1) - }) + expect(res.body.length).toBe(2); + expect(res.body.find((r: Row) => r.name === newRow.name)).toBeDefined(); + expect(res.body.find((r: Row) => r.name === row.name)).toBeDefined(); + await assertQueryUsage(queryUsage + 1); + }); it("load should return 404 when row does not exist", async () => { - await config.createRow() - const queryUsage = await getQueryUsage() + await config.createRow(); + const queryUsage = await getQueryUsage(); await request .get(`/api/${table._id}/rows/not-a-valid-id`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(404) - await assertQueryUsage(queryUsage) // no change - }) + .expect(404); + await assertQueryUsage(queryUsage); // no change + }); it("row values are coerced", async () => { const str = { type: FieldType.STRING, name: "str", constraints: { type: "string", presence: false }, - } + }; const attachment = { type: FieldType.ATTACHMENT, name: "attachment", constraints: { type: "array", presence: false }, - } + }; const bool = { type: FieldType.BOOLEAN, name: "boolean", constraints: { type: "boolean", presence: false }, - } + }; const number = { type: FieldType.NUMBER, name: "str", constraints: { type: "number", presence: false }, - } + }; const datetime = { type: FieldType.DATETIME, name: "datetime", @@ -252,7 +252,7 @@ describe("/rows", () => { presence: false, datetime: { earliest: "", latest: "" }, }, - } + }; const arrayField = { type: FieldType.ARRAY, constraints: { @@ -262,7 +262,7 @@ describe("/rows", () => { }, name: "Sample Tags", sortable: false, - } + }; const optsField = { fieldName: "Sample Opts", name: "Sample Opts", @@ -309,7 +309,7 @@ describe("/rows", () => { optsFieldNull: optsField, optsFieldStrKnown: optsField, }, - }) + }); row = { name: "Test Row", @@ -344,54 +344,54 @@ describe("/rows", () => { optsFieldUndefined: undefined, optsFieldNull: null, optsFieldStrKnown: "Alpha", - } + }; - const createdRow = await config.createRow(row) - const id = createdRow._id! + const createdRow = await config.createRow(row); + const id = createdRow._id!; - const saved = (await loadRow(id, table._id!)).body + const saved = (await loadRow(id, table._id!)).body; - expect(saved.stringUndefined).toBe(undefined) - expect(saved.stringNull).toBe("") - expect(saved.stringString).toBe("i am a string") - expect(saved.numberEmptyString).toBe(null) - expect(saved.numberNull).toBe(null) - expect(saved.numberUndefined).toBe(undefined) - expect(saved.numberString).toBe(123) - expect(saved.numberNumber).toBe(123) - expect(saved.datetimeEmptyString).toBe(null) - expect(saved.datetimeNull).toBe(null) - expect(saved.datetimeUndefined).toBe(undefined) + expect(saved.stringUndefined).toBe(undefined); + expect(saved.stringNull).toBe(""); + expect(saved.stringString).toBe("i am a string"); + expect(saved.numberEmptyString).toBe(null); + expect(saved.numberNull).toBe(null); + expect(saved.numberUndefined).toBe(undefined); + expect(saved.numberString).toBe(123); + expect(saved.numberNumber).toBe(123); + expect(saved.datetimeEmptyString).toBe(null); + expect(saved.datetimeNull).toBe(null); + expect(saved.datetimeUndefined).toBe(undefined); expect(saved.datetimeString).toBe( new Date(row.datetimeString).toISOString() - ) - expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString()) - expect(saved.boolNull).toBe(null) - expect(saved.boolEmpty).toBe(null) - expect(saved.boolUndefined).toBe(undefined) - expect(saved.boolString).toBe(true) - expect(saved.boolBool).toBe(true) - expect(saved.attachmentNull).toEqual([]) - expect(saved.attachmentUndefined).toBe(undefined) - expect(saved.attachmentEmpty).toEqual([]) - expect(saved.attachmentEmptyArrayStr).toEqual([]) - expect(saved.arrayFieldEmptyArrayStr).toEqual([]) - expect(saved.arrayFieldNull).toEqual([]) - expect(saved.arrayFieldUndefined).toEqual(undefined) - expect(saved.optsFieldEmptyStr).toEqual(null) - expect(saved.optsFieldUndefined).toEqual(undefined) - expect(saved.optsFieldNull).toEqual(null) - expect(saved.arrayFieldArrayStrKnown).toEqual(["One"]) - expect(saved.optsFieldStrKnown).toEqual("Alpha") - }) - }) + ); + expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString()); + expect(saved.boolNull).toBe(null); + expect(saved.boolEmpty).toBe(null); + expect(saved.boolUndefined).toBe(undefined); + expect(saved.boolString).toBe(true); + expect(saved.boolBool).toBe(true); + expect(saved.attachmentNull).toEqual([]); + expect(saved.attachmentUndefined).toBe(undefined); + expect(saved.attachmentEmpty).toEqual([]); + expect(saved.attachmentEmptyArrayStr).toEqual([]); + expect(saved.arrayFieldEmptyArrayStr).toEqual([]); + expect(saved.arrayFieldNull).toEqual([]); + expect(saved.arrayFieldUndefined).toEqual(undefined); + expect(saved.optsFieldEmptyStr).toEqual(null); + expect(saved.optsFieldUndefined).toEqual(undefined); + expect(saved.optsFieldNull).toEqual(null); + expect(saved.arrayFieldArrayStrKnown).toEqual(["One"]); + expect(saved.optsFieldStrKnown).toEqual("Alpha"); + }); + }); describe("patch", () => { it("should update only the fields that are supplied", async () => { - const existing = await config.createRow() + const existing = await config.createRow(); - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .patch(`/api/${table._id}/rows`) @@ -403,26 +403,26 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); expect((res as any).res.statusMessage).toEqual( `${table.name} updated successfully.` - ) - expect(res.body.name).toEqual("Updated Name") - expect(res.body.description).toEqual(existing.description) + ); + expect(res.body.name).toEqual("Updated Name"); + expect(res.body.description).toEqual(existing.description); - const savedRow = await loadRow(res.body._id, table._id!) + const savedRow = await loadRow(res.body._id, table._id!); - expect(savedRow.body.description).toEqual(existing.description) - expect(savedRow.body.name).toEqual("Updated Name") - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage + 2) // account for the second load - }) + expect(savedRow.body.description).toEqual(existing.description); + expect(savedRow.body.name).toEqual("Updated Name"); + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage + 2); // account for the second load + }); it("should throw an error when given improper types", async () => { - const existing = await config.createRow() - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const existing = await config.createRow(); + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); await request .patch(`/api/${table._id}/rows`) @@ -433,18 +433,18 @@ describe("/rows", () => { name: 1, }) .set(config.defaultHeaders()) - .expect(400) + .expect(400); - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage) - }) - }) + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage); + }); + }); describe("destroy", () => { it("should be able to delete a row", async () => { - const createdRow = await config.createRow(row) - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const createdRow = await config.createRow(row); + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .delete(`/api/${table._id}/rows`) @@ -453,55 +453,55 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(res.body[0]._id).toEqual(createdRow._id) - await assertRowUsage(rowUsage - 1) - await assertQueryUsage(queryUsage + 1) - }) - }) + .expect(200); + expect(res.body[0]._id).toEqual(createdRow._id); + await assertRowUsage(rowUsage - 1); + await assertQueryUsage(queryUsage + 1); + }); + }); describe("validate", () => { it("should return no errors on valid row", async () => { - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .post(`/api/${table._id}/rows/validate`) .send({ name: "ivan" }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); - expect(res.body.valid).toBe(true) - expect(Object.keys(res.body.errors)).toEqual([]) - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage) - }) + expect(res.body.valid).toBe(true); + expect(Object.keys(res.body.errors)).toEqual([]); + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage); + }); it("should errors on invalid row", async () => { - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .post(`/api/${table._id}/rows/validate`) .send({ name: 1 }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); - expect(res.body.valid).toBe(false) - expect(Object.keys(res.body.errors)).toEqual(["name"]) - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage) - }) - }) + expect(res.body.valid).toBe(false); + expect(Object.keys(res.body.errors)).toEqual(["name"]); + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage); + }); + }); describe("bulkDelete", () => { it("should be able to delete a bulk set of rows", async () => { - const row1 = await config.createRow() - const row2 = await config.createRow() - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const row1 = await config.createRow(); + const row2 = await config.createRow(); + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .delete(`/api/${table._id}/rows`) @@ -510,115 +510,115 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); - expect(res.body.length).toEqual(2) - await loadRow(row1._id!, table._id!, 404) - await assertRowUsage(rowUsage - 2) - await assertQueryUsage(queryUsage + 1) - }) - }) + expect(res.body.length).toEqual(2); + await loadRow(row1._id!, table._id!, 404); + await assertRowUsage(rowUsage - 2); + await assertQueryUsage(queryUsage + 1); + }); + }); describe("fetchView", () => { it("should be able to fetch tables contents via 'view'", async () => { - const row = await config.createRow() - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const row = await config.createRow(); + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .get(`/api/views/${table._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(res.body.length).toEqual(1) - expect(res.body[0]._id).toEqual(row._id) - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage + 1) - }) + .expect(200); + expect(res.body.length).toEqual(1); + expect(res.body[0]._id).toEqual(row._id); + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage + 1); + }); it("should throw an error if view doesn't exist", async () => { - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); await request .get(`/api/views/derp`) .set(config.defaultHeaders()) - .expect(404) + .expect(404); - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage) - }) + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage); + }); it("should be able to run on a view", async () => { - const view = await config.createView() - const row = await config.createRow() - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + const view = await config.createView(); + const row = await config.createRow(); + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); const res = await request .get(`/api/views/${view.name}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(res.body.length).toEqual(1) - expect(res.body[0]._id).toEqual(row._id) + .expect(200); + expect(res.body.length).toEqual(1); + expect(res.body[0]._id).toEqual(row._id); - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage + 1) - }) - }) + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage + 1); + }); + }); describe("fetchEnrichedRows", () => { it("should allow enriching some linked rows", async () => { const { table, firstRow, secondRow } = await tenancy.doInTenant( config.getTenantId(), async () => { - const table = await config.createLinkedTable() + const table = await config.createLinkedTable(); const firstRow = await config.createRow({ name: "Test Contact", description: "original description", tableId: table._id, - }) + }); const secondRow = await config.createRow({ name: "Test 2", description: "og desc", link: [{ _id: firstRow._id }], tableId: table._id, - }) - return { table, firstRow, secondRow } + }); + return { table, firstRow, secondRow }; } - ) - const rowUsage = await getRowUsage() - const queryUsage = await getQueryUsage() + ); + const rowUsage = await getRowUsage(); + const queryUsage = await getQueryUsage(); // test basic enrichment const resBasic = await request .get(`/api/${table._id}/rows/${secondRow._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(resBasic.body.link[0]._id).toBe(firstRow._id) - expect(resBasic.body.link[0].primaryDisplay).toBe("Test Contact") + .expect(200); + expect(resBasic.body.link[0]._id).toBe(firstRow._id); + expect(resBasic.body.link[0].primaryDisplay).toBe("Test Contact"); // test full enrichment const resEnriched = await request .get(`/api/${table._id}/${secondRow._id}/enrich`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(resEnriched.body.link.length).toBe(1) - expect(resEnriched.body.link[0]._id).toBe(firstRow._id) - expect(resEnriched.body.link[0].name).toBe("Test Contact") - expect(resEnriched.body.link[0].description).toBe("original description") - await assertRowUsage(rowUsage) - await assertQueryUsage(queryUsage + 2) - }) - }) + .expect(200); + expect(resEnriched.body.link.length).toBe(1); + expect(resEnriched.body.link[0]._id).toBe(firstRow._id); + expect(resEnriched.body.link[0].name).toBe("Test Contact"); + expect(resEnriched.body.link[0].description).toBe("original description"); + await assertRowUsage(rowUsage); + await assertQueryUsage(queryUsage + 2); + }); + }); describe("attachments", () => { it("should allow enriching attachment rows", async () => { - const table = await config.createAttachmentTable() - const attachmentId = `${structures.uuid()}.csv` + const table = await config.createAttachmentTable(); + const attachmentId = `${structures.uuid()}.csv`; const row = await config.createRow({ name: "test", description: "test", @@ -628,22 +628,22 @@ describe("/rows", () => { }, ], tableId: table._id, - }) + }); // the environment needs configured for this await setup.switchToSelfHosted(async () => { context.doInAppContext(config.getAppId(), async () => { - const enriched = await outputProcessing(table, [row]) + const enriched = await outputProcessing(table, [row]); expect((enriched as Row[])[0].attachment[0].url).toBe( `/files/signed/prod-budi-app-assets/${config.getProdAppId()}/attachments/${attachmentId}` - ) - }) - }) - }) - }) + ); + }); + }); + }); + }); describe("exportData", () => { it("should allow exporting all columns", async () => { - const existing = await config.createRow() + const existing = await config.createRow(); const res = await request .post(`/api/${table._id}/rows/exportRows?format=json`) .set(config.defaultHeaders()) @@ -651,22 +651,22 @@ describe("/rows", () => { rows: [existing._id], }) .expect("Content-Type", /json/) - .expect(200) - const results = JSON.parse(res.text) - expect(results.length).toEqual(1) - const row = results[0] + .expect(200); + const results = JSON.parse(res.text); + expect(results.length).toEqual(1); + const row = results[0]; // Ensure all original columns were exported expect(Object.keys(row).length).toBeGreaterThanOrEqual( Object.keys(existing).length - ) - Object.keys(existing).forEach(key => { - expect(row[key]).toEqual(existing[key]) - }) - }) + ); + Object.keys(existing).forEach((key) => { + expect(row[key]).toEqual(existing[key]); + }); + }); it("should allow exporting only certain columns", async () => { - const existing = await config.createRow() + const existing = await config.createRow(); const res = await request .post(`/api/${table._id}/rows/exportRows?format=json`) .set(config.defaultHeaders()) @@ -675,16 +675,16 @@ describe("/rows", () => { columns: ["_id"], }) .expect("Content-Type", /json/) - .expect(200) - const results = JSON.parse(res.text) - expect(results.length).toEqual(1) - const row = results[0] + .expect(200); + const results = JSON.parse(res.text); + expect(results.length).toEqual(1); + const row = results[0]; // Ensure only the _id column was exported - expect(Object.keys(row).length).toEqual(1) - expect(row._id).toEqual(existing._id) - }) - }) + expect(Object.keys(row).length).toEqual(1); + expect(row._id).toEqual(existing._id); + }); + }); describe("view search", () => { function priceTable(): Table { @@ -705,27 +705,27 @@ describe("/rows", () => { }, }, }, - } + }; } it("returns table rows from view", async () => { - const table = await config.createTable(priceTable()) - const rows = [] + const table = await config.createTable(priceTable()); + const rows = []; for (let i = 0; i < 10; i++) - rows.push(await config.createRow({ tableId: table._id })) + rows.push(await config.createRow({ tableId: table._id })); - const createViewResponse = await config.api.viewV2.create() + const createViewResponse = await config.api.viewV2.create(); const response = await request - .get(`/api/views/v2/${createViewResponse._id}/search`) + .get(`/api/v2/views/${createViewResponse._id}/search`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) + .expect(200); - expect(response.body.rows).toHaveLength(10) + expect(response.body.rows).toHaveLength(10); expect(response.body).toEqual({ rows: expect.arrayContaining(rows.map(expect.objectContaining)), - }) - }) - }) -}) + }); + }); + }); +}); From b29e54e84caa0beac4d48a36c6628c8efb225220 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 09:58:50 +0200 Subject: [PATCH 29/89] Lint --- packages/server/src/api/routes/row.ts | 20 +- .../server/src/api/routes/tests/row.spec.ts | 546 +++++++++--------- 2 files changed, 283 insertions(+), 283 deletions(-) diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index e5969fc4a2..5fdc02b7a7 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -1,12 +1,12 @@ -import Router from "@koa/router"; -import * as rowController from "../controllers/row"; -import authorized from "../../middleware/authorized"; -import { paramResource, paramSubResource } from "../../middleware/resourceId"; -import { permissions } from "@budibase/backend-core"; -import { internalSearchValidator } from "./utils/validators"; -const { PermissionType, PermissionLevel } = permissions; +import Router from "@koa/router" +import * as rowController from "../controllers/row" +import authorized from "../../middleware/authorized" +import { paramResource, paramSubResource } from "../../middleware/resourceId" +import { permissions } from "@budibase/backend-core" +import { internalSearchValidator } from "./utils/validators" +const { PermissionType, PermissionLevel } = permissions -const router: Router = new Router(); +const router: Router = new Router() router /** @@ -266,6 +266,6 @@ router paramResource("tableId"), authorized(PermissionType.TABLE, PermissionLevel.WRITE), rowController.exportRows - ); + ) -export default router; +export default router diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index f388782d2d..0a068f77b9 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1,12 +1,12 @@ -import tk from "timekeeper"; -const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString(); -tk.freeze(timestamp); +import tk from "timekeeper" +const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() +tk.freeze(timestamp) -import { outputProcessing } from "../../../utilities/rowProcessor"; -import * as setup from "./utilities"; -const { basicRow } = setup.structures; -import { context, tenancy } from "@budibase/backend-core"; -import { quotas } from "@budibase/pro"; +import { outputProcessing } from "../../../utilities/rowProcessor" +import * as setup from "./utilities" +const { basicRow } = setup.structures +import { context, tenancy } from "@budibase/backend-core" +import { quotas } from "@budibase/pro" import { QuotaUsageType, StaticQuotaName, @@ -14,39 +14,39 @@ import { Row, Table, FieldType, -} from "@budibase/types"; -import { structures } from "@budibase/backend-core/tests"; +} from "@budibase/types" +import { structures } from "@budibase/backend-core/tests" describe("/rows", () => { - let request = setup.getRequest(); - let config = setup.getConfig(); - let table: Table; - let row: Row; + let request = setup.getRequest() + let config = setup.getConfig() + let table: Table + let row: Row - afterAll(setup.afterAll); + afterAll(setup.afterAll) beforeAll(async () => { - await config.init(); - }); + await config.init() + }) beforeEach(async () => { - table = await config.createTable(); - row = basicRow(table._id!); - }); + table = await config.createTable() + row = basicRow(table._id!) + }) const loadRow = async (id: string, tbl_Id: string, status = 200) => await request .get(`/api/${tbl_Id}/rows/${id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(status); + .expect(status) const getRowUsage = async () => { const { total } = await config.doInContext(null, () => quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS) - ); - return total; - }; + ) + return total + } const getQueryUsage = async () => { const { total } = await config.doInContext(null, () => @@ -54,43 +54,43 @@ describe("/rows", () => { QuotaUsageType.MONTHLY, MonthlyQuotaName.QUERIES ) - ); - return total; - }; + ) + return total + } const assertRowUsage = async (expected: number) => { - const usage = await getRowUsage(); - expect(usage).toBe(expected); - }; + const usage = await getRowUsage() + expect(usage).toBe(expected) + } const assertQueryUsage = async (expected: number) => { - const usage = await getQueryUsage(); - expect(usage).toBe(expected); - }; + const usage = await getQueryUsage() + expect(usage).toBe(expected) + } describe("save, load, update", () => { it("returns a success message when the row is created", async () => { - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .post(`/api/${row.tableId}/rows`) .send(row) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) expect((res as any).res.statusMessage).toEqual( `${table.name} saved successfully` - ); - expect(res.body.name).toEqual("Test Contact"); - expect(res.body._rev).toBeDefined(); - await assertRowUsage(rowUsage + 1); - await assertQueryUsage(queryUsage + 1); - }); + ) + expect(res.body.name).toEqual("Test Contact") + expect(res.body._rev).toBeDefined() + await assertRowUsage(rowUsage + 1) + await assertQueryUsage(queryUsage + 1) + }) it("Increment row autoId per create row request", async () => { - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const newTable = await config.createTable({ name: "TestTableAuto", @@ -113,9 +113,9 @@ describe("/rows", () => { }, }, }, - }); + }) - const ids = [1, 2, 3]; + const ids = [1, 2, 3] // Performing several create row requests should increment the autoID fields accordingly const createRow = async (id: number) => { @@ -126,27 +126,27 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) expect((res as any).res.statusMessage).toEqual( `${newTable.name} saved successfully` - ); - expect(res.body.name).toEqual("row_" + id); - expect(res.body._rev).toBeDefined(); - expect(res.body["Row ID"]).toEqual(id); - }; - - for (let i = 0; i < ids.length; i++) { - await createRow(ids[i]); + ) + expect(res.body.name).toEqual("row_" + id) + expect(res.body._rev).toBeDefined() + expect(res.body["Row ID"]).toEqual(id) } - await assertRowUsage(rowUsage + ids.length); - await assertQueryUsage(queryUsage + ids.length); - }); + for (let i = 0; i < ids.length; i++) { + await createRow(ids[i]) + } + + await assertRowUsage(rowUsage + ids.length) + await assertQueryUsage(queryUsage + ids.length) + }) it("updates a row successfully", async () => { - const existing = await config.createRow(); - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const existing = await config.createRow() + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .post(`/api/${table._id}/rows`) @@ -158,25 +158,25 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) expect((res as any).res.statusMessage).toEqual( `${table.name} updated successfully.` - ); - expect(res.body.name).toEqual("Updated Name"); - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage + 1); - }); + ) + expect(res.body.name).toEqual("Updated Name") + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage + 1) + }) it("should load a row", async () => { - const existing = await config.createRow(); - const queryUsage = await getQueryUsage(); + const existing = await config.createRow() + const queryUsage = await getQueryUsage() const res = await request .get(`/api/${table._id}/rows/${existing._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) expect(res.body).toEqual({ ...row, @@ -185,65 +185,65 @@ describe("/rows", () => { type: "row", createdAt: timestamp, updatedAt: timestamp, - }); - await assertQueryUsage(queryUsage + 1); - }); + }) + await assertQueryUsage(queryUsage + 1) + }) it("should list all rows for given tableId", async () => { const newRow = { tableId: table._id, name: "Second Contact", status: "new", - }; - await config.createRow(); - await config.createRow(newRow); - const queryUsage = await getQueryUsage(); + } + await config.createRow() + await config.createRow(newRow) + const queryUsage = await getQueryUsage() const res = await request .get(`/api/${table._id}/rows`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) - expect(res.body.length).toBe(2); - expect(res.body.find((r: Row) => r.name === newRow.name)).toBeDefined(); - expect(res.body.find((r: Row) => r.name === row.name)).toBeDefined(); - await assertQueryUsage(queryUsage + 1); - }); + expect(res.body.length).toBe(2) + expect(res.body.find((r: Row) => r.name === newRow.name)).toBeDefined() + expect(res.body.find((r: Row) => r.name === row.name)).toBeDefined() + await assertQueryUsage(queryUsage + 1) + }) it("load should return 404 when row does not exist", async () => { - await config.createRow(); - const queryUsage = await getQueryUsage(); + await config.createRow() + const queryUsage = await getQueryUsage() await request .get(`/api/${table._id}/rows/not-a-valid-id`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(404); - await assertQueryUsage(queryUsage); // no change - }); + .expect(404) + await assertQueryUsage(queryUsage) // no change + }) it("row values are coerced", async () => { const str = { type: FieldType.STRING, name: "str", constraints: { type: "string", presence: false }, - }; + } const attachment = { type: FieldType.ATTACHMENT, name: "attachment", constraints: { type: "array", presence: false }, - }; + } const bool = { type: FieldType.BOOLEAN, name: "boolean", constraints: { type: "boolean", presence: false }, - }; + } const number = { type: FieldType.NUMBER, name: "str", constraints: { type: "number", presence: false }, - }; + } const datetime = { type: FieldType.DATETIME, name: "datetime", @@ -252,7 +252,7 @@ describe("/rows", () => { presence: false, datetime: { earliest: "", latest: "" }, }, - }; + } const arrayField = { type: FieldType.ARRAY, constraints: { @@ -262,7 +262,7 @@ describe("/rows", () => { }, name: "Sample Tags", sortable: false, - }; + } const optsField = { fieldName: "Sample Opts", name: "Sample Opts", @@ -309,7 +309,7 @@ describe("/rows", () => { optsFieldNull: optsField, optsFieldStrKnown: optsField, }, - }); + }) row = { name: "Test Row", @@ -344,54 +344,54 @@ describe("/rows", () => { optsFieldUndefined: undefined, optsFieldNull: null, optsFieldStrKnown: "Alpha", - }; + } - const createdRow = await config.createRow(row); - const id = createdRow._id!; + const createdRow = await config.createRow(row) + const id = createdRow._id! - const saved = (await loadRow(id, table._id!)).body; + const saved = (await loadRow(id, table._id!)).body - expect(saved.stringUndefined).toBe(undefined); - expect(saved.stringNull).toBe(""); - expect(saved.stringString).toBe("i am a string"); - expect(saved.numberEmptyString).toBe(null); - expect(saved.numberNull).toBe(null); - expect(saved.numberUndefined).toBe(undefined); - expect(saved.numberString).toBe(123); - expect(saved.numberNumber).toBe(123); - expect(saved.datetimeEmptyString).toBe(null); - expect(saved.datetimeNull).toBe(null); - expect(saved.datetimeUndefined).toBe(undefined); + expect(saved.stringUndefined).toBe(undefined) + expect(saved.stringNull).toBe("") + expect(saved.stringString).toBe("i am a string") + expect(saved.numberEmptyString).toBe(null) + expect(saved.numberNull).toBe(null) + expect(saved.numberUndefined).toBe(undefined) + expect(saved.numberString).toBe(123) + expect(saved.numberNumber).toBe(123) + expect(saved.datetimeEmptyString).toBe(null) + expect(saved.datetimeNull).toBe(null) + expect(saved.datetimeUndefined).toBe(undefined) expect(saved.datetimeString).toBe( new Date(row.datetimeString).toISOString() - ); - expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString()); - expect(saved.boolNull).toBe(null); - expect(saved.boolEmpty).toBe(null); - expect(saved.boolUndefined).toBe(undefined); - expect(saved.boolString).toBe(true); - expect(saved.boolBool).toBe(true); - expect(saved.attachmentNull).toEqual([]); - expect(saved.attachmentUndefined).toBe(undefined); - expect(saved.attachmentEmpty).toEqual([]); - expect(saved.attachmentEmptyArrayStr).toEqual([]); - expect(saved.arrayFieldEmptyArrayStr).toEqual([]); - expect(saved.arrayFieldNull).toEqual([]); - expect(saved.arrayFieldUndefined).toEqual(undefined); - expect(saved.optsFieldEmptyStr).toEqual(null); - expect(saved.optsFieldUndefined).toEqual(undefined); - expect(saved.optsFieldNull).toEqual(null); - expect(saved.arrayFieldArrayStrKnown).toEqual(["One"]); - expect(saved.optsFieldStrKnown).toEqual("Alpha"); - }); - }); + ) + expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString()) + expect(saved.boolNull).toBe(null) + expect(saved.boolEmpty).toBe(null) + expect(saved.boolUndefined).toBe(undefined) + expect(saved.boolString).toBe(true) + expect(saved.boolBool).toBe(true) + expect(saved.attachmentNull).toEqual([]) + expect(saved.attachmentUndefined).toBe(undefined) + expect(saved.attachmentEmpty).toEqual([]) + expect(saved.attachmentEmptyArrayStr).toEqual([]) + expect(saved.arrayFieldEmptyArrayStr).toEqual([]) + expect(saved.arrayFieldNull).toEqual([]) + expect(saved.arrayFieldUndefined).toEqual(undefined) + expect(saved.optsFieldEmptyStr).toEqual(null) + expect(saved.optsFieldUndefined).toEqual(undefined) + expect(saved.optsFieldNull).toEqual(null) + expect(saved.arrayFieldArrayStrKnown).toEqual(["One"]) + expect(saved.optsFieldStrKnown).toEqual("Alpha") + }) + }) describe("patch", () => { it("should update only the fields that are supplied", async () => { - const existing = await config.createRow(); + const existing = await config.createRow() - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .patch(`/api/${table._id}/rows`) @@ -403,26 +403,26 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) expect((res as any).res.statusMessage).toEqual( `${table.name} updated successfully.` - ); - expect(res.body.name).toEqual("Updated Name"); - expect(res.body.description).toEqual(existing.description); + ) + expect(res.body.name).toEqual("Updated Name") + expect(res.body.description).toEqual(existing.description) - const savedRow = await loadRow(res.body._id, table._id!); + const savedRow = await loadRow(res.body._id, table._id!) - expect(savedRow.body.description).toEqual(existing.description); - expect(savedRow.body.name).toEqual("Updated Name"); - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage + 2); // account for the second load - }); + expect(savedRow.body.description).toEqual(existing.description) + expect(savedRow.body.name).toEqual("Updated Name") + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage + 2) // account for the second load + }) it("should throw an error when given improper types", async () => { - const existing = await config.createRow(); - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const existing = await config.createRow() + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() await request .patch(`/api/${table._id}/rows`) @@ -433,18 +433,18 @@ describe("/rows", () => { name: 1, }) .set(config.defaultHeaders()) - .expect(400); + .expect(400) - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage); - }); - }); + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage) + }) + }) describe("destroy", () => { it("should be able to delete a row", async () => { - const createdRow = await config.createRow(row); - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const createdRow = await config.createRow(row) + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .delete(`/api/${table._id}/rows`) @@ -453,55 +453,55 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); - expect(res.body[0]._id).toEqual(createdRow._id); - await assertRowUsage(rowUsage - 1); - await assertQueryUsage(queryUsage + 1); - }); - }); + .expect(200) + expect(res.body[0]._id).toEqual(createdRow._id) + await assertRowUsage(rowUsage - 1) + await assertQueryUsage(queryUsage + 1) + }) + }) describe("validate", () => { it("should return no errors on valid row", async () => { - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .post(`/api/${table._id}/rows/validate`) .send({ name: "ivan" }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) - expect(res.body.valid).toBe(true); - expect(Object.keys(res.body.errors)).toEqual([]); - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage); - }); + expect(res.body.valid).toBe(true) + expect(Object.keys(res.body.errors)).toEqual([]) + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage) + }) it("should errors on invalid row", async () => { - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .post(`/api/${table._id}/rows/validate`) .send({ name: 1 }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) - expect(res.body.valid).toBe(false); - expect(Object.keys(res.body.errors)).toEqual(["name"]); - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage); - }); - }); + expect(res.body.valid).toBe(false) + expect(Object.keys(res.body.errors)).toEqual(["name"]) + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage) + }) + }) describe("bulkDelete", () => { it("should be able to delete a bulk set of rows", async () => { - const row1 = await config.createRow(); - const row2 = await config.createRow(); - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const row1 = await config.createRow() + const row2 = await config.createRow() + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .delete(`/api/${table._id}/rows`) @@ -510,115 +510,115 @@ describe("/rows", () => { }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) - expect(res.body.length).toEqual(2); - await loadRow(row1._id!, table._id!, 404); - await assertRowUsage(rowUsage - 2); - await assertQueryUsage(queryUsage + 1); - }); - }); + expect(res.body.length).toEqual(2) + await loadRow(row1._id!, table._id!, 404) + await assertRowUsage(rowUsage - 2) + await assertQueryUsage(queryUsage + 1) + }) + }) describe("fetchView", () => { it("should be able to fetch tables contents via 'view'", async () => { - const row = await config.createRow(); - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const row = await config.createRow() + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .get(`/api/views/${table._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); - expect(res.body.length).toEqual(1); - expect(res.body[0]._id).toEqual(row._id); - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage + 1); - }); + .expect(200) + expect(res.body.length).toEqual(1) + expect(res.body[0]._id).toEqual(row._id) + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage + 1) + }) it("should throw an error if view doesn't exist", async () => { - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() await request .get(`/api/views/derp`) .set(config.defaultHeaders()) - .expect(404); + .expect(404) - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage); - }); + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage) + }) it("should be able to run on a view", async () => { - const view = await config.createView(); - const row = await config.createRow(); - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + const view = await config.createView() + const row = await config.createRow() + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() const res = await request .get(`/api/views/${view.name}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); - expect(res.body.length).toEqual(1); - expect(res.body[0]._id).toEqual(row._id); + .expect(200) + expect(res.body.length).toEqual(1) + expect(res.body[0]._id).toEqual(row._id) - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage + 1); - }); - }); + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage + 1) + }) + }) describe("fetchEnrichedRows", () => { it("should allow enriching some linked rows", async () => { const { table, firstRow, secondRow } = await tenancy.doInTenant( config.getTenantId(), async () => { - const table = await config.createLinkedTable(); + const table = await config.createLinkedTable() const firstRow = await config.createRow({ name: "Test Contact", description: "original description", tableId: table._id, - }); + }) const secondRow = await config.createRow({ name: "Test 2", description: "og desc", link: [{ _id: firstRow._id }], tableId: table._id, - }); - return { table, firstRow, secondRow }; + }) + return { table, firstRow, secondRow } } - ); - const rowUsage = await getRowUsage(); - const queryUsage = await getQueryUsage(); + ) + const rowUsage = await getRowUsage() + const queryUsage = await getQueryUsage() // test basic enrichment const resBasic = await request .get(`/api/${table._id}/rows/${secondRow._id}`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); - expect(resBasic.body.link[0]._id).toBe(firstRow._id); - expect(resBasic.body.link[0].primaryDisplay).toBe("Test Contact"); + .expect(200) + expect(resBasic.body.link[0]._id).toBe(firstRow._id) + expect(resBasic.body.link[0].primaryDisplay).toBe("Test Contact") // test full enrichment const resEnriched = await request .get(`/api/${table._id}/${secondRow._id}/enrich`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); - expect(resEnriched.body.link.length).toBe(1); - expect(resEnriched.body.link[0]._id).toBe(firstRow._id); - expect(resEnriched.body.link[0].name).toBe("Test Contact"); - expect(resEnriched.body.link[0].description).toBe("original description"); - await assertRowUsage(rowUsage); - await assertQueryUsage(queryUsage + 2); - }); - }); + .expect(200) + expect(resEnriched.body.link.length).toBe(1) + expect(resEnriched.body.link[0]._id).toBe(firstRow._id) + expect(resEnriched.body.link[0].name).toBe("Test Contact") + expect(resEnriched.body.link[0].description).toBe("original description") + await assertRowUsage(rowUsage) + await assertQueryUsage(queryUsage + 2) + }) + }) describe("attachments", () => { it("should allow enriching attachment rows", async () => { - const table = await config.createAttachmentTable(); - const attachmentId = `${structures.uuid()}.csv`; + const table = await config.createAttachmentTable() + const attachmentId = `${structures.uuid()}.csv` const row = await config.createRow({ name: "test", description: "test", @@ -628,22 +628,22 @@ describe("/rows", () => { }, ], tableId: table._id, - }); + }) // the environment needs configured for this await setup.switchToSelfHosted(async () => { context.doInAppContext(config.getAppId(), async () => { - const enriched = await outputProcessing(table, [row]); + const enriched = await outputProcessing(table, [row]) expect((enriched as Row[])[0].attachment[0].url).toBe( `/files/signed/prod-budi-app-assets/${config.getProdAppId()}/attachments/${attachmentId}` - ); - }); - }); - }); - }); + ) + }) + }) + }) + }) describe("exportData", () => { it("should allow exporting all columns", async () => { - const existing = await config.createRow(); + const existing = await config.createRow() const res = await request .post(`/api/${table._id}/rows/exportRows?format=json`) .set(config.defaultHeaders()) @@ -651,22 +651,22 @@ describe("/rows", () => { rows: [existing._id], }) .expect("Content-Type", /json/) - .expect(200); - const results = JSON.parse(res.text); - expect(results.length).toEqual(1); - const row = results[0]; + .expect(200) + const results = JSON.parse(res.text) + expect(results.length).toEqual(1) + const row = results[0] // Ensure all original columns were exported expect(Object.keys(row).length).toBeGreaterThanOrEqual( Object.keys(existing).length - ); - Object.keys(existing).forEach((key) => { - expect(row[key]).toEqual(existing[key]); - }); - }); + ) + Object.keys(existing).forEach(key => { + expect(row[key]).toEqual(existing[key]) + }) + }) it("should allow exporting only certain columns", async () => { - const existing = await config.createRow(); + const existing = await config.createRow() const res = await request .post(`/api/${table._id}/rows/exportRows?format=json`) .set(config.defaultHeaders()) @@ -675,16 +675,16 @@ describe("/rows", () => { columns: ["_id"], }) .expect("Content-Type", /json/) - .expect(200); - const results = JSON.parse(res.text); - expect(results.length).toEqual(1); - const row = results[0]; + .expect(200) + const results = JSON.parse(res.text) + expect(results.length).toEqual(1) + const row = results[0] // Ensure only the _id column was exported - expect(Object.keys(row).length).toEqual(1); - expect(row._id).toEqual(existing._id); - }); - }); + expect(Object.keys(row).length).toEqual(1) + expect(row._id).toEqual(existing._id) + }) + }) describe("view search", () => { function priceTable(): Table { @@ -705,27 +705,27 @@ describe("/rows", () => { }, }, }, - }; + } } it("returns table rows from view", async () => { - const table = await config.createTable(priceTable()); - const rows = []; + const table = await config.createTable(priceTable()) + const rows = [] for (let i = 0; i < 10; i++) - rows.push(await config.createRow({ tableId: table._id })); + rows.push(await config.createRow({ tableId: table._id })) - const createViewResponse = await config.api.viewV2.create(); + const createViewResponse = await config.api.viewV2.create() const response = await request .get(`/api/v2/views/${createViewResponse._id}/search`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200); + .expect(200) - expect(response.body.rows).toHaveLength(10); + expect(response.body.rows).toHaveLength(10) expect(response.body).toEqual({ rows: expect.arrayContaining(rows.map(expect.objectContaining)), - }); - }); - }); -}); + }) + }) + }) +}) From fcd0db8f5761e8b5eb6a44af7f48e076f06f46a0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 11:38:49 +0200 Subject: [PATCH 30/89] Clean code --- packages/server/src/api/routes/tests/row.spec.ts | 3 ++- 1 file changed, 2 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 0a068f77b9..d9d1401cfc 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -711,8 +711,9 @@ describe("/rows", () => { it("returns table rows from view", async () => { const table = await config.createTable(priceTable()) const rows = [] - for (let i = 0; i < 10; i++) + for (let i = 0; i < 10; i++) { rows.push(await config.createRow({ tableId: table._id })) + } const createViewResponse = await config.api.viewV2.create() From 8478816eef9c462e403c9c39da252dadd8b92634 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 12:28:31 +0200 Subject: [PATCH 31/89] Clean --- .../server/src/tests/utilities/api/viewV2.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 5c838e50fb..5783b77e58 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -1,4 +1,4 @@ -import { Account, AccountMetadata, ViewV2 } from "@budibase/types" +import { ViewV2 } from "@budibase/types" import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" import { generator } from "@budibase/backend-core/tests" @@ -32,21 +32,4 @@ export class ViewV2API extends TestAPI { .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) } - // }, - // } - // saveMetadata = async (account: Account) => { - // const res = await this.request - // .put(`/api/system/accounts/${account.accountId}/metadata`) - // .send(account) - // .set(this.config.internalAPIHeaders()) - // .expect("Content-Type", /json/) - // .expect(200) - // return res.body as AccountMetadata - // } - - // destroyMetadata = (accountId: string) => { - // return this.request - // .del(`/api/system/accounts/${accountId}/metadata`) - // .set(this.config.internalAPIHeaders()) - // } } From 2feb22ef643abf466f1ed99784ff5225ee94af2f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 12:38:01 +0200 Subject: [PATCH 32/89] Use api for viewv2 tests --- .../src/api/routes/tests/viewV2.spec.ts | 55 ++++++------------- .../server/src/tests/utilities/api/viewV2.ts | 36 +++++++++++- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index bc5c13297a..e395e59044 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -24,7 +24,6 @@ function priceTable(): Table { } describe("/v2/views", () => { - const request = setup.getRequest() const config = setup.getConfig() afterAll(setup.afterAll) @@ -45,11 +44,7 @@ describe("/v2/views", () => { }) it("returns all views", async () => { - const res = await request - .get(`/api/v2/views`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + const res = await config.api.viewV2.fetch() expect(res.body.views.length).toBe(10) expect(res.body.views).toEqual( @@ -64,11 +59,7 @@ describe("/v2/views", () => { newViews.push(await config.api.viewV2.create({ tableId: newTable._id })) } - const res = await request - .get(`/api/v2/views?tableId=${newTable._id}`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + const res = await config.api.viewV2.fetch(newTable._id) expect(res.body.views.length).toBe(5) expect(res.body.views).toEqual( @@ -77,7 +68,8 @@ describe("/v2/views", () => { }) it("can not filter by multiple table ids", async () => { - const res = await request + const res = await config + .getRequest()! .get( `/api/v2/views?tableId=${structures.generator.guid()}&tableId=${structures.generator.guid()}` ) @@ -90,20 +82,13 @@ describe("/v2/views", () => { }) describe("getView", () => { - const getView = (viewId: string) => { - return request - .get(`/api/v2/views/${viewId}`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - } - let view: ViewV2 beforeAll(async () => { view = await config.api.viewV2.create() }) it("can fetch the expected view", async () => { - const res = await getView(view._id!).expect(200) + const res = await config.api.viewV2.get(view._id!) expect(res.status).toBe(200) expect(res.body).toEqual({ @@ -118,7 +103,9 @@ describe("/v2/views", () => { }) it("will return 404 if the unnexisting id is provided", async () => { - await getView(structures.generator.guid()).expect(404) + await config.api.viewV2.get(structures.generator.guid(), { + expectStatus: 404, + }) }) }) @@ -128,19 +115,12 @@ describe("/v2/views", () => { name: generator.name(), tableId: config.table!._id!, } - const res = await request - .post(`/api/v2/views`) - .send(newView) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(201) + const res = await config.api.viewV2.create(newView) - expect(res.body).toEqual({ - data: { - ...newView, - _id: expect.any(String), - _rev: expect.any(String), - }, + expect(res).toEqual({ + ...newView, + _id: expect.any(String), + _rev: expect.any(String), }) }) }) @@ -154,14 +134,11 @@ describe("/v2/views", () => { }) it("can delete an existing view", async () => { - await config.api.viewV2.get(view._id!).expect(200) + await config.api.viewV2.get(view._id!, { expectStatus: 200 }) - await request - .delete(`/api/v2/views/${view._id}`) - .set(config.defaultHeaders()) - .expect(204) + await config.api.viewV2.delete(view._id!) - await config.api.viewV2.get(view._id!).expect(404) + await config.api.viewV2.get(view._id!, { expectStatus: 404 }) }) }) }) diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 5783b77e58..a1e8f019cf 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -9,7 +9,10 @@ export class ViewV2API extends TestAPI { super(config) } - create = async (viewData?: Partial) => { + create = async ( + viewData?: Partial, + { expectStatus } = { expectStatus: 201 } + ) => { if (!this.config.table) { throw "Test requires table to be configured." } @@ -23,13 +26,40 @@ export class ViewV2API extends TestAPI { .send(view) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(201) + .expect(expectStatus) return result.body.data as ViewV2 } - get = (viewId: string): supertest.Test => { + + get = ( + viewId: string, + { expectStatus } = { expectStatus: 200 } + ): supertest.Test => { return this.request .get(`/api/v2/views/${viewId}`) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) + .expect(expectStatus) + } + + fetch = async ( + tableId?: string, + { expectStatus } = { expectStatus: 200 } + ) => { + let url = `/api/v2/views?` + if (tableId) { + url += `tableId=${tableId}&` + } + return this.request + .get(url) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(expectStatus) + } + + delete = async (viewId: string, { expectStatus } = { expectStatus: 204 }) => { + return this.request + .delete(`/api/v2/views/${viewId}`) + .set(this.config.defaultHeaders()) + .expect(expectStatus) } } From 6809bb451046d45f51366c1e6573cccd34db6f4d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 12:39:41 +0200 Subject: [PATCH 33/89] Use api for view search tests --- packages/server/src/api/routes/tests/row.spec.ts | 6 +----- packages/server/src/tests/utilities/api/viewV2.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index d9d1401cfc..eaec009b88 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -717,11 +717,7 @@ describe("/rows", () => { const createViewResponse = await config.api.viewV2.create() - const response = await request - .get(`/api/v2/views/${createViewResponse._id}/search`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + const response = await config.api.viewV2.search(createViewResponse._id!) expect(response.body.rows).toHaveLength(10) expect(response.body).toEqual({ diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index a1e8f019cf..a830a68ec9 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -62,4 +62,12 @@ export class ViewV2API extends TestAPI { .set(this.config.defaultHeaders()) .expect(expectStatus) } + + search = async (viewId: string, { expectStatus } = { expectStatus: 200 }) => { + return this.request + .get(`/api/v2/views/${viewId}/search`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(expectStatus) + } } From ebd93eb109fc85c25a2dece72da47c881a3bee63 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 14:34:23 +0200 Subject: [PATCH 34/89] Persist queries on crud views --- .../src/api/routes/tests/viewV2.spec.ts | 50 ++++++++++++++++++- .../src/tests/utilities/TestConfiguration.ts | 16 ++++-- packages/types/src/documents/app/view.ts | 2 + 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index e395e59044..23f9918c16 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -53,7 +53,9 @@ describe("/v2/views", () => { }) it("can filter by table id", async () => { - const newTable = await config.createTable(priceTable()) + const newTable = await config.createTable(priceTable(), { + skipReassigning: true, + }) const newViews = [] for (let id = 0; id < 5; id++) { newViews.push(await config.api.viewV2.create({ tableId: newTable._id })) @@ -79,12 +81,32 @@ describe("/v2/views", () => { expect(res.body.message).toBe("tableId type is not valid") }) + + it("returns views with query info", async () => { + const newView = await config.api.viewV2.create({ + query: { allOr: false, equal: { field: "value" } }, + }) + views.push(newView) + const res = await request + .get(`/api/v2/views?tableId=${config.table!._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(res.body.views.length).toBe(11) + expect(newView.query).toEqual({ allOr: false, equal: { field: "value" } }) + expect(res.body.views).toEqual( + expect.arrayContaining([expect.objectContaining(newView)]) + ) + }) }) describe("getView", () => { let view: ViewV2 beforeAll(async () => { - view = await config.api.viewV2.create() + view = await config.api.viewV2.create({ + query: { allOr: false, notEqual: { field: "value" } }, + }) }) it("can fetch the expected view", async () => { @@ -123,6 +145,30 @@ describe("/v2/views", () => { _rev: expect.any(String), }) }) + + it("can persist views with queries", async () => { + const query = { allOr: false, notContains: { name: ["a", "b"] } } + const newView: ViewV2 = { + name: generator.name(), + tableId: config.table!._id!, + query, + } + const res = await request + .post(`/api/v2/views`) + .send(newView) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(201) + + expect(res.body).toEqual({ + data: { + ...newView, + query, + _id: expect.any(String), + _rev: expect.any(String), + }, + }) + }) }) describe("delete", () => { diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 7cf60e6cf7..a93c78d5fc 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -527,17 +527,23 @@ class TestConfiguration { // TABLE - async updateTable(config?: any): Promise
{ + async updateTable( + config?: any, + { skipReassigning } = { skipReassigning: false } + ): Promise
{ config = config || basicTable() - this.table = await this._req(config, null, controllers.table.save) - return this.table! + const response = await this._req(config, null, controllers.table.save) + if (!skipReassigning) { + this.table = response + } + return response } - async createTable(config?: Table) { + async createTable(config?: Table, options = { skipReassigning: false }) { if (config != null && config._id) { delete config._id } - return this.updateTable(config) + return this.updateTable(config, options) } async getTable(tableId?: string) { diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index f3aad235cc..d8c09ae1e0 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,3 +1,4 @@ +import { SearchFilters } from "../../sdk" import { Document } from "../document" export interface View { @@ -15,6 +16,7 @@ export interface View { export interface ViewV2 extends Document { name: string tableId: string + query?: SearchFilters } export type ViewSchema = ViewCountOrSumSchema | ViewStatisticsSchema From 16d39c6fd2fcd7819af23c2004a3ea0eaadbff5f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 14:49:03 +0200 Subject: [PATCH 35/89] Honor query on view search --- .../server/src/api/controllers/row/index.ts | 2 +- .../server/src/api/routes/tests/row.spec.ts | 63 ++++++++++++++----- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index d3c4215182..3e9a3d232b 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -156,7 +156,7 @@ export async function searchView(ctx: Ctx) { ctx.status = 200 ctx.body = await quotas.addQuery( - () => sdk.rows.search({ tableId, query: {} }), + () => sdk.rows.search({ tableId, query: view.query || {} }), { datasourceId: tableId, } diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index eaec009b88..98ec40c9ae 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -15,7 +15,7 @@ import { Table, FieldType, } from "@budibase/types" -import { structures } from "@budibase/backend-core/tests" +import { generator, structures } from "@budibase/backend-core/tests" describe("/rows", () => { let request = setup.getRequest() @@ -687,29 +687,27 @@ describe("/rows", () => { }) describe("view search", () => { - function priceTable(): Table { + function userTable(): Table { return { - name: "table", - type: "table", + name: "user", + type: "user", schema: { - Price: { - type: FieldType.NUMBER, - name: "Price", - constraints: {}, - }, - Category: { + name: { type: FieldType.STRING, - name: "Category", - constraints: { - type: "string", - }, + name: "Name", + constraints: { type: "string" }, + }, + age: { + type: FieldType.NUMBER, + name: "Age", + constraints: {}, }, }, } } it("returns table rows from view", async () => { - const table = await config.createTable(priceTable()) + const table = await config.createTable(userTable()) const rows = [] for (let i = 0; i < 10; i++) { rows.push(await config.createRow({ tableId: table._id })) @@ -724,5 +722,40 @@ describe("/rows", () => { rows: expect.arrayContaining(rows.map(expect.objectContaining)), }) }) + + it("searching respects the view filters", async () => { + const table = await config.createTable(userTable()) + const expectedRows = [] + for (let i = 0; i < 10; i++) + await config.createRow({ + tableId: table._id, + name: generator.name(), + age: generator.integer({ min: 10, max: 30 }), + }) + + for (let i = 0; i < 5; i++) + expectedRows.push( + await config.createRow({ + tableId: table._id, + name: generator.name(), + age: 40, + }) + ) + + const createViewResponse = await config.api.viewV2.create({ + query: { equal: { age: 40 } }, + }) + + const response = await request + .get(`/api/views/v2/${createViewResponse._id}/search`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(response.body.rows).toHaveLength(5) + expect(response.body).toEqual({ + rows: expect.arrayContaining(expectedRows.map(expect.objectContaining)), + }) + }) }) }) From fde008f4d1d3b5489d1ac649359ab29a00802c70 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 15:03:57 +0200 Subject: [PATCH 36/89] Persist sort --- .../src/api/routes/tests/viewV2.spec.ts | 34 ++++++++++++++----- packages/types/src/documents/app/view.ts | 8 ++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 23f9918c16..22c152942d 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -1,5 +1,11 @@ import * as setup from "./utilities" -import { FieldType, Table, ViewV2 } from "@budibase/types" +import { + FieldType, + SortDirection, + SortType, + Table, + ViewV2, +} from "@budibase/types" import { generator, structures } from "@budibase/backend-core/tests" function priceTable(): Table { @@ -26,6 +32,15 @@ function priceTable(): Table { describe("/v2/views", () => { const config = setup.getConfig() + const viewFilters: Omit = { + query: { allOr: false, equal: { field: "value" } }, + sort: { + field: "fieldToSort", + order: SortDirection.DESCENDING, + type: SortType.STRING, + }, + } + afterAll(setup.afterAll) beforeAll(async () => { @@ -83,10 +98,7 @@ describe("/v2/views", () => { }) it("returns views with query info", async () => { - const newView = await config.api.viewV2.create({ - query: { allOr: false, equal: { field: "value" } }, - }) - views.push(newView) + const newView = await config.api.viewV2.create({ ...viewFilters }) const res = await request .get(`/api/v2/views?tableId=${config.table!._id}`) .set(config.defaultHeaders()) @@ -96,7 +108,12 @@ describe("/v2/views", () => { expect(res.body.views.length).toBe(11) expect(newView.query).toEqual({ allOr: false, equal: { field: "value" } }) expect(res.body.views).toEqual( - expect.arrayContaining([expect.objectContaining(newView)]) + expect.arrayContaining([ + expect.objectContaining({ + ...newView, + ...viewFilters, + }), + ]) ) }) }) @@ -147,11 +164,10 @@ describe("/v2/views", () => { }) it("can persist views with queries", async () => { - const query = { allOr: false, notContains: { name: ["a", "b"] } } const newView: ViewV2 = { name: generator.name(), tableId: config.table!._id!, - query, + ...viewFilters, } const res = await request .post(`/api/v2/views`) @@ -163,7 +179,7 @@ describe("/v2/views", () => { expect(res.body).toEqual({ data: { ...newView, - query, + ...viewFilters, _id: expect.any(String), _rev: expect.any(String), }, diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index d8c09ae1e0..7826cc06fd 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,4 +1,5 @@ -import { SearchFilters } from "../../sdk" +import { SortType } from "../../api" +import { SearchFilters, SortDirection } from "../../sdk" import { Document } from "../document" export interface View { @@ -17,6 +18,11 @@ export interface ViewV2 extends Document { name: string tableId: string query?: SearchFilters + sort?: { + field: string + order?: SortDirection + type?: SortType + } } export type ViewSchema = ViewCountOrSumSchema | ViewStatisticsSchema From b778921028314edb0a7647b193fd2b7a171296d9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 15:34:25 +0200 Subject: [PATCH 37/89] Test sorting --- .../server/src/api/controllers/row/index.ts | 9 ++- .../server/src/api/routes/tests/row.spec.ts | 67 ++++++++++++++++++- packages/server/src/sdk/app/rows/search.ts | 6 +- packages/types/src/documents/app/view.ts | 6 +- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 3e9a3d232b..3169e4f77c 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -156,7 +156,14 @@ export async function searchView(ctx: Ctx) { ctx.status = 200 ctx.body = await quotas.addQuery( - () => sdk.rows.search({ tableId, query: view.query || {} }), + () => + sdk.rows.search({ + tableId, + query: view.query || {}, + sort: view.sort?.field, + sortOrder: view.sort?.order, + sortType: view.sort?.type, + }), { datasourceId: tableId, } diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 98ec40c9ae..e6da42ef8c 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -14,6 +14,8 @@ import { Row, Table, FieldType, + SortType, + SortOrder, } from "@budibase/types" import { generator, structures } from "@budibase/backend-core/tests" @@ -694,12 +696,12 @@ describe("/rows", () => { schema: { name: { type: FieldType.STRING, - name: "Name", + name: "name", constraints: { type: "string" }, }, age: { type: FieldType.NUMBER, - name: "Age", + name: "age", constraints: {}, }, }, @@ -757,5 +759,66 @@ describe("/rows", () => { rows: expect.arrayContaining(expectedRows.map(expect.objectContaining)), }) }) + + it.each([ + [ + { + field: "name", + order: SortOrder.ASCENDING, + type: SortType.STRING, + }, + ["Alice", "Bob", "Charly", "Danny"], + ], + [ + { + field: "name", + }, + ["Alice", "Bob", "Charly", "Danny"], + ], + [ + { + field: "name", + order: SortOrder.DESCENDING, + }, + ["Danny", "Charly", "Bob", "Alice"], + ], + [ + { + field: "name", + order: SortOrder.DESCENDING, + type: SortType.STRING, + }, + ["Danny", "Charly", "Bob", "Alice"], + ], + ])("allow sorting", 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 request + .get(`/api/views/v2/${createViewResponse._id}/search`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(response.body.rows).toHaveLength(4) + expect(response.body).toEqual({ + rows: expected.map(name => expect.objectContaining({ name })), + }) + }) }) }) diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 0a4886278a..c6d0e013fa 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -1,4 +1,4 @@ -import { SearchFilters } from "@budibase/types" +import { SearchFilters, SortType } from "@budibase/types" import { isExternalTable } from "../../../integrations/utils" import * as internal from "./search/internal" import * as external from "./search/external" @@ -11,8 +11,8 @@ export interface SearchParams { bookmark?: string limit?: number sort?: string - sortOrder?: string - sortType?: string + sortOrder?: "ascending" | "descending" + sortType?: SortType version?: string disableEscaping?: boolean } diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index 7826cc06fd..c03bc3fe7f 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,5 +1,5 @@ -import { SortType } from "../../api" -import { SearchFilters, SortDirection } from "../../sdk" +import { SortOrder, SortType } from "../../api" +import { SearchFilters } from "../../sdk" import { Document } from "../document" export interface View { @@ -20,7 +20,7 @@ export interface ViewV2 extends Document { query?: SearchFilters sort?: { field: string - order?: SortDirection + order?: SortOrder type?: SortType } } From 82e2385099542599a7ff985e2041c748d5af2b44 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 15:37:23 +0200 Subject: [PATCH 38/89] Test different sort --- .../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 e6da42ef8c..ca7d23e4ac 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -790,7 +790,37 @@ describe("/rows", () => { }, ["Danny", "Charly", "Bob", "Alice"], ], - ])("allow sorting", async (sortParams, expected) => { + [ + { + field: "age", + order: SortOrder.ASCENDING, + type: SortType.number, + }, + ["Danny", "Alice", "Charly", "Bob"], + ], + [ + { + field: "age", + order: SortOrder.ASCENDING, + }, + ["Danny", "Alice", "Charly", "Bob"], + ], + [ + { + field: "age", + order: SortOrder.DESCENDING, + }, + ["Bob", "Charly", "Alice", "Danny"], + ], + [ + { + field: "age", + order: SortOrder.DESCENDING, + type: SortType.number, + }, + ["Bob", "Charly", "Alice", "Danny"], + ], + ])("allow sorting (%s)", async (sortParams, expected) => { await config.createTable(userTable()) const users = [ { name: "Alice", age: 25 }, From 28f4aef07f774c53ec9eebd1d802c0e3799042ad Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 18 Jul 2023 15:47:37 +0200 Subject: [PATCH 39/89] Persist columns in views --- packages/server/src/api/routes/tests/viewV2.spec.ts | 11 +++-------- packages/types/src/documents/app/view.ts | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 22c152942d..cc05a17e25 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -1,11 +1,5 @@ import * as setup from "./utilities" -import { - FieldType, - SortDirection, - SortType, - Table, - ViewV2, -} from "@budibase/types" +import { FieldType, SortOrder, SortType, Table, ViewV2 } from "@budibase/types" import { generator, structures } from "@budibase/backend-core/tests" function priceTable(): Table { @@ -36,9 +30,10 @@ describe("/v2/views", () => { query: { allOr: false, equal: { field: "value" } }, sort: { field: "fieldToSort", - order: SortDirection.DESCENDING, + order: SortOrder.DESCENDING, type: SortType.STRING, }, + columns: ["name"], } afterAll(setup.afterAll) diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index c03bc3fe7f..a4662e3c29 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -23,6 +23,7 @@ export interface ViewV2 extends Document { order?: SortOrder type?: SortType } + columns?: string[] } export type ViewSchema = ViewCountOrSumSchema | ViewStatisticsSchema From 12891456deba2e19e41cf2c6faee631026c24885 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 10:00:05 +0200 Subject: [PATCH 40/89] Fix test --- packages/server/src/api/routes/tests/row.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index ca7d23e4ac..459090fbb6 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -749,7 +749,7 @@ describe("/rows", () => { }) const response = await request - .get(`/api/views/v2/${createViewResponse._id}/search`) + .get(`/api/v2/views/${createViewResponse._id}/search`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -840,7 +840,7 @@ describe("/rows", () => { }) const response = await request - .get(`/api/views/v2/${createViewResponse._id}/search`) + .get(`/api/v2/views/${createViewResponse._id}/search`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) From 582e0eb1dd1080c05f4a87ac4771dac33f1a6990 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 11:49:11 +0200 Subject: [PATCH 41/89] Use types --- packages/server/src/sdk/app/rows/search.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index c6d0e013fa..53f3bbbc43 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -1,4 +1,4 @@ -import { SearchFilters, SortType } from "@budibase/types" +import { SearchFilters, SortOrder, SortType } from "@budibase/types" import { isExternalTable } from "../../../integrations/utils" import * as internal from "./search/internal" import * as external from "./search/external" @@ -11,7 +11,7 @@ export interface SearchParams { bookmark?: string limit?: number sort?: string - sortOrder?: "ascending" | "descending" + sortOrder?: SortOrder sortType?: SortType version?: string disableEscaping?: boolean From 516b5691e847359ae4f08e912af7815f6a86243b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 11:57:20 +0200 Subject: [PATCH 42/89] DRY --- packages/server/src/api/routes/tests/row.spec.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 459090fbb6..95422a81e5 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -748,11 +748,7 @@ describe("/rows", () => { query: { equal: { age: 40 } }, }) - const response = await request - .get(`/api/v2/views/${createViewResponse._id}/search`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + const response = await config.api.viewV2.search(createViewResponse._id!) expect(response.body.rows).toHaveLength(5) expect(response.body).toEqual({ @@ -839,11 +835,7 @@ describe("/rows", () => { sort: sortParams, }) - const response = await request - .get(`/api/v2/views/${createViewResponse._id}/search`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + const response = await config.api.viewV2.search(createViewResponse._id!) expect(response.body.rows).toHaveLength(4) expect(response.body).toEqual({ From 49980dadf16faf5e969d0047504ade1e0dede4b2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 12:50:52 +0200 Subject: [PATCH 43/89] Fix merge conflicts --- .../src/api/routes/tests/viewV2.spec.ts | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index cc05a17e25..3b628d584c 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -94,11 +94,7 @@ describe("/v2/views", () => { it("returns views with query info", async () => { const newView = await config.api.viewV2.create({ ...viewFilters }) - const res = await request - .get(`/api/v2/views?tableId=${config.table!._id}`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + const res = await config.api.viewV2.fetch(config.table!._id) expect(res.body.views.length).toBe(11) expect(newView.query).toEqual({ allOr: false, equal: { field: "value" } }) @@ -164,20 +160,13 @@ describe("/v2/views", () => { tableId: config.table!._id!, ...viewFilters, } - const res = await request - .post(`/api/v2/views`) - .send(newView) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(201) + const res = await config.api.viewV2.create(newView) - expect(res.body).toEqual({ - data: { - ...newView, - ...viewFilters, - _id: expect.any(String), - _rev: expect.any(String), - }, + expect(res).toEqual({ + ...newView, + ...viewFilters, + _id: expect.any(String), + _rev: expect.any(String), }) }) }) From b82876b147b3995379742bbe3e8a51d757412978 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 15:35:41 +0200 Subject: [PATCH 44/89] Remove view fetch logic --- .../src/api/controllers/view/viewsV2.ts | 21 +----- .../src/api/routes/tests/viewV2.spec.ts | 66 ------------------- packages/server/src/api/routes/view.ts | 5 -- packages/server/src/sdk/app/views/index.ts | 33 +--------- .../server/src/tests/utilities/api/viewV2.ts | 15 ----- packages/types/src/api/web/app/view.ts | 4 -- 6 files changed, 2 insertions(+), 142 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 1b6b7a5f31..ae324c0391 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -1,24 +1,5 @@ import sdk from "../../../sdk" -import { - CreateViewRequest, - Ctx, - FetchViewResponse, - ViewResponse, -} from "@budibase/types" - -export async function fetch(ctx: Ctx) { - const { tableId } = ctx.query - - if (tableId && typeof tableId !== "string") { - ctx.throw(400, "tableId type is not valid") - } - - const views = tableId - ? await sdk.views.findByTable(tableId) - : await sdk.views.fetch() - - ctx.body = { views } -} +import { CreateViewRequest, Ctx, ViewResponse } from "@budibase/types" export async function find(ctx: Ctx) { const { viewId } = ctx.params diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 3b628d584c..d40bd82bcf 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -43,72 +43,6 @@ describe("/v2/views", () => { await config.createTable(priceTable()) }) - describe("fetch", () => { - const views: ViewV2[] = [] - - beforeAll(async () => { - await config.createTable(priceTable()) - for (let id = 0; id < 10; id++) { - views.push(await config.api.viewV2.create()) - } - }) - - it("returns all views", async () => { - const res = await config.api.viewV2.fetch() - - expect(res.body.views.length).toBe(10) - expect(res.body.views).toEqual( - expect.arrayContaining(views.map(v => expect.objectContaining(v))) - ) - }) - - it("can filter by table id", async () => { - const newTable = await config.createTable(priceTable(), { - skipReassigning: true, - }) - const newViews = [] - for (let id = 0; id < 5; id++) { - newViews.push(await config.api.viewV2.create({ tableId: newTable._id })) - } - - const res = await config.api.viewV2.fetch(newTable._id) - - expect(res.body.views.length).toBe(5) - expect(res.body.views).toEqual( - expect.arrayContaining(newViews.map(v => expect.objectContaining(v))) - ) - }) - - it("can not filter by multiple table ids", async () => { - const res = await config - .getRequest()! - .get( - `/api/v2/views?tableId=${structures.generator.guid()}&tableId=${structures.generator.guid()}` - ) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(400) - - expect(res.body.message).toBe("tableId type is not valid") - }) - - it("returns views with query info", async () => { - const newView = await config.api.viewV2.create({ ...viewFilters }) - const res = await config.api.viewV2.fetch(config.table!._id) - - expect(res.body.views.length).toBe(11) - expect(newView.query).toEqual({ allOr: false, equal: { field: "value" } }) - expect(res.body.views).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - ...newView, - ...viewFilters, - }), - ]) - ) - }) - }) - describe("getView", () => { let view: ViewV2 beforeAll(async () => { diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 155fce1aad..8850b0882b 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -8,11 +8,6 @@ import { DocumentType, SEPARATOR, permissions } from "@budibase/backend-core" const router: Router = new Router() router - .get( - "/api/v2/views", - authorized(permissions.BUILDER), - viewController.v2.fetch - ) .get( `/api/v2/views/:viewId`, authorized(permissions.BUILDER), diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index fbac7b757a..542c8df82c 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,38 +1,7 @@ -import { - DocumentType, - SEPARATOR, - UNICODE_MAX, - context, -} from "@budibase/backend-core" +import { context } from "@budibase/backend-core" import { ViewV2 } from "@budibase/types" import * as utils from "../../../db/utils" -export async function fetch(): Promise { - const db = context.getAppDB() - - const startKey = `${DocumentType.VIEW}${SEPARATOR}` - const response = await db.allDocs({ - startkey: startKey, - endkey: `${startKey}${UNICODE_MAX}`, - include_docs: true, - }) - - return response.rows.map(r => r.doc) -} - -export async function findByTable(tableId: string): Promise { - const db = context.getAppDB() - - const startKey = utils.viewIDPrefix(tableId) - const response = await db.allDocs({ - startkey: startKey, - endkey: `${startKey}${UNICODE_MAX}`, - include_docs: true, - }) - - return response.rows.map(r => r.doc) -} - export async function get(viewId: string): Promise { const db = context.getAppDB() try { diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index a830a68ec9..e261b55166 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -41,21 +41,6 @@ export class ViewV2API extends TestAPI { .expect(expectStatus) } - fetch = async ( - tableId?: string, - { expectStatus } = { expectStatus: 200 } - ) => { - let url = `/api/v2/views?` - if (tableId) { - url += `tableId=${tableId}&` - } - return this.request - .get(url) - .set(this.config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(expectStatus) - } - delete = async (viewId: string, { expectStatus } = { expectStatus: 204 }) => { return this.request .delete(`/api/v2/views/${viewId}`) diff --git a/packages/types/src/api/web/app/view.ts b/packages/types/src/api/web/app/view.ts index 6ebe8b8a36..e30d35d8ab 100644 --- a/packages/types/src/api/web/app/view.ts +++ b/packages/types/src/api/web/app/view.ts @@ -1,9 +1,5 @@ import { ViewV2 } from "../../../documents" -export interface FetchViewResponse { - views: ViewV2[] -} - export interface ViewResponse { data: ViewV2 } From 76f89e10d3af82e51d462b5a42485753331b46e5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 15:43:21 +0200 Subject: [PATCH 45/89] Create/delete, save to table --- .../src/api/controllers/view/viewsV2.ts | 18 ++---- .../src/api/routes/tests/viewV2.spec.ts | 41 ++++++++----- packages/server/src/api/routes/view.ts | 4 +- packages/server/src/sdk/app/views/index.ts | 59 +++++++++++++------ .../server/src/tests/utilities/api/viewV2.ts | 18 ++++-- packages/types/src/api/web/app/view.ts | 2 +- packages/types/src/documents/app/view.ts | 2 + 7 files changed, 90 insertions(+), 54 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index ae324c0391..3a8b0980b4 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -15,27 +15,19 @@ export async function find(ctx: Ctx) { } export async function create(ctx: Ctx) { + const { tableId } = ctx.params const view = ctx.request.body - const result = await sdk.views.create(view) + const result = await sdk.views.create(tableId, view) ctx.status = 201 ctx.body = { - data: { - ...view, - ...result, - }, + data: result, } } export async function remove(ctx: Ctx) { - const { viewId } = ctx.params - const doc = await sdk.views.get(viewId) - if (!doc) { - ctx.throw(404) - } + const { tableId, viewId } = ctx.params - const { _rev } = doc - - await sdk.views.remove(viewId, _rev!) + await sdk.views.remove(tableId, viewId) ctx.status = 204 } diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index d40bd82bcf..a83795cb9a 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -1,5 +1,12 @@ import * as setup from "./utilities" -import { FieldType, SortOrder, SortType, Table, ViewV2 } from "@budibase/types" +import { + CreateViewRequest, + FieldType, + SortOrder, + SortType, + Table, + ViewV2, +} from "@budibase/types" import { generator, structures } from "@budibase/backend-core/tests" function priceTable(): Table { @@ -26,7 +33,7 @@ function priceTable(): Table { describe("/v2/views", () => { const config = setup.getConfig() - const viewFilters: Omit = { + const viewFilters: Omit = { query: { allOr: false, equal: { field: "value" } }, sort: { field: "fieldToSort", @@ -46,20 +53,20 @@ describe("/v2/views", () => { describe("getView", () => { let view: ViewV2 beforeAll(async () => { - view = await config.api.viewV2.create({ + view = await config.api.viewV2.create(config.table?._id, { query: { allOr: false, notEqual: { field: "value" } }, }) }) it("can fetch the expected view", async () => { - const res = await config.api.viewV2.get(view._id!) + const res = await config.api.viewV2.get(view.id) expect(res.status).toBe(200) expect(res.body).toEqual({ data: { ...view, _id: view._id, - _rev: view._rev, + _rev: expect.any(String), createdAt: expect.any(String), updatedAt: expect.any(String), }, @@ -75,32 +82,38 @@ describe("/v2/views", () => { describe("create", () => { it("persist the view when the view is successfully created", async () => { - const newView: ViewV2 = { + const newView: CreateViewRequest = { name: generator.name(), tableId: config.table!._id!, } - const res = await config.api.viewV2.create(newView) + const res = await config.api.viewV2.create(config.table?._id, newView) expect(res).toEqual({ ...newView, + id: expect.any(String), + version: 2, _id: expect.any(String), - _rev: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), }) }) it("can persist views with queries", async () => { - const newView: ViewV2 = { + const newView: CreateViewRequest = { name: generator.name(), tableId: config.table!._id!, ...viewFilters, } - const res = await config.api.viewV2.create(newView) + const res = await config.api.viewV2.create(config.table!._id!, newView) expect(res).toEqual({ ...newView, ...viewFilters, + id: expect.any(String), + version: 2, _id: expect.any(String), - _rev: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), }) }) }) @@ -114,11 +127,11 @@ describe("/v2/views", () => { }) it("can delete an existing view", async () => { - await config.api.viewV2.get(view._id!, { expectStatus: 200 }) + await config.api.viewV2.get(view.id, { expectStatus: 200 }) - await config.api.viewV2.delete(view._id!) + await config.api.viewV2.delete(config.table?._id!, view.id) - await config.api.viewV2.get(view._id!, { expectStatus: 404 }) + await config.api.viewV2.get(view.id, { expectStatus: 404 }) }) }) }) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 8850b0882b..54ba54c431 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -14,12 +14,12 @@ router viewController.v2.find ) .post( - "/api/v2/views", + "/api/v2/views/:tableId", authorized(permissions.BUILDER), viewController.v2.create ) .delete( - `/api/v2/views/:viewId`, + `/api/v2/views/:tableId/:viewId`, authorized(permissions.BUILDER), viewController.v2.remove ) diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 542c8df82c..3d7e21d448 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,6 +1,8 @@ -import { context } from "@budibase/backend-core" +import { HTTPError, context } from "@budibase/backend-core" import { ViewV2 } from "@budibase/types" import * as utils from "../../../db/utils" +import sdk from "../../../sdk" +import { utils as coreUtils } from "@budibase/backend-core" export async function get(viewId: string): Promise { const db = context.getAppDB() @@ -16,24 +18,45 @@ export async function get(viewId: string): Promise { } } -export async function create(view: ViewV2): Promise { - const db = context.getAppDB() - - const response = await db.put( - { - _id: utils.generateViewID(view.tableId), - ...view, - }, - {} - ) - return { - ...view, - _id: response.id, - _rev: response.rev, +export async function create( + tableId: string, + viewRequest: Omit +): Promise { + const view: ViewV2 = { + ...viewRequest, + id: coreUtils.newid(), + version: 2, } + view._id = view.id + + const db = context.getAppDB() + + await db.put(view, {}) + + const table = await sdk.tables.getTable(tableId) + table.views ??= {} + + // @ts-ignore: TODO + table.views[view.name] = view + await db.put(table) + return view } -export async function remove(viewId: string, rev: string): Promise { - const db = context.getAppDB() - await db.remove(viewId, rev) +function isV2(view: object): view is ViewV2 { + return (view as ViewV2).version === 2 +} + +export async function remove(tableId: string, viewId: string): Promise { + const db = context.getAppDB() + + const doc = await sdk.views.get(viewId) + await db.remove(viewId, doc!._rev) + + const table = await sdk.tables.getTable(tableId) + const view = Object.values(table.views!).find(v => isV2(v) && v.id === viewId) + if (!view) { + throw new HTTPError(`View ${viewId} not found in table ${tableId}`, 404) + } + delete table.views![view?.name] + await db.put(table) } diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index e261b55166..c2d2a79292 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -10,19 +10,21 @@ export class ViewV2API extends TestAPI { } create = async ( + tableId?: string, viewData?: Partial, { expectStatus } = { expectStatus: 201 } - ) => { - if (!this.config.table) { + ): Promise => { + if (!tableId && !this.config.table) { throw "Test requires table to be configured." } + tableId = this.config.table!._id! const view = { - tableId: this.config.table._id, + tableId, name: generator.guid(), ...viewData, } const result = await this.request - .post(`/api/v2/views`) + .post(`/api/v2/views/${tableId}`) .send(view) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) @@ -41,9 +43,13 @@ export class ViewV2API extends TestAPI { .expect(expectStatus) } - delete = async (viewId: string, { expectStatus } = { expectStatus: 204 }) => { + delete = async ( + tableId: string, + viewId: string, + { expectStatus } = { expectStatus: 204 } + ) => { return this.request - .delete(`/api/v2/views/${viewId}`) + .delete(`/api/v2/views/${tableId}/${viewId}`) .set(this.config.defaultHeaders()) .expect(expectStatus) } diff --git a/packages/types/src/api/web/app/view.ts b/packages/types/src/api/web/app/view.ts index e30d35d8ab..fe95a83483 100644 --- a/packages/types/src/api/web/app/view.ts +++ b/packages/types/src/api/web/app/view.ts @@ -6,5 +6,5 @@ export interface ViewResponse { export type CreateViewRequest = Omit< ViewV2, - "_id" | "_rev" | "createdAt" | "updatedAt" + "_id" | "_rev" | "createdAt" | "updatedAt" | "version" | "id" > diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index a4662e3c29..a60fb2a798 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -15,6 +15,8 @@ export interface View { } export interface ViewV2 extends Document { + version: 2 + id: string name: string tableId: string query?: SearchFilters From 56ee61d76c6d304a1914a1d84ac978fc8dc67cf5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 15:47:45 +0200 Subject: [PATCH 46/89] Remove get from delete tests --- .../src/api/routes/tests/viewV2.spec.ts | 10 ++++++--- .../server/src/tests/utilities/api/index.ts | 3 +++ .../server/src/tests/utilities/api/table.ts | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 packages/server/src/tests/utilities/api/table.ts diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index a83795cb9a..dd3cf3b658 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -127,11 +127,15 @@ describe("/v2/views", () => { }) it("can delete an existing view", async () => { - await config.api.viewV2.get(view.id, { expectStatus: 200 }) + const tableId = config.table!._id! + const getPersistedView = async () => + (await config.api.table.get(tableId)).views![view.name] - await config.api.viewV2.delete(config.table?._id!, view.id) + expect(await getPersistedView()).toBeDefined() - await config.api.viewV2.get(view.id, { expectStatus: 404 }) + await config.api.viewV2.delete(tableId, view.id) + + expect(await getPersistedView()).toBeUndefined() }) }) }) diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index f759abd47c..cd9f42b82c 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -1,10 +1,13 @@ import TestConfiguration from "../TestConfiguration" +import { TableAPI } from "./table" import { ViewV2API } from "./viewV2" export default class API { + table: TableAPI viewV2: ViewV2API constructor(config: TestConfiguration) { + this.table = new TableAPI(config) this.viewV2 = new ViewV2API(config) } } diff --git a/packages/server/src/tests/utilities/api/table.ts b/packages/server/src/tests/utilities/api/table.ts new file mode 100644 index 0000000000..7e3ef6efa4 --- /dev/null +++ b/packages/server/src/tests/utilities/api/table.ts @@ -0,0 +1,21 @@ +import { Table } from "@budibase/types" +import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" + +export class TableAPI extends TestAPI { + constructor(config: TestConfiguration) { + super(config) + } + + get = async ( + tableId: string, + { expectStatus } = { expectStatus: 200 } + ): Promise
=> { + const res = await this.request + .get(`/api/tables/${tableId}`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(expectStatus) + return res.body + } +} From d6121d1504896a3ff40fdcbe6dba1441426aa3ca Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 15:49:46 +0200 Subject: [PATCH 47/89] Remove fetch view --- .../src/api/controllers/view/viewsV2.ts | 13 -------- .../src/api/routes/tests/viewV2.spec.ts | 32 +------------------ packages/server/src/api/routes/view.ts | 5 --- packages/server/src/sdk/app/views/index.ts | 19 +---------- .../server/src/tests/utilities/api/viewV2.ts | 12 ------- 5 files changed, 2 insertions(+), 79 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 3a8b0980b4..597dc54e66 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -1,19 +1,6 @@ import sdk from "../../../sdk" import { CreateViewRequest, Ctx, ViewResponse } from "@budibase/types" -export async function find(ctx: Ctx) { - const { viewId } = ctx.params - - const view = await sdk.views.get(viewId) - if (!view) { - ctx.throw(404) - } - - ctx.body = { - data: view, - } -} - export async function create(ctx: Ctx) { const { tableId } = ctx.params const view = ctx.request.body diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index dd3cf3b658..7dc4fc1bad 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -7,7 +7,7 @@ import { Table, ViewV2, } from "@budibase/types" -import { generator, structures } from "@budibase/backend-core/tests" +import { generator } from "@budibase/backend-core/tests" function priceTable(): Table { return { @@ -50,36 +50,6 @@ describe("/v2/views", () => { await config.createTable(priceTable()) }) - describe("getView", () => { - let view: ViewV2 - beforeAll(async () => { - view = await config.api.viewV2.create(config.table?._id, { - query: { allOr: false, notEqual: { field: "value" } }, - }) - }) - - it("can fetch the expected view", async () => { - const res = await config.api.viewV2.get(view.id) - expect(res.status).toBe(200) - - expect(res.body).toEqual({ - data: { - ...view, - _id: view._id, - _rev: expect.any(String), - createdAt: expect.any(String), - updatedAt: expect.any(String), - }, - }) - }) - - it("will return 404 if the unnexisting id is provided", async () => { - await config.api.viewV2.get(structures.generator.guid(), { - expectStatus: 404, - }) - }) - }) - describe("create", () => { it("persist the view when the view is successfully created", async () => { const newView: CreateViewRequest = { diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 54ba54c431..010168aa13 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -8,11 +8,6 @@ import { DocumentType, SEPARATOR, permissions } from "@budibase/backend-core" const router: Router = new Router() router - .get( - `/api/v2/views/:viewId`, - authorized(permissions.BUILDER), - viewController.v2.find - ) .post( "/api/v2/views/:tableId", authorized(permissions.BUILDER), diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 3d7e21d448..b4bb382bb3 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,23 +1,9 @@ import { HTTPError, context } from "@budibase/backend-core" import { ViewV2 } from "@budibase/types" -import * as utils from "../../../db/utils" + import sdk from "../../../sdk" import { utils as coreUtils } from "@budibase/backend-core" -export async function get(viewId: string): Promise { - const db = context.getAppDB() - try { - const result = await db.get(viewId) - return result - } catch (err: any) { - if (err.status === 404) { - return undefined - } - - throw err - } -} - export async function create( tableId: string, viewRequest: Omit @@ -49,9 +35,6 @@ function isV2(view: object): view is ViewV2 { export async function remove(tableId: string, viewId: string): Promise { const db = context.getAppDB() - const doc = await sdk.views.get(viewId) - await db.remove(viewId, doc!._rev) - const table = await sdk.tables.getTable(tableId) const view = Object.values(table.views!).find(v => isV2(v) && v.id === viewId) if (!view) { diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index c2d2a79292..e40bfe7030 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -2,7 +2,6 @@ import { ViewV2 } from "@budibase/types" import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" import { generator } from "@budibase/backend-core/tests" -import supertest from "supertest" export class ViewV2API extends TestAPI { constructor(config: TestConfiguration) { @@ -32,17 +31,6 @@ export class ViewV2API extends TestAPI { return result.body.data as ViewV2 } - get = ( - viewId: string, - { expectStatus } = { expectStatus: 200 } - ): supertest.Test => { - return this.request - .get(`/api/v2/views/${viewId}`) - .set(this.config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(expectStatus) - } - delete = async ( tableId: string, viewId: string, From 21e17053c635ed625df1b7328ff2ace71c2318fb Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 15:53:22 +0200 Subject: [PATCH 48/89] Don't persist the view as doc --- packages/server/src/api/routes/tests/viewV2.spec.ts | 6 ------ packages/server/src/sdk/app/views/index.ts | 4 ---- packages/types/src/api/web/app/view.ts | 5 +---- packages/types/src/documents/app/view.ts | 2 +- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 7dc4fc1bad..d4c81db55c 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -62,9 +62,6 @@ describe("/v2/views", () => { ...newView, id: expect.any(String), version: 2, - _id: expect.any(String), - createdAt: expect.any(String), - updatedAt: expect.any(String), }) }) @@ -81,9 +78,6 @@ describe("/v2/views", () => { ...viewFilters, id: expect.any(String), version: 2, - _id: expect.any(String), - createdAt: expect.any(String), - updatedAt: expect.any(String), }) }) }) diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index b4bb382bb3..f8798d4947 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -13,12 +13,8 @@ export async function create( id: coreUtils.newid(), version: 2, } - view._id = view.id const db = context.getAppDB() - - await db.put(view, {}) - const table = await sdk.tables.getTable(tableId) table.views ??= {} diff --git a/packages/types/src/api/web/app/view.ts b/packages/types/src/api/web/app/view.ts index fe95a83483..2cd371a025 100644 --- a/packages/types/src/api/web/app/view.ts +++ b/packages/types/src/api/web/app/view.ts @@ -4,7 +4,4 @@ export interface ViewResponse { data: ViewV2 } -export type CreateViewRequest = Omit< - ViewV2, - "_id" | "_rev" | "createdAt" | "updatedAt" | "version" | "id" -> +export type CreateViewRequest = Omit diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index a60fb2a798..238cc69c45 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -14,7 +14,7 @@ export interface View { meta?: Record } -export interface ViewV2 extends Document { +export interface ViewV2 { version: 2 id: string name: string From 9bf22213b2738f489f0dbfe0f5c3767ad7287d1d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 15:54:39 +0200 Subject: [PATCH 49/89] Clean code --- packages/types/src/documents/app/view.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index 238cc69c45..781a1a8c5d 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,6 +1,5 @@ import { SortOrder, SortType } from "../../api" import { SearchFilters } from "../../sdk" -import { Document } from "../document" export interface View { name: string From ac0ae34808af74294140ce72486bcbcc28394eba Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 16:04:53 +0200 Subject: [PATCH 50/89] Fix search --- .../server/src/api/controllers/row/index.ts | 8 ++--- packages/server/src/api/routes/row.ts | 2 +- .../server/src/api/routes/tests/row.spec.ts | 34 +++++++++++++------ packages/server/src/sdk/app/views/index.ts | 14 +++++++- .../server/src/tests/utilities/api/viewV2.ts | 8 +++-- 5 files changed, 48 insertions(+), 18 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 3169e4f77c..8a9947e707 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -147,12 +147,12 @@ export async function search(ctx: any) { } export async function searchView(ctx: Ctx) { - const { viewId } = ctx.params - const view = await sdk.views.get(viewId) + const { tableId, viewId } = ctx.params + + const view = await sdk.views.get(tableId, viewId) if (!view) { - ctx.throw(404) + ctx.throw(404, `View ${viewId} not found in table ${tableId}`) } - const tableId = view.tableId ctx.status = 200 ctx.body = await quotas.addQuery( diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index 5fdc02b7a7..1f71d3c1ae 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -147,7 +147,7 @@ router rowController.search ) .get( - "/api/v2/views/:viewId/search", + "/api/v2/views/:tableId/:viewId/search", authorized(PermissionType.VIEW, PermissionLevel.READ), rowController.searchView ) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 95422a81e5..b082bacea0 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -716,8 +716,10 @@ describe("/rows", () => { } const createViewResponse = await config.api.viewV2.create() - - const response = await config.api.viewV2.search(createViewResponse._id!) + const response = await config.api.viewV2.search( + createViewResponse.tableId, + createViewResponse.id + ) expect(response.body.rows).toHaveLength(10) expect(response.body).toEqual({ @@ -744,11 +746,17 @@ describe("/rows", () => { }) ) - const createViewResponse = await config.api.viewV2.create({ - query: { equal: { age: 40 } }, - }) + const createViewResponse = await config.api.viewV2.create( + config.table?._id!, + { + query: { equal: { age: 40 } }, + } + ) - const response = await config.api.viewV2.search(createViewResponse._id!) + const response = await config.api.viewV2.search( + createViewResponse.tableId, + createViewResponse.id + ) expect(response.body.rows).toHaveLength(5) expect(response.body).toEqual({ @@ -831,11 +839,17 @@ describe("/rows", () => { }) } - const createViewResponse = await config.api.viewV2.create({ - sort: sortParams, - }) + const createViewResponse = await config.api.viewV2.create( + config.table?._id!, + { + sort: sortParams, + } + ) - const response = await config.api.viewV2.search(createViewResponse._id!) + const response = await config.api.viewV2.search( + createViewResponse.tableId, + createViewResponse.id + ) expect(response.body.rows).toHaveLength(4) expect(response.body).toEqual({ diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index f8798d4947..004fe7e8d9 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -4,6 +4,17 @@ import { ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import { utils as coreUtils } from "@budibase/backend-core" +export async function get( + tableId: string, + viewId: string +): Promise { + const table = await sdk.tables.getTable(tableId) + const view = Object.values(table.views!).find(v => isV2(v) && v.id === viewId) + + // @ts-ignore TODO + return view +} + export async function create( tableId: string, viewRequest: Omit @@ -32,10 +43,11 @@ export async function remove(tableId: string, viewId: string): Promise { const db = context.getAppDB() const table = await sdk.tables.getTable(tableId) - const view = Object.values(table.views!).find(v => isV2(v) && v.id === viewId) + const view = await get(tableId, viewId) if (!view) { throw new HTTPError(`View ${viewId} not found in table ${tableId}`, 404) } + delete table.views![view?.name] await db.put(table) } diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index e40bfe7030..950b149cea 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -42,9 +42,13 @@ export class ViewV2API extends TestAPI { .expect(expectStatus) } - search = async (viewId: string, { expectStatus } = { expectStatus: 200 }) => { + search = async ( + tableId: string, + viewId: string, + { expectStatus } = { expectStatus: 200 } + ) => { return this.request - .get(`/api/v2/views/${viewId}/search`) + .get(`/api/v2/views/${tableId}/${viewId}/search`) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(expectStatus) From ff3bbf62171906a775aa8d8e08d2d480011b5040 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 16:33:29 +0200 Subject: [PATCH 51/89] Allow ViewV2 types --- packages/server/src/api/controllers/view/views.ts | 5 ++++- .../src/migrations/functions/backfill/app/tables.ts | 4 ++++ packages/server/src/sdk/app/views/index.ts | 9 ++++----- packages/types/src/documents/app/table/table.ts | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/controllers/view/views.ts b/packages/server/src/api/controllers/view/views.ts index 99c4224c62..257baf5e45 100644 --- a/packages/server/src/api/controllers/view/views.ts +++ b/packages/server/src/api/controllers/view/views.ts @@ -55,7 +55,10 @@ export async function save(ctx: Ctx) { existingTable.views[viewName] = existingTable.views[originalName] } await db.put(table) - await handleViewEvents(existingTable.views[viewName], table.views[viewName]) + await handleViewEvents( + existingTable.views[viewName] as View, + table.views[viewName] + ) ctx.body = table.views[viewName] builderSocket?.emitTableUpdate(ctx, table) diff --git a/packages/server/src/migrations/functions/backfill/app/tables.ts b/packages/server/src/migrations/functions/backfill/app/tables.ts index 51b0de5d29..081b81ede5 100644 --- a/packages/server/src/migrations/functions/backfill/app/tables.ts +++ b/packages/server/src/migrations/functions/backfill/app/tables.ts @@ -10,6 +10,10 @@ export const backfill = async (appDb: Database, timestamp: string | number) => { if (table.views) { for (const view of Object.values(table.views)) { + if (sdk.views.isV2(view)) { + continue + } + await events.view.created(view, timestamp) if (view.calculation) { diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 004fe7e8d9..bf02e03629 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,5 +1,5 @@ import { HTTPError, context } from "@budibase/backend-core" -import { ViewV2 } from "@budibase/types" +import { View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import { utils as coreUtils } from "@budibase/backend-core" @@ -9,9 +9,9 @@ export async function get( viewId: string ): Promise { const table = await sdk.tables.getTable(tableId) - const view = Object.values(table.views!).find(v => isV2(v) && v.id === viewId) + const views = Object.values(table.views!) + const view = views.find(v => isV2(v) && v.id === viewId) as ViewV2 | undefined - // @ts-ignore TODO return view } @@ -29,13 +29,12 @@ export async function create( const table = await sdk.tables.getTable(tableId) table.views ??= {} - // @ts-ignore: TODO table.views[view.name] = view await db.put(table) return view } -function isV2(view: object): view is ViewV2 { +export function isV2(view: View | ViewV2): view is ViewV2 { return (view as ViewV2).version === 2 } diff --git a/packages/types/src/documents/app/table/table.ts b/packages/types/src/documents/app/table/table.ts index f4dc790267..76b2c587b2 100644 --- a/packages/types/src/documents/app/table/table.ts +++ b/packages/types/src/documents/app/table/table.ts @@ -1,11 +1,11 @@ import { Document } from "../../document" -import { View } from "../view" +import { View, ViewV2 } from "../view" import { RenameColumn } from "../../../sdk" import { TableSchema } from "./schema" export interface Table extends Document { type?: string - views?: { [key: string]: View } + views?: { [key: string]: View | ViewV2 } name: string primary?: string[] schema: TableSchema From fc831db06b690a580013f0817d363496cdf96c32 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 17:52:00 +0200 Subject: [PATCH 52/89] Store ids containing table info --- packages/server/src/api/routes/tests/viewV2.spec.ts | 2 +- packages/server/src/db/utils.ts | 6 +----- packages/server/src/sdk/app/views/index.ts | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index d4c81db55c..fa69023530 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -60,7 +60,7 @@ describe("/v2/views", () => { expect(res).toEqual({ ...newView, - id: expect.any(String), + id: expect.stringMatching(new RegExp(`^vi_${config.table?._id!}_`)), version: 2, }) }) diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 8854cbc824..ba6ca88330 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -277,9 +277,5 @@ export function getMultiIDParams(ids: string[]) { * @returns {string} The new view ID which the view doc can be stored under. */ export function generateViewID(tableId: string) { - return `${viewIDPrefix(tableId)}${newid()}` -} - -export function viewIDPrefix(tableId: string) { - return `${DocumentType.VIEW}${SEPARATOR}${tableId}${SEPARATOR}` + return `${DocumentType.VIEW}${SEPARATOR}${tableId}${SEPARATOR}${newid()}` } diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index bf02e03629..8492de51a3 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -2,7 +2,7 @@ import { HTTPError, context } from "@budibase/backend-core" import { View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" -import { utils as coreUtils } from "@budibase/backend-core" +import * as utils from "../../../db/utils" export async function get( tableId: string, @@ -21,7 +21,7 @@ export async function create( ): Promise { const view: ViewV2 = { ...viewRequest, - id: coreUtils.newid(), + id: utils.generateViewID(tableId), version: 2, } From 36b82681ff01f459cd9ab9759436087266823f9d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 18:02:15 +0200 Subject: [PATCH 53/89] Remove tableid from remove view url --- packages/backend-core/src/constants/db.ts | 1 - packages/server/src/api/controllers/view/viewsV2.ts | 4 ++-- packages/server/src/api/routes/tests/viewV2.spec.ts | 4 ++-- packages/server/src/api/routes/view.ts | 2 +- packages/server/src/db/utils.ts | 10 +++++++++- packages/server/src/sdk/app/views/index.ts | 4 +++- packages/server/src/tests/utilities/api/viewV2.ts | 8 ++------ 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index ea21c04bc3..be49b9f261 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -70,7 +70,6 @@ export enum DocumentType { USER_FLAG = "flag", AUTOMATION_METADATA = "meta_au", AUDIT_LOG = "al", - VIEW = "vi", } export const StaticDatabases = { diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 597dc54e66..9b4d91a822 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -13,8 +13,8 @@ export async function create(ctx: Ctx) { } export async function remove(ctx: Ctx) { - const { tableId, viewId } = ctx.params + const { viewId } = ctx.params - await sdk.views.remove(tableId, viewId) + await sdk.views.remove(viewId) ctx.status = 204 } diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index fa69023530..743b06f34d 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -60,7 +60,7 @@ describe("/v2/views", () => { expect(res).toEqual({ ...newView, - id: expect.stringMatching(new RegExp(`^vi_${config.table?._id!}_`)), + id: expect.stringMatching(new RegExp(`${config.table?._id!}_`)), version: 2, }) }) @@ -97,7 +97,7 @@ describe("/v2/views", () => { expect(await getPersistedView()).toBeDefined() - await config.api.viewV2.delete(tableId, view.id) + await config.api.viewV2.delete(view.id) expect(await getPersistedView()).toBeUndefined() }) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 010168aa13..ca93ec4de4 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -14,7 +14,7 @@ router viewController.v2.create ) .delete( - `/api/v2/views/:tableId/:viewId`, + `/api/v2/views/:viewId`, authorized(permissions.BUILDER), viewController.v2.remove ) diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index ba6ca88330..4d0981798b 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -277,5 +277,13 @@ export function getMultiIDParams(ids: string[]) { * @returns {string} The new view ID which the view doc can be stored under. */ export function generateViewID(tableId: string) { - return `${DocumentType.VIEW}${SEPARATOR}${tableId}${SEPARATOR}${newid()}` + return `${tableId}${SEPARATOR}${newid()}` +} + +export function extractViewInfoFromId(viewId: string) { + const regex = new RegExp(`^(?.+)${SEPARATOR}([^${SEPARATOR}]+)$`) + const res = regex.exec(viewId) + return { + tableId: res!.groups!["tableId"], + } } diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 8492de51a3..e2c37db562 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -38,9 +38,11 @@ export function isV2(view: View | ViewV2): view is ViewV2 { return (view as ViewV2).version === 2 } -export async function remove(tableId: string, viewId: string): Promise { +export async function remove( viewId: string): Promise { const db = context.getAppDB() + const {tableId}=utils.extractViewInfoFromId(viewId) + const table = await sdk.tables.getTable(tableId) const view = await get(tableId, viewId) if (!view) { diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 950b149cea..fe00f50dfe 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -31,13 +31,9 @@ export class ViewV2API extends TestAPI { return result.body.data as ViewV2 } - delete = async ( - tableId: string, - viewId: string, - { expectStatus } = { expectStatus: 204 } - ) => { + delete = async (viewId: string, { expectStatus } = { expectStatus: 204 }) => { return this.request - .delete(`/api/v2/views/${tableId}/${viewId}`) + .delete(`/api/v2/views/${viewId}`) .set(this.config.defaultHeaders()) .expect(expectStatus) } From 6d973ce99a4a5d0b4e4b85b3fe492b87e7a6f760 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 18:08:26 +0200 Subject: [PATCH 54/89] Infer table on search --- packages/server/src/api/controllers/row/index.ts | 10 +++++----- packages/server/src/api/routes/row.ts | 2 +- packages/server/src/api/routes/tests/row.spec.ts | 15 +++------------ packages/server/src/api/routes/view.ts | 2 +- packages/server/src/sdk/app/views/index.ts | 16 ++++++---------- .../server/src/tests/utilities/api/viewV2.ts | 8 ++------ 6 files changed, 18 insertions(+), 35 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 8a9947e707..8ba2f4b0ba 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -147,25 +147,25 @@ export async function search(ctx: any) { } export async function searchView(ctx: Ctx) { - const { tableId, viewId } = ctx.params + const { viewId } = ctx.params - const view = await sdk.views.get(tableId, viewId) + const view = await sdk.views.get(viewId) if (!view) { - ctx.throw(404, `View ${viewId} not found in table ${tableId}`) + ctx.throw(404, `View ${viewId} not found`) } ctx.status = 200 ctx.body = await quotas.addQuery( () => sdk.rows.search({ - tableId, + tableId: view.tableId, query: view.query || {}, sort: view.sort?.field, sortOrder: view.sort?.order, sortType: view.sort?.type, }), { - datasourceId: tableId, + datasourceId: view.tableId, } ) } diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index 1f71d3c1ae..5fdc02b7a7 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -147,7 +147,7 @@ router rowController.search ) .get( - "/api/v2/views/:tableId/:viewId/search", + "/api/v2/views/:viewId/search", authorized(PermissionType.VIEW, PermissionLevel.READ), rowController.searchView ) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index b082bacea0..24305ab261 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -716,10 +716,7 @@ describe("/rows", () => { } const createViewResponse = await config.api.viewV2.create() - const response = await config.api.viewV2.search( - createViewResponse.tableId, - createViewResponse.id - ) + const response = await config.api.viewV2.search(createViewResponse.id) expect(response.body.rows).toHaveLength(10) expect(response.body).toEqual({ @@ -753,10 +750,7 @@ describe("/rows", () => { } ) - const response = await config.api.viewV2.search( - createViewResponse.tableId, - createViewResponse.id - ) + const response = await config.api.viewV2.search(createViewResponse.id) expect(response.body.rows).toHaveLength(5) expect(response.body).toEqual({ @@ -846,10 +840,7 @@ describe("/rows", () => { } ) - const response = await config.api.viewV2.search( - createViewResponse.tableId, - createViewResponse.id - ) + const response = await config.api.viewV2.search(createViewResponse.id) expect(response.body.rows).toHaveLength(4) expect(response.body).toEqual({ diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index ca93ec4de4..7a79d2dc44 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -3,7 +3,7 @@ import * as viewController from "../controllers/view" import * as rowController from "../controllers/row" import authorized from "../../middleware/authorized" import { paramResource } from "../../middleware/resourceId" -import { DocumentType, SEPARATOR, permissions } from "@budibase/backend-core" +import { permissions } from "@budibase/backend-core" const router: Router = new Router() diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index e2c37db562..ea8f5dd591 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -4,10 +4,8 @@ import { View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import * as utils from "../../../db/utils" -export async function get( - tableId: string, - viewId: string -): Promise { +export async function get(viewId: string): Promise { + const { tableId } = utils.extractViewInfoFromId(viewId) const table = await sdk.tables.getTable(tableId) const views = Object.values(table.views!) const view = views.find(v => isV2(v) && v.id === viewId) as ViewV2 | undefined @@ -38,15 +36,13 @@ export function isV2(view: View | ViewV2): view is ViewV2 { return (view as ViewV2).version === 2 } -export async function remove( viewId: string): Promise { +export async function remove(viewId: string): Promise { const db = context.getAppDB() - const {tableId}=utils.extractViewInfoFromId(viewId) - - const table = await sdk.tables.getTable(tableId) - const view = await get(tableId, viewId) + const view = await get(viewId) + const table = await sdk.tables.getTable(view?.tableId) if (!view) { - throw new HTTPError(`View ${viewId} not found in table ${tableId}`, 404) + throw new HTTPError(`View ${viewId} not found`, 404) } delete table.views![view?.name] diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index fe00f50dfe..33ec9493c6 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -38,13 +38,9 @@ export class ViewV2API extends TestAPI { .expect(expectStatus) } - search = async ( - tableId: string, - viewId: string, - { expectStatus } = { expectStatus: 200 } - ) => { + search = async (viewId: string, { expectStatus } = { expectStatus: 200 }) => { return this.request - .get(`/api/v2/views/${tableId}/${viewId}/search`) + .get(`/api/v2/views/${viewId}/search`) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(expectStatus) From 56e6d48ec7891f02247057b8c8497cef2b159794 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 18:26:24 +0200 Subject: [PATCH 55/89] Remove :tableid from view create --- .../server/src/api/controllers/view/viewsV2.ts | 2 +- .../server/src/api/routes/tests/row.spec.ts | 18 ++++++------------ .../server/src/api/routes/tests/viewV2.spec.ts | 4 ++-- packages/server/src/api/routes/view.ts | 2 +- .../server/src/tests/utilities/api/viewV2.ts | 4 ++-- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 9b4d91a822..94e53e52fb 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -2,8 +2,8 @@ import sdk from "../../../sdk" import { CreateViewRequest, Ctx, ViewResponse } from "@budibase/types" export async function create(ctx: Ctx) { - const { tableId } = ctx.params const view = ctx.request.body + const { tableId } = view const result = await sdk.views.create(tableId, view) ctx.status = 201 diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 24305ab261..2890085c88 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -743,12 +743,9 @@ describe("/rows", () => { }) ) - const createViewResponse = await config.api.viewV2.create( - config.table?._id!, - { - query: { equal: { age: 40 } }, - } - ) + const createViewResponse = await config.api.viewV2.create({ + query: { equal: { age: 40 } }, + }) const response = await config.api.viewV2.search(createViewResponse.id) @@ -833,12 +830,9 @@ describe("/rows", () => { }) } - const createViewResponse = await config.api.viewV2.create( - config.table?._id!, - { - sort: sortParams, - } - ) + const createViewResponse = await config.api.viewV2.create({ + sort: sortParams, + }) const response = await config.api.viewV2.search(createViewResponse.id) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 743b06f34d..2dc7a0f6cb 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -56,7 +56,7 @@ describe("/v2/views", () => { name: generator.name(), tableId: config.table!._id!, } - const res = await config.api.viewV2.create(config.table?._id, newView) + const res = await config.api.viewV2.create(newView) expect(res).toEqual({ ...newView, @@ -71,7 +71,7 @@ describe("/v2/views", () => { tableId: config.table!._id!, ...viewFilters, } - const res = await config.api.viewV2.create(config.table!._id!, newView) + const res = await config.api.viewV2.create(newView) expect(res).toEqual({ ...newView, diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 7a79d2dc44..f8ae4abf0d 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -9,7 +9,7 @@ const router: Router = new Router() router .post( - "/api/v2/views/:tableId", + "/api/v2/views", authorized(permissions.BUILDER), viewController.v2.create ) diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 33ec9493c6..d79f324d85 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -9,10 +9,10 @@ export class ViewV2API extends TestAPI { } create = async ( - tableId?: string, viewData?: Partial, { expectStatus } = { expectStatus: 201 } ): Promise => { + let tableId = viewData?.tableId if (!tableId && !this.config.table) { throw "Test requires table to be configured." } @@ -23,7 +23,7 @@ export class ViewV2API extends TestAPI { ...viewData, } const result = await this.request - .post(`/api/v2/views/${tableId}`) + .post(`/api/v2/views`) .send(view) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) From fbccec64c760434aa5120b1b73090022e9eb65c9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Jul 2023 22:37:24 +0200 Subject: [PATCH 56/89] Renames --- packages/server/src/db/utils.ts | 2 +- packages/server/src/sdk/app/views/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 4d0981798b..266ddeb341 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -280,7 +280,7 @@ export function generateViewID(tableId: string) { return `${tableId}${SEPARATOR}${newid()}` } -export function extractViewInfoFromId(viewId: string) { +export function extractViewInfoFromID(viewId: string) { const regex = new RegExp(`^(?.+)${SEPARATOR}([^${SEPARATOR}]+)$`) const res = regex.exec(viewId) return { diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index ea8f5dd591..e4d6f061eb 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -5,7 +5,7 @@ import sdk from "../../../sdk" import * as utils from "../../../db/utils" export async function get(viewId: string): Promise { - const { tableId } = utils.extractViewInfoFromId(viewId) + const { tableId } = utils.extractViewInfoFromID(viewId) const table = await sdk.tables.getTable(tableId) const views = Object.values(table.views!) const view = views.find(v => isV2(v) && v.id === viewId) as ViewV2 | undefined From de9b74fd810384d4b8981589a06597f416381994 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 20 Jul 2023 10:47:51 +0200 Subject: [PATCH 57/89] Fix bbui build flakiness --- packages/bbui/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/bbui/package.json b/packages/bbui/package.json index b03c83d71b..4d39f6330b 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -96,7 +96,8 @@ "dependsOn": [ { "projects": [ - "@budibase/string-templates" + "@budibase/string-templates", + "@budibase/shared-core" ], "target": "build" } From e068e62eb15ea1409219a44be924f18a76a53186 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 11:11:00 +0200 Subject: [PATCH 58/89] Change view columns for schema --- packages/server/src/api/routes/tests/viewV2.spec.ts | 8 +++++++- packages/types/src/documents/app/view.ts | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 2dc7a0f6cb..5af82996b0 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -40,7 +40,13 @@ describe("/v2/views", () => { order: SortOrder.DESCENDING, type: SortType.STRING, }, - columns: ["name"], + schema: { + name: { + name: "name", + type: FieldType.STRING, + visible: true, + }, + }, } afterAll(setup.afterAll) diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index 781a1a8c5d..73037150d0 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,5 +1,6 @@ import { SortOrder, SortType } from "../../api" import { SearchFilters } from "../../sdk" +import { TableSchema } from "./table" export interface View { name: string @@ -24,7 +25,7 @@ export interface ViewV2 { order?: SortOrder type?: SortType } - columns?: string[] + schema?: TableSchema } export type ViewSchema = ViewCountOrSumSchema | ViewStatisticsSchema From 4e646bb46373ca68092b63a2e9ec9074081959e7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 12:00:59 +0200 Subject: [PATCH 59/89] Only ui metadata fields --- packages/server/src/api/routes/tests/viewV2.spec.ts | 4 +--- packages/types/src/documents/app/view.ts | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 5af82996b0..480580eb86 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -40,10 +40,8 @@ describe("/v2/views", () => { order: SortOrder.DESCENDING, type: SortType.STRING, }, - schema: { + columns: { name: { - name: "name", - type: FieldType.STRING, visible: true, }, }, diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index 73037150d0..aeef95ed5b 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,6 +1,6 @@ import { SortOrder, SortType } from "../../api" import { SearchFilters } from "../../sdk" -import { TableSchema } from "./table" +import { TableSchema, UIFieldMetadata } from "./table" export interface View { name: string @@ -25,7 +25,7 @@ export interface ViewV2 { order?: SortOrder type?: SortType } - schema?: TableSchema + columns?: Record } export type ViewSchema = ViewCountOrSumSchema | ViewStatisticsSchema From 1760980aad1c163e8e0deff147d694d71109adfc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 10:36:54 +0200 Subject: [PATCH 60/89] Allow overriding default sort --- .../server/src/api/controllers/row/index.ts | 51 +++++++++- .../server/src/api/routes/tests/row.spec.ts | 96 ++++++++++++++----- .../server/src/tests/utilities/api/viewV2.ts | 30 +++++- 3 files changed, 148 insertions(+), 29 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 8ba2f4b0ba..13986f4e4b 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(404, "sort_column cannot be an array") + } + if (Array.isArray(sort_order)) { + ctx.throw(404, "sort_order cannot be an array") + } + if (Array.isArray(sort_type)) { + ctx.throw(404, "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) From 7d84deea1a3b15308ebe5d3c25136342b1c26542 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 15:11:57 +0200 Subject: [PATCH 61/89] Fix return code --- packages/server/src/api/controllers/row/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 13986f4e4b..cc007e121c 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -164,13 +164,13 @@ function getSortOptions( | undefined { const { sort_column, sort_order, sort_type } = ctx.query if (Array.isArray(sort_column)) { - ctx.throw(404, "sort_column cannot be an array") + ctx.throw(400, "sort_column cannot be an array") } if (Array.isArray(sort_order)) { - ctx.throw(404, "sort_order cannot be an array") + ctx.throw(400, "sort_order cannot be an array") } if (Array.isArray(sort_type)) { - ctx.throw(404, "sort_type cannot be an array") + ctx.throw(400, "sort_type cannot be an array") } if (sort_column) { From f3f0ee095966b469aa9bd7f7c8176855a6f25914 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 20 Jul 2023 09:45:49 +0200 Subject: [PATCH 62/89] Return view schema endpoint --- .../src/api/controllers/view/viewsV2.ts | 14 ++++++++++- .../src/api/routes/tests/viewV2.spec.ts | 23 +++++++++++++++++++ packages/server/src/api/routes/view.ts | 5 ++++ packages/server/src/sdk/app/views/index.ts | 9 +++++++- .../server/src/tests/utilities/api/viewV2.ts | 13 ++++++++++- packages/types/src/api/web/app/view.ts | 6 ++++- 6 files changed, 66 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index 94e53e52fb..a6d53a1d81 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -1,5 +1,10 @@ import sdk from "../../../sdk" -import { CreateViewRequest, Ctx, ViewResponse } from "@budibase/types" +import { + CreateViewRequest, + Ctx, + ViewResponse, + ViewSchemaResponse, +} from "@budibase/types" export async function create(ctx: Ctx) { const view = ctx.request.body @@ -18,3 +23,10 @@ export async function remove(ctx: Ctx) { await sdk.views.remove(viewId) ctx.status = 204 } + +export async function getSchema(ctx: Ctx) { + const { viewId } = ctx.params + + const schema = await sdk.views.getSchema(viewId) + ctx.body = { schema } +} diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 480580eb86..35a36c38f7 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -106,4 +106,27 @@ describe("/v2/views", () => { expect(await getPersistedView()).toBeUndefined() }) }) + + describe("getSchema", () => { + let view: ViewV2 + + beforeAll(async () => { + await config.createTable(priceTable()) + view = await config.api.viewV2.create() + }) + + it("returns table schema if no columns are defined", async () => { + const result = await config.api.viewV2.getSchema(view.id) + expect(result).toEqual({ + schema: { + Price: { type: "number", name: "Price", constraints: {} }, + Category: { + type: "string", + name: "Category", + constraints: { type: "string" }, + }, + }, + }) + }) + }) }) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index f8ae4abf0d..53b556ed76 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -18,6 +18,11 @@ router authorized(permissions.BUILDER), viewController.v2.remove ) + .get( + `/api/v2/views/:viewId/schema`, + authorized(permissions.BUILDER), + viewController.v2.getSchema + ) router .get( diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index e4d6f061eb..5b11389e9a 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,5 +1,5 @@ import { HTTPError, context } from "@budibase/backend-core" -import { View, ViewV2 } from "@budibase/types" +import { TableSchema, View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import * as utils from "../../../db/utils" @@ -48,3 +48,10 @@ export async function remove(viewId: string): Promise { delete table.views![view?.name] await db.put(table) } + +export async function getSchema(viewId: string): Promise { + const view = await get(viewId) + const table = await sdk.tables.getTable(view?.tableId) + + return table.schema +} diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index d79f324d85..bcc0eb56c9 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 { ViewSchemaResponse, ViewV2 } from "@budibase/types" import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" import { generator } from "@budibase/backend-core/tests" @@ -38,6 +38,17 @@ export class ViewV2API extends TestAPI { .expect(expectStatus) } + getSchema = async ( + viewId: string, + { expectStatus } = { expectStatus: 200 } + ): Promise => { + const res = await this.request + .get(`/api/v2/views/${viewId}/schema`) + .set(this.config.defaultHeaders()) + .expect(expectStatus) + return res.body + } + search = async (viewId: string, { expectStatus } = { expectStatus: 200 }) => { return this.request .get(`/api/v2/views/${viewId}/search`) diff --git a/packages/types/src/api/web/app/view.ts b/packages/types/src/api/web/app/view.ts index 2cd371a025..11df32dec1 100644 --- a/packages/types/src/api/web/app/view.ts +++ b/packages/types/src/api/web/app/view.ts @@ -1,7 +1,11 @@ -import { ViewV2 } from "../../../documents" +import { TableSchema, ViewV2 } from "../../../documents" export interface ViewResponse { data: ViewV2 } export type CreateViewRequest = Omit + +export interface ViewSchemaResponse { + schema: TableSchema +} From c747881d738037bc68699b6a4302e1399cee4492 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 20 Jul 2023 09:54:22 +0200 Subject: [PATCH 63/89] Allow selecting certain columns --- .../server/src/api/routes/tests/viewV2.spec.ts | 18 +++++++++++++++--- packages/server/src/sdk/app/views/index.ts | 8 +++++++- 2 files changed, 22 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 35a36c38f7..e7ad2a0170 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -108,14 +108,12 @@ describe("/v2/views", () => { }) describe("getSchema", () => { - let view: ViewV2 - beforeAll(async () => { await config.createTable(priceTable()) - view = await config.api.viewV2.create() }) it("returns table schema if no columns are defined", async () => { + const view = await config.api.viewV2.create() const result = await config.api.viewV2.getSchema(view.id) expect(result).toEqual({ schema: { @@ -128,5 +126,19 @@ describe("/v2/views", () => { }, }) }) + + it("respects view column definition if exists", async () => { + const view = await config.api.viewV2.create({ columns: ["Category"] }) + const result = await config.api.viewV2.getSchema(view.id) + expect(result).toEqual({ + schema: { + Category: { + type: "string", + name: "Category", + constraints: { type: "string" }, + }, + }, + }) + }) }) }) diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 5b11389e9a..5decb6c201 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -3,6 +3,7 @@ import { TableSchema, View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import * as utils from "../../../db/utils" +import _ from "lodash" export async function get(viewId: string): Promise { const { tableId } = utils.extractViewInfoFromID(viewId) @@ -53,5 +54,10 @@ export async function getSchema(viewId: string): Promise { const view = await get(viewId) const table = await sdk.tables.getTable(view?.tableId) - return table.schema + if (!view?.columns?.length) { + return table.schema + } + + const schema = _.pick(table.schema, ...view.columns) + return schema } From d12d6f5beff9a24ac84c0268d576203455f3170a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 20 Jul 2023 09:58:01 +0200 Subject: [PATCH 64/89] Add tests --- .../src/api/routes/tests/viewV2.spec.ts | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index e7ad2a0170..1c22fa54cb 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -19,9 +19,14 @@ function priceTable(): Table { name: "Price", constraints: {}, }, - Category: { + Currency: { type: FieldType.STRING, - name: "Category", + name: "Currency", + constraints: {}, + }, + ItemId: { + type: FieldType.STRING, + name: "ItemId", constraints: { type: "string", }, @@ -117,25 +122,61 @@ describe("/v2/views", () => { const result = await config.api.viewV2.getSchema(view.id) expect(result).toEqual({ schema: { - Price: { type: "number", name: "Price", constraints: {} }, - Category: { + Price: { + type: "number", + name: "Price", + constraints: {}, + }, + Currency: { type: "string", - name: "Category", - constraints: { type: "string" }, + name: "Currency", + constraints: {}, + }, + ItemId: { + type: "string", + name: "ItemId", + constraints: { + type: "string", + }, }, }, }) }) it("respects view column definition if exists", async () => { - const view = await config.api.viewV2.create({ columns: ["Category"] }) + const view = await config.api.viewV2.create({ + columns: ["Price", "ItemId"], + }) const result = await config.api.viewV2.getSchema(view.id) expect(result).toEqual({ schema: { - Category: { + Price: { + type: "number", + name: "Price", + constraints: {}, + }, + ItemId: { type: "string", - name: "Category", - constraints: { type: "string" }, + name: "ItemId", + constraints: { + type: "string", + }, + }, + }, + }) + }) + + it("respects view column definition if exists", async () => { + const view = await config.api.viewV2.create({ + columns: ["Price", "innexistingColumn"], + }) + const result = await config.api.viewV2.getSchema(view.id) + expect(result).toEqual({ + schema: { + Price: { + type: "number", + name: "Price", + constraints: {}, }, }, }) From 46cb7c7117a2eaf3656590ee2715a6e191a2c0b1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 11:13:12 +0200 Subject: [PATCH 65/89] Table tests to ts --- .../tests/{table.spec.js => table.spec.ts} | 92 ++++++++++--------- .../server/src/tests/utilities/structures.ts | 11 ++- 2 files changed, 58 insertions(+), 45 deletions(-) rename packages/server/src/api/routes/tests/{table.spec.js => table.spec.ts} (80%) diff --git a/packages/server/src/api/routes/tests/table.spec.js b/packages/server/src/api/routes/tests/table.spec.ts similarity index 80% rename from packages/server/src/api/routes/tests/table.spec.js rename to packages/server/src/api/routes/tests/table.spec.ts index 9c6980c1d7..9e25ba3e7f 100644 --- a/packages/server/src/api/routes/tests/table.spec.js +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -1,12 +1,13 @@ -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const setup = require("./utilities") +import { checkBuilderEndpoint } from "./utilities/TestFunctions" +import * as setup from "./utilities" const { basicTable } = setup.structures -const { events, context } = require("@budibase/backend-core") +import { events, context } from "@budibase/backend-core" +import { FieldType, Table } from "@budibase/types" describe("/tables", () => { let request = setup.getRequest() let config = setup.getConfig() - let appId + let appId: string afterAll(setup.afterAll) @@ -16,12 +17,11 @@ describe("/tables", () => { }) describe("create", () => { - beforeEach(() => { jest.clearAllMocks() }) - const createTable = (table) => { + const createTable = (table?: Table) => { if (!table) { table = basicTable() } @@ -29,15 +29,16 @@ describe("/tables", () => { .post(`/api/tables`) .send(table) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) - } it("returns a success message when the table is successfully created", async () => { const res = await createTable() - expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.") + expect((res as any).res.statusMessage).toEqual( + "Table TestTable saved successfully." + ) expect(res.body.name).toEqual("TestTable") expect(events.table.created).toBeCalledTimes(1) expect(events.table.created).toBeCalledWith(res.body) @@ -45,7 +46,7 @@ describe("/tables", () => { it("creates a table via data import", async () => { const table = basicTable() - table.rows = [{ name: 'test-name', description: 'test-desc' }] + table.rows = [{ name: "test-name", description: "test-desc" }] const res = await createTable(table) @@ -62,7 +63,7 @@ describe("/tables", () => { config, method: "POST", url: `/api/tables`, - body: basicTable() + body: basicTable(), }) }) }) @@ -75,7 +76,7 @@ describe("/tables", () => { .post(`/api/tables`) .send(testTable) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(events.table.updated).toBeCalledTimes(1) @@ -94,10 +95,10 @@ describe("/tables", () => { const testRow = await request .post(`/api/${testTable._id}/rows`) .send({ - name: "test" + name: "test", }) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) const updatedTable = await request @@ -109,22 +110,24 @@ describe("/tables", () => { key: "name", _rename: { old: "name", - updated: "updatedName" + updated: "updatedName", }, schema: { - updatedName: { type: "string" } - } + updatedName: { type: "string" }, + }, }) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) - expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully.") + expect((updatedTable as any).res.statusMessage).toEqual( + "Table TestTable saved successfully." + ) expect(updatedTable.body.name).toEqual("TestTable") const res = await request .get(`/api/${testTable._id}/rows/${testRow.body._id}`) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.updatedName).toEqual("test") @@ -140,7 +143,7 @@ describe("/tables", () => { _id: "ta_users", }) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.schema.email).toBeDefined() expect(res.body.schema.roleId).toBeDefined() @@ -153,7 +156,7 @@ describe("/tables", () => { const table = await config.createTable() const importRequest = { schema: table.schema, - rows: [{ name: 'test-name', description: 'test-desc' }] + rows: [{ name: "test-name", description: "test-desc" }], } jest.clearAllMocks() @@ -162,20 +165,23 @@ describe("/tables", () => { .post(`/api/tables/${table._id}/import`) .send(importRequest) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(events.table.created).not.toHaveBeenCalled() expect(events.rows.imported).toBeCalledTimes(1) - expect(events.rows.imported).toBeCalledWith(expect.objectContaining({ - name: "TestTable", - _id: table._id - }), 1) + expect(events.rows.imported).toBeCalledWith( + expect.objectContaining({ + name: "TestTable", + _id: table._id, + }), + 1 + ) }) }) describe("fetch", () => { - let testTable + let testTable: Table beforeEach(async () => { testTable = await config.createTable(testTable) @@ -189,7 +195,7 @@ describe("/tables", () => { const res = await request .get(`/api/tables`) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) const fetchedTable = res.body[0] expect(fetchedTable.name).toEqual(testTable.name) @@ -216,7 +222,7 @@ describe("/tables", () => { .post(`/api/tables`) .send(table) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body._id).toBeDefined() expect(res.body._rev).toBeDefined() @@ -231,7 +237,7 @@ describe("/tables", () => { _rev: res.body._rev, }) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) // shouldn't have created a new index expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1) @@ -240,7 +246,7 @@ describe("/tables", () => { }) describe("destroy", () => { - let testTable + let testTable: Table beforeEach(async () => { testTable = await config.createTable(testTable) @@ -254,40 +260,44 @@ describe("/tables", () => { const res = await request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`) expect(events.table.deleted).toBeCalledTimes(1) - expect(events.table.deleted).toBeCalledWith({ ...testTable, tableId: testTable._id }) + expect(events.table.deleted).toBeCalledWith({ + ...testTable, + tableId: testTable._id, + }) }) it("deletes linked references to the table after deletion", async () => { const linkedTable = await config.createTable({ name: "LinkedTable", type: "table", - key: "name", schema: { name: { - type: "string", + type: FieldType.STRING, + name: "name", constraints: { type: "string", }, }, TestTable: { - type: "link", + type: FieldType.LINK, + name: "TestTable", fieldName: "TestTable", tableId: testTable._id, constraints: { - type: "array" - } - } + type: "array", + }, + }, }, }) const res = await request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) .set(config.defaultHeaders()) - .expect('Content-Type', /json/) + .expect("Content-Type", /json/) .expect(200) expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`) const dependentTable = await config.getTable(linkedTable._id) diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts index 8fb988f5fb..d3e92ea34d 100644 --- a/packages/server/src/tests/utilities/structures.ts +++ b/packages/server/src/tests/utilities/structures.ts @@ -16,23 +16,26 @@ import { AutomationTrigger, AutomationTriggerStepId, Datasource, + FieldType, SourceName, + Table, } from "@budibase/types" -export function basicTable() { +export function basicTable(): Table { return { name: "TestTable", type: "table", - key: "name", schema: { name: { - type: "string", + type: FieldType.STRING, + name: "name", constraints: { type: "string", }, }, description: { - type: "string", + type: FieldType.STRING, + name: "description", constraints: { type: "string", }, From 5fa7972564dfd185d2c8ec6c227a33cb2231e5f6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 11:21:22 +0200 Subject: [PATCH 66/89] Test returning views --- .../server/src/api/routes/tests/table.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 9e25ba3e7f..f5703681b5 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -209,6 +209,32 @@ describe("/tables", () => { url: `/api/tables`, }) }) + + it("should fetch views", async () => { + const tableId = config.table!._id! + const views = [ + await config.api.viewV2.create({ tableId }), + await config.api.viewV2.create({ tableId }), + ] + + const res = await request + .get(`/api/tables`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(res.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + _id: tableId, + views: views.reduce((p, c) => { + p[c.name] = c + return p + }, {} as any), + }), + ]) + ) + }) }) describe("indexing", () => { From 30138570d5b9775577d8ecf141ae22c2d088eb1a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 11:32:45 +0200 Subject: [PATCH 67/89] Typex --- .../server/src/api/controllers/table/index.ts | 25 +++++++------------ .../src/sdk/app/datasources/datasources.ts | 12 +++++++++ packages/types/src/api/web/app/index.ts | 1 + packages/types/src/api/web/app/table.ts | 3 +++ 4 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 packages/types/src/api/web/app/table.ts diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 5ade77592a..c2cdbbb06c 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -6,9 +6,8 @@ import { isRows, } from "../../../utilities/schema" import { isExternalTable, isSQL } from "../../../integrations/utils" -import { getDatasourceParams } from "../../../db/utils" -import { context, events } from "@budibase/backend-core" -import { Table, UserCtx } from "@budibase/types" +import { events } from "@budibase/backend-core" +import { FetchTablesResponse, Table, UserCtx } from "@budibase/types" import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" import { builderSocket } from "../../../websockets" @@ -26,25 +25,19 @@ function pickApi({ tableId, table }: { tableId?: string; table?: Table }) { } // covers both internal and external -export async function fetch(ctx: UserCtx) { - const db = context.getAppDB() - +export async function fetch(ctx: UserCtx) { const internal = await sdk.tables.getAllInternalTables() - const externalTables = await db.allDocs( - getDatasourceParams("plus", { - include_docs: true, - }) - ) + const externalTables = await sdk.datasources.getExternalDatasources() - const external = externalTables.rows.flatMap(tableDoc => { - let entities = tableDoc.doc.entities + const external = externalTables.flatMap(table => { + let entities = table.entities if (entities) { - return Object.values(entities).map((entity: any) => ({ + return Object.values(entities).map
((entity: Table) => ({ ...entity, type: "external", - sourceId: tableDoc.doc._id, - sql: isSQL(tableDoc.doc), + sourceId: table._id, + sql: isSQL(table), })) } else { return [] diff --git a/packages/server/src/sdk/app/datasources/datasources.ts b/packages/server/src/sdk/app/datasources/datasources.ts index 4145b1db63..993db04523 100644 --- a/packages/server/src/sdk/app/datasources/datasources.ts +++ b/packages/server/src/sdk/app/datasources/datasources.ts @@ -243,3 +243,15 @@ export function mergeConfigs(update: Datasource, old: Datasource) { return update } + +export async function getExternalDatasources(): Promise { + const db = context.getAppDB() + + const externalDatasources = await db.allDocs( + getDatasourceParams("plus", { + include_docs: true, + }) + ) + + return externalDatasources.rows.map(r => r.doc) +} diff --git a/packages/types/src/api/web/app/index.ts b/packages/types/src/api/web/app/index.ts index 5d12a31903..9c4aa35f57 100644 --- a/packages/types/src/api/web/app/index.ts +++ b/packages/types/src/api/web/app/index.ts @@ -2,3 +2,4 @@ export * from "./backup" export * from "./datasource" export * from "./view" export * from "./rows" +export * from "./table" diff --git a/packages/types/src/api/web/app/table.ts b/packages/types/src/api/web/app/table.ts new file mode 100644 index 0000000000..1044398500 --- /dev/null +++ b/packages/types/src/api/web/app/table.ts @@ -0,0 +1,3 @@ +import { Table } from "../../../documents" + +export type FetchTablesResponse = Table[] From 22dd218b1ae0259f27ebc725035d0ae7f727551a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 12:30:21 +0200 Subject: [PATCH 68/89] Fetch schema --- .../server/src/api/controllers/table/index.ts | 18 ++++++++- .../server/src/api/routes/tests/table.spec.ts | 38 ++++++++++++++++++- .../server/src/tests/utilities/api/table.ts | 11 ++++++ packages/types/src/api/web/app/table.ts | 14 ++++++- 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index c2cdbbb06c..7450b3250e 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -11,6 +11,7 @@ import { FetchTablesResponse, Table, UserCtx } from "@budibase/types" import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" import { builderSocket } from "../../../websockets" +import _ from "lodash" function pickApi({ tableId, table }: { tableId?: string; table?: Table }) { if (table && !tableId) { @@ -44,7 +45,22 @@ export async function fetch(ctx: UserCtx) { } }) - ctx.body = [...internal, ...external] + const tables = [...internal, ...external] + + for (const t of tables.filter(t => t.views)) { + for (const [viewName, view] of Object.entries(t.views!)) { + if (sdk.views.isV2(view)) { + t.views![viewName] = { + ...view, + schema: !view?.columns?.length + ? t.schema + : _.pick(t.schema, ...view.columns), + } + } + } + } + + ctx.body = tables as FetchTablesResponse } export async function find(ctx: UserCtx) { diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index f5703681b5..b8520760a7 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -228,13 +228,49 @@ describe("/tables", () => { expect.objectContaining({ _id: tableId, views: views.reduce((p, c) => { - p[c.name] = c + p[c.name] = { ...c, schema: expect.anything() } return p }, {} as any), }), ]) ) }) + + it("should fetch the default schema if not overriden", async () => { + const tableId = config.table!._id! + const view = await config.api.viewV2.create({ tableId }) + + const res = await config.api.table.fetch() + + expect(res).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + _id: tableId, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + constraints: { + type: "string", + }, + }, + description: { + type: "string", + name: "description", + constraints: { + type: "string", + }, + }, + }, + }, + }, + }), + ]) + ) + }) }) describe("indexing", () => { diff --git a/packages/server/src/tests/utilities/api/table.ts b/packages/server/src/tests/utilities/api/table.ts index 7e3ef6efa4..70f0869650 100644 --- a/packages/server/src/tests/utilities/api/table.ts +++ b/packages/server/src/tests/utilities/api/table.ts @@ -7,6 +7,17 @@ export class TableAPI extends TestAPI { super(config) } + fetch = async ( + { expectStatus } = { expectStatus: 200 } + ): Promise => { + const res = await this.request + .get(`/api/tables`) + .set(this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(expectStatus) + return res.body + } + get = async ( tableId: string, { expectStatus } = { expectStatus: 200 } diff --git a/packages/types/src/api/web/app/table.ts b/packages/types/src/api/web/app/table.ts index 1044398500..2075d3b338 100644 --- a/packages/types/src/api/web/app/table.ts +++ b/packages/types/src/api/web/app/table.ts @@ -1,3 +1,13 @@ -import { Table } from "../../../documents" +import { Table, TableSchema, View, ViewV2 } from "../../../documents" -export type FetchTablesResponse = Table[] +interface ViewV2Response extends ViewV2 { + schema: TableSchema +} + +type TableViewsResponse = { [key: string]: View | ViewV2Response } + +interface TableResponse extends Table { + views?: TableViewsResponse +} + +export type FetchTablesResponse = TableResponse[] From 3f2fa1a8dc71e6a271bed2da1190fb816bf70c14 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 12:30:33 +0200 Subject: [PATCH 69/89] Remove view endpoint --- .../src/api/controllers/view/viewsV2.ts | 14 +- .../src/api/routes/tests/viewV2.spec.ts | 134 +++++++++--------- packages/server/src/api/routes/view.ts | 5 - packages/server/src/sdk/app/views/index.ts | 15 +- .../server/src/tests/utilities/api/viewV2.ts | 13 +- packages/types/src/api/web/app/view.ts | 4 - 6 files changed, 70 insertions(+), 115 deletions(-) diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index a6d53a1d81..94e53e52fb 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -1,10 +1,5 @@ import sdk from "../../../sdk" -import { - CreateViewRequest, - Ctx, - ViewResponse, - ViewSchemaResponse, -} from "@budibase/types" +import { CreateViewRequest, Ctx, ViewResponse } from "@budibase/types" export async function create(ctx: Ctx) { const view = ctx.request.body @@ -23,10 +18,3 @@ export async function remove(ctx: Ctx) { await sdk.views.remove(viewId) ctx.status = 204 } - -export async function getSchema(ctx: Ctx) { - const { viewId } = ctx.params - - const schema = await sdk.views.getSchema(viewId) - ctx.body = { schema } -} diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 1c22fa54cb..22c12fcccf 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -112,74 +112,74 @@ describe("/v2/views", () => { }) }) - describe("getSchema", () => { - beforeAll(async () => { - await config.createTable(priceTable()) - }) + // describe("getSchema", () => { + // beforeAll(async () => { + // await config.createTable(priceTable()) + // }) - it("returns table schema if no columns are defined", async () => { - const view = await config.api.viewV2.create() - const result = await config.api.viewV2.getSchema(view.id) - expect(result).toEqual({ - schema: { - Price: { - type: "number", - name: "Price", - constraints: {}, - }, - Currency: { - type: "string", - name: "Currency", - constraints: {}, - }, - ItemId: { - type: "string", - name: "ItemId", - constraints: { - type: "string", - }, - }, - }, - }) - }) + // it("returns table schema if no columns are defined", async () => { + // const view = await config.api.viewV2.create() + // const result = await config.api.viewV2.getSchema(view.id) + // expect(result).toEqual({ + // schema: { + // Price: { + // type: "number", + // name: "Price", + // constraints: {}, + // }, + // Currency: { + // type: "string", + // name: "Currency", + // constraints: {}, + // }, + // ItemId: { + // type: "string", + // name: "ItemId", + // constraints: { + // type: "string", + // }, + // }, + // }, + // }) + // }) - it("respects view column definition if exists", async () => { - const view = await config.api.viewV2.create({ - columns: ["Price", "ItemId"], - }) - const result = await config.api.viewV2.getSchema(view.id) - expect(result).toEqual({ - schema: { - Price: { - type: "number", - name: "Price", - constraints: {}, - }, - ItemId: { - type: "string", - name: "ItemId", - constraints: { - type: "string", - }, - }, - }, - }) - }) + // it("respects view column definition if exists", async () => { + // const view = await config.api.viewV2.create({ + // columns: ["Price", "ItemId"], + // }) + // const result = await config.api.viewV2.getSchema(view.id) + // expect(result).toEqual({ + // schema: { + // Price: { + // type: "number", + // name: "Price", + // constraints: {}, + // }, + // ItemId: { + // type: "string", + // name: "ItemId", + // constraints: { + // type: "string", + // }, + // }, + // }, + // }) + // }) - it("respects view column definition if exists", async () => { - const view = await config.api.viewV2.create({ - columns: ["Price", "innexistingColumn"], - }) - const result = await config.api.viewV2.getSchema(view.id) - expect(result).toEqual({ - schema: { - Price: { - type: "number", - name: "Price", - constraints: {}, - }, - }, - }) - }) - }) + // it("respects view column definition if exists", async () => { + // const view = await config.api.viewV2.create({ + // columns: ["Price", "innexistingColumn"], + // }) + // const result = await config.api.viewV2.getSchema(view.id) + // expect(result).toEqual({ + // schema: { + // Price: { + // type: "number", + // name: "Price", + // constraints: {}, + // }, + // }, + // }) + // }) + // }) }) diff --git a/packages/server/src/api/routes/view.ts b/packages/server/src/api/routes/view.ts index 53b556ed76..f8ae4abf0d 100644 --- a/packages/server/src/api/routes/view.ts +++ b/packages/server/src/api/routes/view.ts @@ -18,11 +18,6 @@ router authorized(permissions.BUILDER), viewController.v2.remove ) - .get( - `/api/v2/views/:viewId/schema`, - authorized(permissions.BUILDER), - viewController.v2.getSchema - ) router .get( diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 5decb6c201..e4d6f061eb 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,9 +1,8 @@ import { HTTPError, context } from "@budibase/backend-core" -import { TableSchema, View, ViewV2 } from "@budibase/types" +import { View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import * as utils from "../../../db/utils" -import _ from "lodash" export async function get(viewId: string): Promise { const { tableId } = utils.extractViewInfoFromID(viewId) @@ -49,15 +48,3 @@ export async function remove(viewId: string): Promise { delete table.views![view?.name] await db.put(table) } - -export async function getSchema(viewId: string): Promise { - const view = await get(viewId) - const table = await sdk.tables.getTable(view?.tableId) - - if (!view?.columns?.length) { - return table.schema - } - - const schema = _.pick(table.schema, ...view.columns) - return schema -} diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index bcc0eb56c9..d79f324d85 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -1,4 +1,4 @@ -import { ViewSchemaResponse, ViewV2 } from "@budibase/types" +import { ViewV2 } from "@budibase/types" import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" import { generator } from "@budibase/backend-core/tests" @@ -38,17 +38,6 @@ export class ViewV2API extends TestAPI { .expect(expectStatus) } - getSchema = async ( - viewId: string, - { expectStatus } = { expectStatus: 200 } - ): Promise => { - const res = await this.request - .get(`/api/v2/views/${viewId}/schema`) - .set(this.config.defaultHeaders()) - .expect(expectStatus) - return res.body - } - search = async (viewId: string, { expectStatus } = { expectStatus: 200 }) => { return this.request .get(`/api/v2/views/${viewId}/search`) diff --git a/packages/types/src/api/web/app/view.ts b/packages/types/src/api/web/app/view.ts index 11df32dec1..e5c5855c1b 100644 --- a/packages/types/src/api/web/app/view.ts +++ b/packages/types/src/api/web/app/view.ts @@ -5,7 +5,3 @@ export interface ViewResponse { } export type CreateViewRequest = Omit - -export interface ViewSchemaResponse { - schema: TableSchema -} From a65e69e614923e427e0c48dcee8a6378edba1bfd Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 12:38:02 +0200 Subject: [PATCH 70/89] Add tests --- .../server/src/api/routes/tests/table.spec.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index b8520760a7..bd36b35c1e 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -271,6 +271,89 @@ describe("/tables", () => { ]) ) }) + + it("should fetch the default schema if not overriden", async () => { + const tableId = config.table!._id! + const views = [ + await config.api.viewV2.create({ tableId }), + await config.api.viewV2.create({ tableId, columns: ["name"] }), + ] + + const res = await config.api.table.fetch() + + expect(res).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + _id: tableId, + views: { + [views[0].name]: { + ...views[0], + schema: { + name: { + type: "string", + name: "name", + constraints: { + type: "string", + }, + }, + description: { + type: "string", + name: "description", + constraints: { + type: "string", + }, + }, + }, + }, + [views[1].name]: { + ...views[1], + schema: { + name: { + type: "string", + name: "name", + constraints: { + type: "string", + }, + }, + }, + }, + }, + }), + ]) + ) + }) + + it("should fetch the default schema if not overriden", async () => { + const tableId = config.table!._id! + const view = await config.api.viewV2.create({ + tableId, + columns: ["unnexisting", "name"], + }) + + const res = await config.api.table.fetch() + + expect(res).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + _id: tableId, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + constraints: { + type: "string", + }, + }, + }, + }, + }, + }), + ]) + ) + }) }) describe("indexing", () => { From ed02aa4d8bca0d0dbca60b745599b332c1183e18 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 13:45:33 +0200 Subject: [PATCH 71/89] Enrich view schemas --- .../server/src/api/controllers/table/index.ts | 52 ++++++++++++++----- packages/types/src/api/web/app/table.ts | 4 +- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 7450b3250e..42bfedda8b 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -7,7 +7,13 @@ import { } from "../../../utilities/schema" import { isExternalTable, isSQL } from "../../../integrations/utils" import { events } from "@budibase/backend-core" -import { FetchTablesResponse, Table, UserCtx } from "@budibase/types" +import { + FetchTablesResponse, + Table, + TableResponse, + TableViewsResponse, + UserCtx, +} from "@budibase/types" import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" import { builderSocket } from "../../../websockets" @@ -45,27 +51,47 @@ export async function fetch(ctx: UserCtx) { } }) - const tables = [...internal, ...external] + const response = [...internal, ...external].map(enrichTable) + ctx.body = response +} - for (const t of tables.filter(t => t.views)) { - for (const [viewName, view] of Object.entries(t.views!)) { - if (sdk.views.isV2(view)) { - t.views![viewName] = { - ...view, - schema: !view?.columns?.length - ? t.schema - : _.pick(t.schema, ...view.columns), +function enrichTable(table: Table): TableResponse { + const result: TableResponse = { + ...table, + views: Object.values(table.views ?? []).reduce((p, v) => { + if (!sdk.views.isV2(v)) { + p[v.name] = v + } else { + p[v.name] = { + ...v, + schema: !v?.columns?.length + ? table.schema + : _.pick(table.schema, ...v.columns), } } + return p + }, {} as TableViewsResponse), + } + + for (const [viewName, view] of Object.entries(table.views!)) { + if (sdk.views.isV2(view)) { + table.views![viewName] = { + ...view, + schema: !view?.columns?.length + ? table.schema + : _.pick(table.schema, ...view.columns), + } } } - ctx.body = tables as FetchTablesResponse + return result } -export async function find(ctx: UserCtx) { +export async function find(ctx: UserCtx) { const tableId = ctx.params.tableId - ctx.body = await sdk.tables.getTable(tableId) + const table = await sdk.tables.getTable(tableId) + + ctx.body = enrichTable(table) } export async function save(ctx: UserCtx) { diff --git a/packages/types/src/api/web/app/table.ts b/packages/types/src/api/web/app/table.ts index 2075d3b338..178f758254 100644 --- a/packages/types/src/api/web/app/table.ts +++ b/packages/types/src/api/web/app/table.ts @@ -4,9 +4,9 @@ interface ViewV2Response extends ViewV2 { schema: TableSchema } -type TableViewsResponse = { [key: string]: View | ViewV2Response } +export type TableViewsResponse = { [key: string]: View | ViewV2Response } -interface TableResponse extends Table { +export interface TableResponse extends Table { views?: TableViewsResponse } From 396c4ad4391cd7f0f9f65e0101d2156ec2371aaa Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 12:40:55 +0200 Subject: [PATCH 72/89] Use new table schema --- .../server/src/api/controllers/table/index.ts | 39 ++++--- .../server/src/api/routes/tests/table.spec.ts | 106 +++++++++++++++++- .../src/api/routes/tests/viewV2.spec.ts | 71 ------------ 3 files changed, 127 insertions(+), 89 deletions(-) diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 42bfedda8b..ab6c819a63 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -11,7 +11,9 @@ import { FetchTablesResponse, Table, TableResponse, + TableSchema, TableViewsResponse, + UIFieldMetadata, UserCtx, } from "@budibase/types" import sdk from "../../../sdk" @@ -55,6 +57,25 @@ export async function fetch(ctx: UserCtx) { ctx.body = response } +function enrichSchema( + tableSchema: TableSchema, + viewOverrides: Record +) { + const result: TableSchema = {} + for (const [columnName, columnUIMetadata] of Object.entries(viewOverrides)) { + if (!columnUIMetadata.visible) { + continue + } + + if (!tableSchema[columnName]) { + continue + } + + result[columnName] = _.merge(tableSchema[columnName], columnUIMetadata) + } + return result +} + function enrichTable(table: Table): TableResponse { const result: TableResponse = { ...table, @@ -64,26 +85,16 @@ function enrichTable(table: Table): TableResponse { } else { p[v.name] = { ...v, - schema: !v?.columns?.length - ? table.schema - : _.pick(table.schema, ...v.columns), + schema: + !v?.columns || !Object.entries(v?.columns).length + ? table.schema + : enrichSchema(table.schema, v.columns), } } return p }, {} as TableViewsResponse), } - for (const [viewName, view] of Object.entries(table.views!)) { - if (sdk.views.isV2(view)) { - table.views![viewName] = { - ...view, - schema: !view?.columns?.length - ? table.schema - : _.pick(table.schema, ...view.columns), - } - } - } - return result } diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index bd36b35c1e..297bbc79a5 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -184,7 +184,46 @@ describe("/tables", () => { let testTable: Table beforeEach(async () => { - testTable = await config.createTable(testTable) + testTable = await config.createTable({ + name: "TestTable", + type: "table", + schema: { + name: { + type: FieldType.STRING, + name: "name", + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + description: { + type: FieldType.STRING, + name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: FieldType.NUMBER, + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: FieldType.STRING, + name: "hiddenField", + visible: false, + constraints: { + type: "string", + }, + }, + }, + }) }) afterEach(() => { @@ -253,6 +292,8 @@ describe("/tables", () => { name: { type: "string", name: "name", + visible: true, + width: 80, constraints: { type: "string", }, @@ -260,6 +301,24 @@ describe("/tables", () => { description: { type: "string", name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: "string", + name: "hiddenField", + visible: false, constraints: { type: "string", }, @@ -273,10 +332,17 @@ describe("/tables", () => { }) it("should fetch the default schema if not overriden", async () => { - const tableId = config.table!._id! + const tableId = testTable._id! const views = [ await config.api.viewV2.create({ tableId }), - await config.api.viewV2.create({ tableId, columns: ["name"] }), + await config.api.viewV2.create({ + tableId, + columns: { + name: { visible: true }, + id: { visible: true }, + description: { visible: false }, + }, + }), ] const res = await config.api.table.fetch() @@ -292,6 +358,8 @@ describe("/tables", () => { name: { type: "string", name: "name", + visible: true, + width: 80, constraints: { type: "string", }, @@ -299,6 +367,24 @@ describe("/tables", () => { description: { type: "string", name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: "string", + name: "hiddenField", + visible: false, constraints: { type: "string", }, @@ -311,10 +397,20 @@ describe("/tables", () => { name: { type: "string", name: "name", + visible: true, + width: 80, constraints: { type: "string", }, }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, }, }, }, @@ -327,7 +423,7 @@ describe("/tables", () => { const tableId = config.table!._id! const view = await config.api.viewV2.create({ tableId, - columns: ["unnexisting", "name"], + columns: { unnexisting: { visible: true }, name: { visible: true } }, }) const res = await config.api.table.fetch() @@ -343,6 +439,8 @@ describe("/tables", () => { name: { type: "string", name: "name", + visible: true, + width: 80, constraints: { type: "string", }, diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 22c12fcccf..c7e7698f75 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -111,75 +111,4 @@ describe("/v2/views", () => { expect(await getPersistedView()).toBeUndefined() }) }) - - // describe("getSchema", () => { - // beforeAll(async () => { - // await config.createTable(priceTable()) - // }) - - // it("returns table schema if no columns are defined", async () => { - // const view = await config.api.viewV2.create() - // const result = await config.api.viewV2.getSchema(view.id) - // expect(result).toEqual({ - // schema: { - // Price: { - // type: "number", - // name: "Price", - // constraints: {}, - // }, - // Currency: { - // type: "string", - // name: "Currency", - // constraints: {}, - // }, - // ItemId: { - // type: "string", - // name: "ItemId", - // constraints: { - // type: "string", - // }, - // }, - // }, - // }) - // }) - - // it("respects view column definition if exists", async () => { - // const view = await config.api.viewV2.create({ - // columns: ["Price", "ItemId"], - // }) - // const result = await config.api.viewV2.getSchema(view.id) - // expect(result).toEqual({ - // schema: { - // Price: { - // type: "number", - // name: "Price", - // constraints: {}, - // }, - // ItemId: { - // type: "string", - // name: "ItemId", - // constraints: { - // type: "string", - // }, - // }, - // }, - // }) - // }) - - // it("respects view column definition if exists", async () => { - // const view = await config.api.viewV2.create({ - // columns: ["Price", "innexistingColumn"], - // }) - // const result = await config.api.viewV2.getSchema(view.id) - // expect(result).toEqual({ - // schema: { - // Price: { - // type: "number", - // name: "Price", - // constraints: {}, - // }, - // }, - // }) - // }) - // }) }) From d2020fd6bc379cf1a2ca50d2d5833064ccf3714f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 12:44:00 +0200 Subject: [PATCH 73/89] Move enrich views to sdk --- .../server/src/api/controllers/table/index.ts | 49 +---------------- packages/server/src/sdk/app/tables/index.ts | 53 ++++++++++++++++++- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index ab6c819a63..53202d6878 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -11,15 +11,11 @@ import { FetchTablesResponse, Table, TableResponse, - TableSchema, - TableViewsResponse, - UIFieldMetadata, UserCtx, } from "@budibase/types" import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" import { builderSocket } from "../../../websockets" -import _ from "lodash" function pickApi({ tableId, table }: { tableId?: string; table?: Table }) { if (table && !tableId) { @@ -53,56 +49,15 @@ export async function fetch(ctx: UserCtx) { } }) - const response = [...internal, ...external].map(enrichTable) + const response = [...internal, ...external].map(sdk.tables.enrichViewSchemas) ctx.body = response } -function enrichSchema( - tableSchema: TableSchema, - viewOverrides: Record -) { - const result: TableSchema = {} - for (const [columnName, columnUIMetadata] of Object.entries(viewOverrides)) { - if (!columnUIMetadata.visible) { - continue - } - - if (!tableSchema[columnName]) { - continue - } - - result[columnName] = _.merge(tableSchema[columnName], columnUIMetadata) - } - return result -} - -function enrichTable(table: Table): TableResponse { - const result: TableResponse = { - ...table, - views: Object.values(table.views ?? []).reduce((p, v) => { - if (!sdk.views.isV2(v)) { - p[v.name] = v - } else { - p[v.name] = { - ...v, - schema: - !v?.columns || !Object.entries(v?.columns).length - ? table.schema - : enrichSchema(table.schema, v.columns), - } - } - return p - }, {} as TableViewsResponse), - } - - return result -} - export async function find(ctx: UserCtx) { const tableId = ctx.params.tableId const table = await sdk.tables.getTable(tableId) - ctx.body = enrichTable(table) + ctx.body = sdk.tables.enrichViewSchemas(table) } export async function save(ctx: UserCtx) { diff --git a/packages/server/src/sdk/app/tables/index.ts b/packages/server/src/sdk/app/tables/index.ts index 65010aeaa4..b94ca58538 100644 --- a/packages/server/src/sdk/app/tables/index.ts +++ b/packages/server/src/sdk/app/tables/index.ts @@ -5,9 +5,18 @@ import { isExternalTable, isSQL, } from "../../../integrations/utils" -import { Table, Database } from "@budibase/types" +import { + Table, + Database, + TableResponse, + TableViewsResponse, + TableSchema, + UIFieldMetadata, +} from "@budibase/types" import datasources from "../datasources" import { populateExternalTableSchemas, isEditableColumn } from "./validation" +import sdk from "../../../sdk" +import _ from "lodash" async function getAllInternalTables(db?: Database): Promise { if (!db) { @@ -55,6 +64,47 @@ async function getTable(tableId: any): Promise
{ } } +function enrichSchema( + tableSchema: TableSchema, + viewOverrides: Record +) { + const result: TableSchema = {} + for (const [columnName, columnUIMetadata] of Object.entries(viewOverrides)) { + if (!columnUIMetadata.visible) { + continue + } + + if (!tableSchema[columnName]) { + continue + } + + result[columnName] = _.merge(tableSchema[columnName], columnUIMetadata) + } + return result +} + +function enrichViewSchemas(table: Table): TableResponse { + const result: TableResponse = { + ...table, + views: Object.values(table.views ?? []).reduce((p, v) => { + if (!sdk.views.isV2(v)) { + p[v.name] = v + } else { + p[v.name] = { + ...v, + schema: + !v?.columns || !Object.entries(v?.columns).length + ? table.schema + : enrichSchema(table.schema, v.columns), + } + } + return p + }, {} as TableViewsResponse), + } + + return result +} + export default { getAllInternalTables, getAllExternalTables, @@ -62,4 +112,5 @@ export default { getTable, populateExternalTableSchemas, isEditableColumn, + enrichViewSchemas, } From 810bb415471afabc3e18c6a31871ca1202ed6ed9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 13:37:11 +0200 Subject: [PATCH 74/89] Add sdk tests --- .../src/sdk/app/tables/tests/tables.spec.ts | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 packages/server/src/sdk/app/tables/tests/tables.spec.ts diff --git a/packages/server/src/sdk/app/tables/tests/tables.spec.ts b/packages/server/src/sdk/app/tables/tests/tables.spec.ts new file mode 100644 index 0000000000..48bcac7920 --- /dev/null +++ b/packages/server/src/sdk/app/tables/tests/tables.spec.ts @@ -0,0 +1,198 @@ +import { FieldType, Table, ViewV2 } from "@budibase/types" +import { generator } from "@budibase/backend-core/tests" +import sdk from "../../.." + +describe("table sdk", () => { + describe("enrichViewSchemas", () => { + describe("fetch", () => { + const basicTable: Table = { + _id: generator.guid(), + name: "TestTable", + type: "table", + schema: { + name: { + type: FieldType.STRING, + name: "name", + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + description: { + type: FieldType.STRING, + name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: FieldType.NUMBER, + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: FieldType.STRING, + name: "hiddenField", + visible: false, + constraints: { + type: "string", + }, + }, + }, + } + + it("should fetch the default schema if not overriden", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + } + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + description: { + type: "string", + name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: "string", + name: "hiddenField", + visible: false, + constraints: { + type: "string", + }, + }, + }, + }, + }, + }) + }) + + it("if view schema only defines visiblility, should only fetch the selected fields", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true }, + id: { visible: true }, + description: { visible: false }, + }, + } + + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual( + expect.objectContaining({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + }, + }, + }, + }) + ) + }) + + it("schema does not break if the view has corrupted columns", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { unnexisting: { visible: true }, name: { visible: true } }, + } + + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual( + expect.objectContaining({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + }, + }, + }, + }) + ) + }) + }) + }) +}) From 559cb3d9e0a4f835a79664620c588e5951ab9422 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 14:57:16 +0200 Subject: [PATCH 75/89] Test sdk calls --- .../server/src/api/routes/tests/table.spec.ts | 188 +++--------------- 1 file changed, 25 insertions(+), 163 deletions(-) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 297bbc79a5..1dfba9d7cf 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -1,8 +1,10 @@ +import { generator } from "@budibase/backend-core/tests" +import { events, context } from "@budibase/backend-core" +import { FieldType, Table } from "@budibase/types" import { checkBuilderEndpoint } from "./utilities/TestFunctions" import * as setup from "./utilities" const { basicTable } = setup.structures -import { events, context } from "@budibase/backend-core" -import { FieldType, Table } from "@budibase/types" +import sdk from "../../../sdk" describe("/tables", () => { let request = setup.getRequest() @@ -275,75 +277,23 @@ describe("/tables", () => { ) }) - it("should fetch the default schema if not overriden", async () => { + it("should enrich the view schemas for viewsV2", async () => { const tableId = config.table!._id! - const view = await config.api.viewV2.create({ tableId }) - - const res = await config.api.table.fetch() - - expect(res).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - _id: tableId, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - description: { - type: "string", - name: "description", - visible: true, - width: 200, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - visible: true, - constraints: { - type: "number", - }, - }, - hiddenField: { - type: "string", - name: "hiddenField", - visible: false, - constraints: { - type: "string", - }, - }, - }, - }, - }, - }), - ]) - ) - }) - - it("should fetch the default schema if not overriden", async () => { - const tableId = testTable._id! - const views = [ - await config.api.viewV2.create({ tableId }), - await config.api.viewV2.create({ - tableId, - columns: { - name: { visible: true }, - id: { visible: true }, - description: { visible: false }, + jest.spyOn(sdk.tables, "enrichViewSchemas").mockImplementation(t => ({ + ...t, + views: { + view1: { + version: 2, + name: "view1", + schema: {}, + id: "new_view_id", + tableId, }, - }), - ] + }, + })) + + await config.api.viewV2.create({ tableId }) + await config.createView({ tableId, name: generator.guid() }) const res = await config.api.table.fetch() @@ -352,100 +302,12 @@ describe("/tables", () => { expect.objectContaining({ _id: tableId, views: { - [views[0].name]: { - ...views[0], - schema: { - name: { - type: "string", - name: "name", - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - description: { - type: "string", - name: "description", - visible: true, - width: 200, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - visible: true, - constraints: { - type: "number", - }, - }, - hiddenField: { - type: "string", - name: "hiddenField", - visible: false, - constraints: { - type: "string", - }, - }, - }, - }, - [views[1].name]: { - ...views[1], - schema: { - name: { - type: "string", - name: "name", - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - visible: true, - constraints: { - type: "number", - }, - }, - }, - }, - }, - }), - ]) - ) - }) - - it("should fetch the default schema if not overriden", async () => { - const tableId = config.table!._id! - const view = await config.api.viewV2.create({ - tableId, - columns: { unnexisting: { visible: true }, name: { visible: true } }, - }) - - const res = await config.api.table.fetch() - - expect(res).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - _id: tableId, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - }, + view1: { + version: 2, + name: "view1", + schema: {}, + id: "new_view_id", + tableId, }, }, }), From 0f53fa14ad35f84189072256f4826df57e260c69 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 15:00:38 +0200 Subject: [PATCH 76/89] Undo not required changes --- .../server/src/api/routes/tests/table.spec.ts | 41 +------------------ .../src/api/routes/tests/viewV2.spec.ts | 9 +--- 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 1dfba9d7cf..04911e5505 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -186,46 +186,7 @@ describe("/tables", () => { let testTable: Table beforeEach(async () => { - testTable = await config.createTable({ - name: "TestTable", - type: "table", - schema: { - name: { - type: FieldType.STRING, - name: "name", - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - description: { - type: FieldType.STRING, - name: "description", - visible: true, - width: 200, - constraints: { - type: "string", - }, - }, - id: { - type: FieldType.NUMBER, - name: "id", - visible: true, - constraints: { - type: "number", - }, - }, - hiddenField: { - type: FieldType.STRING, - name: "hiddenField", - visible: false, - constraints: { - type: "string", - }, - }, - }, - }) + testTable = await config.createTable(testTable) }) afterEach(() => { diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index c7e7698f75..480580eb86 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -19,14 +19,9 @@ function priceTable(): Table { name: "Price", constraints: {}, }, - Currency: { + Category: { type: FieldType.STRING, - name: "Currency", - constraints: {}, - }, - ItemId: { - type: FieldType.STRING, - name: "ItemId", + name: "Category", constraints: { type: "string", }, From 8e904cea13c9bd116bda0e1cc42be5c60a987e52 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 15:05:20 +0200 Subject: [PATCH 77/89] Set order to the tests --- .../src/sdk/app/tables/tests/tables.spec.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/server/src/sdk/app/tables/tests/tables.spec.ts b/packages/server/src/sdk/app/tables/tests/tables.spec.ts index 48bcac7920..a474f2aa43 100644 --- a/packages/server/src/sdk/app/tables/tests/tables.spec.ts +++ b/packages/server/src/sdk/app/tables/tests/tables.spec.ts @@ -15,6 +15,7 @@ describe("table sdk", () => { name: "name", visible: true, width: 80, + order: 2, constraints: { type: "string", }, @@ -32,6 +33,7 @@ describe("table sdk", () => { type: FieldType.NUMBER, name: "id", visible: true, + order: 1, constraints: { type: "number", }, @@ -70,6 +72,7 @@ describe("table sdk", () => { type: "string", name: "name", visible: true, + order: 2, width: 80, constraints: { type: "string", @@ -88,6 +91,7 @@ describe("table sdk", () => { type: "number", name: "id", visible: true, + order: 1, constraints: { type: "number", }, @@ -136,6 +140,7 @@ describe("table sdk", () => { type: "string", name: "name", visible: true, + order: 2, width: 80, constraints: { type: "string", @@ -145,6 +150,7 @@ describe("table sdk", () => { type: "number", name: "id", visible: true, + order: 1, constraints: { type: "number", }, @@ -181,6 +187,7 @@ describe("table sdk", () => { name: { type: "string", name: "name", + order: 2, visible: true, width: 80, constraints: { @@ -193,6 +200,58 @@ describe("table sdk", () => { }) ) }) + + it("if view schema only defines visiblility, should only fetch the selected fields", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true }, + id: { visible: true }, + description: { visible: false }, + }, + } + + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual( + expect.objectContaining({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + order: 2, + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + order: 1, + visible: true, + constraints: { + type: "number", + }, + }, + }, + }, + }, + }) + ) + }) }) }) }) From dceb51a08d5b19e44227d5dda73458504648bf56 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 15:11:23 +0200 Subject: [PATCH 78/89] Skip table order if defined on view --- packages/server/src/sdk/app/tables/index.ts | 11 +++- .../src/sdk/app/tables/tests/tables.spec.ts | 51 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/server/src/sdk/app/tables/index.ts b/packages/server/src/sdk/app/tables/index.ts index b94ca58538..6ede4c48dc 100644 --- a/packages/server/src/sdk/app/tables/index.ts +++ b/packages/server/src/sdk/app/tables/index.ts @@ -69,7 +69,9 @@ function enrichSchema( viewOverrides: Record ) { const result: TableSchema = {} - for (const [columnName, columnUIMetadata] of Object.entries(viewOverrides)) { + const viewOverridesEntries = Object.entries(viewOverrides) + const viewSetsOrder = viewOverridesEntries.some(([_, v]) => v.order) + for (const [columnName, columnUIMetadata] of viewOverridesEntries) { if (!columnUIMetadata.visible) { continue } @@ -78,7 +80,12 @@ function enrichSchema( continue } - result[columnName] = _.merge(tableSchema[columnName], columnUIMetadata) + const tableFieldSchema = tableSchema[columnName] + if (viewSetsOrder) { + delete tableFieldSchema.order + } + + result[columnName] = _.merge(tableFieldSchema, columnUIMetadata) } return result } diff --git a/packages/server/src/sdk/app/tables/tests/tables.spec.ts b/packages/server/src/sdk/app/tables/tests/tables.spec.ts index a474f2aa43..b1e2ab252f 100644 --- a/packages/server/src/sdk/app/tables/tests/tables.spec.ts +++ b/packages/server/src/sdk/app/tables/tests/tables.spec.ts @@ -252,6 +252,57 @@ describe("table sdk", () => { }) ) }) + + it("if view defines order, the table schema order should be ignored", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true, order: 1 }, + id: { visible: true }, + description: { visible: false, order: 2 }, + }, + } + + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual( + expect.objectContaining({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + order: 1, + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + }, + }, + }, + }) + ) + }) }) }) }) From ff57b982f8d8b46f1fc3b938c535cf454b0e20cf Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 15:57:17 +0200 Subject: [PATCH 79/89] Move enrich view schemas to view sdk --- packages/server/src/sdk/app/views/index.ts | 43 ++- .../src/sdk/app/views/tests/views.spec.ts | 265 ++++++++++++++++++ 2 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/sdk/app/views/tests/views.spec.ts diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index e4d6f061eb..17691c999d 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,8 +1,9 @@ import { HTTPError, context } from "@budibase/backend-core" -import { View, ViewV2 } from "@budibase/types" +import { TableSchema, UIFieldMetadata, View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import * as utils from "../../../db/utils" +import _ from "lodash" export async function get(viewId: string): Promise { const { tableId } = utils.extractViewInfoFromID(viewId) @@ -48,3 +49,43 @@ export async function remove(viewId: string): Promise { delete table.views![view?.name] await db.put(table) } + +export function enrichSchema(view: View | ViewV2, tableSchema: TableSchema) { + if (!sdk.views.isV2(view)) { + return view + } + + return { + ...view, + schema: + !view?.columns || !Object.entries(view?.columns).length + ? tableSchema + : enrichViewV2Schema(tableSchema, view.columns), + } +} + +function enrichViewV2Schema( + tableSchema: TableSchema, + viewOverrides: Record +) { + const result: TableSchema = {} + const viewOverridesEntries = Object.entries(viewOverrides) + const viewSetsOrder = viewOverridesEntries.some(([_, v]) => v.order) + for (const [columnName, columnUIMetadata] of viewOverridesEntries) { + if (!columnUIMetadata.visible) { + continue + } + + if (!tableSchema[columnName]) { + continue + } + + const tableFieldSchema = tableSchema[columnName] + if (viewSetsOrder) { + delete tableFieldSchema.order + } + + result[columnName] = _.merge(tableFieldSchema, columnUIMetadata) + } + return result +} diff --git a/packages/server/src/sdk/app/views/tests/views.spec.ts b/packages/server/src/sdk/app/views/tests/views.spec.ts new file mode 100644 index 0000000000..cbcd98eb91 --- /dev/null +++ b/packages/server/src/sdk/app/views/tests/views.spec.ts @@ -0,0 +1,265 @@ +import { FieldType, Table, ViewV2 } from "@budibase/types" +import { generator } from "@budibase/backend-core/tests" +import { enrichSchema } from ".." + +describe("table sdk", () => { + describe("enrichViewSchemas", () => { + const basicTable: Table = { + _id: generator.guid(), + name: "TestTable", + type: "table", + schema: { + name: { + type: FieldType.STRING, + name: "name", + visible: true, + width: 80, + order: 2, + constraints: { + type: "string", + }, + }, + description: { + type: FieldType.STRING, + name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: FieldType.NUMBER, + name: "id", + visible: true, + order: 1, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: FieldType.STRING, + name: "hiddenField", + visible: false, + constraints: { + type: "string", + }, + }, + }, + } + + it("should fetch the default schema if not overriden", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + } + + const res = enrichSchema(view, basicTable.schema) + + expect(res).toEqual({ + ...view, + schema: { + name: { + type: "string", + name: "name", + visible: true, + order: 2, + width: 80, + constraints: { + type: "string", + }, + }, + description: { + type: "string", + name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + order: 1, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: "string", + name: "hiddenField", + visible: false, + constraints: { + type: "string", + }, + }, + }, + }) + }) + + it("if view schema only defines visiblility, should only fetch the selected fields", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true }, + id: { visible: true }, + description: { visible: false }, + }, + } + + const res = enrichSchema(view, basicTable.schema) + + expect(res).toEqual({ + ...view, + schema: { + name: { + type: "string", + name: "name", + visible: true, + order: 2, + width: 80, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + order: 1, + constraints: { + type: "number", + }, + }, + }, + }) + }) + + it("schema does not break if the view has corrupted columns", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { unnexisting: { visible: true }, name: { visible: true } }, + } + + const res = enrichSchema(view, basicTable.schema) + + expect(res).toEqual( + expect.objectContaining({ + ...view, + schema: { + name: { + type: "string", + name: "name", + order: 2, + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + }, + }) + ) + }) + + it("if view schema only defines visiblility, should only fetch the selected fields", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true }, + id: { visible: true }, + description: { visible: false }, + }, + } + + const res = enrichSchema(view, basicTable.schema) + + expect(res).toEqual( + expect.objectContaining({ + ...view, + schema: { + name: { + type: "string", + name: "name", + order: 2, + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + order: 1, + visible: true, + constraints: { + type: "number", + }, + }, + }, + }) + ) + }) + + it("if view defines order, the table schema order should be ignored", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true, order: 1 }, + id: { visible: true }, + description: { visible: false, order: 2 }, + }, + } + + const res = enrichSchema(view, basicTable.schema) + + expect(res).toEqual( + expect.objectContaining({ + ...view, + schema: { + name: { + type: "string", + name: "name", + order: 1, + visible: true, + width: 80, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { + type: "number", + }, + }, + }, + }) + ) + }) + }) +}) From 77a004f19ae6e88e77a03e2e30777c22172e2716 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 16:02:59 +0200 Subject: [PATCH 80/89] Remove wrong describe --- .../server/src/sdk/app/views/tests/views.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/server/src/sdk/app/views/tests/views.spec.ts b/packages/server/src/sdk/app/views/tests/views.spec.ts index cbcd98eb91..a40a91943d 100644 --- a/packages/server/src/sdk/app/views/tests/views.spec.ts +++ b/packages/server/src/sdk/app/views/tests/views.spec.ts @@ -1,6 +1,6 @@ import { FieldType, Table, ViewV2 } from "@budibase/types" import { generator } from "@budibase/backend-core/tests" -import { enrichSchema } from ".." +import views from ".." describe("table sdk", () => { describe("enrichViewSchemas", () => { @@ -57,7 +57,7 @@ describe("table sdk", () => { tableId, } - const res = enrichSchema(view, basicTable.schema) + const res = views.enrichSchema(view, basicTable.schema) expect(res).toEqual({ ...view, @@ -116,7 +116,7 @@ describe("table sdk", () => { }, } - const res = enrichSchema(view, basicTable.schema) + const res = views.enrichSchema(view, basicTable.schema) expect(res).toEqual({ ...view, @@ -154,7 +154,7 @@ describe("table sdk", () => { columns: { unnexisting: { visible: true }, name: { visible: true } }, } - const res = enrichSchema(view, basicTable.schema) + const res = views.enrichSchema(view, basicTable.schema) expect(res).toEqual( expect.objectContaining({ @@ -189,7 +189,7 @@ describe("table sdk", () => { }, } - const res = enrichSchema(view, basicTable.schema) + const res = views.enrichSchema(view, basicTable.schema) expect(res).toEqual( expect.objectContaining({ @@ -233,7 +233,7 @@ describe("table sdk", () => { }, } - const res = enrichSchema(view, basicTable.schema) + const res = views.enrichSchema(view, basicTable.schema) expect(res).toEqual( expect.objectContaining({ From d8b801e647148d2373d97207535d921be4346446 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 16:04:54 +0200 Subject: [PATCH 81/89] Remove wrong describe --- .../src/sdk/app/tables/tests/tables.spec.ts | 468 +++++++++--------- 1 file changed, 233 insertions(+), 235 deletions(-) diff --git a/packages/server/src/sdk/app/tables/tests/tables.spec.ts b/packages/server/src/sdk/app/tables/tests/tables.spec.ts index b1e2ab252f..72b078c2b8 100644 --- a/packages/server/src/sdk/app/tables/tests/tables.spec.ts +++ b/packages/server/src/sdk/app/tables/tests/tables.spec.ts @@ -4,65 +4,132 @@ import sdk from "../../.." describe("table sdk", () => { describe("enrichViewSchemas", () => { - describe("fetch", () => { - const basicTable: Table = { - _id: generator.guid(), - name: "TestTable", - type: "table", - schema: { - name: { - type: FieldType.STRING, - name: "name", - visible: true, - width: 80, - order: 2, - constraints: { - type: "string", - }, + const basicTable: Table = { + _id: generator.guid(), + name: "TestTable", + type: "table", + schema: { + name: { + type: FieldType.STRING, + name: "name", + visible: true, + width: 80, + order: 2, + constraints: { + type: "string", }, - description: { - type: FieldType.STRING, - name: "description", - visible: true, - width: 200, - constraints: { - type: "string", - }, + }, + description: { + type: FieldType.STRING, + name: "description", + visible: true, + width: 200, + constraints: { + type: "string", }, - id: { - type: FieldType.NUMBER, - name: "id", - visible: true, - order: 1, - constraints: { - type: "number", - }, + }, + id: { + type: FieldType.NUMBER, + name: "id", + visible: true, + order: 1, + constraints: { + type: "number", }, - hiddenField: { - type: FieldType.STRING, - name: "hiddenField", - visible: false, - constraints: { - type: "string", + }, + hiddenField: { + type: FieldType.STRING, + name: "hiddenField", + visible: false, + constraints: { + type: "string", + }, + }, + }, + } + + it("should fetch the default schema if not overriden", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + } + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + visible: true, + order: 2, + width: 80, + constraints: { + type: "string", + }, + }, + description: { + type: "string", + name: "description", + visible: true, + width: 200, + constraints: { + type: "string", + }, + }, + id: { + type: "number", + name: "id", + visible: true, + order: 1, + constraints: { + type: "number", + }, + }, + hiddenField: { + type: "string", + name: "hiddenField", + visible: false, + constraints: { + type: "string", + }, + }, }, }, }, + }) + }) + + it("if view schema only defines visiblility, should only fetch the selected fields", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true }, + id: { visible: true }, + description: { visible: false }, + }, } - it("should fetch the default schema if not overriden", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - } - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, - }) + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) - expect(res).toEqual({ + expect(res).toEqual( + expect.objectContaining({ ...basicTable, views: { [view.name]: { @@ -78,15 +145,6 @@ describe("table sdk", () => { type: "string", }, }, - description: { - type: "string", - name: "description", - visible: true, - width: 200, - constraints: { - type: "string", - }, - }, id: { type: "number", name: "id", @@ -96,10 +154,41 @@ describe("table sdk", () => { type: "number", }, }, - hiddenField: { + }, + }, + }, + }) + ) + }) + + it("schema does not break if the view has corrupted columns", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { unnexisting: { visible: true }, name: { visible: true } }, + } + + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual( + expect.objectContaining({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { type: "string", - name: "hiddenField", - visible: false, + name: "name", + order: 2, + visible: true, + width: 80, constraints: { type: "string", }, @@ -108,201 +197,110 @@ describe("table sdk", () => { }, }, }) + ) + }) + + it("if view schema only defines visiblility, should only fetch the selected fields", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true }, + id: { visible: true }, + description: { visible: false }, + }, + } + + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, }) - it("if view schema only defines visiblility, should only fetch the selected fields", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { - name: { visible: true }, - id: { visible: true }, - description: { visible: false }, - }, - } - - const res = sdk.tables.enrichViewSchemas({ + expect(res).toEqual( + expect.objectContaining({ ...basicTable, - views: { [view.name]: view }, - }) - - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + order: 2, + visible: true, + width: 80, + constraints: { type: "string", - name: "name", - visible: true, - order: 2, - width: 80, - constraints: { - type: "string", - }, }, - id: { + }, + id: { + type: "number", + name: "id", + order: 1, + visible: true, + constraints: { type: "number", - name: "id", - visible: true, - order: 1, - constraints: { - type: "number", - }, }, }, }, }, - }) - ) - }) - - it("schema does not break if the view has corrupted columns", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { unnexisting: { visible: true }, name: { visible: true } }, - } - - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, - }) - - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - order: 2, - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - }, - }, - }, - }) - ) - }) - - it("if view schema only defines visiblility, should only fetch the selected fields", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { - name: { visible: true }, - id: { visible: true }, - description: { visible: false }, }, - } - - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, }) + ) + }) - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { + it("if view defines order, the table schema order should be ignored", async () => { + const tableId = basicTable._id! + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + columns: { + name: { visible: true, order: 1 }, + id: { visible: true }, + description: { visible: false, order: 2 }, + }, + } + + const res = sdk.tables.enrichViewSchemas({ + ...basicTable, + views: { [view.name]: view }, + }) + + expect(res).toEqual( + expect.objectContaining({ + ...basicTable, + views: { + [view.name]: { + ...view, + schema: { + name: { + type: "string", + name: "name", + order: 1, + visible: true, + width: 80, + constraints: { type: "string", - name: "name", - order: 2, - visible: true, - width: 80, - constraints: { - type: "string", - }, }, - id: { + }, + id: { + type: "number", + name: "id", + visible: true, + constraints: { type: "number", - name: "id", - order: 1, - visible: true, - constraints: { - type: "number", - }, }, }, }, }, - }) - ) - }) - - it("if view defines order, the table schema order should be ignored", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { - name: { visible: true, order: 1 }, - id: { visible: true }, - description: { visible: false, order: 2 }, }, - } - - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, }) - - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - order: 1, - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - visible: true, - constraints: { - type: "number", - }, - }, - }, - }, - }, - }) - ) - }) + ) }) }) }) From c58b145afd9be0f5236fe7ac56b92cc8cc6e24fc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 16:12:42 +0200 Subject: [PATCH 82/89] Test sdks --- packages/server/src/sdk/app/tables/index.ts | 46 +-- .../src/sdk/app/tables/tests/tables.spec.ts | 274 +++--------------- .../src/sdk/app/views/tests/views.spec.ts | 12 +- 3 files changed, 45 insertions(+), 287 deletions(-) diff --git a/packages/server/src/sdk/app/tables/index.ts b/packages/server/src/sdk/app/tables/index.ts index 6ede4c48dc..7f88c35010 100644 --- a/packages/server/src/sdk/app/tables/index.ts +++ b/packages/server/src/sdk/app/tables/index.ts @@ -10,8 +10,6 @@ import { Database, TableResponse, TableViewsResponse, - TableSchema, - UIFieldMetadata, } from "@budibase/types" import datasources from "../datasources" import { populateExternalTableSchemas, isEditableColumn } from "./validation" @@ -64,49 +62,15 @@ async function getTable(tableId: any): Promise
{ } } -function enrichSchema( - tableSchema: TableSchema, - viewOverrides: Record -) { - const result: TableSchema = {} - const viewOverridesEntries = Object.entries(viewOverrides) - const viewSetsOrder = viewOverridesEntries.some(([_, v]) => v.order) - for (const [columnName, columnUIMetadata] of viewOverridesEntries) { - if (!columnUIMetadata.visible) { - continue - } - - if (!tableSchema[columnName]) { - continue - } - - const tableFieldSchema = tableSchema[columnName] - if (viewSetsOrder) { - delete tableFieldSchema.order - } - - result[columnName] = _.merge(tableFieldSchema, columnUIMetadata) - } - return result -} - function enrichViewSchemas(table: Table): TableResponse { const result: TableResponse = { ...table, - views: Object.values(table.views ?? []).reduce((p, v) => { - if (!sdk.views.isV2(v)) { + views: Object.values(table.views ?? []) + .map(v => sdk.views.enrichSchema(v, table.schema)) + .reduce((p, v) => { p[v.name] = v - } else { - p[v.name] = { - ...v, - schema: - !v?.columns || !Object.entries(v?.columns).length - ? table.schema - : enrichSchema(table.schema, v.columns), - } - } - return p - }, {} as TableViewsResponse), + return p + }, {} as TableViewsResponse), } return result diff --git a/packages/server/src/sdk/app/tables/tests/tables.spec.ts b/packages/server/src/sdk/app/tables/tests/tables.spec.ts index 72b078c2b8..78ebe59f01 100644 --- a/packages/server/src/sdk/app/tables/tests/tables.spec.ts +++ b/packages/server/src/sdk/app/tables/tests/tables.spec.ts @@ -2,6 +2,11 @@ import { FieldType, Table, ViewV2 } from "@budibase/types" import { generator } from "@budibase/backend-core/tests" import sdk from "../../.." +jest.mock("../../views", () => ({ + ...jest.requireActual("../../views"), + enrichSchema: jest.fn().mockImplementation(v => ({ ...v, mocked: true })), +})) + describe("table sdk", () => { describe("enrichViewSchemas", () => { const basicTable: Table = { @@ -50,257 +55,46 @@ describe("table sdk", () => { it("should fetch the default schema if not overriden", async () => { const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, + function getTable() { + const view: ViewV2 = { + version: 2, + id: generator.guid(), + name: generator.guid(), + tableId, + } + return view } + const view1 = getTable() + const view2 = getTable() + const view3 = getTable() const res = sdk.tables.enrichViewSchemas({ ...basicTable, - views: { [view.name]: view }, + views: { + [view1.name]: view1, + [view2.name]: view2, + [view3.name]: view3, + }, }) + expect(sdk.views.enrichSchema).toBeCalledTimes(3) + expect(res).toEqual({ ...basicTable, views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - visible: true, - order: 2, - width: 80, - constraints: { - type: "string", - }, - }, - description: { - type: "string", - name: "description", - visible: true, - width: 200, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - visible: true, - order: 1, - constraints: { - type: "number", - }, - }, - hiddenField: { - type: "string", - name: "hiddenField", - visible: false, - constraints: { - type: "string", - }, - }, - }, + [view1.name]: { + ...view1, + mocked: true, + }, + [view2.name]: { + ...view2, + mocked: true, + }, + [view3.name]: { + ...view3, + mocked: true, }, }, }) }) - - it("if view schema only defines visiblility, should only fetch the selected fields", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { - name: { visible: true }, - id: { visible: true }, - description: { visible: false }, - }, - } - - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, - }) - - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - visible: true, - order: 2, - width: 80, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - visible: true, - order: 1, - constraints: { - type: "number", - }, - }, - }, - }, - }, - }) - ) - }) - - it("schema does not break if the view has corrupted columns", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { unnexisting: { visible: true }, name: { visible: true } }, - } - - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, - }) - - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - order: 2, - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - }, - }, - }, - }) - ) - }) - - it("if view schema only defines visiblility, should only fetch the selected fields", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { - name: { visible: true }, - id: { visible: true }, - description: { visible: false }, - }, - } - - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, - }) - - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - order: 2, - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - order: 1, - visible: true, - constraints: { - type: "number", - }, - }, - }, - }, - }, - }) - ) - }) - - it("if view defines order, the table schema order should be ignored", async () => { - const tableId = basicTable._id! - const view: ViewV2 = { - version: 2, - id: generator.guid(), - name: generator.guid(), - tableId, - columns: { - name: { visible: true, order: 1 }, - id: { visible: true }, - description: { visible: false, order: 2 }, - }, - } - - const res = sdk.tables.enrichViewSchemas({ - ...basicTable, - views: { [view.name]: view }, - }) - - expect(res).toEqual( - expect.objectContaining({ - ...basicTable, - views: { - [view.name]: { - ...view, - schema: { - name: { - type: "string", - name: "name", - order: 1, - visible: true, - width: 80, - constraints: { - type: "string", - }, - }, - id: { - type: "number", - name: "id", - visible: true, - constraints: { - type: "number", - }, - }, - }, - }, - }, - }) - ) - }) }) }) diff --git a/packages/server/src/sdk/app/views/tests/views.spec.ts b/packages/server/src/sdk/app/views/tests/views.spec.ts index a40a91943d..cbcd98eb91 100644 --- a/packages/server/src/sdk/app/views/tests/views.spec.ts +++ b/packages/server/src/sdk/app/views/tests/views.spec.ts @@ -1,6 +1,6 @@ import { FieldType, Table, ViewV2 } from "@budibase/types" import { generator } from "@budibase/backend-core/tests" -import views from ".." +import { enrichSchema } from ".." describe("table sdk", () => { describe("enrichViewSchemas", () => { @@ -57,7 +57,7 @@ describe("table sdk", () => { tableId, } - const res = views.enrichSchema(view, basicTable.schema) + const res = enrichSchema(view, basicTable.schema) expect(res).toEqual({ ...view, @@ -116,7 +116,7 @@ describe("table sdk", () => { }, } - const res = views.enrichSchema(view, basicTable.schema) + const res = enrichSchema(view, basicTable.schema) expect(res).toEqual({ ...view, @@ -154,7 +154,7 @@ describe("table sdk", () => { columns: { unnexisting: { visible: true }, name: { visible: true } }, } - const res = views.enrichSchema(view, basicTable.schema) + const res = enrichSchema(view, basicTable.schema) expect(res).toEqual( expect.objectContaining({ @@ -189,7 +189,7 @@ describe("table sdk", () => { }, } - const res = views.enrichSchema(view, basicTable.schema) + const res = enrichSchema(view, basicTable.schema) expect(res).toEqual( expect.objectContaining({ @@ -233,7 +233,7 @@ describe("table sdk", () => { }, } - const res = views.enrichSchema(view, basicTable.schema) + const res = enrichSchema(view, basicTable.schema) expect(res).toEqual( expect.objectContaining({ From 2186b0407af82c34f81dbce7748e232b1fd18502 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 21 Jul 2023 18:30:17 +0200 Subject: [PATCH 83/89] Honor schema on view search --- .../server/src/api/controllers/row/index.ts | 10 +++++++- .../server/src/api/routes/tests/row.spec.ts | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 8ba2f4b0ba..0b17a35772 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -8,6 +8,7 @@ import { gridSocket } from "../../../websockets" import sdk from "../../../sdk" import * as exporters from "../view/exporters" import { apiFileReturn } from "../../../utilities/fileSystem" +import _ from "lodash" function pickApi(tableId: any) { if (isExternalTable(tableId)) { @@ -155,7 +156,7 @@ export async function searchView(ctx: Ctx) { } ctx.status = 200 - ctx.body = await quotas.addQuery( + let { rows } = await quotas.addQuery( () => sdk.rows.search({ tableId: view.tableId, @@ -168,6 +169,13 @@ export async function searchView(ctx: Ctx) { datasourceId: view.tableId, } ) + + const { columns } = view + if (columns) { + rows = rows.map(r => _.pick(r, columns)) + } + + ctx.body = { rows } } export async function validate(ctx: Ctx) { diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 2890085c88..ba0afd1fcf 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -841,5 +841,29 @@ describe("/rows", () => { rows: expected.map(name => expect.objectContaining({ name })), }) }) + + it("when schema is defined, no other columns are returnd", async () => { + const table = await config.createTable(userTable()) + const rows = [] + for (let i = 0; i < 10; i++) { + rows.push( + await config.createRow({ + tableId: table._id, + name: generator.name(), + age: generator.age(), + }) + ) + } + + const createViewResponse = await config.api.viewV2.create({ + columns: ["name"], + }) + const response = await config.api.viewV2.search(createViewResponse.id) + + expect(response.body.rows).toHaveLength(10) + expect(response.body.rows).toEqual( + expect.arrayContaining(rows.map(r => ({ name: r.name }))) + ) + }) }) }) From d1d86b6803fccbb4d1e865b1474c637ccfd105b5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Sun, 23 Jul 2023 22:36:34 +0200 Subject: [PATCH 84/89] Filter on the sdk --- packages/server/src/api/controllers/row/index.ts | 9 ++------- packages/server/src/sdk/app/rows/search.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 0b17a35772..62d81fb29e 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -8,7 +8,6 @@ import { gridSocket } from "../../../websockets" import sdk from "../../../sdk" import * as exporters from "../view/exporters" import { apiFileReturn } from "../../../utilities/fileSystem" -import _ from "lodash" function pickApi(tableId: any) { if (isExternalTable(tableId)) { @@ -156,7 +155,7 @@ export async function searchView(ctx: Ctx) { } ctx.status = 200 - let { rows } = await quotas.addQuery( + const { rows } = await quotas.addQuery( () => sdk.rows.search({ tableId: view.tableId, @@ -164,17 +163,13 @@ export async function searchView(ctx: Ctx) { sort: view.sort?.field, sortOrder: view.sort?.order, sortType: view.sort?.type, + fields: view.columns, }), { datasourceId: view.tableId, } ) - const { columns } = view - if (columns) { - rows = rows.map(r => _.pick(r, columns)) - } - ctx.body = { rows } } diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 53f3bbbc43..ab0723ab10 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -3,6 +3,7 @@ import { isExternalTable } from "../../../integrations/utils" import * as internal from "./search/internal" import * as external from "./search/external" import { Format } from "../../../api/controllers/view/exporters" +import _ from "lodash" export interface SearchParams { tableId: string @@ -15,6 +16,7 @@ export interface SearchParams { sortType?: SortType version?: string disableEscaping?: boolean + fields?: string[] } export interface ViewParams { @@ -33,7 +35,12 @@ function pickApi(tableId: any) { export async function search(options: SearchParams): Promise<{ rows: any[] }> { - return pickApi(options.tableId).search(options) + let { rows } = await pickApi(options.tableId).search(options) + + if (options.fields) { + rows = rows.map((r: any) => _.pick(r, options.fields!)) + } + return { rows } } export interface ExportRowsParams { From 9cf401162be1ef04895671b62161b4284b95b105 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Sun, 23 Jul 2023 22:40:53 +0200 Subject: [PATCH 85/89] Add views --- packages/server/src/api/routes/tests/row.spec.ts | 11 ++++++++++- 1 file changed, 10 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 ba0afd1fcf..e6be3a5cfc 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -842,7 +842,7 @@ describe("/rows", () => { }) }) - it("when schema is defined, no other columns are returnd", async () => { + it("when schema is defined, no other columns are returned", async () => { const table = await config.createTable(userTable()) const rows = [] for (let i = 0; i < 10; i++) { @@ -865,5 +865,14 @@ describe("/rows", () => { expect.arrayContaining(rows.map(r => ({ name: r.name }))) ) }) + + it("views without data can be returned", async () => { + const table = await config.createTable(userTable()) + + const createViewResponse = await config.api.viewV2.create() + const response = await config.api.viewV2.search(createViewResponse.id) + + expect(response.body.rows).toHaveLength(0) + }) }) }) From 1cf4e6e85a7a3f55ef1ee35633bbae9244b3a732 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 10:05:27 +0200 Subject: [PATCH 86/89] Return full object from search --- packages/server/src/api/controllers/row/index.ts | 4 +--- packages/server/src/sdk/app/rows/search.ts | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 62d81fb29e..5ffc91c102 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -155,7 +155,7 @@ export async function searchView(ctx: Ctx) { } ctx.status = 200 - const { rows } = await quotas.addQuery( + ctx.body = await quotas.addQuery( () => sdk.rows.search({ tableId: view.tableId, @@ -169,8 +169,6 @@ export async function searchView(ctx: Ctx) { datasourceId: view.tableId, } ) - - ctx.body = { rows } } export async function validate(ctx: Ctx) { diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index ab0723ab10..4937460686 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -34,13 +34,15 @@ function pickApi(tableId: any) { export async function search(options: SearchParams): Promise<{ rows: any[] + hasNextPage?: boolean + bookmark?: number | null }> { - let { rows } = await pickApi(options.tableId).search(options) + const result = await pickApi(options.tableId).search(options) if (options.fields) { - rows = rows.map((r: any) => _.pick(r, options.fields!)) + result.rows = result.rows.map((r: any) => _.pick(r, options.fields!)) } - return { rows } + return result } export interface ExportRowsParams { From 5bd149f66ffc291083b97ccc42fd88391fd7327e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 15:37:28 +0200 Subject: [PATCH 87/89] Use sdk to populate views --- packages/server/src/api/controllers/row/index.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 5ffc91c102..79a1d54c63 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -154,6 +154,16 @@ export async function searchView(ctx: Ctx) { ctx.throw(404, `View ${viewId} not found`) } + if (view.version !== 2) { + ctx.throw(400, `This method only supports viewsV2`) + } + + const table = await sdk.tables.getTable(view?.tableId) + + const viewFields = + view.columns?.length && + Object.keys(sdk.views.enrichSchema(view, table.schema).schema) + ctx.status = 200 ctx.body = await quotas.addQuery( () => @@ -163,7 +173,7 @@ export async function searchView(ctx: Ctx) { sort: view.sort?.field, sortOrder: view.sort?.order, sortType: view.sort?.type, - fields: view.columns, + fields: viewFields, }), { datasourceId: view.tableId, From c8700394163ef8e8d3f61055c2b334c1bba2d6c4 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 16:29:49 +0200 Subject: [PATCH 88/89] Refactor plus selector --- packages/server/src/db/utils.ts | 7 +++++++ packages/server/src/sdk/app/datasources/datasources.ts | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 266ddeb341..f2cc8618cb 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -186,6 +186,13 @@ export function getDatasourceParams( return getDocParams(DocumentType.DATASOURCE, datasourceId, otherProps) } +export function getDatasourcePlusParams( + datasourceId?: Optional, + otherProps?: { include_docs: boolean } +) { + return getDocParams(DocumentType.DATASOURCE_PLUS, datasourceId, otherProps) +} + /** * Generates a new query ID. * @returns {string} The new query ID which the query doc can be stored under. diff --git a/packages/server/src/sdk/app/datasources/datasources.ts b/packages/server/src/sdk/app/datasources/datasources.ts index 993db04523..9713cea38f 100644 --- a/packages/server/src/sdk/app/datasources/datasources.ts +++ b/packages/server/src/sdk/app/datasources/datasources.ts @@ -19,6 +19,7 @@ import _ from "lodash" import { BudibaseInternalDB, getDatasourceParams, + getDatasourcePlusParams, getTableParams, } from "../../../db/utils" import sdk from "../../index" @@ -248,7 +249,7 @@ export async function getExternalDatasources(): Promise { const db = context.getAppDB() const externalDatasources = await db.allDocs( - getDatasourceParams("plus", { + getDatasourcePlusParams(undefined, { include_docs: true, }) ) From 7465c0479913b8c23f6f9c666983ab76455c13a2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Jul 2023 16:35:22 +0200 Subject: [PATCH 89/89] Fix controller --- packages/server/src/api/controllers/row/index.ts | 6 ++++-- packages/server/src/api/routes/tests/row.spec.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 79a1d54c63..6456d0def9 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -161,8 +161,10 @@ export async function searchView(ctx: Ctx) { const table = await sdk.tables.getTable(view?.tableId) const viewFields = - view.columns?.length && - Object.keys(sdk.views.enrichSchema(view, table.schema).schema) + (view.columns && + Object.entries(view.columns).length && + Object.keys(sdk.views.enrichSchema(view, table.schema).schema)) || + undefined ctx.status = 200 ctx.body = await quotas.addQuery( diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index e6be3a5cfc..e1dd11e6dd 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -856,7 +856,7 @@ describe("/rows", () => { } const createViewResponse = await config.api.viewV2.create({ - columns: ["name"], + columns: { name: { visible: true } }, }) const response = await config.api.viewV2.search(createViewResponse.id)