From 06fc89a1015816fd47c201cc7eb78118fc4395a2 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 19 Apr 2023 18:53:56 +0800 Subject: [PATCH 001/233] feat(nocodb): utc timezone handling for mysql --- packages/nocodb/src/lib/meta/NcMetaIOImpl.ts | 33 +++++++++++++---- .../nocodb/src/lib/utils/NcConfigFactory.ts | 35 +++++++++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts b/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts index b04157a088..50e58686de 100644 --- a/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts +++ b/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts @@ -1,5 +1,6 @@ import CryptoJS from 'crypto-js'; import { customAlphabet } from 'nanoid'; +import dayjs from 'dayjs'; import { XKnex } from '../db/sql-data-mapper'; import XcMigrationSource from '../migrations/XcMigrationSource'; import NcConnectionMgr from '../utils/common/NcConnectionMgr'; @@ -94,6 +95,20 @@ export default class NcMetaIOImpl extends NcMetaIO { return (this.trx || this.connection) as any; } + private isMySQL(): boolean { + return ( + this.config?.meta?.db?.client === 'mysql' || + this.config?.meta?.db?.client === 'mysql2' + ); + } + + private now(): any { + if (this.isMySQL()) { + return dayjs().utc().format('YYYY-MM-DD HH:mm:ss'); + } + return dayjs().utc().toISOString(); + } + public updateKnex(connectionConfig): void { this.connection = XKnex(connectionConfig); } @@ -107,6 +122,10 @@ export default class NcMetaIOImpl extends NcMetaIO { migrationSource: new XcMigrationSourcev2(), tableName: 'xc_knex_migrationsv2', }); + if (this.isMySQL()) { + // set timezone + await this.connection.raw(`SET time_zone = '+00:00'`); + } return true; } @@ -238,8 +257,8 @@ export default class NcMetaIOImpl extends NcMetaIO { return this.knexConnection(target).insert({ db_alias: dbAlias, project_id, - created_at: this.knexConnection?.fn?.now(), - updated_at: this.knexConnection?.fn?.now(), + created_at: this.now(), + updated_at: this.now(), ...data, }); } @@ -260,8 +279,8 @@ export default class NcMetaIOImpl extends NcMetaIO { if (project_id !== null) insertObj.project_id = project_id; await this.knexConnection(target).insert({ ...insertObj, - created_at: insertObj?.created_at || this.knexConnection?.fn?.now(), - updated_at: insertObj?.updated_at || this.knexConnection?.fn?.now(), + created_at: insertObj?.created_at || this.now(), + updated_at: insertObj?.updated_at || this.now(), }); return insertObj; } @@ -410,7 +429,7 @@ export default class NcMetaIOImpl extends NcMetaIO { delete data.created_at; - query.update({ ...data, updated_at: this.knexConnection?.fn?.now() }); + query.update({ ...data, updated_at: this.now() }); if (typeof idOrCondition !== 'object') { query.where('id', idOrCondition); } else if (idOrCondition) { @@ -530,8 +549,8 @@ export default class NcMetaIOImpl extends NcMetaIO { // todo: check project name used or not await this.knexConnection('nc_projects').insert({ ...project, - created_at: this.knexConnection?.fn?.now(), - updated_at: this.knexConnection?.fn?.now(), + created_at: this.now(), + updated_at: this.now(), }); // todo diff --git a/packages/nocodb/src/lib/utils/NcConfigFactory.ts b/packages/nocodb/src/lib/utils/NcConfigFactory.ts index 85c1fe1d7c..f521585e92 100644 --- a/packages/nocodb/src/lib/utils/NcConfigFactory.ts +++ b/packages/nocodb/src/lib/utils/NcConfigFactory.ts @@ -232,6 +232,13 @@ export default class NcConfigFactory implements NcConfig { acquireConnectionTimeout: 600000, } as any; + if (url.protocol.startsWith('mysql')) { + dbConfig.connection = { + ...dbConfig.connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } + if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -344,6 +351,14 @@ export default class NcConfigFactory implements NcConfig { } : {}), }; + + if (url.protocol.startsWith('mysql')) { + dbConfig.connection = { + ...dbConfig.connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } + if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -506,6 +521,13 @@ export default class NcConfigFactory implements NcConfig { }, }; } + + if (dbConfig.client.startsWith('mysql')) { + dbConfig.connection = { + ...dbConfig.connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } // todo: const key = ''; @@ -739,6 +761,19 @@ export default class NcConfigFactory implements NcConfig { return res; } + private static mysqlConnectionTypeCastConfig = { + typeCast: function (field, next) { + if ( + field.type === 'DATETIME' && + (field.name === 'created_at' || field.name === 'updated_at') + ) { + return new Date(field.string() + ' UTC'); + } + return next(); + }, + timezone: '+00:00', + }; + // public static initOneClickDeployment() { // if (process.env.NC_ONE_CLICK) { // const url = NcConfigFactory.extractXcUrlFromJdbc(process.env.DATABASE_URL); From b32e8014ab83360abfe97876ef1755a999bc0e35 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 19 Apr 2023 18:54:20 +0800 Subject: [PATCH 002/233] feat(nc-gui): extend dayjs timezone --- packages/nc-gui/plugins/a.dayjs.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nc-gui/plugins/a.dayjs.ts b/packages/nc-gui/plugins/a.dayjs.ts index 76a1c35bc9..eecbf09832 100644 --- a/packages/nc-gui/plugins/a.dayjs.ts +++ b/packages/nc-gui/plugins/a.dayjs.ts @@ -4,6 +4,7 @@ import customParseFormat from 'dayjs/plugin/customParseFormat.js' import duration from 'dayjs/plugin/duration.js' import utc from 'dayjs/plugin/utc.js' import weekday from 'dayjs/plugin/weekday.js' +import timezone from 'dayjs/plugin/timezone.js' import { defineNuxtPlugin } from '#imports' export default defineNuxtPlugin(() => { @@ -12,4 +13,5 @@ export default defineNuxtPlugin(() => { extend(customParseFormat) extend(duration) extend(weekday) + extend(timezone) }) From 3a46a335730b7260c0350587c3187527fab37d2b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 19 Apr 2023 20:07:52 +0800 Subject: [PATCH 003/233] fix(nocodb): use system time as always --- packages/nocodb/src/lib/meta/NcMetaIOImpl.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts b/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts index 50e58686de..cf8d246b66 100644 --- a/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts +++ b/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts @@ -257,9 +257,9 @@ export default class NcMetaIOImpl extends NcMetaIO { return this.knexConnection(target).insert({ db_alias: dbAlias, project_id, + ...data, created_at: this.now(), updated_at: this.now(), - ...data, }); } @@ -279,8 +279,8 @@ export default class NcMetaIOImpl extends NcMetaIO { if (project_id !== null) insertObj.project_id = project_id; await this.knexConnection(target).insert({ ...insertObj, - created_at: insertObj?.created_at || this.now(), - updated_at: insertObj?.updated_at || this.now(), + created_at: this.now(), + updated_at: this.now(), }); return insertObj; } From 90fe83c9e5214ef802af4d2ae765b6010284694b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 20 Apr 2023 12:11:04 +0800 Subject: [PATCH 004/233] fix(nc-gui): timeAgo logic --- packages/nc-gui/utils/dateTimeUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/utils/dateTimeUtils.ts b/packages/nc-gui/utils/dateTimeUtils.ts index 803936ebdb..d9a8e986e2 100644 --- a/packages/nc-gui/utils/dateTimeUtils.ts +++ b/packages/nc-gui/utils/dateTimeUtils.ts @@ -1,7 +1,7 @@ import dayjs from 'dayjs' export const timeAgo = (date: any) => { - return dayjs.utc(date).fromNow() + return dayjs(date).fromNow() } export const dateFormats = [ From 95e57083d29beb988e9c7b7c280e612b5c7ecfe8 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 20 Apr 2023 13:31:59 +0800 Subject: [PATCH 005/233] chore(nocodb): sync backend --- packages/nocodb-nest/src/meta/meta.service.ts | 34 +++++++++++++----- .../nocodb-nest/src/utils/NcConfigFactory.ts | 35 +++++++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index e68cda8274..6bc447afe7 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -6,7 +6,7 @@ import { OnModuleInit, Optional, } from '@nestjs/common'; - +import dayjs from 'dayjs'; import { customAlphabet } from 'nanoid'; import CryptoJS from 'crypto-js'; import { Connection } from '../connection/connection'; @@ -256,8 +256,8 @@ export class MetaService { await this.knexConnection(target).insert({ ...insertObj, - created_at: data?.created_at || this.knexConnection?.fn?.now(), - updated_at: data?.updated_at || this.knexConnection?.fn?.now(), + created_at: this.now(), + updated_at: this.now(), }); return insertObj; } @@ -539,9 +539,9 @@ export class MetaService { return this.knexConnection(target).insert({ db_alias: dbAlias, project_id, - created_at: this.knexConnection?.fn?.now(), - updated_at: this.knexConnection?.fn?.now(), ...data, + created_at: this.now(), + updated_at: this.now(), }); } @@ -689,7 +689,7 @@ export class MetaService { delete data.created_at; - query.update({ ...data, updated_at: this.knexConnection?.fn?.now() }); + query.update({ ...data, updated_at: this.now() }); if (typeof idOrCondition !== 'object') { query.where('id', idOrCondition); } else if (idOrCondition) { @@ -810,8 +810,8 @@ export class MetaService { // todo: check project name used or not await this.knexConnection('nc_projects').insert({ ...project, - created_at: this.knexConnection?.fn?.now(), - updated_at: this.knexConnection?.fn?.now(), + created_at: this.now(), + updated_at: this.now(), }); // todo @@ -1030,6 +1030,20 @@ export class MetaService { return nanoid(); } + private isMySQL(): boolean { + return ( + this.connection.clientType() === 'mysql' || + this.connection.clientType() === 'mysql2' + ); + } + + private now(): any { + if (this.isMySQL()) { + return dayjs().utc().format('YYYY-MM-DD HH:mm:ss'); + } + return dayjs().utc().toISOString(); + } + public async audit( project_id: string, dbAlias: string, @@ -1051,6 +1065,10 @@ export class MetaService { migrationSource: new XcMigrationSourcev2(), tableName: 'xc_knex_migrationsv2', }); + if (this.isMySQL()) { + // set timezone + await this.connection.raw(`SET time_zone = '+00:00'`); + } return true; } } diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index 5fd54e6676..a42a1c18fb 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -236,6 +236,13 @@ export default class NcConfigFactory { acquireConnectionTimeout: 600000, } as any; + if (url.protocol.startsWith('mysql')) { + dbConfig.connection = { + ...dbConfig.connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } + if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -348,6 +355,14 @@ export default class NcConfigFactory { } : {}), }; + + if (url.protocol.startsWith('mysql')) { + dbConfig.connection = { + ...dbConfig.connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } + if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -511,6 +526,13 @@ export default class NcConfigFactory { }; } + if (dbConfig.client.startsWith('mysql')) { + dbConfig.connection = { + ...dbConfig.connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } + // todo: const key = ''; Object.assign(dbConfig, { @@ -744,6 +766,19 @@ export default class NcConfigFactory { return res; } + private static mysqlConnectionTypeCastConfig = { + typeCast: function (field, next) { + if ( + field.type === 'DATETIME' && + (field.name === 'created_at' || field.name === 'updated_at') + ) { + return new Date(field.string() + ' UTC'); + } + return next(); + }, + timezone: '+00:00', + }; + // public static initOneClickDeployment() { // if (process.env.NC_ONE_CLICK) { // const url = NcConfigFactory.extractXcUrlFromJdbc(process.env.DATABASE_URL); From 97948a46b4b5da08c64393fda592f2b5f8644d64 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 20 Apr 2023 16:29:57 +0800 Subject: [PATCH 006/233] refactor(nocodb): move to addTypeCastConfig --- .../nocodb-nest/src/utils/NcConfigFactory.ts | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index a42a1c18fb..0e9a5c9714 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -236,12 +236,10 @@ export default class NcConfigFactory { acquireConnectionTimeout: 600000, } as any; - if (url.protocol.startsWith('mysql')) { - dbConfig.connection = { - ...dbConfig.connection, - ...this.mysqlConnectionTypeCastConfig, - }; - } + dbConfig.connection = this.addTypeCastConfig( + url.protocol, + dbConfig.connection, + ); if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; @@ -304,6 +302,16 @@ export default class NcConfigFactory { .replace(/[ -]/g, '_');*/ } + private static addTypeCastConfig(clientType: string, connection) { + if (clientType.startsWith('mysql')) { + connection = { + ...connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } + return connection; + } + static async metaUrlToDbConfig(urlString) { const url = new URL(urlString); @@ -356,12 +364,10 @@ export default class NcConfigFactory { : {}), }; - if (url.protocol.startsWith('mysql')) { - dbConfig.connection = { - ...dbConfig.connection, - ...this.mysqlConnectionTypeCastConfig, - }; - } + dbConfig.connection = this.addTypeCastConfig( + url.protocol, + dbConfig.connection, + ); if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; @@ -526,12 +532,10 @@ export default class NcConfigFactory { }; } - if (dbConfig.client.startsWith('mysql')) { - dbConfig.connection = { - ...dbConfig.connection, - ...this.mysqlConnectionTypeCastConfig, - }; - } + dbConfig.connection = this.addTypeCastConfig( + dbConfig.client, + dbConfig.connection, + ); // todo: const key = ''; From 8f6382e42c64ea35e9f9553eb04551eb14919193 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 20 Apr 2023 18:06:53 +0800 Subject: [PATCH 007/233] feat(nc-gui): revise emit model value logic for datetime picker --- packages/nc-gui/components/cell/DateTimePicker.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 82f25ee283..97d728a9b5 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -64,7 +64,11 @@ let localState = $computed({ } if (val.isValid()) { - emit('update:modelValue', val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')) + if (isMysql(column.value.base_id)) { + emit('update:modelValue', val?.format('YYYY-MM-DD HH:mm:ss')) + } else { + emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ')) + } } }, }) From 59511868bea69dd9fb99e2448a53a8269ef97ea8 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 20 Apr 2023 18:07:08 +0800 Subject: [PATCH 008/233] feat(nocodb): add setTypeParser for timestamp --- packages/nocodb-nest/src/db/CustomKnex.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/nocodb-nest/src/db/CustomKnex.ts b/packages/nocodb-nest/src/db/CustomKnex.ts index 37dd2569e1..4ca7e0eb51 100644 --- a/packages/nocodb-nest/src/db/CustomKnex.ts +++ b/packages/nocodb-nest/src/db/CustomKnex.ts @@ -7,6 +7,10 @@ import type { BaseModelSql } from './BaseModelSql'; // override parsing date column to Date() types.setTypeParser(1082, (val) => val); +// override timestamp +types.setTypeParser(1114, (val) => { + return new Date(val + '+0000'); +}); const opMappingGen = { eq: '=', From 00b598da1a85b4d267a482bc9b20e009aec76662 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 20 Apr 2023 18:07:29 +0800 Subject: [PATCH 009/233] feat(nocodb): set timezone for non mysql --- packages/nocodb-nest/src/meta/meta.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index 6bc447afe7..69833fbbca 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -1065,9 +1065,12 @@ export class MetaService { migrationSource: new XcMigrationSourcev2(), tableName: 'xc_knex_migrationsv2', }); + + // set timezone if (this.isMySQL()) { - // set timezone await this.connection.raw(`SET time_zone = '+00:00'`); + } else { + await this.connection.raw(`SET timezone='UTC'`); } return true; } From f6cd6c2a8ff04d9f0f25ded61184b253b66cc3a8 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 20 Apr 2023 18:07:39 +0800 Subject: [PATCH 010/233] chore(nocodb): add comment --- packages/nocodb-nest/src/utils/NcConfigFactory.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index 0e9a5c9714..a908faf951 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -309,6 +309,7 @@ export default class NcConfigFactory { ...this.mysqlConnectionTypeCastConfig, }; } + // for postgres - see `setTypeParser` in `CustomKnex.ts` return connection; } From 205a2a21909dc32fb3c32ad87296a27534187086 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 21 Apr 2023 17:00:07 +0800 Subject: [PATCH 011/233] feat(nocodb): revise type parser for timestamp --- packages/nocodb-nest/src/db/CustomKnex.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/nocodb-nest/src/db/CustomKnex.ts b/packages/nocodb-nest/src/db/CustomKnex.ts index 4ca7e0eb51..69d765bf21 100644 --- a/packages/nocodb-nest/src/db/CustomKnex.ts +++ b/packages/nocodb-nest/src/db/CustomKnex.ts @@ -1,16 +1,21 @@ import { Knex, knex } from 'knex'; import { SnowflakeClient } from 'nc-help'; -import { types } from 'pg'; +import pg, { types } from 'pg'; +import dayjs from 'dayjs'; import Filter from '../models/Filter'; import type { FilterType } from 'nocodb-sdk'; import type { BaseModelSql } from './BaseModelSql'; +pg.defaults.parseInputDatesAsUTC = true; + // override parsing date column to Date() types.setTypeParser(1082, (val) => val); // override timestamp -types.setTypeParser(1114, (val) => { - return new Date(val + '+0000'); -}); +for (const oid of [1114, 1184]) { + types.setTypeParser(oid, (val) => { + return dayjs(val).utc(true).local().format('YYYY-MM-DD HH:mm:ssZ'); + }); +} const opMappingGen = { eq: '=', From e46510a235e63274f17f6e2ed720314cf87d506a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 21 Apr 2023 17:01:33 +0800 Subject: [PATCH 012/233] feat: set utc timezone at connection --- packages/nocodb-nest/src/utils/NcConfigFactory.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index a908faf951..de66b957e9 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -308,8 +308,13 @@ export default class NcConfigFactory { ...connection, ...this.mysqlConnectionTypeCastConfig, }; + } else { + // for postgres - see `setTypeParser` in `CustomKnex.ts` + connection = { + ...connection, + timezone: 'UTC', + }; } - // for postgres - see `setTypeParser` in `CustomKnex.ts` return connection; } From f40aa8b539350782d54404643ea99563cd05f871 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 21 Apr 2023 17:04:12 +0800 Subject: [PATCH 013/233] feat: set timezone for each db types n fix now() --- packages/nocodb-nest/src/meta/meta.service.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index 69833fbbca..f630c75d05 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -1037,11 +1037,27 @@ export class MetaService { ); } + private isSqlite() { + return this.connection.clientType() === 'sqlite3'; + } + + private isMssql() { + return this.connection.clientType() === 'mssql'; + } + + private isPg() { + return this.connection.clientType() === 'pg'; + } + + private isSnowflake() { + return this.connection.clientType() === 'snowflake'; + } + private now(): any { if (this.isMySQL()) { return dayjs().utc().format('YYYY-MM-DD HH:mm:ss'); } - return dayjs().utc().toISOString(); + return dayjs().utc().format('YYYY-MM-DD HH:mm:ssZ'); } public async audit( @@ -1069,8 +1085,14 @@ export class MetaService { // set timezone if (this.isMySQL()) { await this.connection.raw(`SET time_zone = '+00:00'`); - } else { - await this.connection.raw(`SET timezone='UTC'`); + } else if (this.isPg()) { + await this.connection.raw(`SET TIME ZONE 'UTC'`); + } else if (this.isMssql()) { + await this.connection.raw(`SET TIMEZONE = 'UTC'`); + } else if (this.isSqlite()) { + await this.connection.raw(`PRAGMA timezone = 'UTC'`); + } else if (this.isSnowflake()) { + // TODO } return true; } From 00bdc1dc9d8b6060fb7fe84fc64fbe9d9cc5cf04 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 21 Apr 2023 19:47:25 +0800 Subject: [PATCH 014/233] feat(nocodb): add dayjs extend --- packages/nocodb-nest/src/db/CustomKnex.ts | 5 +++++ packages/nocodb-nest/src/meta/meta.service.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/nocodb-nest/src/db/CustomKnex.ts b/packages/nocodb-nest/src/db/CustomKnex.ts index 69d765bf21..8629f45d6f 100644 --- a/packages/nocodb-nest/src/db/CustomKnex.ts +++ b/packages/nocodb-nest/src/db/CustomKnex.ts @@ -2,10 +2,15 @@ import { Knex, knex } from 'knex'; import { SnowflakeClient } from 'nc-help'; import pg, { types } from 'pg'; import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; import Filter from '../models/Filter'; import type { FilterType } from 'nocodb-sdk'; import type { BaseModelSql } from './BaseModelSql'; +dayjs.extend(utc); +dayjs.extend(timezone); + pg.defaults.parseInputDatesAsUTC = true; // override parsing date column to Date() diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index f630c75d05..015fbf30ef 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -7,6 +7,8 @@ import { Optional, } from '@nestjs/common'; import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; import { customAlphabet } from 'nanoid'; import CryptoJS from 'crypto-js'; import { Connection } from '../connection/connection'; @@ -16,6 +18,9 @@ import XcMigrationSourcev2 from './migrations/XcMigrationSourcev2'; import XcMigrationSource from './migrations/XcMigrationSource'; import type { Knex } from 'knex'; +dayjs.extend(utc); +dayjs.extend(timezone); + const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4); // todo: tobe fixed From a13a2f195ecaa8a4f2d16dd2848c4b0fcd7f6279 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 12:14:43 +0800 Subject: [PATCH 015/233] feat(nc-gui): add isSqlite --- packages/nc-gui/store/project.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/nc-gui/store/project.ts b/packages/nc-gui/store/project.ts index 31a239cab2..f3e0dba3d8 100644 --- a/packages/nc-gui/store/project.ts +++ b/packages/nc-gui/store/project.ts @@ -82,6 +82,10 @@ export const useProject = defineStore('projectStore', () => { return ['mysql', ClientType.MYSQL].includes(getBaseType(baseId)) } + function isSqlite(baseId?: string) { + return getBaseType(baseId) === ClientType.SQLITE + } + function isMssql(baseId?: string) { return getBaseType(baseId) === 'mssql' } @@ -209,6 +213,7 @@ export const useProject = defineStore('projectStore', () => { isMysql, isMssql, isPg, + isSqlite, sqlUis, isSharedBase, loadProjectMetaInfo, From f451594b9f84ad8f45d9517ccfd3991d91c03a51 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 12:14:56 +0800 Subject: [PATCH 016/233] feat(nc-gui): handle sqlite datetime --- packages/nc-gui/components/cell/DateTimePicker.vue | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 97d728a9b5..e430b6c854 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -24,7 +24,7 @@ const { modelValue, isPk } = defineProps() const emit = defineEmits(['update:modelValue']) -const { isMysql } = useProject() +const { isMysql, isSqlite } = useProject() const { showNull } = useGlobal() @@ -55,6 +55,14 @@ let localState = $computed({ return undefined } + if (isSqlite(column.value.base_id)) { + return /^\d+$/.test(modelValue) + ? dayjs(+modelValue) + .utc(true) + .local() + : dayjs(modelValue).utc(true).local() + } + return /^\d+$/.test(modelValue) ? dayjs(+modelValue) : dayjs(modelValue) }, set(val?: dayjs.Dayjs) { @@ -66,6 +74,8 @@ let localState = $computed({ if (val.isValid()) { if (isMysql(column.value.base_id)) { emit('update:modelValue', val?.format('YYYY-MM-DD HH:mm:ss')) + } else if (isSqlite(column.value.base_id)) { + emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss')) } else { emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ')) } From 636c6513b88a0004f79ac11715eda8a9251dd980 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 16:47:35 +0800 Subject: [PATCH 017/233] fix(nocodb): apply type parse on 1114 only --- packages/nocodb-nest/src/db/CustomKnex.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/nocodb-nest/src/db/CustomKnex.ts b/packages/nocodb-nest/src/db/CustomKnex.ts index 8629f45d6f..b68e9ab5c2 100644 --- a/packages/nocodb-nest/src/db/CustomKnex.ts +++ b/packages/nocodb-nest/src/db/CustomKnex.ts @@ -16,11 +16,9 @@ pg.defaults.parseInputDatesAsUTC = true; // override parsing date column to Date() types.setTypeParser(1082, (val) => val); // override timestamp -for (const oid of [1114, 1184]) { - types.setTypeParser(oid, (val) => { - return dayjs(val).utc(true).local().format('YYYY-MM-DD HH:mm:ssZ'); - }); -} +types.setTypeParser(1114, (val) => { + return dayjs(val).utc(true).local().format('YYYY-MM-DD HH:mm:ssZ'); +}); const opMappingGen = { eq: '=', From 9e2eefea056656b368b3728cd450222f7148cbda Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 18:14:15 +0800 Subject: [PATCH 018/233] fix(nc-gui): add utc in timeAgo --- packages/nc-gui/utils/dateTimeUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/utils/dateTimeUtils.ts b/packages/nc-gui/utils/dateTimeUtils.ts index d9a8e986e2..803936ebdb 100644 --- a/packages/nc-gui/utils/dateTimeUtils.ts +++ b/packages/nc-gui/utils/dateTimeUtils.ts @@ -1,7 +1,7 @@ import dayjs from 'dayjs' export const timeAgo = (date: any) => { - return dayjs(date).fromNow() + return dayjs.utc(date).fromNow() } export const dateFormats = [ From 4e7f3211595a79522cd6171d43a8cc786dc48e8f Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 18:14:49 +0800 Subject: [PATCH 019/233] fix(nocodb): remove timezone setting in meta.service.ts --- packages/nocodb-nest/src/meta/meta.service.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index 015fbf30ef..29f02d4b8e 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -1086,19 +1086,6 @@ export class MetaService { migrationSource: new XcMigrationSourcev2(), tableName: 'xc_knex_migrationsv2', }); - - // set timezone - if (this.isMySQL()) { - await this.connection.raw(`SET time_zone = '+00:00'`); - } else if (this.isPg()) { - await this.connection.raw(`SET TIME ZONE 'UTC'`); - } else if (this.isMssql()) { - await this.connection.raw(`SET TIMEZONE = 'UTC'`); - } else if (this.isSqlite()) { - await this.connection.raw(`PRAGMA timezone = 'UTC'`); - } else if (this.isSnowflake()) { - // TODO - } return true; } } From f3c7f95c700cb18544644582e69bf7648a335d13 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 18:18:46 +0800 Subject: [PATCH 020/233] refactor(nocodb): remove unused functions --- packages/nocodb-nest/src/meta/meta.service.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index 29f02d4b8e..2cdf8900a6 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -1042,22 +1042,10 @@ export class MetaService { ); } - private isSqlite() { - return this.connection.clientType() === 'sqlite3'; - } - private isMssql() { return this.connection.clientType() === 'mssql'; } - private isPg() { - return this.connection.clientType() === 'pg'; - } - - private isSnowflake() { - return this.connection.clientType() === 'snowflake'; - } - private now(): any { if (this.isMySQL()) { return dayjs().utc().format('YYYY-MM-DD HH:mm:ss'); From 0abf4a247591007f4f482e90be95542c03c0ecd4 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 18:47:29 +0800 Subject: [PATCH 021/233] fix(nocodb): remove unused code --- packages/nocodb-nest/src/utils/NcConfigFactory.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index de66b957e9..1c2c5e1eb9 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -303,17 +303,12 @@ export default class NcConfigFactory { } private static addTypeCastConfig(clientType: string, connection) { + // typeCast only works for mysql if (clientType.startsWith('mysql')) { connection = { ...connection, ...this.mysqlConnectionTypeCastConfig, }; - } else { - // for postgres - see `setTypeParser` in `CustomKnex.ts` - connection = { - ...connection, - timezone: 'UTC', - }; } return connection; } From e348c44125b6ec8f55654b1fec6982741573958e Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 19:45:21 +0800 Subject: [PATCH 022/233] feat(nc-gui): apply new logic on xcdb base only --- .../nc-gui/components/cell/DateTimePicker.vue | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index e430b6c854..2ed3c88a12 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -24,7 +24,7 @@ const { modelValue, isPk } = defineProps() const emit = defineEmits(['update:modelValue']) -const { isMysql, isSqlite } = useProject() +const { isMysql, isSqlite, isXcdbBase } = useProject() const { showNull } = useGlobal() @@ -55,7 +55,7 @@ let localState = $computed({ return undefined } - if (isSqlite(column.value.base_id)) { + if (isXcdbBase(column.value.base_id) && isSqlite(column.value.base_id)) { return /^\d+$/.test(modelValue) ? dayjs(+modelValue) .utc(true) @@ -72,12 +72,17 @@ let localState = $computed({ } if (val.isValid()) { - if (isMysql(column.value.base_id)) { - emit('update:modelValue', val?.format('YYYY-MM-DD HH:mm:ss')) - } else if (isSqlite(column.value.base_id)) { - emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss')) + if (isXcdbBase(column.value.base_id)) { + if (isMysql(column.value.base_id)) { + emit('update:modelValue', val?.format('YYYY-MM-DD HH:mm:ss')) + } else if (isSqlite(column.value.base_id)) { + emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss')) + } else { + emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ')) + } } else { - emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ')) + // TODO(timezone): keep ext db as it is + emit('update:modelValue', val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')) } } }, From 570a3f214e6e8deb4d343afe6246328267f531a7 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 22 Apr 2023 20:05:06 +0800 Subject: [PATCH 023/233] feat(nocodb): set time zone before insert / update (tbc) --- packages/nocodb-nest/src/db/BaseModelSqlv2.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts index 96be9282d3..96703bea6e 100644 --- a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts @@ -1755,6 +1755,10 @@ class BaseModelSqlv2 { let response; // const driver = trx ? trx : this.dbDriver; + if (this.isPg) { + await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); + } + const query = this.dbDriver(this.tnPath).insert(insertObj); if ((this.isPg || this.isMssql) && this.model.primaryKey) { query.returning( @@ -1889,6 +1893,10 @@ class BaseModelSqlv2 { const prevData = await this.readByPk(id); + if (this.isPg) { + await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); + } + const query = this.dbDriver(this.tnPath) .update(updateObj) .where(await this._wherePk(id)); @@ -2122,6 +2130,10 @@ class BaseModelSqlv2 { // refer : https://www.sqlite.org/limits.html const chunkSize = this.isSqlite ? 10 : _chunkSize; + if (this.isPg) { + await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); + } + const response = this.isPg || this.isMssql ? await this.dbDriver @@ -2168,6 +2180,10 @@ class BaseModelSqlv2 { updatePkValues.push(pkValues); } + if (this.isPg) { + await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); + } + transaction = await this.dbDriver.transaction(); for (const o of toBeUpdated) { From 4e6ea666a535132d9fee3798b8d76bd3ae8c276a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 11:41:37 +0800 Subject: [PATCH 024/233] chore(nocodb): remove parseInputDatesAsUTC --- packages/nocodb-nest/src/db/CustomKnex.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nocodb-nest/src/db/CustomKnex.ts b/packages/nocodb-nest/src/db/CustomKnex.ts index b68e9ab5c2..9bc09537db 100644 --- a/packages/nocodb-nest/src/db/CustomKnex.ts +++ b/packages/nocodb-nest/src/db/CustomKnex.ts @@ -11,8 +11,6 @@ import type { BaseModelSql } from './BaseModelSql'; dayjs.extend(utc); dayjs.extend(timezone); -pg.defaults.parseInputDatesAsUTC = true; - // override parsing date column to Date() types.setTypeParser(1082, (val) => val); // override timestamp From 873e52b23541efebe7b050ae4ff1e18e8825d0a4 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 12:30:25 +0800 Subject: [PATCH 025/233] refactor(nocodb): setUtcTimezoneForPg --- packages/nocodb-nest/src/db/BaseModelSqlv2.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts index 96703bea6e..0b6a4b8ba4 100644 --- a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts @@ -15,10 +15,6 @@ import ejs from 'ejs'; import Validator from 'validator'; import { customAlphabet } from 'nanoid'; import DOMPurify from 'isomorphic-dompurify'; - -const GROUP_COL = '__nc_group_id'; - -const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14); import { v4 as uuidv4 } from 'uuid'; import { NcError } from '../helpers/catchError'; import getAst from '../helpers/getAst'; @@ -29,6 +25,7 @@ import { } from '../helpers/webhookHelpers'; import { Audit, + Base, Column, Filter, FormView, @@ -67,6 +64,10 @@ import type { import type { Knex } from 'knex'; import type { SortType } from 'nocodb-sdk'; +const GROUP_COL = '__nc_group_id'; + +const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14); + export async function getViewAndModelByAliasOrId(param: { projectName: string; tableName: string; @@ -1755,9 +1756,7 @@ class BaseModelSqlv2 { let response; // const driver = trx ? trx : this.dbDriver; - if (this.isPg) { - await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); - } + await this.setUtcTimezoneForPg(); const query = this.dbDriver(this.tnPath).insert(insertObj); if ((this.isPg || this.isMssql) && this.model.primaryKey) { @@ -1893,9 +1892,7 @@ class BaseModelSqlv2 { const prevData = await this.readByPk(id); - if (this.isPg) { - await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); - } + await this.setUtcTimezoneForPg(); const query = this.dbDriver(this.tnPath) .update(updateObj) @@ -2130,9 +2127,7 @@ class BaseModelSqlv2 { // refer : https://www.sqlite.org/limits.html const chunkSize = this.isSqlite ? 10 : _chunkSize; - if (this.isPg) { - await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); - } + await this.setUtcTimezoneForPg(); const response = this.isPg || this.isMssql @@ -2180,9 +2175,7 @@ class BaseModelSqlv2 { updatePkValues.push(pkValues); } - if (this.isPg) { - await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); - } + await this.setUtcTimezoneForPg(); transaction = await this.dbDriver.transaction(); @@ -3176,6 +3169,13 @@ class BaseModelSqlv2 { } return data; } + + private async setUtcTimezoneForPg() { + const base = await Base.get(this.model.base_id); + if (this.isPg && base.is_meta) { + await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); + } + } } function extractSortsObject( From da0e1cf815d6658d1d1969ca572ed0f87d5e84e1 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 13:50:28 +0800 Subject: [PATCH 026/233] chore(nocodb): rollback old version to avoid confusion --- packages/nocodb/src/lib/meta/NcMetaIOImpl.ts | 33 ++++------------- .../nocodb/src/lib/utils/NcConfigFactory.ts | 35 ------------------- 2 files changed, 7 insertions(+), 61 deletions(-) diff --git a/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts b/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts index cf8d246b66..b04157a088 100644 --- a/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts +++ b/packages/nocodb/src/lib/meta/NcMetaIOImpl.ts @@ -1,6 +1,5 @@ import CryptoJS from 'crypto-js'; import { customAlphabet } from 'nanoid'; -import dayjs from 'dayjs'; import { XKnex } from '../db/sql-data-mapper'; import XcMigrationSource from '../migrations/XcMigrationSource'; import NcConnectionMgr from '../utils/common/NcConnectionMgr'; @@ -95,20 +94,6 @@ export default class NcMetaIOImpl extends NcMetaIO { return (this.trx || this.connection) as any; } - private isMySQL(): boolean { - return ( - this.config?.meta?.db?.client === 'mysql' || - this.config?.meta?.db?.client === 'mysql2' - ); - } - - private now(): any { - if (this.isMySQL()) { - return dayjs().utc().format('YYYY-MM-DD HH:mm:ss'); - } - return dayjs().utc().toISOString(); - } - public updateKnex(connectionConfig): void { this.connection = XKnex(connectionConfig); } @@ -122,10 +107,6 @@ export default class NcMetaIOImpl extends NcMetaIO { migrationSource: new XcMigrationSourcev2(), tableName: 'xc_knex_migrationsv2', }); - if (this.isMySQL()) { - // set timezone - await this.connection.raw(`SET time_zone = '+00:00'`); - } return true; } @@ -257,9 +238,9 @@ export default class NcMetaIOImpl extends NcMetaIO { return this.knexConnection(target).insert({ db_alias: dbAlias, project_id, + created_at: this.knexConnection?.fn?.now(), + updated_at: this.knexConnection?.fn?.now(), ...data, - created_at: this.now(), - updated_at: this.now(), }); } @@ -279,8 +260,8 @@ export default class NcMetaIOImpl extends NcMetaIO { if (project_id !== null) insertObj.project_id = project_id; await this.knexConnection(target).insert({ ...insertObj, - created_at: this.now(), - updated_at: this.now(), + created_at: insertObj?.created_at || this.knexConnection?.fn?.now(), + updated_at: insertObj?.updated_at || this.knexConnection?.fn?.now(), }); return insertObj; } @@ -429,7 +410,7 @@ export default class NcMetaIOImpl extends NcMetaIO { delete data.created_at; - query.update({ ...data, updated_at: this.now() }); + query.update({ ...data, updated_at: this.knexConnection?.fn?.now() }); if (typeof idOrCondition !== 'object') { query.where('id', idOrCondition); } else if (idOrCondition) { @@ -549,8 +530,8 @@ export default class NcMetaIOImpl extends NcMetaIO { // todo: check project name used or not await this.knexConnection('nc_projects').insert({ ...project, - created_at: this.now(), - updated_at: this.now(), + created_at: this.knexConnection?.fn?.now(), + updated_at: this.knexConnection?.fn?.now(), }); // todo diff --git a/packages/nocodb/src/lib/utils/NcConfigFactory.ts b/packages/nocodb/src/lib/utils/NcConfigFactory.ts index f521585e92..85c1fe1d7c 100644 --- a/packages/nocodb/src/lib/utils/NcConfigFactory.ts +++ b/packages/nocodb/src/lib/utils/NcConfigFactory.ts @@ -232,13 +232,6 @@ export default class NcConfigFactory implements NcConfig { acquireConnectionTimeout: 600000, } as any; - if (url.protocol.startsWith('mysql')) { - dbConfig.connection = { - ...dbConfig.connection, - ...this.mysqlConnectionTypeCastConfig, - }; - } - if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -351,14 +344,6 @@ export default class NcConfigFactory implements NcConfig { } : {}), }; - - if (url.protocol.startsWith('mysql')) { - dbConfig.connection = { - ...dbConfig.connection, - ...this.mysqlConnectionTypeCastConfig, - }; - } - if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -521,13 +506,6 @@ export default class NcConfigFactory implements NcConfig { }, }; } - - if (dbConfig.client.startsWith('mysql')) { - dbConfig.connection = { - ...dbConfig.connection, - ...this.mysqlConnectionTypeCastConfig, - }; - } // todo: const key = ''; @@ -761,19 +739,6 @@ export default class NcConfigFactory implements NcConfig { return res; } - private static mysqlConnectionTypeCastConfig = { - typeCast: function (field, next) { - if ( - field.type === 'DATETIME' && - (field.name === 'created_at' || field.name === 'updated_at') - ) { - return new Date(field.string() + ' UTC'); - } - return next(); - }, - timezone: '+00:00', - }; - // public static initOneClickDeployment() { // if (process.env.NC_ONE_CLICK) { // const url = NcConfigFactory.extractXcUrlFromJdbc(process.env.DATABASE_URL); From 2f8a826023ae7f9069b3148868494bf9e53c6991 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 14:13:34 +0800 Subject: [PATCH 027/233] chore(nocodb): remove type cast (seems not required) --- .../nocodb-nest/src/utils/NcConfigFactory.ts | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index 1c2c5e1eb9..cd13e3f2a5 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -236,11 +236,6 @@ export default class NcConfigFactory { acquireConnectionTimeout: 600000, } as any; - dbConfig.connection = this.addTypeCastConfig( - url.protocol, - dbConfig.connection, - ); - if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -302,17 +297,6 @@ export default class NcConfigFactory { .replace(/[ -]/g, '_');*/ } - private static addTypeCastConfig(clientType: string, connection) { - // typeCast only works for mysql - if (clientType.startsWith('mysql')) { - connection = { - ...connection, - ...this.mysqlConnectionTypeCastConfig, - }; - } - return connection; - } - static async metaUrlToDbConfig(urlString) { const url = new URL(urlString); @@ -365,11 +349,6 @@ export default class NcConfigFactory { : {}), }; - dbConfig.connection = this.addTypeCastConfig( - url.protocol, - dbConfig.connection, - ); - if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -533,11 +512,6 @@ export default class NcConfigFactory { }; } - dbConfig.connection = this.addTypeCastConfig( - dbConfig.client, - dbConfig.connection, - ); - // todo: const key = ''; Object.assign(dbConfig, { @@ -771,19 +745,6 @@ export default class NcConfigFactory { return res; } - private static mysqlConnectionTypeCastConfig = { - typeCast: function (field, next) { - if ( - field.type === 'DATETIME' && - (field.name === 'created_at' || field.name === 'updated_at') - ) { - return new Date(field.string() + ' UTC'); - } - return next(); - }, - timezone: '+00:00', - }; - // public static initOneClickDeployment() { // if (process.env.NC_ONE_CLICK) { // const url = NcConfigFactory.extractXcUrlFromJdbc(process.env.DATABASE_URL); From 69fb0296bc4f5ffb8ac9e17f3dbf93c552f70e89 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 14:17:12 +0800 Subject: [PATCH 028/233] chore(nocodb): sync with develop --- packages/nocodb-nest/src/utils/NcConfigFactory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index cd13e3f2a5..5fd54e6676 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -348,7 +348,6 @@ export default class NcConfigFactory { } : {}), }; - if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } From de903a1debacc9218e4871522697855624087d1c Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 17:55:08 +0800 Subject: [PATCH 029/233] feat(nc-gui): timezone handling in copied cell data --- .../composables/useMultiSelect/convertCellData.ts | 14 +++++++++++++- .../nc-gui/composables/useMultiSelect/index.ts | 6 +++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 33cce2db2a..c766b38a00 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -7,6 +7,8 @@ import { parseProp } from '#imports' export default function convertCellData( args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo }, isMysql = false, + isSqlite = false, + isXcdbBase = false, ) { const { from, to, value } = args if (from === to && ![UITypes.Attachment, UITypes.Date, UITypes.DateTime, UITypes.Time, UITypes.Year].includes(to)) { @@ -42,7 +44,17 @@ export default function convertCellData( if (!parsedDateTime.isValid()) { throw new Error('Not a valid datetime value') } - return parsedDateTime.format(dateFormat) + if (isXcdbBase) { + if (isMysql) { + return parsedDateTime?.format('YYYY-MM-DD HH:mm:ss') + } else if (isSqlite) { + return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss') + } else { + return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') + } + } + // TODO(timezone): keep ext db as it is + return parsedDateTime.format(isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') } case UITypes.Time: { let parsedTime = dayjs(value) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 507cd81f63..d319bc0273 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -52,7 +52,7 @@ export function useMultiSelect( const { appInfo } = useGlobal() - const { isMysql } = useProject() + const { isMysql, isSqlite, isXcdbBase } = useProject() let clipboardContext = $ref<{ value: any; uidt: UITypes } | null>(null) @@ -305,6 +305,8 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), + isSqlite(meta.value?.base_id), + isXcdbBase(meta.value?.base_id), ) e.preventDefault() @@ -339,6 +341,8 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), + isSqlite(meta.value?.base_id), + isXcdbBase(meta.value?.base_id), ) e.preventDefault() syncCellData?.(activeCell) From 1f1d3bf374e903bf5bad6aac7633b41a353492e1 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 19:55:28 +0800 Subject: [PATCH 030/233] refactor(nc-gui): move the logic to backend --- .../composables/useMultiSelect/convertCellData.ts | 14 +------------- .../nc-gui/composables/useMultiSelect/index.ts | 6 +----- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index c766b38a00..33cce2db2a 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -7,8 +7,6 @@ import { parseProp } from '#imports' export default function convertCellData( args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo }, isMysql = false, - isSqlite = false, - isXcdbBase = false, ) { const { from, to, value } = args if (from === to && ![UITypes.Attachment, UITypes.Date, UITypes.DateTime, UITypes.Time, UITypes.Year].includes(to)) { @@ -44,17 +42,7 @@ export default function convertCellData( if (!parsedDateTime.isValid()) { throw new Error('Not a valid datetime value') } - if (isXcdbBase) { - if (isMysql) { - return parsedDateTime?.format('YYYY-MM-DD HH:mm:ss') - } else if (isSqlite) { - return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss') - } else { - return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') - } - } - // TODO(timezone): keep ext db as it is - return parsedDateTime.format(isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') + return parsedDateTime.format(dateFormat) } case UITypes.Time: { let parsedTime = dayjs(value) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index d319bc0273..507cd81f63 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -52,7 +52,7 @@ export function useMultiSelect( const { appInfo } = useGlobal() - const { isMysql, isSqlite, isXcdbBase } = useProject() + const { isMysql } = useProject() let clipboardContext = $ref<{ value: any; uidt: UITypes } | null>(null) @@ -305,8 +305,6 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), - isSqlite(meta.value?.base_id), - isXcdbBase(meta.value?.base_id), ) e.preventDefault() @@ -341,8 +339,6 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), - isSqlite(meta.value?.base_id), - isXcdbBase(meta.value?.base_id), ) e.preventDefault() syncCellData?.(activeCell) From be613ee2a8e4b50c0e58e4524959792967dabec2 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 19:55:46 +0800 Subject: [PATCH 031/233] refactor(nocodb): remove unused function --- packages/nocodb-nest/src/meta/meta.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index 2cdf8900a6..fd2ad6665c 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -1042,10 +1042,6 @@ export class MetaService { ); } - private isMssql() { - return this.connection.clientType() === 'mssql'; - } - private now(): any { if (this.isMySQL()) { return dayjs().utc().format('YYYY-MM-DD HH:mm:ss'); From 07c2e90ab3ef7b14c828eecea4f4b76fe56f2992 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 19:56:37 +0800 Subject: [PATCH 032/233] fix(nocodb): handle datetime timezone in backend --- packages/nocodb-nest/src/models/Model.ts | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/models/Model.ts b/packages/nocodb-nest/src/models/Model.ts index abb6ec5268..818c3eb067 100644 --- a/packages/nocodb-nest/src/models/Model.ts +++ b/packages/nocodb-nest/src/models/Model.ts @@ -1,4 +1,5 @@ import { isVirtualCol, ModelTypes, UITypes, ViewTypes } from 'nocodb-sdk'; +import dayjs from 'dayjs'; import { BaseModelSqlv2 } from '../db/BaseModelSqlv2'; import Noco from '../Noco'; import { parseMetaProp } from '../utils/modelUtils'; @@ -15,6 +16,7 @@ import { sanitize } from '../helpers/sqlSanitize'; import { extractProps } from '../helpers/extractProps'; import Audit from './Audit'; import View from './View'; +import Base from './Base'; import Column from './Column'; import type { BoolType, TableReqType, TableType } from 'nocodb-sdk'; import type { XKnex } from '../db/CustomKnex'; @@ -430,8 +432,15 @@ export default class Model implements TableType { return true; } - async mapAliasToColumn(data) { + async mapAliasToColumn( + data, + clientMeta = { + isMySQL: false, + isSqlite: false, + }, + ) { const insertObj = {}; + const base = await Base.get(this.base_id); for (const col of await this.getColumns()) { if (isVirtualCol(col)) continue; let val = @@ -442,6 +451,23 @@ export default class Model implements TableType { if (col.uidt === UITypes.Attachment && typeof val !== 'string') { val = JSON.stringify(val); } + if (col.uidt === UITypes.DateTime && dayjs(val).isValid()) { + const { isMySQL, isSqlite } = clientMeta; + if (base.is_meta) { + if (isMySQL) { + val = dayjs(val)?.format('YYYY-MM-DD HH:mm:ss'); + } else if (isSqlite) { + val = dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss'); + } else { + val = dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ'); + } + } else { + // TODO(timezone): keep ext db as it is + val = dayjs(val).format( + isMySQL ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ', + ); + } + } insertObj[sanitize(col.column_name)] = val; } } From 115d652fc7177b2159a55e896b4889fb94897ed9 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 19:57:04 +0800 Subject: [PATCH 033/233] feat(nocodb): pass client meta to mapAliasToColumn --- packages/nocodb-nest/src/db/BaseModelSqlv2.ts | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts index 0b6a4b8ba4..22648cdd03 100644 --- a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts @@ -1744,7 +1744,10 @@ class BaseModelSqlv2 { await populatePk(this.model, data); // todo: filter based on view - const insertObj = await this.model.mapAliasToColumn(data); + const insertObj = await this.model.mapAliasToColumn( + data, + this.clientMeta, + ); await this.validate(insertObj); @@ -1884,7 +1887,10 @@ class BaseModelSqlv2 { async updateByPk(id, data, trx?, cookie?) { try { - const updateObj = await this.model.mapAliasToColumn(data); + const updateObj = await this.model.mapAliasToColumn( + data, + this.clientMeta, + ); await this.validate(data); @@ -1934,6 +1940,16 @@ class BaseModelSqlv2 { return this.getTnPath(this.model); } + public get clientMeta() { + return { + isSqlite: this.isSqlite, + // isMssql: this.isMssql, + // isPg: this.isPg, + isMySQL: this.isMySQL, + // isSnowflake: this.isSnowflake, + }; + } + get isSqlite() { return this.clientType === 'sqlite3'; } @@ -1962,7 +1978,10 @@ class BaseModelSqlv2 { // const driver = trx ? trx : await this.dbDriver.transaction(); try { await populatePk(this.model, data); - const insertObj = await this.model.mapAliasToColumn(data); + const insertObj = await this.model.mapAliasToColumn( + data, + this.clientMeta, + ); let rowId = null; const postInsertOps = []; @@ -2112,7 +2131,7 @@ class BaseModelSqlv2 { const insertDatas = await Promise.all( datas.map(async (d) => { await populatePk(this.model, d); - return this.model.mapAliasToColumn(d); + return this.model.mapAliasToColumn(d, this.clientMeta); }), ); @@ -2153,7 +2172,7 @@ class BaseModelSqlv2 { let transaction; try { const updateDatas = await Promise.all( - datas.map((d) => this.model.mapAliasToColumn(d)), + datas.map((d) => this.model.mapAliasToColumn(d, this.clientMeta)), ); const prevData = []; @@ -2205,7 +2224,10 @@ class BaseModelSqlv2 { ) { try { let count = 0; - const updateData = await this.model.mapAliasToColumn(data); + const updateData = await this.model.mapAliasToColumn( + data, + this.clientMeta, + ); await this.validate(updateData); const pkValues = await this._extractPksValues(updateData); if (pkValues) { @@ -2251,7 +2273,7 @@ class BaseModelSqlv2 { let transaction; try { const deleteIds = await Promise.all( - ids.map((d) => this.model.mapAliasToColumn(d)), + ids.map((d) => this.model.mapAliasToColumn(d, this.clientMeta)), ); const deleted = []; From 7470da51d279f050f2b09f418479337e40c2fd89 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 27 Apr 2023 20:19:51 +0800 Subject: [PATCH 034/233] refactor(nc-gui): move to backend --- packages/nc-gui/components/cell/DateTimePicker.vue | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 2ed3c88a12..55f0994331 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -72,18 +72,7 @@ let localState = $computed({ } if (val.isValid()) { - if (isXcdbBase(column.value.base_id)) { - if (isMysql(column.value.base_id)) { - emit('update:modelValue', val?.format('YYYY-MM-DD HH:mm:ss')) - } else if (isSqlite(column.value.base_id)) { - emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss')) - } else { - emit('update:modelValue', dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ')) - } - } else { - // TODO(timezone): keep ext db as it is - emit('update:modelValue', val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')) - } + emit('update:modelValue', val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')) } }, }) From 4cb842b61a41a29efa2336130f9fa9bb1511b651 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 28 Apr 2023 16:24:52 +0800 Subject: [PATCH 035/233] fix(nocodb): move parsing logic out of type parser --- packages/nocodb-nest/src/db/CustomKnex.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/nocodb-nest/src/db/CustomKnex.ts b/packages/nocodb-nest/src/db/CustomKnex.ts index 9bc09537db..9bee0a6c65 100644 --- a/packages/nocodb-nest/src/db/CustomKnex.ts +++ b/packages/nocodb-nest/src/db/CustomKnex.ts @@ -2,20 +2,15 @@ import { Knex, knex } from 'knex'; import { SnowflakeClient } from 'nc-help'; import pg, { types } from 'pg'; import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -import timezone from 'dayjs/plugin/timezone'; import Filter from '../models/Filter'; import type { FilterType } from 'nocodb-sdk'; import type { BaseModelSql } from './BaseModelSql'; -dayjs.extend(utc); -dayjs.extend(timezone); - // override parsing date column to Date() types.setTypeParser(1082, (val) => val); // override timestamp types.setTypeParser(1114, (val) => { - return dayjs(val).utc(true).local().format('YYYY-MM-DD HH:mm:ssZ'); + return dayjs(val).format('YYYY-MM-DD HH:mm:ss'); }); const opMappingGen = { From 4ae44474dffaa009b4296d1322fe070c5e91946f Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 28 Apr 2023 16:25:05 +0800 Subject: [PATCH 036/233] fix(nc-gui): revise parsing logic --- .../nc-gui/components/cell/DateTimePicker.vue | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 55f0994331..d64419833a 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -44,6 +44,8 @@ const dateTimeFormat = $computed(() => { return `${dateFormat} ${timeFormat}` }) +let localModelValue = $ref(modelValue ? dayjs(modelValue).utc(true).local() : undefined) + let localState = $computed({ get() { if (!modelValue) { @@ -55,15 +57,11 @@ let localState = $computed({ return undefined } - if (isXcdbBase(column.value.base_id) && isSqlite(column.value.base_id)) { - return /^\d+$/.test(modelValue) - ? dayjs(+modelValue) - .utc(true) - .local() - : dayjs(modelValue).utc(true).local() + if (localModelValue) { + return localModelValue } - return /^\d+$/.test(modelValue) ? dayjs(+modelValue) : dayjs(modelValue) + return dayjs(modelValue).utc(true).local() }, set(val?: dayjs.Dayjs) { if (!val) { @@ -72,7 +70,9 @@ let localState = $computed({ } if (val.isValid()) { - emit('update:modelValue', val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')) + const formattedValue = dayjs(val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')) + localModelValue = formattedValue + emit('update:modelValue', formattedValue) } }, }) From d39df667521d9ff02d08b7ddfcf459d1e842cdf7 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 28 Apr 2023 21:44:11 +0800 Subject: [PATCH 037/233] fix(nc-gui): cast boolean in isXcdbBase --- packages/nc-gui/store/project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/store/project.ts b/packages/nc-gui/store/project.ts index f3e0dba3d8..2771d428cb 100644 --- a/packages/nc-gui/store/project.ts +++ b/packages/nc-gui/store/project.ts @@ -95,7 +95,7 @@ export const useProject = defineStore('projectStore', () => { } function isXcdbBase(baseId?: string) { - return bases.value.find((base) => base.id === baseId)?.is_meta + return (bases.value.find((base) => base.id === baseId)?.is_meta as boolean) || false } const isSharedBase = computed(() => projectType === 'base') From 5728102332b7e899ef304cda17c43ebaaa020790 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 13:01:00 +0800 Subject: [PATCH 038/233] feat(nc-gui): add isUpdateOutside --- packages/nc-gui/lib/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nc-gui/lib/types.ts b/packages/nc-gui/lib/types.ts index d2c06f44d1..e011c39efb 100644 --- a/packages/nc-gui/lib/types.ts +++ b/packages/nc-gui/lib/types.ts @@ -60,6 +60,8 @@ export interface Row { commentCount?: number changed?: boolean saving?: boolean + // use in datetime picker component + isUpdateOutside?: Record } } From bdd67879577711ac4bad2a9e62527c32dc94df82 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 13:04:24 +0800 Subject: [PATCH 039/233] fix(nc-gui): copy datetime in local time --- .../composables/useMultiSelect/index.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 507cd81f63..8e93a7b3c0 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -1,3 +1,4 @@ +import dayjs from 'dayjs' import type { MaybeRef } from '@vueuse/core' import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk' import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' @@ -7,6 +8,7 @@ import convertCellData from './convertCellData' import type { Nullable, Row } from '~/lib' import { copyTable, + dateFormats, extractPkFromRow, extractSdkResponseErrorMsg, isMac, @@ -14,6 +16,7 @@ import { message, reactive, ref, + timeFormats, unref, useCopy, useEventListener, @@ -52,7 +55,7 @@ export function useMultiSelect( const { appInfo } = useGlobal() - const { isMysql } = useProject() + const { isMysql, isSqlite, isXcdbBase } = useProject() let clipboardContext = $ref<{ value: any; uidt: UITypes } | null>(null) @@ -79,6 +82,12 @@ export function useMultiSelect( activeCell.col = col } + function constructDateTimeFormat(column: ColumnType) { + const dateFormat = parseProp(column?.meta)?.date_format ?? dateFormats[0] + const timeFormat = parseProp(column?.meta)?.time_format ?? timeFormats[0] + return `${dateFormat} ${timeFormat}` + } + async function copyValue(ctx?: Cell) { try { if (selectedRange.start !== null && selectedRange.end !== null && !selectedRange.isSingleCell()) { @@ -106,6 +115,14 @@ export function useMultiSelect( if (typeof textToCopy === 'object') { textToCopy = JSON.stringify(textToCopy) } + + if (columnObj.uidt === UITypes.DateTime) { + textToCopy = dayjs(textToCopy).utc(true).local().format(constructDateTimeFormat(columnObj)) + if (!dayjs(textToCopy).isValid()) { + throw new Error('Invalid Date') + } + } + await copy(textToCopy) message.success(t('msg.info.copiedToClipboard')) } @@ -305,6 +322,8 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), + isSqlite(meta.value?.base_id), + isXcdbBase(meta.value?.base_id), ) e.preventDefault() @@ -339,6 +358,8 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), + isSqlite(meta.value?.base_id), + isXcdbBase(meta.value?.base_id), ) e.preventDefault() syncCellData?.(activeCell) From 212860c7ac535f270c25a2b094d82c03daf43df4 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 13:05:10 +0800 Subject: [PATCH 040/233] feat(nc-gui): paste datetime in utc for xcdb base --- .../composables/useMultiSelect/convertCellData.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 33cce2db2a..6dc76bcf91 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -7,6 +7,8 @@ import { parseProp } from '#imports' export default function convertCellData( args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo }, isMysql = false, + isSqlite = false, + isXcdbBase = false, ) { const { from, to, value } = args if (from === to && ![UITypes.Attachment, UITypes.Date, UITypes.DateTime, UITypes.Time, UITypes.Year].includes(to)) { @@ -42,7 +44,17 @@ export default function convertCellData( if (!parsedDateTime.isValid()) { throw new Error('Not a valid datetime value') } - return parsedDateTime.format(dateFormat) + if (isXcdbBase) { + if (isMysql) { + return parsedDateTime?.format('YYYY-MM-DD HH:mm:ss') + } else if (isSqlite) { + return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss') + } else { + return parsedDateTime.utc(true).format('YYYY-MM-DD HH:mm:ssZ') + } + } + // TODO(timezone): keep ext db as it is + return parsedDateTime.format(isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') } case UITypes.Time: { let parsedTime = dayjs(value) From 617a4869d43db49f5565986cf33bb0193ed1c115 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 13:05:28 +0800 Subject: [PATCH 041/233] feat(nc-gui): add isUpdateOutside after updateOrSaveRow --- packages/nc-gui/components/smartsheet/Grid.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/nc-gui/components/smartsheet/Grid.vue b/packages/nc-gui/components/smartsheet/Grid.vue index b79dc2d558..9efba29d49 100644 --- a/packages/nc-gui/components/smartsheet/Grid.vue +++ b/packages/nc-gui/components/smartsheet/Grid.vue @@ -318,6 +318,12 @@ const { // update/save cell value await updateOrSaveRow(rowObj, ctx.updatedColumnTitle || columnObj.title) + + // See DateTimePicker.vue for details + data.value[ctx.row].rowMeta.isUpdateOutside = { + ...data.value[ctx.row].rowMeta.isUpdateOutside, + [ctx.updatedColumnTitle || columnObj.title]: true, + } }, ) From 46e7ed989b01ec415c3e977d4f37aaac26236ce5 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 13:07:43 +0800 Subject: [PATCH 042/233] feat(nc-gui): include isUpdateOutside to cate copy-n-paste --- packages/nc-gui/components/smartsheet/Cell.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/components/smartsheet/Cell.vue b/packages/nc-gui/components/smartsheet/Cell.vue index 2023ec9604..2ef824b933 100644 --- a/packages/nc-gui/components/smartsheet/Cell.vue +++ b/packages/nc-gui/components/smartsheet/Cell.vue @@ -209,7 +209,12 @@ onUnmounted(() => { - + From 63783fd99235d05a92b9641d6683c4482e46e028 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 13:08:01 +0800 Subject: [PATCH 043/233] fix(nc-gui): revise datetime picker rendering logic ... --- .../nc-gui/components/cell/DateTimePicker.vue | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index d64419833a..c037166173 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -18,9 +18,10 @@ import { interface Props { modelValue?: string | null isPk?: boolean + isUpdateOutside: Record } -const { modelValue, isPk } = defineProps() +const { modelValue, isPk, isUpdateOutside } = defineProps() const emit = defineEmits(['update:modelValue']) @@ -44,7 +45,7 @@ const dateTimeFormat = $computed(() => { return `${dateFormat} ${timeFormat}` }) -let localModelValue = $ref(modelValue ? dayjs(modelValue).utc(true).local() : undefined) +let localModelValue = modelValue ? dayjs(modelValue).utc(true).local() : undefined let localState = $computed({ get() { @@ -57,10 +58,28 @@ let localState = $computed({ return undefined } + // if cdf is defined, that means the value is auto-generated + // hence, show the local time + if (column?.value?.cdf) { + return dayjs(modelValue).utc(true).local() + } + + // cater copy and paste + // when copying a datetime cell, the copied value would be local time + // when pasting a datetime cell, UTC (xcdb) will be saved in DB + // we convert back to local time + if (column.value.title! in (isUpdateOutside ?? {})) { + localModelValue = dayjs(modelValue).utc().local() + return localModelValue + } + + // if localModelValue is defined, show localModelValue instead + // localModelValue is set in setter below if (localModelValue) { return localModelValue } + // empty cell - use modelValue in local time return dayjs(modelValue).utc(true).local() }, set(val?: dayjs.Dayjs) { @@ -71,6 +90,7 @@ let localState = $computed({ if (val.isValid()) { const formattedValue = dayjs(val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')) + // setting localModelValue to cater NOW function in date picker localModelValue = formattedValue emit('update:modelValue', formattedValue) } From ebb7210736c30aa9a7b845c060996a05559d2563 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 17:05:40 +0800 Subject: [PATCH 044/233] feat(nocodb): add type cast for mysql --- .../nocodb-nest/src/utils/NcConfigFactory.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index 5fd54e6676..3914c185c3 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -4,6 +4,8 @@ import { promisify } from 'util'; import * as path from 'path'; import parseDbUrl from 'parse-database-url'; import { SqlClientFactory } from '../db/sql-client/lib/SqlClientFactory'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; // import SqlClientFactory from '../db/sql-client/lib/SqlClientFactory'; // import type { // AuthConfig, @@ -19,6 +21,8 @@ import { SqlClientFactory } from '../db/sql-client/lib/SqlClientFactory'; // animals, // } = require('unique-names-generator'); +dayjs.extend(utc); + type NcConfig = any; type DbConfig = any; @@ -236,6 +240,11 @@ export default class NcConfigFactory { acquireConnectionTimeout: 600000, } as any; + dbConfig.connection = this.addTypeCastConfig( + url.protocol, + dbConfig.connection, + ); + if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -297,6 +306,16 @@ export default class NcConfigFactory { .replace(/[ -]/g, '_');*/ } + private static addTypeCastConfig(clientType: string, connection) { + if (clientType.startsWith('mysql')) { + connection = { + ...connection, + ...this.mysqlConnectionTypeCastConfig, + }; + } + return connection; + } + static async metaUrlToDbConfig(urlString) { const url = new URL(urlString); @@ -348,6 +367,12 @@ export default class NcConfigFactory { } : {}), }; + + dbConfig.connection = this.addTypeCastConfig( + url.protocol, + dbConfig.connection, + ); + if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { dbConfig.connection.ssl = true; } @@ -511,6 +536,11 @@ export default class NcConfigFactory { }; } + dbConfig.connection = this.addTypeCastConfig( + dbConfig.client, + dbConfig.connection, + ); + // todo: const key = ''; Object.assign(dbConfig, { @@ -744,6 +774,22 @@ export default class NcConfigFactory { return res; } + // mysql driver will cast mysql types into native JavaScript types by default + // hence we use typeCast to convert to the expected value (UTC) for date time fields + private static mysqlConnectionTypeCastConfig = { + typeCast: function (field, next) { + if (field.type === 'DATETIME' || field.type === 'TIMESTAMP') { + const d = dayjs(field.string()); + if (!d.isValid()) { + return null; + } + return d.utc().format('YYYY-MM-DD HH:mm:ss'); + } + return next(); + }, + timezone: '+00:00', + }; + // public static initOneClickDeployment() { // if (process.env.NC_ONE_CLICK) { // const url = NcConfigFactory.extractXcUrlFromJdbc(process.env.DATABASE_URL); From c1fa63951ba87beae51e235ba58428e352815889 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 17:58:20 +0800 Subject: [PATCH 045/233] fix(nocodb): mysql now() --- packages/nocodb-nest/src/meta/meta.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index fd2ad6665c..baaf7c33ad 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -1044,7 +1044,7 @@ export class MetaService { private now(): any { if (this.isMySQL()) { - return dayjs().utc().format('YYYY-MM-DD HH:mm:ss'); + return dayjs().format('YYYY-MM-DD HH:mm:ss'); } return dayjs().utc().format('YYYY-MM-DD HH:mm:ssZ'); } From 54fea15a5ac306f0dd33b2e7f92307b20c85a1a3 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 17:58:36 +0800 Subject: [PATCH 046/233] fix(nc-gui): timeAgo logic --- packages/nc-gui/utils/dateTimeUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/utils/dateTimeUtils.ts b/packages/nc-gui/utils/dateTimeUtils.ts index 803936ebdb..0ab64555ef 100644 --- a/packages/nc-gui/utils/dateTimeUtils.ts +++ b/packages/nc-gui/utils/dateTimeUtils.ts @@ -1,7 +1,11 @@ import dayjs from 'dayjs' +// show in local time export const timeAgo = (date: any) => { - return dayjs.utc(date).fromNow() + if (date.slice(-1) === 'Z') { + return dayjs(date).fromNow() + } + return dayjs(date).utc(true).local().fromNow() } export const dateFormats = [ From 66f0c20ea3ff1c10823394ee007affe9f7849007 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 18:41:14 +0800 Subject: [PATCH 047/233] fix(nc-gui): paste datetime logic for sqlite --- packages/nc-gui/composables/useMultiSelect/convertCellData.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 6dc76bcf91..65cfc7c08f 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -47,8 +47,6 @@ export default function convertCellData( if (isXcdbBase) { if (isMysql) { return parsedDateTime?.format('YYYY-MM-DD HH:mm:ss') - } else if (isSqlite) { - return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss') } else { return parsedDateTime.utc(true).format('YYYY-MM-DD HH:mm:ssZ') } From 61cc90a5ed06d56c785bc08f1e6aeb46c050aae6 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 19:10:30 +0800 Subject: [PATCH 048/233] feat(nc-gui): handle +00:00 as well --- packages/nc-gui/utils/dateTimeUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/utils/dateTimeUtils.ts b/packages/nc-gui/utils/dateTimeUtils.ts index 0ab64555ef..ad446a1937 100644 --- a/packages/nc-gui/utils/dateTimeUtils.ts +++ b/packages/nc-gui/utils/dateTimeUtils.ts @@ -2,7 +2,8 @@ import dayjs from 'dayjs' // show in local time export const timeAgo = (date: any) => { - if (date.slice(-1) === 'Z') { + // handle Z and +00:00 + if (date.slice(-1) === 'Z' || date.slice(-6) === '+00:00') { return dayjs(date).fromNow() } return dayjs(date).utc(true).local().fromNow() From fde0c682ea4baf8d966f033e0394bc6462d9f1eb Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Sat, 29 Apr 2023 17:20:14 +0530 Subject: [PATCH 049/233] test: timezone (PG) --- .../pages/Dashboard/ExpandedForm/index.ts | 8 + .../Dashboard/common/Cell/DateTimeCell.ts | 9 + .../pages/Dashboard/common/Cell/index.ts | 8 + tests/playwright/tests/timezone.spec.ts | 315 ++++++++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 tests/playwright/tests/timezone.spec.ts diff --git a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts index ce6b0f8660..b5b932fe7b 100644 --- a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts +++ b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts @@ -1,6 +1,7 @@ import { expect, Locator } from '@playwright/test'; import BasePage from '../../Base'; import { DashboardPage } from '..'; +import { DateTimeCellPageObject } from '../common/Cell/DateTimeCell'; export class ExpandedFormPage extends BasePage { readonly dashboard: DashboardPage; @@ -93,6 +94,13 @@ export class ExpandedFormPage extends BasePage { await field.locator(`[data-testid="nc-child-list-button-link-to"]`).click(); await this.dashboard.linkRecord.select(value); break; + case 'dateTime': + await field.locator('.nc-cell').click(); + // eslint-disable-next-line no-case-declarations + const dateTimeObj = new DateTimeCellPageObject(this.dashboard.grid.cell); + await dateTimeObj.selectDate({ date: value.slice(0, 10) }); + await dateTimeObj.selectTime({ hour: +value.slice(11, 13), minute: +value.slice(14, 16) }); + break; } } diff --git a/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts b/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts index 22c95925ae..dea4bcaa45 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts +++ b/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts @@ -75,4 +75,13 @@ export class DateTimeCellPageObject extends BasePage { async close() { await this.rootPage.keyboard.press('Escape'); } + + async setDateTime({ index, columnHeader, dateTime }: { index: number; columnHeader: string; dateTime: string }) { + const [date, time] = dateTime.split(' '); + const [hour, minute, second] = time.split(':'); + await this.open({ index, columnHeader }); + await this.selectDate({ date }); + await this.selectTime({ hour: +hour, minute: +minute }); + await this.save(); + } } diff --git a/tests/playwright/pages/Dashboard/common/Cell/index.ts b/tests/playwright/pages/Dashboard/common/Cell/index.ts index 1b287cb595..c0515715f8 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/index.ts +++ b/tests/playwright/pages/Dashboard/common/Cell/index.ts @@ -352,4 +352,12 @@ export class CellPageObject extends BasePage { await this.get({ index, columnHeader }).press((await this.isMacOs()) ? 'Meta+C' : 'Control+C'); await this.verifyToast({ message: 'Copied to clipboard' }); } + + async pasteFromClipboard({ index, columnHeader }: CellProps, ...clickOptions: Parameters) { + await this.get({ index, columnHeader }).scrollIntoViewIfNeeded(); + await this.get({ index, columnHeader }).click(...clickOptions); + await (await this.get({ index, columnHeader }).elementHandle()).waitForElementState('stable'); + + await this.get({ index, columnHeader }).press((await this.isMacOs()) ? 'Meta+V' : 'Control+V'); + } } diff --git a/tests/playwright/tests/timezone.spec.ts b/tests/playwright/tests/timezone.spec.ts new file mode 100644 index 0000000000..38111f398e --- /dev/null +++ b/tests/playwright/tests/timezone.spec.ts @@ -0,0 +1,315 @@ +import { expect, test } from '@playwright/test'; +import { DashboardPage } from '../pages/Dashboard'; +import setup from '../setup'; +import { Api, UITypes } from 'nocodb-sdk'; +let api: Api, records: any[]; + +const columns = [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + ai: 1, + pk: 1, + }, + { + column_name: 'DateTime', + title: 'DateTime', + uidt: UITypes.DateTime, + }, +]; + +const rowAttributes = [ + { Id: 1, DateTime: '2021-01-01 00:00:00' }, + { Id: 2, DateTime: '2021-01-01 04:00:00+04:00' }, + { Id: 3, DateTime: '2020-12-31 20:00:00-04:00' }, +]; + +test.describe('Timezone : Europe/Berlin', () => { + let dashboard: DashboardPage; + let context: any; + + test.beforeEach(async ({ page }) => { + context = await setup({ page, isEmptyProject: true }); + dashboard = new DashboardPage(page, context.project); + + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': context.token, + }, + }); + + try { + const project = await api.project.read(context.project.id); + const table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { + table_name: 'dateTimeTable', + title: 'dateTimeTable', + columns: columns, + }); + + await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes); + records = await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 10 }); + } catch (e) { + console.error(e); + } + + await page.reload(); + }); + + test.use({ + locale: 'de-DE', // Change to German locale + timezoneId: 'Europe/Berlin', + }); + + /* + * This test is to verify the display value of DateTime column in the grid + * when the timezone is set to Europe/Berlin + * + * The test inserts 3 rows using API + * 1. DateTime inserted without timezone + * 2. DateTime inserted with timezone (UTC+4) + * 3. DateTime inserted with timezone (UTC-4) + * + * Expected display values: + * Display value is converted to Europe/Berlin + */ + test('API insert, verify display value', async () => { + await dashboard.treeView.openTable({ title: 'dateTimeTable' }); + + // DateTime inserted using API without timezone is converted to UTC + // Display value is converted to Europe/Berlin + await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 01:00' }); + + // DateTime inserted using API with timezone is converted to UTC + // Display value is converted to Europe/Berlin + await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DateTime', value: '2021-01-01 01:00' }); + await dashboard.grid.cell.verifyDateCell({ index: 2, columnHeader: 'DateTime', value: '2021-01-01 01:00' }); + }); + + /* + * This test is to verify the API read response of DateTime column + * when the timezone is set to Europe/Berlin + * + * The test inserts 3 rows using API + * 1. DateTime inserted without timezone + * 2. DateTime inserted with timezone (UTC+4) + * 3. DateTime inserted with timezone (UTC-4) + * + * Expected API response: + * API response is in UTC + */ + + test('API Insert, verify API read response', async () => { + // UTC expected response + const dateUTC = ['2021-01-01 00:00:00', '2021-01-01 00:00:00', '2021-01-01 00:00:00']; + + const readDate = records.list.map(record => record.DateTime); + + // expect API response to be in UTC + expect(readDate).toEqual(dateUTC); + }); +}); + +// Change browser timezone & locale to Asia/Hong-Kong +// +test.describe('Timezone : Asia/Hong-kong', () => { + let dashboard: DashboardPage; + let context: any; + + test.beforeEach(async ({ page }) => { + context = await setup({ page, isEmptyProject: true }); + dashboard = new DashboardPage(page, context.project); + + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': context.token, + }, + }); + + try { + const project = await api.project.read(context.project.id); + const table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { + table_name: 'dateTimeTable', + title: 'dateTimeTable', + columns: columns, + }); + + await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes); + } catch (e) { + console.error(e); + } + + await page.reload(); + }); + + test.use({ + locale: 'zh-HK', + timezoneId: 'Asia/Hong_Kong', + }); + + /* + * This test is to verify the display value of DateTime column in the grid + * when the timezone is set to Asia/Hong-Kong + * + * The test inserts 3 rows using API + * 1. DateTime inserted without timezone + * 2. DateTime inserted with timezone (UTC+4) + * 3. DateTime inserted with timezone (UTC-4) + * + * Expected display values: + * Display value is converted to Asia/Hong-Kong + */ + test('API inserted, verify display value', async () => { + await dashboard.treeView.openTable({ title: 'dateTimeTable' }); + + // DateTime inserted using API without timezone is converted to UTC + // Display value is converted to Asia/Hong_Kong + await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 08:00' }); + + // DateTime inserted using API with timezone is converted to UTC + // Display value is converted to Asia/Hong_Kong + await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DateTime', value: '2021-01-01 08:00' }); + await dashboard.grid.cell.verifyDateCell({ index: 2, columnHeader: 'DateTime', value: '2021-01-01 08:00' }); + }); +}); + +test.describe('Timezone', () => { + let dashboard: DashboardPage; + let context: any; + + test.use({ + locale: 'zh-HK', + timezoneId: 'Asia/Hong_Kong', + }); + + test.beforeEach(async ({ page }) => { + context = await setup({ page, isEmptyProject: true }); + dashboard = new DashboardPage(page, context.project); + + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': context.token, + }, + }); + + // Using API for test preparation was not working + // Hence switched over to UI based table creation + + await dashboard.treeView.createTable({ title: 'dateTimeTable' }); + await dashboard.grid.column.create({ + title: 'DateTime', + type: 'DateTime', + dateFormat: 'YYYY-MM-DD', + timeFormat: 'HH:mm', + }); + + await dashboard.grid.cell.dateTime.setDateTime({ + index: 0, + columnHeader: 'DateTime', + dateTime: '2021-01-01 08:00:00', + }); + + // await dashboard.rootPage.reload(); + }); + + /* + * This test is to verify the display value & API response of DateTime column in the grid + * when the value inserted is from the UI + * + * Note: Timezone for this test is set as Asia/Hong-Kong + * + * 1. Create table with DateTime column + * 2. Insert DateTime value from UI '2021-01-01 08:00:00' + * 3. Verify display value : should be '2021-01-01 08:00:00' + * 4. Verify API response, expect UTC : should be '2021-01-01 00:00:00' + * + */ + test('Cell insert', async () => { + // Verify stored value in database is UTC + records = await api.dbTableRow.list('noco', context.project.id, 'dateTimeTable', { limit: 10 }); + const readDate = records.list[0].DateTime; + // skip seconds from readDate + // stored value expected to be in UTC + expect(readDate.slice(0, 16)).toEqual('2021-01-01 00:00'); + + // DateTime inserted from cell is converted to UTC & stored + // Display value is same as inserted value + await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 08:00' }); + }); + + /* + * This test is to verify the display value & API response of DateTime column in the grid + * when the value inserted is from expanded record + * + * Note: Timezone for this test is set as Asia/Hong-Kong + * + * 1. Create table with DateTime column + * 2. Insert DateTime value from UI '2021-01-01 08:00:00' + * 3. Expand record & update DateTime value to '2021-02-02 12:30:00' + * 4. Verify display value : should be '2021-02-02 12:30:00' + * 5. Verify API response, expect UTC : should be '2021-02-02 04:30:00' + * + */ + test('Expanded record insert', async () => { + await dashboard.grid.openExpandedRow({ index: 0 }); + await dashboard.expandedForm.fillField({ + columnTitle: 'DateTime', + value: '2021-02-02 12:30:00', + type: 'dateTime', + }); + await dashboard.expandedForm.save(); + + records = await api.dbTableRow.list('noco', context.project.id, 'dateTimeTable', { limit: 10 }); + const readDate = records.list[0].DateTime; + // skip seconds from readDate + // stored value expected to be in UTC + expect(readDate.slice(0, 16)).toEqual('2021-02-02 04:30'); + + // DateTime inserted from cell is converted to UTC & stored + // Display value is same as inserted value + await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 12:30' }); + }); + + /* + * This test is to verify the display value & API response of DateTime column in the grid + * when the value inserted is from copy and paste + * + * Note: Timezone for this test is set as Asia/Hong-Kong + * + * 1. Create table with DateTime column + * 2. Insert DateTime value from UI '2021-01-01 08:00:00' + * 3. Add new row & copy and paste DateTime value to '2021-01-01 08:00:00' + * 4. Verify display value : should be '2021-01-01 08:00:00' + * 5. Verify API response, expect UTC : should be '2021-01-01 00:00:00' + * + */ + test('Copy paste', async () => { + await dashboard.grid.addNewRow({ index: 1, columnHeader: 'Title', value: 'Copy paste test' }); + + await dashboard.rootPage.reload(); + await dashboard.rootPage.waitForTimeout(1000); + await dashboard.grid.cell.copyToClipboard( + { + index: 0, + columnHeader: 'DateTime', + }, + { position: { x: 1, y: 1 } } + ); + + expect(await dashboard.grid.cell.getClipboardText()).toBe('2021-01-01 08:00'); + await dashboard.grid.cell.pasteFromClipboard({ index: 1, columnHeader: 'DateTime' }); + + records = await api.dbTableRow.list('noco', context.project.id, 'dateTimeTable', { limit: 10 }); + const readDate = records.list[1].DateTime; + // skip seconds from readDate + // stored value expected to be in UTC + expect(readDate.slice(0, 16)).toEqual('2021-01-01 00:00'); + + // DateTime inserted from cell is converted to UTC & stored + // Display value is same as inserted value + await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DateTime', value: '2021-01-01 08:00' }); + }); +}); From ba77ca157cd400b1e788218523c37faa8ffefc33 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 19:51:32 +0800 Subject: [PATCH 050/233] feat(nc-gui): datetime logic for mssql --- packages/nc-gui/components/cell/DateTimePicker.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index c037166173..2311a38497 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -25,7 +25,7 @@ const { modelValue, isPk, isUpdateOutside } = defineProps() const emit = defineEmits(['update:modelValue']) -const { isMysql, isSqlite, isXcdbBase } = useProject() +const { isMssql, isMysql, isSqlite, isXcdbBase } = useProject() const { showNull } = useGlobal() @@ -58,6 +58,11 @@ let localState = $computed({ return undefined } + if (isMssql(column.value.base_id)) { + // e.g. 2023-04-29T11:41:53.000Z + return dayjs(modelValue) + } + // if cdf is defined, that means the value is auto-generated // hence, show the local time if (column?.value?.cdf) { From 966dd333b3d35a918895b6e9a00b1f792ef54788 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 19:55:30 +0800 Subject: [PATCH 051/233] feat(nc-gui): copy value for mssql --- .../nc-gui/composables/useMultiSelect/convertCellData.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 65cfc7c08f..1f333f0f4f 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -8,6 +8,7 @@ export default function convertCellData( args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo }, isMysql = false, isSqlite = false, + isMssql = false, isXcdbBase = false, ) { const { from, to, value } = args @@ -46,7 +47,9 @@ export default function convertCellData( } if (isXcdbBase) { if (isMysql) { - return parsedDateTime?.format('YYYY-MM-DD HH:mm:ss') + return parsedDateTime.format('YYYY-MM-DD HH:mm:ss') + } else if (isMssql) { + return parsedDateTime.format('YYYY-MM-DD HH:mm:ssZ') } else { return parsedDateTime.utc(true).format('YYYY-MM-DD HH:mm:ssZ') } From a3ff704060bcfdb3f76663287d8245bf6e5e3803 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 19:55:47 +0800 Subject: [PATCH 052/233] feat(nc-gui): copy value for mssql --- packages/nc-gui/composables/useMultiSelect/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 8e93a7b3c0..cae4768011 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -55,7 +55,7 @@ export function useMultiSelect( const { appInfo } = useGlobal() - const { isMysql, isSqlite, isXcdbBase } = useProject() + const { isMssql, isMysql, isSqlite, isXcdbBase } = useProject() let clipboardContext = $ref<{ value: any; uidt: UITypes } | null>(null) @@ -117,7 +117,12 @@ export function useMultiSelect( } if (columnObj.uidt === UITypes.DateTime) { - textToCopy = dayjs(textToCopy).utc(true).local().format(constructDateTimeFormat(columnObj)) + if (isMssql(meta.value?.base_id)) { + textToCopy = dayjs(textToCopy).format(constructDateTimeFormat(columnObj)) + } else { + textToCopy = dayjs(textToCopy).utc(true).local().format(constructDateTimeFormat(columnObj)) + } + if (!dayjs(textToCopy).isValid()) { throw new Error('Invalid Date') } @@ -323,6 +328,7 @@ export function useMultiSelect( }, isMysql(meta.value?.base_id), isSqlite(meta.value?.base_id), + isMssql(meta.value?.base_id), isXcdbBase(meta.value?.base_id), ) e.preventDefault() @@ -359,6 +365,7 @@ export function useMultiSelect( }, isMysql(meta.value?.base_id), isSqlite(meta.value?.base_id), + isMssql(meta.value?.base_id), isXcdbBase(meta.value?.base_id), ) e.preventDefault() From ee771628ebb8daec5e24541f5bcce447b4297a63 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 29 Apr 2023 19:56:12 +0800 Subject: [PATCH 053/233] chore(nocodb): lint --- packages/nocodb-nest/src/utils/NcConfigFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/utils/NcConfigFactory.ts b/packages/nocodb-nest/src/utils/NcConfigFactory.ts index 3914c185c3..0a773e31f8 100644 --- a/packages/nocodb-nest/src/utils/NcConfigFactory.ts +++ b/packages/nocodb-nest/src/utils/NcConfigFactory.ts @@ -3,9 +3,9 @@ import { URL } from 'url'; import { promisify } from 'util'; import * as path from 'path'; import parseDbUrl from 'parse-database-url'; -import { SqlClientFactory } from '../db/sql-client/lib/SqlClientFactory'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; +import { SqlClientFactory } from '../db/sql-client/lib/SqlClientFactory'; // import SqlClientFactory from '../db/sql-client/lib/SqlClientFactory'; // import type { // AuthConfig, From 35958678ae888fb03bfa3cea9b32badfaaae9d6f Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 13:09:08 +0800 Subject: [PATCH 054/233] fix(nocodb): handle timezone with offset in pg --- packages/nocodb-nest/src/models/Model.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/models/Model.ts b/packages/nocodb-nest/src/models/Model.ts index 818c3eb067..82e2224bc6 100644 --- a/packages/nocodb-nest/src/models/Model.ts +++ b/packages/nocodb-nest/src/models/Model.ts @@ -454,12 +454,21 @@ export default class Model implements TableType { if (col.uidt === UITypes.DateTime && dayjs(val).isValid()) { const { isMySQL, isSqlite } = clientMeta; if (base.is_meta) { + const d = new Date(val); if (isMySQL) { val = dayjs(val)?.format('YYYY-MM-DD HH:mm:ss'); } else if (isSqlite) { val = dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss'); } else { - val = dayjs(val).utc().format('YYYY-MM-DD HH:mm:ssZ'); + let keepLocalTime = false; + if (val.slice(-1) === 'Z') { + // from UI + keepLocalTime = true; + } + val = dayjs + .utc(val) + .utcOffset(d.getTimezoneOffset(), keepLocalTime) + .format('YYYY-MM-DD HH:mm:ssZ'); } } else { // TODO(timezone): keep ext db as it is From d15aec9d5890d57fb9ffc1899a93b88fbdd2473a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 13:29:12 +0800 Subject: [PATCH 055/233] fix(nocodb): handle timezone with offset in mysql --- packages/nocodb-nest/src/models/Model.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/models/Model.ts b/packages/nocodb-nest/src/models/Model.ts index 82e2224bc6..0921f1698f 100644 --- a/packages/nocodb-nest/src/models/Model.ts +++ b/packages/nocodb-nest/src/models/Model.ts @@ -456,7 +456,15 @@ export default class Model implements TableType { if (base.is_meta) { const d = new Date(val); if (isMySQL) { - val = dayjs(val)?.format('YYYY-MM-DD HH:mm:ss'); + if (val.slice(-1) === 'Z') { + // from UI + val = dayjs(val).format('YYYY-MM-DD HH:mm:ss'); + } else { + val = dayjs + .utc(val) + .utcOffset(d.getTimezoneOffset(), true) + .format('YYYY-MM-DD HH:mm:ss'); + } } else if (isSqlite) { val = dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss'); } else { From 7e51d3fbe7671db5ba9e62b2336b91ab9fd63f3b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 13:49:24 +0800 Subject: [PATCH 056/233] fix(nocodb): handle timezone with offset in sqlite --- packages/nocodb-nest/src/models/Model.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/nocodb-nest/src/models/Model.ts b/packages/nocodb-nest/src/models/Model.ts index 0921f1698f..ca5013c9d7 100644 --- a/packages/nocodb-nest/src/models/Model.ts +++ b/packages/nocodb-nest/src/models/Model.ts @@ -466,7 +466,15 @@ export default class Model implements TableType { .format('YYYY-MM-DD HH:mm:ss'); } } else if (isSqlite) { - val = dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss'); + let keepLocalTime = false; + if (val.slice(-1) === 'Z') { + // from UI + keepLocalTime = true; + } + val = dayjs + .utc(val) + .utcOffset(d.getTimezoneOffset(), keepLocalTime) + .format('YYYY-MM-DD HH:mm:ss'); } else { let keepLocalTime = false; if (val.slice(-1) === 'Z') { From 591ec3a5f92b2d4765004158dd6eea00efc83a75 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 16:36:14 +0800 Subject: [PATCH 057/233] fix(nc-gui): add 'Z' for mysql --- packages/nc-gui/composables/useMultiSelect/convertCellData.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 1f333f0f4f..3376aa570b 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -47,7 +47,8 @@ export default function convertCellData( } if (isXcdbBase) { if (isMysql) { - return parsedDateTime.format('YYYY-MM-DD HH:mm:ss') + // UTC + 'Z' + return parsedDateTime.format('YYYY-MM-DD HH:mm:ss') + 'Z' } else if (isMssql) { return parsedDateTime.format('YYYY-MM-DD HH:mm:ssZ') } else { From f3c92ad1cfc6b430b08b608a418882015a33e5aa Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 16:36:41 +0800 Subject: [PATCH 058/233] fix(nocodb): handle copy n paste datetime --- packages/nocodb-nest/src/models/Model.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/nocodb-nest/src/models/Model.ts b/packages/nocodb-nest/src/models/Model.ts index ca5013c9d7..40c7d98146 100644 --- a/packages/nocodb-nest/src/models/Model.ts +++ b/packages/nocodb-nest/src/models/Model.ts @@ -458,8 +458,11 @@ export default class Model implements TableType { if (isMySQL) { if (val.slice(-1) === 'Z') { // from UI + // e.g. 2023-05-02 08:09:43Z val = dayjs(val).format('YYYY-MM-DD HH:mm:ss'); } else { + // from API + // e.g. 2021-01-01 04:00:00+04:00 val = dayjs .utc(val) .utcOffset(d.getTimezoneOffset(), true) @@ -467,7 +470,7 @@ export default class Model implements TableType { } } else if (isSqlite) { let keepLocalTime = false; - if (val.slice(-1) === 'Z') { + if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { // from UI keepLocalTime = true; } @@ -477,7 +480,7 @@ export default class Model implements TableType { .format('YYYY-MM-DD HH:mm:ss'); } else { let keepLocalTime = false; - if (val.slice(-1) === 'Z') { + if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { // from UI keepLocalTime = true; } From 928666842df75317ad092a15c6a36366a925bb7d Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 17:36:45 +0800 Subject: [PATCH 059/233] fea(nocodb): pass isMssql & isPg --- packages/nocodb-nest/src/db/BaseModelSqlv2.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts index a09fbe69b5..44460389ae 100644 --- a/packages/nocodb-nest/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb-nest/src/db/BaseModelSqlv2.ts @@ -1943,8 +1943,8 @@ class BaseModelSqlv2 { public get clientMeta() { return { isSqlite: this.isSqlite, - // isMssql: this.isMssql, - // isPg: this.isPg, + isMssql: this.isMssql, + isPg: this.isPg, isMySQL: this.isMySQL, // isSnowflake: this.isSnowflake, }; From 3648f9dd9da6310e51a5dc3dc6b25bb1262e1d19 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 17:37:02 +0800 Subject: [PATCH 060/233] fix(nc-gui): mssql copy n paste issue --- packages/nc-gui/composables/useMultiSelect/convertCellData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 3376aa570b..bbaf0cd9a8 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -50,7 +50,7 @@ export default function convertCellData( // UTC + 'Z' return parsedDateTime.format('YYYY-MM-DD HH:mm:ss') + 'Z' } else if (isMssql) { - return parsedDateTime.format('YYYY-MM-DD HH:mm:ssZ') + return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') } else { return parsedDateTime.utc(true).format('YYYY-MM-DD HH:mm:ssZ') } From 759a48425ef863d930a94d4cb8370789990aac52 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 17:45:47 +0800 Subject: [PATCH 061/233] fix(nocodb): handle timezone with offset in mssql --- packages/nocodb-nest/src/models/Model.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/nocodb-nest/src/models/Model.ts b/packages/nocodb-nest/src/models/Model.ts index 40c7d98146..305fbceca0 100644 --- a/packages/nocodb-nest/src/models/Model.ts +++ b/packages/nocodb-nest/src/models/Model.ts @@ -437,6 +437,8 @@ export default class Model implements TableType { clientMeta = { isMySQL: false, isSqlite: false, + isMssql: false, + isPg: false, }, ) { const insertObj = {}; @@ -452,7 +454,7 @@ export default class Model implements TableType { val = JSON.stringify(val); } if (col.uidt === UITypes.DateTime && dayjs(val).isValid()) { - const { isMySQL, isSqlite } = clientMeta; + const { isMySQL, isSqlite, isMssql, isPg } = clientMeta; if (base.is_meta) { const d = new Date(val); if (isMySQL) { @@ -478,7 +480,7 @@ export default class Model implements TableType { .utc(val) .utcOffset(d.getTimezoneOffset(), keepLocalTime) .format('YYYY-MM-DD HH:mm:ss'); - } else { + } else if (isPg) { let keepLocalTime = false; if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { // from UI @@ -488,6 +490,19 @@ export default class Model implements TableType { .utc(val) .utcOffset(d.getTimezoneOffset(), keepLocalTime) .format('YYYY-MM-DD HH:mm:ssZ'); + } else if (isMssql) { + if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { + // from UI + val = dayjs + .utc(val) + .utcOffset(d.getTimezoneOffset(), false) + .format('YYYY-MM-DD HH:mm:ssZ'); + } else { + val = dayjs + .utc(val) + .utcOffset(d.getTimezoneOffset() * -1, true) + .format('YYYY-MM-DD HH:mm:ssZ'); + } } } else { // TODO(timezone): keep ext db as it is From 4fc02f670ef47eb7ccaa791221a2d60732fe3aee Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 19:52:21 +0800 Subject: [PATCH 062/233] feat(nocodb): upgrader for datetime --- .../version-upgrader/ncDateTimeUpgrader.ts | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 packages/nocodb-nest/src/version-upgrader/ncDateTimeUpgrader.ts diff --git a/packages/nocodb-nest/src/version-upgrader/ncDateTimeUpgrader.ts b/packages/nocodb-nest/src/version-upgrader/ncDateTimeUpgrader.ts new file mode 100644 index 0000000000..2529555da8 --- /dev/null +++ b/packages/nocodb-nest/src/version-upgrader/ncDateTimeUpgrader.ts @@ -0,0 +1,145 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { UITypes } from 'nocodb-sdk'; +import { MetaTable } from '../utils/globals'; +import Base from '../models/Base'; +import Model from '../models/Model'; +import { throwTimeoutError } from './ncUpgradeErrors'; +import type { BaseType } from 'nocodb-sdk'; +import type { NcUpgraderCtx } from './NcUpgrader'; +import type { XKnex } from '../db/CustomKnex'; +import type { Knex } from 'knex'; + +dayjs.extend(utc); + +function getTnPath(knex: XKnex, tb: Model) { + const schema = (knex as any).searchPath?.(); + const clientType = knex.clientType(); + if (clientType === 'mssql' && schema) { + return knex.raw('??.??', [schema, tb.table_name]).toQuery(); + } else if (clientType === 'snowflake') { + return [ + knex.client.config.connection.database, + knex.client.config.connection.schema, + tb.table_name, + ].join('.'); + } else { + return tb.table_name; + } +} + +export default async function ({ ncMeta }: NcUpgraderCtx) { + const bases: BaseType[] = await ncMeta.metaList2(null, null, MetaTable.BASES); + for (const _base of bases) { + const base = new Base(_base); + + // skip if the project_id is missing + if (!base.project_id) { + continue; + } + + const project = await ncMeta.metaGet2(null, null, MetaTable.PROJECT, { + id: base.project_id, + }); + + // skip if the project is missing + if (!project) { + continue; + } + + // skip if the base is not meta + if (!base.is_meta) { + continue; + } + + const isProjectDeleted = project.deleted; + + const knex: Knex = ncMeta.knexConnection; + + const models = await base.getModels(ncMeta); + + // used in timeout error message + const timeoutErrorInfo = { + projectTitle: project.title, + connection: knex.client.config.connection, + }; + + for (const model of models) { + try { + // if the table is missing in database, skip + if (!(await knex.schema.hasTable(getTnPath(knex, model)))) { + continue; + } + + const updateRecords = []; + + // get all date times columns + // and filter out the columns that are missing in database + const columns = await (await Model.get(model.id, ncMeta)) + .getColumns(ncMeta) + .then(async (columns) => { + const filteredColumns = []; + for (const column of columns) { + if (column.uidt !== UITypes.DateTime && !column.pk) continue; + if ( + !(await knex.schema.hasColumn( + getTnPath(knex, model), + column.column_name, + )) + ) + continue; + filteredColumns.push(column); + } + return filteredColumns; + }); + + const dateTimeColumns = columns + .filter((c) => c.uidt === UITypes.DateTime) + .map((c) => c.column_name); + + if (dateTimeColumns.length === 0) { + continue; + } + + const primaryKeys = columns + .filter((c) => c.pk) + .map((c) => c.column_name); + + const records = await knex(getTnPath(knex, model)).select(); + + for (const record of records) { + const where = primaryKeys + .map((key) => { + return { [key]: record[key] }; + }) + .reduce((acc, val) => Object.assign(acc, val), {}); + + for (const dateTimeColumn of dateTimeColumns) { + updateRecords.push( + await knex(getTnPath(knex, model)) + .update({ + [dateTimeColumn]: dayjs(record[dateTimeColumn]) + .utc() + .format( + knex.clientType().startsWith('mysql') + ? 'YYYY-MM-DD HH:mm:ss' + : 'YYYY-MM-DD HH:mm:ssZ', + ), + }) + .where(where), + ); + } + } + await Promise.all(updateRecords); + } catch (e) { + // ignore the error related to deleted project + if (!isProjectDeleted) { + // throw the custom timeout error message if applicable + throwTimeoutError(e, timeoutErrorInfo); + // throw general error + throw e; + } + } + } + } +} From d8111bdb398acd5f42f64a01ec2543f713ccda0e Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 19:52:29 +0800 Subject: [PATCH 063/233] feat(nocodb): add ncDateTimeUpgrader --- packages/nocodb-nest/src/version-upgrader/NcUpgrader.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nocodb-nest/src/version-upgrader/NcUpgrader.ts b/packages/nocodb-nest/src/version-upgrader/NcUpgrader.ts index c2d351dc2f..8d3afe8f94 100644 --- a/packages/nocodb-nest/src/version-upgrader/NcUpgrader.ts +++ b/packages/nocodb-nest/src/version-upgrader/NcUpgrader.ts @@ -13,6 +13,7 @@ import ncProjectUpgraderV2_0090000 from './ncProjectUpgraderV2_0090000'; import ncProjectEnvUpgrader0011045 from './ncProjectEnvUpgrader0011045'; import ncProjectEnvUpgrader from './ncProjectEnvUpgrader'; import ncHookUpgrader from './ncHookUpgrader'; +import ncDateTimeUpgrader from './ncDateTimeUpgrader'; import type { MetaService } from '../meta/meta.service'; import type { NcConfig } from '../interface/config'; @@ -48,6 +49,7 @@ export default class NcUpgrader { { name: '0105002', handler: ncStickyColumnUpgrader }, { name: '0105003', handler: ncFilterUpgrader_0105003 }, { name: '0105004', handler: ncHookUpgrader }, + { name: '0106001', handler: ncDateTimeUpgrader }, ]; if (!(await ctx.ncMeta.knexConnection?.schema?.hasTable?.('nc_store'))) { return; From 1e41e7f0cfee44be954c76fb082fb61bedb72a88 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 2 May 2023 19:52:37 +0800 Subject: [PATCH 064/233] feat(nocodb): bump to 0106001 --- packages/nocodb-nest/src/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nocodb-nest/src/app.module.ts b/packages/nocodb-nest/src/app.module.ts index 1307931d5d..fc9f8d136c 100644 --- a/packages/nocodb-nest/src/app.module.ts +++ b/packages/nocodb-nest/src/app.module.ts @@ -16,7 +16,7 @@ import { GlobalModule } from './modules/global/global.module'; import { LocalStrategy } from './strategies/local.strategy'; import { AuthTokenStrategy } from './strategies/authtoken.strategy/authtoken.strategy'; import { BaseViewStrategy } from './strategies/base-view.strategy/base-view.strategy'; -import NcConfigFactory from './utils/NcConfigFactory' +import NcConfigFactory from './utils/NcConfigFactory'; import NcUpgrader from './version-upgrader/NcUpgrader'; import { MetasModule } from './modules/metas/metas.module'; import NocoCache from './cache/NocoCache'; @@ -64,7 +64,7 @@ export class AppModule implements OnApplicationBootstrap { // app init async onApplicationBootstrap(): Promise { - process.env.NC_VERSION = '0105004'; + process.env.NC_VERSION = '0106001'; await NocoCache.init(); From b13a1e7231168ac05bd0479e3c07357442a274a8 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 4 May 2023 18:40:49 +0800 Subject: [PATCH 065/233] fix(nc-gui): show as it is in ext db --- packages/nc-gui/components/cell/DateTimePicker.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 2311a38497..0c8f41cd25 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -58,6 +58,11 @@ let localState = $computed({ return undefined } + // ext db + if (!isXcdbBase(column.value.base_id)) { + return /^\d+$/.test(modelValue) ? dayjs(+modelValue) : dayjs(modelValue) + } + if (isMssql(column.value.base_id)) { // e.g. 2023-04-29T11:41:53.000Z return dayjs(modelValue) From 96d986d80a247f0b4fa6ce42a2eec7c9b29514bb Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Thu, 4 May 2023 16:48:35 +0530 Subject: [PATCH 066/233] refactor: move test file inside db folder Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/tests/{ => db}/timezone.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/playwright/tests/{ => db}/timezone.spec.ts (99%) diff --git a/tests/playwright/tests/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts similarity index 99% rename from tests/playwright/tests/timezone.spec.ts rename to tests/playwright/tests/db/timezone.spec.ts index 38111f398e..eae44ccbd4 100644 --- a/tests/playwright/tests/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; -import { DashboardPage } from '../pages/Dashboard'; -import setup from '../setup'; +import { DashboardPage } from '../../pages/Dashboard'; +import setup from '../../setup'; import { Api, UITypes } from 'nocodb-sdk'; let api: Api, records: any[]; From b0c90e92434ce0b877bb848511ec1526ade7bbfa Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 4 May 2023 20:05:28 +0800 Subject: [PATCH 067/233] fix(nocodb): add local() for mysql api datetime --- packages/nocodb/src/models/Model.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts index 305fbceca0..6162410415 100644 --- a/packages/nocodb/src/models/Model.ts +++ b/packages/nocodb/src/models/Model.ts @@ -467,6 +467,7 @@ export default class Model implements TableType { // e.g. 2021-01-01 04:00:00+04:00 val = dayjs .utc(val) + .local() .utcOffset(d.getTimezoneOffset(), true) .format('YYYY-MM-DD HH:mm:ss'); } From 906db2dc9d1d2bd22f3a8811d68a4c5dbe47c6b0 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Thu, 4 May 2023 21:55:22 +0530 Subject: [PATCH 068/233] test: include extDb tests Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/package-lock.json | 264 ++++++++++++++++-- tests/playwright/package.json | 1 + tests/playwright/pages/Dashboard/TreeView.ts | 17 ++ .../Dashboard/common/Cell/DateTimeCell.ts | 20 +- tests/playwright/tests/db/timezone.spec.ts | 255 ++++++++++++++++- 5 files changed, 513 insertions(+), 44 deletions(-) diff --git a/tests/playwright/package-lock.json b/tests/playwright/package-lock.json index 18189b8a16..14cc95303a 100644 --- a/tests/playwright/package-lock.json +++ b/tests/playwright/package-lock.json @@ -12,6 +12,7 @@ "body-parser": "^1.20.1", "dayjs": "^1.11.7", "express": "^4.18.2", + "knex": "^2.4.2", "nocodb-sdk": "file:../../packages/nocodb-sdk", "xlsx": "^0.18.5" }, @@ -1097,14 +1098,12 @@ "node_modules/colorette": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, "node_modules/commander": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", - "dev": true, "engines": { "node": "^12.20.0 || >=14" } @@ -1408,6 +1407,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1780,6 +1787,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -2167,6 +2182,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -2204,6 +2227,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2482,6 +2510,14 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2534,7 +2570,6 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -2772,6 +2807,85 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "node_modules/knex": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz", + "integrity": "sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==", + "dependencies": { + "colorette": "2.0.19", + "commander": "^9.1.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.5.0", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "bin": { + "knex": "bin/cli.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/knex/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2907,8 +3021,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -3718,8 +3831,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { "version": "0.1.7", @@ -3764,8 +3876,7 @@ "node_modules/pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", - "dev": true + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -4093,6 +4204,17 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -4135,7 +4257,6 @@ "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, "dependencies": { "is-core-module": "^2.9.0", "path-parse": "^1.0.7", @@ -4609,7 +4730,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4655,6 +4775,14 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4667,6 +4795,14 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", + "engines": { + "node": ">=8" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5736,14 +5872,12 @@ "colorette": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, "commander": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", - "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", - "dev": true + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==" }, "concat-map": { "version": "0.0.1", @@ -5979,6 +6113,11 @@ "is-symbol": "^1.0.2" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -6241,6 +6380,11 @@ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" + }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -6541,6 +6685,11 @@ "has-symbols": "^1.0.3" } }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -6563,6 +6712,11 @@ "get-intrinsic": "^1.1.1" } }, + "getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6760,6 +6914,11 @@ "side-channel": "^1.0.4" } }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6794,7 +6953,6 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -6963,6 +7121,47 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "knex": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz", + "integrity": "sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==", + "requires": { + "colorette": "2.0.19", + "commander": "^9.1.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.5.0", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + } + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7059,8 +7258,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.merge": { "version": "4.6.2", @@ -7711,8 +7909,7 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-to-regexp": { "version": "0.1.7", @@ -7743,8 +7940,7 @@ "pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", - "dev": true + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, "pg-int8": { "version": "1.0.1", @@ -7983,6 +8179,14 @@ } } }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "requires": { + "resolve": "^1.20.0" + } + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -8010,7 +8214,6 @@ "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, "requires": { "is-core-module": "^2.9.0", "path-parse": "^1.0.7", @@ -8355,8 +8558,7 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "table": { "version": "6.8.1", @@ -8391,6 +8593,11 @@ } } }, + "tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8403,6 +8610,11 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==" + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 16b9708480..59befbff34 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -46,6 +46,7 @@ "body-parser": "^1.20.1", "dayjs": "^1.11.7", "express": "^4.18.2", + "knex": "^2.4.2", "nocodb-sdk": "file:../../packages/nocodb-sdk", "xlsx": "^0.18.5" } diff --git a/tests/playwright/pages/Dashboard/TreeView.ts b/tests/playwright/pages/Dashboard/TreeView.ts index 804cc1d2fe..29a42fc5e2 100644 --- a/tests/playwright/pages/Dashboard/TreeView.ts +++ b/tests/playwright/pages/Dashboard/TreeView.ts @@ -48,6 +48,23 @@ export class TreeViewPage extends BasePage { await this.get().locator(`.nc-project-tree-tbl-${title}`).focus(); } + async openBase({ title }: { title: string }) { + const nodes = await this.get().locator(`.ant-collapse`); + // loop through nodes.count() to find the node with title + for (let i = 0; i < (await nodes.count()); i++) { + const node = nodes.nth(i); + const nodeTitle = await node.innerText(); + // check if nodeTitle contains title + if (nodeTitle.includes(title)) { + // click on node + await node.click(); + break; + } + } + + await this.rootPage.waitForTimeout(1000); + } + // assumption: first view rendered is always GRID // async openTable({ diff --git a/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts b/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts index dea4bcaa45..2514afcd1d 100644 --- a/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts +++ b/tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts @@ -23,7 +23,7 @@ export class DateTimeCellPageObject extends BasePage { } async save() { - await this.rootPage.locator('button:has-text("Ok")').click(); + await this.rootPage.locator('button:has-text("Ok"):visible').click(); } async selectDate({ @@ -36,15 +36,15 @@ export class DateTimeCellPageObject extends BasePage { const [year, month, day] = date.split('-'); // configure year - await this.rootPage.locator('.ant-picker-year-btn').click(); + await this.rootPage.locator('.ant-picker-year-btn:visible').click(); await this.rootPage.locator(`td[title="${year}"]`).click(); // configure month - await this.rootPage.locator('.ant-picker-month-btn').click(); + await this.rootPage.locator('.ant-picker-month-btn:visible').click(); await this.rootPage.locator(`td[title="${year}-${month}"]`).click(); // configure day - await this.rootPage.locator(`td[title="${year}-${month}-${day}"]`).click(); + await this.rootPage.locator(`td[title="${year}-${month}-${day}"]:visible`).click(); } async selectTime({ @@ -60,14 +60,20 @@ export class DateTimeCellPageObject extends BasePage { second?: number | null; }) { await this.rootPage - .locator(`.ant-picker-time-panel-column:nth-child(1) > .ant-picker-time-panel-cell:nth-child(${hour + 1})`) + .locator( + `.ant-picker-time-panel-column:nth-child(1) > .ant-picker-time-panel-cell:nth-child(${hour + 1}):visible` + ) .click(); await this.rootPage - .locator(`.ant-picker-time-panel-column:nth-child(2) > .ant-picker-time-panel-cell:nth-child(${minute + 1})`) + .locator( + `.ant-picker-time-panel-column:nth-child(2) > .ant-picker-time-panel-cell:nth-child(${minute + 1}):visible` + ) .click(); if (second != null) { await this.rootPage - .locator(`.ant-picker-time-panel-column:nth-child(3) > .ant-picker-time-panel-cell:nth-child(${second + 1})`) + .locator( + `.ant-picker-time-panel-column:nth-child(3) > .ant-picker-time-panel-cell:nth-child(${second + 1}):visible` + ) .click(); } } diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index eae44ccbd4..e64f8fa28b 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from '@playwright/test'; import { DashboardPage } from '../../pages/Dashboard'; import setup from '../../setup'; +import { knex } from 'knex'; import { Api, UITypes } from 'nocodb-sdk'; let api: Api, records: any[]; @@ -25,7 +26,7 @@ const rowAttributes = [ { Id: 3, DateTime: '2020-12-31 20:00:00-04:00' }, ]; -test.describe('Timezone : Europe/Berlin', () => { +test.describe('Timezone : Japan/Tokyo', () => { let dashboard: DashboardPage; let context: any; @@ -57,14 +58,15 @@ test.describe('Timezone : Europe/Berlin', () => { await page.reload(); }); + // DST independent test test.use({ - locale: 'de-DE', // Change to German locale - timezoneId: 'Europe/Berlin', + locale: 'ja-JP', // Change to Japanese locale + timezoneId: 'Asia/Tokyo', // Set timezone to Tokyo timezone }); /* * This test is to verify the display value of DateTime column in the grid - * when the timezone is set to Europe/Berlin + * when the timezone is set to Asia/Tokyo * * The test inserts 3 rows using API * 1. DateTime inserted without timezone @@ -72,24 +74,24 @@ test.describe('Timezone : Europe/Berlin', () => { * 3. DateTime inserted with timezone (UTC-4) * * Expected display values: - * Display value is converted to Europe/Berlin + * Display value is converted to Asia/Tokyo */ test('API insert, verify display value', async () => { await dashboard.treeView.openTable({ title: 'dateTimeTable' }); // DateTime inserted using API without timezone is converted to UTC - // Display value is converted to Europe/Berlin - await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 01:00' }); + // Display value is converted to Asia/Tokyo + await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 09:00' }); // DateTime inserted using API with timezone is converted to UTC - // Display value is converted to Europe/Berlin - await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DateTime', value: '2021-01-01 01:00' }); - await dashboard.grid.cell.verifyDateCell({ index: 2, columnHeader: 'DateTime', value: '2021-01-01 01:00' }); + // Display value is converted to Asia/Tokyo + await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DateTime', value: '2021-01-01 09:00' }); + await dashboard.grid.cell.verifyDateCell({ index: 2, columnHeader: 'DateTime', value: '2021-01-01 09:00' }); }); /* * This test is to verify the API read response of DateTime column - * when the timezone is set to Europe/Berlin + * when the timezone is set to Asia/Tokyo * * The test inserts 3 rows using API * 1. DateTime inserted without timezone @@ -313,3 +315,234 @@ test.describe('Timezone', () => { await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DateTime', value: '2021-01-01 08:00' }); }); }); + +async function createTableWithDateTimeColumn(database: string) { + if (database === 'pg') { + const config = { + client: 'pg', + connection: { + host: 'localhost', + port: 5432, + user: 'postgres', + password: 'password', + database: 'postgres', + multipleStatements: true, + }, + searchPath: ['public', 'information_schema'], + pool: { min: 0, max: 5 }, + }; + + const config2 = { + ...config, + connection: { + ...config.connection, + database: 'datetimetable', + }, + }; + const pgknex = knex(config); + await pgknex.raw(`DROP DATABASE IF EXISTS dateTimeTable`); + await pgknex.raw(`CREATE DATABASE dateTimeTable`); + await pgknex.destroy(); + + const pgknex2 = knex(config2); + await pgknex2.raw(` + CREATE TABLE my_table ( + title SERIAL PRIMARY KEY, + datetime_without_tz TIMESTAMP WITHOUT TIME ZONE, + datetime_with_tz TIMESTAMP WITH TIME ZONE + ); + SET timezone = 'Asia/Hong_Kong'; + SELECT pg_sleep(1); + INSERT INTO my_table (datetime_without_tz, datetime_with_tz) + VALUES + ('2023-04-27 10:00:00', '2023-04-27 12:30:00'), + ('2023-04-27 10:00:00+05:30', '2023-04-27 10:00:00+05:30'); + `); + await pgknex2.destroy(); + } else if (database === 'mysql') { + const config = { + client: 'mysql', + connection: { + host: 'localhost', + port: 3306, + user: 'root', + password: 'password', + database: 'sakila', + }, + pool: { min: 0, max: 5 }, + }; + + const config2 = { + ...config, + connection: { + ...config.connection, + database: 'datetimetable', + }, + }; + + const mysqlknex = knex(config); + await mysqlknex.raw(`DROP DATABASE IF EXISTS dateTimeTable`); + await mysqlknex.raw(`CREATE DATABASE dateTimeTable`); + await mysqlknex.destroy(); + + const mysqlknex2 = knex(config2); + await mysqlknex2.raw(` + CREATE TABLE my_table ( + title INT AUTO_INCREMENT PRIMARY KEY, + datetime_without_tz DATETIME, + datetime_with_tz TIMESTAMP + ); + SET time_zone = '+08:00'; + SELECT sleep(1); + INSERT INTO my_table (datetime_without_tz, datetime_with_tz) + VALUES + ('2023-04-27 10:00:00', '2023-04-27 12:30:00'), + ('2023-04-27 10:00:00+05:30', '2023-04-27 10:00:00+05:30'); + `); + await mysqlknex2.destroy(); + } else if (database === 'sqlite') { + const config = { + client: 'sqlite3', + connection: { + filename: './mydb.sqlite3', + }, + useNullAsDefault: true, + pool: { min: 0, max: 5 }, + }; + + // SQLite supports just one type of datetime + // Timezone information, if specified is stored as is in the database + // https://www.sqlite.org/lang_datefunc.html + + const sqliteknex = knex(config); + await sqliteknex.raw(`DROP TABLE IF EXISTS my_table`); + await sqliteknex.raw(` + CREATE TABLE my_table ( + title INTEGER PRIMARY KEY AUTOINCREMENT, + datetime_without_tz DATETIME, + datetime_with_tz DATETIME + ) +`); + const datetimeData = [ + ['2023-04-27 10:00:00', '2023-04-27 10:00:00'], + ['2023-04-27 10:00:00+05:30', '2023-04-27 10:00:00+05:30'], + ]; + for (const data of datetimeData) { + await sqliteknex('my_table').insert({ + datetime_without_tz: data[0], + datetime_with_tz: data[1], + }); + } + await sqliteknex.destroy(); + } +} + +test.describe('External DB - DateTime column', async () => { + let dashboard: DashboardPage; + let context: any; + + test.use({ + locale: 'zh-HK', + timezoneId: 'Asia/Hong_Kong', + }); + + test.beforeEach(async ({ page }) => { + context = await setup({ page, isEmptyProject: true }); + dashboard = new DashboardPage(page, context.project); + + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': context.token, + }, + }); + + await createTableWithDateTimeColumn(context.dbType); + + await api.base.create(context.project.id, { + alias: 'datetimetable', + type: 'pg', + config: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + user: 'postgres', + password: 'password', + database: 'datetimetable', + }, + searchPath: ['public'], + }, + inflection_column: 'camelize', + inflection_table: 'camelize', + }); + + await dashboard.rootPage.reload(); + }); + + test('Verify display value, UI insert, API response', async () => { + await dashboard.treeView.openBase({ title: 'datetimetable' }); + await dashboard.treeView.openTable({ title: 'MyTable' }); + + // display value for datetime column without tz should be same as stored value + // display value for datetime column with tz should be converted to browser timezone (HK in this case) + await dashboard.grid.cell.verifyDateCell({ + index: 0, + columnHeader: 'DatetimeWithoutTz', + value: '2023-04-27 10:00', + }); + await dashboard.grid.cell.verifyDateCell({ + index: 1, + columnHeader: 'DatetimeWithoutTz', + value: '2023-04-27 10:00', + }); + await dashboard.grid.cell.verifyDateCell({ + index: 0, + columnHeader: 'DatetimeWithTz', + value: '2023-04-27 12:30', + }); + await dashboard.grid.cell.verifyDateCell({ + index: 1, + columnHeader: 'DatetimeWithTz', + value: '2023-04-27 12:30', + }); + + // Insert new row + await dashboard.grid.cell.dateTime.setDateTime({ + index: 2, + columnHeader: 'DatetimeWithoutTz', + dateTime: '2023-04-27 10:00:00', + }); + await dashboard.rootPage.waitForTimeout(1000); + await dashboard.grid.cell.dateTime.setDateTime({ + index: 2, + columnHeader: 'DatetimeWithTz', + dateTime: '2023-04-27 12:30:00', + }); + + // verify API response + // Note that, for UI inserted records - second part of datetime may be non-zero (though not shown in UI) + // Hence, we skip seconds from API response + // + const records = await api.dbTableRow.list('noco', context.project.id, 'MyTable', { limit: 10 }); + let dateTimeWithoutTz = records.list.map(record => record.DatetimeWithoutTz); + let dateTimeWithTz = records.list.map(record => record.DatetimeWithTz); + const expectedDateTimeWithoutTz = ['2023-04-27 10:00:00', '2023-04-27 10:00:00', '2023-04-27 10:00:00']; + const expectedDateTimeWithTz = ['2023-04-27T04:30:00.000Z', '2023-04-27T04:30:00.000Z', '2023-04-27T04:30:00.000Z']; + + dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { + const [datePart, timePart] = dateTimeStr.split(' '); + const updatedTimePart = timePart.split(':').slice(0, 2).join(':') + ':00'; + return `${datePart} ${updatedTimePart}`; + }); + + dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + return dateObj.toISOString(); + }); + + expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); + expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); + }); +}); From d80f0888dcd14ef331b530e9bc658057c943b07d Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 12:32:03 +0800 Subject: [PATCH 069/233] fix(nocodb): revise pg api logic --- packages/nocodb/src/models/Model.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts index 6162410415..bd34915ddd 100644 --- a/packages/nocodb/src/models/Model.ts +++ b/packages/nocodb/src/models/Model.ts @@ -482,14 +482,9 @@ export default class Model implements TableType { .utcOffset(d.getTimezoneOffset(), keepLocalTime) .format('YYYY-MM-DD HH:mm:ss'); } else if (isPg) { - let keepLocalTime = false; - if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { - // from UI - keepLocalTime = true; - } val = dayjs .utc(val) - .utcOffset(d.getTimezoneOffset(), keepLocalTime) + .utcOffset(d.getTimezoneOffset(), true) .format('YYYY-MM-DD HH:mm:ssZ'); } else if (isMssql) { if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { From 753e0d7a41a7b6ff08ae3d50ef2c199ed7296b59 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 12:54:53 +0800 Subject: [PATCH 070/233] fix(nocodb): revise sqlite api logic --- packages/nocodb/src/models/Model.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts index bd34915ddd..1c157b6f52 100644 --- a/packages/nocodb/src/models/Model.ts +++ b/packages/nocodb/src/models/Model.ts @@ -472,14 +472,9 @@ export default class Model implements TableType { .format('YYYY-MM-DD HH:mm:ss'); } } else if (isSqlite) { - let keepLocalTime = false; - if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { - // from UI - keepLocalTime = true; - } val = dayjs .utc(val) - .utcOffset(d.getTimezoneOffset(), keepLocalTime) + .utcOffset(d.getTimezoneOffset(), true) .format('YYYY-MM-DD HH:mm:ss'); } else if (isPg) { val = dayjs From f68ebba2653ed765f6b7bb50633682e5892701f3 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 13:18:26 +0800 Subject: [PATCH 071/233] fix(nocodb): revise mssql api logic --- packages/nocodb/src/models/Model.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts index 1c157b6f52..e62bce7899 100644 --- a/packages/nocodb/src/models/Model.ts +++ b/packages/nocodb/src/models/Model.ts @@ -482,18 +482,10 @@ export default class Model implements TableType { .utcOffset(d.getTimezoneOffset(), true) .format('YYYY-MM-DD HH:mm:ssZ'); } else if (isMssql) { - if (val.slice(-1) === 'Z' || val.slice(-6) === '+00:00') { - // from UI - val = dayjs - .utc(val) - .utcOffset(d.getTimezoneOffset(), false) - .format('YYYY-MM-DD HH:mm:ssZ'); - } else { - val = dayjs - .utc(val) - .utcOffset(d.getTimezoneOffset() * -1, true) - .format('YYYY-MM-DD HH:mm:ssZ'); - } + val = dayjs + .utc(val) + .utcOffset(d.getTimezoneOffset(), false) + .format('YYYY-MM-DD HH:mm:ssZ'); } } else { // TODO(timezone): keep ext db as it is From a948eaefa1f5f4c8948398ba797fb24a150f9486 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Fri, 5 May 2023 14:19:14 +0530 Subject: [PATCH 072/233] test: corrections for SQLite Xcdb Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- packages/nocodb/package.json | 2 + tests/playwright/package-lock.json | 2 +- .../pages/Dashboard/ExpandedForm/index.ts | 1 + tests/playwright/pages/Dashboard/TreeView.ts | 4 +- tests/playwright/tests/db/timezone.spec.ts | 122 +++++++++++------- 5 files changed, 78 insertions(+), 53 deletions(-) diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json index e5418572e2..e71f836e07 100644 --- a/packages/nocodb/package.json +++ b/packages/nocodb/package.json @@ -32,6 +32,8 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "watch:run": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", + "watch:run:mysql": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunMysql --log-error --project tsconfig.json\"", + "watch:run:pg": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"", "watch:run:playwright": "rm -f ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"", "watch:run:playwright:quick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"", "watch:run:playwright:pg:cyquick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"", diff --git a/tests/playwright/package-lock.json b/tests/playwright/package-lock.json index 14cc95303a..a9266da513 100644 --- a/tests/playwright/package-lock.json +++ b/tests/playwright/package-lock.json @@ -38,7 +38,7 @@ } }, "../../packages/nocodb-sdk": { - "version": "0.106.1", + "version": "0.107.0-beta.0", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^0.21.1", diff --git a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts index b5b932fe7b..8799e4f464 100644 --- a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts +++ b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts @@ -100,6 +100,7 @@ export class ExpandedFormPage extends BasePage { const dateTimeObj = new DateTimeCellPageObject(this.dashboard.grid.cell); await dateTimeObj.selectDate({ date: value.slice(0, 10) }); await dateTimeObj.selectTime({ hour: +value.slice(11, 13), minute: +value.slice(14, 16) }); + await dateTimeObj.save(); break; } } diff --git a/tests/playwright/pages/Dashboard/TreeView.ts b/tests/playwright/pages/Dashboard/TreeView.ts index 29a42fc5e2..169918f93d 100644 --- a/tests/playwright/pages/Dashboard/TreeView.ts +++ b/tests/playwright/pages/Dashboard/TreeView.ts @@ -103,7 +103,7 @@ export class TreeViewPage extends BasePage { } } - async createTable({ title, skipOpeningModal }: { title: string; skipOpeningModal?: boolean }) { + async createTable({ title, skipOpeningModal, mode }: { title: string; skipOpeningModal?: boolean; mode?: string }) { if (!skipOpeningModal) await this.get().locator('.nc-add-new-table').click(); await this.dashboard.get().locator('.nc-modal-table-create').locator('.ant-modal-body').waitFor(); @@ -118,7 +118,7 @@ export class TreeViewPage extends BasePage { }); // Tab render is slow for playwright - await this.dashboard.waitForTabRender({ title }); + await this.dashboard.waitForTabRender({ title, mode }); } async verifyTable({ title, index, exists = true }: { title: string; index?: number; exists?: boolean }) { diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index e64f8fa28b..178b0719f0 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -3,6 +3,8 @@ import { DashboardPage } from '../../pages/Dashboard'; import setup from '../../setup'; import { knex } from 'knex'; import { Api, UITypes } from 'nocodb-sdk'; +import { ProjectsPage } from '../../pages/ProjectsPage'; +import { isSqlite } from '../../setup/db'; let api: Api, records: any[]; const columns = [ @@ -26,6 +28,33 @@ const rowAttributes = [ { Id: 3, DateTime: '2020-12-31 20:00:00-04:00' }, ]; +async function timezoneSuite(token?: string, skipTableCreate?: boolean) { + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': token, + }, + }); + + const projectList = await api.project.list(); + for (const project of projectList.list) { + // delete project with title 'xcdb' if it exists + if (project.title === 'xcdb') { + await api.project.delete(project.id); + } + } + + const project = await api.project.create({ title: 'xcdb' }); + if (skipTableCreate) return { project }; + const table = await api.base.tableCreate(project.id, project.bases?.[0].id, { + table_name: 'dateTimeTable', + title: 'dateTimeTable', + columns: columns, + }); + + return { project, table }; +} + test.describe('Timezone : Japan/Tokyo', () => { let dashboard: DashboardPage; let context: any; @@ -33,24 +62,13 @@ test.describe('Timezone : Japan/Tokyo', () => { test.beforeEach(async ({ page }) => { context = await setup({ page, isEmptyProject: true }); dashboard = new DashboardPage(page, context.project); - - api = new Api({ - baseURL: `http://localhost:8080/`, - headers: { - 'xc-auth': context.token, - }, - }); + if (!isSqlite(context)) return; try { - const project = await api.project.read(context.project.id); - const table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { - table_name: 'dateTimeTable', - title: 'dateTimeTable', - columns: columns, - }); + const { project, table } = await timezoneSuite(context.token); - await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes); - records = await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 10 }); + await api.dbTableRow.bulkCreate('noco', project.id, table.id, rowAttributes); + records = await api.dbTableRow.list('noco', project.id, table.id, { limit: 10 }); } catch (e) { console.error(e); } @@ -77,6 +95,12 @@ test.describe('Timezone : Japan/Tokyo', () => { * Display value is converted to Asia/Tokyo */ test('API insert, verify display value', async () => { + if (!isSqlite(context)) return; + + await dashboard.clickHome(); + const projectsPage = new ProjectsPage(dashboard.rootPage); + await projectsPage.openProject({ title: 'xcdb', withoutPrefix: true }); + await dashboard.treeView.openTable({ title: 'dateTimeTable' }); // DateTime inserted using API without timezone is converted to UTC @@ -103,6 +127,8 @@ test.describe('Timezone : Japan/Tokyo', () => { */ test('API Insert, verify API read response', async () => { + if (!isSqlite(context)) return; + // UTC expected response const dateUTC = ['2021-01-01 00:00:00', '2021-01-01 00:00:00', '2021-01-01 00:00:00']; @@ -123,22 +149,9 @@ test.describe('Timezone : Asia/Hong-kong', () => { context = await setup({ page, isEmptyProject: true }); dashboard = new DashboardPage(page, context.project); - api = new Api({ - baseURL: `http://localhost:8080/`, - headers: { - 'xc-auth': context.token, - }, - }); - try { - const project = await api.project.read(context.project.id); - const table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { - table_name: 'dateTimeTable', - title: 'dateTimeTable', - columns: columns, - }); - - await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes); + const { project, table } = await timezoneSuite(context.token); + await api.dbTableRow.bulkCreate('noco', project.id, table.id, rowAttributes); } catch (e) { console.error(e); } @@ -164,6 +177,10 @@ test.describe('Timezone : Asia/Hong-kong', () => { * Display value is converted to Asia/Hong-Kong */ test('API inserted, verify display value', async () => { + await dashboard.clickHome(); + const projectsPage = new ProjectsPage(dashboard.rootPage); + await projectsPage.openProject({ title: 'xcdb', withoutPrefix: true }); + await dashboard.treeView.openTable({ title: 'dateTimeTable' }); // DateTime inserted using API without timezone is converted to UTC @@ -189,18 +206,19 @@ test.describe('Timezone', () => { test.beforeEach(async ({ page }) => { context = await setup({ page, isEmptyProject: true }); dashboard = new DashboardPage(page, context.project); + if (!isSqlite(context)) return; - api = new Api({ - baseURL: `http://localhost:8080/`, - headers: { - 'xc-auth': context.token, - }, - }); + const { project } = await timezoneSuite(context.token, true); + context.project = project; // Using API for test preparation was not working // Hence switched over to UI based table creation - await dashboard.treeView.createTable({ title: 'dateTimeTable' }); + await dashboard.clickHome(); + const projectsPage = new ProjectsPage(dashboard.rootPage); + await projectsPage.openProject({ title: 'xcdb', withoutPrefix: true }); + + await dashboard.treeView.createTable({ title: 'dateTimeTable', mode: 'Xcdb' }); await dashboard.grid.column.create({ title: 'DateTime', type: 'DateTime', @@ -230,8 +248,11 @@ test.describe('Timezone', () => { * */ test('Cell insert', async () => { + if (!isSqlite(context)) return; + // Verify stored value in database is UTC records = await api.dbTableRow.list('noco', context.project.id, 'dateTimeTable', { limit: 10 }); + const readDate = records.list[0].DateTime; // skip seconds from readDate // stored value expected to be in UTC @@ -256,6 +277,8 @@ test.describe('Timezone', () => { * */ test('Expanded record insert', async () => { + if (!isSqlite(context)) return; + await dashboard.grid.openExpandedRow({ index: 0 }); await dashboard.expandedForm.fillField({ columnTitle: 'DateTime', @@ -289,6 +312,8 @@ test.describe('Timezone', () => { * */ test('Copy paste', async () => { + if (!isSqlite(context)) return; + await dashboard.grid.addNewRow({ index: 1, columnHeader: 'Title', value: 'Copy paste test' }); await dashboard.rootPage.reload(); @@ -340,8 +365,8 @@ async function createTableWithDateTimeColumn(database: string) { }, }; const pgknex = knex(config); - await pgknex.raw(`DROP DATABASE IF EXISTS dateTimeTable`); - await pgknex.raw(`CREATE DATABASE dateTimeTable`); + await pgknex.raw(`DROP DATABASE IF EXISTS datetimetable`); + await pgknex.raw(`CREATE DATABASE datetimetable`); await pgknex.destroy(); const pgknex2 = knex(config2); @@ -361,7 +386,7 @@ async function createTableWithDateTimeColumn(database: string) { await pgknex2.destroy(); } else if (database === 'mysql') { const config = { - client: 'mysql', + client: 'mysql2', connection: { host: 'localhost', port: 3306, @@ -381,8 +406,8 @@ async function createTableWithDateTimeColumn(database: string) { }; const mysqlknex = knex(config); - await mysqlknex.raw(`DROP DATABASE IF EXISTS dateTimeTable`); - await mysqlknex.raw(`CREATE DATABASE dateTimeTable`); + await mysqlknex.raw(`DROP DATABASE IF EXISTS datetimetable`); + await mysqlknex.raw(`CREATE DATABASE datetimetable`); await mysqlknex.destroy(); const mysqlknex2 = knex(config2); @@ -392,8 +417,6 @@ async function createTableWithDateTimeColumn(database: string) { datetime_without_tz DATETIME, datetime_with_tz TIMESTAMP ); - SET time_zone = '+08:00'; - SELECT sleep(1); INSERT INTO my_table (datetime_without_tz, datetime_with_tz) VALUES ('2023-04-27 10:00:00', '2023-04-27 12:30:00'), @@ -437,7 +460,7 @@ async function createTableWithDateTimeColumn(database: string) { } } -test.describe('External DB - DateTime column', async () => { +test.describe.skip('External DB - DateTime column', async () => { let dashboard: DashboardPage; let context: any; @@ -461,17 +484,16 @@ test.describe('External DB - DateTime column', async () => { await api.base.create(context.project.id, { alias: 'datetimetable', - type: 'pg', + type: 'mysql2', config: { - client: 'pg', + client: 'mysql', connection: { host: 'localhost', - port: '5432', - user: 'postgres', + port: '3306', + user: 'root', password: 'password', database: 'datetimetable', }, - searchPath: ['public'], }, inflection_column: 'camelize', inflection_table: 'camelize', From 9147ff0668aa41846cd961963a811b241074b46e Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 17:12:31 +0800 Subject: [PATCH 073/233] fix(nc-gui): reorder the row meta set logic --- packages/nc-gui/components/smartsheet/Grid.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/Grid.vue b/packages/nc-gui/components/smartsheet/Grid.vue index 1fc290e4d9..134edc59d9 100644 --- a/packages/nc-gui/components/smartsheet/Grid.vue +++ b/packages/nc-gui/components/smartsheet/Grid.vue @@ -316,14 +316,14 @@ const { return } - // update/save cell value - await updateOrSaveRow(rowObj, ctx.updatedColumnTitle || columnObj.title) - // See DateTimePicker.vue for details - data.value[ctx.row].rowMeta.isUpdateOutside = { - ...data.value[ctx.row].rowMeta.isUpdateOutside, + data.value[ctx.row].rowMeta.isUpdatedFromCopyNPaste = { + ...data.value[ctx.row].rowMeta.isUpdatedFromCopyNPaste, [ctx.updatedColumnTitle || columnObj.title]: true, } + + // update/save cell value + await updateOrSaveRow(rowObj, ctx.updatedColumnTitle || columnObj.title) }, ) From 35231947425bfd9ac30bef4e46506ba4df89f5a0 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 17:12:42 +0800 Subject: [PATCH 074/233] refactor(nc-gui): isUpdateOutside -> isUpdatedFromCopyNPaste --- packages/nc-gui/lib/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/lib/types.ts b/packages/nc-gui/lib/types.ts index e011c39efb..5f158dbe3e 100644 --- a/packages/nc-gui/lib/types.ts +++ b/packages/nc-gui/lib/types.ts @@ -61,7 +61,7 @@ export interface Row { changed?: boolean saving?: boolean // use in datetime picker component - isUpdateOutside?: Record + isUpdatedFromCopyNPaste?: Record } } From 3d2a3c70902f5335c36abb1ee899e6803a907a4a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 17:12:55 +0800 Subject: [PATCH 075/233] refactor(nc-gui): isUpdateOutside -> isUpdatedFromCopyNPaste --- packages/nc-gui/components/smartsheet/Cell.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/components/smartsheet/Cell.vue b/packages/nc-gui/components/smartsheet/Cell.vue index 2ef824b933..2bdaf83de4 100644 --- a/packages/nc-gui/components/smartsheet/Cell.vue +++ b/packages/nc-gui/components/smartsheet/Cell.vue @@ -213,7 +213,7 @@ onUnmounted(() => { v-else-if="isDateTime(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" - :is-update-outside="currentRow.rowMeta.isUpdateOutside" + :is-updated-from-copy-n-paste="currentRow.rowMeta.isUpdatedFromCopyNPaste" /> From c69eaba40bdccfc45731aa68ea2cadd53a918441 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 17:14:12 +0800 Subject: [PATCH 076/233] fix(nc-gui): incorrect datetime value after saving in expanded form --- packages/nc-gui/components/cell/DateTimePicker.vue | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 0c8f41cd25..47e000f24f 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -18,10 +18,10 @@ import { interface Props { modelValue?: string | null isPk?: boolean - isUpdateOutside: Record + isUpdatedFromCopyNPaste: Record } -const { modelValue, isPk, isUpdateOutside } = defineProps() +const { modelValue, isPk, isUpdatedFromCopyNPaste } = defineProps() const emit = defineEmits(['update:modelValue']) @@ -78,7 +78,7 @@ let localState = $computed({ // when copying a datetime cell, the copied value would be local time // when pasting a datetime cell, UTC (xcdb) will be saved in DB // we convert back to local time - if (column.value.title! in (isUpdateOutside ?? {})) { + if (column.value.title! in (isUpdatedFromCopyNPaste ?? {})) { localModelValue = dayjs(modelValue).utc().local() return localModelValue } @@ -86,7 +86,11 @@ let localState = $computed({ // if localModelValue is defined, show localModelValue instead // localModelValue is set in setter below if (localModelValue) { - return localModelValue + const res = localModelValue + // resetting localModelValue here + // e.g. save in expanded form -> render the correct modelValue + localModelValue = undefined + return res } // empty cell - use modelValue in local time From 0ed7ceba04962d6c5ed5f6f79013151eed6f6d38 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 17:14:28 +0800 Subject: [PATCH 077/233] chore(nc-gui): lint --- packages/nc-gui/composables/useMultiSelect/convertCellData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index bbaf0cd9a8..2e2fea3fbf 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -48,7 +48,7 @@ export default function convertCellData( if (isXcdbBase) { if (isMysql) { // UTC + 'Z' - return parsedDateTime.format('YYYY-MM-DD HH:mm:ss') + 'Z' + return `${parsedDateTime.format('YYYY-MM-DD HH:mm:ss')}Z` } else if (isMssql) { return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') } else { From fd6e7449a95f9608ee764f44fac72a301fd1c838 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 5 May 2023 17:40:35 +0800 Subject: [PATCH 078/233] fix(nc-gui): handle copy n paste datetime for ext db --- packages/nc-gui/composables/useMultiSelect/index.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index cae4768011..0aa9f2fbeb 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -117,10 +117,14 @@ export function useMultiSelect( } if (columnObj.uidt === UITypes.DateTime) { - if (isMssql(meta.value?.base_id)) { - textToCopy = dayjs(textToCopy).format(constructDateTimeFormat(columnObj)) + if (isXcdbBase(meta.value?.base_id)) { + if (isMssql(meta.value?.base_id)) { + textToCopy = dayjs(textToCopy).format(constructDateTimeFormat(columnObj)) + } else { + textToCopy = dayjs(textToCopy).utc(true).local().format(constructDateTimeFormat(columnObj)) + } } else { - textToCopy = dayjs(textToCopy).utc(true).local().format(constructDateTimeFormat(columnObj)) + textToCopy = dayjs(textToCopy).format(constructDateTimeFormat(columnObj)) } if (!dayjs(textToCopy).isValid()) { From 644a0027581476e10f0d736cd037f88e43508294 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Fri, 5 May 2023 14:36:53 +0530 Subject: [PATCH 079/233] test: extDB for PG Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/tests/db/timezone.spec.ts | 54 +++++++++++++++------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index 178b0719f0..a21e42ea05 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -4,7 +4,7 @@ import setup from '../../setup'; import { knex } from 'knex'; import { Api, UITypes } from 'nocodb-sdk'; import { ProjectsPage } from '../../pages/ProjectsPage'; -import { isSqlite } from '../../setup/db'; +import { isMysql, isPg, isSqlite } from '../../setup/db'; let api: Api, records: any[]; const columns = [ @@ -460,7 +460,7 @@ async function createTableWithDateTimeColumn(database: string) { } } -test.describe.skip('External DB - DateTime column', async () => { +test.describe('External DB - DateTime column', async () => { let dashboard: DashboardPage; let context: any; @@ -482,22 +482,27 @@ test.describe.skip('External DB - DateTime column', async () => { await createTableWithDateTimeColumn(context.dbType); - await api.base.create(context.project.id, { - alias: 'datetimetable', - type: 'mysql2', - config: { - client: 'mysql', - connection: { - host: 'localhost', - port: '3306', - user: 'root', - password: 'password', - database: 'datetimetable', + if (isPg(context)) { + await api.base.create(context.project.id, { + alias: 'datetimetable', + type: 'pg', + config: { + client: 'pg', + connection: { + host: 'localhost', + port: '5432', + user: 'postgres', + password: 'password', + database: 'datetimetable', + }, + searchPath: ['public'], }, - }, - inflection_column: 'camelize', - inflection_table: 'camelize', - }); + inflection_column: 'camelize', + inflection_table: 'camelize', + }); + } else if (isMysql(context)) { + } else if (isSqlite(context)) { + } await dashboard.rootPage.reload(); }); @@ -542,6 +547,19 @@ test.describe.skip('External DB - DateTime column', async () => { dateTime: '2023-04-27 12:30:00', }); + // reload page & verify if inserted values are shown correctly + await dashboard.rootPage.reload(); + await dashboard.grid.cell.verifyDateCell({ + index: 2, + columnHeader: 'DatetimeWithoutTz', + value: '2023-04-27 10:00', + }); + await dashboard.grid.cell.verifyDateCell({ + index: 2, + columnHeader: 'DatetimeWithTz', + value: '2023-04-27 12:30', + }); + // verify API response // Note that, for UI inserted records - second part of datetime may be non-zero (though not shown in UI) // Hence, we skip seconds from API response @@ -552,12 +570,14 @@ test.describe.skip('External DB - DateTime column', async () => { const expectedDateTimeWithoutTz = ['2023-04-27 10:00:00', '2023-04-27 10:00:00', '2023-04-27 10:00:00']; const expectedDateTimeWithTz = ['2023-04-27T04:30:00.000Z', '2023-04-27T04:30:00.000Z', '2023-04-27T04:30:00.000Z']; + // reset seconds to 0 dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { const [datePart, timePart] = dateTimeStr.split(' '); const updatedTimePart = timePart.split(':').slice(0, 2).join(':') + ':00'; return `${datePart} ${updatedTimePart}`; }); + // reset seconds to 0 dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { const dateObj = new Date(dateTimeStr); dateObj.setSeconds(0); From 6f0b7ebb43f50884097b3b670b64c7a4008ccaed Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Fri, 5 May 2023 16:48:48 +0530 Subject: [PATCH 080/233] test: extDB corrections for mysql, pg Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/tests/db/timezone.spec.ts | 172 ++++++++++++++++++--- 1 file changed, 147 insertions(+), 25 deletions(-) diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index a21e42ea05..830ba880ba 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -393,6 +393,7 @@ async function createTableWithDateTimeColumn(database: string) { user: 'root', password: 'password', database: 'sakila', + multipleStatements: true, }, pool: { min: 0, max: 5 }, }; @@ -412,11 +413,13 @@ async function createTableWithDateTimeColumn(database: string) { const mysqlknex2 = knex(config2); await mysqlknex2.raw(` + USE datetimetable; CREATE TABLE my_table ( title INT AUTO_INCREMENT PRIMARY KEY, datetime_without_tz DATETIME, datetime_with_tz TIMESTAMP ); + SET time_zone = '+08:00'; INSERT INTO my_table (datetime_without_tz, datetime_with_tz) VALUES ('2023-04-27 10:00:00', '2023-04-27 12:30:00'), @@ -443,9 +446,7 @@ async function createTableWithDateTimeColumn(database: string) { CREATE TABLE my_table ( title INTEGER PRIMARY KEY AUTOINCREMENT, datetime_without_tz DATETIME, - datetime_with_tz DATETIME - ) -`); + datetime_with_tz DATETIME )`); const datetimeData = [ ['2023-04-27 10:00:00', '2023-04-27 10:00:00'], ['2023-04-27 10:00:00+05:30', '2023-04-27 10:00:00+05:30'], @@ -460,10 +461,28 @@ async function createTableWithDateTimeColumn(database: string) { } } -test.describe('External DB - DateTime column', async () => { +test.describe.skip('External DB - DateTime column', async () => { let dashboard: DashboardPage; let context: any; + const expectedDisplayValues = { + pg: { + DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 10:00'], + DatetimeWithTz: ['2023-04-27 12:30', '2023-04-27 12:30'], + }, + sqlite: { + // without +HH:MM information, display value is same as inserted value + // with +HH:MM information, display value is converted to browser timezone + // SQLite doesn't have with & without timezone fields; both are same in this case + DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 12:30'], + DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 12:30'], + }, + mysql: { + DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 12:30'], + DatetimeWithTz: ['2023-04-27 04:30', '2023-04-27 04:30'], + }, + }; + test.use({ locale: 'zh-HK', timezoneId: 'Asia/Hong_Kong', @@ -501,7 +520,40 @@ test.describe('External DB - DateTime column', async () => { inflection_table: 'camelize', }); } else if (isMysql(context)) { + await api.base.create(context.project.id, { + alias: 'datetimetable', + type: 'mysql2', + config: { + client: 'mysql2', + connection: { + host: 'localhost', + port: '3306', + user: 'root', + password: 'password', + database: 'datetimetable', + }, + }, + inflection_column: 'camelize', + inflection_table: 'camelize', + }); } else if (isSqlite(context)) { + await api.base.create(context.project.id, { + alias: 'datetimetable', + type: 'sqlite3', + config: { + client: 'sqlite3', + connection: { + client: 'sqlite3', + database: 'datetimetable', + connection: { + filename: '../../tests/playwright/mydb.sqlite3', + }, + useNullAsDefault: true, + }, + }, + inflection_column: 'camelize', + inflection_table: 'camelize', + }); } await dashboard.rootPage.reload(); @@ -516,22 +568,22 @@ test.describe('External DB - DateTime column', async () => { await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DatetimeWithoutTz', - value: '2023-04-27 10:00', + value: expectedDisplayValues[context.dbType].DatetimeWithoutTz[0], }); await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DatetimeWithoutTz', - value: '2023-04-27 10:00', + value: expectedDisplayValues[context.dbType].DatetimeWithoutTz[1], }); await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DatetimeWithTz', - value: '2023-04-27 12:30', + value: expectedDisplayValues[context.dbType].DatetimeWithTz[0], }); await dashboard.grid.cell.verifyDateCell({ index: 1, columnHeader: 'DatetimeWithTz', - value: '2023-04-27 12:30', + value: expectedDisplayValues[context.dbType].DatetimeWithTz[1], }); // Insert new row @@ -564,27 +616,97 @@ test.describe('External DB - DateTime column', async () => { // Note that, for UI inserted records - second part of datetime may be non-zero (though not shown in UI) // Hence, we skip seconds from API response // + const records = await api.dbTableRow.list('noco', context.project.id, 'MyTable', { limit: 10 }); let dateTimeWithoutTz = records.list.map(record => record.DatetimeWithoutTz); let dateTimeWithTz = records.list.map(record => record.DatetimeWithTz); - const expectedDateTimeWithoutTz = ['2023-04-27 10:00:00', '2023-04-27 10:00:00', '2023-04-27 10:00:00']; - const expectedDateTimeWithTz = ['2023-04-27T04:30:00.000Z', '2023-04-27T04:30:00.000Z', '2023-04-27T04:30:00.000Z']; - - // reset seconds to 0 - dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { - const [datePart, timePart] = dateTimeStr.split(' '); - const updatedTimePart = timePart.split(':').slice(0, 2).join(':') + ':00'; - return `${datePart} ${updatedTimePart}`; - }); - // reset seconds to 0 - dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - return dateObj.toISOString(); - }); + if (isPg(context)) { + const expectedDateTimeWithoutTz = [ + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 02:00:00 GMT', + ]; + const expectedDateTimeWithTz = [ + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 04:30:00 GMT', + ]; + + // convert to ISO string, skip seconds part or reset seconds to 00 + dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + return dateObj.toUTCString(); + }); + dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + return dateObj.toUTCString(); + }); + + console.log(dateTimeWithoutTz); + console.log(dateTimeWithTz); + expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); + expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); + } else if (isMysql(context)) { + const expectedDateTimeWithoutTz = [ + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 07:00:00 GMT', + 'Thu, 27 Apr 2023 02:00:00 GMT', + ]; + const expectedDateTimeWithTz = [ + 'Wed, 26 Apr 2023 23:00:00 GMT', + 'Wed, 26 Apr 2023 23:00:00 GMT', + 'Thu, 27 Apr 2023 04:30:00 GMT', + ]; + + // convert to ISO string, skip seconds part or reset seconds to 00 + dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + return dateObj.toUTCString(); + }); + dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + return dateObj.toUTCString(); + }); + + console.log(dateTimeWithoutTz); + console.log(dateTimeWithTz); - expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); - expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); + expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); + expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); + } else if (isSqlite(context)) { + const expectedDateTimeWithoutTz = [ + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 02:00:00 GMT', + ]; + const expectedDateTimeWithTz = [ + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 04:30:00 GMT', + 'Thu, 27 Apr 2023 04:30:00 GMT', + ]; + + // convert to ISO string, skip seconds part or reset seconds to 00 + dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + return dateObj.toUTCString(); + }); + dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + return dateObj.toUTCString(); + }); + + console.log(dateTimeWithoutTz); + console.log(dateTimeWithTz); + + expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); + expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); + } }); }); From 3a12c94af73d3a562b22a2e8afbc67e07d1a7328 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Fri, 5 May 2023 20:44:07 +0530 Subject: [PATCH 081/233] fix: typo during validation --- tests/playwright/tests/db/timezone.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index 830ba880ba..86b05c53b4 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -295,7 +295,7 @@ test.describe('Timezone', () => { // DateTime inserted from cell is converted to UTC & stored // Display value is same as inserted value - await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-01-01 12:30' }); + await dashboard.grid.cell.verifyDateCell({ index: 0, columnHeader: 'DateTime', value: '2021-02-02 12:30' }); }); /* From 11e27ca4989e986d2134f4fe3795152b5dba9d67 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 6 May 2023 13:24:41 +0800 Subject: [PATCH 082/233] fix(nc-gui): revise paste datetime logic --- .../useMultiSelect/convertCellData.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 2e2fea3fbf..5804107d09 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -47,12 +47,22 @@ export default function convertCellData( } if (isXcdbBase) { if (isMysql) { - // UTC + 'Z' - return `${parsedDateTime.format('YYYY-MM-DD HH:mm:ss')}Z` + let res = `${parsedDateTime.format('YYYY-MM-DD HH:mm:ss')}` + if (!dayjs.isDayjs(value)) { + // UTC + 'Z' + res += 'Z' + } + return res } else if (isMssql) { return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') } else { - return parsedDateTime.utc(true).format('YYYY-MM-DD HH:mm:ssZ') + if (!dayjs.isDayjs(value)) { + // e.g. copy the existing cell - 2023-05-06 13:06:51 (UTC) + return parsedDateTime.utc(true).format('YYYY-MM-DD HH:mm:ssZ') + } + // e.g. copy right after setting by datepicker + // value includes timezone + return parsedDateTime } } // TODO(timezone): keep ext db as it is From 7b6c780c0a237f7d78697c9f82f67c289b7600dc Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 6 May 2023 13:24:55 +0800 Subject: [PATCH 083/233] fix(nc-gui): revise copy datetime logic --- packages/nc-gui/composables/useMultiSelect/index.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 0aa9f2fbeb..09e4903bed 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -117,16 +117,21 @@ export function useMultiSelect( } if (columnObj.uidt === UITypes.DateTime) { + let d = dayjs(textToCopy) + if (!d.isValid()) { + // e.g. textToCopy = 2023-05-06T05:12:29.000Z + // feed custom parse format + d = dayjs(textToCopy, isMysql(columnObj.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') + } if (isXcdbBase(meta.value?.base_id)) { if (isMssql(meta.value?.base_id)) { - textToCopy = dayjs(textToCopy).format(constructDateTimeFormat(columnObj)) + textToCopy = d.format(constructDateTimeFormat(columnObj)) } else { - textToCopy = dayjs(textToCopy).utc(true).local().format(constructDateTimeFormat(columnObj)) + textToCopy = d.utc(true).local().format(constructDateTimeFormat(columnObj)) } } else { - textToCopy = dayjs(textToCopy).format(constructDateTimeFormat(columnObj)) + textToCopy = d.format(constructDateTimeFormat(columnObj)) } - if (!dayjs(textToCopy).isValid()) { throw new Error('Invalid Date') } From 9c3fb728147a10a0a6ea0eef9d335b7dd8b9e43b Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Sat, 6 May 2023 12:13:19 +0530 Subject: [PATCH 084/233] test: setup required packages --- tests/playwright/package-lock.json | 1673 ++++++++++++++++++++++++++-- tests/playwright/package.json | 1 + 2 files changed, 1555 insertions(+), 119 deletions(-) diff --git a/tests/playwright/package-lock.json b/tests/playwright/package-lock.json index a9266da513..2205b90373 100644 --- a/tests/playwright/package-lock.json +++ b/tests/playwright/package-lock.json @@ -14,6 +14,7 @@ "express": "^4.18.2", "knex": "^2.4.2", "nocodb-sdk": "file:../../packages/nocodb-sdk", + "sqlite3": "^5.1.6", "xlsx": "^0.18.5" }, "devDependencies": { @@ -226,6 +227,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -269,6 +276,25 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -304,6 +330,30 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@playwright/test": { "version": "1.27.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.27.1.tgz", @@ -320,6 +370,15 @@ "node": ">=14" } }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -576,8 +635,7 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/accepts": { "version": "1.3.8", @@ -620,11 +678,80 @@ "node": ">=0.8" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "optional": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/agentkeepalive/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agentkeepalive/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "devOptional": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -689,7 +816,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -709,6 +835,36 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -808,8 +964,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/body-parser": { "version": "1.20.1", @@ -849,7 +1004,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -884,6 +1038,35 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -933,11 +1116,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -1095,6 +1286,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorette": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", @@ -1111,14 +1310,12 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -1244,8 +1441,7 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "node_modules/denque": { "version": "2.1.0", @@ -1273,6 +1469,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1320,8 +1524,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -1331,6 +1534,15 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -1343,6 +1555,21 @@ "node": ">=8.6" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "node_modules/es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", @@ -2116,11 +2343,21 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/function-bind": { "version": "1.1.1", @@ -2160,6 +2397,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -2236,7 +2492,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2299,6 +2554,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "optional": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -2369,8 +2630,13 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true }, "node_modules/http-errors": { "version": "2.0.0", @@ -2387,6 +2653,76 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/human-signals": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", @@ -2396,6 +2732,15 @@ "node": ">=12.20.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/husky": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", @@ -2415,7 +2760,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, + "devOptional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -2461,7 +2806,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8.19" } @@ -2470,16 +2815,21 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2518,6 +2868,12 @@ "node": ">= 0.10" } }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "optional": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2605,7 +2961,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -2622,6 +2977,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -2756,7 +3117,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "devOptional": true }, "node_modules/js-tokens": { "version": "4.0.0", @@ -3077,7 +3438,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3085,6 +3445,55 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3180,7 +3589,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3197,18 +3605,117 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/mysql2": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", - "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", - "dev": true, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dependencies": { - "denque": "^2.0.1", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mysql2": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", + "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "dev": true, + "dependencies": { + "denque": "^2.0.1", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", "long": "^4.0.0", @@ -3311,6 +3818,115 @@ "resolved": "../../packages/nocodb-sdk", "link": true }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -3540,6 +4156,20 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3602,6 +4232,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -3615,7 +4256,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3704,7 +4344,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3773,7 +4412,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, + "devOptional": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -3814,7 +4453,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4053,6 +4691,25 @@ "node": ">=0.4.0" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/promised-sqlite3": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/promised-sqlite3/-/promised-sqlite3-1.2.0.tgz", @@ -4315,6 +4972,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4335,7 +5001,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -4432,7 +5097,6 @@ "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -4489,8 +5153,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -4534,8 +5197,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/slash": { "version": "3.0.0", @@ -4563,6 +5225,67 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "optional": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, "node_modules/split2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", @@ -4578,6 +5301,28 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/sqlite3": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz", + "integrity": "sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^4.2.0", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -4598,6 +5343,18 @@ "node": ">=0.8" } }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4610,7 +5367,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -4618,8 +5374,7 @@ "node_modules/string_decoder/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/string-argv": { "version": "0.3.1", @@ -4634,7 +5389,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4676,7 +5430,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4775,6 +5528,30 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/tar": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz", + "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/tarn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", @@ -4823,6 +5600,11 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -4921,6 +5703,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4941,8 +5741,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -5003,11 +5802,25 @@ "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==", "dev": true }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -5038,7 +5851,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -5088,8 +5900,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/xlsx": { "version": "0.18.5", @@ -5123,8 +5934,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "2.2.2", @@ -5267,6 +6077,12 @@ } } }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -5301,6 +6117,22 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5327,6 +6159,26 @@ "fastq": "^1.6.0" } }, + "@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "requires": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "optional": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, "@playwright/test": { "version": "1.27.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.27.1.tgz", @@ -5337,6 +6189,12 @@ "playwright-core": "1.27.1" } }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -5501,8 +6359,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.8", @@ -5531,11 +6388,62 @@ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==" }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "optional": true, + "requires": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "devOptional": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -5579,8 +6487,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -5591,6 +6498,32 @@ "color-convert": "^2.0.1" } }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -5666,8 +6599,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "body-parser": { "version": "1.20.1", @@ -5702,7 +6634,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5728,6 +6659,32 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "requires": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -5762,11 +6719,16 @@ "supports-color": "^7.1.0" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "devOptional": true }, "cli-cursor": { "version": "3.1.0", @@ -5869,6 +6831,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "colorette": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", @@ -5882,14 +6849,12 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "content-disposition": { "version": "0.5.4", @@ -5987,8 +6952,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "denque": { "version": "2.1.0", @@ -6006,6 +6970,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -6044,14 +7013,22 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -6061,6 +7038,18 @@ "ansi-colors": "^4.1.1" } }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true + }, "es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", @@ -6631,11 +7620,18 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "function-bind": { "version": "1.1.1", @@ -6666,6 +7662,22 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -6721,7 +7733,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6763,6 +7774,12 @@ "slash": "^3.0.0" } }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "optional": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6809,8 +7826,13 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true }, "http-errors": { "version": "2.0.0", @@ -6824,12 +7846,73 @@ "toidentifier": "1.0.1" } }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "human-signals": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", "dev": true }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, + "requires": { + "ms": "^2.0.0" + } + }, "husky": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", @@ -6840,7 +7923,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, + "devOptional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } @@ -6874,19 +7957,24 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true + "devOptional": true }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "devOptional": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -6919,6 +8007,12 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==" }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "optional": true + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6975,8 +8069,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.3", @@ -6987,6 +8080,12 @@ "is-extglob": "^2.1.1" } }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -7076,7 +8175,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "devOptional": true }, "js-tokens": { "version": "4.0.0", @@ -7307,11 +8406,49 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "optional": true, + "requires": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7377,7 +8514,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7388,6 +8524,76 @@ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "optional": true, + "requires": { + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7503,6 +8709,88 @@ "typescript": "^4.0.2" } }, + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "optional": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "optional": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "optional": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -7697,6 +8985,14 @@ } } }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7746,6 +9042,17 @@ } } }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -7755,8 +9062,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { "version": "1.12.2", @@ -7815,7 +9121,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -7869,7 +9174,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, + "devOptional": true, "requires": { "aggregate-error": "^3.0.0" } @@ -7897,8 +9202,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -8061,6 +9365,22 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, "promised-sqlite3": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/promised-sqlite3/-/promised-sqlite3-1.2.0.tgz", @@ -8253,6 +9573,12 @@ } } }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -8269,7 +9595,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -8331,7 +9656,6 @@ "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -8376,8 +9700,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "setprototypeof": { "version": "1.2.0", @@ -8412,8 +9735,7 @@ "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "slash": { "version": "3.0.0", @@ -8432,6 +9754,50 @@ "is-fullwidth-code-point": "^3.0.0" } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "optional": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "optional": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "split2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", @@ -8444,6 +9810,17 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "sqlite3": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz", + "integrity": "sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^4.2.0", + "node-gyp": "8.x", + "tar": "^6.1.11" + } + }, "sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -8458,6 +9835,15 @@ "frac": "~1.1.2" } }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "requires": { + "minipass": "^3.1.1" + } + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -8467,7 +9853,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" }, @@ -8475,8 +9860,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -8490,7 +9874,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8523,7 +9906,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -8593,6 +9975,26 @@ } } }, + "tar": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz", + "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + } + } + }, "tarn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", @@ -8629,6 +10031,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -8699,6 +10106,24 @@ "which-boxed-primitive": "^1.0.2" } }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8716,8 +10141,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "utils-merge": { "version": "1.0.1", @@ -8772,11 +10196,25 @@ "integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==", "dev": true }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "requires": { "isexe": "^2.0.0" } @@ -8798,7 +10236,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, "requires": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -8833,8 +10270,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "xlsx": { "version": "0.18.5", @@ -8859,8 +10295,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "2.2.2", diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 59befbff34..2c21f0029c 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -48,6 +48,7 @@ "express": "^4.18.2", "knex": "^2.4.2", "nocodb-sdk": "file:../../packages/nocodb-sdk", + "sqlite3": "^5.1.6", "xlsx": "^0.18.5" } } From 160fa61b22993d5cd271d43903fe5072e2f3778c Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Sat, 6 May 2023 12:18:57 +0530 Subject: [PATCH 085/233] test: extDB corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/pages/Dashboard/TreeView.ts | 5 +- tests/playwright/tests/db/timezone.spec.ts | 233 +++++-------------- tests/playwright/tests/utils/config.ts | 48 ++++ 3 files changed, 115 insertions(+), 171 deletions(-) create mode 100644 tests/playwright/tests/utils/config.ts diff --git a/tests/playwright/pages/Dashboard/TreeView.ts b/tests/playwright/pages/Dashboard/TreeView.ts index 169918f93d..f294ec8f7b 100644 --- a/tests/playwright/pages/Dashboard/TreeView.ts +++ b/tests/playwright/pages/Dashboard/TreeView.ts @@ -57,12 +57,13 @@ export class TreeViewPage extends BasePage { // check if nodeTitle contains title if (nodeTitle.includes(title)) { // click on node + await node.waitFor({ state: 'visible' }); await node.click(); break; } } - await this.rootPage.waitForTimeout(1000); + await this.rootPage.waitForTimeout(5000); } // assumption: first view rendered is always GRID @@ -89,6 +90,8 @@ export class TreeViewPage extends BasePage { } } + await this.get().locator(`.nc-project-tree-tbl-${title}`).waitFor({ state: 'visible' }); + if (networkResponse === true) { await this.waitForResponse({ uiAction: () => this.get().locator(`.nc-project-tree-tbl-${title}`).click(), diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index 86b05c53b4..d58c3a2434 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -5,6 +5,7 @@ import { knex } from 'knex'; import { Api, UITypes } from 'nocodb-sdk'; import { ProjectsPage } from '../../pages/ProjectsPage'; import { isMysql, isPg, isSqlite } from '../../setup/db'; +import { getKnexConfig } from '../utils/config'; let api: Api, records: any[]; const columns = [ @@ -343,27 +344,9 @@ test.describe('Timezone', () => { async function createTableWithDateTimeColumn(database: string) { if (database === 'pg') { - const config = { - client: 'pg', - connection: { - host: 'localhost', - port: 5432, - user: 'postgres', - password: 'password', - database: 'postgres', - multipleStatements: true, - }, - searchPath: ['public', 'information_schema'], - pool: { min: 0, max: 5 }, - }; - - const config2 = { - ...config, - connection: { - ...config.connection, - database: 'datetimetable', - }, - }; + const config = getKnexConfig({ dbName: 'postgres', dbType: 'pg' }); + const config2 = getKnexConfig({ dbName: 'datetimetable', dbType: 'pg' }); + const pgknex = knex(config); await pgknex.raw(`DROP DATABASE IF EXISTS datetimetable`); await pgknex.raw(`CREATE DATABASE datetimetable`); @@ -376,35 +359,17 @@ async function createTableWithDateTimeColumn(database: string) { datetime_without_tz TIMESTAMP WITHOUT TIME ZONE, datetime_with_tz TIMESTAMP WITH TIME ZONE ); - SET timezone = 'Asia/Hong_Kong'; - SELECT pg_sleep(1); + -- SET timezone = 'Asia/Hong_Kong'; + -- SELECT pg_sleep(1); INSERT INTO my_table (datetime_without_tz, datetime_with_tz) VALUES - ('2023-04-27 10:00:00', '2023-04-27 12:30:00'), + ('2023-04-27 10:00:00', '2023-04-27 10:00:00'), ('2023-04-27 10:00:00+05:30', '2023-04-27 10:00:00+05:30'); `); await pgknex2.destroy(); } else if (database === 'mysql') { - const config = { - client: 'mysql2', - connection: { - host: 'localhost', - port: 3306, - user: 'root', - password: 'password', - database: 'sakila', - multipleStatements: true, - }, - pool: { min: 0, max: 5 }, - }; - - const config2 = { - ...config, - connection: { - ...config.connection, - database: 'datetimetable', - }, - }; + const config = getKnexConfig({ dbName: 'sakila', dbType: 'mysql' }); + const config2 = getKnexConfig({ dbName: 'datetimetable', dbType: 'mysql' }); const mysqlknex = knex(config); await mysqlknex.raw(`DROP DATABASE IF EXISTS datetimetable`); @@ -419,22 +384,15 @@ async function createTableWithDateTimeColumn(database: string) { datetime_without_tz DATETIME, datetime_with_tz TIMESTAMP ); - SET time_zone = '+08:00'; + -- SET time_zone = '+08:00'; INSERT INTO my_table (datetime_without_tz, datetime_with_tz) VALUES - ('2023-04-27 10:00:00', '2023-04-27 12:30:00'), + ('2023-04-27 10:00:00', '2023-04-27 10:00:00'), ('2023-04-27 10:00:00+05:30', '2023-04-27 10:00:00+05:30'); `); await mysqlknex2.destroy(); } else if (database === 'sqlite') { - const config = { - client: 'sqlite3', - connection: { - filename: './mydb.sqlite3', - }, - useNullAsDefault: true, - pool: { min: 0, max: 5 }, - }; + const config = getKnexConfig({ dbName: 'mydb', dbType: 'sqlite' }); // SQLite supports just one type of datetime // Timezone information, if specified is stored as is in the database @@ -461,32 +419,32 @@ async function createTableWithDateTimeColumn(database: string) { } } -test.describe.skip('External DB - DateTime column', async () => { +test.describe('External DB - DateTime column', async () => { let dashboard: DashboardPage; let context: any; const expectedDisplayValues = { pg: { DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 10:00'], - DatetimeWithTz: ['2023-04-27 12:30', '2023-04-27 12:30'], + DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 10:00'], }, sqlite: { // without +HH:MM information, display value is same as inserted value // with +HH:MM information, display value is converted to browser timezone // SQLite doesn't have with & without timezone fields; both are same in this case - DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 12:30'], - DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 12:30'], + DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 10:00'], + DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 10:00'], }, mysql: { - DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 12:30'], - DatetimeWithTz: ['2023-04-27 04:30', '2023-04-27 04:30'], + DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 04:30'], + DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 04:30'], }, }; - test.use({ - locale: 'zh-HK', - timezoneId: 'Asia/Hong_Kong', - }); + // test.use({ + // locale: 'zh-HK', + // timezoneId: 'Asia/Hong_Kong', + // }); test.beforeEach(async ({ page }) => { context = await setup({ page, isEmptyProject: true }); @@ -505,17 +463,7 @@ test.describe.skip('External DB - DateTime column', async () => { await api.base.create(context.project.id, { alias: 'datetimetable', type: 'pg', - config: { - client: 'pg', - connection: { - host: 'localhost', - port: '5432', - user: 'postgres', - password: 'password', - database: 'datetimetable', - }, - searchPath: ['public'], - }, + config: getKnexConfig({ dbName: 'datetimetable', dbType: 'pg' }), inflection_column: 'camelize', inflection_table: 'camelize', }); @@ -523,16 +471,7 @@ test.describe.skip('External DB - DateTime column', async () => { await api.base.create(context.project.id, { alias: 'datetimetable', type: 'mysql2', - config: { - client: 'mysql2', - connection: { - host: 'localhost', - port: '3306', - user: 'root', - password: 'password', - database: 'datetimetable', - }, - }, + config: getKnexConfig({ dbName: 'datetimetable', dbType: 'mysql' }), inflection_column: 'camelize', inflection_table: 'camelize', }); @@ -557,9 +496,15 @@ test.describe.skip('External DB - DateTime column', async () => { } await dashboard.rootPage.reload(); + // wait for 5 seconds for the base to be created + // hack for CI + await dashboard.rootPage.waitForTimeout(5000); }); test('Verify display value, UI insert, API response', async () => { + // get timezone offset + const timezoneOffset = new Date().getTimezoneOffset(); + await dashboard.treeView.openBase({ title: 'datetimetable' }); await dashboard.treeView.openTable({ title: 'MyTable' }); @@ -596,7 +541,7 @@ test.describe.skip('External DB - DateTime column', async () => { await dashboard.grid.cell.dateTime.setDateTime({ index: 2, columnHeader: 'DatetimeWithTz', - dateTime: '2023-04-27 12:30:00', + dateTime: '2023-04-27 10:00:00', }); // reload page & verify if inserted values are shown correctly @@ -609,7 +554,7 @@ test.describe.skip('External DB - DateTime column', async () => { await dashboard.grid.cell.verifyDateCell({ index: 2, columnHeader: 'DatetimeWithTz', - value: '2023-04-27 12:30', + value: '2023-04-27 10:00', }); // verify API response @@ -621,92 +566,40 @@ test.describe.skip('External DB - DateTime column', async () => { let dateTimeWithoutTz = records.list.map(record => record.DatetimeWithoutTz); let dateTimeWithTz = records.list.map(record => record.DatetimeWithTz); - if (isPg(context)) { - const expectedDateTimeWithoutTz = [ - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 02:00:00 GMT', - ]; - const expectedDateTimeWithTz = [ - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 04:30:00 GMT', - ]; - - // convert to ISO string, skip seconds part or reset seconds to 00 - dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - return dateObj.toUTCString(); - }); - dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - return dateObj.toUTCString(); - }); - - console.log(dateTimeWithoutTz); - console.log(dateTimeWithTz); - expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); - expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); - } else if (isMysql(context)) { - const expectedDateTimeWithoutTz = [ - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 07:00:00 GMT', - 'Thu, 27 Apr 2023 02:00:00 GMT', - ]; - const expectedDateTimeWithTz = [ - 'Wed, 26 Apr 2023 23:00:00 GMT', - 'Wed, 26 Apr 2023 23:00:00 GMT', - 'Thu, 27 Apr 2023 04:30:00 GMT', - ]; - - // convert to ISO string, skip seconds part or reset seconds to 00 - dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - return dateObj.toUTCString(); - }); - dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - return dateObj.toUTCString(); - }); + const expectedDateTimeWithoutTz = [ + 'Thu, 27 Apr 2023 10:00:00 GMT', + 'Thu, 27 Apr 2023 10:00:00 GMT', + 'Thu, 27 Apr 2023 10:00:00 GMT', + ]; + const expectedDateTimeWithTz = [ + 'Thu, 27 Apr 2023 10:00:00 GMT', + 'Thu, 27 Apr 2023 10:00:00 GMT', + 'Thu, 27 Apr 2023 10:00:00 GMT', + ]; - console.log(dateTimeWithoutTz); - console.log(dateTimeWithTz); + if (isMysql(context)) { + expectedDateTimeWithoutTz[1] = 'Thu, 27 Apr 2023 04:30:00 GMT'; + expectedDateTimeWithTz[1] = 'Thu, 27 Apr 2023 04:30:00 GMT'; + } - expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); - expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); - } else if (isSqlite(context)) { - const expectedDateTimeWithoutTz = [ - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 02:00:00 GMT', - ]; - const expectedDateTimeWithTz = [ - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 04:30:00 GMT', - 'Thu, 27 Apr 2023 04:30:00 GMT', - ]; - - // convert to ISO string, skip seconds part or reset seconds to 00 - dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - return dateObj.toUTCString(); - }); - dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - return dateObj.toUTCString(); - }); + // convert to ISO string, skip seconds part or reset seconds to 00 + dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + dateObj.setMinutes(dateObj.getMinutes() - timezoneOffset); + return dateObj.toUTCString(); + }); + dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { + const dateObj = new Date(dateTimeStr); + dateObj.setSeconds(0); + dateObj.setMinutes(dateObj.getMinutes() - timezoneOffset); + return dateObj.toUTCString(); + }); - console.log(dateTimeWithoutTz); - console.log(dateTimeWithTz); + // console.log(dateTimeWithoutTz); + // console.log(dateTimeWithTz); - expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); - expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); - } + expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); + expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); }); }); diff --git a/tests/playwright/tests/utils/config.ts b/tests/playwright/tests/utils/config.ts new file mode 100644 index 0000000000..f587574c0c --- /dev/null +++ b/tests/playwright/tests/utils/config.ts @@ -0,0 +1,48 @@ +const knexConfig = { + pg: { + client: 'pg', + connection: { + host: 'localhost', + port: 5432, + user: 'postgres', + password: 'password', + database: 'postgres', + multipleStatements: true, + }, + searchPath: ['public', 'information_schema'], + pool: { min: 0, max: 5 }, + }, + mysql: { + client: 'mysql2', + connection: { + host: 'localhost', + port: 3306, + user: 'root', + password: 'password', + database: 'sakila', + multipleStatements: true, + }, + pool: { min: 0, max: 5 }, + }, + sqlite: { + client: 'sqlite3', + connection: { + filename: './mydb.sqlite3', + }, + useNullAsDefault: true, + pool: { min: 0, max: 5 }, + }, +}; + +function getKnexConfig({ dbName, dbType }: { dbName: string; dbType: string }) { + const config = knexConfig[dbType]; + + if (dbType === 'sqlite') { + config.connection.filename = `./${dbName}.sqlite3`; + return config; + } + config.connection.database = dbName; + return config; +} + +export { getKnexConfig }; From 6b48c69af69623b9b5f680334694d1a48ffd4ccd Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Sat, 6 May 2023 12:54:34 +0530 Subject: [PATCH 086/233] test: config reorder Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/tests/db/timezone.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index d58c3a2434..7440b33254 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -345,13 +345,14 @@ test.describe('Timezone', () => { async function createTableWithDateTimeColumn(database: string) { if (database === 'pg') { const config = getKnexConfig({ dbName: 'postgres', dbType: 'pg' }); - const config2 = getKnexConfig({ dbName: 'datetimetable', dbType: 'pg' }); const pgknex = knex(config); await pgknex.raw(`DROP DATABASE IF EXISTS datetimetable`); await pgknex.raw(`CREATE DATABASE datetimetable`); await pgknex.destroy(); + const config2 = getKnexConfig({ dbName: 'datetimetable', dbType: 'pg' }); + const pgknex2 = knex(config2); await pgknex2.raw(` CREATE TABLE my_table ( @@ -369,13 +370,14 @@ async function createTableWithDateTimeColumn(database: string) { await pgknex2.destroy(); } else if (database === 'mysql') { const config = getKnexConfig({ dbName: 'sakila', dbType: 'mysql' }); - const config2 = getKnexConfig({ dbName: 'datetimetable', dbType: 'mysql' }); const mysqlknex = knex(config); await mysqlknex.raw(`DROP DATABASE IF EXISTS datetimetable`); await mysqlknex.raw(`CREATE DATABASE datetimetable`); await mysqlknex.destroy(); + const config2 = getKnexConfig({ dbName: 'datetimetable', dbType: 'mysql' }); + const mysqlknex2 = knex(config2); await mysqlknex2.raw(` USE datetimetable; From 54231da8dfad66c650565b2dd5e7d4c641d91b19 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Sat, 6 May 2023 14:29:20 +0530 Subject: [PATCH 087/233] test: adjust timezone settings Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/tests/db/timezone.spec.ts | 25 +++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index 7440b33254..db1c8456a4 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -421,6 +421,25 @@ async function createTableWithDateTimeColumn(database: string) { } } +function getDateTimeInLocalTimeZone(dateString: string) { + // create a Date object with the input string + // assumes local system timezone + const date = new Date(dateString); + + // get the timezone offset in minutes and convert to milliseconds + // subtract the offset from the provided time in milliseconds for IST + const offsetMs = date.getTimezoneOffset() * 60 * 1000; + + // adjust the date by the offset + const adjustedDate = new Date(date.getTime() - offsetMs); + + // format the adjusted date as a string in the desired format + const outputString = adjustedDate.toISOString().slice(0, 16).replace('T', ' '); + + // output the result + return outputString; +} + test.describe('External DB - DateTime column', async () => { let dashboard: DashboardPage; let context: any; @@ -428,14 +447,14 @@ test.describe('External DB - DateTime column', async () => { const expectedDisplayValues = { pg: { DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 10:00'], - DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 10:00'], + DatetimeWithTz: ['2023-04-27 10:00', getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00')], }, sqlite: { // without +HH:MM information, display value is same as inserted value // with +HH:MM information, display value is converted to browser timezone // SQLite doesn't have with & without timezone fields; both are same in this case - DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 10:00'], - DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 10:00'], + DatetimeWithoutTz: ['2023-04-27 10:00', getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00')], + DatetimeWithTz: ['2023-04-27 10:00', getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00')], }, mysql: { DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 04:30'], From f91935b92590330c530daaf69cdafe84756f0a9d Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Sat, 6 May 2023 19:46:28 +0530 Subject: [PATCH 088/233] test: extDB offset corrections --- tests/playwright/tests/db/timezone.spec.ts | 71 +++++++++++++--------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index db1c8456a4..d127408c1d 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -447,7 +447,10 @@ test.describe('External DB - DateTime column', async () => { const expectedDisplayValues = { pg: { DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 10:00'], - DatetimeWithTz: ['2023-04-27 10:00', getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00')], + DatetimeWithTz: [ + getDateTimeInLocalTimeZone('2023-04-27 10:00:00+00:00'), + getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00'), + ], }, sqlite: { // without +HH:MM information, display value is same as inserted value @@ -525,6 +528,10 @@ test.describe('External DB - DateTime column', async () => { test('Verify display value, UI insert, API response', async () => { // get timezone offset const timezoneOffset = new Date().getTimezoneOffset(); + const hours = Math.floor(Math.abs(timezoneOffset) / 60); + const minutes = Math.abs(timezoneOffset % 60); + const sign = timezoneOffset <= 0 ? '+' : '-'; + const formattedOffset = `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; await dashboard.treeView.openBase({ title: 'datetimetable' }); await dashboard.treeView.openTable({ title: 'MyTable' }); @@ -587,38 +594,42 @@ test.describe('External DB - DateTime column', async () => { let dateTimeWithoutTz = records.list.map(record => record.DatetimeWithoutTz); let dateTimeWithTz = records.list.map(record => record.DatetimeWithTz); - const expectedDateTimeWithoutTz = [ - 'Thu, 27 Apr 2023 10:00:00 GMT', - 'Thu, 27 Apr 2023 10:00:00 GMT', - 'Thu, 27 Apr 2023 10:00:00 GMT', - ]; - const expectedDateTimeWithTz = [ - 'Thu, 27 Apr 2023 10:00:00 GMT', - 'Thu, 27 Apr 2023 10:00:00 GMT', - 'Thu, 27 Apr 2023 10:00:00 GMT', - ]; - - if (isMysql(context)) { - expectedDateTimeWithoutTz[1] = 'Thu, 27 Apr 2023 04:30:00 GMT'; - expectedDateTimeWithTz[1] = 'Thu, 27 Apr 2023 04:30:00 GMT'; + let expectedDateTimeWithoutTz = []; + let expectedDateTimeWithTz = []; + + if (isSqlite(context)) { + expectedDateTimeWithoutTz = [ + '2023-04-27 10:00:00', + '2023-04-27 10:00:00+05:30', + `2023-04-27 10:00:00${formattedOffset}`, + ]; + expectedDateTimeWithTz = [ + '2023-04-27 10:00:00', + '2023-04-27 10:00:00+05:30', + `2023-04-27 10:00:00${formattedOffset}`, + ]; + } else if (isPg(context)) { + expectedDateTimeWithoutTz = ['2023-04-27 10:00:00', '2023-04-27 10:00:00', '2023-04-27 10:00:00']; + expectedDateTimeWithTz = [ + '2023-04-27T10:00:00.000Z', + '2023-04-27T04:30:00.000Z', + new Date('2023-04-27T10:00:00').toISOString(), + ]; + } else if (isMysql(context)) { + expectedDateTimeWithoutTz = ['2023-04-27 10:00:00', '2023-04-27 04:30:00', '2023-04-27 10:00:00']; + expectedDateTimeWithTz = ['2023-04-27 10:00:00', '2023-04-27 04:30:00', '2023-04-27 10:00:00']; } - // convert to ISO string, skip seconds part or reset seconds to 00 - dateTimeWithoutTz = dateTimeWithoutTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - dateObj.setMinutes(dateObj.getMinutes() - timezoneOffset); - return dateObj.toUTCString(); - }); - dateTimeWithTz = dateTimeWithTz.map(dateTimeStr => { - const dateObj = new Date(dateTimeStr); - dateObj.setSeconds(0); - dateObj.setMinutes(dateObj.getMinutes() - timezoneOffset); - return dateObj.toUTCString(); - }); + // reset seconds to 00 using string functions in dateTimeWithoutTz + dateTimeWithoutTz = dateTimeWithoutTz.map( + dateTimeString => dateTimeString.substring(0, 17) + '00' + dateTimeString.substring(19) + ); + dateTimeWithTz = dateTimeWithTz.map( + dateTimeString => dateTimeString.substring(0, 17) + '00' + dateTimeString.substring(19) + ); - // console.log(dateTimeWithoutTz); - // console.log(dateTimeWithTz); + // console.log('dateTimeWithoutTz', dateTimeWithoutTz); + // console.log('dateTimeWithTz', dateTimeWithTz); expect(dateTimeWithoutTz).toEqual(expectedDateTimeWithoutTz); expect(dateTimeWithTz).toEqual(expectedDateTimeWithTz); From 89bcccac3aa0c639db86e13862eab1a5a571b55b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 8 May 2023 16:38:09 +0800 Subject: [PATCH 089/233] fix(nocodb): mysql xcdb api response logic --- packages/nocodb/src/db/BaseModelSqlv2.ts | 67 +++++++++++++++++++++--- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 983dc88012..c3164c15b6 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -1,6 +1,8 @@ import autoBind from 'auto-bind'; import groupBy from 'lodash/groupBy'; import DataLoader from 'dataloader'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc.js'; import { nocoExecute } from 'nc-help'; import { AuditOperationSubTypes, @@ -64,6 +66,8 @@ import type { import type { Knex } from 'knex'; import type { SortType } from 'nocodb-sdk'; +dayjs.extend(utc); + const GROUP_COL = '__nc_group_id'; const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14); @@ -1949,6 +1953,11 @@ class BaseModelSqlv2 { }; } + private async isXcdbBase() { + const base = await Base.get(this.model.base_id); + return base.is_meta; + } + get isSqlite() { return this.clientType === 'sqlite3'; } @@ -3147,16 +3156,24 @@ class BaseModelSqlv2 { } else { query = sanitize(query); } - return this.convertAttachmentType( + let data = this.isPg || this.isSnowflake ? (await this.dbDriver.raw(query))?.rows : query.slice(0, 6) === 'select' && !this.isMssql ? await this.dbDriver.from( this.dbDriver.raw(query).wrap('(', ') __nc_alias'), ) - : await this.dbDriver.raw(query), - childTable, - ); + : await this.dbDriver.raw(query); + + // update attachment fields + data = this.convertAttachmentType(data, childTable); + + // update date time fields + if (this.isMySQL && !(await this.isXcdbBase())) { + data = this.convertDateFormat(data, childTable); + } + + return data; } private _convertAttachmentType( @@ -3195,9 +3212,47 @@ class BaseModelSqlv2 { return data; } + private _convertDateFormat( + dateTimeColumns: Record[], + d: Record, + ) { + try { + if (d) { + dateTimeColumns.forEach((col) => { + if (d[col.title] && typeof d[col.title] === 'string') { + // e.g. 2022-01-01 04:30:00+00:00 + d[col.title] = dayjs(d[col.title]) + .utc() + .format('YYYY-MM-DD HH:mm:ssZ'); + } + }); + } + } catch {} + return d; + } + + private convertDateFormat(data: Record, childTable?: Model) { + // MySQL converts TIMESTAMP values from the current time zone to UTC for storage. + // Then, MySQL converts those values back from UTC to the current time zone for retrieval. + // For xcdb base, to make it consistent with other DB types, we show the result in UTC instead + // e.g. 2022-01-01 04:30:00+00:00 + if (data) { + const dateTimeColumns = ( + childTable ? childTable.columns : this.model.columns + ).filter((c) => c.uidt === UITypes.DateTime); + if (dateTimeColumns.length) { + if (Array.isArray(data)) { + data = data.map((d) => this._convertDateFormat(dateTimeColumns, d)); + } else { + this._convertDateFormat(dateTimeColumns, data); + } + } + } + return data; + } + private async setUtcTimezoneForPg() { - const base = await Base.get(this.model.base_id); - if (this.isPg && base.is_meta) { + if (this.isPg && (await this.isXcdbBase())) { await this.dbDriver.raw(`SET TIME ZONE 'UTC'`); } } From 78a5a5a30543b7db7ad58751e14d4fdec91e6a0d Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 8 May 2023 16:52:43 +0800 Subject: [PATCH 090/233] refactor(nc-gui): remove unused code --- packages/nc-gui/components/cell/DateTimePicker.vue | 2 +- packages/nc-gui/composables/useMultiSelect/convertCellData.ts | 1 - packages/nc-gui/composables/useMultiSelect/index.ts | 4 +--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 47e000f24f..e3f3d53c74 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -25,7 +25,7 @@ const { modelValue, isPk, isUpdatedFromCopyNPaste } = defineProps() const emit = defineEmits(['update:modelValue']) -const { isMssql, isMysql, isSqlite, isXcdbBase } = useProject() +const { isMssql, isMysql, isXcdbBase } = useProject() const { showNull } = useGlobal() diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 5804107d09..1c9ebb0ba0 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -7,7 +7,6 @@ import { parseProp } from '#imports' export default function convertCellData( args: { from: UITypes; to: UITypes; value: any; column: ColumnType; appInfo: AppInfo }, isMysql = false, - isSqlite = false, isMssql = false, isXcdbBase = false, ) { diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 09e4903bed..3878f2d729 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -55,7 +55,7 @@ export function useMultiSelect( const { appInfo } = useGlobal() - const { isMssql, isMysql, isSqlite, isXcdbBase } = useProject() + const { isMssql, isMysql, isXcdbBase } = useProject() let clipboardContext = $ref<{ value: any; uidt: UITypes } | null>(null) @@ -336,7 +336,6 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), - isSqlite(meta.value?.base_id), isMssql(meta.value?.base_id), isXcdbBase(meta.value?.base_id), ) @@ -373,7 +372,6 @@ export function useMultiSelect( appInfo: unref(appInfo), }, isMysql(meta.value?.base_id), - isSqlite(meta.value?.base_id), isMssql(meta.value?.base_id), isXcdbBase(meta.value?.base_id), ) From f91cd96bbcd2f16a69c66a7db4191c442d4506ed Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 8 May 2023 17:04:59 +0800 Subject: [PATCH 091/233] refactor(nc-gui): update comments --- packages/nc-gui/composables/useMultiSelect/convertCellData.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 1c9ebb0ba0..79dda7e840 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -48,7 +48,7 @@ export default function convertCellData( if (isMysql) { let res = `${parsedDateTime.format('YYYY-MM-DD HH:mm:ss')}` if (!dayjs.isDayjs(value)) { - // UTC + 'Z' + // value is a UTC string, we append 'Z' to `res` res += 'Z' } return res @@ -64,7 +64,7 @@ export default function convertCellData( return parsedDateTime } } - // TODO(timezone): keep ext db as it is + // External DB - keep it as it is return parsedDateTime.format(isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') } case UITypes.Time: { From a8d05f2da44425f387ecd5e43d68e67c2332bab9 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 8 May 2023 17:05:17 +0800 Subject: [PATCH 092/233] refactor(nocodb): add / revise comments --- packages/nocodb/src/models/Model.ts | 2 +- packages/nocodb/src/version-upgrader/ncDateTimeUpgrader.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts index e62bce7899..3b3deaa1d9 100644 --- a/packages/nocodb/src/models/Model.ts +++ b/packages/nocodb/src/models/Model.ts @@ -488,7 +488,7 @@ export default class Model implements TableType { .format('YYYY-MM-DD HH:mm:ssZ'); } } else { - // TODO(timezone): keep ext db as it is + // External DB - keep it as it is val = dayjs(val).format( isMySQL ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ', ); diff --git a/packages/nocodb/src/version-upgrader/ncDateTimeUpgrader.ts b/packages/nocodb/src/version-upgrader/ncDateTimeUpgrader.ts index 2529555da8..974a3c1f0d 100644 --- a/packages/nocodb/src/version-upgrader/ncDateTimeUpgrader.ts +++ b/packages/nocodb/src/version-upgrader/ncDateTimeUpgrader.ts @@ -28,6 +28,11 @@ function getTnPath(knex: XKnex, tb: Model) { } } +// This upgrader is to update all datetime fields in xcdb base due to the datetime changes +// ref: https://github.com/nocodb/nocodb/pull/5505 +// Originally, for XCDB-based projects, we store the local time in DB and display local time in UI +// After the above PR, we store UTC time in DB and display local time in UI +// Therefore, we convert all the target datetime to UTC format in DB export default async function ({ ncMeta }: NcUpgraderCtx) { const bases: BaseType[] = await ncMeta.metaList2(null, null, MetaTable.BASES); for (const _base of bases) { From e878b4d5753fbb1896ffe6176638b2d870cd8c82 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 8 May 2023 18:01:01 +0800 Subject: [PATCH 093/233] fix(nocodb): handle mysql xcdb api response --- packages/nocodb/src/db/BaseModelSqlv2.ts | 34 +++++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index c3164c15b6..9d37132c27 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -7,6 +7,7 @@ import { nocoExecute } from 'nc-help'; import { AuditOperationSubTypes, AuditOperationTypes, + BoolType, isSystemColumn, isVirtualCol, RelationTypes, @@ -3169,9 +3170,8 @@ class BaseModelSqlv2 { data = this.convertAttachmentType(data, childTable); // update date time fields - if (this.isMySQL && !(await this.isXcdbBase())) { - data = this.convertDateFormat(data, childTable); - } + const isXcdbBase = await this.isXcdbBase(); + data = this.convertDateFormat(data, isXcdbBase, childTable); return data; } @@ -3215,15 +3215,23 @@ class BaseModelSqlv2 { private _convertDateFormat( dateTimeColumns: Record[], d: Record, + isXcdbBase: BoolType, ) { try { if (d) { dateTimeColumns.forEach((col) => { if (d[col.title] && typeof d[col.title] === 'string') { - // e.g. 2022-01-01 04:30:00+00:00 - d[col.title] = dayjs(d[col.title]) - .utc() - .format('YYYY-MM-DD HH:mm:ssZ'); + if (isXcdbBase) { + // e.g. 01.01.2022 10:00:00+05:30 -> 2022-01-01 04:30:00+00:00 + d[col.title] = dayjs(d[col.title]) + .utc(true) + .format('YYYY-MM-DD HH:mm:ssZ'); + } else { + // e.g. 01.01.2022 10:00:00+05:30 -> 2022-01-01 04:30:00+00:00 + d[col.title] = dayjs(d[col.title]) + .utc() + .format('YYYY-MM-DD HH:mm:ssZ'); + } } }); } @@ -3231,7 +3239,11 @@ class BaseModelSqlv2 { return d; } - private convertDateFormat(data: Record, childTable?: Model) { + private convertDateFormat( + data: Record, + isXcdbBase: BoolType, + childTable?: Model, + ) { // MySQL converts TIMESTAMP values from the current time zone to UTC for storage. // Then, MySQL converts those values back from UTC to the current time zone for retrieval. // For xcdb base, to make it consistent with other DB types, we show the result in UTC instead @@ -3242,9 +3254,11 @@ class BaseModelSqlv2 { ).filter((c) => c.uidt === UITypes.DateTime); if (dateTimeColumns.length) { if (Array.isArray(data)) { - data = data.map((d) => this._convertDateFormat(dateTimeColumns, d)); + data = data.map((d) => + this._convertDateFormat(dateTimeColumns, d, isXcdbBase), + ); } else { - this._convertDateFormat(dateTimeColumns, data); + this._convertDateFormat(dateTimeColumns, data, isXcdbBase); } } } From f18f788c7fa0cb50bf4d98833337141b9e7c6258 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 8 May 2023 18:01:36 +0800 Subject: [PATCH 094/233] refactor(nocodb): revise comment --- packages/nocodb/src/db/BaseModelSqlv2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 9d37132c27..4a9418d10e 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3246,7 +3246,7 @@ class BaseModelSqlv2 { ) { // MySQL converts TIMESTAMP values from the current time zone to UTC for storage. // Then, MySQL converts those values back from UTC to the current time zone for retrieval. - // For xcdb base, to make it consistent with other DB types, we show the result in UTC instead + // To make it consistent with other DB types, we show the result in UTC instead // e.g. 2022-01-01 04:30:00+00:00 if (data) { const dateTimeColumns = ( From 5901ca0fa1efa4d3bd6e24c5ec0ade40e0517475 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Mon, 8 May 2023 18:03:01 +0800 Subject: [PATCH 095/233] fix(nocodb): add missing isMySQL condition --- packages/nocodb/src/db/BaseModelSqlv2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 4a9418d10e..6cd8e73dc0 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3248,7 +3248,7 @@ class BaseModelSqlv2 { // Then, MySQL converts those values back from UTC to the current time zone for retrieval. // To make it consistent with other DB types, we show the result in UTC instead // e.g. 2022-01-01 04:30:00+00:00 - if (data) { + if (this.isMySQL && data) { const dateTimeColumns = ( childTable ? childTable.columns : this.model.columns ).filter((c) => c.uidt === UITypes.DateTime); From deac3a1a031ff81962cb0227faba794564dbf7df Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Mon, 8 May 2023 21:05:10 +0530 Subject: [PATCH 096/233] test: updates for mysql extDB handling Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- tests/playwright/tests/db/timezone.spec.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/playwright/tests/db/timezone.spec.ts b/tests/playwright/tests/db/timezone.spec.ts index d127408c1d..4fb6e2ab27 100644 --- a/tests/playwright/tests/db/timezone.spec.ts +++ b/tests/playwright/tests/db/timezone.spec.ts @@ -460,8 +460,8 @@ test.describe('External DB - DateTime column', async () => { DatetimeWithTz: ['2023-04-27 10:00', getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00')], }, mysql: { - DatetimeWithoutTz: ['2023-04-27 10:00', '2023-04-27 04:30'], - DatetimeWithTz: ['2023-04-27 10:00', '2023-04-27 04:30'], + DatetimeWithoutTz: ['2023-04-27 10:00', getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00')], + DatetimeWithTz: ['2023-04-27 10:00', getDateTimeInLocalTimeZone('2023-04-27 04:30:00+00:00')], }, }; @@ -616,8 +616,12 @@ test.describe('External DB - DateTime column', async () => { new Date('2023-04-27T10:00:00').toISOString(), ]; } else if (isMysql(context)) { - expectedDateTimeWithoutTz = ['2023-04-27 10:00:00', '2023-04-27 04:30:00', '2023-04-27 10:00:00']; - expectedDateTimeWithTz = ['2023-04-27 10:00:00', '2023-04-27 04:30:00', '2023-04-27 10:00:00']; + expectedDateTimeWithoutTz = [ + '2023-04-27 10:00:00+00:00', + '2023-04-27 04:30:00+00:00', + '2023-04-27 04:30:00+00:00', + ]; + expectedDateTimeWithTz = ['2023-04-27 10:00:00+00:00', '2023-04-27 04:30:00+00:00', '2023-04-27 04:30:00+00:00']; } // reset seconds to 00 using string functions in dateTimeWithoutTz From e7e47a605a4d0e7222c433f788067109fa83ad55 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 19:50:02 +0800 Subject: [PATCH 097/233] fix(nc-gui): convert back to utc --- .../nc-gui/composables/useMultiSelect/convertCellData.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 79dda7e840..0ffd2465f3 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -46,12 +46,9 @@ export default function convertCellData( } if (isXcdbBase) { if (isMysql) { - let res = `${parsedDateTime.format('YYYY-MM-DD HH:mm:ss')}` - if (!dayjs.isDayjs(value)) { - // value is a UTC string, we append 'Z' to `res` - res += 'Z' - } - return res + // convert back to utc + // e.g. 2023-05-09T19:41:49+08:00 -> 2023-05-09 11:41:49 + return `${parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss')}` } else if (isMssql) { return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') } else { From 6bee408c2f7c416317b81bd85d2ba3800582096b Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 19:50:52 +0800 Subject: [PATCH 098/233] fix(nc-gui): copy as it is for mysql --- packages/nc-gui/composables/useMultiSelect/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 3878f2d729..dba2253f28 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -124,7 +124,7 @@ export function useMultiSelect( d = dayjs(textToCopy, isMysql(columnObj.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') } if (isXcdbBase(meta.value?.base_id)) { - if (isMssql(meta.value?.base_id)) { + if (isMssql(meta.value?.base_id) || isMysql(meta.value?.base_id)) { textToCopy = d.format(constructDateTimeFormat(columnObj)) } else { textToCopy = d.utc(true).local().format(constructDateTimeFormat(columnObj)) From 1c33493a45367c05046018745d7a3e24c2944cc1 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 19:52:56 +0800 Subject: [PATCH 099/233] fix(nocodb): revise _convertDateFormat logic --- packages/nocodb/src/db/BaseModelSqlv2.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 6cd8e73dc0..ad5c642789 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3215,23 +3215,17 @@ class BaseModelSqlv2 { private _convertDateFormat( dateTimeColumns: Record[], d: Record, - isXcdbBase: BoolType, + isXcdbBase: BoolType, // TODO(timezone): remove ) { try { if (d) { dateTimeColumns.forEach((col) => { if (d[col.title] && typeof d[col.title] === 'string') { - if (isXcdbBase) { - // e.g. 01.01.2022 10:00:00+05:30 -> 2022-01-01 04:30:00+00:00 - d[col.title] = dayjs(d[col.title]) - .utc(true) - .format('YYYY-MM-DD HH:mm:ssZ'); - } else { - // e.g. 01.01.2022 10:00:00+05:30 -> 2022-01-01 04:30:00+00:00 - d[col.title] = dayjs(d[col.title]) - .utc() - .format('YYYY-MM-DD HH:mm:ssZ'); - } + // e.g. 01.01.2022 10:00:00+05:30 -> 2022-01-01 04:30:00+00:00 (TBC) + // e.g. 2023-05-09 11:41:49 -> 2023-05-09 11:41:49+00:00 + d[col.title] = dayjs(d[col.title]) + .utc(true) + .format('YYYY-MM-DD HH:mm:ssZ'); } }); } From 942993e7a9ae87cf8e5c5c048f58f70daa59deea Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 19:54:06 +0800 Subject: [PATCH 100/233] fix(nocodb): revise mapAliasToColumn for mysql external db --- packages/nocodb/src/models/Model.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/nocodb/src/models/Model.ts b/packages/nocodb/src/models/Model.ts index 3b3deaa1d9..0d60abe431 100644 --- a/packages/nocodb/src/models/Model.ts +++ b/packages/nocodb/src/models/Model.ts @@ -488,10 +488,14 @@ export default class Model implements TableType { .format('YYYY-MM-DD HH:mm:ssZ'); } } else { - // External DB - keep it as it is - val = dayjs(val).format( - isMySQL ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ', - ); + // External DB + if (isMySQL) { + // convert to utc + val = dayjs(val).utc().format('YYYY-MM-DD HH:mm:ss'); + } else { + // keep it as it is + val = dayjs(val).format('YYYY-MM-DD HH:mm:ssZ'); + } } } insertObj[sanitize(col.column_name)] = val; From 885bcab0d7c9ef6ddb7d5147971b1206bf711fd2 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 19:54:44 +0800 Subject: [PATCH 101/233] fix(nc-gui): dont keep local time when getting but setting to localModelValue --- packages/nc-gui/components/cell/DateTimePicker.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index e3f3d53c74..a9488e4e52 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -45,7 +45,7 @@ const dateTimeFormat = $computed(() => { return `${dateFormat} ${timeFormat}` }) -let localModelValue = modelValue ? dayjs(modelValue).utc(true).local() : undefined +let localModelValue = modelValue ? dayjs(modelValue).utc().local() : undefined let localState = $computed({ get() { @@ -79,7 +79,7 @@ let localState = $computed({ // when pasting a datetime cell, UTC (xcdb) will be saved in DB // we convert back to local time if (column.value.title! in (isUpdatedFromCopyNPaste ?? {})) { - localModelValue = dayjs(modelValue).utc().local() + localModelValue = dayjs(modelValue).utc(true).local() return localModelValue } @@ -94,7 +94,7 @@ let localState = $computed({ } // empty cell - use modelValue in local time - return dayjs(modelValue).utc(true).local() + return dayjs(modelValue).utc().local() }, set(val?: dayjs.Dayjs) { if (!val) { From 331b48cf9bc6a72d80ab314f38a6acf26df8d12d Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:05:52 +0800 Subject: [PATCH 102/233] fix(nocodb): convertDateFormat for all types --- packages/nocodb/src/db/BaseModelSqlv2.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index ad5c642789..3c6fcf5cf7 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3238,11 +3238,9 @@ class BaseModelSqlv2 { isXcdbBase: BoolType, childTable?: Model, ) { - // MySQL converts TIMESTAMP values from the current time zone to UTC for storage. - // Then, MySQL converts those values back from UTC to the current time zone for retrieval. - // To make it consistent with other DB types, we show the result in UTC instead + // Show the date time in UTC format in API response // e.g. 2022-01-01 04:30:00+00:00 - if (this.isMySQL && data) { + if (data) { const dateTimeColumns = ( childTable ? childTable.columns : this.model.columns ).filter((c) => c.uidt === UITypes.DateTime); From 5abb5d850b6875a3232d079033ec678d3357e19c Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:15:49 +0800 Subject: [PATCH 103/233] fix(nc-gui): copy n paste logic --- .../composables/useMultiSelect/convertCellData.ts | 12 +++--------- packages/nc-gui/composables/useMultiSelect/index.ts | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 0ffd2465f3..e8d3af07a5 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -45,20 +45,14 @@ export default function convertCellData( throw new Error('Not a valid datetime value') } if (isXcdbBase) { + // convert back to utc + // e.g. 2023-05-09T19:41:49+08:00 -> 2023-05-09 11:41:49 if (isMysql) { - // convert back to utc - // e.g. 2023-05-09T19:41:49+08:00 -> 2023-05-09 11:41:49 return `${parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss')}` } else if (isMssql) { return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') } else { - if (!dayjs.isDayjs(value)) { - // e.g. copy the existing cell - 2023-05-06 13:06:51 (UTC) - return parsedDateTime.utc(true).format('YYYY-MM-DD HH:mm:ssZ') - } - // e.g. copy right after setting by datepicker - // value includes timezone - return parsedDateTime + return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') } } // External DB - keep it as it is diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index dba2253f28..4bd0f2b60b 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -123,15 +123,9 @@ export function useMultiSelect( // feed custom parse format d = dayjs(textToCopy, isMysql(columnObj.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') } - if (isXcdbBase(meta.value?.base_id)) { - if (isMssql(meta.value?.base_id) || isMysql(meta.value?.base_id)) { - textToCopy = d.format(constructDateTimeFormat(columnObj)) - } else { - textToCopy = d.utc(true).local().format(constructDateTimeFormat(columnObj)) - } - } else { - textToCopy = d.format(constructDateTimeFormat(columnObj)) - } + + textToCopy = d.format(constructDateTimeFormat(columnObj)) + if (!dayjs(textToCopy).isValid()) { throw new Error('Invalid Date') } From 52582f9800f441648469500f55dad044f419a58e Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:19:52 +0800 Subject: [PATCH 104/233] fix(nc-gui): keep local time for mysql only --- packages/nc-gui/components/cell/DateTimePicker.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index a9488e4e52..2a5ff5fd88 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -79,7 +79,7 @@ let localState = $computed({ // when pasting a datetime cell, UTC (xcdb) will be saved in DB // we convert back to local time if (column.value.title! in (isUpdatedFromCopyNPaste ?? {})) { - localModelValue = dayjs(modelValue).utc(true).local() + localModelValue = dayjs(modelValue).utc(isMysql(column.value.base_id)).local() return localModelValue } From a823feb5b91435c9696c57c2260d8ae82e4dbb2f Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:39:53 +0800 Subject: [PATCH 105/233] fix(nc-gui): keep local time for mysql only --- packages/nc-gui/components/cell/DateTimePicker.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue index 2a5ff5fd88..1395d909b7 100644 --- a/packages/nc-gui/components/cell/DateTimePicker.vue +++ b/packages/nc-gui/components/cell/DateTimePicker.vue @@ -71,7 +71,7 @@ let localState = $computed({ // if cdf is defined, that means the value is auto-generated // hence, show the local time if (column?.value?.cdf) { - return dayjs(modelValue).utc(true).local() + return dayjs(modelValue).utc(isMysql(column.value.base_id)).local() } // cater copy and paste From 18dc415bb6129305b0eb1de565bf9d0a4c0f25b5 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:47:09 +0800 Subject: [PATCH 106/233] refactor(nc-gui): convert cell date time logic --- packages/nc-gui/composables/useMultiSelect/convertCellData.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index e8d3af07a5..8193ef5b05 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -48,9 +48,7 @@ export default function convertCellData( // convert back to utc // e.g. 2023-05-09T19:41:49+08:00 -> 2023-05-09 11:41:49 if (isMysql) { - return `${parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss')}` - } else if (isMssql) { - return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') + return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss') } else { return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') } From ac8765aa98dcb861f9333a69b433ea0dc288755c Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:52:17 +0800 Subject: [PATCH 107/233] refactor(nc-gui): combine the logic together --- .../nc-gui/composables/useMultiSelect/convertCellData.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 8193ef5b05..2cd878a9b2 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts @@ -47,11 +47,7 @@ export default function convertCellData( if (isXcdbBase) { // convert back to utc // e.g. 2023-05-09T19:41:49+08:00 -> 2023-05-09 11:41:49 - if (isMysql) { - return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ss') - } else { - return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') - } + return parsedDateTime.utc().format(isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') } // External DB - keep it as it is return parsedDateTime.format(isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ') From d03e689b92dea98c60a77c19b1f4d7eb752e06d9 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:54:05 +0800 Subject: [PATCH 108/233] fix(nocodb): allow date type for parsing --- packages/nocodb/src/db/BaseModelSqlv2.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 3c6fcf5cf7..4bd54101cd 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3220,8 +3220,8 @@ class BaseModelSqlv2 { try { if (d) { dateTimeColumns.forEach((col) => { - if (d[col.title] && typeof d[col.title] === 'string') { - // e.g. 01.01.2022 10:00:00+05:30 -> 2022-01-01 04:30:00+00:00 (TBC) + if (d[col.title]) { + // e.g. 01.01.2022 10:00:00+05:30 -> 2022-01-01 04:30:00+00:00 // e.g. 2023-05-09 11:41:49 -> 2023-05-09 11:41:49+00:00 d[col.title] = dayjs(d[col.title]) .utc(true) From 295156c4eb237426ce0e7a528c59f8ca77f449dd Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Tue, 9 May 2023 20:55:44 +0800 Subject: [PATCH 109/233] refactor(nocodb): remove unused isXcdbBase --- packages/nocodb/src/db/BaseModelSqlv2.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 4bd54101cd..10e0c72cc5 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -3170,8 +3170,7 @@ class BaseModelSqlv2 { data = this.convertAttachmentType(data, childTable); // update date time fields - const isXcdbBase = await this.isXcdbBase(); - data = this.convertDateFormat(data, isXcdbBase, childTable); + data = this.convertDateFormat(data, childTable); return data; } @@ -3215,7 +3214,6 @@ class BaseModelSqlv2 { private _convertDateFormat( dateTimeColumns: Record[], d: Record, - isXcdbBase: BoolType, // TODO(timezone): remove ) { try { if (d) { @@ -3233,11 +3231,7 @@ class BaseModelSqlv2 { return d; } - private convertDateFormat( - data: Record, - isXcdbBase: BoolType, - childTable?: Model, - ) { + private convertDateFormat(data: Record, childTable?: Model) { // Show the date time in UTC format in API response // e.g. 2022-01-01 04:30:00+00:00 if (data) { @@ -3246,11 +3240,9 @@ class BaseModelSqlv2 { ).filter((c) => c.uidt === UITypes.DateTime); if (dateTimeColumns.length) { if (Array.isArray(data)) { - data = data.map((d) => - this._convertDateFormat(dateTimeColumns, d, isXcdbBase), - ); + data = data.map((d) => this._convertDateFormat(dateTimeColumns, d)); } else { - this._convertDateFormat(dateTimeColumns, data, isXcdbBase); + this._convertDateFormat(dateTimeColumns, data); } } } From 1c2604eef6fd269df5572bdcee24e5cb2fbded45 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Wed, 10 May 2023 09:26:00 +0530 Subject: [PATCH 110/233] test: mysql corrections --- .run/test-debug.run.xml | 4 +++- tests/playwright/tests/db/timezone.spec.ts | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.run/test-debug.run.xml b/.run/test-debug.run.xml index b6cd6887cb..670661dd4c 100644 --- a/.run/test-debug.run.xml +++ b/.run/test-debug.run.xml @@ -6,7 +6,9 @@