Synchronization between Core Views and Workspace Views (#13461)

Closes https://github.com/twentyhq/core-team-issues/issues/1248

- Create listeners on each CRUD operation for all view related objects
and update the core views accordingly

Some fields have to be parsed since we changed the data model a little
bit when switching to core views.
This commit is contained in:
Raphaël Bosi
2025-07-28 14:41:27 +02:00
committed by GitHub
parent 400dd6d969
commit 7cf778b579
19 changed files with 1461 additions and 15 deletions

View File

@ -26,6 +26,7 @@ import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/vie
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
import { ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity';
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
import { transformViewFilterWorkspaceValueToCoreValue } from 'src/modules/view/utils/transform-view-filter-workspace-value-to-core-value';
@Command({
name: 'migrate:views-to-core',
@ -298,22 +299,12 @@ export class MigrateViewsToCoreCommand extends ActiveOrSuspendedWorkspacesMigrat
continue;
}
let parsedValue: JSON;
try {
parsedValue = JSON.parse(filter.value);
} catch {
throw new Error(
`Could not parse value to JSON for view filter ${filter.id} for workspace ${workspaceId}`,
);
}
const coreViewFilter: Partial<ViewFilter> = {
id: filter.id,
fieldMetadataId: filter.fieldMetadataId,
viewId: filter.viewId,
operand: filter.operand,
value: parsedValue,
value: transformViewFilterWorkspaceValueToCoreValue(filter.value),
viewFilterGroupId: filter.viewFilterGroupId,
workspaceId,
createdAt: new Date(filter.createdAt),

View File

@ -0,0 +1,12 @@
export type RelationFilterValue = {
isCurrentWorkspaceMemberSelected?: boolean;
selectedRecordIds: string[];
};
export type ViewFilterValue =
| string
| string[]
| RelationFilterValue
| Record<string, unknown>
| null
| undefined;

View File

@ -14,6 +14,7 @@ import {
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ViewFilterValue } from 'src/engine/metadata-modules/view/types/view-filter-value.type';
import { View } from 'src/engine/metadata-modules/view/view.entity';
@Entity({ name: 'viewFilter', schema: 'core' })
@ -31,7 +32,7 @@ export class ViewFilter {
operand: string;
@Column({ nullable: false, type: 'jsonb' })
value: JSON;
value: ViewFilterValue;
@Column({ nullable: true, type: 'uuid' })
viewFilterGroupId?: string | null;

View File

@ -0,0 +1,209 @@
import { Inject, Injectable, Logger } from '@nestjs/common';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import {
ViewException,
ViewExceptionCode,
} from 'src/modules/view/views.exception';
type EntityWithId = { id: string };
type SyncOperations<T extends EntityWithId> = {
create: (workspaceId: string, entity: T) => Promise<void>;
update: (
workspaceId: string,
entity: T,
diff?: Partial<ObjectRecordDiff<T>>,
) => Promise<void>;
delete: (workspaceId: string, entity: Pick<T, 'id'>) => Promise<void>;
destroy: (workspaceId: string, entity: Pick<T, 'id'>) => Promise<void>;
restore: (workspaceId: string, entity: Pick<T, 'id'>) => Promise<void>;
};
@Injectable()
export abstract class BaseViewSyncListener<T extends EntityWithId> {
@Inject(FeatureFlagService)
protected readonly featureFlagService: FeatureFlagService;
@Inject(ExceptionHandlerService)
protected readonly exceptionHandlerService: ExceptionHandlerService;
protected readonly logger: Logger;
constructor(
protected readonly syncOperations: SyncOperations<T>,
loggerName: string,
protected readonly entityTypeName: string,
) {
this.logger = new Logger(loggerName);
}
protected async handleCreated(
batchEvent: WorkspaceEventBatch<ObjectRecordCreateEvent<T>>,
): Promise<void> {
const isEnabled = await this.isFeatureFlagEnabled(batchEvent.workspaceId);
if (!isEnabled) {
return;
}
for (const event of batchEvent.events) {
try {
await this.syncOperations.create(
batchEvent.workspaceId,
event.properties.after,
);
} catch (error) {
this.captureException(
error,
batchEvent.workspaceId,
'create',
event.properties.after.id,
);
}
}
}
protected async handleUpdated(
batchEvent: WorkspaceEventBatch<ObjectRecordUpdateEvent<T>>,
): Promise<void> {
const isEnabled = await this.isFeatureFlagEnabled(batchEvent.workspaceId);
if (!isEnabled) {
return;
}
for (const event of batchEvent.events) {
try {
await this.syncOperations.update(
batchEvent.workspaceId,
event.properties.after,
event.properties.diff,
);
} catch (error) {
this.captureException(
error,
batchEvent.workspaceId,
'update',
event.properties.after.id,
);
}
}
}
protected async handleDeleted(
batchEvent: WorkspaceEventBatch<ObjectRecordDeleteEvent<T>>,
): Promise<void> {
const isEnabled = await this.isFeatureFlagEnabled(batchEvent.workspaceId);
if (!isEnabled) {
return;
}
for (const event of batchEvent.events) {
try {
await this.syncOperations.delete(
batchEvent.workspaceId,
event.properties.before,
);
} catch (error) {
this.captureException(
error,
batchEvent.workspaceId,
'delete',
event.properties.before.id,
);
}
}
}
protected async handleDestroyed(
batchEvent: WorkspaceEventBatch<ObjectRecordDestroyEvent<T>>,
): Promise<void> {
const isEnabled = await this.isFeatureFlagEnabled(batchEvent.workspaceId);
if (!isEnabled) {
return;
}
for (const event of batchEvent.events) {
try {
await this.syncOperations.destroy(
batchEvent.workspaceId,
event.properties.before,
);
} catch (error) {
this.captureException(
error,
batchEvent.workspaceId,
'destroy',
event.properties.before.id,
);
}
}
}
protected async handleRestored(
batchEvent: WorkspaceEventBatch<ObjectRecordRestoreEvent<T>>,
): Promise<void> {
const isEnabled = await this.isFeatureFlagEnabled(batchEvent.workspaceId);
if (!isEnabled) {
return;
}
for (const event of batchEvent.events) {
try {
await this.syncOperations.restore(
batchEvent.workspaceId,
event.properties.after,
);
} catch (error) {
this.captureException(
error,
batchEvent.workspaceId,
'restore',
event.properties.after.id,
);
}
}
}
private async isFeatureFlagEnabled(workspaceId: string): Promise<boolean> {
const featureFlags =
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
return featureFlags.IS_CORE_VIEW_SYNCING_ENABLED;
}
private captureException(
error: Error,
workspaceId: string,
operation: string,
entityId: string,
) {
const viewException = new ViewException(
`Failed to sync ${this.entityTypeName} ${entityId} to core: ${error.message}`,
ViewExceptionCode.CORE_VIEW_SYNC_ERROR,
);
this.exceptionHandlerService.captureExceptions([viewException], {
workspace: {
id: workspaceId,
},
additionalData: {
entityId: entityId,
entityType: this.entityTypeName,
operation: operation,
},
});
}
}

View File

@ -0,0 +1,81 @@
import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { ViewFieldSyncService } from 'src/modules/view/services/view-field-sync.service';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
import { BaseViewSyncListener } from './base-view-sync.listener';
@Injectable()
export class ViewFieldListener extends BaseViewSyncListener<ViewFieldWorkspaceEntity> {
constructor(viewFieldSyncService: ViewFieldSyncService) {
super(
{
create:
viewFieldSyncService.createCoreViewField.bind(viewFieldSyncService),
update:
viewFieldSyncService.updateCoreViewField.bind(viewFieldSyncService),
delete:
viewFieldSyncService.deleteCoreViewField.bind(viewFieldSyncService),
destroy:
viewFieldSyncService.destroyCoreViewField.bind(viewFieldSyncService),
restore:
viewFieldSyncService.restoreCoreViewField.bind(viewFieldSyncService),
},
ViewFieldListener.name,
'view field',
);
}
@OnDatabaseBatchEvent('viewField', DatabaseEventAction.CREATED)
async handleViewFieldCreated(
batchEvent: WorkspaceEventBatch<
ObjectRecordCreateEvent<ViewFieldWorkspaceEntity>
>,
) {
return this.handleCreated(batchEvent);
}
@OnDatabaseBatchEvent('viewField', DatabaseEventAction.UPDATED)
async handleViewFieldUpdated(
batchEvent: WorkspaceEventBatch<
ObjectRecordUpdateEvent<ViewFieldWorkspaceEntity>
>,
) {
return this.handleUpdated(batchEvent);
}
@OnDatabaseBatchEvent('viewField', DatabaseEventAction.DELETED)
async handleViewFieldDeleted(
batchEvent: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ViewFieldWorkspaceEntity>
>,
) {
return this.handleDeleted(batchEvent);
}
@OnDatabaseBatchEvent('viewField', DatabaseEventAction.DESTROYED)
async handleViewFieldDestroyed(
batchEvent: WorkspaceEventBatch<
ObjectRecordDestroyEvent<ViewFieldWorkspaceEntity>
>,
) {
return this.handleDestroyed(batchEvent);
}
@OnDatabaseBatchEvent('viewField', DatabaseEventAction.RESTORED)
async handleViewFieldRestored(
batchEvent: WorkspaceEventBatch<
ObjectRecordRestoreEvent<ViewFieldWorkspaceEntity>
>,
) {
return this.handleRestored(batchEvent);
}
}

View File

@ -0,0 +1,86 @@
import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { ViewFilterGroupSyncService } from 'src/modules/view/services/view-filter-group-sync.service';
import { ViewFilterGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity';
import { BaseViewSyncListener } from './base-view-sync.listener';
@Injectable()
export class ViewFilterGroupListener extends BaseViewSyncListener<ViewFilterGroupWorkspaceEntity> {
constructor(viewFilterGroupSyncService: ViewFilterGroupSyncService) {
super(
{
create: viewFilterGroupSyncService.createCoreViewFilterGroup.bind(
viewFilterGroupSyncService,
),
update: viewFilterGroupSyncService.updateCoreViewFilterGroup.bind(
viewFilterGroupSyncService,
),
delete: viewFilterGroupSyncService.deleteCoreViewFilterGroup.bind(
viewFilterGroupSyncService,
),
destroy: viewFilterGroupSyncService.destroyCoreViewFilterGroup.bind(
viewFilterGroupSyncService,
),
restore: viewFilterGroupSyncService.restoreCoreViewFilterGroup.bind(
viewFilterGroupSyncService,
),
},
ViewFilterGroupListener.name,
'view filter group',
);
}
@OnDatabaseBatchEvent('viewFilterGroup', DatabaseEventAction.CREATED)
async handleViewFilterGroupCreated(
batchEvent: WorkspaceEventBatch<
ObjectRecordCreateEvent<ViewFilterGroupWorkspaceEntity>
>,
) {
return this.handleCreated(batchEvent);
}
@OnDatabaseBatchEvent('viewFilterGroup', DatabaseEventAction.UPDATED)
async handleViewFilterGroupUpdated(
batchEvent: WorkspaceEventBatch<
ObjectRecordUpdateEvent<ViewFilterGroupWorkspaceEntity>
>,
) {
return this.handleUpdated(batchEvent);
}
@OnDatabaseBatchEvent('viewFilterGroup', DatabaseEventAction.DELETED)
async handleViewFilterGroupDeleted(
batchEvent: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ViewFilterGroupWorkspaceEntity>
>,
) {
return this.handleDeleted(batchEvent);
}
@OnDatabaseBatchEvent('viewFilterGroup', DatabaseEventAction.DESTROYED)
async handleViewFilterGroupDestroyed(
batchEvent: WorkspaceEventBatch<
ObjectRecordDestroyEvent<ViewFilterGroupWorkspaceEntity>
>,
) {
return this.handleDestroyed(batchEvent);
}
@OnDatabaseBatchEvent('viewFilterGroup', DatabaseEventAction.RESTORED)
async handleViewFilterGroupRestored(
batchEvent: WorkspaceEventBatch<
ObjectRecordRestoreEvent<ViewFilterGroupWorkspaceEntity>
>,
) {
return this.handleRestored(batchEvent);
}
}

View File

@ -0,0 +1,86 @@
import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { ViewFilterSyncService } from 'src/modules/view/services/view-filter-sync.service';
import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity';
import { BaseViewSyncListener } from './base-view-sync.listener';
@Injectable()
export class ViewFilterListener extends BaseViewSyncListener<ViewFilterWorkspaceEntity> {
constructor(viewFilterSyncService: ViewFilterSyncService) {
super(
{
create: viewFilterSyncService.createCoreViewFilter.bind(
viewFilterSyncService,
),
update: viewFilterSyncService.updateCoreViewFilter.bind(
viewFilterSyncService,
),
delete: viewFilterSyncService.deleteCoreViewFilter.bind(
viewFilterSyncService,
),
destroy: viewFilterSyncService.destroyCoreViewFilter.bind(
viewFilterSyncService,
),
restore: viewFilterSyncService.restoreCoreViewFilter.bind(
viewFilterSyncService,
),
},
ViewFilterListener.name,
'view filter',
);
}
@OnDatabaseBatchEvent('viewFilter', DatabaseEventAction.CREATED)
async handleViewFilterCreated(
batchEvent: WorkspaceEventBatch<
ObjectRecordCreateEvent<ViewFilterWorkspaceEntity>
>,
) {
return this.handleCreated(batchEvent);
}
@OnDatabaseBatchEvent('viewFilter', DatabaseEventAction.UPDATED)
async handleViewFilterUpdated(
batchEvent: WorkspaceEventBatch<
ObjectRecordUpdateEvent<ViewFilterWorkspaceEntity>
>,
) {
return this.handleUpdated(batchEvent);
}
@OnDatabaseBatchEvent('viewFilter', DatabaseEventAction.DELETED)
async handleViewFilterDeleted(
batchEvent: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ViewFilterWorkspaceEntity>
>,
) {
return this.handleDeleted(batchEvent);
}
@OnDatabaseBatchEvent('viewFilter', DatabaseEventAction.DESTROYED)
async handleViewFilterDestroyed(
batchEvent: WorkspaceEventBatch<
ObjectRecordDestroyEvent<ViewFilterWorkspaceEntity>
>,
) {
return this.handleDestroyed(batchEvent);
}
@OnDatabaseBatchEvent('viewFilter', DatabaseEventAction.RESTORED)
async handleViewFilterRestored(
batchEvent: WorkspaceEventBatch<
ObjectRecordRestoreEvent<ViewFilterWorkspaceEntity>
>,
) {
return this.handleRestored(batchEvent);
}
}

View File

@ -0,0 +1,81 @@
import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { ViewGroupSyncService } from 'src/modules/view/services/view-group-sync.service';
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
import { BaseViewSyncListener } from './base-view-sync.listener';
@Injectable()
export class ViewGroupListener extends BaseViewSyncListener<ViewGroupWorkspaceEntity> {
constructor(viewGroupSyncService: ViewGroupSyncService) {
super(
{
create:
viewGroupSyncService.createCoreViewGroup.bind(viewGroupSyncService),
update:
viewGroupSyncService.updateCoreViewGroup.bind(viewGroupSyncService),
delete:
viewGroupSyncService.deleteCoreViewGroup.bind(viewGroupSyncService),
destroy:
viewGroupSyncService.destroyCoreViewGroup.bind(viewGroupSyncService),
restore:
viewGroupSyncService.restoreCoreViewGroup.bind(viewGroupSyncService),
},
ViewGroupListener.name,
'view group',
);
}
@OnDatabaseBatchEvent('viewGroup', DatabaseEventAction.CREATED)
async handleViewGroupCreated(
batchEvent: WorkspaceEventBatch<
ObjectRecordCreateEvent<ViewGroupWorkspaceEntity>
>,
) {
return this.handleCreated(batchEvent);
}
@OnDatabaseBatchEvent('viewGroup', DatabaseEventAction.UPDATED)
async handleViewGroupUpdated(
batchEvent: WorkspaceEventBatch<
ObjectRecordUpdateEvent<ViewGroupWorkspaceEntity>
>,
) {
return this.handleUpdated(batchEvent);
}
@OnDatabaseBatchEvent('viewGroup', DatabaseEventAction.DELETED)
async handleViewGroupDeleted(
batchEvent: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ViewGroupWorkspaceEntity>
>,
) {
return this.handleDeleted(batchEvent);
}
@OnDatabaseBatchEvent('viewGroup', DatabaseEventAction.DESTROYED)
async handleViewGroupDestroyed(
batchEvent: WorkspaceEventBatch<
ObjectRecordDestroyEvent<ViewGroupWorkspaceEntity>
>,
) {
return this.handleDestroyed(batchEvent);
}
@OnDatabaseBatchEvent('viewGroup', DatabaseEventAction.RESTORED)
async handleViewGroupRestored(
batchEvent: WorkspaceEventBatch<
ObjectRecordRestoreEvent<ViewGroupWorkspaceEntity>
>,
) {
return this.handleRestored(batchEvent);
}
}

View File

@ -0,0 +1,81 @@
import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { ViewSortSyncService } from 'src/modules/view/services/view-sort-sync.service';
import { ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity';
import { BaseViewSyncListener } from './base-view-sync.listener';
@Injectable()
export class ViewSortListener extends BaseViewSyncListener<ViewSortWorkspaceEntity> {
constructor(viewSortSyncService: ViewSortSyncService) {
super(
{
create:
viewSortSyncService.createCoreViewSort.bind(viewSortSyncService),
update:
viewSortSyncService.updateCoreViewSort.bind(viewSortSyncService),
delete:
viewSortSyncService.deleteCoreViewSort.bind(viewSortSyncService),
destroy:
viewSortSyncService.destroyCoreViewSort.bind(viewSortSyncService),
restore:
viewSortSyncService.restoreCoreViewSort.bind(viewSortSyncService),
},
ViewSortListener.name,
'view sort',
);
}
@OnDatabaseBatchEvent('viewSort', DatabaseEventAction.CREATED)
async handleViewSortCreated(
batchEvent: WorkspaceEventBatch<
ObjectRecordCreateEvent<ViewSortWorkspaceEntity>
>,
) {
return this.handleCreated(batchEvent);
}
@OnDatabaseBatchEvent('viewSort', DatabaseEventAction.UPDATED)
async handleViewSortUpdated(
batchEvent: WorkspaceEventBatch<
ObjectRecordUpdateEvent<ViewSortWorkspaceEntity>
>,
) {
return this.handleUpdated(batchEvent);
}
@OnDatabaseBatchEvent('viewSort', DatabaseEventAction.DELETED)
async handleViewSortDeleted(
batchEvent: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ViewSortWorkspaceEntity>
>,
) {
return this.handleDeleted(batchEvent);
}
@OnDatabaseBatchEvent('viewSort', DatabaseEventAction.DESTROYED)
async handleViewSortDestroyed(
batchEvent: WorkspaceEventBatch<
ObjectRecordDestroyEvent<ViewSortWorkspaceEntity>
>,
) {
return this.handleDestroyed(batchEvent);
}
@OnDatabaseBatchEvent('viewSort', DatabaseEventAction.RESTORED)
async handleViewSortRestored(
batchEvent: WorkspaceEventBatch<
ObjectRecordRestoreEvent<ViewSortWorkspaceEntity>
>,
) {
return this.handleRestored(batchEvent);
}
}

View File

@ -0,0 +1,76 @@
import { Injectable } from '@nestjs/common';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { ViewSyncService } from 'src/modules/view/services/view-sync.service';
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
import { BaseViewSyncListener } from './base-view-sync.listener';
@Injectable()
export class ViewListener extends BaseViewSyncListener<ViewWorkspaceEntity> {
constructor(viewSyncService: ViewSyncService) {
super(
{
create: viewSyncService.createCoreView.bind(viewSyncService),
update: viewSyncService.updateCoreView.bind(viewSyncService),
delete: viewSyncService.deleteCoreView.bind(viewSyncService),
destroy: viewSyncService.destroyCoreView.bind(viewSyncService),
restore: viewSyncService.restoreCoreView.bind(viewSyncService),
},
ViewListener.name,
'view',
);
}
@OnDatabaseBatchEvent('view', DatabaseEventAction.CREATED)
async handleViewCreated(
batchEvent: WorkspaceEventBatch<
ObjectRecordCreateEvent<ViewWorkspaceEntity>
>,
) {
return this.handleCreated(batchEvent);
}
@OnDatabaseBatchEvent('view', DatabaseEventAction.UPDATED)
async handleViewUpdated(
batchEvent: WorkspaceEventBatch<
ObjectRecordUpdateEvent<ViewWorkspaceEntity>
>,
) {
return this.handleUpdated(batchEvent);
}
@OnDatabaseBatchEvent('view', DatabaseEventAction.DELETED)
async handleViewDeleted(
batchEvent: WorkspaceEventBatch<
ObjectRecordDeleteEvent<ViewWorkspaceEntity>
>,
) {
return this.handleDeleted(batchEvent);
}
@OnDatabaseBatchEvent('view', DatabaseEventAction.DESTROYED)
async handleViewDestroyed(
batchEvent: WorkspaceEventBatch<
ObjectRecordDestroyEvent<ViewWorkspaceEntity>
>,
) {
return this.handleDestroyed(batchEvent);
}
@OnDatabaseBatchEvent('view', DatabaseEventAction.RESTORED)
async handleViewRestored(
batchEvent: WorkspaceEventBatch<
ObjectRecordRestoreEvent<ViewWorkspaceEntity>
>,
) {
return this.handleRestored(batchEvent);
}
}

View File

@ -0,0 +1,104 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ViewField } from 'src/engine/metadata-modules/view/view-field.entity';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
@Injectable()
export class ViewFieldSyncService {
constructor(
@InjectRepository(ViewField, 'core')
private readonly coreViewFieldRepository: Repository<ViewField>,
) {}
private parseUpdateDataFromDiff(
diff: Partial<ObjectRecordDiff<ViewFieldWorkspaceEntity>>,
): Partial<ViewField> {
const updateData: Record<string, unknown> = {};
for (const key of Object.keys(diff)) {
const diffValue = diff[key as keyof ViewFieldWorkspaceEntity];
if (isDefined(diffValue)) {
updateData[key] = diffValue.after;
}
}
return updateData as Partial<ViewField>;
}
public async createCoreViewField(
workspaceId: string,
workspaceViewField: ViewFieldWorkspaceEntity,
): Promise<void> {
const coreViewField: Partial<ViewField> = {
id: workspaceViewField.id,
fieldMetadataId: workspaceViewField.fieldMetadataId,
viewId: workspaceViewField.viewId,
position: workspaceViewField.position,
isVisible: workspaceViewField.isVisible,
size: workspaceViewField.size,
workspaceId,
createdAt: new Date(workspaceViewField.createdAt),
updatedAt: new Date(workspaceViewField.updatedAt),
deletedAt: workspaceViewField.deletedAt
? new Date(workspaceViewField.deletedAt)
: null,
};
await this.coreViewFieldRepository.save(coreViewField);
}
public async updateCoreViewField(
workspaceId: string,
workspaceViewField: Pick<ViewFieldWorkspaceEntity, 'id'>,
diff?: Partial<ObjectRecordDiff<ViewFieldWorkspaceEntity>>,
): Promise<void> {
if (!diff || Object.keys(diff).length === 0) {
return;
}
const updateData = this.parseUpdateDataFromDiff(diff);
if (Object.keys(updateData).length > 0) {
await this.coreViewFieldRepository.update(
{ id: workspaceViewField.id, workspaceId },
updateData,
);
}
}
public async deleteCoreViewField(
workspaceId: string,
workspaceViewField: Pick<ViewFieldWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFieldRepository.softDelete({
id: workspaceViewField.id,
workspaceId,
});
}
public async destroyCoreViewField(
workspaceId: string,
workspaceViewField: Pick<ViewFieldWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFieldRepository.delete({
id: workspaceViewField.id,
workspaceId,
});
}
public async restoreCoreViewField(
workspaceId: string,
workspaceViewField: Pick<ViewFieldWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFieldRepository.restore({
id: workspaceViewField.id,
workspaceId,
});
}
}

View File

@ -0,0 +1,110 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view/enums/view-filter-group-logical-operator';
import { ViewFilterGroup } from 'src/engine/metadata-modules/view/view-filter-group.entity';
import { ViewFilterGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter-group.workspace-entity';
@Injectable()
export class ViewFilterGroupSyncService {
constructor(
@InjectRepository(ViewFilterGroup, 'core')
private readonly coreViewFilterGroupRepository: Repository<ViewFilterGroup>,
) {}
private parseUpdateDataFromDiff(
diff: Partial<ObjectRecordDiff<ViewFilterGroupWorkspaceEntity>>,
): Partial<ViewFilterGroup> {
const updateData: Record<string, unknown> = {};
for (const key of Object.keys(diff)) {
const diffValue = diff[key as keyof ViewFilterGroupWorkspaceEntity];
if (isDefined(diffValue)) {
if (key === 'logicalOperator') {
updateData[key] = diffValue.after as ViewFilterGroupLogicalOperator;
} else {
updateData[key] = diffValue.after;
}
}
}
return updateData as Partial<ViewFilterGroup>;
}
public async createCoreViewFilterGroup(
workspaceId: string,
workspaceViewFilterGroup: ViewFilterGroupWorkspaceEntity,
): Promise<void> {
const coreViewFilterGroup: Partial<ViewFilterGroup> = {
id: workspaceViewFilterGroup.id,
viewId: workspaceViewFilterGroup.viewId,
logicalOperator:
workspaceViewFilterGroup.logicalOperator as ViewFilterGroupLogicalOperator,
parentViewFilterGroupId: workspaceViewFilterGroup.parentViewFilterGroupId,
positionInViewFilterGroup:
workspaceViewFilterGroup.positionInViewFilterGroup,
workspaceId,
createdAt: new Date(workspaceViewFilterGroup.createdAt),
updatedAt: new Date(workspaceViewFilterGroup.updatedAt),
deletedAt: workspaceViewFilterGroup.deletedAt
? new Date(workspaceViewFilterGroup.deletedAt)
: null,
};
await this.coreViewFilterGroupRepository.save(coreViewFilterGroup);
}
public async updateCoreViewFilterGroup(
workspaceId: string,
workspaceViewFilterGroup: Pick<ViewFilterGroupWorkspaceEntity, 'id'>,
diff?: Partial<ObjectRecordDiff<ViewFilterGroupWorkspaceEntity>>,
): Promise<void> {
if (!diff || Object.keys(diff).length === 0) {
return;
}
const updateData = this.parseUpdateDataFromDiff(diff);
if (Object.keys(updateData).length > 0) {
await this.coreViewFilterGroupRepository.update(
{ id: workspaceViewFilterGroup.id, workspaceId },
updateData,
);
}
}
public async deleteCoreViewFilterGroup(
workspaceId: string,
workspaceViewFilterGroup: Pick<ViewFilterGroupWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFilterGroupRepository.softDelete({
id: workspaceViewFilterGroup.id,
workspaceId,
});
}
public async destroyCoreViewFilterGroup(
workspaceId: string,
workspaceViewFilterGroup: Pick<ViewFilterGroupWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFilterGroupRepository.delete({
id: workspaceViewFilterGroup.id,
workspaceId,
});
}
public async restoreCoreViewFilterGroup(
workspaceId: string,
workspaceViewFilterGroup: ViewFilterGroupWorkspaceEntity,
): Promise<void> {
await this.coreViewFilterGroupRepository.restore({
id: workspaceViewFilterGroup.id,
workspaceId,
});
}
}

View File

@ -0,0 +1,121 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ViewFilter } from 'src/engine/metadata-modules/view/view-filter.entity';
import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity';
import { transformViewFilterWorkspaceValueToCoreValue } from 'src/modules/view/utils/transform-view-filter-workspace-value-to-core-value';
@Injectable()
export class ViewFilterSyncService {
constructor(
@InjectRepository(ViewFilter, 'core')
private readonly coreViewFilterRepository: Repository<ViewFilter>,
) {}
private parseUpdateDataFromDiff(
diff: Partial<ObjectRecordDiff<ViewFilterWorkspaceEntity>>,
): Partial<ViewFilter> {
const updateData: Record<string, unknown> = {};
for (const key of Object.keys(diff)) {
const diffValue = diff[key as keyof ViewFilterWorkspaceEntity];
if (isDefined(diffValue)) {
if (key === 'value' && typeof diffValue.after === 'string') {
updateData[key] = transformViewFilterWorkspaceValueToCoreValue(
diffValue.after,
);
} else {
updateData[key] = diffValue.after;
}
}
}
return updateData as Partial<ViewFilter>;
}
public async createCoreViewFilter(
workspaceId: string,
workspaceViewFilter: ViewFilterWorkspaceEntity,
): Promise<void> {
if (!workspaceViewFilter.viewId) {
return;
}
const coreViewFilter: Partial<ViewFilter> = {
id: workspaceViewFilter.id,
fieldMetadataId: workspaceViewFilter.fieldMetadataId,
viewId: workspaceViewFilter.viewId,
operand: workspaceViewFilter.operand,
value: transformViewFilterWorkspaceValueToCoreValue(
workspaceViewFilter.value,
),
viewFilterGroupId: workspaceViewFilter.viewFilterGroupId,
workspaceId,
createdAt: new Date(workspaceViewFilter.createdAt),
updatedAt: new Date(workspaceViewFilter.updatedAt),
deletedAt: workspaceViewFilter.deletedAt
? new Date(workspaceViewFilter.deletedAt)
: null,
};
await this.coreViewFilterRepository.save(coreViewFilter);
}
public async updateCoreViewFilter(
workspaceId: string,
workspaceViewFilter: ViewFilterWorkspaceEntity,
diff?: Partial<ObjectRecordDiff<ViewFilterWorkspaceEntity>>,
): Promise<void> {
if (!workspaceViewFilter.viewId) {
return;
}
if (!diff || Object.keys(diff).length === 0) {
return;
}
const updateData = this.parseUpdateDataFromDiff(diff);
if (Object.keys(updateData).length > 0) {
await this.coreViewFilterRepository.update(
{ id: workspaceViewFilter.id, workspaceId },
updateData,
);
}
}
public async deleteCoreViewFilter(
workspaceId: string,
workspaceViewFilter: Pick<ViewFilterWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFilterRepository.softDelete({
id: workspaceViewFilter.id,
workspaceId,
});
}
public async destroyCoreViewFilter(
workspaceId: string,
workspaceViewFilter: Pick<ViewFilterWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFilterRepository.delete({
id: workspaceViewFilter.id,
workspaceId,
});
}
public async restoreCoreViewFilter(
workspaceId: string,
workspaceViewFilter: Pick<ViewFilterWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewFilterRepository.restore({
id: workspaceViewFilter.id,
workspaceId,
});
}
}

View File

@ -0,0 +1,112 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ViewGroup } from 'src/engine/metadata-modules/view/view-group.entity';
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
@Injectable()
export class ViewGroupSyncService {
constructor(
@InjectRepository(ViewGroup, 'core')
private readonly coreViewGroupRepository: Repository<ViewGroup>,
) {}
private parseUpdateDataFromDiff(
diff: Partial<ObjectRecordDiff<ViewGroupWorkspaceEntity>>,
): Partial<ViewGroup> {
const updateData: Record<string, unknown> = {};
for (const key of Object.keys(diff)) {
const diffValue = diff[key as keyof ViewGroupWorkspaceEntity];
if (isDefined(diffValue)) {
updateData[key] = diffValue.after;
}
}
return updateData as Partial<ViewGroup>;
}
public async createCoreViewGroup(
workspaceId: string,
workspaceViewGroup: ViewGroupWorkspaceEntity,
): Promise<void> {
if (!workspaceViewGroup.viewId) {
return;
}
const coreViewGroup: Partial<ViewGroup> = {
id: workspaceViewGroup.id,
fieldMetadataId: workspaceViewGroup.fieldMetadataId,
viewId: workspaceViewGroup.viewId,
fieldValue: workspaceViewGroup.fieldValue,
isVisible: workspaceViewGroup.isVisible,
position: workspaceViewGroup.position,
workspaceId,
createdAt: new Date(workspaceViewGroup.createdAt),
updatedAt: new Date(workspaceViewGroup.updatedAt),
deletedAt: workspaceViewGroup.deletedAt
? new Date(workspaceViewGroup.deletedAt)
: null,
};
await this.coreViewGroupRepository.save(coreViewGroup);
}
public async updateCoreViewGroup(
workspaceId: string,
workspaceViewGroup: ViewGroupWorkspaceEntity,
diff?: Partial<ObjectRecordDiff<ViewGroupWorkspaceEntity>>,
): Promise<void> {
if (!workspaceViewGroup.viewId) {
return;
}
if (!diff || Object.keys(diff).length === 0) {
return;
}
const updateData = this.parseUpdateDataFromDiff(diff);
if (Object.keys(updateData).length > 0) {
await this.coreViewGroupRepository.update(
{ id: workspaceViewGroup.id, workspaceId },
updateData,
);
}
}
public async deleteCoreViewGroup(
workspaceId: string,
workspaceViewGroup: Pick<ViewGroupWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewGroupRepository.softDelete({
id: workspaceViewGroup.id,
workspaceId,
});
}
public async destroyCoreViewGroup(
workspaceId: string,
workspaceViewGroup: Pick<ViewGroupWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewGroupRepository.delete({
id: workspaceViewGroup.id,
workspaceId,
});
}
public async restoreCoreViewGroup(
workspaceId: string,
workspaceViewGroup: Pick<ViewGroupWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewGroupRepository.restore({
id: workspaceViewGroup.id,
workspaceId,
});
}
}

View File

@ -0,0 +1,120 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ViewSortDirection } from 'src/engine/metadata-modules/view/enums/view-sort-direction';
import { ViewSort } from 'src/engine/metadata-modules/view/view-sort.entity';
import { ViewSortWorkspaceEntity } from 'src/modules/view/standard-objects/view-sort.workspace-entity';
@Injectable()
export class ViewSortSyncService {
constructor(
@InjectRepository(ViewSort, 'core')
private readonly coreViewSortRepository: Repository<ViewSort>,
) {}
private parseUpdateDataFromDiff(
diff: Partial<ObjectRecordDiff<ViewSortWorkspaceEntity>>,
): Partial<ViewSort> {
const updateData: Record<string, unknown> = {};
for (const key of Object.keys(diff)) {
const diffValue = diff[key as keyof ViewSortWorkspaceEntity];
if (isDefined(diffValue)) {
if (key === 'direction') {
updateData[key] = (
diffValue.after as string
).toUpperCase() as ViewSortDirection;
} else {
updateData[key] = diffValue.after;
}
}
}
return updateData as Partial<ViewSort>;
}
public async createCoreViewSort(
workspaceId: string,
workspaceViewSort: ViewSortWorkspaceEntity,
): Promise<void> {
if (!workspaceViewSort.viewId) {
return;
}
const direction =
workspaceViewSort.direction.toUpperCase() as ViewSortDirection;
const coreViewSort: Partial<ViewSort> = {
id: workspaceViewSort.id,
fieldMetadataId: workspaceViewSort.fieldMetadataId,
viewId: workspaceViewSort.viewId,
direction: direction,
workspaceId,
createdAt: new Date(workspaceViewSort.createdAt),
updatedAt: new Date(workspaceViewSort.updatedAt),
deletedAt: workspaceViewSort.deletedAt
? new Date(workspaceViewSort.deletedAt)
: null,
};
await this.coreViewSortRepository.save(coreViewSort);
}
public async updateCoreViewSort(
workspaceId: string,
workspaceViewSort: ViewSortWorkspaceEntity,
diff?: Partial<ObjectRecordDiff<ViewSortWorkspaceEntity>>,
): Promise<void> {
if (!workspaceViewSort.viewId) {
return;
}
if (!diff || Object.keys(diff).length === 0) {
return;
}
const updateData = this.parseUpdateDataFromDiff(diff);
if (Object.keys(updateData).length > 0) {
await this.coreViewSortRepository.update(
{ id: workspaceViewSort.id, workspaceId },
updateData,
);
}
}
public async deleteCoreViewSort(
workspaceId: string,
workspaceViewSort: Pick<ViewSortWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewSortRepository.softDelete({
id: workspaceViewSort.id,
workspaceId,
});
}
public async destroyCoreViewSort(
workspaceId: string,
workspaceViewSort: Pick<ViewSortWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewSortRepository.delete({
id: workspaceViewSort.id,
workspaceId,
});
}
public async restoreCoreViewSort(
workspaceId: string,
workspaceViewSort: Pick<ViewSortWorkspaceEntity, 'id'>,
): Promise<void> {
await this.coreViewSortRepository.restore({
id: workspaceViewSort.id,
workspaceId,
});
}
}

View File

@ -0,0 +1,121 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
import { View } from 'src/engine/metadata-modules/view/view.entity';
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
@Injectable()
export class ViewSyncService {
constructor(
@InjectRepository(View, 'core')
private readonly coreViewRepository: Repository<View>,
) {}
private parseUpdateDataFromDiff(
diff: Partial<ObjectRecordDiff<ViewWorkspaceEntity>>,
): Partial<View> {
const updateData: Record<string, unknown> = {};
for (const key of Object.keys(diff)) {
const diffValue = diff[key as keyof ViewWorkspaceEntity];
if (isDefined(diffValue)) {
if (key === 'openRecordIn') {
updateData[key] =
diffValue.after === 'SIDE_PANEL'
? ViewOpenRecordIn.SIDE_PANEL
: ViewOpenRecordIn.RECORD_PAGE;
} else {
updateData[key] = diffValue.after;
}
}
}
return updateData as Partial<View>;
}
public async createCoreView(
workspaceId: string,
workspaceView: ViewWorkspaceEntity,
): Promise<void> {
const coreView: Partial<View> = {
id: workspaceView.id,
name: workspaceView.name,
objectMetadataId: workspaceView.objectMetadataId,
type: workspaceView.type,
key: workspaceView.key,
icon: workspaceView.icon,
position: workspaceView.position,
isCompact: workspaceView.isCompact,
openRecordIn:
workspaceView.openRecordIn === 'SIDE_PANEL'
? ViewOpenRecordIn.SIDE_PANEL
: ViewOpenRecordIn.RECORD_PAGE,
kanbanAggregateOperation: workspaceView.kanbanAggregateOperation,
kanbanAggregateOperationFieldMetadataId:
workspaceView.kanbanAggregateOperationFieldMetadataId,
workspaceId,
createdAt: new Date(workspaceView.createdAt),
updatedAt: new Date(workspaceView.updatedAt),
deletedAt: workspaceView.deletedAt
? new Date(workspaceView.deletedAt)
: null,
};
await this.coreViewRepository.save(coreView);
}
public async updateCoreView(
workspaceId: string,
workspaceView: ViewWorkspaceEntity,
diff?: Partial<ObjectRecordDiff<ViewWorkspaceEntity>>,
): Promise<void> {
if (!diff || Object.keys(diff).length === 0) {
return;
}
const updateData = this.parseUpdateDataFromDiff(diff);
if (Object.keys(updateData).length > 0) {
await this.coreViewRepository.update(
{ id: workspaceView.id, workspaceId },
updateData,
);
}
}
public async deleteCoreView(
workspaceId: string,
workspaceView: ViewWorkspaceEntity,
): Promise<void> {
await this.coreViewRepository.softDelete({
id: workspaceView.id,
workspaceId,
});
}
public async destroyCoreView(
workspaceId: string,
workspaceView: ViewWorkspaceEntity,
): Promise<void> {
await this.coreViewRepository.delete({
id: workspaceView.id,
workspaceId,
});
}
public async restoreCoreView(
workspaceId: string,
workspaceView: ViewWorkspaceEntity,
): Promise<void> {
await this.coreViewRepository.restore({
id: workspaceView.id,
workspaceId,
});
}
}

View File

@ -0,0 +1,11 @@
import { ViewFilterValue } from 'src/engine/metadata-modules/view/types/view-filter-value.type';
export const transformViewFilterWorkspaceValueToCoreValue = (
value: string,
): ViewFilterValue => {
try {
return JSON.parse(value);
} catch {
return value;
}
};

View File

@ -1,11 +1,52 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { ViewField } from 'src/engine/metadata-modules/view/view-field.entity';
import { ViewFilterGroup } from 'src/engine/metadata-modules/view/view-filter-group.entity';
import { ViewFilter } from 'src/engine/metadata-modules/view/view-filter.entity';
import { ViewGroup } from 'src/engine/metadata-modules/view/view-group.entity';
import { ViewSort } from 'src/engine/metadata-modules/view/view-sort.entity';
import { View } from 'src/engine/metadata-modules/view/view.entity';
import { ViewFieldListener } from 'src/modules/view/listeners/view-field.listener';
import { ViewFilterGroupListener } from 'src/modules/view/listeners/view-filter-group.listener';
import { ViewFilterListener } from 'src/modules/view/listeners/view-filter.listener';
import { ViewGroupListener } from 'src/modules/view/listeners/view-group.listener';
import { ViewSortListener } from 'src/modules/view/listeners/view-sort.listener';
import { ViewListener } from 'src/modules/view/listeners/view.listener';
import { ViewDeleteOnePreQueryHook } from 'src/modules/view/pre-hooks/view-delete-one.pre-query.hook';
import { ViewFieldSyncService } from 'src/modules/view/services/view-field-sync.service';
import { ViewFilterGroupSyncService } from 'src/modules/view/services/view-filter-group-sync.service';
import { ViewFilterSyncService } from 'src/modules/view/services/view-filter-sync.service';
import { ViewGroupSyncService } from 'src/modules/view/services/view-group-sync.service';
import { ViewSortSyncService } from 'src/modules/view/services/view-sort-sync.service';
import { ViewSyncService } from 'src/modules/view/services/view-sync.service';
import { ViewService } from 'src/modules/view/services/view.service';
import { ViewDeleteOnePreQueryHook } from './pre-hooks/view-delete-one.pre-query.hook';
@Module({
imports: [],
providers: [ViewService, ViewDeleteOnePreQueryHook],
imports: [
TypeOrmModule.forFeature(
[View, ViewField, ViewFilter, ViewFilterGroup, ViewGroup, ViewSort],
'core',
),
FeatureFlagModule,
],
providers: [
ViewService,
ViewDeleteOnePreQueryHook,
ViewSyncService,
ViewFieldSyncService,
ViewFilterSyncService,
ViewFilterGroupSyncService,
ViewGroupSyncService,
ViewSortSyncService,
ViewListener,
ViewFieldListener,
ViewFilterListener,
ViewFilterGroupListener,
ViewGroupListener,
ViewSortListener,
],
exports: [ViewService],
})
export class ViewModule {}

View File

@ -10,10 +10,12 @@ export enum ViewExceptionCode {
VIEW_NOT_FOUND = 'VIEW_NOT_FOUND',
CANNOT_DELETE_INDEX_VIEW = 'CANNOT_DELETE_INDEX_VIEW',
METHOD_NOT_IMPLEMENTED = 'METHOD_NOT_IMPLEMENTED',
CORE_VIEW_SYNC_ERROR = 'CORE_VIEW_SYNC_ERROR',
}
export enum ViewExceptionMessage {
VIEW_NOT_FOUND = 'View not found',
CANNOT_DELETE_INDEX_VIEW = 'Cannot delete index view',
METHOD_NOT_IMPLEMENTED = 'Method not implemented',
CORE_VIEW_SYNC_ERROR = 'Failed to sync view data to core',
}