From b9a33c9694951efc73220b0102193672fcc6125d Mon Sep 17 00:00:00 2001 From: Pranav C Date: Mon, 12 Jun 2023 12:18:25 +0530 Subject: [PATCH 01/12] fix: save any unsaved data on tab close/view switch/table switch/data reload Signed-off-by: Pranav C --- .../nc-gui/components/smartsheet/Grid.vue | 23 +++++++++++++++---- packages/nc-gui/composables/useViewData.ts | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/Grid.vue b/packages/nc-gui/components/smartsheet/Grid.vue index c9b81b0e1e..233a69230b 100644 --- a/packages/nc-gui/components/smartsheet/Grid.vue +++ b/packages/nc-gui/components/smartsheet/Grid.vue @@ -677,9 +677,9 @@ const saveOrUpdateRecords = async (args: { metaValue?: TableType; viewMetaValue? index++ /** if new record save row and save the LTAR cells */ if (currentRow.rowMeta.new) { - const syncLTARRefs = rowRefs[index]!.syncLTARRefs + const syncLTARRefs = rowRefs?.[index]?.syncLTARRefs const savedRow = await updateOrSaveRow(currentRow, '', {}, args) - await syncLTARRefs(savedRow, args) + await syncLTARRefs?.(savedRow, args) currentRow.rowMeta.changed = false continue } @@ -700,6 +700,9 @@ const saveOrUpdateRecords = async (args: { metaValue?: TableType; viewMetaValue? } async function reloadViewDataHandler(shouldShowLoading: boolean | void) { + // save any unsaved data before reload + await saveOrUpdateRecords(); + // set value if spinner should be hidden showLoading.value = !!shouldShowLoading await loadData() @@ -715,9 +718,21 @@ async function openNewRecordHandler() { reloadViewDataHook?.on(reloadViewDataHandler) openNewRecordFormHook?.on(openNewRecordHandler) -onBeforeUnmount(() => { +onBeforeUnmount(async () => { /** save/update records before unmounting the component */ - saveOrUpdateRecords() + const viewMetaValue = view.value + const dataValue = data.value + if (viewMetaValue) { + getMeta(viewMetaValue.fk_model_id).then((res) => { + const metaValue = res + if (!metaValue) return + saveOrUpdateRecords({ + metaValue, + viewMetaValue, + data: dataValue, + }) + }) + } // reset hooks reloadViewDataHook?.off(reloadViewDataHandler) diff --git a/packages/nc-gui/composables/useViewData.ts b/packages/nc-gui/composables/useViewData.ts index 22e829f0b9..260c441a35 100644 --- a/packages/nc-gui/composables/useViewData.ts +++ b/packages/nc-gui/composables/useViewData.ts @@ -316,6 +316,7 @@ export function useViewData( await syncCount() return insertedData } catch (error: any) { + console.log(error) message.error(await extractSdkResponseErrorMsg(error)) } finally { if (currentRow.rowMeta) currentRow.rowMeta.saving = false From 19f66d78fae65853169898952abcd48fcb9550bc Mon Sep 17 00:00:00 2001 From: Pranav C Date: Mon, 12 Jun 2023 15:08:19 +0530 Subject: [PATCH 02/12] fix: filter view list based on current schema in pg Signed-off-by: Pranav C --- packages/nocodb/src/db/sql-client/lib/pg/PgClient.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/db/sql-client/lib/pg/PgClient.ts b/packages/nocodb/src/db/sql-client/lib/pg/PgClient.ts index 06db320e25..9d6623dfa2 100644 --- a/packages/nocodb/src/db/sql-client/lib/pg/PgClient.ts +++ b/packages/nocodb/src/db/sql-client/lib/pg/PgClient.ts @@ -1350,7 +1350,8 @@ class PGClient extends KnexClient { const { rows } = await this.sqlClient.raw( `select * from INFORMATION_SCHEMA.views - WHERE table_schema = ANY (current_schemas(false));`, + WHERE table_schema = ?;`, + [this.schema], ); for (let i = 0; i < rows.length; ++i) { From 1c6850e56342052d85895b01a0b6a4de80792af1 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Mon, 12 Jun 2023 15:55:20 +0530 Subject: [PATCH 03/12] fix: filter schema list based on current schema in mssql Signed-off-by: Pranav C --- packages/nocodb/src/db/sql-client/lib/mssql/MssqlClient.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/db/sql-client/lib/mssql/MssqlClient.ts b/packages/nocodb/src/db/sql-client/lib/mssql/MssqlClient.ts index 9df83f88f2..6e08545867 100644 --- a/packages/nocodb/src/db/sql-client/lib/mssql/MssqlClient.ts +++ b/packages/nocodb/src/db/sql-client/lib/mssql/MssqlClient.ts @@ -1118,8 +1118,9 @@ class MssqlClient extends KnexClient { args.databaseName = this.connectionConfig.connection.database; const response = await this.sqlClient.raw( - `SELECT v.name AS view_name,v.*,m.* FROM sys.views v INNER JOIN sys.schemas s ON s.schema_id = v.schema_id + `SELECT v.name AS view_name,v.*,m.* FROM sys.views v INNER JOIN sys.schemas s ON s.schema_id = v.schema_id AND schema_name(v.schema_id) = ? INNER JOIN sys.sql_modules AS m ON m.object_id = v.object_id`, + [this.schema || 'dbo'], ); result.data.list = response; From c08152a371fc805e0462262be87f64f5316b4e65 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Tue, 13 Jun 2023 12:17:55 +0530 Subject: [PATCH 04/12] chore: update sdk path Signed-off-by: Pranav C --- packages/nc-gui/package-lock.json | 52 +++++++++++---------------- packages/nc-gui/package.json | 2 +- packages/nocodb-sdk/package-lock.json | 4 +-- packages/nocodb/package-lock.json | 30 +++++++++------- packages/nocodb/package.json | 4 +-- 5 files changed, 42 insertions(+), 50 deletions(-) diff --git a/packages/nc-gui/package-lock.json b/packages/nc-gui/package-lock.json index e4e6a11d5c..c988beb40a 100644 --- a/packages/nc-gui/package-lock.json +++ b/packages/nc-gui/package-lock.json @@ -30,7 +30,7 @@ "leaflet.markercluster": "^1.5.3", "locale-codes": "^1.3.1", "monaco-editor": "^0.33.0", - "nocodb-sdk": "0.109.0", + "nocodb-sdk": "file:../nocodb-sdk", "papaparse": "^5.3.2", "pinia": "^2.0.33", "qrcode": "^1.5.1", @@ -111,7 +111,6 @@ }, "../nocodb-sdk": { "version": "0.109.0", - "extraneous": true, "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", @@ -8720,6 +8719,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "devOptional": true, "funding": [ { "type": "individual", @@ -12238,21 +12238,8 @@ } }, "node_modules/nocodb-sdk": { - "version": "0.109.0", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.109.0.tgz", - "integrity": "sha512-5yKLiiDR0noHdhyOqqdF/+UWyTqU7em6QZouURRQSxC72qHU1KBrL1ig5JaIL0CX0pwlANq9hnQ0OzSH8J46fQ==", - "dependencies": { - "axios": "^0.21.1", - "jsep": "^1.3.6" - } - }, - "node_modules/nocodb-sdk/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } + "resolved": "../nocodb-sdk", + "link": true }, "node_modules/node-abi": { "version": "3.23.0", @@ -24729,7 +24716,8 @@ "follow-redirects": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "devOptional": true }, "form-data": { "version": "4.0.0", @@ -27279,22 +27267,22 @@ } }, "nocodb-sdk": { - "version": "0.109.0", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.109.0.tgz", - "integrity": "sha512-5yKLiiDR0noHdhyOqqdF/+UWyTqU7em6QZouURRQSxC72qHU1KBrL1ig5JaIL0CX0pwlANq9hnQ0OzSH8J46fQ==", + "version": "file:../nocodb-sdk", "requires": { + "@typescript-eslint/eslint-plugin": "^4.0.1", + "@typescript-eslint/parser": "^4.0.1", "axios": "^0.21.1", - "jsep": "^1.3.6" - }, - "dependencies": { - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - } + "cspell": "^4.1.0", + "eslint": "^7.8.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-functional": "^3.0.2", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-prettier": "^4.0.0", + "jsep": "^1.3.6", + "npm-run-all": "^4.1.5", + "prettier": "^2.1.1", + "typescript": "^4.0.2" } }, "node-abi": { diff --git a/packages/nc-gui/package.json b/packages/nc-gui/package.json index 6f922a8b68..44f0905daf 100644 --- a/packages/nc-gui/package.json +++ b/packages/nc-gui/package.json @@ -54,7 +54,7 @@ "leaflet.markercluster": "^1.5.3", "locale-codes": "^1.3.1", "monaco-editor": "^0.33.0", - "nocodb-sdk": "0.109.0", + "nocodb-sdk": "file:../nocodb-sdk", "papaparse": "^5.3.2", "pinia": "^2.0.33", "qrcode": "^1.5.1", diff --git a/packages/nocodb-sdk/package-lock.json b/packages/nocodb-sdk/package-lock.json index b0c351704d..98358d85f2 100644 --- a/packages/nocodb-sdk/package-lock.json +++ b/packages/nocodb-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "nocodb-sdk", - "version": "0.108.1", + "version": "0.109.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nocodb-sdk", - "version": "0.108.1", + "version": "0.109.0", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", diff --git a/packages/nocodb/package-lock.json b/packages/nocodb/package-lock.json index 238d0fe5f0..8dc5356fb5 100644 --- a/packages/nocodb/package-lock.json +++ b/packages/nocodb/package-lock.json @@ -83,7 +83,7 @@ "nc-lib-gui": "0.109.0", "nc-plugin": "^0.1.3", "ncp": "^2.0.0", - "nocodb-sdk": "0.109.0", + "nocodb-sdk": "file:../nocodb-sdk", "nodemailer": "^6.4.10", "object-hash": "^3.0.0", "os-locale": "^6.0.2", @@ -191,7 +191,6 @@ }, "../nocodb-sdk": { "version": "0.109.0", - "extraneous": true, "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", @@ -13207,13 +13206,8 @@ } }, "node_modules/nocodb-sdk": { - "version": "0.109.0", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.109.0.tgz", - "integrity": "sha512-5yKLiiDR0noHdhyOqqdF/+UWyTqU7em6QZouURRQSxC72qHU1KBrL1ig5JaIL0CX0pwlANq9hnQ0OzSH8J46fQ==", - "dependencies": { - "axios": "^0.21.1", - "jsep": "^1.3.6" - } + "resolved": "../nocodb-sdk", + "link": true }, "node_modules/node-abort-controller": { "version": "3.1.1", @@ -28485,12 +28479,22 @@ "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" }, "nocodb-sdk": { - "version": "0.109.0", - "resolved": "https://registry.npmjs.org/nocodb-sdk/-/nocodb-sdk-0.109.0.tgz", - "integrity": "sha512-5yKLiiDR0noHdhyOqqdF/+UWyTqU7em6QZouURRQSxC72qHU1KBrL1ig5JaIL0CX0pwlANq9hnQ0OzSH8J46fQ==", + "version": "file:../nocodb-sdk", "requires": { + "@typescript-eslint/eslint-plugin": "^4.0.1", + "@typescript-eslint/parser": "^4.0.1", "axios": "^0.21.1", - "jsep": "^1.3.6" + "cspell": "^4.1.0", + "eslint": "^7.8.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-functional": "^3.0.2", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-prettier": "^4.0.0", + "jsep": "^1.3.6", + "npm-run-all": "^4.1.5", + "prettier": "^2.1.1", + "typescript": "^4.0.2" } }, "node-abort-controller": { diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index 784f37a1db..7e3211dd21 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -116,7 +116,7 @@ "nc-lib-gui": "0.109.0", "nc-plugin": "^0.1.3", "ncp": "^2.0.0", - "nocodb-sdk": "0.109.0", + "nocodb-sdk": "file:../nocodb-sdk", "nodemailer": "^6.4.10", "object-hash": "^3.0.0", "os-locale": "^6.0.2", @@ -203,4 +203,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} From 379f82b5e6fe57314efb1e318cb0e9ca92cecc6d Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Tue, 13 Jun 2023 13:06:20 +0530 Subject: [PATCH 05/12] fix: on delete fail, return error instead of 200 OK Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- packages/nocodb/src/services/datas.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/services/datas.service.ts b/packages/nocodb/src/services/datas.service.ts index c41609f6c0..56bdc589a1 100644 --- a/packages/nocodb/src/services/datas.service.ts +++ b/packages/nocodb/src/services/datas.service.ts @@ -106,7 +106,7 @@ export class DatasService { // todo: Should have error http status code const message = await baseModel.hasLTARData(param.rowId, model); if (message.length) { - return { message }; + NcError.badRequest(message); } return await baseModel.delByPk(param.rowId, null, param.cookie); } From e3bcd63db698c98fd329714176bb516c0ca2bc27 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:16:57 +0530 Subject: [PATCH 06/12] test: api response corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- .../tests/unit/rest/tests/tableRow.test.ts | 8 +- .../tests/unit/rest/tests/viewRow.test.ts | 131 +++++++++--------- 2 files changed, 68 insertions(+), 71 deletions(-) diff --git a/packages/nocodb/tests/unit/rest/tests/tableRow.test.ts b/packages/nocodb/tests/unit/rest/tests/tableRow.test.ts index 488bb50493..bffdeb77b8 100644 --- a/packages/nocodb/tests/unit/rest/tests/tableRow.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/tableRow.test.ts @@ -1425,7 +1425,7 @@ function tableTest() { const response = await request(context.app) .delete(`/api/v1/db/data/noco/${project.id}/${table.id}/${row['Id']}`) .set('xc-auth', context.token) - .expect(200); + .expect(400); const deleteRow = await getRow(context, { project, table, id: row['Id'] }); if (!deleteRow) { @@ -1433,11 +1433,9 @@ function tableTest() { } if ( - !(response.body.message[0] as string).includes( - 'is a LinkToAnotherRecord of', - ) + !(response.body.msg as string).includes('is a LinkToAnotherRecord of') ) { - throw new Error('Should give ltar foreign key error'); + throw new Error('Should give LTAR foreign key error'); } }); diff --git a/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts b/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts index 1cf5501855..7056bde79a 100644 --- a/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/viewRow.test.ts @@ -1,12 +1,11 @@ import 'mocha'; +import { isString } from 'util'; +import request from 'supertest'; +import { UITypes, ViewTypes } from 'nocodb-sdk'; +import { expect } from 'chai'; import init from '../../init'; import { createProject, createSakilaProject } from '../../factory/project'; -import request from 'supertest'; -import Project from '../../../../src/models/Project'; -import Model from '../../../../src/models/Model'; import { createTable, getTable } from '../../factory/table'; -import View from '../../../../src/models/View'; -import { ColumnType, UITypes, ViewTypes } from 'nocodb-sdk'; import { createView } from '../../factory/view'; import { createColumn, @@ -21,9 +20,11 @@ import { getOneRow, getRow, } from '../../factory/row'; -import { expect } from 'chai'; import { isPg } from '../../init/db'; -import { isString } from 'util'; +import type { ColumnType } from 'nocodb-sdk'; +import type View from '../../../../src/models/View'; +import type Model from '../../../../src/models/Model'; +import type Project from '../../../../src/models/Project'; // Test case list // 1. Get view row list g @@ -160,7 +161,7 @@ function viewRowTests() { const testGetViewRowList = async (view: View) => { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .expect(200); @@ -179,7 +180,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}`, ) .set('xc-auth', context.token) .expect(200); @@ -225,7 +226,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -244,7 +245,7 @@ function viewRowTests() { requiredColumns.map((c: ColumnType) => ({ title: c.title, uidt: c.uidt, - })) + })), ); throw new Error('Wrong columns'); } @@ -271,7 +272,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}`, ) .set('xc-auth', context.token) .query({ @@ -287,7 +288,7 @@ function viewRowTests() { expect( Object.keys(response.body.find((e) => e.key === 'NC-17').value.list[0]) .sort() - .join(',') + .join(','), ).to.equal('FilmId,Title'); }; @@ -297,14 +298,14 @@ function viewRowTests() { const testDescSortedViewDataList = async (view: View) => { const firstNameColumn = customerColumns.find( - (col) => col.title === 'FirstName' + (col) => col.title === 'FirstName', ); const visibleColumns = [firstNameColumn]; const sortInfo = [{ fk_column_id: firstNameColumn.id, direction: 'desc' }]; const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -332,7 +333,7 @@ function viewRowTests() { Math.trunc(pageInfo.totalRows / pageInfo.pageSize) * pageInfo.pageSize; const lastPageResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -375,7 +376,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}`, ) .set('xc-auth', context.token) .query({ @@ -390,7 +391,7 @@ function viewRowTests() { expect(response.body).to.be.have.length(6); expect( - response.body.find((e) => e.key === 'PG').value.list[0].Title + response.body.find((e) => e.key === 'PG').value.list[0].Title, ).to.equal('WORST BANGER'); }; @@ -400,14 +401,14 @@ function viewRowTests() { const testAscSortedViewDataList = async (view: View) => { const firstNameColumn = customerColumns.find( - (col) => col.title === 'FirstName' + (col) => col.title === 'FirstName', ); const visibleColumns = [firstNameColumn]; const sortInfo = [{ fk_column_id: firstNameColumn.id, direction: 'asc' }]; const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -435,7 +436,7 @@ function viewRowTests() { Math.trunc(pageInfo.totalRows / pageInfo.pageSize) * pageInfo.pageSize; const lastPageResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -478,7 +479,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${filmTable.id}/views/${view.id}/group/${ratingColumn.id}`, ) .set('xc-auth', context.token) .query({ @@ -493,7 +494,7 @@ function viewRowTests() { expect(response.body).to.be.have.length(6); expect( - response.body.find((e) => e.key === 'PG').value.list[0].Title + response.body.find((e) => e.key === 'PG').value.list[0].Title, ).to.equal('ACADEMY DINOSAUR'); }; @@ -502,7 +503,7 @@ function viewRowTests() { }); const testGetViewDataListWithRequiredColumnsAndFilter = async ( - viewType: ViewTypes + viewType: ViewTypes, ) => { const rentalTable = await getTable({ project: sakilaProject, @@ -523,7 +524,7 @@ function viewRowTests() { }); const paymentListColumn = (await rentalTable.getColumns()).find( - (c) => c.title === 'Payment List' + (c) => c.title === 'Payment List', ); const nestedFilter = { @@ -549,7 +550,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -560,7 +561,7 @@ function viewRowTests() { const ascResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -579,7 +580,7 @@ function viewRowTests() { const descResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${rentalTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -606,7 +607,7 @@ function viewRowTests() { }); const testGetNestedSortedFilteredTableDataListWithLookupColumn = async ( - viewType: ViewTypes + viewType: ViewTypes, ) => { const view = await createView(context, { title: 'View', @@ -624,11 +625,11 @@ function viewRowTests() { }); const paymentListColumn = (await customerTable.getColumns()).find( - (c) => c.title === 'Payment List' + (c) => c.title === 'Payment List', ); const activeColumn = (await customerTable.getColumns()).find( - (c) => c.title === 'Active' + (c) => c.title === 'Active', ); const nestedFields = { @@ -681,7 +682,7 @@ function viewRowTests() { const ascResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .query({ @@ -703,7 +704,7 @@ function viewRowTests() { } const nestedRentalResponse = Object.keys( - ascResponse.body.list[0]['Rental List'][0] + ascResponse.body.list[0]['Rental List'][0], ); if ( @@ -719,7 +720,7 @@ function viewRowTests() { it('Get nested sorted filtered table with nested fields data list with a rollup column in customer table view grid', async () => { await testGetNestedSortedFilteredTableDataListWithLookupColumn( - ViewTypes.GRID + ViewTypes.GRID, ); }); @@ -774,7 +775,7 @@ function viewRowTests() { await request(context.app) .post( - `/api/v1/db/data/noco/${project.id}/${table.id}/views/${nonRelatedView.id}` + `/api/v1/db/data/noco/${project.id}/${table.id}/views/${nonRelatedView.id}`, ) .set('xc-auth', context.token) .send({ @@ -802,7 +803,7 @@ function viewRowTests() { // todo: Test that all the columns needed to be shown in the view are returned const testFindOneSortedDataWithRequiredColumns = async ( - viewType: ViewTypes + viewType: ViewTypes, ) => { const view = await createView(context, { title: 'View', @@ -810,13 +811,13 @@ function viewRowTests() { type: viewType, }); const firstNameColumn = customerColumns.find( - (col) => col.title === 'FirstName' + (col) => col.title === 'FirstName', ); const visibleColumns = [firstNameColumn]; let response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/find-one` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/find-one`, ) .set('xc-auth', context.token) .query({ @@ -837,7 +838,7 @@ function viewRowTests() { response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/find-one` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/find-one`, ) .set('xc-auth', context.token) .query({ @@ -870,7 +871,7 @@ function viewRowTests() { }); const testFindOneSortedFilteredNestedFieldsDataWithRollup = async ( - viewType: ViewTypes + viewType: ViewTypes, ) => { const rollupColumn = await createRollupColumn(context, { project: sakilaProject, @@ -893,11 +894,11 @@ function viewRowTests() { }); const paymentListColumn = (await customerTable.getColumns()).find( - (c) => c.title === 'Payment List' + (c) => c.title === 'Payment List', ); const activeColumn = (await customerTable.getColumns()).find( - (c) => c.title === 'Active' + (c) => c.title === 'Active', ); const nestedFields = { @@ -950,7 +951,7 @@ function viewRowTests() { const ascResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/find-one` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/find-one`, ) .set('xc-auth', context.token) .query({ @@ -995,7 +996,7 @@ function viewRowTests() { type: viewType, }); const firstNameColumn = customerColumns.find( - (col) => col.title === 'FirstName' + (col) => col.title === 'FirstName', ); const rollupColumn = await createRollupColumn(context, { @@ -1012,7 +1013,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/groupby` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/groupby`, ) .set('xc-auth', context.token) .query({ @@ -1049,7 +1050,7 @@ function viewRowTests() { }); const firstNameColumn = customerColumns.find( - (col) => col.title === 'FirstName' + (col) => col.title === 'FirstName', ); const rollupColumn = await createRollupColumn(context, { @@ -1066,7 +1067,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/groupby` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/groupby`, ) .set('xc-auth', context.token) .query({ @@ -1105,7 +1106,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/count` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/count`, ) .set('xc-auth', context.token) .expect(200); @@ -1136,7 +1137,7 @@ function viewRowTests() { const listResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}`, ) .set('xc-auth', context.token) .expect(200); @@ -1145,7 +1146,7 @@ function viewRowTests() { const readResponse = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/${row['CustomerId']}` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/${row['CustomerId']}`, ) .set('xc-auth', context.token) .expect(200); @@ -1181,7 +1182,7 @@ function viewRowTests() { const updateResponse = await request(context.app) .patch( - `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}` + `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}`, ) .set('xc-auth', context.token) .send({ @@ -1207,7 +1208,7 @@ function viewRowTests() { }); const testUpdateViewRowWithValidationAndInvalidData = async ( - viewType: ViewTypes + viewType: ViewTypes, ) => { const table = await createTable(context, project); const emailColumn = await createColumn(context, table, { @@ -1228,7 +1229,7 @@ function viewRowTests() { await request(context.app) .patch( - `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}` + `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}`, ) .set('xc-auth', context.token) .send({ @@ -1253,7 +1254,7 @@ function viewRowTests() { // todo: Test with form view const testUpdateViewRowWithValidationAndValidData = async ( - viewType: ViewTypes + viewType: ViewTypes, ) => { const table = await createTable(context, project); const emailColumn = await createColumn(context, table, { @@ -1273,7 +1274,7 @@ function viewRowTests() { const response = await request(context.app) .patch( - `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}` + `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}`, ) .set('xc-auth', context.token) .send({ @@ -1314,7 +1315,7 @@ function viewRowTests() { await request(context.app) .delete( - `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}` + `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}`, ) .set('xc-auth', context.token) .expect(200); @@ -1339,7 +1340,7 @@ function viewRowTests() { }); const testDeleteViewRowWithForiegnKeyConstraint = async ( - viewType: ViewTypes + viewType: ViewTypes, ) => { const table = await createTable(context, project); const relatedTable = await createTable(context, project, { @@ -1371,10 +1372,10 @@ function viewRowTests() { const response = await request(context.app) .delete( - `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}` + `/api/v1/db/data/noco/${project.id}/${table.id}/views/${view.id}/${row['Id']}`, ) .set('xc-auth', context.token) - .expect(200); + .expect(400); const deleteRow = await getRow(context, { project, table, id: row['Id'] }); if (!deleteRow) { @@ -1382,9 +1383,7 @@ function viewRowTests() { } if ( - !(response.body.message[0] as string).includes( - 'is a LinkToAnotherRecord of' - ) + !(response.body.msg as string).includes('is a LinkToAnotherRecord of') ) { throw new Error('Should give ltar foreign key error'); } @@ -1415,7 +1414,7 @@ function viewRowTests() { const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/${row['CustomerId']}/exist` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/${row['CustomerId']}/exist`, ) .set('xc-auth', context.token) .expect(200); @@ -1445,7 +1444,7 @@ function viewRowTests() { }); const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/999999/exist` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.id}/views/${view.id}/999999/exist`, ) .set('xc-auth', context.token) .expect(200); @@ -1475,7 +1474,7 @@ function viewRowTests() { }); const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.title}/views/${view.id}/export/csv` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.title}/views/${view.id}/export/csv`, ) .set('xc-auth', context.token) .expect(200); @@ -1499,7 +1498,7 @@ function viewRowTests() { }); const response = await request(context.app) .get( - `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.title}/views/${view.id}/export/excel` + `/api/v1/db/data/noco/${sakilaProject.id}/${customerTable.title}/views/${view.id}/export/excel`, ) .set('xc-auth', context.token) .expect(200); From 4deb2a1f0b63e1e4af2bb8e6bfdd89e9e94ed90d Mon Sep 17 00:00:00 2001 From: mertmit Date: Tue, 13 Jun 2023 18:38:50 +0300 Subject: [PATCH 07/12] fix: avoid adding undo if delete fails Signed-off-by: mertmit --- packages/nc-gui/composables/useViewData.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/nc-gui/composables/useViewData.ts b/packages/nc-gui/composables/useViewData.ts index 0667742671..34bb78db9b 100644 --- a/packages/nc-gui/composables/useViewData.ts +++ b/packages/nc-gui/composables/useViewData.ts @@ -611,6 +611,11 @@ export function useViewData( .map((c) => row.row[c.title!]) .join('___') + const deleted = await deleteRowById(id as string) + if (!deleted) { + return + } + if (!undo) { addUndo({ redo: { @@ -648,11 +653,6 @@ export function useViewData( scope: defineViewScope({ view: viewMeta.value }), }) } - - const deleted = await deleteRowById(id as string) - if (!deleted) { - return - } } formattedData.value.splice(rowIndex, 1) From 481abfe87b4017eda72b38c4297186a0348e84e0 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Tue, 13 Jun 2023 22:53:07 +0530 Subject: [PATCH 08/12] feat(gui): update method to support custom role Signed-off-by: Pranav C --- .../composables/useUIPermission/index.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/composables/useUIPermission/index.ts b/packages/nc-gui/composables/useUIPermission/index.ts index fcac173c89..806b006d02 100644 --- a/packages/nc-gui/composables/useUIPermission/index.ts +++ b/packages/nc-gui/composables/useUIPermission/index.ts @@ -24,14 +24,32 @@ export const useUIPermission = createSharedComposable(() => { const { previewAs } = useGlobal() const { allRoles } = useRoles() - const isUIAllowed = (permission: Permission | string, skipPreviewAs = false) => { + const isUIAllowed = ( + permission: Permission | string, + skipPreviewAs = false, + userRoles?: string | Record | string[], + ) => { if (previewAs.value && !skipPreviewAs) { return hasPermission(previewAs.value, true, permission) } - return Object.entries(allRoles.value).some(([role, hasRole]) => - hasPermission(role as Role | ProjectRole, hasRole, permission), - ) + let roles: Record = {} + + if (!userRoles) { + roles = allRoles.value + } else if (Array.isArray(userRoles) || typeof userRoles === 'string') { + roles = (Array.isArray(userRoles) ? userRoles : userRoles.split(',')) + // filter out any empty-string/null/undefined values + .filter(Boolean) + .reduce>((acc, role) => { + acc[role] = true + return acc + }, {}) + } else if (typeof userRoles === 'object') { + roles = userRoles + } + + return Object.entries(roles).some(([role, hasRole]) => hasPermission(role as Role | ProjectRole, hasRole, permission)) } return { isUIAllowed } From 11d60017de00b0323f4c19a84c87f1dff662084e Mon Sep 17 00:00:00 2001 From: Pranav C Date: Tue, 13 Jun 2023 22:59:50 +0530 Subject: [PATCH 09/12] fix(gui): show action button based on project user permission Signed-off-by: Pranav C --- packages/nc-gui/pages/index/index/index.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/pages/index/index/index.vue b/packages/nc-gui/pages/index/index/index.vue index 8aeedb8bc6..626f2ef3f7 100644 --- a/packages/nc-gui/pages/index/index/index.vue +++ b/packages/nc-gui/pages/index/index/index.vue @@ -249,7 +249,7 @@ const copyProjectMeta = async () => {
-