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:
@ -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),
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
export type RelationFilterValue = {
|
||||
isCurrentWorkspaceMemberSelected?: boolean;
|
||||
selectedRecordIds: string[];
|
||||
};
|
||||
|
||||
export type ViewFilterValue =
|
||||
| string
|
||||
| string[]
|
||||
| RelationFilterValue
|
||||
| Record<string, unknown>
|
||||
| null
|
||||
| undefined;
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
@ -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 {}
|
||||
|
||||
@ -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',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user