Update workflow statuses in real time (#11653)

Statuses are maintained by an async job. Those are calculations that we
would like to avoid using in both frontend and backend. Using push
updates are an easier way.



https://github.com/user-attachments/assets/31e44a82-08a8-4100-a38e-c965d5c73ee8
This commit is contained in:
Thomas Trompette
2025-04-22 11:03:12 +02:00
committed by GitHub
parent 3ce3793129
commit 33e0794da9
7 changed files with 223 additions and 39 deletions

View File

@ -1,9 +1,12 @@
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox'; import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip'; import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell'; import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
import { RecordTableCells } from '@/object-record/record-table/record-table-row/components/RecordTableCells'; import { RecordTableCells } from '@/object-record/record-table/record-table-row/components/RecordTableCells';
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr'; import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
import { ListenRecordUpdatesEffect } from '@/subscription/components/ListenUpdatesEffect';
import { getDefaultRecordFieldsToListen } from '@/subscription/utils/getDefaultRecordFieldsToListen.util';
type RecordTableRowProps = { type RecordTableRowProps = {
recordId: string; recordId: string;
@ -16,6 +19,11 @@ export const RecordTableRow = ({
rowIndexForFocus, rowIndexForFocus,
rowIndexForDrag, rowIndexForDrag,
}: RecordTableRowProps) => { }: RecordTableRowProps) => {
const { objectNameSingular } = useRecordIndexContextOrThrow();
const listenedFields = getDefaultRecordFieldsToListen({
objectNameSingular,
});
return ( return (
<RecordTableDraggableTr <RecordTableDraggableTr
recordId={recordId} recordId={recordId}
@ -27,6 +35,11 @@ export const RecordTableRow = ({
<RecordTableCells /> <RecordTableCells />
<RecordTableLastEmptyCell /> <RecordTableLastEmptyCell />
<RecordValueSetterEffect recordId={recordId} /> <RecordValueSetterEffect recordId={recordId} />
<ListenRecordUpdatesEffect
objectNameSingular={objectNameSingular}
recordId={recordId}
listenedFields={listenedFields}
/>
</RecordTableDraggableTr> </RecordTableDraggableTr>
); );
}; };

View File

@ -1,7 +1,7 @@
import { useApolloClient } from '@apollo/client';
import { useOnDbEvent } from '@/subscription/hooks/useOnDbEvent'; import { useOnDbEvent } from '@/subscription/hooks/useOnDbEvent';
import { DatabaseEventAction } from '~/generated/graphql'; import { useApolloClient } from '@apollo/client';
import { capitalize, isDefined } from 'twenty-shared/utils'; import { capitalize, isDefined } from 'twenty-shared/utils';
import { DatabaseEventAction } from '~/generated/graphql';
type ListenRecordUpdatesEffectProps = { type ListenRecordUpdatesEffectProps = {
objectNameSingular: string; objectNameSingular: string;
@ -39,6 +39,7 @@ export const ListenRecordUpdatesEffect = ({
fields: fieldsUpdater, fields: fieldsUpdater,
}); });
}, },
skip: listenedFields.length === 0,
}); });
return null; return null;

View File

@ -0,0 +1,14 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
export const getDefaultRecordFieldsToListen = ({
objectNameSingular,
}: {
objectNameSingular: string;
}) => {
switch (objectNameSingular) {
case CoreObjectNameSingular.Workflow:
return ['statuses'];
default:
return [];
}
};

View File

@ -1,13 +1,13 @@
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { RedisPubSub } from 'graphql-redis-subscriptions'; import { RedisPubSub } from 'graphql-redis-subscriptions';
import { isDefined } from 'twenty-shared/utils';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/types/object-record-event.event'; import { ObjectRecordEvent } from 'src/engine/core-modules/event-emitter/types/object-record-event.event';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event.type';
import { removeSecretFromWebhookRecord } from 'src/utils/remove-secret-from-webhook-record'; import { removeSecretFromWebhookRecord } from 'src/utils/remove-secret-from-webhook-record';
@Processor(MessageQueue.subscriptionsQueue) @Processor(MessageQueue.subscriptionsQueue)

View File

@ -1,6 +1,10 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { import {
@ -8,7 +12,6 @@ import {
WorkflowVersionBatchEvent, WorkflowVersionBatchEvent,
WorkflowVersionEventType, WorkflowVersionEventType,
} from 'src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job'; } from 'src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job';
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
describe('WorkflowStatusesUpdate', () => { describe('WorkflowStatusesUpdate', () => {
let job: WorkflowStatusesUpdateJob; let job: WorkflowStatusesUpdateJob;
@ -26,6 +29,10 @@ describe('WorkflowStatusesUpdate', () => {
publishOneServerlessFunction: jest.fn(), publishOneServerlessFunction: jest.fn(),
}; };
const mockWorkspaceEventEmitter = {
emitDatabaseBatchEvent: jest.fn(),
};
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [ providers: [
@ -38,6 +45,18 @@ describe('WorkflowStatusesUpdate', () => {
provide: ServerlessFunctionService, provide: ServerlessFunctionService,
useValue: mockServerlessFunctionService, useValue: mockServerlessFunctionService,
}, },
{
provide: WorkspaceEventEmitter,
useValue: mockWorkspaceEventEmitter,
},
{
provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
useValue: {
findOneOrFail: jest.fn().mockResolvedValue({
nameSingular: 'workflow',
}),
},
},
], ],
}).compile(); }).compile();
@ -69,6 +88,9 @@ describe('WorkflowStatusesUpdate', () => {
expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(1); expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(1);
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0); expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0);
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(0);
}); });
it('when no draft yet, update statuses', async () => { it('when no draft yet, update statuses', async () => {
@ -92,6 +114,9 @@ describe('WorkflowStatusesUpdate', () => {
{ id: '1' }, { id: '1' },
{ statuses: [WorkflowStatus.ACTIVE, WorkflowStatus.DRAFT] }, { statuses: [WorkflowStatus.ACTIVE, WorkflowStatus.DRAFT] },
); );
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(1);
}); });
}); });
@ -120,6 +145,9 @@ describe('WorkflowStatusesUpdate', () => {
expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(2); expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(2);
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0); expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0);
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(0);
}); });
test('when update that should be impossible, do not do anything', async () => { test('when update that should be impossible, do not do anything', async () => {
@ -146,6 +174,9 @@ describe('WorkflowStatusesUpdate', () => {
expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(2); expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(2);
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0); expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0);
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(0);
}); });
test('when WorkflowVersionStatus.DEACTIVATED to WorkflowVersionStatus.ACTIVE, should activate', async () => { test('when WorkflowVersionStatus.DEACTIVATED to WorkflowVersionStatus.ACTIVE, should activate', async () => {
@ -175,6 +206,9 @@ describe('WorkflowStatusesUpdate', () => {
{ id: '1' }, { id: '1' },
{ statuses: [WorkflowStatus.ACTIVE] }, { statuses: [WorkflowStatus.ACTIVE] },
); );
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(1);
}); });
test('when WorkflowVersionStatus.ACTIVE to WorkflowVersionStatus.DEACTIVATED, should deactivate', async () => { test('when WorkflowVersionStatus.ACTIVE to WorkflowVersionStatus.DEACTIVATED, should deactivate', async () => {
@ -204,6 +238,9 @@ describe('WorkflowStatusesUpdate', () => {
{ id: '1' }, { id: '1' },
{ statuses: [WorkflowStatus.DEACTIVATED] }, { statuses: [WorkflowStatus.DEACTIVATED] },
); );
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(1);
}); });
test('when WorkflowVersionStatus.DRAFT to WorkflowVersionStatus.ACTIVE, should activate', async () => { test('when WorkflowVersionStatus.DRAFT to WorkflowVersionStatus.ACTIVE, should activate', async () => {
@ -233,6 +270,9 @@ describe('WorkflowStatusesUpdate', () => {
{ id: '1' }, { id: '1' },
{ statuses: [WorkflowStatus.ACTIVE] }, { statuses: [WorkflowStatus.ACTIVE] },
); );
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(1);
}); });
}); });
@ -276,6 +316,9 @@ describe('WorkflowStatusesUpdate', () => {
{ id: '1' }, { id: '1' },
{ statuses: [] }, { statuses: [] },
); );
expect(
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
).toHaveBeenCalledTimes(1);
}); });
}); });
}); });

View File

@ -1,18 +1,27 @@
import { Logger, Scope } from '@nestjs/common'; import { Logger, Scope } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ServerlessFunctionExceptionCode } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { import {
WorkflowVersionStatus, WorkflowVersionStatus,
WorkflowVersionWorkspaceEntity, WorkflowVersionWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; import {
WorkflowStatus,
WorkflowWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { import {
WorkflowAction, WorkflowAction,
WorkflowActionType, WorkflowActionType,
@ -20,7 +29,6 @@ import {
import { getStatusCombinationFromArray } from 'src/modules/workflow/workflow-status/utils/get-status-combination-from-array.util'; import { getStatusCombinationFromArray } from 'src/modules/workflow/workflow-status/utils/get-status-combination-from-array.util';
import { getStatusCombinationFromUpdate } from 'src/modules/workflow/workflow-status/utils/get-status-combination-from-update.util'; import { getStatusCombinationFromUpdate } from 'src/modules/workflow/workflow-status/utils/get-status-combination-from-update.util';
import { getWorkflowStatusesFromCombination } from 'src/modules/workflow/workflow-status/utils/get-statuses-from-combination.util'; import { getWorkflowStatusesFromCombination } from 'src/modules/workflow/workflow-status/utils/get-statuses-from-combination.util';
import { ServerlessFunctionExceptionCode } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
export enum WorkflowVersionEventType { export enum WorkflowVersionEventType {
CREATE = 'CREATE', CREATE = 'CREATE',
@ -66,32 +74,51 @@ export class WorkflowStatusesUpdateJob {
constructor( constructor(
private readonly twentyORMManager: TwentyORMManager, private readonly twentyORMManager: TwentyORMManager,
private readonly serverlessFunctionService: ServerlessFunctionService, private readonly serverlessFunctionService: ServerlessFunctionService,
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
@InjectRepository(ObjectMetadataEntity, 'metadata')
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
) {} ) {}
@Process(WorkflowStatusesUpdateJob.name) @Process(WorkflowStatusesUpdateJob.name)
async handle(event: WorkflowVersionBatchEvent): Promise<void> { async handle(event: WorkflowVersionBatchEvent): Promise<void> {
const workflowObjectMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: {
nameSingular: 'workflow',
},
});
switch (event.type) { switch (event.type) {
case WorkflowVersionEventType.CREATE: case WorkflowVersionEventType.CREATE:
await Promise.all( await Promise.all(
event.workflowIds.map((workflowId) => event.workflowIds.map((workflowId) =>
this.handleWorkflowVersionCreated(workflowId), this.handleWorkflowVersionCreated({
workflowId,
workflowObjectMetadata,
workspaceId: event.workspaceId,
}),
), ),
); );
break; break;
case WorkflowVersionEventType.STATUS_UPDATE: case WorkflowVersionEventType.STATUS_UPDATE:
await Promise.all( await Promise.all(
event.statusUpdates.map((statusUpdate) => event.statusUpdates.map((statusUpdate) =>
this.handleWorkflowVersionStatusUpdated( this.handleWorkflowVersionStatusUpdated({
statusUpdate, statusUpdate,
event.workspaceId, workflowObjectMetadata,
), workspaceId: event.workspaceId,
}),
), ),
); );
break; break;
case WorkflowVersionEventType.DELETE: case WorkflowVersionEventType.DELETE:
await Promise.all( await Promise.all(
event.workflowIds.map((workflowId) => event.workflowIds.map((workflowId) =>
this.handleWorkflowVersionDeleted(workflowId), this.handleWorkflowVersionDeleted({
workflowId,
workflowObjectMetadata,
workspaceId: event.workspaceId,
}),
), ),
); );
break; break;
@ -100,22 +127,28 @@ export class WorkflowStatusesUpdateJob {
} }
} }
private async handleWorkflowVersionCreated( private async handleWorkflowVersionCreated({
workflowId: string, workflowId,
): Promise<void> { workflowObjectMetadata,
workspaceId,
}: {
workflowId: string;
workflowObjectMetadata: ObjectMetadataEntity;
workspaceId: string;
}): Promise<void> {
const workflowRepository = const workflowRepository =
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>( await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
'workflow', 'workflow',
); );
const workflow = await workflowRepository.findOneOrFail({ const previousWorkflow = await workflowRepository.findOneOrFail({
where: { where: {
id: workflowId, id: workflowId,
}, },
}); });
const currentWorkflowStatusCombination = getStatusCombinationFromArray( const currentWorkflowStatusCombination = getStatusCombinationFromArray(
workflow.statuses || [], previousWorkflow.statuses || [],
); );
const newWorkflowStatusCombination = getStatusCombinationFromUpdate( const newWorkflowStatusCombination = getStatusCombinationFromUpdate(
@ -128,16 +161,25 @@ export class WorkflowStatusesUpdateJob {
return; return;
} }
const newWorkflowStatuses = getWorkflowStatusesFromCombination(
newWorkflowStatusCombination,
);
await workflowRepository.update( await workflowRepository.update(
{ {
id: workflow.id, id: workflowId,
}, },
{ {
statuses: getWorkflowStatusesFromCombination( statuses: newWorkflowStatuses,
newWorkflowStatusCombination,
),
}, },
); );
this.emitWorkflowStatusUpdatedEvent({
currentWorkflow: previousWorkflow,
workflowObjectMetadata,
newWorkflowStatuses,
workspaceId,
});
} }
private async handlePublishServerlessFunction({ private async handlePublishServerlessFunction({
@ -207,10 +249,15 @@ export class WorkflowStatusesUpdateJob {
} }
} }
private async handleWorkflowVersionStatusUpdated( private async handleWorkflowVersionStatusUpdated({
statusUpdate: WorkflowVersionStatusUpdate, statusUpdate,
workspaceId: string, workflowObjectMetadata,
): Promise<void> { workspaceId,
}: {
statusUpdate: WorkflowVersionStatusUpdate;
workflowObjectMetadata: ObjectMetadataEntity;
workspaceId: string;
}): Promise<void> {
const workflowRepository = const workflowRepository =
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>( await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
'workflow', 'workflow',
@ -252,21 +299,36 @@ export class WorkflowStatusesUpdateJob {
return; return;
} }
const newWorkflowStatuses = getWorkflowStatusesFromCombination(
newWorkflowStatusCombination,
);
await workflowRepository.update( await workflowRepository.update(
{ {
id: statusUpdate.workflowId, id: statusUpdate.workflowId,
}, },
{ {
statuses: getWorkflowStatusesFromCombination( statuses: newWorkflowStatuses,
newWorkflowStatusCombination,
),
}, },
); );
this.emitWorkflowStatusUpdatedEvent({
currentWorkflow: workflow,
workflowObjectMetadata,
newWorkflowStatuses,
workspaceId,
});
} }
private async handleWorkflowVersionDeleted( private async handleWorkflowVersionDeleted({
workflowId: string, workflowId,
): Promise<void> { workflowObjectMetadata,
workspaceId,
}: {
workflowId: string;
workflowObjectMetadata: ObjectMetadataEntity;
workspaceId: string;
}): Promise<void> {
const workflowRepository = const workflowRepository =
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>( await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
'workflow', 'workflow',
@ -292,15 +354,59 @@ export class WorkflowStatusesUpdateJob {
return; return;
} }
const newWorkflowStatuses = getWorkflowStatusesFromCombination(
newWorkflowStatusCombination,
);
await workflowRepository.update( await workflowRepository.update(
{ {
id: workflowId, id: workflowId,
}, },
{ {
statuses: getWorkflowStatusesFromCombination( statuses: newWorkflowStatuses,
newWorkflowStatusCombination,
),
}, },
); );
this.emitWorkflowStatusUpdatedEvent({
currentWorkflow: workflow,
workflowObjectMetadata,
newWorkflowStatuses,
workspaceId,
});
}
private emitWorkflowStatusUpdatedEvent({
currentWorkflow,
workflowObjectMetadata,
newWorkflowStatuses,
workspaceId,
}: {
currentWorkflow: WorkflowWorkspaceEntity;
workflowObjectMetadata: ObjectMetadataEntity;
newWorkflowStatuses: WorkflowStatus[];
workspaceId: string;
}) {
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: workflowObjectMetadata.nameSingular,
action: DatabaseEventAction.UPDATED,
events: [
{
recordId: currentWorkflow.id,
objectMetadata: workflowObjectMetadata,
properties: {
before: currentWorkflow,
after: {
...currentWorkflow,
statuses: newWorkflowStatuses,
},
updatedFields: ['statuses'],
diff: {
statuses: newWorkflowStatuses,
},
},
},
],
workspaceId,
});
} }
} }

View File

@ -1,11 +1,18 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
import { WorkflowStatusesUpdateJob } from 'src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job'; import { WorkflowStatusesUpdateJob } from 'src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job';
import { WorkflowVersionStatusListener } from 'src/modules/workflow/workflow-status/listeners/workflow-version-status.listener'; import { WorkflowVersionStatusListener } from 'src/modules/workflow/workflow-status/listeners/workflow-version-status.listener';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
@Module({ @Module({
imports: [ServerlessFunctionModule], imports: [
ServerlessFunctionModule,
WorkspaceEventEmitterModule,
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
],
providers: [WorkflowStatusesUpdateJob, WorkflowVersionStatusListener], providers: [WorkflowStatusesUpdateJob, WorkflowVersionStatusListener],
}) })
export class WorkflowStatusModule {} export class WorkflowStatusModule {}