Trigger workflow on database event (#6480)
- Add global listener on database event - Fetch event listener associated - Trigger associated workflow Also updated the runner so it expects the input to be in the payload rather than the trigger
This commit is contained in:
@ -0,0 +1,36 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import {
|
||||
RunWorkflowJobData,
|
||||
WorkflowRunnerJob,
|
||||
} from 'src/modules/workflow/workflow-runner/workflow-runner.job';
|
||||
|
||||
export type WorkflowEventTriggerJobData = {
|
||||
workspaceId: string;
|
||||
workflowId: string;
|
||||
payload: object;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.workflowQueue)
|
||||
export class WorkflowEventTriggerJob {
|
||||
private readonly logger = new Logger(WorkflowEventTriggerJob.name);
|
||||
|
||||
constructor(
|
||||
@InjectMessageQueue(MessageQueue.workflowQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {}
|
||||
|
||||
@Process(WorkflowEventTriggerJob.name)
|
||||
async handle(data: WorkflowEventTriggerJobData): Promise<void> {
|
||||
this.messageQueueService.add<RunWorkflowJobData>(WorkflowRunnerJob.name, {
|
||||
workspaceId: data.workspaceId,
|
||||
workflowId: data.workflowId,
|
||||
payload: data.payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkflowRunnerModule } from 'src/modules/workflow/workflow-runner/workflow-runner.module';
|
||||
import { WorkflowEventTriggerJob } from 'src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job';
|
||||
|
||||
@Module({
|
||||
imports: [WorkflowRunnerModule],
|
||||
providers: [WorkflowEventTriggerJob],
|
||||
})
|
||||
export class WorkflowTriggerJobModule {}
|
||||
@ -0,0 +1,98 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { IsFeatureEnabledService } from 'src/engine/core-modules/feature-flag/services/is-feature-enabled.service';
|
||||
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
||||
import { ObjectRecordDeleteEvent } from 'src/engine/integrations/event-emitter/types/object-record-delete.event';
|
||||
import { ObjectRecordUpdateEvent } from 'src/engine/integrations/event-emitter/types/object-record-update.event';
|
||||
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
|
||||
import {
|
||||
WorkflowEventTriggerJob,
|
||||
WorkflowEventTriggerJobData,
|
||||
} from 'src/modules/workflow/workflow-trigger/jobs/workflow-event-trigger.job';
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseEventTriggerListener {
|
||||
private readonly logger = new Logger('DatabaseEventTriggerListener');
|
||||
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectMessageQueue(MessageQueue.workflowQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
private readonly isFeatureFlagEnabledService: IsFeatureEnabledService,
|
||||
) {}
|
||||
|
||||
@OnEvent('*.created')
|
||||
async handleObjectRecordCreateEvent(payload: ObjectRecordCreateEvent<any>) {
|
||||
await this.handleEvent(payload);
|
||||
}
|
||||
|
||||
@OnEvent('*.updated')
|
||||
async handleObjectRecordUpdateEvent(payload: ObjectRecordUpdateEvent<any>) {
|
||||
await this.handleEvent(payload);
|
||||
}
|
||||
|
||||
@OnEvent('*.deleted')
|
||||
async handleObjectRecordDeleteEvent(payload: ObjectRecordDeleteEvent<any>) {
|
||||
await this.handleEvent(payload);
|
||||
}
|
||||
|
||||
private async handleEvent(
|
||||
payload:
|
||||
| ObjectRecordCreateEvent<any>
|
||||
| ObjectRecordUpdateEvent<any>
|
||||
| ObjectRecordDeleteEvent<any>,
|
||||
) {
|
||||
const workspaceId = payload.workspaceId;
|
||||
const eventName = payload.name;
|
||||
|
||||
if (!workspaceId || !eventName) {
|
||||
this.logger.error(
|
||||
`Missing workspaceId or eventName in payload ${JSON.stringify(
|
||||
payload,
|
||||
)}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const isWorkflowEnabled =
|
||||
await this.isFeatureFlagEnabledService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isWorkflowEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workflowEventListenerRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowEventListenerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowEventListener',
|
||||
);
|
||||
|
||||
const eventListeners = await workflowEventListenerRepository.find({
|
||||
where: {
|
||||
eventName,
|
||||
},
|
||||
});
|
||||
|
||||
for (const eventListener of eventListeners) {
|
||||
this.messageQueueService.add<WorkflowEventTriggerJobData>(
|
||||
WorkflowEventTriggerJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
workflowId: eventListener.workflowId,
|
||||
payload,
|
||||
},
|
||||
{ retryLimit: 3 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { DatabaseEventTriggerListener } from 'src/modules/workflow/workflow-trigger/listeners/database-event-trigger.listener';
|
||||
|
||||
@Module({
|
||||
imports: [FeatureFlagModule],
|
||||
providers: [DatabaseEventTriggerListener],
|
||||
})
|
||||
export class WorkflowTriggerListenerModule {}
|
||||
@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkflowTriggerJobModule } from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger-job.module';
|
||||
import { WorkflowTriggerListenerModule } from 'src/modules/workflow/workflow-trigger/listeners/workflow-trigger-listener.module';
|
||||
|
||||
@Module({
|
||||
imports: [WorkflowTriggerJobModule, WorkflowTriggerListenerModule],
|
||||
})
|
||||
export class WorkflowTriggerModule {}
|
||||
@ -2,16 +2,17 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
|
||||
import {
|
||||
WorkflowTriggerException,
|
||||
WorkflowTriggerExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-trigger/workflow-trigger.exception';
|
||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||
import {
|
||||
WorkflowDatabaseEventTrigger,
|
||||
WorkflowTriggerType,
|
||||
} from 'src/modules/workflow/common/types/workflow-trigger.type';
|
||||
import { WorkflowCommonService } from 'src/modules/workflow/common/workflow-common.services';
|
||||
import { WorkflowRunnerService } from 'src/modules/workflow/workflow-runner/workflow-runner.service';
|
||||
import {
|
||||
WorkflowTriggerException,
|
||||
WorkflowTriggerExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-trigger/workflow-trigger.exception';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowTriggerService {
|
||||
@ -21,7 +22,11 @@ export class WorkflowTriggerService {
|
||||
private readonly workflowRunnerService: WorkflowRunnerService,
|
||||
) {}
|
||||
|
||||
async runWorkflow(workspaceId: string, workflowVersionId: string) {
|
||||
async runWorkflowVersion(
|
||||
workspaceId: string,
|
||||
workflowVersionId: string,
|
||||
payload: object,
|
||||
) {
|
||||
const workflowVersion = await this.workflowCommonService.getWorkflowVersion(
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
@ -30,7 +35,7 @@ export class WorkflowTriggerService {
|
||||
return await this.workflowRunnerService.run({
|
||||
action: workflowVersion.trigger.nextAction,
|
||||
workspaceId,
|
||||
payload: workflowVersion.trigger.input,
|
||||
payload,
|
||||
});
|
||||
}
|
||||
|
||||
@ -42,9 +47,10 @@ export class WorkflowTriggerService {
|
||||
|
||||
switch (workflowVersion.trigger.type) {
|
||||
case WorkflowTriggerType.DATABASE_EVENT:
|
||||
await this.upsertWorkflowEventListener(
|
||||
await this.upsertEventListenerAndPublishVersion(
|
||||
workspaceId,
|
||||
workflowVersion.workflowId,
|
||||
workflowVersionId,
|
||||
workflowVersion.trigger,
|
||||
);
|
||||
break;
|
||||
@ -55,9 +61,10 @@ export class WorkflowTriggerService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async upsertWorkflowEventListener(
|
||||
private async upsertEventListenerAndPublishVersion(
|
||||
workspaceId: string,
|
||||
workflowId: string,
|
||||
workflowVersionId: string,
|
||||
trigger: WorkflowDatabaseEventTrigger,
|
||||
) {
|
||||
const eventName = trigger?.settings?.eventName;
|
||||
@ -75,17 +82,41 @@ export class WorkflowTriggerService {
|
||||
'workflowEventListener',
|
||||
);
|
||||
|
||||
// TODO: Use upsert when available for workspace entities
|
||||
await workflowEventListenerRepository.delete({
|
||||
workflowId,
|
||||
eventName,
|
||||
});
|
||||
|
||||
const workflowEventListener = await workflowEventListenerRepository.create({
|
||||
workflowId,
|
||||
eventName,
|
||||
});
|
||||
|
||||
await workflowEventListenerRepository.save(workflowEventListener);
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId);
|
||||
|
||||
const workflowRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflow',
|
||||
);
|
||||
|
||||
await workspaceDataSource?.transaction(async (transactionManager) => {
|
||||
// TODO: Use upsert when available for workspace entities
|
||||
await workflowEventListenerRepository.delete(
|
||||
{
|
||||
workflowId,
|
||||
eventName,
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await workflowEventListenerRepository.save(
|
||||
workflowEventListener,
|
||||
{},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await workflowRepository.update(
|
||||
{ id: workflowId },
|
||||
{ publishedVersionId: workflowVersionId },
|
||||
transactionManager,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user