Decouple Send Email node from workflows (#13322)
- Renamed `WorkflowActionAdapter` to `ToolExecutorWorkflowAction` - Renamed `settingPermission` table to `permissionFlag` and `setting` column to `flag` - Decoupled the send email logic from workflows to tools - Add new `Tools Permission` section in FE --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
+4
-6
@@ -10,26 +10,24 @@ import { AiAgentWorkflowAction } from 'src/modules/workflow/workflow-executor/wo
|
||||
import { CodeWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action';
|
||||
import { FilterWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/filter.workflow-action';
|
||||
import { FormWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action';
|
||||
import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action';
|
||||
import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action';
|
||||
import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action';
|
||||
import { FindRecordsWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action';
|
||||
import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action';
|
||||
import { ToolExecutorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action';
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { WorkflowActionAdapter } from 'src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowActionFactory {
|
||||
constructor(
|
||||
private readonly codeWorkflowAction: CodeWorkflowAction,
|
||||
private readonly sendEmailWorkflowAction: SendEmailWorkflowAction,
|
||||
private readonly createRecordWorkflowAction: CreateRecordWorkflowAction,
|
||||
private readonly updateRecordWorkflowAction: UpdateRecordWorkflowAction,
|
||||
private readonly deleteRecordWorkflowAction: DeleteRecordWorkflowAction,
|
||||
private readonly findRecordsWorkflowAction: FindRecordsWorkflowAction,
|
||||
private readonly formWorkflowAction: FormWorkflowAction,
|
||||
private readonly filterWorkflowAction: FilterWorkflowAction,
|
||||
private readonly workflowActionAdapter: WorkflowActionAdapter,
|
||||
private readonly toolExecutorWorkflowAction: ToolExecutorWorkflowAction,
|
||||
private readonly aiAgentWorkflowAction: AiAgentWorkflowAction,
|
||||
) {}
|
||||
|
||||
@@ -38,7 +36,7 @@ export class WorkflowActionFactory {
|
||||
case WorkflowActionType.CODE:
|
||||
return this.codeWorkflowAction;
|
||||
case WorkflowActionType.SEND_EMAIL:
|
||||
return this.sendEmailWorkflowAction;
|
||||
return this.toolExecutorWorkflowAction;
|
||||
case WorkflowActionType.CREATE_RECORD:
|
||||
return this.createRecordWorkflowAction;
|
||||
case WorkflowActionType.UPDATE_RECORD:
|
||||
@@ -52,7 +50,7 @@ export class WorkflowActionFactory {
|
||||
case WorkflowActionType.FILTER:
|
||||
return this.filterWorkflowAction;
|
||||
case WorkflowActionType.HTTP_REQUEST:
|
||||
return this.workflowActionAdapter;
|
||||
return this.toolExecutorWorkflowAction;
|
||||
case WorkflowActionType.AI_AGENT:
|
||||
return this.aiAgentWorkflowAction;
|
||||
default:
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
|
||||
import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action';
|
||||
|
||||
@Module({
|
||||
imports: [MessagingImportManagerModule],
|
||||
providers: [ScopedWorkspaceContextFactory, SendEmailWorkflowAction],
|
||||
exports: [SendEmailWorkflowAction],
|
||||
})
|
||||
export class SendEmailActionModule {}
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import DOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||
|
||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessagingSendMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-send-message.service';
|
||||
import {
|
||||
WorkflowStepExecutorException,
|
||||
WorkflowStepExecutorExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
|
||||
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
|
||||
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
|
||||
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
import {
|
||||
SendEmailActionException,
|
||||
SendEmailActionExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/exceptions/send-email-action.exception';
|
||||
import { isWorkflowSendEmailAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/guards/is-workflow-send-email-action.guard';
|
||||
import { WorkflowSendEmailActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-input.type';
|
||||
|
||||
export type WorkflowSendEmailStepOutputSchema = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SendEmailWorkflowAction implements WorkflowAction {
|
||||
private readonly logger = new Logger(SendEmailWorkflowAction.name);
|
||||
constructor(
|
||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly sendMessageService: MessagingSendMessageService,
|
||||
) {}
|
||||
|
||||
private async getConnectedAccount(connectedAccountId: string) {
|
||||
if (!isValidUuid(connectedAccountId)) {
|
||||
throw new SendEmailActionException(
|
||||
`Connected Account ID is not a valid UUID`,
|
||||
SendEmailActionExceptionCode.INVALID_CONNECTED_ACCOUNT_ID,
|
||||
);
|
||||
}
|
||||
|
||||
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
|
||||
|
||||
if (!workspaceId) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Scoped workspace not found',
|
||||
WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
const connectedAccount = await connectedAccountRepository.findOneBy({
|
||||
id: connectedAccountId,
|
||||
});
|
||||
|
||||
if (!isDefined(connectedAccount)) {
|
||||
throw new SendEmailActionException(
|
||||
`Connected Account '${connectedAccountId}' not found`,
|
||||
SendEmailActionExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return connectedAccount;
|
||||
}
|
||||
|
||||
async execute({
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowActionInput): Promise<WorkflowActionOutput> {
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkflowSendEmailAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step is not a send email action',
|
||||
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
const connectedAccount = await this.getConnectedAccount(
|
||||
step.settings.input.connectedAccountId,
|
||||
);
|
||||
|
||||
const workflowActionInput = resolveInput(
|
||||
step.settings.input,
|
||||
context,
|
||||
) as WorkflowSendEmailActionInput;
|
||||
|
||||
const { email, body, subject } = workflowActionInput;
|
||||
|
||||
const emailSchema = z.string().trim().email('Invalid email');
|
||||
|
||||
const result = emailSchema.safeParse(email);
|
||||
|
||||
if (!result.success) {
|
||||
throw new SendEmailActionException(
|
||||
`Email '${email}' invalid`,
|
||||
SendEmailActionExceptionCode.INVALID_EMAIL,
|
||||
);
|
||||
}
|
||||
|
||||
const window = new JSDOM('').window;
|
||||
const purify = DOMPurify(window);
|
||||
const safeBody = purify.sanitize(body || '');
|
||||
const safeSubject = purify.sanitize(subject || '');
|
||||
|
||||
await this.sendMessageService.sendMessage(
|
||||
{
|
||||
to: email,
|
||||
subject: safeSubject,
|
||||
body: safeBody,
|
||||
},
|
||||
connectedAccount,
|
||||
);
|
||||
|
||||
this.logger.log(`Email sent successfully`);
|
||||
|
||||
return {
|
||||
result: { success: true } satisfies WorkflowSendEmailStepOutputSchema,
|
||||
};
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||
|
||||
import { TOOLS } from 'src/engine/core-modules/tool/constants/tools.const';
|
||||
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
|
||||
import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service';
|
||||
import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
|
||||
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
|
||||
@@ -11,7 +11,9 @@ import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/varia
|
||||
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowActionAdapter implements WorkflowAction {
|
||||
export class ToolExecutorWorkflowAction implements WorkflowAction {
|
||||
constructor(private readonly toolRegistry: ToolRegistryService) {}
|
||||
|
||||
async execute({
|
||||
currentStepId,
|
||||
steps,
|
||||
@@ -31,7 +33,7 @@ export class WorkflowActionAdapter implements WorkflowAction {
|
||||
);
|
||||
}
|
||||
|
||||
const tool = TOOLS.get(toolType);
|
||||
const tool = this.toolRegistry.getTool(toolType);
|
||||
|
||||
if (!tool) {
|
||||
throw new Error(
|
||||
@@ -54,6 +56,7 @@ export class WorkflowActionAdapter implements WorkflowAction {
|
||||
): ToolType | null {
|
||||
const mapping: Partial<Record<WorkflowActionType, ToolType>> = {
|
||||
[WorkflowActionType.HTTP_REQUEST]: ToolType.HTTP_REQUEST,
|
||||
[WorkflowActionType.SEND_EMAIL]: ToolType.SEND_EMAIL,
|
||||
};
|
||||
|
||||
return mapping[actionType] || null;
|
||||
+4
-4
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AiModule } from 'src/engine/core-modules/ai/ai.module';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||
@@ -9,9 +10,8 @@ import { AiAgentActionModule } from 'src/modules/workflow/workflow-executor/work
|
||||
import { CodeActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module';
|
||||
import { FilterActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/filter-action.module';
|
||||
import { FormActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module';
|
||||
import { SendEmailActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email-action.module';
|
||||
import { RecordCRUDActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module';
|
||||
import { WorkflowActionAdapter } from 'src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter';
|
||||
import { ToolExecutorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action';
|
||||
import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service';
|
||||
import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.module';
|
||||
|
||||
@@ -19,7 +19,6 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
|
||||
imports: [
|
||||
WorkflowCommonModule,
|
||||
CodeActionModule,
|
||||
SendEmailActionModule,
|
||||
RecordCRUDActionModule,
|
||||
FormActionModule,
|
||||
WorkflowRunModule,
|
||||
@@ -27,12 +26,13 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
|
||||
FilterActionModule,
|
||||
AiAgentActionModule,
|
||||
FeatureFlagModule,
|
||||
AiModule,
|
||||
],
|
||||
providers: [
|
||||
WorkflowExecutorWorkspaceService,
|
||||
ScopedWorkspaceContextFactory,
|
||||
WorkflowActionFactory,
|
||||
WorkflowActionAdapter,
|
||||
ToolExecutorWorkflowAction,
|
||||
],
|
||||
exports: [WorkflowExecutorWorkspaceService],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user