Ws poc (#11293)
related to https://github.com/twentyhq/core-team-issues/issues/601 ## Done - add a `onDbEvent` `Subscription` graphql endpoint to listen to database_event using what we have done with webhooks: - you can subscribe to any `action` (created, updated, ...) for any `objectNameSingular` or a specific `recordId`. Parameters are nullable and treated as wildcards when null. - returns events with following shape ```typescript @Field(() => String) eventId: string; @Field() emittedAt: string; @Field(() => DatabaseEventAction) action: DatabaseEventAction; @Field(() => String) objectNameSingular: string; @Field(() => GraphQLJSON) record: ObjectRecord; @Field(() => [String], { nullable: true }) updatedFields?: string[]; ``` - front provide a componentEffect `<ListenRecordUpdatesEffect />` that listen for an `objectNameSingular`, a `recordId` and a list of `listenedFields`. It subscribes to record updates and updates its apollo cached value for specified `listenedFields` - subscription is protected with credentials ## Result Here is an application with `workflowRun` https://github.com/user-attachments/assets/c964d857-3b54-495f-bf14-587ba26c5a8c --------- Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@ -12,4 +12,5 @@ export enum WorkflowRunExceptionCode {
|
||||
INVALID_INPUT = 'INVALID_INPUT',
|
||||
WORKFLOW_RUN_LIMIT_REACHED = 'WORKFLOW_RUN_LIMIT_REACHED',
|
||||
WORKFLOW_RUN_INVALID = 'WORKFLOW_RUN_INVALID',
|
||||
FAILURE = 'FAILURE',
|
||||
}
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
|
||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@Module({
|
||||
imports: [WorkflowCommonModule, RecordPositionModule],
|
||||
imports: [
|
||||
WorkflowCommonModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||
RecordPositionModule,
|
||||
],
|
||||
providers: [WorkflowRunWorkspaceService, ScopedWorkspaceContextFactory],
|
||||
exports: [WorkflowRunWorkspaceService],
|
||||
})
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
@ -17,14 +20,20 @@ import {
|
||||
WorkflowRunException,
|
||||
WorkflowRunExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-runner/exceptions/workflow-run.exception';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowRunWorkspaceService {
|
||||
constructor(
|
||||
private readonly twentyORMManager: TwentyORMManager,
|
||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||
private readonly recordPositionService: RecordPositionService,
|
||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly recordPositionService: RecordPositionService,
|
||||
) {}
|
||||
|
||||
async createWorkflowRun({
|
||||
@ -131,11 +140,19 @@ export class WorkflowRunWorkspaceService {
|
||||
);
|
||||
}
|
||||
|
||||
return workflowRunRepository.update(workflowRunToUpdate.id, {
|
||||
const partialUpdate = {
|
||||
status: WorkflowRunStatus.RUNNING,
|
||||
startedAt: new Date().toISOString(),
|
||||
context,
|
||||
output,
|
||||
};
|
||||
|
||||
await workflowRunRepository.update(workflowRunToUpdate.id, partialUpdate);
|
||||
|
||||
await this.emitWorkflowRunUpdatedEvent({
|
||||
workflowRunBefore: workflowRunToUpdate,
|
||||
diff: partialUpdate,
|
||||
updatedFields: ['status', 'startedAt', 'context', 'output'],
|
||||
});
|
||||
}
|
||||
|
||||
@ -164,13 +181,21 @@ export class WorkflowRunWorkspaceService {
|
||||
);
|
||||
}
|
||||
|
||||
return workflowRunRepository.update(workflowRunToUpdate.id, {
|
||||
const partialUpdate = {
|
||||
status,
|
||||
endedAt: new Date().toISOString(),
|
||||
output: {
|
||||
...(workflowRunToUpdate.output ?? {}),
|
||||
error,
|
||||
},
|
||||
};
|
||||
|
||||
await workflowRunRepository.update(workflowRunToUpdate.id, partialUpdate);
|
||||
|
||||
await this.emitWorkflowRunUpdatedEvent({
|
||||
workflowRunBefore: workflowRunToUpdate,
|
||||
diff: partialUpdate,
|
||||
updatedFields: ['status', 'endedAt', 'output'],
|
||||
});
|
||||
}
|
||||
|
||||
@ -199,7 +224,7 @@ export class WorkflowRunWorkspaceService {
|
||||
);
|
||||
}
|
||||
|
||||
return workflowRunRepository.update(workflowRunId, {
|
||||
const partialUpdate = {
|
||||
output: {
|
||||
flow: workflowRunToUpdate.output?.flow ?? {
|
||||
trigger: undefined,
|
||||
@ -211,6 +236,14 @@ export class WorkflowRunWorkspaceService {
|
||||
},
|
||||
},
|
||||
context,
|
||||
};
|
||||
|
||||
await workflowRunRepository.update(workflowRunId, partialUpdate);
|
||||
|
||||
await this.emitWorkflowRunUpdatedEvent({
|
||||
workflowRunBefore: workflowRunToUpdate,
|
||||
diff: partialUpdate,
|
||||
updatedFields: ['context', 'output'],
|
||||
});
|
||||
}
|
||||
|
||||
@ -251,7 +284,7 @@ export class WorkflowRunWorkspaceService {
|
||||
(existingStep) => (step.id === existingStep.id ? step : existingStep),
|
||||
);
|
||||
|
||||
return workflowRunRepository.update(workflowRunToUpdate.id, {
|
||||
const partialUpdate = {
|
||||
output: {
|
||||
...(workflowRunToUpdate.output ?? {}),
|
||||
flow: {
|
||||
@ -259,6 +292,14 @@ export class WorkflowRunWorkspaceService {
|
||||
steps: updatedSteps,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await workflowRunRepository.update(workflowRunToUpdate.id, partialUpdate);
|
||||
|
||||
await this.emitWorkflowRunUpdatedEvent({
|
||||
workflowRunBefore: workflowRunToUpdate,
|
||||
diff: partialUpdate,
|
||||
updatedFields: ['output'],
|
||||
});
|
||||
}
|
||||
|
||||
@ -283,4 +324,68 @@ export class WorkflowRunWorkspaceService {
|
||||
|
||||
return workflowRun;
|
||||
}
|
||||
|
||||
private async emitWorkflowRunUpdatedEvent({
|
||||
workflowRunBefore,
|
||||
updatedFields,
|
||||
diff,
|
||||
}: {
|
||||
workflowRunBefore: WorkflowRunWorkspaceEntity;
|
||||
updatedFields: string[];
|
||||
diff: object;
|
||||
}) {
|
||||
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
||||
|
||||
if (!workspaceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
nameSingular: 'workflowRun',
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new WorkflowRunException(
|
||||
'Object metadata not found',
|
||||
WorkflowRunExceptionCode.FAILURE,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowRunRepository =
|
||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
'workflowRun',
|
||||
);
|
||||
|
||||
const workflowRunAfter = await workflowRunRepository.findOneBy({
|
||||
id: workflowRunBefore.id,
|
||||
});
|
||||
|
||||
if (!workflowRunAfter) {
|
||||
throw new WorkflowRunException(
|
||||
'WorkflowRun not found',
|
||||
WorkflowRunExceptionCode.FAILURE,
|
||||
);
|
||||
}
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'workflowRun',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: [
|
||||
{
|
||||
recordId: workflowRunBefore.id,
|
||||
objectMetadata,
|
||||
properties: {
|
||||
after: workflowRunAfter,
|
||||
before: workflowRunBefore,
|
||||
updatedFields,
|
||||
diff,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user