Start using next step ids (#11683)
- update workflow executor - update next step ids on step creation/deletion - use these in workflow run - use these in variables
This commit is contained in:
@ -2274,6 +2274,7 @@ export type WorkflowAction = {
|
|||||||
__typename?: 'WorkflowAction';
|
__typename?: 'WorkflowAction';
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
|
nextStepIds?: Maybe<Array<Scalars['UUID']>>;
|
||||||
settings: Scalars['JSON'];
|
settings: Scalars['JSON'];
|
||||||
type: Scalars['String'];
|
type: Scalars['String'];
|
||||||
valid: Scalars['Boolean'];
|
valid: Scalars['Boolean'];
|
||||||
@ -2893,7 +2894,7 @@ export type CreateWorkflowVersionStepMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type CreateWorkflowVersionStepMutation = { __typename?: 'Mutation', createWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean } };
|
export type CreateWorkflowVersionStepMutation = { __typename?: 'Mutation', createWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean, nextStepIds?: Array<any> | null } };
|
||||||
|
|
||||||
export type DeactivateWorkflowVersionMutationVariables = Exact<{
|
export type DeactivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
workflowVersionId: Scalars['String'];
|
||||||
@ -2907,7 +2908,7 @@ export type DeleteWorkflowVersionStepMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type DeleteWorkflowVersionStepMutation = { __typename?: 'Mutation', deleteWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean } };
|
export type DeleteWorkflowVersionStepMutation = { __typename?: 'Mutation', deleteWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean, nextStepIds?: Array<any> | null } };
|
||||||
|
|
||||||
export type RunWorkflowVersionMutationVariables = Exact<{
|
export type RunWorkflowVersionMutationVariables = Exact<{
|
||||||
input: RunWorkflowVersionInput;
|
input: RunWorkflowVersionInput;
|
||||||
@ -2921,14 +2922,14 @@ export type UpdateWorkflowRunStepMutationVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UpdateWorkflowRunStepMutation = { __typename?: 'Mutation', updateWorkflowRunStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean } };
|
export type UpdateWorkflowRunStepMutation = { __typename?: 'Mutation', updateWorkflowRunStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean, nextStepIds?: Array<any> | null } };
|
||||||
|
|
||||||
export type UpdateWorkflowVersionStepMutationVariables = Exact<{
|
export type UpdateWorkflowVersionStepMutationVariables = Exact<{
|
||||||
input: UpdateWorkflowVersionStepInput;
|
input: UpdateWorkflowVersionStepInput;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UpdateWorkflowVersionStepMutation = { __typename?: 'Mutation', updateWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean } };
|
export type UpdateWorkflowVersionStepMutation = { __typename?: 'Mutation', updateWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean, nextStepIds?: Array<any> | null } };
|
||||||
|
|
||||||
export type SubmitFormStepMutationVariables = Exact<{
|
export type SubmitFormStepMutationVariables = Exact<{
|
||||||
input: SubmitFormStepInput;
|
input: SubmitFormStepInput;
|
||||||
@ -5741,6 +5742,7 @@ export const CreateWorkflowVersionStepDocument = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -5809,6 +5811,7 @@ export const DeleteWorkflowVersionStepDocument = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -5879,6 +5882,7 @@ export const UpdateWorkflowRunStepDocument = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -5916,6 +5920,7 @@ export const UpdateWorkflowVersionStepDocument = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const CREATE_WORKFLOW_VERSION_STEP = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const DELETE_WORKFLOW_VERSION_STEP = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const UPDATE_WORKFLOW_RUN_STEP = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const UPDATE_WORKFLOW_VERSION_STEP = gql`
|
|||||||
type
|
type
|
||||||
settings
|
settings
|
||||||
valid
|
valid
|
||||||
|
nextStepIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -6,13 +6,13 @@ import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordF
|
|||||||
import { DELETE_WORKFLOW_VERSION_STEP } from '@/workflow/graphql/mutations/deleteWorkflowVersionStep';
|
import { DELETE_WORKFLOW_VERSION_STEP } from '@/workflow/graphql/mutations/deleteWorkflowVersionStep';
|
||||||
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
||||||
import { useApolloClient, useMutation } from '@apollo/client';
|
import { useApolloClient, useMutation } from '@apollo/client';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
DeleteWorkflowVersionStepInput,
|
DeleteWorkflowVersionStepInput,
|
||||||
DeleteWorkflowVersionStepMutation,
|
DeleteWorkflowVersionStepMutation,
|
||||||
DeleteWorkflowVersionStepMutationVariables,
|
DeleteWorkflowVersionStepMutationVariables,
|
||||||
WorkflowAction,
|
WorkflowAction,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const useDeleteWorkflowVersionStep = () => {
|
export const useDeleteWorkflowVersionStep = () => {
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
@ -47,9 +47,16 @@ export const useDeleteWorkflowVersionStep = () => {
|
|||||||
|
|
||||||
const newCachedRecord = {
|
const newCachedRecord = {
|
||||||
...cachedRecord,
|
...cachedRecord,
|
||||||
steps: (cachedRecord.steps || []).filter(
|
steps: (cachedRecord.steps || [])
|
||||||
(step: WorkflowAction) => step.id !== deletedStep.id,
|
.filter((step: WorkflowAction) => step.id !== deletedStep.id)
|
||||||
),
|
.map((step) => {
|
||||||
|
return {
|
||||||
|
...step,
|
||||||
|
nextStepIds: step.nextStepIds?.filter(
|
||||||
|
(nextStepId) => nextStepId !== deletedStep.id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const recordGqlFields = {
|
const recordGqlFields = {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export const baseWorkflowActionSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
valid: z.boolean(),
|
valid: z.boolean(),
|
||||||
|
nextStepIds: z.array(z.string()).optional().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const baseTriggerSchema = z.object({
|
export const baseTriggerSchema = z.object({
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThro
|
|||||||
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
||||||
import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer';
|
import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer';
|
||||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
import { getWorkflowPreviousStepId } from '@/workflow/workflow-steps/utils/getWorkflowPreviousStep';
|
import { getWorkflowPreviousStepId } from '@/workflow/workflow-steps/utils/getWorkflowPreviousStepId';
|
||||||
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
|
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
|
||||||
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
|
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
|
||||||
import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
|
import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
|
||||||
|
|||||||
@ -6,12 +6,12 @@ import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordF
|
|||||||
import { CREATE_WORKFLOW_VERSION_STEP } from '@/workflow/graphql/mutations/createWorkflowVersionStep';
|
import { CREATE_WORKFLOW_VERSION_STEP } from '@/workflow/graphql/mutations/createWorkflowVersionStep';
|
||||||
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
||||||
import { useApolloClient, useMutation } from '@apollo/client';
|
import { useApolloClient, useMutation } from '@apollo/client';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
CreateWorkflowVersionStepInput,
|
CreateWorkflowVersionStepInput,
|
||||||
CreateWorkflowVersionStepMutation,
|
CreateWorkflowVersionStepMutation,
|
||||||
CreateWorkflowVersionStepMutationVariables,
|
CreateWorkflowVersionStepMutationVariables,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const useCreateWorkflowVersionStep = () => {
|
export const useCreateWorkflowVersionStep = () => {
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
@ -34,6 +34,7 @@ export const useCreateWorkflowVersionStep = () => {
|
|||||||
const result = await mutate({
|
const result = await mutate({
|
||||||
variables: { input },
|
variables: { input },
|
||||||
});
|
});
|
||||||
|
|
||||||
const createdStep = result?.data?.createWorkflowVersionStep;
|
const createdStep = result?.data?.createWorkflowVersionStep;
|
||||||
if (!isDefined(createdStep)) {
|
if (!isDefined(createdStep)) {
|
||||||
return;
|
return;
|
||||||
@ -42,18 +43,31 @@ export const useCreateWorkflowVersionStep = () => {
|
|||||||
const cachedRecord = getRecordFromCache<WorkflowVersion>(
|
const cachedRecord = getRecordFromCache<WorkflowVersion>(
|
||||||
input.workflowVersionId,
|
input.workflowVersionId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isDefined(cachedRecord)) {
|
if (!isDefined(cachedRecord)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedExistingSteps =
|
||||||
|
cachedRecord.steps?.map((step) => {
|
||||||
|
if (step.id === input.parentStepId) {
|
||||||
|
return {
|
||||||
|
...step,
|
||||||
|
nextStepIds: [...(step.nextStepIds || []), createdStep.id],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return step;
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
const newCachedRecord = {
|
const newCachedRecord = {
|
||||||
...cachedRecord,
|
...cachedRecord,
|
||||||
steps: [...(cachedRecord.steps || []), createdStep],
|
steps: [...updatedExistingSteps, createdStep],
|
||||||
};
|
};
|
||||||
|
|
||||||
const recordGqlFields = {
|
const recordGqlFields = {
|
||||||
steps: true,
|
steps: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateRecordFromCache({
|
updateRecordFromCache({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
|||||||
@ -0,0 +1,111 @@
|
|||||||
|
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||||
|
import { getPreviousSteps } from '../getWorkflowPreviousSteps';
|
||||||
|
|
||||||
|
const mockWorkflow: WorkflowStep[] = [
|
||||||
|
{
|
||||||
|
id: 'step1',
|
||||||
|
name: 'First Step',
|
||||||
|
type: 'CODE',
|
||||||
|
valid: true,
|
||||||
|
nextStepIds: ['step2', 'step3'],
|
||||||
|
settings: {
|
||||||
|
input: {
|
||||||
|
serverlessFunctionId: 'func1',
|
||||||
|
serverlessFunctionVersion: '1.0.0',
|
||||||
|
serverlessFunctionInput: {},
|
||||||
|
},
|
||||||
|
outputSchema: {},
|
||||||
|
errorHandlingOptions: {
|
||||||
|
retryOnFailure: { value: true },
|
||||||
|
continueOnFailure: { value: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'step2',
|
||||||
|
name: 'Second Step',
|
||||||
|
type: 'CODE',
|
||||||
|
valid: true,
|
||||||
|
nextStepIds: ['step4'],
|
||||||
|
settings: {
|
||||||
|
input: {
|
||||||
|
serverlessFunctionId: 'func2',
|
||||||
|
serverlessFunctionVersion: '1.0.0',
|
||||||
|
serverlessFunctionInput: {},
|
||||||
|
},
|
||||||
|
outputSchema: {},
|
||||||
|
errorHandlingOptions: {
|
||||||
|
retryOnFailure: { value: true },
|
||||||
|
continueOnFailure: { value: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'step3',
|
||||||
|
name: 'Third Step',
|
||||||
|
type: 'CODE',
|
||||||
|
valid: true,
|
||||||
|
nextStepIds: ['step4'],
|
||||||
|
settings: {
|
||||||
|
input: {
|
||||||
|
serverlessFunctionId: 'func3',
|
||||||
|
serverlessFunctionVersion: '1.0.0',
|
||||||
|
serverlessFunctionInput: {},
|
||||||
|
},
|
||||||
|
outputSchema: {},
|
||||||
|
errorHandlingOptions: {
|
||||||
|
retryOnFailure: { value: true },
|
||||||
|
continueOnFailure: { value: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'step4',
|
||||||
|
name: 'Fourth Step',
|
||||||
|
type: 'CODE',
|
||||||
|
valid: true,
|
||||||
|
nextStepIds: [],
|
||||||
|
settings: {
|
||||||
|
input: {
|
||||||
|
serverlessFunctionId: 'func4',
|
||||||
|
serverlessFunctionVersion: '1.0.0',
|
||||||
|
serverlessFunctionInput: {},
|
||||||
|
},
|
||||||
|
outputSchema: {},
|
||||||
|
errorHandlingOptions: {
|
||||||
|
retryOnFailure: { value: true },
|
||||||
|
continueOnFailure: { value: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('getWorkflowPreviousSteps', () => {
|
||||||
|
it('should return empty array when there are no previous steps', () => {
|
||||||
|
const result = getPreviousSteps(mockWorkflow, 'step1');
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return direct previous steps', () => {
|
||||||
|
const result = getPreviousSteps(mockWorkflow, 'step2');
|
||||||
|
expect(result).toEqual([mockWorkflow[0]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all previous steps including indirect ones', () => {
|
||||||
|
const result = getPreviousSteps(mockWorkflow, 'step4');
|
||||||
|
expect(result).toEqual([mockWorkflow[0], mockWorkflow[1], mockWorkflow[2]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle circular dependencies', () => {
|
||||||
|
const circularWorkflow = [...mockWorkflow];
|
||||||
|
circularWorkflow[3].nextStepIds = ['step1']; // Make step4 point back to step1
|
||||||
|
|
||||||
|
const result = getPreviousSteps(circularWorkflow, 'step4');
|
||||||
|
expect(result).toEqual([mockWorkflow[0], mockWorkflow[1], mockWorkflow[2]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle non-existent step ID', () => {
|
||||||
|
const result = getPreviousSteps(mockWorkflow, 'non-existent-step');
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -55,6 +55,7 @@ describe('getWorkflowRunStepContext', () => {
|
|||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
},
|
},
|
||||||
valid: true,
|
valid: true,
|
||||||
|
nextStepIds: ['step2'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step2',
|
id: 'step2',
|
||||||
@ -72,6 +73,7 @@ describe('getWorkflowRunStepContext', () => {
|
|||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
},
|
},
|
||||||
valid: true,
|
valid: true,
|
||||||
|
nextStepIds: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} satisfies WorkflowRunFlow;
|
} satisfies WorkflowRunFlow;
|
||||||
@ -195,6 +197,7 @@ describe('getWorkflowRunStepContext', () => {
|
|||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
},
|
},
|
||||||
valid: true,
|
valid: true,
|
||||||
|
nextStepIds: ['step2'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step2',
|
id: 'step2',
|
||||||
@ -212,6 +215,7 @@ describe('getWorkflowRunStepContext', () => {
|
|||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
},
|
},
|
||||||
valid: true,
|
valid: true,
|
||||||
|
nextStepIds: ['step3'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step3',
|
id: 'step3',
|
||||||
@ -229,6 +233,7 @@ describe('getWorkflowRunStepContext', () => {
|
|||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
},
|
},
|
||||||
valid: true,
|
valid: true,
|
||||||
|
nextStepIds: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} satisfies WorkflowRunFlow;
|
} satisfies WorkflowRunFlow;
|
||||||
|
|||||||
@ -16,10 +16,7 @@ export const getWorkflowPreviousStepId = ({
|
|||||||
return TRIGGER_STEP_ID;
|
return TRIGGER_STEP_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stepIndex = steps.findIndex((step) => step.id === stepId);
|
const previousStep = steps.find((step) => step.nextStepIds?.includes(stepId));
|
||||||
if (stepIndex === -1) {
|
|
||||||
throw new Error('Step not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return steps[stepIndex - 1].id;
|
return previousStep?.id;
|
||||||
};
|
};
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||||
|
|
||||||
|
export const getPreviousSteps = (
|
||||||
|
steps: WorkflowStep[],
|
||||||
|
currentStepId: string,
|
||||||
|
visitedSteps: Set<string> = new Set([currentStepId]),
|
||||||
|
): WorkflowStep[] => {
|
||||||
|
const parentSteps = steps
|
||||||
|
.filter((step) => step.nextStepIds?.includes(currentStepId))
|
||||||
|
.filter((step) => !visitedSteps.has(step.id));
|
||||||
|
|
||||||
|
const grandParentSteps = parentSteps
|
||||||
|
.map((step) => {
|
||||||
|
if (visitedSteps.has(step.id)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
visitedSteps.add(step.id);
|
||||||
|
return getPreviousSteps(steps, step.id, visitedSteps);
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
return [...grandParentSteps, ...parentSteps];
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { WorkflowRunContext, WorkflowRunFlow } from '@/workflow/types/Workflow';
|
import { WorkflowRunContext, WorkflowRunFlow } from '@/workflow/types/Workflow';
|
||||||
|
import { getPreviousSteps } from '@/workflow/workflow-steps/utils/getWorkflowPreviousSteps';
|
||||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||||
|
|
||||||
export const getWorkflowRunStepContext = ({
|
export const getWorkflowRunStepContext = ({
|
||||||
@ -10,29 +11,26 @@ export const getWorkflowRunStepContext = ({
|
|||||||
context: WorkflowRunContext;
|
context: WorkflowRunContext;
|
||||||
flow: WorkflowRunFlow;
|
flow: WorkflowRunFlow;
|
||||||
}) => {
|
}) => {
|
||||||
const stepContext: Array<{ id: string; name: string; context: any }> = [];
|
|
||||||
|
|
||||||
if (stepId === TRIGGER_STEP_ID) {
|
if (stepId === TRIGGER_STEP_ID) {
|
||||||
return stepContext;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
stepContext.push({
|
const previousSteps = getPreviousSteps(flow.steps, stepId);
|
||||||
id: TRIGGER_STEP_ID,
|
|
||||||
name: flow.trigger.name ?? 'Trigger',
|
|
||||||
context: context[TRIGGER_STEP_ID],
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const step of flow.steps) {
|
const previousStepsContext = previousSteps.map((step) => {
|
||||||
if (step.id === stepId) {
|
return {
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stepContext.push({
|
|
||||||
id: step.id,
|
id: step.id,
|
||||||
name: step.name,
|
name: step.name,
|
||||||
context: context[step.id],
|
context: context[step.id],
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
|
|
||||||
return stepContext;
|
return [
|
||||||
|
{
|
||||||
|
id: TRIGGER_STEP_ID,
|
||||||
|
name: flow.trigger.name ?? 'Trigger',
|
||||||
|
context: context[TRIGGER_STEP_ID],
|
||||||
|
},
|
||||||
|
...previousStepsContext,
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||||
import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector';
|
import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector';
|
||||||
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||||
|
import { getPreviousSteps } from '@/workflow/workflow-steps/utils/getWorkflowPreviousSteps';
|
||||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||||
import {
|
import {
|
||||||
OutputSchema,
|
OutputSchema,
|
||||||
@ -18,17 +19,12 @@ export const useAvailableVariablesInWorkflowStep = ({
|
|||||||
}): StepOutputSchema[] => {
|
}): StepOutputSchema[] => {
|
||||||
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
||||||
const flow = useFlowOrThrow();
|
const flow = useFlowOrThrow();
|
||||||
|
|
||||||
const steps = flow.steps ?? [];
|
const steps = flow.steps ?? [];
|
||||||
|
|
||||||
const previousStepIds: string[] = [];
|
const previousStepIds: string[] = getPreviousSteps(
|
||||||
|
steps,
|
||||||
for (const step of steps) {
|
workflowSelectedNode,
|
||||||
if (step.id === workflowSelectedNode) {
|
).map((step) => step.id);
|
||||||
break;
|
|
||||||
}
|
|
||||||
previousStepIds.push(step.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableStepsOutputSchema: StepOutputSchema[] = useRecoilValue(
|
const availableStepsOutputSchema: StepOutputSchema[] = useRecoilValue(
|
||||||
stepsOutputSchemaFamilySelector({
|
stepsOutputSchemaFamilySelector({
|
||||||
|
|||||||
@ -21,4 +21,7 @@ export class WorkflowActionDTO {
|
|||||||
|
|
||||||
@Field(() => Boolean)
|
@Field(() => Boolean)
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
|
|
||||||
|
@Field(() => [UUIDScalarType], { nullable: true })
|
||||||
|
nextStepIds?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,4 +9,5 @@ export class WorkflowStepExecutorException extends CustomException {
|
|||||||
export enum WorkflowStepExecutorExceptionCode {
|
export enum WorkflowStepExecutorExceptionCode {
|
||||||
SCOPED_WORKSPACE_NOT_FOUND = 'SCOPED_WORKSPACE_NOT_FOUND',
|
SCOPED_WORKSPACE_NOT_FOUND = 'SCOPED_WORKSPACE_NOT_FOUND',
|
||||||
INVALID_STEP_TYPE = 'INVALID_STEP_TYPE',
|
INVALID_STEP_TYPE = 'INVALID_STEP_TYPE',
|
||||||
|
STEP_NOT_FOUND = 'STEP_NOT_FOUND',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||||
|
|
||||||
export type WorkflowExecutorInput = {
|
export type WorkflowExecutorInput = {
|
||||||
currentStepIndex: number;
|
currentStepId: string;
|
||||||
steps: WorkflowAction[];
|
steps: WorkflowAction[];
|
||||||
context: Record<string, unknown>;
|
context: Record<string, unknown>;
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
|
|||||||
@ -22,11 +22,18 @@ export class CodeWorkflowAction implements WorkflowExecutor {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
const step = steps[currentStepIndex];
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
throw new WorkflowStepExecutorException(
|
||||||
|
'Step not found',
|
||||||
|
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isWorkflowCodeAction(step)) {
|
if (!isWorkflowCodeAction(step)) {
|
||||||
throw new WorkflowStepExecutorException(
|
throw new WorkflowStepExecutorException(
|
||||||
|
|||||||
@ -13,10 +13,17 @@ import { isWorkflowFormAction } from 'src/modules/workflow/workflow-executor/wor
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class FormWorkflowAction implements WorkflowExecutor {
|
export class FormWorkflowAction implements WorkflowExecutor {
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
const step = steps[currentStepIndex];
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
throw new WorkflowStepExecutorException(
|
||||||
|
'Step not found',
|
||||||
|
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isWorkflowFormAction(step)) {
|
if (!isWorkflowFormAction(step)) {
|
||||||
throw new WorkflowStepExecutorException(
|
throw new WorkflowStepExecutorException(
|
||||||
|
|||||||
@ -75,11 +75,18 @@ export class SendEmailWorkflowAction implements WorkflowExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
const step = steps[currentStepIndex];
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
throw new WorkflowStepExecutorException(
|
||||||
|
'Step not found',
|
||||||
|
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isWorkflowSendEmailAction(step)) {
|
if (!isWorkflowSendEmailAction(step)) {
|
||||||
throw new WorkflowStepExecutorException(
|
throw new WorkflowStepExecutorException(
|
||||||
|
|||||||
@ -42,12 +42,18 @@ export class CreateRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
const step = steps[currentStepIndex];
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
throw new WorkflowStepExecutorException(
|
||||||
|
'Step not found',
|
||||||
|
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!isWorkflowCreateRecordAction(step)) {
|
if (!isWorkflowCreateRecordAction(step)) {
|
||||||
throw new WorkflowStepExecutorException(
|
throw new WorkflowStepExecutorException(
|
||||||
'Step is not a create record action',
|
'Step is not a create record action',
|
||||||
|
|||||||
@ -35,12 +35,18 @@ export class DeleteRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
const step = steps[currentStepIndex];
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
throw new WorkflowStepExecutorException(
|
||||||
|
'Step not found',
|
||||||
|
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!isWorkflowDeleteRecordAction(step)) {
|
if (!isWorkflowDeleteRecordAction(step)) {
|
||||||
throw new WorkflowStepExecutorException(
|
throw new WorkflowStepExecutorException(
|
||||||
'Step is not a delete record action',
|
'Step is not a delete record action',
|
||||||
|
|||||||
@ -45,11 +45,18 @@ export class FindRecordsWorkflowAction implements WorkflowExecutor {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
const step = steps[currentStepIndex];
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
throw new WorkflowStepExecutorException(
|
||||||
|
'Step not found',
|
||||||
|
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isWorkflowFindRecordsAction(step)) {
|
if (!isWorkflowFindRecordsAction(step)) {
|
||||||
throw new WorkflowStepExecutorException(
|
throw new WorkflowStepExecutorException(
|
||||||
|
|||||||
@ -42,11 +42,18 @@ export class UpdateRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
const step = steps[currentStepIndex];
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
throw new WorkflowStepExecutorException(
|
||||||
|
'Step not found',
|
||||||
|
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isWorkflowUpdateRecordAction(step)) {
|
if (!isWorkflowUpdateRecordAction(step)) {
|
||||||
throw new WorkflowStepExecutorException(
|
throw new WorkflowStepExecutorException(
|
||||||
|
|||||||
@ -107,6 +107,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
retryOnFailure: { value: false },
|
retryOnFailure: { value: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
nextStepIds: ['step-2'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step-2',
|
id: 'step-2',
|
||||||
@ -117,6 +118,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
retryOnFailure: { value: false },
|
retryOnFailure: { value: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
nextStepIds: [],
|
||||||
},
|
},
|
||||||
] as WorkflowAction[];
|
] as WorkflowAction[];
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
// No steps to execute
|
// No steps to execute
|
||||||
const result = await service.execute({
|
const result = await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 2,
|
currentStepId: 'step-2',
|
||||||
steps: mockSteps,
|
steps: mockSteps,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
});
|
});
|
||||||
@ -145,7 +147,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
const result = await service.execute({
|
const result = await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: mockSteps,
|
steps: mockSteps,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
});
|
});
|
||||||
@ -156,7 +158,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
);
|
);
|
||||||
expect(mockWorkflowExecutor.execute).toHaveBeenCalledWith({
|
expect(mockWorkflowExecutor.execute).toHaveBeenCalledWith({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: mockSteps,
|
steps: mockSteps,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
attemptCount: 1,
|
attemptCount: 1,
|
||||||
@ -199,7 +201,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
const result = await service.execute({
|
const result = await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: mockSteps,
|
steps: mockSteps,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
});
|
});
|
||||||
@ -231,7 +233,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
const result = await service.execute({
|
const result = await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: mockSteps,
|
steps: mockSteps,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
});
|
});
|
||||||
@ -265,6 +267,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
retryOnFailure: { value: false },
|
retryOnFailure: { value: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
nextStepIds: ['step-2'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step-2',
|
id: 'step-2',
|
||||||
@ -284,7 +287,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
const result = await service.execute({
|
const result = await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: stepsWithContinueOnFailure,
|
steps: stepsWithContinueOnFailure,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
});
|
});
|
||||||
@ -330,7 +333,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
await service.execute({
|
await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: stepsWithRetryOnFailure,
|
steps: stepsWithRetryOnFailure,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
});
|
});
|
||||||
@ -367,7 +370,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
const result = await service.execute({
|
const result = await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: stepsWithRetryOnFailure,
|
steps: stepsWithRetryOnFailure,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
attemptCount: 3, // MAX_RETRIES_ON_FAILURE is 3
|
attemptCount: 3, // MAX_RETRIES_ON_FAILURE is 3
|
||||||
@ -394,7 +397,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
const result = await service.execute({
|
const result = await service.execute({
|
||||||
workflowRunId: mockWorkflowRunId,
|
workflowRunId: mockWorkflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: 'step-1',
|
||||||
steps: mockSteps,
|
steps: mockSteps,
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
||||||
|
|
||||||
import { BILLING_FEATURE_USED } from 'src/engine/core-modules/billing/constants/billing-feature-used.constant';
|
import { BILLING_FEATURE_USED } from 'src/engine/core-modules/billing/constants/billing-feature-used.constant';
|
||||||
@ -38,22 +40,20 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
attemptCount = 1,
|
attemptCount = 1,
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||||
if (currentStepIndex >= steps.length) {
|
const step = steps.find((step) => step.id === currentStepId);
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
return {
|
return {
|
||||||
result: {
|
error: 'Step not found',
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const step = steps[currentStepIndex];
|
|
||||||
|
|
||||||
const workflowExecutor = this.workflowExecutorFactory.get(step.type);
|
const workflowExecutor = this.workflowExecutorFactory.get(step.type);
|
||||||
|
|
||||||
let actionOutput: WorkflowExecutorOutput;
|
let actionOutput: WorkflowExecutorOutput;
|
||||||
@ -80,7 +80,7 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
actionOutput = await workflowExecutor.execute({
|
actionOutput = await workflowExecutor.execute({
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
attemptCount,
|
attemptCount,
|
||||||
@ -111,11 +111,17 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
return actionOutput;
|
return actionOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionOutput.result) {
|
const shouldContinue =
|
||||||
const updatedContext = {
|
isDefined(actionOutput.result) ||
|
||||||
...context,
|
step.settings.errorHandlingOptions.continueOnFailure.value;
|
||||||
[step.id]: actionOutput.result,
|
|
||||||
};
|
if (shouldContinue) {
|
||||||
|
const updatedContext = isDefined(actionOutput.result)
|
||||||
|
? {
|
||||||
|
...context,
|
||||||
|
[step.id]: actionOutput.result,
|
||||||
|
}
|
||||||
|
: context;
|
||||||
|
|
||||||
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
@ -123,36 +129,26 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
context: updatedContext,
|
context: updatedContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!isDefined(step.nextStepIds?.[0])) {
|
||||||
|
return actionOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle multiple next steps
|
||||||
return await this.execute({
|
return await this.execute({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
currentStepIndex: currentStepIndex + 1,
|
currentStepId: step.nextStepIds[0],
|
||||||
steps,
|
steps,
|
||||||
context: updatedContext,
|
context: updatedContext,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step.settings.errorHandlingOptions.continueOnFailure.value) {
|
|
||||||
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
|
||||||
workflowRunId,
|
|
||||||
stepOutput,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
|
|
||||||
return await this.execute({
|
|
||||||
workflowRunId,
|
|
||||||
currentStepIndex: currentStepIndex + 1,
|
|
||||||
steps,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
step.settings.errorHandlingOptions.retryOnFailure.value &&
|
step.settings.errorHandlingOptions.retryOnFailure.value &&
|
||||||
attemptCount < MAX_RETRIES_ON_FAILURE
|
attemptCount < MAX_RETRIES_ON_FAILURE
|
||||||
) {
|
) {
|
||||||
return await this.execute({
|
return await this.execute({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
attemptCount: attemptCount + 1,
|
attemptCount: attemptCount + 1,
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { Processor } from 'src/engine/core-modules/message-queue/decorators/proc
|
|||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
|
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
|
||||||
import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||||
@ -31,7 +30,6 @@ export class RunWorkflowJob {
|
|||||||
private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService,
|
private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService,
|
||||||
private readonly throttlerService: ThrottlerService,
|
private readonly throttlerService: ThrottlerService,
|
||||||
private readonly twentyConfigService: TwentyConfigService,
|
private readonly twentyConfigService: TwentyConfigService,
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process(RunWorkflowJob.name)
|
@Process(RunWorkflowJob.name)
|
||||||
@ -109,7 +107,7 @@ export class RunWorkflowJob {
|
|||||||
|
|
||||||
await this.executeWorkflow({
|
await this.executeWorkflow({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
currentStepIndex: 0,
|
currentStepId: workflowVersion.steps[0].id,
|
||||||
steps: workflowVersion.steps,
|
steps: workflowVersion.steps,
|
||||||
context,
|
context,
|
||||||
});
|
});
|
||||||
@ -134,20 +132,31 @@ export class RunWorkflowJob {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastExecutedStepIndex = workflowRun.output?.flow?.steps?.findIndex(
|
const lastExecutedStep = workflowRun.output?.flow?.steps?.find(
|
||||||
(step) => step.id === lastExecutedStepId,
|
(step) => step.id === lastExecutedStepId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (lastExecutedStepIndex === undefined) {
|
if (!lastExecutedStep) {
|
||||||
throw new WorkflowRunException(
|
throw new WorkflowRunException(
|
||||||
'Last executed step not found',
|
'Last executed step not found',
|
||||||
WorkflowRunExceptionCode.INVALID_INPUT,
|
WorkflowRunExceptionCode.INVALID_INPUT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nextStepId = lastExecutedStep.nextStepIds?.[0];
|
||||||
|
|
||||||
|
if (!nextStepId) {
|
||||||
|
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||||
|
workflowRunId,
|
||||||
|
status: WorkflowRunStatus.COMPLETED,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.executeWorkflow({
|
await this.executeWorkflow({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
currentStepIndex: lastExecutedStepIndex + 1,
|
currentStepId: nextStepId,
|
||||||
steps: workflowRun.output?.flow?.steps ?? [],
|
steps: workflowRun.output?.flow?.steps ?? [],
|
||||||
context: workflowRun.context ?? {},
|
context: workflowRun.context ?? {},
|
||||||
});
|
});
|
||||||
@ -155,19 +164,19 @@ export class RunWorkflowJob {
|
|||||||
|
|
||||||
private async executeWorkflow({
|
private async executeWorkflow({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
currentStepIndex: number;
|
currentStepId: string;
|
||||||
steps: WorkflowAction[];
|
steps: WorkflowAction[];
|
||||||
context: Record<string, any>;
|
context: Record<string, any>;
|
||||||
}) {
|
}) {
|
||||||
const { error, pendingEvent } =
|
const { error, pendingEvent } =
|
||||||
await this.workflowExecutorWorkspaceService.execute({
|
await this.workflowExecutorWorkspaceService.execute({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
currentStepIndex,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user