Browse Source

Merge pull request #5624 from nocodb/test/tz

test: time filter timezone fix
pull/5634/head
Raju Udava 2 years ago committed by GitHub
parent
commit
325b2bd7b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  2. 2
      packages/nc-gui/components/webhook/Editor.vue
  3. 9
      packages/nc-gui/composables/useViewFilters.ts
  4. 2
      packages/nocodb/src/Noco.ts
  5. 21
      packages/nocodb/src/db/BaseModelSqlv2.ts
  6. 85
      packages/nocodb/src/db/sql-client/lib/sqlite/SqliteClient.ts
  7. 5
      packages/nocodb/src/models/Model.ts
  8. 2
      packages/nocodb/src/modules/event-emitter/fallback-event-emitter.ts
  9. 4
      packages/nocodb/src/modules/event-emitter/nestjs-event-emitter.ts
  10. 2
      packages/nocodb/src/modules/metas/metas.module.ts
  11. 3
      packages/nocodb/src/services/hook-handler.service.spec.ts
  12. 6
      packages/nocodb/src/services/hook-handler.service.ts
  13. 1
      tests/playwright/pages/Dashboard/Grid/index.ts
  14. 16
      tests/playwright/pages/Dashboard/WebhookForm/index.ts
  15. 12
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  16. 47
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  17. 26
      tests/playwright/tests/db/filters.spec.ts
  18. 6
      tests/playwright/tests/db/undo-redo.spec.ts

2
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -64,6 +64,7 @@ const {
() => reloadDataHook.trigger(showLoading), () => reloadDataHook.trigger(showLoading),
modelValue || nestedFilters.value, modelValue || nestedFilters.value,
!modelValue, !modelValue,
webHook,
) )
const localNestedFilters = ref() const localNestedFilters = ref()
@ -249,6 +250,7 @@ defineExpose({
:parent-id="filter.id" :parent-id="filter.id"
nested nested
:auto-save="autoSave" :auto-save="autoSave"
:web-hook="webHook"
/> />
</div> </div>
</template> </template>

2
packages/nc-gui/components/webhook/Editor.vue

@ -710,7 +710,7 @@ onMounted(async () => {
:auto-save="false" :auto-save="false"
:show-loading="false" :show-loading="false"
:hook-id="hook.id" :hook-id="hook.id"
web-hook :web-hook="true"
/> />
</a-card> </a-card>
</a-col> </a-col>

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

@ -29,6 +29,7 @@ export function useViewFilters(
reloadData?: () => void, reloadData?: () => void,
_currentFilters?: Filter[], _currentFilters?: Filter[],
isNestedRoot?: boolean, isNestedRoot?: boolean,
isWebhook?: boolean,
) { ) {
let currentFilters = $ref(_currentFilters) let currentFilters = $ref(_currentFilters)
@ -238,7 +239,7 @@ export function useViewFilters(
} }
} }
reloadData?.() if (!isWebhook) reloadData?.()
} catch (e: any) { } catch (e: any) {
console.log(e) console.log(e)
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -308,7 +309,7 @@ export function useViewFilters(
lastFilters.value = clone(filters.value) lastFilters.value = clone(filters.value)
reloadData?.() if (!isWebhook) reloadData?.()
} }
const deleteFilter = async (filter: Filter, i: number, undo = false) => { const deleteFilter = async (filter: Filter, i: number, undo = false) => {
@ -335,7 +336,7 @@ export function useViewFilters(
if (nestedMode.value) { if (nestedMode.value) {
filters.value.splice(i, 1) filters.value.splice(i, 1)
filters.value = [...filters.value] filters.value = [...filters.value]
reloadData?.() if (!isWebhook) reloadData?.()
} else { } else {
if (filter.id) { if (filter.id) {
// if auto-apply disabled mark it as disabled // if auto-apply disabled mark it as disabled
@ -346,7 +347,7 @@ export function useViewFilters(
} else { } else {
try { try {
await $api.dbTableFilter.delete(filter.id) await $api.dbTableFilter.delete(filter.id)
reloadData?.() if (!isWebhook) reloadData?.()
filters.value.splice(i, 1) filters.value.splice(i, 1)
} catch (e: any) { } catch (e: any) {
console.log(e) console.log(e)

2
packages/nocodb/src/Noco.ts

@ -8,9 +8,9 @@ import { AppModule } from './app.module';
import { NC_LICENSE_KEY } from './constants'; import { NC_LICENSE_KEY } from './constants';
import Store from './models/Store'; import Store from './models/Store';
import type { IEventEmitter } from './modules/event-emitter/event-emitter.interface';
import type { Express } from 'express'; import type { Express } from 'express';
import type * as http from 'http'; import type * as http from 'http';
import { IEventEmitter } from './modules/event-emitter/event-emitter.interface'
export default class Noco { export default class Noco {
private static _this: Noco; private static _this: Noco;

21
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -9,7 +9,6 @@ import {
isVirtualCol, isVirtualCol,
RelationTypes, RelationTypes,
UITypes, UITypes,
ViewTypes,
} from 'nocodb-sdk'; } from 'nocodb-sdk';
import Validator from 'validator'; import Validator from 'validator';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
@ -18,23 +17,15 @@ import { v4 as uuidv4 } from 'uuid';
import { NcError } from '../helpers/catchError'; import { NcError } from '../helpers/catchError';
import getAst from '../helpers/getAst'; import getAst from '../helpers/getAst';
import { import { Audit, Column, Filter, Model, Project, Sort, View } from '../models';
Audit,
Column,
Filter,
Model,
Project,
Sort,
View,
} from '../models';
import { sanitize, unsanitize } from '../helpers/sqlSanitize'; import { sanitize, unsanitize } from '../helpers/sqlSanitize';
import { import {
COMPARISON_OPS, COMPARISON_OPS,
COMPARISON_SUB_OPS, COMPARISON_SUB_OPS,
IS_WITHIN_COMPARISON_SUB_OPS, IS_WITHIN_COMPARISON_SUB_OPS,
} from '../models/Filter'; } from '../models/Filter';
import Noco from '../Noco' import Noco from '../Noco';
import { HANDLE_WEBHOOK } from '../services/hook-handler.service' import { HANDLE_WEBHOOK } from '../services/hook-handler.service';
import formulaQueryBuilderv2 from './formulav2/formulaQueryBuilderv2'; import formulaQueryBuilderv2 from './formulav2/formulaQueryBuilderv2';
import genRollupSelectv2 from './genRollupSelectv2'; import genRollupSelectv2 from './genRollupSelectv2';
import conditionV2 from './conditionV2'; import conditionV2 from './conditionV2';
@ -2523,7 +2514,6 @@ class BaseModelSqlv2 {
} }
private async handleHooks(hookName, prevData, newData, req): Promise<void> { private async handleHooks(hookName, prevData, newData, req): Promise<void> {
Noco.eventEmitter.emit(HANDLE_WEBHOOK, { Noco.eventEmitter.emit(HANDLE_WEBHOOK, {
hookName, hookName,
prevData, prevData,
@ -2532,9 +2522,9 @@ class BaseModelSqlv2 {
viewId: this.viewId, viewId: this.viewId,
modelId: this.model.id, modelId: this.model.id,
tnPath: this.tnPath, tnPath: this.tnPath,
}) });
/*
/*
const view = await View.get(this.viewId); const view = await View.get(this.viewId);
// handle form view data submission // handle form view data submission
@ -3317,7 +3307,6 @@ function validateFilterComparison(uidt: UITypes, op: any, sub_op?: any) {
function extractCondition(nestedArrayConditions, aliasColObjMap) { function extractCondition(nestedArrayConditions, aliasColObjMap) {
return nestedArrayConditions?.map((str) => { return nestedArrayConditions?.map((str) => {
// eslint-disable-next-line prefer-const
let [logicOp, alias, op, value] = let [logicOp, alias, op, value] =
str.match(/(?:~(and|or|not))?\((.*?),(\w+),(.*)\)/)?.slice(1) || []; str.match(/(?:~(and|or|not))?\((.*?),(\w+),(.*)\)/)?.slice(1) || [];

85
packages/nocodb/src/db/sql-client/lib/sqlite/SqliteClient.ts

@ -200,7 +200,7 @@ class SqliteClient extends KnexClient {
table.integer('status').nullable(); table.integer('status').nullable();
table.dateTime('created'); table.dateTime('created');
table.timestamps(); table.timestamps();
} },
); );
log.debug('Table created:', `${args.tn}`, data); log.debug('Table created:', `${args.tn}`, data);
} else { } else {
@ -295,7 +295,7 @@ class SqliteClient extends KnexClient {
try { try {
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SELECT name as tn FROM sqlite_master where type = 'table'` `SELECT name as tn FROM sqlite_master where type = 'table'`,
); );
result.data.list = []; result.data.list = [];
@ -359,7 +359,7 @@ class SqliteClient extends KnexClient {
try { try {
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`PRAGMA table_info("${args.tn}")` `PRAGMA table_info("${args.tn}")`,
); );
const triggerList = (await this.triggerList(args)).data.list; const triggerList = (await this.triggerList(args)).data.list;
@ -420,7 +420,8 @@ class SqliteClient extends KnexClient {
response[i].dtxs = ''; response[i].dtxs = '';
response[i].au = !!triggerList.find( response[i].au = !!triggerList.find(
({ trigger }) => trigger === `xc_trigger_${args.tn}_${response[i].cn}` ({ trigger }) =>
trigger === `xc_trigger_${args.tn}_${response[i].cn}`,
); );
} }
@ -466,7 +467,7 @@ class SqliteClient extends KnexClient {
// PRAGMA index_xinfo('idx_fk_original_language_id'); // PRAGMA index_xinfo('idx_fk_original_language_id');
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`PRAGMA index_list("${args.tn}")` `PRAGMA index_list("${args.tn}")`,
); );
const rows = []; const rows = [];
@ -478,7 +479,7 @@ class SqliteClient extends KnexClient {
response[i].unique = response[i].unique === 1 ? 1 : 0; response[i].unique = response[i].unique === 1 ? 1 : 0;
const colsInIndex = await this.sqlClient.raw( const colsInIndex = await this.sqlClient.raw(
`PRAGMA index_info('${response[i].key_name}')` `PRAGMA index_info('${response[i].key_name}')`,
); );
if (colsInIndex.length === 1) { if (colsInIndex.length === 1) {
@ -531,7 +532,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`PRAGMA foreign_key_list('${args.tn}')` `PRAGMA foreign_key_list('${args.tn}')`,
); );
for (let i = 0; i < response.length; ++i) { for (let i = 0; i < response.length; ++i) {
@ -582,7 +583,7 @@ class SqliteClient extends KnexClient {
for (let i = 0; i < tables.length; ++i) { for (let i = 0; i < tables.length; ++i) {
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`PRAGMA foreign_key_list('${tables[i].tn}')` `PRAGMA foreign_key_list('${tables[i].tn}')`,
); );
for (let j = 0; j < response.length; ++j) { for (let j = 0; j < response.length; ++j) {
@ -633,7 +634,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`select *, name as trigger_name from sqlite_master where type = 'trigger' and tbl_name='${args.tn}';` `select *, name as trigger_name from sqlite_master where type = 'trigger' and tbl_name='${args.tn}';`,
); );
for (let i = 0; i < response.length; ++i) { for (let i = 0; i < response.length; ++i) {
@ -676,7 +677,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`show function status where db='${args.databaseName}'` `show function status where db='${args.databaseName}'`,
); );
if (response.length === 2) { if (response.length === 2) {
@ -730,7 +731,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`show procedure status where db='${args.databaseName}'` `show procedure status where db='${args.databaseName}'`,
); );
if (response.length === 2) { if (response.length === 2) {
@ -775,7 +776,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SELECT * FROM sqlite_master WHERE type = 'view'` `SELECT * FROM sqlite_master WHERE type = 'view'`,
); );
for (let i = 0; i < response.length; ++i) { for (let i = 0; i < response.length; ++i) {
@ -813,7 +814,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SHOW CREATE FUNCTION ${args.function_name};` `SHOW CREATE FUNCTION ${args.function_name};`,
); );
if (response.length === 2) { if (response.length === 2) {
@ -865,7 +866,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`show create procedure ${args.procedure_name};` `show create procedure ${args.procedure_name};`,
); );
if (response.length === 2) { if (response.length === 2) {
@ -911,7 +912,7 @@ class SqliteClient extends KnexClient {
try { try {
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SELECT * FROM sqlite_master WHERE type = 'view' AND name = '${args.view_name}'` `SELECT * FROM sqlite_master WHERE type = 'view' AND name = '${args.view_name}'`,
); );
for (let i = 0; i < response.length; ++i) { for (let i = 0; i < response.length; ++i) {
@ -938,7 +939,7 @@ class SqliteClient extends KnexClient {
args.databaseName = this.connectionConfig.connection.database; args.databaseName = this.connectionConfig.connection.database;
const response = await this.sqlClient.raw( const response = await this.sqlClient.raw(
`SHOW FULL TABLES IN ${args.databaseName} WHERE TABLE_TYPE LIKE 'VIEW';` `SHOW FULL TABLES IN ${args.databaseName} WHERE TABLE_TYPE LIKE 'VIEW';`,
); );
if (response.length === 2) { if (response.length === 2) {
@ -970,7 +971,7 @@ class SqliteClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`create database ${args.database_name}` `create database ${args.database_name}`,
); );
return rows; return rows;
} }
@ -981,7 +982,7 @@ class SqliteClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`drop database ${args.database_name}` `drop database ${args.database_name}`,
); );
return rows; return rows;
} }
@ -1011,7 +1012,7 @@ class SqliteClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`DROP FUNCTION IF EXISTS ${args.function_name}` `DROP FUNCTION IF EXISTS ${args.function_name}`,
); );
return rows; return rows;
} }
@ -1022,7 +1023,7 @@ class SqliteClient extends KnexClient {
log.api(`${_func}:args:`, args); log.api(`${_func}:args:`, args);
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`DROP PROCEDURE IF EXISTS ${args.procedure_name}` `DROP PROCEDURE IF EXISTS ${args.procedure_name}`,
); );
return rows; return rows;
} }
@ -1042,7 +1043,7 @@ class SqliteClient extends KnexClient {
this._version = result.data.object; this._version = result.data.object;
log.debug( log.debug(
`Version was empty for ${args.func}: population version for database as`, `Version was empty for ${args.func}: population version for database as`,
this._version this._version,
); );
} }
@ -1073,7 +1074,7 @@ class SqliteClient extends KnexClient {
log.api(`${func}:args:`, args); log.api(`${func}:args:`, args);
try { try {
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` `CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`,
); );
result.data.list = rows; result.data.list = rows;
} catch (e) { } catch (e) {
@ -1101,7 +1102,7 @@ class SqliteClient extends KnexClient {
try { try {
await this.sqlClient.raw(`DROP TRIGGER ${args.function_name}`); await this.sqlClient.raw(`DROP TRIGGER ${args.function_name}`);
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` `CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`,
); );
result.data.list = rows; result.data.list = rows;
} catch (e) { } catch (e) {
@ -1128,7 +1129,7 @@ class SqliteClient extends KnexClient {
log.api(`${func}:args:`, args); log.api(`${func}:args:`, args);
try { try {
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`,
); );
result.data.list = rows; result.data.list = rows;
} catch (e) { } catch (e) {
@ -1156,7 +1157,7 @@ class SqliteClient extends KnexClient {
try { try {
await this.sqlClient.raw(`DROP TRIGGER ${args.procedure_name}`); await this.sqlClient.raw(`DROP TRIGGER ${args.procedure_name}`);
const rows = await this.sqlClient.raw( const rows = await this.sqlClient.raw(
`CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`,
); );
result.data.list = rows; result.data.list = rows;
} catch (e) { } catch (e) {
@ -1216,7 +1217,7 @@ class SqliteClient extends KnexClient {
try { try {
await this.sqlClient.raw(`DROP TRIGGER ${args.trigger_name}`); await this.sqlClient.raw(`DROP TRIGGER ${args.trigger_name}`);
await this.sqlClient.raw( await this.sqlClient.raw(
`CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`,
); );
const upQuery = `DROP TRIGGER ${args.trigger_name};\nCREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`; const upQuery = `DROP TRIGGER ${args.trigger_name};\nCREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`;
@ -1507,13 +1508,13 @@ class SqliteClient extends KnexClient {
args.table, args.table,
args.columns[i], args.columns[i],
oldColumn, oldColumn,
upQuery upQuery,
); );
downQuery += this.alterTableAddColumn( downQuery += this.alterTableAddColumn(
args.table, args.table,
oldColumn, oldColumn,
args.columns[i], args.columns[i],
downQuery downQuery,
); );
} else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) { } else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) {
// col edit // col edit
@ -1521,7 +1522,7 @@ class SqliteClient extends KnexClient {
args.table, args.table,
args.columns[i], args.columns[i],
oldColumn, oldColumn,
upQuery upQuery,
); );
downQuery += ';'; downQuery += ';';
// downQuery += this.alterTableChangeColumn( // downQuery += this.alterTableChangeColumn(
@ -1537,7 +1538,7 @@ class SqliteClient extends KnexClient {
args.table, args.table,
args.columns[i], args.columns[i],
oldColumn, oldColumn,
upQuery upQuery,
); );
downQuery += ';'; downQuery += ';';
// downQuery += alterTableRemoveColumn( // downQuery += alterTableRemoveColumn(
@ -1553,7 +1554,7 @@ class SqliteClient extends KnexClient {
const pkQuery = this.alterTablePK( const pkQuery = this.alterTablePK(
args.columns, args.columns,
args.originalColumns, args.originalColumns,
upQuery upQuery,
); );
await this.sqlClient.raw('PRAGMA foreign_keys = OFF;'); await this.sqlClient.raw('PRAGMA foreign_keys = OFF;');
@ -1572,7 +1573,7 @@ class SqliteClient extends KnexClient {
if (pkQuery) { if (pkQuery) {
await trx.schema.alterTable(args.table, (table) => { await trx.schema.alterTable(args.table, (table) => {
for (const pk of pkQuery.oldPks.filter( for (const pk of pkQuery.oldPks.filter(
(el) => !pkQuery.newPks.includes(el) (el) => !pkQuery.newPks.includes(el),
)) { )) {
table.dropPrimary(pk); table.dropPrimary(pk);
} }
@ -1858,7 +1859,7 @@ class SqliteClient extends KnexClient {
/* Filter relations for current table */ /* Filter relations for current table */
if (args.tn) { if (args.tn) {
relations = relations.filter( relations = relations.filter(
(r) => r.tn === args.tn || r.rtn === args.tn (r) => r.tn === args.tn || r.rtn === args.tn,
); );
} }
@ -1867,7 +1868,7 @@ class SqliteClient extends KnexClient {
let columns: any = await this.columnList({ tn: tables[i].tn }); let columns: any = await this.columnList({ tn: tables[i].tn });
columns = columns.data.list; columns = columns.data.list;
console.log( console.log(
`Sequelize model created: ${tables[i].tn}(${columns.length})\n` `Sequelize model created: ${tables[i].tn}(${columns.length})\n`,
); );
// let SqliteSequelizeRender = require('./SqliteSequelizeRender'); // let SqliteSequelizeRender = require('./SqliteSequelizeRender');
@ -1967,7 +1968,7 @@ class SqliteClient extends KnexClient {
query += this.genQuery( query += this.genQuery(
`ALTER TABLE ?? DROP COLUMN ??`, `ALTER TABLE ?? DROP COLUMN ??`,
[t, n.cn], [t, n.cn],
shouldSanitize shouldSanitize,
); );
return query; return query;
} }
@ -2007,14 +2008,14 @@ class SqliteClient extends KnexClient {
const backupOldColumnQuery = this.genQuery( const backupOldColumnQuery = this.genQuery(
`ALTER TABLE ?? RENAME COLUMN ?? TO ??;`, `ALTER TABLE ?? RENAME COLUMN ?? TO ??;`,
[t, o.cn, `${o.cno}_nc_${suffix}`], [t, o.cn, `${o.cno}_nc_${suffix}`],
shouldSanitize shouldSanitize,
); );
let addNewColumnQuery = ''; let addNewColumnQuery = '';
addNewColumnQuery += this.genQuery( addNewColumnQuery += this.genQuery(
` ADD ?? ${this.sanitiseDataType(n.dt)}`, ` ADD ?? ${this.sanitiseDataType(n.dt)}`,
[n.cn], [n.cn],
shouldSanitize shouldSanitize,
); );
addNewColumnQuery += n.dtxp && n.dt !== 'text' ? `(${n.dtxp})` : ''; addNewColumnQuery += n.dtxp && n.dt !== 'text' ? `(${n.dtxp})` : '';
addNewColumnQuery += n.cdf addNewColumnQuery += n.cdf
@ -2026,19 +2027,19 @@ class SqliteClient extends KnexClient {
addNewColumnQuery = this.genQuery( addNewColumnQuery = this.genQuery(
`ALTER TABLE ?? ${addNewColumnQuery};`, `ALTER TABLE ?? ${addNewColumnQuery};`,
[t], [t],
shouldSanitize shouldSanitize,
); );
const updateNewColumnQuery = this.genQuery( const updateNewColumnQuery = this.genQuery(
`UPDATE ?? SET ?? = ??;`, `UPDATE ?? SET ?? = ??;`,
[t, n.cn, `${o.cno}_nc_${suffix}`], [t, n.cn, `${o.cno}_nc_${suffix}`],
shouldSanitize shouldSanitize,
); );
const dropOldColumnQuery = this.genQuery( const dropOldColumnQuery = this.genQuery(
`ALTER TABLE ?? DROP COLUMN ??;`, `ALTER TABLE ?? DROP COLUMN ??;`,
[t, `${o.cno}_nc_${suffix}`], [t, `${o.cno}_nc_${suffix}`],
shouldSanitize shouldSanitize,
); );
query = `${backupOldColumnQuery}${addNewColumnQuery}${updateNewColumnQuery}${dropOldColumnQuery}`; query = `${backupOldColumnQuery}${addNewColumnQuery}${updateNewColumnQuery}${dropOldColumnQuery}`;
@ -2105,12 +2106,12 @@ class SqliteClient extends KnexClient {
try { try {
const tables = await this.sqlClient.raw( const tables = await this.sqlClient.raw(
`SELECT name FROM sqlite_master WHERE type='table';` `SELECT name FROM sqlite_master WHERE type='table';`,
); );
let count = 0; let count = 0;
for (const tb of tables) { for (const tb of tables) {
const tmp = await this.sqlClient.raw( const tmp = await this.sqlClient.raw(
`SELECT COUNT(*) as ct FROM '${tb.name}';` `SELECT COUNT(*) as ct FROM '${tb.name}';`,
); );
if (tmp && tmp.length) { if (tmp && tmp.length) {
count += tmp[0].ct; count += tmp[0].ct;

5
packages/nocodb/src/models/Model.ts

@ -339,6 +339,11 @@ export default class Model implements TableType {
): Promise<BaseModelSqlv2> { ): Promise<BaseModelSqlv2> {
const model = args?.model || (await this.get(args.id, ncMeta)); const model = args?.model || (await this.get(args.id, ncMeta));
if (!args?.viewId) {
const view = await View.getDefaultView(model.id, ncMeta);
args.viewId = view.id;
}
return new BaseModelSqlv2({ return new BaseModelSqlv2({
dbDriver: args.dbDriver, dbDriver: args.dbDriver,
viewId: args.viewId, viewId: args.viewId,

2
packages/nocodb/src/modules/event-emitter/fallback-event-emitter.ts

@ -1,5 +1,5 @@
import Emittery from 'emittery'; import Emittery from 'emittery';
import { IEventEmitter } from './event-emitter.interface'; import type { IEventEmitter } from './event-emitter.interface';
export class FallbackEventEmitter implements IEventEmitter { export class FallbackEventEmitter implements IEventEmitter {
private readonly emitter: Emittery; private readonly emitter: Emittery;

4
packages/nocodb/src/modules/event-emitter/nestjs-event-emitter.ts

@ -1,5 +1,5 @@
import { EventEmitter2 } from '@nestjs/event-emitter'; import type { EventEmitter2 } from '@nestjs/event-emitter';
import { IEventEmitter } from './event-emitter.interface'; import type { IEventEmitter } from './event-emitter.interface';
export class NestjsEventEmitter implements IEventEmitter { export class NestjsEventEmitter implements IEventEmitter {
constructor(private readonly eventEmitter: EventEmitter2) {} constructor(private readonly eventEmitter: EventEmitter2) {}

2
packages/nocodb/src/modules/metas/metas.module.ts

@ -67,7 +67,7 @@ import { UtilsService } from '../../services/utils.service';
import { ViewColumnsService } from '../../services/view-columns.service'; import { ViewColumnsService } from '../../services/view-columns.service';
import { ViewsService } from '../../services/views.service'; import { ViewsService } from '../../services/views.service';
import { ApiDocsService } from '../../services/api-docs/api-docs.service'; import { ApiDocsService } from '../../services/api-docs/api-docs.service';
import { EventEmitterModule } from '../event-emitter/event-emitter.module' import { EventEmitterModule } from '../event-emitter/event-emitter.module';
import { GlobalModule } from '../global/global.module'; import { GlobalModule } from '../global/global.module';
import { ProjectUsersController } from '../../controllers/project-users.controller'; import { ProjectUsersController } from '../../controllers/project-users.controller';
import { ProjectUsersService } from '../../services/project-users/project-users.service'; import { ProjectUsersService } from '../../services/project-users/project-users.service';

3
packages/nocodb/src/services/hook-handler.service.spec.ts

@ -1,5 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { HookHandlerService } from './hook-handler.service'; import { HookHandlerService } from './hook-handler.service';
import type { TestingModule } from '@nestjs/testing';
describe('HookHandlerService', () => { describe('HookHandlerService', () => {
let service: HookHandlerService; let service: HookHandlerService;

6
packages/nocodb/src/services/hook-handler.service.ts

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable } from '@nestjs/common';
import { UITypes, ViewTypes } from 'nocodb-sdk'; import { UITypes, ViewTypes } from 'nocodb-sdk';
import ejs from 'ejs'; import ejs from 'ejs';
import NcPluginMgrv2 from '../helpers/NcPluginMgrv2'; import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
@ -18,7 +18,9 @@ export const HANDLE_WEBHOOK = '__nc_handleHooks';
export class HookHandlerService implements OnModuleInit, OnModuleDestroy { export class HookHandlerService implements OnModuleInit, OnModuleDestroy {
private unsubscribe: () => void; private unsubscribe: () => void;
constructor(@Inject('IEventEmitter') private readonly eventEmitter: IEventEmitter) {} constructor(
@Inject('IEventEmitter') private readonly eventEmitter: IEventEmitter,
) {}
private async handleHooks({ private async handleHooks({
hookName, hookName,

1
tests/playwright/pages/Dashboard/Grid/index.ts

@ -52,6 +52,7 @@ export class GridPage extends BasePage {
private async _fillRow({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) { private async _fillRow({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) {
const cell = this.cell.get({ index, columnHeader }); const cell = this.cell.get({ index, columnHeader });
await cell.waitFor({ state: 'visible' });
await this.cell.dblclick({ await this.cell.dblclick({
index, index,
columnHeader, columnHeader,

16
tests/playwright/pages/Dashboard/WebhookForm/index.ts

@ -83,7 +83,7 @@ export class WebhookFormPage extends BasePage {
} }
if (save) { if (save) {
await this.save(true); await this.save();
await this.close(); await this.close();
} }
} }
@ -91,27 +91,20 @@ export class WebhookFormPage extends BasePage {
async deleteCondition(p: { save: boolean }) { async deleteCondition(p: { save: boolean }) {
await this.get().locator(`.nc-filter-item-remove-btn`).click(); await this.get().locator(`.nc-filter-item-remove-btn`).click();
if (p.save) { if (p.save) {
await this.save(true); await this.save();
await this.close(); await this.close();
} }
} }
async save(condition = false) { async save() {
const saveAction = () => this.saveButton.click(); const saveAction = () => this.saveButton.click();
await this.waitForResponse({ await this.waitForResponse({
uiAction: saveAction, uiAction: saveAction,
requestUrlPathToMatch: '/hooks', requestUrlPathToMatch: '/hooks',
httpMethodsToMatch: ['POST', 'PATCH'], httpMethodsToMatch: ['POST', 'PATCH'],
}); });
if (condition) {
await this.waitForResponse({
uiAction: saveAction,
requestUrlPathToMatch: '/filters',
httpMethodsToMatch: ['POST', 'PATCH', 'DELETE'],
});
}
await this.verifyToast({ message: 'Webhook details updated successfully' }); await this.verifyToast({ message: 'Webhook details updated successfully' });
} }
@ -142,6 +135,7 @@ export class WebhookFormPage extends BasePage {
await this.toolbar.clickActions(); await this.toolbar.clickActions();
await this.toolbar.actions.click('Webhooks'); await this.toolbar.actions.click('Webhooks');
await this.dashboard.get().locator(`.nc-hook`).nth(index).click(); await this.dashboard.get().locator(`.nc-hook`).nth(index).click();
await this.get().locator('.nc-check-box-enable-webhook').waitFor({ state: 'visible' });
} }
async openForm({ index }: { index: number }) { async openForm({ index }: { index: number }) {

12
tests/playwright/pages/Dashboard/common/Cell/index.ts

@ -291,7 +291,11 @@ export class CellPageObject extends BasePage {
// arrow expand doesn't exist for bt columns // arrow expand doesn't exist for bt columns
if (await arrow_expand.count()) { if (await arrow_expand.count()) {
await arrow_expand.click(); await this.waitForResponse({
uiAction: () => arrow_expand.click(),
requestUrlPathToMatch: '/api/v1/db',
httpMethodsToMatch: ['GET'],
});
// wait for child list to open // wait for child list to open
await this.rootPage.waitForSelector('.nc-modal-child-list:visible'); await this.rootPage.waitForSelector('.nc-modal-child-list:visible');
@ -309,7 +313,11 @@ export class CellPageObject extends BasePage {
async unlinkVirtualCell({ index, columnHeader }: CellProps) { async unlinkVirtualCell({ index, columnHeader }: CellProps) {
const cell = this.get({ index, columnHeader }); const cell = this.get({ index, columnHeader });
await cell.click(); await cell.click();
await cell.locator('.unlink-icon').first().click(); await this.waitForResponse({
uiAction: () => cell.locator('.unlink-icon').first().click(),
requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
} }
async verifyRoleAccess(param: { role: string }) { async verifyRoleAccess(param: { role: string }) {

47
tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -45,6 +45,7 @@ export class ToolbarFilterPage extends BasePage {
locallySaved = false, locallySaved = false,
dataType, dataType,
openModal = false, openModal = false,
skipWaitingResponse = false, // used for undo (single request, less stable)
}: { }: {
title: string; title: string;
operation: string; operation: string;
@ -53,15 +54,25 @@ export class ToolbarFilterPage extends BasePage {
locallySaved?: boolean; locallySaved?: boolean;
dataType?: string; dataType?: string;
openModal?: boolean; openModal?: boolean;
skipWaitingResponse?: boolean;
}) { }) {
if (!openModal) await this.get().locator(`button:has-text("Add Filter")`).first().click(); if (!openModal) await this.get().locator(`button:has-text("Add Filter")`).first().click();
const selectedField = await getTextExcludeIconText(await this.rootPage.locator('.nc-filter-field-select .ant-select-selection-item')); const selectedField = await getTextExcludeIconText(
await this.rootPage.locator('.nc-filter-field-select .ant-select-selection-item')
);
if (selectedField !== title) { if (selectedField !== title) {
await this.rootPage.locator('.nc-filter-field-select').last().click(); await this.rootPage.locator('.nc-filter-field-select').last().click();
if (skipWaitingResponse) {
this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${title}"]:visible`)
.click();
} else {
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.rootPage uiAction: () =>
this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list') .locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${title}"]:visible`) .locator(`div[label="${title}"]:visible`)
.click(), .click(),
@ -69,14 +80,23 @@ export class ToolbarFilterPage extends BasePage {
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`, requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
}); });
} }
}
const selectedOpType = await getTextExcludeIconText(await this.rootPage.locator('.nc-filter-operation-select')); const selectedOpType = await getTextExcludeIconText(await this.rootPage.locator('.nc-filter-operation-select'));
if (selectedOpType !== operation) { if (selectedOpType !== operation) {
await this.rootPage.locator('.nc-filter-operation-select').click(); await this.rootPage.locator('.nc-filter-operation-select').click();
// first() : filter list has >, >= // first() : filter list has >, >=
if (skipWaitingResponse) {
this.rootPage
.locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item:has-text("${operation}")`)
.first()
.click();
} else {
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.rootPage uiAction: () =>
this.rootPage
.locator('.nc-dropdown-filter-comp-op') .locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item:has-text("${operation}")`) .locator(`.ant-select-item:has-text("${operation}")`)
.first() .first()
@ -85,6 +105,7 @@ export class ToolbarFilterPage extends BasePage {
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`, requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
}); });
} }
}
// subtype for date // subtype for date
if (dataType === UITypes.Date && subOperation) { if (dataType === UITypes.Date && subOperation) {
@ -95,8 +116,16 @@ export class ToolbarFilterPage extends BasePage {
await this.rootPage.locator('.nc-filter-sub_operation-select').click(); await this.rootPage.locator('.nc-filter-sub_operation-select').click();
// first() : filter list has >, >= // first() : filter list has >, >=
if (skipWaitingResponse) {
this.rootPage
.locator('.nc-dropdown-filter-comp-sub-op')
.locator(`.ant-select-item:has-text("${subOperation}")`)
.first()
.click();
} else {
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.rootPage uiAction: () =>
this.rootPage
.locator('.nc-dropdown-filter-comp-sub-op') .locator('.nc-dropdown-filter-comp-sub-op')
.locator(`.ant-select-item:has-text("${subOperation}")`) .locator(`.ant-select-item:has-text("${subOperation}")`)
.first() .first()
@ -106,6 +135,7 @@ export class ToolbarFilterPage extends BasePage {
}); });
} }
} }
}
// if value field was provided, fill it // if value field was provided, fill it
if (value) { if (value) {
@ -135,11 +165,16 @@ export class ToolbarFilterPage extends BasePage {
if (subOperation === 'exact date') { if (subOperation === 'exact date') {
await this.get().locator('.nc-filter-value-select').click(); await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`); await this.rootPage.locator(`.ant-picker-dropdown:visible`);
if (skipWaitingResponse) {
this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
} else {
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(), uiAction: () => this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(),
httpMethodsToMatch: ['GET'], httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`, requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
}); });
}
} else { } else {
fillFilter = () => this.rootPage.locator('.nc-filter-value-select > input').last().fill(value); fillFilter = () => this.rootPage.locator('.nc-filter-value-select > input').last().fill(value);
await this.waitForResponse({ await this.waitForResponse({
@ -152,11 +187,15 @@ export class ToolbarFilterPage extends BasePage {
} }
break; break;
case UITypes.Duration: case UITypes.Duration:
if (skipWaitingResponse) {
this.get().locator('.nc-filter-value-select').locator('input').fill(value);
} else {
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.get().locator('.nc-filter-value-select').locator('input').fill(value), uiAction: () => this.get().locator('.nc-filter-value-select').locator('input').fill(value),
httpMethodsToMatch: ['GET'], httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`, requestUrlPathToMatch: locallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
}); });
}
break; break;
case UITypes.Rating: case UITypes.Rating:
await this.get() await this.get()

26
tests/playwright/tests/db/filters.spec.ts

@ -336,7 +336,31 @@ test.describe('Filter Tests: Numerical', () => {
}); });
test('Filter: Time', async () => { test('Filter: Time', async () => {
await numBasedFilterTest('Time', '02:02:00', '04:04:00'); const getTime = date => {
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
// let ap = hours >= 12 ? 'pm' : 'am';
hours = hours % 12;
hours = hours ? hours : 12;
hours = hours.toString().padStart(2, '0');
minutes = minutes.toString().padStart(2, '0');
seconds = seconds.toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
};
// compute timezone offset
const offset = new Date().getTimezoneOffset();
const timezoneOffset =
(offset <= 0 ? '+' : '-') +
String(Math.abs(Math.round(offset / 60))).padStart(2, '0') +
':' +
String(Math.abs(offset % 60)).padStart(2, '0');
const date1 = new Date(`1999-01-01 02:02:00${timezoneOffset}`);
const date2 = new Date(`1999-01-01 04:04:00${timezoneOffset}`);
await numBasedFilterTest('Time', getTime(date1), getTime(date2));
}); });
}); });

6
tests/playwright/tests/db/undo-redo.spec.ts

@ -273,15 +273,19 @@ test.describe('Undo Redo', () => {
} }
await toolbar.clickFilter(); await toolbar.clickFilter();
await toolbar.filter.add({ title: 'Number', operation: '=', value: '33' }); await toolbar.filter.add({ title: 'Number', operation: '=', value: '33', skipWaitingResponse: true });
await toolbar.clickFilter(); await toolbar.clickFilter();
await verifyRecords({ filtered: true }); await verifyRecords({ filtered: true });
await toolbar.filter.reset(); await toolbar.filter.reset();
await verifyRecords({ filtered: false }); await verifyRecords({ filtered: false });
// undo: remove filter
await undo({ page }); await undo({ page });
await verifyRecords({ filtered: true }); await verifyRecords({ filtered: true });
// undo: update filter
await undo({ page });
// undo: add filter
await undo({ page }); await undo({ page });
await verifyRecords({ filtered: false }); await verifyRecords({ filtered: false });
}); });

Loading…
Cancel
Save