Browse Source

Merge pull request #5403 from nocodb/enhancement/audits

enhancement: audits
pull/5435/head
Raju Udava 2 years ago committed by GitHub
parent
commit
5a416c77dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      packages/nc-gui/components/dashboard/settings/AuditTab.vue
  2. 21
      packages/nc-gui/composables/useExpandedFormStore.ts
  3. 9
      packages/nc-gui/composables/useKanbanViewStore.ts
  4. 9
      packages/nc-gui/composables/useViewData.ts
  5. 6
      packages/nocodb-sdk/src/lib/Api.ts
  6. 6
      packages/nocodb-sdk/src/lib/globals.ts
  7. 9
      packages/nocodb/src/lib/controllers/user/user.ctl.ts
  8. 46
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  9. 6
      packages/nocodb/src/lib/models/Audit.ts
  10. 18
      packages/nocodb/src/lib/models/Model.ts
  11. 2
      packages/nocodb/src/lib/services/audit.svc.ts
  12. 12
      packages/nocodb/src/lib/services/column.svc.ts
  13. 4
      packages/nocodb/src/lib/services/orgUser.svc.ts
  14. 30
      packages/nocodb/src/lib/services/projectUser.svc.ts
  15. 6
      packages/nocodb/src/lib/services/table.svc.ts
  16. 38
      packages/nocodb/src/lib/services/user/index.ts
  17. 6
      packages/nocodb/src/schema/swagger.json
  18. 22
      packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts
  19. 4
      tests/playwright/tests/tableOperations.spec.ts

15
packages/nc-gui/components/dashboard/settings/AuditTab.vue

@ -67,6 +67,7 @@ const columns = [
title: tableHeaderRenderer(t('labels.description')),
dataIndex: 'description',
key: 'description',
customRender: (value: { text: string }) => h('pre', {}, value.text),
},
{
// User
@ -83,6 +84,7 @@ const columns = [
sort: 'desc',
customRender: (value: { text: string }) =>
h(ATooltip, { placement: 'bottom', title: h('span', {}, value.text) }, () => timeAgo(value.text)),
width: '10%',
},
]
</script>
@ -110,7 +112,7 @@ const columns = [
</div>
<a-table
class="w-full"
class="nc-audit-table w-full"
size="small"
:data-source="audits ?? []"
:columns="columns"
@ -124,3 +126,14 @@ const columns = [
</a-table>
</div>
</template>
<style lang="scss">
.nc-audit-table pre {
display: table;
table-layout: fixed;
width: 100%;
white-space: break-spaces;
font-size: unset;
font-family: unset;
}
</style>

21
packages/nc-gui/composables/useExpandedFormStore.ts

@ -7,7 +7,6 @@ import {
computed,
extractPkFromRow,
extractSdkResponseErrorMsg,
getHTMLEncodedText,
message,
populateInsertObject,
ref,
@ -127,7 +126,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
await api.utils.commentRow({
fk_model_id: meta.value?.id as string,
row_id: rowId,
description: comment.value,
description: `The following comment has been created: ${comment.value}`,
})
comment.value = ''
@ -253,22 +252,8 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
})
}
for (const key of Object.keys(updateOrInsertObj)) {
// audit
$api.utils
.auditRowUpdate(id, {
fk_model_id: meta.value.id,
column_name: key,
row_id: id,
value: getHTMLEncodedText(updateOrInsertObj[key]),
prev_value: getHTMLEncodedText(row.value.oldRow[key]),
})
.then(async () => {
/** load latest comments/audit if right drawer is open */
if (commentsDrawer.value) {
await loadCommentsAndLogs()
}
})
if (commentsDrawer.value) {
await loadCommentsAndLogs()
}
} else {
// No columns to update

9
packages/nc-gui/composables/useKanbanViewStore.ts

@ -8,7 +8,6 @@ import {
enumColor,
extractPkFromRow,
extractSdkResponseErrorMsg,
getHTMLEncodedText,
inject,
message,
parseProp,
@ -401,14 +400,6 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// query: { ignoreWebhook: !saved }
// }
)
// audit
$api.utils.auditRowUpdate(id, {
fk_model_id: meta.value?.id as string,
column_name: property,
row_id: id,
value: getHTMLEncodedText(toUpdate.row[property]),
prev_value: getHTMLEncodedText(toUpdate.oldRow[property]),
})
if (!undo) {
const oldRowIndex = moveHistory.value.find((ele) => ele.op === 'removed' && ele.pk === id)

9
packages/nc-gui/composables/useViewData.ts

@ -8,7 +8,6 @@ import {
computed,
extractPkFromRow,
extractSdkResponseErrorMsg,
getHTMLEncodedText,
message,
populateInsertObject,
ref,
@ -350,14 +349,6 @@ export function useViewData(
// query: { ignoreWebhook: !saved }
// }
)
// audit
$api.utils.auditRowUpdate(encodeURIComponent(id), {
fk_model_id: metaValue?.id as string,
column_name: property,
row_id: id,
value: getHTMLEncodedText(toUpdate.row[property]),
prev_value: getHTMLEncodedText(toUpdate.oldRow[property]),
})
if (!undo) {
addUndo({

6
packages/nocodb-sdk/src/lib/Api.ts

@ -154,13 +154,11 @@ export interface AuditType {
| 'LINK_RECORD'
| 'UNLINK_RECORD'
| 'DELETE'
| 'CREATED'
| 'DELETED'
| 'RENAMED'
| 'CREATE'
| 'RENAME'
| 'IMPORT_FROM_ZIP'
| 'EXPORT_TO_FS'
| 'EXPORT_TO_ZIP'
| 'UPDATED'
| 'SIGNIN'
| 'SIGNUP'
| 'PASSWORD_RESET'

6
packages/nocodb-sdk/src/lib/globals.ts

@ -47,13 +47,11 @@ export enum AuditOperationSubTypes {
LINK_RECORD = 'LINK_RECORD',
UNLINK_RECORD = 'UNLINK_RECORD',
DELETE = 'DELETE',
CREATED = 'CREATED',
DELETED = 'DELETED',
RENAMED = 'RENAMED',
CREATE = 'CREATE',
RENAME = 'RENAME',
IMPORT_FROM_ZIP = 'IMPORT_FROM_ZIP',
EXPORT_TO_FS = 'EXPORT_TO_FS',
EXPORT_TO_ZIP = 'EXPORT_TO_ZIP',
UPDATED = 'UPDATED',
SIGNIN = 'SIGNIN',
SIGNUP = 'SIGNUP',
PASSWORD_RESET = 'PASSWORD_RESET',

9
packages/nocodb/src/lib/controllers/user/user.ctl.ts

@ -1,4 +1,5 @@
import { promisify } from 'util';
import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk';
import * as ejs from 'ejs';
import passport from 'passport';
import catchError, { NcError } from '../../meta/helpers/catchError';
@ -65,8 +66,8 @@ async function successfulSignIn({
setTokenCookie(res, refreshToken);
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'SIGNIN',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.SIGNIN,
user: user.email,
ip: req.clientIp,
description: auditDescription,
@ -92,7 +93,7 @@ async function signin(req, res, next) {
info,
req,
res,
auditDescription: 'signed in',
auditDescription: 'User has signed in successfully',
})
)(req, res, next);
}
@ -111,7 +112,7 @@ async function googleSignin(req, res, next) {
info,
req,
res,
auditDescription: 'signed in using Google Auth',
auditDescription: 'User has signed in successfully using Google Auth ',
})
)(req, res, next);
}

46
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts

@ -1864,7 +1864,7 @@ class BaseModelSqlv2 {
await this.execAndParse(query);
const newData = await this.readByPk(id);
await this.afterUpdate(prevData, newData, trx, cookie);
await this.afterUpdate(prevData, newData, trx, cookie, updateObj);
return newData;
} catch (e) {
console.log(e);
@ -2296,7 +2296,7 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.INSERT,
description: DOMPurify.sanitize(
`${id} inserted into ${this.model.title}`
`Record with ID ${id} has been inserted into Table ${this.model.title}`
),
// details: JSON.stringify(data),
ip: req?.clientIp,
@ -2322,7 +2322,9 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.BULK_UPDATE,
description: DOMPurify.sanitize(
`${noOfUpdatedRecords} records bulk updated in ${this.model.title}`
`${noOfUpdatedRecords} ${
noOfUpdatedRecords > 1 ? 'records have' : 'record has'
} been bulk updated in ${this.model.title}`
),
// details: JSON.stringify(data),
ip: req?.clientIp,
@ -2347,7 +2349,9 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.BULK_DELETE,
description: DOMPurify.sanitize(
`${noOfDeletedRecords} records bulk deleted in ${this.model.title}`
`${noOfDeletedRecords} ${
noOfDeletedRecords > 1 ? 'records have' : 'record has'
} been bulk deleted in ${this.model.title}`
),
// details: JSON.stringify(data),
ip: req?.clientIp,
@ -2363,7 +2367,9 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.BULK_INSERT,
description: DOMPurify.sanitize(
`${data.length} records bulk inserted into ${this.model.title}`
`${data.length} ${
data.length > 1 ? 'records have' : 'record has'
} been bulk inserted in ${this.model.title}`
),
// details: JSON.stringify(data),
ip: req?.clientIp,
@ -2387,16 +2393,32 @@ class BaseModelSqlv2 {
prevData: any,
newData: any,
_trx: any,
req
req,
updateObj?: Record<string, any>
): Promise<void> {
const id = this._extractPksValues(newData);
let desc = `Record with ID ${id} has been updated in Table ${this.model.title}.`;
if (updateObj) {
updateObj = await this.model.mapColumnToAlias(updateObj);
for (const k of Object.keys(updateObj)) {
const prevValue =
typeof prevData[k] === 'object'
? JSON.stringify(prevData[k])
: prevData[k];
const newValue =
typeof newData[k] === 'object'
? JSON.stringify(newData[k])
: newData[k];
desc += `\n`;
desc += `Column "${k}" got changed from "${prevValue}" to "${newValue}"`;
}
}
await Audit.insert({
fk_model_id: this.model.id,
row_id: id,
op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.UPDATE,
description: DOMPurify.sanitize(`${id} updated in ${this.model.title}`),
description: DOMPurify.sanitize(desc),
// details: JSON.stringify(data),
ip: req?.clientIp,
user: req?.user?.email,
@ -2424,7 +2446,9 @@ class BaseModelSqlv2 {
row_id: id,
op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.DELETE,
description: DOMPurify.sanitize(`${id} deleted from ${this.model.title}`),
description: DOMPurify.sanitize(
`Record with ID ${id} has been deleted in Table ${this.model.title}`
),
// details: JSON.stringify(data),
ip: req?.clientIp,
user: req?.user?.email,
@ -2695,7 +2719,7 @@ class BaseModelSqlv2 {
op_sub_type: AuditOperationSubTypes.LINK_RECORD,
row_id: rowId,
description: DOMPurify.sanitize(
`Record [id:${childId}] record linked with record [id:${rowId}] record in ${this.model.title}`
`Record [id:${childId}] has been linked with record [id:${rowId}] in ${this.model.title}`
),
// details: JSON.stringify(data),
ip: req?.clientIp,
@ -2797,7 +2821,7 @@ class BaseModelSqlv2 {
op_sub_type: AuditOperationSubTypes.UNLINK_RECORD,
row_id: rowId,
description: DOMPurify.sanitize(
`Record [id:${childId}] record unlinked with record [id:${rowId}] record in ${this.model.title}`
`Record [id:${childId}] has been unlinked with record [id:${rowId}] in ${this.model.title}`
),
// details: JSON.stringify(data),
ip: req?.clientIp,

6
packages/nocodb/src/lib/models/Audit.ts

@ -30,13 +30,11 @@ const opSubTypes = <const>[
'LINK_RECORD',
'UNLINK_RECORD',
'DELETE',
'CREATED',
'DELETED',
'RENAMED',
'CREATE',
'RENAME',
'IMPORT_FROM_ZIP',
'EXPORT_TO_FS',
'EXPORT_TO_ZIP',
'UPDATED',
'SIGNIN',
'SIGNUP',
'PASSWORD_RESET',

18
packages/nocodb/src/lib/models/Model.ts

@ -456,6 +456,24 @@ export default class Model implements TableType {
return insertObj;
}
async mapColumnToAlias(data) {
const res = {};
for (const col of await this.getColumns()) {
if (isVirtualCol(col)) continue;
let val =
data?.[col.title] !== undefined
? data?.[col.title]
: data?.[col.column_name];
if (val !== undefined) {
if (col.uidt === UITypes.Attachment && typeof val !== 'string') {
val = JSON.stringify(val);
}
res[sanitize(col.title)] = val;
}
}
return res;
}
static async updateAliasAndTableName(
tableId,
title: string,

2
packages/nocodb/src/lib/services/audit.svc.ts

@ -36,7 +36,7 @@ export async function auditRowUpdate(param: {
op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.UPDATE,
description: DOMPurify.sanitize(
`Table ${model.table_name} : field ${param.body.column_name} got changed from ${param.body.prev_value} to ${param.body.value}`
`The column ${param.body.column_name} in Table ${model.table_name} has been changed from ${param.body.prev_value} to ${param.body.value}`
),
details: DOMPurify.sanitize(`<span class="">${param.body.column_name}</span>
: <span class="text-decoration-line-through red px-2 lighten-4 black--text">${param.body.prev_value}</span>

12
packages/nocodb/src/lib/services/column.svc.ts

@ -822,9 +822,9 @@ export async function columnUpdate(param: {
await Audit.insert({
project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.UPDATED,
op_sub_type: AuditOperationSubTypes.UPDATE,
user: param.req?.user?.email,
description: `updated column ${column.column_name} with alias ${column.title} from table ${table.table_name}`,
description: `The column ${column.column_name} with alias ${column.title} from table ${table.table_name} has been updated`,
ip: param.req?.clientIp,
}).then(() => {});
@ -1127,9 +1127,9 @@ export async function columnAdd(param: {
await Audit.insert({
project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.CREATED,
op_sub_type: AuditOperationSubTypes.CREATE,
user: param?.req.user?.email,
description: `created column ${colBody.column_name} with alias ${colBody.title} from table ${table.table_name}`,
description: `The column ${colBody.column_name} with alias ${colBody.title} from table ${table.table_name} has been created`,
ip: param?.req.clientIp,
}).then(() => {});
@ -1339,9 +1339,9 @@ export async function columnDelete(param: { req?: any; columnId: string }) {
await Audit.insert({
project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.DELETED,
op_sub_type: AuditOperationSubTypes.DELETE,
user: param?.req?.user?.email,
description: `deleted column ${column.column_name} with alias ${column.title} from table ${table.table_name}`,
description: `The column ${column.column_name} with alias ${column.title} from table ${table.table_name} has been deleted`,
ip: param?.req.clientIp,
}).then(() => {});

4
packages/nocodb/src/lib/services/orgUser.svc.ts

@ -147,7 +147,7 @@ export async function userAdd(param: {
op_type: AuditOperationTypes.ORG_USER,
op_sub_type: AuditOperationSubTypes.INVITE,
user: param.req.user.email,
description: `invited ${email} to ${param.projectId} project `,
description: `${email} has been invited to ${param.projectId} project`,
ip: param.req.clientIp,
});
// in case of single user check for smtp failure
@ -218,7 +218,7 @@ export async function userInviteResend(param: {
op_type: AuditOperationTypes.ORG_USER,
op_sub_type: AuditOperationSubTypes.RESEND_INVITE,
user: user.email,
description: `resent a invite to ${user.email} `,
description: `${user.email} has been re-invited`,
ip: param.req.clientIp,
});

30
packages/nocodb/src/lib/services/projectUser.svc.ts

@ -1,9 +1,13 @@
import { OrgUserRoles } from 'nocodb-sdk';
import {
AuditOperationSubTypes,
AuditOperationTypes,
OrgUserRoles,
PluginCategory,
} from 'nocodb-sdk';
import { T } from 'nc-help';
import validator from 'validator';
import { v4 as uuidv4 } from 'uuid';
import * as ejs from 'ejs';
import { PluginCategory } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import ProjectUser from '../models/ProjectUser';
@ -91,10 +95,10 @@ export async function userInvite(param: {
await Audit.insert({
project_id: param.projectId,
op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.INVITE,
user: param.req.user.email,
description: `invited ${email} to ${param.projectId} project `,
description: `${email} has been invited to ${param.projectId} project`,
ip: param.req.clientIp,
});
} else {
@ -120,8 +124,8 @@ export async function userInvite(param: {
await Audit.insert({
project_id: param.projectId,
op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.INVITE,
user: param.req.user.email,
description: `invited ${email} to ${param.projectId} project `,
ip: param.req.clientIp,
@ -202,10 +206,10 @@ export async function projectUserUpdate(param: {
);
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'ROLES_MANAGEMENT',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.ROLES_MANAGEMENT,
user: param.req.user.email,
description: `updated roles for ${user.email} with ${param.projectUser.roles} `,
description: `Roles for ${user.email} with has been updated to ${param.projectUser.roles}`,
ip: param.req.clientIp,
});
@ -274,10 +278,10 @@ export async function projectUserInviteResend(param: {
await sendInviteEmail(user.email, invite_token, param.req);
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'RESEND_INVITE',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.RESEND_INVITE,
user: user.email,
description: `resent a invite to ${user.email} `,
description: `${user.email} has been re-invited`,
ip: param.req.clientIp,
project_id: param.projectId,
});

6
packages/nocodb/src/lib/services/table.svc.ts

@ -185,7 +185,7 @@ export async function tableDelete(param: {
project_id: project.id,
base_id: base.id,
op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.DELETED,
op_sub_type: AuditOperationSubTypes.DELETE,
user: param.user?.email,
description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `,
ip: param.req?.clientIp,
@ -433,9 +433,9 @@ export async function tableCreate(param: {
project_id: project.id,
base_id: base.id,
op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.CREATED,
op_sub_type: AuditOperationSubTypes.CREATE,
user: param.user?.email,
description: `created table ${tableCreatePayLoad.table_name} with alias ${tableCreatePayLoad.title} `,
description: `Table ${tableCreatePayLoad.table_name} with alias ${tableCreatePayLoad.title} has been created`,
ip: param.req?.clientIp,
}).then(() => {});

38
packages/nocodb/src/lib/services/user/index.ts

@ -1,6 +1,10 @@
import { promisify } from 'util';
import { validatePassword } from 'nocodb-sdk';
import { OrgUserRoles } from 'nocodb-sdk';
import {
AuditOperationSubTypes,
AuditOperationTypes,
OrgUserRoles,
validatePassword,
} from 'nocodb-sdk';
import { T } from 'nc-help';
import * as ejs from 'ejs';
import bcrypt from 'bcryptjs';
@ -120,10 +124,10 @@ export async function passwordChange(param: {
});
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'PASSWORD_CHANGE',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.PASSWORD_CHANGE,
user: user.email,
description: `changed password `,
description: `Password has been changed`,
ip: param.req?.clientIp,
});
@ -178,10 +182,10 @@ export async function passwordForgot(param: {
}
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'PASSWORD_FORGOT',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.PASSWORD_FORGOT,
user: user.email,
description: `requested for password reset `,
description: `Password Reset has been requested`,
ip: param.req?.clientIp,
});
} else {
@ -254,10 +258,10 @@ export async function passwordReset(param: {
});
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'PASSWORD_RESET',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.PASSWORD_RESET,
user: user.email,
description: `did reset password `,
description: `Password has been reset`,
ip: req.clientIp,
});
@ -286,10 +290,10 @@ export async function emailVerification(param: {
});
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'EMAIL_VERIFICATION',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.EMAIL_VERIFICATION,
user: user.email,
description: `verified email `,
description: `Email has been verified`,
ip: req.clientIp,
});
@ -442,10 +446,10 @@ export async function signup(param: {
user = (param.req as any).user;
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'SIGNUP',
op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: AuditOperationSubTypes.SIGNUP,
user: user.email,
description: `signed up `,
description: `User has signed up`,
ip: (param.req as any).clientIp,
});

6
packages/nocodb/src/schema/swagger.json

@ -14020,13 +14020,11 @@
"LINK_RECORD",
"UNLINK_RECORD",
"DELETE",
"CREATED",
"DELETED",
"RENAMED",
"CREATE",
"RENAME",
"IMPORT_FROM_ZIP",
"EXPORT_TO_FS",
"EXPORT_TO_ZIP",
"UPDATED",
"SIGNIN",
"SIGNUP",
"PASSWORD_RESET",

22
packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts

@ -78,7 +78,7 @@ function baseModelSqlTests() {
row_id: '1',
op_type: 'DATA',
op_sub_type: 'INSERT',
description: '1 inserted into Table1_Title',
description: 'Record with ID 1 has been inserted into Table Table1_Title',
});
});
@ -123,7 +123,7 @@ function baseModelSqlTests() {
op_type: 'DATA',
op_sub_type: 'BULK_INSERT',
status: null,
description: '10 records bulk inserted into Table1_Title',
description: '10 records have been bulk inserted in Table1_Title',
details: null,
});
});
@ -156,7 +156,7 @@ function baseModelSqlTests() {
row_id: '1',
op_type: 'DATA',
op_sub_type: 'UPDATE',
description: '1 updated in Table1_Title',
description: 'Record with ID 1 has been updated in Table Table1_Title.\nColumn "Title" got changed from "test-0" to "test"',
});
});
@ -199,7 +199,7 @@ function baseModelSqlTests() {
op_type: 'DATA',
op_sub_type: 'BULK_UPDATE',
status: null,
description: '10 records bulk updated in Table1_Title',
description: '10 records have been bulk updated in Table1_Title',
details: null,
});
});
@ -250,7 +250,7 @@ function baseModelSqlTests() {
op_type: 'DATA',
op_sub_type: 'BULK_UPDATE',
status: null,
description: '4 records bulk updated in Table1_Title',
description: '4 records have been bulk updated in Table1_Title',
details: null,
});
});
@ -288,7 +288,7 @@ function baseModelSqlTests() {
row_id: '1',
op_type: 'DATA',
op_sub_type: 'DELETE',
description: '1 deleted from Table1_Title',
description: 'Record with ID 1 has been deleted in Table Table1_Title',
});
});
@ -330,7 +330,7 @@ function baseModelSqlTests() {
op_type: 'DATA',
op_sub_type: 'BULK_DELETE',
status: null,
description: '4 records bulk deleted in Table1_Title',
description: '4 records have been bulk deleted in Table1_Title',
details: null,
});
});
@ -378,7 +378,7 @@ function baseModelSqlTests() {
op_type: 'DATA',
op_sub_type: 'BULK_DELETE',
status: null,
description: '4 records bulk deleted in Table1_Title',
description: '4 records have been bulk deleted in Table1_Title',
details: null,
});
});
@ -438,7 +438,7 @@ function baseModelSqlTests() {
row_id: '1',
op_type: 'DATA',
op_sub_type: 'INSERT',
description: '1 inserted into Table1_Title',
description: 'Record with ID 1 has been inserted into Table Table1_Title',
});
});
@ -506,7 +506,7 @@ function baseModelSqlTests() {
op_type: 'DATA',
op_sub_type: 'LINK_RECORD',
description:
'Record [id:1] record linked with record [id:1] record in Table1_Title',
'Record [id:1] has been linked with record [id:1] in Table1_Title',
});
});
@ -581,7 +581,7 @@ function baseModelSqlTests() {
op_type: 'DATA',
op_sub_type: 'UNLINK_RECORD',
description:
'Record [id:1] record unlinked with record [id:1] record in Table1_Title',
'Record [id:1] has been unlinked with record [id:1] in Table1_Title',
});
});
}

4
tests/playwright/tests/tableOperations.spec.ts

@ -25,13 +25,13 @@ test.describe('Table Operations', () => {
await settings.audit.verifyRow({
index: 0,
opType: 'TABLE',
opSubtype: 'DELETED',
opSubtype: 'DELETE',
user: 'user@nocodb.com',
});
await settings.audit.verifyRow({
index: 1,
opType: 'TABLE',
opSubtype: 'CREATED',
opSubtype: 'CREATE',
user: 'user@nocodb.com',
});
await settings.close();

Loading…
Cancel
Save