feat: add configurable response format for AI agents (text/JSON) (#15953)
## Summary
This PR adds configurable response format support for AI agents,
allowing them to return either plain text or structured JSON data based
on a defined schema.
## Key Features
### 1. Agent Response Format Configuration
- Added `AgentResponseFormat` type supporting:
- `text`: Returns plain text responses (default)
- `json`: Returns structured JSON based on defined schema
- New `AgentResponseSchema` type moved to `twenty-shared/ai` for sharing
between frontend/backend
### 2. Settings UI
- New `SettingsAgentResponseFormat` component for configuring response
format
- Visual schema builder for defining JSON output structure
- Real-time validation and preview
- Integrated into agent settings tab
### 3. Workflow Integration
- AI Agent workflow action automatically uses agent's configured
response format
- Output schema dynamically generated from agent's response format
- Workflow variable picker shows structured fields for JSON responses
- Backward compatible with existing text-only agents
### 4. Backend Implementation
- Added `convertAgentSchemaToZod` utility to validate JSON responses
- Agent executor service handles both text and JSON generation
- Automatic agent creation/cloning when adding AI agent steps to
workflows
- Unique agent naming with conflict resolution
### 5. Database Migration
- Migration `1763622159656-update-agent-response-format.ts`
- Sets default `responseFormat` to `{"type":"text"}` for existing agents
- Updated all standard agents with proper response format
## Changes by Module
### Frontend (`twenty-front`)
- 🆕 `AgentResponseFormat` type
- 🆕 `SettingsAgentResponseFormat` component
- ✏️ Updated `WorkflowEditActionAiAgent` to support response format
configuration
- 🗑️ Removed deprecated `useAiAgentOutputSchema` hook and
`AiAgentOutputSchema` type
### Backend (`twenty-server`)
- 🆕 `AgentResponseFormat` type in agent entity
- 🆕 `convertAgentSchemaToZod` utility for schema validation
- ✏️ Updated `AiAgentExecutorService` to handle both text and JSON
generation
- ✏️ Updated `WorkflowSchemaWorkspaceService` to generate output schema
from agent config
- ✏️ Enhanced `WorkflowVersionStepOperationsWorkspaceService` with agent
creation/cloning
- 🆕 Agent naming constants for conflict resolution
### Shared (`twenty-shared`)
- 🆕 `AgentResponseSchema` type
- 🆕 `ModelConfiguration` type moved to shared package
- Updated exports in `ai/index.ts`
## Code Quality
- Removed useless comments following code style guidelines
- All linter checks passed
- Type-safe implementation with proper TypeScript types
## Testing
- ✅ Database migration tested
- ✅ Agent creation/cloning in workflows verified
- ✅ Response format switching (text ↔ JSON) validated
- ✅ Backward compatibility with existing agents confirmed
## Migration Notes
- Existing agents will have `responseFormat: {type: 'text'}` set
automatically
- No breaking changes - all existing functionality preserved
- Agents can be updated to use JSON format through settings UI
This commit is contained in:
+7
-1
@@ -1,11 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
|
||||
@Module({
|
||||
imports: [WorkflowCommonModule, FeatureFlagModule],
|
||||
imports: [
|
||||
WorkflowCommonModule,
|
||||
FeatureFlagModule,
|
||||
TypeOrmModule.forFeature([AgentEntity]),
|
||||
],
|
||||
providers: [WorkflowSchemaWorkspaceService],
|
||||
exports: [WorkflowSchemaWorkspaceService],
|
||||
})
|
||||
|
||||
+43
-6
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isString } from '@sniptt/guards';
|
||||
import { isDefined, isValidVariable } from 'twenty-shared/utils';
|
||||
@@ -11,9 +12,11 @@ import {
|
||||
SingleRecordAvailability,
|
||||
TRIGGER_STEP_ID,
|
||||
} from 'twenty-shared/workflow';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { type DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/utils/check-string-is-database-event-action';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
import { DEFAULT_ITERATOR_CURRENT_ITEM } from 'src/modules/workflow/workflow-builder/workflow-schema/constants/default-iterator-current-item.const';
|
||||
@@ -42,6 +45,8 @@ import {
|
||||
export class WorkflowSchemaWorkspaceService {
|
||||
constructor(
|
||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||
@InjectRepository(AgentEntity)
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
) {}
|
||||
|
||||
async computeStepOutputSchema({
|
||||
@@ -122,6 +127,39 @@ export class WorkflowSchemaWorkspaceService {
|
||||
},
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
const agentId = step.settings.input.agentId;
|
||||
|
||||
if (!isDefined(agentId) || agentId === '') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: { id: agentId, workspaceId },
|
||||
});
|
||||
|
||||
if (
|
||||
!isDefined(agent) ||
|
||||
agent.responseFormat?.type !== 'json' ||
|
||||
!isDefined(agent.responseFormat.schema)
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(agent.responseFormat.schema.properties).map(
|
||||
([key, field]) => [
|
||||
key,
|
||||
{
|
||||
isLeaf: true,
|
||||
type: field.type,
|
||||
label: field.description || key,
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
),
|
||||
) as OutputSchema;
|
||||
}
|
||||
case WorkflowActionType.CODE: // StepOutput schema is computed on serverlessFunction draft execution
|
||||
default:
|
||||
return {};
|
||||
@@ -139,13 +177,12 @@ export class WorkflowSchemaWorkspaceService {
|
||||
}): Promise<WorkflowAction> {
|
||||
// We don't enrich on the fly for code and HTTP request workflow actions.
|
||||
// For code actions, OutputSchema is computed and updated when testing the serverless function.
|
||||
// For HTTP requests and AI agent, OutputSchema is determined by the example response input
|
||||
// For HTTP requests, OutputSchema is determined by the example response input
|
||||
// AI agent OutputSchema is enriched from agent's responseFormat
|
||||
if (
|
||||
[
|
||||
WorkflowActionType.CODE,
|
||||
WorkflowActionType.HTTP_REQUEST,
|
||||
WorkflowActionType.AI_AGENT,
|
||||
].includes(step.type)
|
||||
[WorkflowActionType.CODE, WorkflowActionType.HTTP_REQUEST].includes(
|
||||
step.type,
|
||||
)
|
||||
) {
|
||||
return step;
|
||||
}
|
||||
|
||||
+16
@@ -9,6 +9,10 @@ import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/work
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
|
||||
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
|
||||
import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service';
|
||||
import { WorkflowVersionStepCreationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-creation.workspace-service';
|
||||
import { WorkflowVersionStepUpdateWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-update.workspace-service';
|
||||
import { WorkflowVersionStepDeletionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-deletion.workspace-service';
|
||||
import {
|
||||
type WorkflowAction,
|
||||
WorkflowActionType,
|
||||
@@ -113,6 +117,10 @@ describe('WorkflowVersionStepWorkspaceService', () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
WorkflowVersionStepWorkspaceService,
|
||||
WorkflowVersionStepHelpersWorkspaceService,
|
||||
WorkflowVersionStepCreationWorkspaceService,
|
||||
WorkflowVersionStepUpdateWorkspaceService,
|
||||
WorkflowVersionStepDeletionWorkspaceService,
|
||||
{
|
||||
provide: TwentyORMGlobalManager,
|
||||
useValue: twentyORMGlobalManager,
|
||||
@@ -140,6 +148,14 @@ describe('WorkflowVersionStepWorkspaceService', () => {
|
||||
additionalCreatedSteps: [],
|
||||
})),
|
||||
runWorkflowVersionStepDeletionSideEffects: jest.fn(),
|
||||
cloneStep: jest.fn().mockImplementation(({ step }) => ({
|
||||
...step,
|
||||
id: 'cloned-step-id',
|
||||
})),
|
||||
markStepAsDuplicate: jest
|
||||
.fn()
|
||||
.mockImplementation(({ step }) => step),
|
||||
createDraftStep: jest.fn().mockImplementation(({ step }) => step),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
||||
import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto';
|
||||
import {
|
||||
WorkflowVersionStepException,
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util';
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
import { insertStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step';
|
||||
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
|
||||
import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowVersionStepCreationWorkspaceService {
|
||||
constructor(
|
||||
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
||||
private readonly workflowVersionStepOperationsWorkspaceService: WorkflowVersionStepOperationsWorkspaceService,
|
||||
private readonly workflowVersionStepHelpersWorkspaceService: WorkflowVersionStepHelpersWorkspaceService,
|
||||
) {}
|
||||
|
||||
async createWorkflowVersionStep({
|
||||
workspaceId,
|
||||
input,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
input: CreateWorkflowVersionStepInput;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const {
|
||||
workflowVersionId,
|
||||
stepType,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
position,
|
||||
parentStepConnectionOptions,
|
||||
id,
|
||||
} = input;
|
||||
|
||||
const workflowVersion =
|
||||
await this.workflowVersionStepHelpersWorkspaceService.getValidatedDraftWorkflowVersion(
|
||||
{
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const existingSteps = workflowVersion.steps;
|
||||
const existingTrigger = workflowVersion.trigger;
|
||||
|
||||
const { builtStep, additionalCreatedSteps } =
|
||||
await this.workflowVersionStepOperationsWorkspaceService.runStepCreationSideEffectsAndBuildStep(
|
||||
{
|
||||
type: stepType,
|
||||
workspaceId,
|
||||
position,
|
||||
workflowVersionId,
|
||||
id,
|
||||
},
|
||||
);
|
||||
|
||||
const enrichedNewStep =
|
||||
await this.workflowSchemaWorkspaceService.enrichOutputSchema({
|
||||
step: builtStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
|
||||
const { updatedSteps, updatedTrigger } = insertStep({
|
||||
existingSteps: existingSteps ?? [],
|
||||
existingTrigger,
|
||||
insertedStep: enrichedNewStep,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
parentStepConnectionOptions,
|
||||
});
|
||||
|
||||
if (isDefined(additionalCreatedSteps)) {
|
||||
updatedSteps.push(...additionalCreatedSteps);
|
||||
}
|
||||
|
||||
await this.workflowVersionStepHelpersWorkspaceService.updateWorkflowVersionStepsAndTrigger(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId: workflowVersion.id,
|
||||
trigger: updatedTrigger,
|
||||
steps: updatedSteps,
|
||||
},
|
||||
);
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
existingTrigger,
|
||||
existingSteps,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
}
|
||||
|
||||
async duplicateWorkflowVersionStep({
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
stepId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
stepId: string;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const workflowVersion =
|
||||
await this.workflowVersionStepHelpersWorkspaceService.getValidatedDraftWorkflowVersion(
|
||||
{
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const stepToDuplicate = workflowVersion.steps?.find(
|
||||
(step) => step.id === stepId,
|
||||
);
|
||||
|
||||
if (!isDefined(stepToDuplicate)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Step not found',
|
||||
WorkflowVersionStepExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const clonedStep =
|
||||
await this.workflowVersionStepOperationsWorkspaceService.cloneStep({
|
||||
step: stepToDuplicate,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const duplicatedStep =
|
||||
this.workflowVersionStepOperationsWorkspaceService.markStepAsDuplicate({
|
||||
step: clonedStep,
|
||||
});
|
||||
|
||||
const { updatedSteps, updatedTrigger } = insertStep({
|
||||
existingSteps: workflowVersion.steps ?? [],
|
||||
existingTrigger: workflowVersion.trigger,
|
||||
insertedStep: duplicatedStep,
|
||||
});
|
||||
|
||||
await this.workflowVersionStepHelpersWorkspaceService.updateWorkflowVersionStepsAndTrigger(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId: workflowVersion.id,
|
||||
steps: updatedSteps,
|
||||
trigger: updatedTrigger,
|
||||
},
|
||||
);
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
existingTrigger: workflowVersion.trigger,
|
||||
existingSteps: workflowVersion.steps,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
}
|
||||
|
||||
async createDraftStep({
|
||||
step,
|
||||
workspaceId,
|
||||
}: {
|
||||
step: WorkflowAction;
|
||||
workspaceId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
return this.workflowVersionStepOperationsWorkspaceService.createDraftStep({
|
||||
step,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto';
|
||||
import {
|
||||
WorkflowVersionStepException,
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util';
|
||||
import { removeStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step';
|
||||
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
|
||||
import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowVersionStepDeletionWorkspaceService {
|
||||
constructor(
|
||||
private readonly workflowVersionStepOperationsWorkspaceService: WorkflowVersionStepOperationsWorkspaceService,
|
||||
private readonly workflowVersionStepHelpersWorkspaceService: WorkflowVersionStepHelpersWorkspaceService,
|
||||
) {}
|
||||
|
||||
async deleteWorkflowVersionStep({
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
stepIdToDelete,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
stepIdToDelete: string;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const workflowVersion =
|
||||
await this.workflowVersionStepHelpersWorkspaceService.getValidatedDraftWorkflowVersion(
|
||||
{
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const existingTrigger = workflowVersion.trigger;
|
||||
|
||||
const isDeletingTrigger =
|
||||
stepIdToDelete === TRIGGER_STEP_ID && isDefined(existingTrigger);
|
||||
|
||||
if (!isDeletingTrigger && !isDefined(workflowVersion.steps)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
"Can't delete step from undefined steps",
|
||||
WorkflowVersionStepExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const stepToDelete = workflowVersion.steps?.find(
|
||||
(step) => step.id === stepIdToDelete,
|
||||
);
|
||||
|
||||
if (!isDeletingTrigger && !isDefined(stepToDelete)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
"Can't delete not existing step",
|
||||
WorkflowVersionStepExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const stepToDeleteChildrenIds = isDeletingTrigger
|
||||
? (existingTrigger?.nextStepIds ?? [])
|
||||
: (stepToDelete?.nextStepIds ?? []);
|
||||
|
||||
const { updatedSteps, updatedTrigger, removedStepIds } = removeStep({
|
||||
existingTrigger,
|
||||
existingSteps: workflowVersion.steps,
|
||||
stepIdToDelete,
|
||||
stepToDeleteChildrenIds,
|
||||
});
|
||||
|
||||
await this.workflowVersionStepHelpersWorkspaceService.updateWorkflowVersionStepsAndTrigger(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId: workflowVersion.id,
|
||||
steps: updatedSteps,
|
||||
trigger: updatedTrigger,
|
||||
},
|
||||
);
|
||||
|
||||
const removedSteps =
|
||||
workflowVersion.steps?.filter((step) =>
|
||||
removedStepIds.includes(step.id),
|
||||
) ?? [];
|
||||
|
||||
await Promise.all(
|
||||
removedSteps.map((step) =>
|
||||
this.workflowVersionStepOperationsWorkspaceService.runWorkflowVersionStepDeletionSideEffects(
|
||||
{
|
||||
step,
|
||||
workspaceId,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
existingTrigger,
|
||||
existingSteps: workflowVersion.steps,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/assert-workflow-version-is-draft.util';
|
||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowVersionStepHelpersWorkspaceService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||
) {}
|
||||
|
||||
async getValidatedDraftWorkflowVersion({
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
}: {
|
||||
workflowVersionId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<WorkflowVersionWorkspaceEntity> {
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
return workflowVersion;
|
||||
}
|
||||
|
||||
async updateWorkflowVersionStepsAndTrigger({
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
steps,
|
||||
trigger,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
steps?: WorkflowAction[] | null;
|
||||
trigger?: WorkflowTrigger | null;
|
||||
}): Promise<void> {
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const updateData: Partial<WorkflowVersionWorkspaceEntity> = {};
|
||||
|
||||
if (steps !== undefined) {
|
||||
updateData.steps = steps;
|
||||
}
|
||||
|
||||
if (trigger !== undefined) {
|
||||
updateData.trigger = trigger;
|
||||
}
|
||||
|
||||
await workflowVersionRepository.update(workflowVersionId, updateData);
|
||||
}
|
||||
}
|
||||
+66
-1
@@ -336,6 +336,32 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
// Get workflow version to use workflow ID and name in agent name
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const newAgent = await this.agentRepository.save({
|
||||
name: 'workflow-service-agent' + v4(),
|
||||
label: 'Workflow Agent' + workflowVersion.workflowId.substring(0, 4),
|
||||
icon: 'IconRobot',
|
||||
description: '',
|
||||
prompt: '',
|
||||
modelId: 'auto',
|
||||
responseFormat: { type: 'text' },
|
||||
workspaceId,
|
||||
isCustom: true,
|
||||
});
|
||||
|
||||
if (!isDefined(newAgent)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Failed to create AI Agent step',
|
||||
WorkflowVersionStepExceptionCode.AI_AGENT_STEP_FAILURE,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
builtStep: {
|
||||
...baseStep,
|
||||
@@ -344,7 +370,7 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
settings: {
|
||||
...BASE_STEP_DEFINITION,
|
||||
input: {
|
||||
agentId: '',
|
||||
agentId: newAgent.id,
|
||||
prompt: '',
|
||||
},
|
||||
},
|
||||
@@ -510,6 +536,45 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
},
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
const existingAgent = await this.agentRepository.findOne({
|
||||
where: { id: step.settings.input.agentId, workspaceId },
|
||||
});
|
||||
|
||||
if (!isDefined(existingAgent)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Agent not found for cloning',
|
||||
WorkflowVersionStepExceptionCode.AI_AGENT_STEP_FAILURE,
|
||||
);
|
||||
}
|
||||
|
||||
const clonedAgent = await this.agentRepository.save({
|
||||
name: 'workflow-service-agent' + v4(),
|
||||
label: existingAgent.label,
|
||||
icon: existingAgent.icon,
|
||||
description: existingAgent.description,
|
||||
prompt: existingAgent.prompt,
|
||||
modelId: existingAgent.modelId,
|
||||
responseFormat: existingAgent.responseFormat,
|
||||
workspaceId,
|
||||
isCustom: true,
|
||||
modelConfiguration: existingAgent.modelConfiguration,
|
||||
});
|
||||
|
||||
return {
|
||||
...step,
|
||||
id: v4(),
|
||||
nextStepIds: [],
|
||||
position: duplicatedStepPosition,
|
||||
settings: {
|
||||
...step.settings,
|
||||
input: {
|
||||
...step.settings.input,
|
||||
agentId: clonedAgent.id,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.ITERATOR: {
|
||||
return {
|
||||
...step,
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-action.dto';
|
||||
import {
|
||||
WorkflowVersionStepException,
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
|
||||
import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowVersionStepUpdateWorkspaceService {
|
||||
constructor(
|
||||
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
||||
private readonly workflowVersionStepOperationsWorkspaceService: WorkflowVersionStepOperationsWorkspaceService,
|
||||
private readonly workflowVersionStepHelpersWorkspaceService: WorkflowVersionStepHelpersWorkspaceService,
|
||||
) {}
|
||||
|
||||
async updateWorkflowVersionStep({
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
step,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
step: WorkflowAction;
|
||||
}): Promise<WorkflowActionDTO> {
|
||||
const workflowVersion =
|
||||
await this.workflowVersionStepHelpersWorkspaceService.getValidatedDraftWorkflowVersion(
|
||||
{
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (!isDefined(workflowVersion.steps)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
"Can't update step from undefined steps",
|
||||
WorkflowVersionStepExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const existingStep = workflowVersion.steps.find(
|
||||
(existingStep) => existingStep.id === step.id,
|
||||
);
|
||||
|
||||
if (!isDefined(existingStep)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Step not found',
|
||||
WorkflowVersionStepExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const isStepTypeChanged = existingStep.type !== step.type;
|
||||
|
||||
const updatedStep = isStepTypeChanged
|
||||
? await this.updateWorkflowVersionStepType({
|
||||
existingStep,
|
||||
newStep: step,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
})
|
||||
: await this.updateWorkflowVersionStepSettings({
|
||||
newStep: step,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
|
||||
const updatedSteps = workflowVersion.steps.map((existingStep) => {
|
||||
if (existingStep.id === step.id) {
|
||||
return updatedStep;
|
||||
} else {
|
||||
return existingStep;
|
||||
}
|
||||
});
|
||||
|
||||
await this.workflowVersionStepHelpersWorkspaceService.updateWorkflowVersionStepsAndTrigger(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId: workflowVersion.id,
|
||||
steps: updatedSteps,
|
||||
},
|
||||
);
|
||||
|
||||
return updatedStep;
|
||||
}
|
||||
|
||||
private async updateWorkflowVersionStepType({
|
||||
existingStep,
|
||||
newStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
}: {
|
||||
existingStep: WorkflowAction;
|
||||
newStep: WorkflowAction;
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
await this.workflowVersionStepOperationsWorkspaceService.runWorkflowVersionStepDeletionSideEffects(
|
||||
{
|
||||
step: existingStep,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const { builtStep } =
|
||||
await this.workflowVersionStepOperationsWorkspaceService.runStepCreationSideEffectsAndBuildStep(
|
||||
{
|
||||
type: newStep.type,
|
||||
workspaceId,
|
||||
position: newStep.position,
|
||||
workflowVersionId,
|
||||
},
|
||||
);
|
||||
|
||||
return this.workflowSchemaWorkspaceService.enrichOutputSchema({
|
||||
step: {
|
||||
...builtStep,
|
||||
id: existingStep.id,
|
||||
nextStepIds: existingStep.nextStepIds,
|
||||
position: existingStep.position,
|
||||
},
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
}
|
||||
|
||||
private async updateWorkflowVersionStepSettings({
|
||||
newStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
}: {
|
||||
newStep: WorkflowAction;
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
return this.workflowSchemaWorkspaceService.enrichOutputSchema({
|
||||
step: newStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
}
|
||||
}
|
||||
+8
@@ -9,6 +9,10 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module';
|
||||
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
|
||||
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
|
||||
import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service';
|
||||
import { WorkflowVersionStepCreationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-creation.workspace-service';
|
||||
import { WorkflowVersionStepUpdateWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-update.workspace-service';
|
||||
import { WorkflowVersionStepDeletionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-deletion.workspace-service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -20,6 +24,10 @@ import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workfl
|
||||
providers: [
|
||||
WorkflowVersionStepWorkspaceService,
|
||||
WorkflowVersionStepOperationsWorkspaceService,
|
||||
WorkflowVersionStepHelpersWorkspaceService,
|
||||
WorkflowVersionStepCreationWorkspaceService,
|
||||
WorkflowVersionStepUpdateWorkspaceService,
|
||||
WorkflowVersionStepDeletionWorkspaceService,
|
||||
],
|
||||
exports: [
|
||||
WorkflowVersionStepWorkspaceService,
|
||||
|
||||
+27
-331
@@ -1,33 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
||||
|
||||
import { type CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
||||
import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-action.dto';
|
||||
import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import {
|
||||
WorkflowVersionStepException,
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/assert-workflow-version-is-draft.util';
|
||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util';
|
||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||
import { insertStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step';
|
||||
import { removeStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step';
|
||||
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
|
||||
import { WorkflowVersionStepCreationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-creation.workspace-service';
|
||||
import { WorkflowVersionStepDeletionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-deletion.workspace-service';
|
||||
import { WorkflowVersionStepUpdateWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-update.workspace-service';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowVersionStepWorkspaceService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
||||
private readonly workflowVersionStepOperationsWorkspaceService: WorkflowVersionStepOperationsWorkspaceService,
|
||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||
private readonly workflowVersionStepCreationWorkspaceService: WorkflowVersionStepCreationWorkspaceService,
|
||||
private readonly workflowVersionStepUpdateWorkspaceService: WorkflowVersionStepUpdateWorkspaceService,
|
||||
private readonly workflowVersionStepDeletionWorkspaceService: WorkflowVersionStepDeletionWorkspaceService,
|
||||
) {}
|
||||
|
||||
async createWorkflowVersionStep({
|
||||
@@ -37,77 +23,12 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
workspaceId: string;
|
||||
input: CreateWorkflowVersionStepInput;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const {
|
||||
workflowVersionId,
|
||||
stepType,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
position,
|
||||
parentStepConnectionOptions,
|
||||
id,
|
||||
} = input;
|
||||
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
workflowVersionId,
|
||||
return this.workflowVersionStepCreationWorkspaceService.createWorkflowVersionStep(
|
||||
{
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const existingSteps = workflowVersion.steps;
|
||||
|
||||
const existingTrigger = workflowVersion.trigger;
|
||||
|
||||
const { builtStep, additionalCreatedSteps } =
|
||||
await this.workflowVersionStepOperationsWorkspaceService.runStepCreationSideEffectsAndBuildStep(
|
||||
{
|
||||
type: stepType,
|
||||
workspaceId,
|
||||
position,
|
||||
workflowVersionId,
|
||||
id,
|
||||
},
|
||||
);
|
||||
|
||||
const enrichedNewStep =
|
||||
await this.workflowSchemaWorkspaceService.enrichOutputSchema({
|
||||
step: builtStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
|
||||
const { updatedSteps, updatedTrigger } = insertStep({
|
||||
existingSteps: existingSteps ?? [],
|
||||
existingTrigger,
|
||||
insertedStep: enrichedNewStep,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
parentStepConnectionOptions,
|
||||
});
|
||||
|
||||
if (isDefined(additionalCreatedSteps)) {
|
||||
updatedSteps.push(...additionalCreatedSteps);
|
||||
}
|
||||
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
trigger: updatedTrigger,
|
||||
steps: updatedSteps,
|
||||
});
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
existingTrigger,
|
||||
existingSteps,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
input,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async updateWorkflowVersionStep({
|
||||
@@ -119,67 +40,13 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
workflowVersionId: string;
|
||||
step: WorkflowAction;
|
||||
}): Promise<WorkflowActionDTO> {
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
return this.workflowVersionStepUpdateWorkspaceService.updateWorkflowVersionStep(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
if (!isDefined(workflowVersion.steps)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
"Can't update step from undefined steps",
|
||||
WorkflowVersionStepExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const existingStep = workflowVersion.steps.find(
|
||||
(existingStep) => existingStep.id === step.id,
|
||||
step,
|
||||
},
|
||||
);
|
||||
|
||||
if (!isDefined(existingStep)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Step not found',
|
||||
WorkflowVersionStepExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const isStepTypeChanged = existingStep.type !== step.type;
|
||||
|
||||
const updatedStep = isStepTypeChanged
|
||||
? await this.updateWorkflowVersionStepType({
|
||||
existingStep,
|
||||
newStep: step,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
})
|
||||
: await this.updateWorkflowVersionStepSettings({
|
||||
newStep: step,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
|
||||
const updatedSteps = workflowVersion.steps.map((existingStep) => {
|
||||
if (existingStep.id === step.id) {
|
||||
return updatedStep;
|
||||
} else {
|
||||
return existingStep;
|
||||
}
|
||||
});
|
||||
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
steps: updatedSteps,
|
||||
});
|
||||
|
||||
return updatedStep;
|
||||
}
|
||||
|
||||
async deleteWorkflowVersionStep({
|
||||
@@ -191,82 +58,13 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
workflowVersionId: string;
|
||||
stepIdToDelete: string;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
return this.workflowVersionStepDeletionWorkspaceService.deleteWorkflowVersionStep(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const existingTrigger = workflowVersion.trigger;
|
||||
|
||||
const isDeletingTrigger =
|
||||
stepIdToDelete === TRIGGER_STEP_ID && isDefined(existingTrigger);
|
||||
|
||||
if (!isDeletingTrigger && !isDefined(workflowVersion.steps)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
"Can't delete step from undefined steps",
|
||||
WorkflowVersionStepExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
const stepToDelete = workflowVersion.steps?.find(
|
||||
(step) => step.id === stepIdToDelete,
|
||||
stepIdToDelete,
|
||||
},
|
||||
);
|
||||
|
||||
if (!isDeletingTrigger && !isDefined(stepToDelete)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
"Can't delete not existing step",
|
||||
WorkflowVersionStepExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const stepToDeleteChildrenIds = isDeletingTrigger
|
||||
? (existingTrigger?.nextStepIds ?? [])
|
||||
: (stepToDelete?.nextStepIds ?? []);
|
||||
|
||||
const { updatedSteps, updatedTrigger, removedStepIds } = removeStep({
|
||||
existingTrigger,
|
||||
existingSteps: workflowVersion.steps,
|
||||
stepIdToDelete,
|
||||
stepToDeleteChildrenIds,
|
||||
});
|
||||
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
steps: updatedSteps,
|
||||
trigger: updatedTrigger,
|
||||
});
|
||||
|
||||
const removedSteps =
|
||||
workflowVersion.steps?.filter((step) =>
|
||||
removedStepIds.includes(step.id),
|
||||
) ?? [];
|
||||
|
||||
await Promise.all(
|
||||
removedSteps.map((step) =>
|
||||
this.workflowVersionStepOperationsWorkspaceService.runWorkflowVersionStepDeletionSideEffects(
|
||||
{
|
||||
step,
|
||||
workspaceId,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
existingTrigger,
|
||||
existingSteps: workflowVersion.steps,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
}
|
||||
|
||||
async duplicateWorkflowVersionStep({
|
||||
@@ -278,59 +76,13 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
workflowVersionId: string;
|
||||
stepId: string;
|
||||
}): Promise<WorkflowVersionStepChangesDTO> {
|
||||
const workflowVersion =
|
||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||
return this.workflowVersionStepCreationWorkspaceService.duplicateWorkflowVersionStep(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const stepToDuplicate = workflowVersion.steps?.find(
|
||||
(step) => step.id === stepId,
|
||||
stepId,
|
||||
},
|
||||
);
|
||||
|
||||
if (!isDefined(stepToDuplicate)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Step not found',
|
||||
WorkflowVersionStepExceptionCode.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const clonedStep =
|
||||
await this.workflowVersionStepOperationsWorkspaceService.cloneStep({
|
||||
step: stepToDuplicate,
|
||||
workspaceId,
|
||||
});
|
||||
const duplicatedStep =
|
||||
this.workflowVersionStepOperationsWorkspaceService.markStepAsDuplicate({
|
||||
step: clonedStep,
|
||||
});
|
||||
|
||||
const { updatedSteps, updatedTrigger } = insertStep({
|
||||
existingSteps: workflowVersion.steps ?? [],
|
||||
existingTrigger: workflowVersion.trigger,
|
||||
insertedStep: duplicatedStep,
|
||||
});
|
||||
|
||||
const workflowVersionRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
await workflowVersionRepository.update(workflowVersion.id, {
|
||||
steps: updatedSteps,
|
||||
trigger: updatedTrigger,
|
||||
});
|
||||
|
||||
return computeWorkflowVersionStepChanges({
|
||||
existingTrigger: workflowVersion.trigger,
|
||||
existingSteps: workflowVersion.steps,
|
||||
updatedTrigger,
|
||||
updatedSteps,
|
||||
});
|
||||
}
|
||||
|
||||
async createDraftStep({
|
||||
@@ -340,65 +92,9 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
step: WorkflowAction;
|
||||
workspaceId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
return this.workflowVersionStepOperationsWorkspaceService.createDraftStep({
|
||||
return this.workflowVersionStepCreationWorkspaceService.createDraftStep({
|
||||
step,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
private async updateWorkflowVersionStepType({
|
||||
existingStep,
|
||||
newStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
}: {
|
||||
existingStep: WorkflowAction;
|
||||
newStep: WorkflowAction;
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
await this.workflowVersionStepOperationsWorkspaceService.runWorkflowVersionStepDeletionSideEffects(
|
||||
{
|
||||
step: existingStep,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const { builtStep } =
|
||||
await this.workflowVersionStepOperationsWorkspaceService.runStepCreationSideEffectsAndBuildStep(
|
||||
{
|
||||
type: newStep.type,
|
||||
workspaceId,
|
||||
position: newStep.position,
|
||||
workflowVersionId,
|
||||
},
|
||||
);
|
||||
|
||||
return this.workflowSchemaWorkspaceService.enrichOutputSchema({
|
||||
step: {
|
||||
...builtStep,
|
||||
id: existingStep.id,
|
||||
nextStepIds: existingStep.nextStepIds,
|
||||
position: existingStep.position,
|
||||
},
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
}
|
||||
|
||||
private async updateWorkflowVersionStepSettings({
|
||||
newStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
}: {
|
||||
newStep: WorkflowAction;
|
||||
workspaceId: string;
|
||||
workflowVersionId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
return this.workflowSchemaWorkspaceService.enrichOutputSchema({
|
||||
step: newStep,
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user