Feature - HTTP request node (#12509)
Closes [#1072](https://github.com/twentyhq/core-team-issues/issues/1072) https://github.com/user-attachments/assets/adff3474-6ec3-4369-a0c8-fb4be7defe85 --------- Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: etiennejouan <jouan.etienne@gmail.com> Co-authored-by: Guillim <guillim@users.noreply.github.com> Co-authored-by: guillim <guigloo@msn.com> Co-authored-by: prastoin <paul@twenty.com> Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Thomas des Francs <tdesfrancs@gmail.com> Co-authored-by: martmull <martmull@hotmail.fr> Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Marie <51697796+ijreilly@users.noreply.github.com> Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com> Co-authored-by: Jordan Chalupka <9794216+jordan-chalupka@users.noreply.github.com> Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr> Co-authored-by: jaspass04 <147055860+jaspass04@users.noreply.github.com> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions <github-actions@twenty.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: Matt Dvertola <64113801+mdvertola@users.noreply.github.com> Co-authored-by: Zeroday BYTE <github@zerodaysec.org> Co-authored-by: Naifer <161821705+omarNaifer12@users.noreply.github.com> Co-authored-by: Karuna Tata <karuna.tata@devrev.ai> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Ajay A Adsule <103304466+AjayAdsule@users.noreply.github.com> Co-authored-by: Baptiste Devessier <baptiste@devessier.fr> Co-authored-by: oliver <8559757+oliverqx@users.noreply.github.com> Co-authored-by: Ahmad Zaheer <55204917+ahmadzaheer-dev@users.noreply.github.com> Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
This commit is contained in:
@ -348,8 +348,14 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
step: WorkflowAction;
|
||||
workspaceId: string;
|
||||
}): Promise<WorkflowAction> {
|
||||
// We don't enrich on the fly for code workflow action. OutputSchema is computed and updated when testing the serverless function
|
||||
if (step.type === WorkflowActionType.CODE) {
|
||||
// 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, OutputSchema is determined by the expamle response input
|
||||
if (
|
||||
[WorkflowActionType.CODE, WorkflowActionType.HTTP_REQUEST].includes(
|
||||
step.type,
|
||||
)
|
||||
) {
|
||||
return step;
|
||||
}
|
||||
|
||||
@ -555,6 +561,23 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
},
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.HTTP_REQUEST: {
|
||||
return {
|
||||
id: newStepId,
|
||||
name: 'HTTP Request',
|
||||
type: WorkflowActionType.HTTP_REQUEST,
|
||||
valid: false,
|
||||
settings: {
|
||||
...BASE_STEP_DEFINITION,
|
||||
input: {
|
||||
url: '',
|
||||
method: 'GET',
|
||||
headers: {},
|
||||
body: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new WorkflowVersionStepException(
|
||||
`WorkflowActionType '${type}' unknown`,
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
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 { HttpRequestWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/http-request.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';
|
||||
@ -27,6 +28,7 @@ export class WorkflowExecutorFactory {
|
||||
private readonly findRecordsWorkflowAction: FindRecordsWorkflowAction,
|
||||
private readonly formWorkflowAction: FormWorkflowAction,
|
||||
private readonly filterWorkflowAction: FilterWorkflowAction,
|
||||
private readonly httpRequestWorkflowAction: HttpRequestWorkflowAction,
|
||||
) {}
|
||||
|
||||
get(stepType: WorkflowActionType): WorkflowExecutor {
|
||||
@ -47,6 +49,8 @@ export class WorkflowExecutorFactory {
|
||||
return this.formWorkflowAction;
|
||||
case WorkflowActionType.FILTER:
|
||||
return this.filterWorkflowAction;
|
||||
case WorkflowActionType.HTTP_REQUEST:
|
||||
return this.httpRequestWorkflowAction;
|
||||
default:
|
||||
throw new WorkflowStepExecutorException(
|
||||
`Workflow step executor not found for step type '${stepType}'`,
|
||||
|
||||
@ -44,16 +44,18 @@ const resolveObject = (
|
||||
input: object,
|
||||
context: Record<string, unknown>,
|
||||
): object => {
|
||||
const resolvedObject = input;
|
||||
return Object.entries(input).reduce<Record<string, unknown>>(
|
||||
(resolvedObject, [key, value]) => {
|
||||
const resolvedKey = resolveInput(key, context);
|
||||
|
||||
const entries = Object.entries(resolvedObject);
|
||||
resolvedObject[
|
||||
typeof resolvedKey === 'string' ? resolvedKey : String(resolvedKey)
|
||||
] = resolveInput(value, context);
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
resolvedObject[key] = resolveInput(value, context);
|
||||
}
|
||||
|
||||
return resolvedObject;
|
||||
return resolvedObject;
|
||||
},
|
||||
{},
|
||||
);
|
||||
};
|
||||
|
||||
const resolveString = (
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import {
|
||||
WorkflowAction,
|
||||
WorkflowActionType,
|
||||
WorkflowHttpRequestAction,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
export const isWorkflowHttpRequestAction = (
|
||||
action: WorkflowAction,
|
||||
): action is WorkflowHttpRequestAction => {
|
||||
return action.type === WorkflowActionType.HTTP_REQUEST;
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { HttpRequestWorkflowAction } from './http-request.workflow-action';
|
||||
|
||||
@Module({
|
||||
providers: [HttpRequestWorkflowAction],
|
||||
exports: [HttpRequestWorkflowAction],
|
||||
})
|
||||
export class HttpRequestActionModule {}
|
||||
@ -0,0 +1,73 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
|
||||
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
||||
|
||||
import {
|
||||
WorkflowStepExecutorException,
|
||||
WorkflowStepExecutorExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
|
||||
import { WorkflowExecutorInput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-input';
|
||||
import { WorkflowExecutorOutput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-output.type';
|
||||
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
|
||||
import { isWorkflowHttpRequestAction } from './guards/is-workflow-http-request-action.guard';
|
||||
import { WorkflowHttpRequestActionInput } from './types/workflow-http-request-action-input.type';
|
||||
|
||||
@Injectable()
|
||||
export class HttpRequestWorkflowAction implements WorkflowExecutor {
|
||||
async execute({
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (!isWorkflowHttpRequestAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step is not an HTTP Request action',
|
||||
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowActionInput = resolveInput(
|
||||
step.settings.input,
|
||||
context,
|
||||
) as WorkflowHttpRequestActionInput;
|
||||
|
||||
const { url, method, headers, body } = workflowActionInput;
|
||||
|
||||
try {
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
url,
|
||||
method: method,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (['POST', 'PUT', 'PATCH'].includes(method) && body) {
|
||||
axiosConfig.data = body;
|
||||
}
|
||||
|
||||
const response = await axios(axiosConfig);
|
||||
|
||||
return { result: response.data };
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
return {
|
||||
error: error.response?.data || error.message || 'HTTP request failed',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'HTTP request failed',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
export type WorkflowHttpRequestActionInput = {
|
||||
url: string;
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
headers?: Record<string, string>;
|
||||
body?: Record<
|
||||
string,
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| Array<string | number | boolean | null>
|
||||
>;
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
|
||||
|
||||
import { WorkflowHttpRequestActionInput } from './workflow-http-request-action-input.type';
|
||||
|
||||
export type WorkflowHttpRequestActionSettings = BaseWorkflowActionSettings & {
|
||||
input: WorkflowHttpRequestActionInput;
|
||||
};
|
||||
@ -2,6 +2,7 @@ import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-sch
|
||||
import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
|
||||
import { WorkflowFilterActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type';
|
||||
import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
import { WorkflowHttpRequestActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/types/workflow-http-request-action-settings.type';
|
||||
import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type';
|
||||
import {
|
||||
WorkflowCreateRecordActionSettings,
|
||||
@ -30,4 +31,5 @@ export type WorkflowActionSettings =
|
||||
| WorkflowDeleteRecordActionSettings
|
||||
| WorkflowFindRecordsActionSettings
|
||||
| WorkflowFormActionSettings
|
||||
| WorkflowFilterActionSettings;
|
||||
| WorkflowFilterActionSettings
|
||||
| WorkflowHttpRequestActionSettings;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
|
||||
import { WorkflowFilterActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type';
|
||||
import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
import { WorkflowHttpRequestActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/types/workflow-http-request-action-settings.type';
|
||||
import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type';
|
||||
import {
|
||||
WorkflowCreateRecordActionSettings,
|
||||
@ -19,6 +20,7 @@ export enum WorkflowActionType {
|
||||
FIND_RECORDS = 'FIND_RECORDS',
|
||||
FORM = 'FORM',
|
||||
FILTER = 'FILTER',
|
||||
HTTP_REQUEST = 'HTTP_REQUEST',
|
||||
}
|
||||
|
||||
type BaseWorkflowAction = {
|
||||
@ -70,6 +72,11 @@ export type WorkflowFilterAction = BaseWorkflowAction & {
|
||||
settings: WorkflowFilterActionSettings;
|
||||
};
|
||||
|
||||
export type WorkflowHttpRequestAction = BaseWorkflowAction & {
|
||||
type: WorkflowActionType.HTTP_REQUEST;
|
||||
settings: WorkflowHttpRequestActionSettings;
|
||||
};
|
||||
|
||||
export type WorkflowAction =
|
||||
| WorkflowCodeAction
|
||||
| WorkflowSendEmailAction
|
||||
@ -78,4 +85,5 @@ export type WorkflowAction =
|
||||
| WorkflowDeleteRecordAction
|
||||
| WorkflowFindRecordsAction
|
||||
| WorkflowFormAction
|
||||
| WorkflowFilterAction;
|
||||
| WorkflowFilterAction
|
||||
| WorkflowHttpRequestAction;
|
||||
|
||||
@ -7,6 +7,7 @@ import { WorkflowExecutorFactory } from 'src/modules/workflow/workflow-executor/
|
||||
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 { HttpRequestActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/http-request-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 { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service';
|
||||
@ -22,6 +23,7 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow
|
||||
WorkflowRunModule,
|
||||
BillingModule,
|
||||
FilterActionModule,
|
||||
HttpRequestActionModule,
|
||||
],
|
||||
providers: [
|
||||
WorkflowExecutorWorkspaceService,
|
||||
|
||||
Reference in New Issue
Block a user