diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 2b985a4b02..42e046774a 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -4758,7 +4758,7 @@ class BaseModelSqlv2 { } protected _convertUserFormat( - userColumns: Record[], + userColumns: Column[], baseUsers: Partial[], d: Record, ) { @@ -4783,6 +4783,11 @@ class BaseModelSqlv2 { }; }); } + + // CreatedBy and LastModifiedBy are always singular + if ([UITypes.CreatedBy, UITypes.LastModifiedBy].includes(col.uidt)) { + d[col.id] = d[col.id]?.[0] ?? null; + } } } } catch {} diff --git a/packages/nocodb/tests/unit/factory/row.ts b/packages/nocodb/tests/unit/factory/row.ts index 6868049834..bf0a0428c9 100644 --- a/packages/nocodb/tests/unit/factory/row.ts +++ b/packages/nocodb/tests/unit/factory/row.ts @@ -1,4 +1,8 @@ -import { isCreatedOrLastModifiedTimeCol, UITypes } from 'nocodb-sdk' +import { + isCreatedOrLastModifiedByCol, + isCreatedOrLastModifiedTimeCol, + UITypes, +} from 'nocodb-sdk'; import request from 'supertest'; import Model from '../../../src/models/Model'; import NcConnectionMgrv2 from '../../../src/utils/common/NcConnectionMgrv2'; @@ -258,7 +262,8 @@ const generateDefaultRowAttributes = ({ column.uidt === UITypes.LinkToAnotherRecord || column.uidt === UITypes.ForeignKey || column.uidt === UITypes.ID || - isCreatedOrLastModifiedTimeCol(column) + isCreatedOrLastModifiedTimeCol(column) || + isCreatedOrLastModifiedByCol(column) ) { return acc; } diff --git a/packages/nocodb/tests/unit/init/index.ts b/packages/nocodb/tests/unit/init/index.ts index f467f51d47..9ce7e4fd22 100644 --- a/packages/nocodb/tests/unit/init/index.ts +++ b/packages/nocodb/tests/unit/init/index.ts @@ -39,7 +39,7 @@ export default async function (forceReset = false, roles = 'editor') { // } await cleanupMeta(); - const { token } = await createUser({ app: server }, { roles }); + const { token, user } = await createUser({ app: server }, { roles }); const extra: any = {}; @@ -61,6 +61,7 @@ export default async function (forceReset = false, roles = 'editor') { return { app: server, token, + user, dbConfig: TestDbMngr.dbConfig, sakilaDbConfig: TestDbMngr.getSakilaDbConfig(), ...extra, diff --git a/packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts b/packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts index 301b27aaa8..f0f8d37530 100644 --- a/packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts +++ b/packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts @@ -42,7 +42,7 @@ function baseModelSqlTests() { it('Insert record', async () => { const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; const columns = await table.getColumns(); @@ -54,11 +54,15 @@ function baseModelSqlTests() { ); const insertedRow = (await baseModelSql.list())[0]; - inputData.CreatedAt = response.CreatedAt; - inputData.UpdatedAt = response.UpdatedAt; + inputData.CreatedBy = { + id: context.user.id, + email: context.user.email, + display_name: context.user.display_name, + }; + inputData.UpdatedBy = null; - expect(insertedRow).to.include(inputData); - expect(insertedRow).to.include(response); + expect(insertedRow).to.deep.include(inputData); + expect(insertedRow).to.deep.include(response); const rowInsertedAudit = (await Audit.baseAuditList(base.id, {})).find( (audit) => audit.op_sub_type === 'INSERT', @@ -80,7 +84,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; const bulkData = Array(10) .fill(0) @@ -125,7 +129,7 @@ function baseModelSqlTests() { it('Update record', async () => { const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; const columns = await table.getColumns(); @@ -162,7 +166,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; const bulkData = Array(10) .fill(0) @@ -206,7 +210,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; const bulkData = Array(10) .fill(0) @@ -256,7 +260,7 @@ function baseModelSqlTests() { it('Delete record', async () => { const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, params: { id: 1 }, }; @@ -294,7 +298,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; const bulkData = Array(10) .fill(0) @@ -337,7 +341,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; const bulkData = Array(10) .fill(0) @@ -403,7 +407,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; await baseModelSql.nestedInsert( @@ -462,7 +466,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; await baseModelSql.insert( @@ -530,7 +534,7 @@ function baseModelSqlTests() { const columns = await table.getColumns(); const request = { clientIp: '::ffff:192.0.0.1', - user: { email: 'test@example.com' }, + user: context.user, }; await baseModelSql.insert( diff --git a/packages/nocodb/tests/unit/rest/tests/columnTypeSpecific.test.ts b/packages/nocodb/tests/unit/rest/tests/columnTypeSpecific.test.ts index 7629981939..3206485f17 100644 --- a/packages/nocodb/tests/unit/rest/tests/columnTypeSpecific.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/columnTypeSpecific.test.ts @@ -1,5 +1,4 @@ import 'mocha'; -import { title } from 'process'; import request from 'supertest'; import { UITypes } from 'nocodb-sdk'; import { expect } from 'chai'; @@ -10,12 +9,7 @@ import { createQrCodeColumn, deleteColumn, } from '../../factory/column'; -import { - createTable, - getColumnsByAPI, - getTable, - getTableByAPI, -} from '../../factory/table'; +import { createTable, getColumnsByAPI, getTable } from '../../factory/table'; import { createBulkRows, listRow, rowMixedValue } from '../../factory/row'; import type Model from '../../../../src/models/Model'; import type Base from '~/models/Base'; @@ -37,6 +31,39 @@ function columnTypeSpecificTests() { const qrValueReferenceColumnTitle = 'Qr Value Column'; const qrCodeReferenceColumnTitle = 'Qr Code Column'; + const defaultTableColumns = [ + { + title: 'Id', + uidt: UITypes.ID, + system: false, + }, + { + title: 'DateField', + uidt: UITypes.Date, + system: false, + }, + { + title: 'CreatedAt', + uidt: UITypes.CreatedTime, + system: true, + }, + { + title: 'UpdatedAt', + uidt: UITypes.LastModifiedTime, + system: true, + }, + { + title: 'CreatedBy', + uidt: UITypes.CreatedBy, + system: true, + }, + { + title: 'UpdatedBy', + uidt: UITypes.LastModifiedBy, + system: true, + }, + ]; + describe('Qr Code Column', () => { beforeEach(async function () { console.time('#### columnTypeSpecificTests'); @@ -89,7 +116,7 @@ function columnTypeSpecificTests() { ), ).to.eq(true); - const response = await request(context.app) + const _response = await request(context.app) .delete(`/api/v1/db/meta/columns/${qrValueReferenceColumn.id}`) .set('xc-auth', context.token) .send({}); @@ -133,7 +160,7 @@ function columnTypeSpecificTests() { columns = await table.getColumns(); - const rowAttributes = []; + const rowAttributes: any = []; for (let i = 0; i < 100; i++) { const row = { DateField: rowMixedValue(columns[1], i), @@ -154,26 +181,19 @@ function columnTypeSpecificTests() { describe('Basic verification', async () => { it('New table: verify system fields are added by default', async () => { - // Id, Date, CreatedAt, LastModifiedAt, Createdby, LastModifiedBy - expect(columns.length).to.equal(6); - - expect(columns[2].title).to.equal('CreatedAt'); - expect(columns[2].uidt).to.equal(UITypes.CreatedTime); - expect(!!columns[2].system).to.equal(true); - expect(columns[3].title).to.equal('UpdatedAt'); - expect(columns[3].uidt).to.equal(UITypes.LastModifiedTime); - expect(columns[3].system).to.equal(true); - - expect(columns[4].title).to.equal('CreatedBy'); - expect(columns[4].uidt).to.equal(UITypes.CreatedBy); - expect(columns[4].system).to.equal(true); - expect(columns[5].title).to.equal('UpdatedBy'); - expect(columns[5].uidt).to.equal(UITypes.LastModifiedBy); - expect(columns[5].system).to.equal(true); + // Id, Date, CreatedAt, LastModifiedAt + expect(columns.length).to.equal(defaultTableColumns.length); + for (let i = 0; i < defaultTableColumns.length; i++) { + expect(columns[i].title).to.equal(defaultTableColumns[i].title); + expect(columns[i].uidt).to.equal(defaultTableColumns[i].uidt); + expect(columns[i].system).to.equal(defaultTableColumns[i].system); + } }); it('New table: should not be able to delete system fields', async () => { - for (let i = 2; i < 6; i++) { + // try to delete system fields + for (let i = 0; i < defaultTableColumns.length; i++) { + if (!defaultTableColumns[i].system) return; await request(context.app) .delete(`/api/v2/meta/columns/${columns[i].id}`) .set('xc-auth', context.token) @@ -332,9 +352,16 @@ function columnTypeSpecificTests() { const records = await listRow({ base, table }); // verify contents of both fields are same - expect(columns.columns[6].title).to.equal('CreatedAt2'); - expect(columns.columns[6].uidt).to.equal(UITypes.CreatedTime); - expect(columns.columns[6].system).to.equal(false); + expect(columns.columns[defaultTableColumns.length].title).to.equal( + 'CreatedAt2', + ); + expect(columns.columns[defaultTableColumns.length].uidt).to.equal( + UITypes.CreatedTime, + ); + expect(columns.columns[defaultTableColumns.length].system).to.equal( + false, + ); + expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2); const d1 = new Date(); @@ -375,14 +402,26 @@ function columnTypeSpecificTests() { const records = await listRow({ base, table }); // verify contents of both fields are same - expect(columns.columns[6].title).to.equal('CreatedBy2'); - expect(columns.columns[6].uidt).to.equal(UITypes.CreatedBy); - expect(columns.columns[6].system).to.equal(false); + expect(columns.columns[defaultTableColumns.length].title).to.equal( + 'CreatedBy2', + ); + expect(columns.columns[defaultTableColumns.length].uidt).to.equal( + UITypes.CreatedBy, + ); + expect(columns.columns[defaultTableColumns.length].system).to.equal( + false, + ); expect(records[0].CreatedBy).to.deep.equal(records[0].CreatedBy2); - expect(columns.columns[7].title).to.equal('ModifiedBy2'); - expect(columns.columns[7].uidt).to.equal(UITypes.LastModifiedBy); - expect(columns.columns[7].system).to.equal(false); + expect(columns.columns[defaultTableColumns.length + 1].title).to.equal( + 'ModifiedBy2', + ); + expect(columns.columns[defaultTableColumns.length + 1].uidt).to.equal( + UITypes.LastModifiedBy, + ); + expect(columns.columns[defaultTableColumns.length + 1].system).to.equal( + false, + ); expect(records[0].UpdatedBy).to.deep.equal(records[0].ModifiedBy2); // update record should fail @@ -419,7 +458,10 @@ function columnTypeSpecificTests() { // get all columns let columns = await getColumnsByAPI(context, base, table); // delete the field - await deleteColumn(context, { table, column: columns.columns[6] }); + await deleteColumn(context, { + table, + column: columns.columns[defaultTableColumns.length], + }); // create column again await createColumn(context, table, { title: 'CreatedAt2', @@ -433,9 +475,16 @@ function columnTypeSpecificTests() { const records = await listRow({ base, table }); // verify contents of both fields are same - expect(columns.columns[6].title).to.equal('CreatedAt2'); - expect(columns.columns[6].uidt).to.equal(UITypes.CreatedTime); - expect(columns.columns[6].system).to.equal(false); + expect(columns.columns[defaultTableColumns.length].title).to.equal( + 'CreatedAt2', + ); + expect(columns.columns[defaultTableColumns.length].uidt).to.equal( + UITypes.CreatedTime, + ); + expect(columns.columns[defaultTableColumns.length].system).to.equal( + false, + ); + expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2); }); @@ -463,9 +512,15 @@ function columnTypeSpecificTests() { const records = await listRow({ base, table }); // verify contents of both fields are same - expect(columns.columns[6].title).to.equal('CreatedBy2'); - expect(columns.columns[6].uidt).to.equal(UITypes.CreatedBy); - expect(columns.columns[6].system).to.equal(false); + expect(columns.columns[defaultTableColumns.length].title).to.equal( + 'CreatedBy2', + ); + expect(columns.columns[defaultTableColumns.length].uidt).to.equal( + UITypes.CreatedBy, + ); + expect(columns.columns[defaultTableColumns.length].system).to.equal( + false, + ); expect(records[0].CreatedBy).to.deep.equal(records[0].CreatedBy2); }); }); diff --git a/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts b/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts index 73a1449241..3a3484e936 100644 --- a/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts +++ b/packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts @@ -83,6 +83,7 @@ import 'mocha'; import { + isCreatedOrLastModifiedByCol, isCreatedOrLastModifiedTimeCol, UITypes, ViewTypes, @@ -679,7 +680,12 @@ function textBased() { expect( verifyColumnsInRsp( rsp.body.list[0], - columns.filter((c) => !isCreatedOrLastModifiedTimeCol(c) || !c.system), + columns.filter( + (c) => + (!isCreatedOrLastModifiedTimeCol(c) && + !isCreatedOrLastModifiedByCol(c)) || + !c.system, + ), ), ).to.equal(true); const filteredArray = rsp.body.list.map((r) => r.SingleLineText); @@ -700,7 +706,9 @@ function textBased() { const displayColumns = columns.filter( (c) => c.title !== 'SingleLineText' && - (!isCreatedOrLastModifiedTimeCol(c) || !c.system), + ((!isCreatedOrLastModifiedTimeCol(c) && + !isCreatedOrLastModifiedByCol(c)) || + !c.system), ); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); }); @@ -749,7 +757,8 @@ function textBased() { (c) => c.title !== 'MultiLineText' && c.title !== 'Email' && - !isCreatedOrLastModifiedTimeCol(c), + !isCreatedOrLastModifiedTimeCol(c) && + !isCreatedOrLastModifiedByCol(c), ); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); return gridView; @@ -769,7 +778,8 @@ function textBased() { (c) => c.title !== 'MultiLineText' && c.title !== 'Email' && - !isCreatedOrLastModifiedTimeCol(c), + !isCreatedOrLastModifiedTimeCol(c) && + !isCreatedOrLastModifiedByCol(c), ); expect(rsp.body.pageInfo.totalRows).to.equal(61); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); @@ -791,7 +801,8 @@ function textBased() { (c) => c.title !== 'MultiLineText' && c.title !== 'Email' && - !isCreatedOrLastModifiedTimeCol(c), + !isCreatedOrLastModifiedTimeCol(c) && + !isCreatedOrLastModifiedByCol(c), ); expect(rsp.body.pageInfo.totalRows).to.equal(7); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); @@ -1628,7 +1639,8 @@ function dateBased() { delete r.Id; delete r.CreatedAt; delete r.UpdatedAt; - delete r.Id; + delete r.CreatedBy; + delete r.UpdatedBy; }); rsp = await ncAxiosPost({ body: records,