diff --git a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts b/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts index 4c3f5e9163..ac73cea258 100644 --- a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts +++ b/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts @@ -10,6 +10,7 @@ import { getViewAndModelFromRequestByAliasOrId } from './helpers'; import apiMetrics from '../../helpers/apiMetrics'; import getAst from '../../../db/sql-data-mapper/lib/sql/helpers/getAst'; +// todo: Handle the error case where view doesnt belong to model async function dataList(req: Request, res: Response) { const { model, view } = await getViewAndModelFromRequestByAliasOrId(req); res.json(await getDataList(model, view, req)); diff --git a/packages/nocodb/tests/unit/rest/tests/factory/view.ts b/packages/nocodb/tests/unit/rest/tests/factory/view.ts new file mode 100644 index 0000000000..ce8524ea57 --- /dev/null +++ b/packages/nocodb/tests/unit/rest/tests/factory/view.ts @@ -0,0 +1,36 @@ +import { UITypes, ViewTypes } from 'nocodb-sdk'; +import request from 'supertest'; +import Column from '../../../../../src/lib/models/Column'; +import Model from '../../../../../src/lib/models/Model'; +import View from '../../../../../src/lib/models/View'; + +const createView = async (context, {title, table, type}: {title: string, table: Model, type: ViewTypes}) => { + const viewTypeStr = (type) => { + switch (type) { + case ViewTypes.GALLERY: + return 'galleries'; + case ViewTypes.FORM: + return 'forms'; + case ViewTypes.GRID: + return 'grids'; + case ViewTypes.KANBAN: + return 'kanbans'; + default: + throw new Error('Invalid view type'); + } + }; + + await request(context.app) + .post(`/api/v1/db/meta/tables/${table.id}/${viewTypeStr(type)}`) + .set('xc-auth', context.token) + .send({ + title, + type, + }); + + const view = await View.getByTitleOrId({fk_model_id: table.id, titleOrId:title}) as View; + + return view +} + +export {createView} \ No newline at end of file diff --git a/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts b/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts index 42dc705f29..2161eb4357 100644 --- a/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts @@ -6,7 +6,9 @@ import Project from '../../../../src/lib/models/Project'; import Model from '../../../../src/lib/models/Model'; import { getTable } from './factory/table'; import View from '../../../../src/lib/models/View'; -import { ColumnType } from 'nocodb-sdk'; +import { ColumnType, UITypes, ViewType, ViewTypes } from 'nocodb-sdk'; +import { createView } from './factory/view'; +import { createLookupColumn } from './factory/column'; const isColumnsCorrectInResponse = (row, columns: ColumnType[]) => { const responseColumnsListStr = Object.keys(row).sort().join(','); @@ -18,12 +20,15 @@ const isColumnsCorrectInResponse = (row, columns: ColumnType[]) => { return responseColumnsListStr === customerColumnsListStr; }; -function tableTest() { +function viewRowTests() { let context; let project: Project; let sakilaProject: Project; let customerTable: Model; let customerColumns; + let customerGridView: View; + let customerGalleryView: View; + let customerFormView: View; beforeEach(async function () { context = await init(); @@ -32,25 +37,49 @@ function tableTest() { project = await createProject(context); customerTable = await getTable({project: sakilaProject, name: 'customer'}) customerColumns = await customerTable.getColumns(); + customerGridView = await createView(context, { + title: 'Customer Gallery', + table: customerTable, + type: ViewTypes.GRID + }); + customerGalleryView = await createView(context, { + title: 'Customer Gallery', + table: customerTable, + type: ViewTypes.GALLERY + }); + customerFormView = await createView(context, { + title: 'Customer Form', + table: customerTable, + type: ViewTypes.FORM + }); }); - it.only('Get view row list', async () => { - const view = (await View.list(customerTable.id))[0] + const testGetViewRowListGallery = async (view: View) => { const response = await request(context.app) .get(`/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`) .set('xc-auth', context.token) .expect(200); const pageInfo = response.body.pageInfo; - if(pageInfo.totalRows !== 599 || response.body.list[0]['CustomerId'] !== 1){ throw new Error('View row list is not correct'); } + } + + it('Get view row list gallery', async () => { + await testGetViewRowListGallery(customerGalleryView); + }) + + it('Get view row list form', async () => { + await testGetViewRowListGallery(customerFormView); }) - it('Get table data list with required columns', async function () { - const view = (await View.list(customerTable.id))[0] - const requiredColumns = customerColumns.filter((_, index) => index < 3); + it('Get view row list grid', async () => { + await testGetViewRowListGallery(customerGridView); + }) + + const testGetViewDataListWithRequiredColumns = async (view: View) => { + const requiredColumns = customerColumns.filter((_, index) => index < 3).filter((c: ColumnType) => c.uidt !== UITypes.ForeignKey); const response = await request(context.app) .get(`/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`) @@ -66,12 +95,24 @@ function tableTest() { } if (!isColumnsCorrectInResponse(response.body.list[0], requiredColumns)) { + console.log(response.body.list[0], requiredColumns.map((c: ColumnType) => ({title: c.title,uidt: c.uidt}))); throw new Error('Wrong columns'); } - }); + } - it('Get desc sorted table data list with required columns', async function () { - const view = (await View.list(customerTable.id))[0] + it('Get view data list with required columns gallery', async () => { + await testGetViewDataListWithRequiredColumns(customerGalleryView); + }) + + it('Get view data list with required columns form', async () => { + await testGetViewDataListWithRequiredColumns(customerFormView); + }) + + it('Get view data list with required columns grid', async () => { + await testGetViewDataListWithRequiredColumns(customerGridView); + }) + + const testDescSortedViewDataList = async (view: View) => { const firstNameColumn = customerColumns.find( (col) => col.title === 'FirstName' ); @@ -105,7 +146,7 @@ function tableTest() { const lastPageOffset = Math.trunc(pageInfo.totalRows / pageInfo.pageSize) * pageInfo.pageSize; const lastPageResponse = await request(context.app) - .get(`/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}`) + .get(`/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`) .set('xc-auth', context.token) .query({ fields: visibleColumns.map((c) => c.title), @@ -122,10 +163,21 @@ function tableTest() { console.log(lastPageOffset, lastPageResponse.body.list); throw new Error('Wrong sort on last page'); } + } + + it('Get desc sorted table data list with required columns gallery', async function () { + await testDescSortedViewDataList(customerGalleryView); }); - it('Get asc sorted table data list with required columns', async function () { - const view = (await View.list(customerTable.id))[0] + it('Get desc sorted table data list with required columns form', async function () { + await testDescSortedViewDataList(customerFormView); + }); + + it('Get desc sorted table data list with required columns grid', async function () { + await testDescSortedViewDataList(customerGridView); + }); + + const testAscSortedViewDataList = async (view: View) => { const firstNameColumn = customerColumns.find( (col) => col.title === 'FirstName' ); @@ -159,7 +211,7 @@ function tableTest() { const lastPageOffset = Math.trunc(pageInfo.totalRows / pageInfo.pageSize) * pageInfo.pageSize; const lastPageResponse = await request(context.app) - .get(`/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}`) + .get(`/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`) .set('xc-auth', context.token) .query({ fields: visibleColumns.map((c) => c.title), @@ -176,7 +228,144 @@ function tableTest() { console.log(lastPageOffset, lastPageResponse.body.list); throw new Error('Wrong sort on last page'); } + } + + it('Get asc sorted view data list with required columns gallery', async function () { + await testAscSortedViewDataList(customerGalleryView); + }); + + it('Get asc sorted view data list with required columns form', async function () { + await testAscSortedViewDataList(customerFormView); + }); + + it('Get asc sorted view data list with required columns grid', async function () { + await testAscSortedViewDataList(customerGridView); + }); + + const testGetViewDataListWithRequiredColumnsAndFilter = async (viewType: ViewTypes) => { + const rentalTable = await getTable({project: sakilaProject, name: 'rental'}); + const view = await createView(context, { + title: 'View', + table: rentalTable, + type: viewType + }); + + const lookupColumn = await createLookupColumn(context, { + project: sakilaProject, + title: 'Lookup', + table: rentalTable, + relatedTableName: customerTable.table_name, + relatedTableColumnTitle: 'FirstName', + }); + + const paymentListColumn = (await rentalTable.getColumns()).find( + (c) => c.title === 'Payment List' + ); + + const returnDateColumn = (await rentalTable.getColumns()).find( + (c) => c.title === 'ReturnDate' + ); + + const nestedFilter = { + is_group: true, + status: 'create', + logical_op: 'and', + children: [ + { + fk_column_id: lookupColumn?.id, + status: 'create', + logical_op: 'and', + comparison_op: 'like', + value: '%a%', + }, + { + fk_column_id: paymentListColumn?.id, + status: 'create', + logical_op: 'and', + comparison_op: 'notempty', + }, + { + is_group: true, + status: 'create', + logical_op: 'and', + children: [ + { + logical_op: 'and', + fk_column_id: returnDateColumn?.id, + status: 'create', + comparison_op: 'gte', + value: '2005-06-02 04:33', + }, + ], + }, + ], + }; + + const response = await request(context.app) + .get(`/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}`) + .set('xc-auth', context.token) + .query({ + filterArrJson: JSON.stringify([nestedFilter]), + }); + + if (response.body.pageInfo.totalRows !== 9133) + throw new Error('Wrong number of rows'); + + if (response.body.list[0][lookupColumn.title] !== 'ANDREW') + throw new Error('Wrong filter'); + + const ascResponse = await request(context.app) + .get(`/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}`) + .set('xc-auth', context.token) + .query({ + filterArrJson: JSON.stringify([nestedFilter]), + sortArrJson: JSON.stringify([ + { + fk_column_id: lookupColumn?.id, + direction: 'asc', + }, + ]), + }) + .expect(200); + + if (ascResponse.body.pageInfo.totalRows !== 9133) + throw new Error('Wrong number of rows asc'); + + if (ascResponse.body.list[0][lookupColumn.title] !== 'AARON') { + console.log(ascResponse.body.list[0][lookupColumn.title]); + throw new Error('Wrong filter asc'); + } + + const descResponse = await request(context.app) + .get(`/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}`) + .set('xc-auth', context.token) + .query({ + filterArrJson: JSON.stringify([nestedFilter]), + sortArrJson: JSON.stringify([ + { + fk_column_id: lookupColumn?.id, + direction: 'desc', + }, + ]), + }) + .expect(200); + + if (descResponse.body.pageInfo.totalRows !== 9133) + throw new Error('Wrong number of rows desc'); + + if (descResponse.body.list[0][lookupColumn.title] !== 'ZACHARY') + throw new Error('Wrong filter desc'); + } + + it('Get nested sorted filtered table data list with a lookup column gallery', async function () { + await testGetViewDataListWithRequiredColumnsAndFilter(ViewTypes.GALLERY); + }); + + it('Get nested sorted filtered table data list with a lookup column grid', async function () { + await testGetViewDataListWithRequiredColumnsAndFilter(ViewTypes.GRID); }); } -export default tableTest; \ No newline at end of file +export default function () { + describe('ViewRow', viewRowTests); +} \ No newline at end of file