Add position in run / version + block creation from generated API (#11318)

- Add position during workflow version / creation. It will allow to have
the versions and runs ordered
- Block the creation from generated api for versions. We use workflow
post hooks or create from draft
This commit is contained in:
Thomas Trompette
2025-04-01 15:29:54 +02:00
committed by GitHub
parent aa5da92555
commit e74c8723d0
9 changed files with 87 additions and 34 deletions

View File

@ -8,6 +8,7 @@ import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
@ -29,6 +30,7 @@ export class WorkflowCreateManyPostQueryHook
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly recordPositionService: RecordPositionService,
) {}
async execute(
@ -41,11 +43,21 @@ export class WorkflowCreateManyPostQueryHook
'workflowVersion',
);
const position = await this.recordPositionService.buildRecordPosition({
value: 'first',
objectMetadata: {
isCustom: false,
nameSingular: 'workflowVersion',
},
workspaceId: authContext.workspace.id,
});
const workflowVersionsToCreate = payload.map((workflow) => {
return workflowVersionRepository.create({
workflowId: workflow.id,
status: WorkflowVersionStatus.DRAFT,
name: 'v1',
position,
});
});

View File

@ -8,6 +8,7 @@ import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
@ -29,6 +30,7 @@ export class WorkflowCreateOnePostQueryHook
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly recordPositionService: RecordPositionService,
) {}
async execute(
@ -43,10 +45,20 @@ export class WorkflowCreateOnePostQueryHook
'workflowVersion',
);
const position = await this.recordPositionService.buildRecordPosition({
value: 'first',
objectMetadata: {
isCustom: false,
nameSingular: 'workflowVersion',
},
workspaceId: authContext.workspace.id,
});
const workflowVersionToCreate = await workflowVersionRepository.create({
workflowId: workflow.id,
status: WorkflowVersionStatus.DRAFT,
name: 'v1',
position,
});
await workflowVersionRepository.save(workflowVersionToCreate);

View File

@ -2,7 +2,9 @@ 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { WorkflowCreateManyPostQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook';
import { WorkflowCreateManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-many.pre-query.hook';
import { WorkflowCreateOnePostQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook';
@ -25,12 +27,12 @@ import { WorkflowVersionUpdateManyPreQueryHook } from 'src/modules/workflow/comm
import { WorkflowVersionUpdateOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-update-one.pre-query.hook';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { WorkflowVersionValidationWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
@Module({
imports: [
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
ServerlessFunctionModule,
RecordPositionModule,
],
providers: [
WorkflowCreateOnePreQueryHook,

View File

@ -1,37 +1,26 @@
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import {
CreateManyResolverArgs,
CreateOneResolverArgs,
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { WorkflowVersionValidationWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service';
import {
WorkflowQueryValidationException,
WorkflowQueryValidationExceptionCode,
} from 'src/modules/workflow/common/exceptions/workflow-query-validation.exception';
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
@WorkspaceQueryHook(`workflowVersion.createMany`)
export class WorkflowVersionCreateManyPreQueryHook
implements WorkspaceQueryHookInstance
{
constructor(
private readonly workflowVersionValidationWorkspaceService: WorkflowVersionValidationWorkspaceService,
) {}
async execute(
_authContext: AuthContext,
_objectName: string,
payload: CreateManyResolverArgs<WorkflowVersionWorkspaceEntity>,
_payload: CreateManyResolverArgs<WorkflowVersionWorkspaceEntity>,
): Promise<CreateManyResolverArgs<WorkflowVersionWorkspaceEntity>> {
await Promise.all(
payload.data.map(async (workflowVersion) => {
await this.workflowVersionValidationWorkspaceService.validateWorkflowVersionForCreateOne(
{
data: workflowVersion,
} satisfies CreateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
);
}),
throw new WorkflowQueryValidationException(
'Method not allowed.',
WorkflowQueryValidationExceptionCode.FORBIDDEN,
);
return payload;
}
}

View File

@ -3,26 +3,24 @@ import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { WorkflowVersionValidationWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service';
import {
WorkflowQueryValidationException,
WorkflowQueryValidationExceptionCode,
} from 'src/modules/workflow/common/exceptions/workflow-query-validation.exception';
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
@WorkspaceQueryHook(`workflowVersion.createOne`)
export class WorkflowVersionCreateOnePreQueryHook
implements WorkspaceQueryHookInstance
{
constructor(
private readonly workflowVersionValidationWorkspaceService: WorkflowVersionValidationWorkspaceService,
) {}
async execute(
_authContext: AuthContext,
_objectName: string,
payload: CreateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
_payload: CreateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
): Promise<CreateOneResolverArgs<WorkflowVersionWorkspaceEntity>> {
await this.workflowVersionValidationWorkspaceService.validateWorkflowVersionForCreateOne(
payload,
throw new WorkflowQueryValidationException(
'Method not allowed.',
WorkflowQueryValidationExceptionCode.FORBIDDEN,
);
return payload;
}
}

View File

@ -2,6 +2,7 @@ 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module';
@ -15,6 +16,7 @@ import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-b
ServerlessFunctionModule,
WorkflowVersionStepModule,
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
RecordPositionModule,
],
providers: [WorkflowVersionWorkspaceService],
exports: [WorkflowVersionWorkspaceService],

View File

@ -1,10 +1,11 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
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 { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
@ -30,6 +31,7 @@ export class WorkflowVersionWorkspaceService {
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
private readonly recordPositionService: RecordPositionService,
) {}
async createDraftFromWorkflowVersion({
@ -77,10 +79,20 @@ export class WorkflowVersionWorkspaceService {
},
});
const position = await this.recordPositionService.buildRecordPosition({
value: 'first',
objectMetadata: {
isCustom: false,
nameSingular: 'workflowVersion',
},
workspaceId,
});
draftWorkflowVersion = await workflowVersionRepository.save({
workflowId,
name: `v${workflowVersionsCount + 1}`,
status: WorkflowVersionStatus.DRAFT,
position,
});
await this.emitWorkflowVersionCreationEvent({

View File

@ -1,11 +1,13 @@
import { Module } from '@nestjs/common';
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';
@Module({
imports: [WorkflowCommonModule],
providers: [WorkflowRunWorkspaceService],
imports: [WorkflowCommonModule, RecordPositionModule],
providers: [WorkflowRunWorkspaceService, ScopedWorkspaceContextFactory],
exports: [WorkflowRunWorkspaceService],
})
export class WorkflowRunModule {}

View File

@ -1,6 +1,8 @@
import { Injectable } from '@nestjs/common';
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';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import {
StepOutput,
@ -21,6 +23,8 @@ export class WorkflowRunWorkspaceService {
constructor(
private readonly twentyORMManager: TwentyORMManager,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
private readonly recordPositionService: RecordPositionService,
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
) {}
async createWorkflowRun({
@ -64,6 +68,25 @@ export class WorkflowRunWorkspaceService {
},
});
const workspaceId =
this.scopedWorkspaceContextFactory.create()?.workspaceId;
if (!workspaceId) {
throw new WorkflowRunException(
'Workspace id is invalid',
WorkflowRunExceptionCode.WORKFLOW_RUN_INVALID,
);
}
const position = await this.recordPositionService.buildRecordPosition({
value: 'first',
objectMetadata: {
isCustom: false,
nameSingular: 'workflowRun',
},
workspaceId,
});
return (
await workflowRunRepository.save({
name: `#${workflowRunCount + 1} - ${workflow.name}`,
@ -71,6 +94,7 @@ export class WorkflowRunWorkspaceService {
createdBy,
workflowId: workflow.id,
status: WorkflowRunStatus.NOT_STARTED,
position,
})
).id;
}