Merge pull request #7076 from nocodb/fix/filter-timezone-column-update

fix: filter timezone when column timezone updated
This commit is contained in:
Raju Udava
2025-10-29 21:52:24 +05:30
committed by GitHub
11 changed files with 284 additions and 11 deletions

View File

@@ -553,3 +553,19 @@ export enum ViewSettingOverrideOptions {
GROUP = 'group',
ROW_COLORING = 'rowColoring',
}
export enum MetaEventType {
COLUMN_ADDED = 'COLUMN_ADDED',
COLUMN_UPDATED = 'COLUMN_UPDATED',
COLUMN_DELETED = 'COLUMN_DELETED',
}
export enum MetaEntityType {
BASE = 'BASE',
TABLE = 'TABLE',
COLUMN = 'COLUMN',
VIEW = 'VIEW',
FILTER = 'FILTER',
SORT = 'SORT',
VIEW_ROW_COLOR = 'VIEW_ROW_COLOR',
}

View File

@@ -1,9 +1,5 @@
import { ColumnType, FilterType } from './Api';
import {
OrgUserRoles,
ProjectRoles,
WorkspaceUserRoles,
} from './enums';
import { OrgUserRoles, ProjectRoles, WorkspaceUserRoles } from './enums';
import { PlanTitles } from './payment';
export const enumColors = {
@@ -239,7 +235,6 @@ export enum NcErrorType {
ERR_SUBSCRIPTION_CREATE_FAILED = 'ERR_SUBSCRIPTION_CREATE_FAILED',
ERR_STRIPE_WEBHOOK_VERIFICATION_FAILED = 'ERR_STRIPE_WEBHOOK_VERIFICATION_FAILED',
ERR_API_CLIENT_NOT_FOUND = 'ERR_API_CLIENT_NOT_FOUND',
}
export enum ROW_COLORING_MODE {

View File

@@ -16,6 +16,7 @@ export interface NcContext {
socket_id?: string;
nc_site_url?: string;
timezone?: string;
suppressDependencyEvaluation?: boolean;
}
export interface NcRequest extends Partial<Request> {

View File

@@ -47,6 +47,7 @@ import { getUniqueColumnAliasName } from '~/helpers/getUniqueName';
import ProjectMgrv2 from '~/db/sql-mgr/v2/ProjectMgrv2';
import { ViewRowColorService } from '~/services/view-row-color.service';
import { FiltersService } from '~/services/filters.service';
import { MetaDependencyEventHandler } from '~/services/meta-dependency/event-handler.service';
@Injectable()
export class ColumnsService extends ColumnsServiceCE {
@@ -57,6 +58,7 @@ export class ColumnsService extends ColumnsServiceCE {
protected readonly formulaColumnTypeChanger,
protected readonly viewRowColorService: ViewRowColorService,
protected readonly filtersService: FiltersService,
protected readonly metaDependencyEventHandler: MetaDependencyEventHandler,
) {
super(
metaService,
@@ -64,6 +66,7 @@ export class ColumnsService extends ColumnsServiceCE {
formulaColumnTypeChanger,
formulaColumnTypeChanger,
filtersService,
metaDependencyEventHandler,
);
}

View File

@@ -353,11 +353,13 @@ export default class Filter implements FilterType {
id,
);
await NocoCache.update(
context,
`${CacheScope.FILTER_EXP}:${id}`,
updateObj,
);
ncMeta.knex.attachToTransaction(async () => {
await NocoCache.update(
context,
`${CacheScope.FILTER_EXP}:${id}`,
updateObj,
);
});
// on update delete any optimised single query cache
{

View File

@@ -4,6 +4,7 @@ import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import multer from 'multer';
import { NotFoundHandlerModule } from './not-found-handler.module';
import { MetaDependencyEventHandler } from '~/services/meta-dependency/event-handler.service';
import { ViewsV3Service } from '~/services/v3/views-v3.service';
import { EventEmitterModule } from '~/modules/event-emitter/event-emitter.module';
import { JobsModule } from '~/modules/jobs/jobs.module';
@@ -161,6 +162,10 @@ import {
InternalApiModuleProvider,
InternalApiModules,
} from '~/controllers/internal/provider';
import {
MetaDependencyModuleProvider,
MetaDependencyServices,
} from '~/services/meta-dependency/meta-dependency.provider';
export const nocoModuleMetadata = {
imports: [
@@ -358,6 +363,11 @@ export const nocoModuleMetadata = {
...InternalApiModules,
InternalApiModuleProvider,
/* Dependency handler */
MetaDependencyEventHandler,
...MetaDependencyServices,
MetaDependencyModuleProvider,
],
exports: [
/* Generic */
@@ -407,6 +417,8 @@ export const nocoModuleMetadata = {
AttachmentUrlUploadHandler,
...InternalApiModules,
MetaDependencyEventHandler,
...MetaDependencyServices,
],
};

View File

@@ -12,6 +12,7 @@ import {
isSystemColumn,
isVirtualCol,
LongTextAiMetaProp,
MetaEventType,
NcApiVersion,
NcBaseError,
ncIsNull,
@@ -103,6 +104,7 @@ import { MetaTable } from '~/utils/globals';
import { parseMetaProp } from '~/utils/modelUtils';
import NocoSocket from '~/socket/NocoSocket';
import { DBErrorExtractor } from '~/helpers/db-error/extractor';
import { MetaDependencyEventHandler } from '~/services/meta-dependency/event-handler.service';
export type { ReusableParams } from '~/services/columns.service.type';
@@ -253,6 +255,7 @@ export class ColumnsService implements IColumnsService {
protected readonly formulaColumnTypeChanger: IFormulaColumnTypeChanger,
protected readonly viewRowColorService: ViewRowColorService,
protected readonly filtersService: FiltersService,
protected readonly metaDependencyEventHandler: MetaDependencyEventHandler,
) {}
async updateFormulas(
@@ -2056,6 +2059,15 @@ export class ColumnsService implements IColumnsService {
context,
columns: table.columns,
});
await this.metaDependencyEventHandler.handleEvent(
context,
{
eventType: MetaEventType.COLUMN_UPDATED,
oldEntity: oldColumn,
newEntity: updatedColumn,
},
ncMeta,
);
NocoSocket.broadcastEvent(
context,

View File

@@ -0,0 +1,76 @@
import { Inject, Injectable } from '@nestjs/common';
import {
META_DEPENDENCY_MODULE_PROVIDER_KEY,
type MetaDependencyEventRequest,
type MetaEventHandler,
} from './types';
import type { MetaEventType, NcContext } from 'nocodb-sdk';
import type { MetaService } from '~/meta/meta.service';
import Noco from '~/Noco';
@Injectable()
export class MetaDependencyEventHandler {
constructor(
@Inject(META_DEPENDENCY_MODULE_PROVIDER_KEY)
protected readonly metaEventHandlers: MetaEventHandler[],
) {
this.registerEvents(metaEventHandlers);
}
metaEventHandlerMap: Record<MetaEventType, MetaEventHandler[]> = {
COLUMN_ADDED: [],
COLUMN_DELETED: [],
COLUMN_UPDATED: [],
};
registerEvents(metaEventHandler: MetaEventHandler[]) {
for (const each of metaEventHandler) {
for (const eachType of each.triggerMetaEvents) {
this.metaEventHandlerMap[eachType] =
this.metaEventHandlerMap[eachType] ?? [];
this.metaEventHandlerMap[eachType].push(each);
}
}
}
async handleEvent(
context: NcContext,
param: MetaDependencyEventRequest,
ncMeta = Noco.ncMeta,
) {
// if suppressed, do not make further evaluation
if (context.suppressDependencyEvaluation) {
return;
}
// next context will have suppressDependencyEvaluation as true by default unless modules override it.
const nextContext = {
...context,
suppressDependencyEvaluation: true,
} as NcContext;
let trxNcMeta: MetaService;
try {
for (const handler of this.metaEventHandlerMap[param.eventType] ?? []) {
const affectedDependencies = await handler.getAffectedDependency(
nextContext,
param,
trxNcMeta ?? ncMeta,
);
if (affectedDependencies) {
trxNcMeta = trxNcMeta ?? (await ncMeta.startTransaction());
await handler.handle(
nextContext,
{
...param,
affectedDependencyResult: affectedDependencies,
},
trxNcMeta,
);
}
}
await trxNcMeta?.commit();
} catch (ex) {
await trxNcMeta?.rollback();
throw ex;
}
}
}

View File

@@ -0,0 +1,102 @@
import { Injectable } from '@nestjs/common';
import { MetaEventType, parseProp, UITypes } from 'nocodb-sdk';
import type { NcContext } from 'nocodb-sdk';
import type {
AffectedDependencyResult,
MetaDependencyEventRequest,
MetaEventHandler,
} from '../../types';
import { Filter } from '~/models';
import { MetaTable } from '~/cli';
import { parseMetaProp, stringifyMetaProp } from '~/utils/modelUtils';
import Noco from '~/Noco';
/**
* @class ColumnTimezoneUpdateDependencyHandler
* @description Handles updates to column timezones and propagates these changes to dependent filters.
* This class is responsible for identifying when a column's timezone changes (for DateTime, Date, or Formula types)
* and then updating any associated filter expressions that rely on that column's timezone.
*/
@Injectable()
export class ColumnTimezoneUpdateDependencyHandler implements MetaEventHandler {
triggerMetaEvents: MetaEventType[] = [MetaEventType.COLUMN_UPDATED];
async getAffectedDependency(
context: NcContext,
param: MetaDependencyEventRequest,
ncMeta = Noco.ncMeta,
): Promise<AffectedDependencyResult> {
let validForProcess = false;
const affectedColumnIds: string[] = [];
const newEntityMeta = parseProp(param.newEntity.meta);
const oldEntityMeta = parseProp(param.oldEntity.meta);
if (
[UITypes.DateTime, UITypes.Date].includes(param.newEntity.uidt) &&
// we leave it as is if the new meta timezone empty / not set
newEntityMeta.timezone &&
newEntityMeta.timezone !== oldEntityMeta.timezone
) {
validForProcess = true;
affectedColumnIds.push(param.newEntity.id);
} else if (
[UITypes.Formula].includes(param.newEntity.uidt) &&
newEntityMeta.display_column_meta?.timezone &&
newEntityMeta.display_column_meta?.timezone !==
oldEntityMeta.display_column_meta?.timezone
) {
validForProcess = true;
affectedColumnIds.push(param.newEntity.id);
}
if (validForProcess) {
return {
filters: await ncMeta.metaList2(
context.workspace_id,
context.base_id,
MetaTable.FILTER_EXP,
{
xcCondition: (qb) => qb.whereIn('fk_column_id', affectedColumnIds),
},
),
};
}
return undefined;
}
async handle(
context: NcContext,
param: MetaDependencyEventRequest & {
affectedDependencyResult: AffectedDependencyResult;
},
ncMeta = Noco.ncMeta,
): Promise<void> {
if (!param.affectedDependencyResult.filters?.length) {
return;
}
let timezone: string;
if (
[UITypes.DateTime, UITypes.Date].includes(param.newEntity.uidt) &&
parseProp(param.newEntity.meta).timezone
) {
timezone = parseProp(param.newEntity.meta).timezone;
} else if (
[UITypes.Formula].includes(param.newEntity.uidt) &&
parseProp(param.newEntity.meta).display_column_meta?.timezone
) {
timezone = parseProp(param.newEntity.meta).display_column_meta?.timezone;
}
for (const each of param.affectedDependencyResult.filters as Filter[]) {
each.meta = parseMetaProp(each);
each.meta.timezone = timezone;
await Filter.update(
{
...context,
base_id: each.base_id,
workspace_id: each.fk_workspace_id,
},
each.id,
{ ...each, meta: stringifyMetaProp(each) },
ncMeta,
);
}
}
}

View File

@@ -0,0 +1,13 @@
import {
META_DEPENDENCY_MODULE_PROVIDER_KEY,
type MetaEventHandler,
} from './types';
import { ColumnTimezoneUpdateDependencyHandler } from '~/services/meta-dependency/handler/column/column-timezone-update.handler';
export const MetaDependencyServices = [ColumnTimezoneUpdateDependencyHandler];
export const MetaDependencyModuleProvider = {
provide: META_DEPENDENCY_MODULE_PROVIDER_KEY,
useFactory: (...internalApiModules: MetaEventHandler[]) => internalApiModules,
inject: MetaDependencyServices,
};

View File

@@ -0,0 +1,41 @@
import type { MetaEntityType, MetaEventType, NcContext } from 'nocodb-sdk';
import type { MetaService } from '~/meta/meta.service';
export const META_DEPENDENCY_MODULE_PROVIDER_KEY = 'META_DEPENDENCY';
export interface AffectedDependencyResult {
bases?: any[];
models?: any[];
filters?: any[];
columns?: any[];
views?: any[];
sorts?: any[];
}
export interface MetaDependencyEventRequest {
eventType: MetaEventType;
oldEntity?: any;
newEntity?: any;
}
export interface MetaEventHandler {
triggerMetaEvents: MetaEventType[];
getAffectedDependency(
context: NcContext,
param: MetaDependencyEventRequest,
ncMeta?: MetaService,
): Promise<undefined | AffectedDependencyResult>;
handle(
context: NcContext,
param: MetaDependencyEventRequest & {
affectedDependencyResult: AffectedDependencyResult;
},
ncMeta?: MetaService,
): Promise<void>;
}
export interface MetaEvent<T> {
eventType: MetaEventType;
entityType: MetaEntityType;
entity: T;
}