Refacto workflow folders (#9302)
- Create separated folders for sections - Add components - Add utils and clean old ones - Add constants - Rename search variables folder and components Next steps: - clean hooks - clean states
This commit is contained in:
@ -1,75 +0,0 @@
|
||||
import { WorkflowStep, WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||
import { generateWorkflowDiagram } from '@/workflow/utils/generateWorkflowDiagram';
|
||||
import { addCreateStepNodes } from '../addCreateStepNodes';
|
||||
|
||||
describe('addCreateStepNodes', () => {
|
||||
it("adds a create step node to the end of a single-branch flow and doesn't change the shape of other nodes", () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
name: 'Company created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
const steps: WorkflowStep[] = [
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Step 1',
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
name: 'Step 2',
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const diagramInitial = generateWorkflowDiagram({ trigger, steps });
|
||||
|
||||
expect(diagramInitial.nodes).toHaveLength(3);
|
||||
expect(diagramInitial.edges).toHaveLength(2);
|
||||
|
||||
const diagramWithCreateStepNodes = addCreateStepNodes(diagramInitial);
|
||||
|
||||
expect(diagramWithCreateStepNodes.nodes).toHaveLength(4);
|
||||
expect(diagramWithCreateStepNodes.edges).toHaveLength(3);
|
||||
|
||||
expect(diagramWithCreateStepNodes.nodes[0].type).toBe(undefined);
|
||||
expect(diagramWithCreateStepNodes.nodes[0].data.nodeType).toBe('trigger');
|
||||
|
||||
expect(diagramWithCreateStepNodes.nodes[1].type).toBe(undefined);
|
||||
expect(diagramWithCreateStepNodes.nodes[1].data.nodeType).toBe('action');
|
||||
|
||||
expect(diagramWithCreateStepNodes.nodes[2].type).toBe(undefined);
|
||||
expect(diagramWithCreateStepNodes.nodes[2].data.nodeType).toBe('action');
|
||||
|
||||
expect(diagramWithCreateStepNodes.nodes[3].type).toBe('create-step');
|
||||
});
|
||||
});
|
||||
@ -1,150 +0,0 @@
|
||||
import { WorkflowStep, WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||
import { generateWorkflowDiagram } from '../generateWorkflowDiagram';
|
||||
|
||||
describe('generateWorkflowDiagram', () => {
|
||||
it('should generate a single trigger node when no step is provided', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
name: 'Company created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
const steps: WorkflowStep[] = [];
|
||||
|
||||
const result = generateWorkflowDiagram({ trigger, steps });
|
||||
|
||||
expect(result.nodes).toHaveLength(1);
|
||||
expect(result.edges).toHaveLength(0);
|
||||
|
||||
expect(result.nodes[0]).toMatchObject({
|
||||
data: {
|
||||
nodeType: 'trigger',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a diagram with nodes and edges corresponding to the steps', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
name: 'Company created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
const steps: WorkflowStep[] = [
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Step 1',
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
name: 'Step 2',
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = generateWorkflowDiagram({ trigger, steps });
|
||||
|
||||
expect(result.nodes).toHaveLength(steps.length + 1); // All steps + trigger
|
||||
expect(result.edges).toHaveLength(steps.length - 1 + 1); // Edges are one less than nodes + the edge from the trigger to the first node
|
||||
|
||||
expect(result.nodes[0].data.nodeType).toBe('trigger');
|
||||
|
||||
const stepNodes = result.nodes.slice(1);
|
||||
|
||||
for (const [index, step] of steps.entries()) {
|
||||
expect(stepNodes[index].data).toEqual({
|
||||
nodeType: 'action',
|
||||
actionType: 'CODE',
|
||||
name: step.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly link nodes with edges', () => {
|
||||
const trigger: WorkflowTrigger = {
|
||||
name: 'Company created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
const steps: WorkflowStep[] = [
|
||||
{
|
||||
id: 'step1',
|
||||
name: 'Step 1',
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
name: 'Step 2',
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = generateWorkflowDiagram({ trigger, steps });
|
||||
|
||||
expect(result.edges[0].source).toEqual(result.nodes[0].id);
|
||||
expect(result.edges[0].target).toEqual(result.nodes[1].id);
|
||||
|
||||
expect(result.edges[1].source).toEqual(result.nodes[1].id);
|
||||
expect(result.edges[1].target).toEqual(result.nodes[2].id);
|
||||
});
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { getManualTriggerDefaultSettings } from '../getManualTriggerDefaultSettings';
|
||||
|
||||
it('returns settings for a manual trigger that can be activated from any where', () => {
|
||||
expect(
|
||||
getManualTriggerDefaultSettings({
|
||||
availability: 'EVERYWHERE',
|
||||
activeObjectMetadataItems: generatedMockObjectMetadataItems,
|
||||
}),
|
||||
).toStrictEqual({
|
||||
objectType: undefined,
|
||||
outputSchema: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns settings for a manual trigger that can be activated from any where', () => {
|
||||
expect(
|
||||
getManualTriggerDefaultSettings({
|
||||
availability: 'WHEN_RECORD_SELECTED',
|
||||
activeObjectMetadataItems: generatedMockObjectMetadataItems,
|
||||
}),
|
||||
).toStrictEqual({
|
||||
objectType: generatedMockObjectMetadataItems[0].nameSingular,
|
||||
outputSchema: {},
|
||||
});
|
||||
});
|
||||
@ -1,50 +0,0 @@
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
import { getTriggerDefaultDefinition } from '../getTriggerDefaultDefinition';
|
||||
|
||||
it('throws if the activeObjectMetadataItems list is empty', () => {
|
||||
expect(() => {
|
||||
getTriggerDefaultDefinition({
|
||||
type: 'DATABASE_EVENT',
|
||||
activeObjectMetadataItems: [],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('returns a valid configuration for DATABASE_EVENT trigger type', () => {
|
||||
expect(
|
||||
getTriggerDefaultDefinition({
|
||||
type: 'DATABASE_EVENT',
|
||||
activeObjectMetadataItems: generatedMockObjectMetadataItems,
|
||||
}),
|
||||
).toStrictEqual({
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.created`,
|
||||
outputSchema: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a valid configuration for MANUAL trigger type', () => {
|
||||
expect(
|
||||
getTriggerDefaultDefinition({
|
||||
type: 'MANUAL',
|
||||
activeObjectMetadataItems: generatedMockObjectMetadataItems,
|
||||
}),
|
||||
).toStrictEqual({
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
objectType: generatedMockObjectMetadataItems[0].nameSingular,
|
||||
outputSchema: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when providing an unknown trigger type', () => {
|
||||
expect(() => {
|
||||
getTriggerDefaultDefinition({
|
||||
type: 'unknown' as any,
|
||||
activeObjectMetadataItems: generatedMockObjectMetadataItems,
|
||||
});
|
||||
}).toThrow('Unknown type: unknown');
|
||||
});
|
||||
@ -1,109 +0,0 @@
|
||||
import { getWorkflowVersionDiagram } from '../getWorkflowVersionDiagram';
|
||||
|
||||
describe('getWorkflowVersionDiagram', () => {
|
||||
it('returns an empty diagram if the provided workflow version', () => {
|
||||
const result = getWorkflowVersionDiagram(undefined);
|
||||
|
||||
expect(result).toEqual({ nodes: [], edges: [] });
|
||||
});
|
||||
|
||||
it('returns a diagram with an empty-trigger node if the provided workflow version has no trigger', () => {
|
||||
const result = getWorkflowVersionDiagram({
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [],
|
||||
trigger: null,
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
nodes: [
|
||||
{
|
||||
data: {},
|
||||
id: 'trigger',
|
||||
position: { x: 0, y: 0 },
|
||||
type: 'empty-trigger',
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a diagram with an empty-trigger node if the provided workflow version has no steps', () => {
|
||||
const result = getWorkflowVersionDiagram({
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: null,
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
nodes: [
|
||||
{
|
||||
data: {
|
||||
name: 'Company created',
|
||||
nodeType: 'trigger',
|
||||
triggerType: 'DATABASE_EVENT',
|
||||
},
|
||||
id: 'trigger',
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the diagram for the last version', () => {
|
||||
const result = getWorkflowVersionDiagram({
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [
|
||||
{
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
});
|
||||
|
||||
// Corresponds to the trigger + 1 step
|
||||
expect(result.nodes).toHaveLength(2);
|
||||
expect(result.edges).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -1,265 +0,0 @@
|
||||
import { WorkflowStep, WorkflowVersion } from '@/workflow/types/Workflow';
|
||||
import { insertStep } from '../insertStep';
|
||||
|
||||
describe('insertStep', () => {
|
||||
it('returns a deep copy of the provided steps array instead of mutating it', () => {
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
const stepToAdd: WorkflowStep = {
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
};
|
||||
|
||||
const stepsUpdated = insertStep({
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepToAdd,
|
||||
parentStepId: undefined,
|
||||
});
|
||||
|
||||
expect(workflowVersionInitial.steps).not.toBe(stepsUpdated);
|
||||
});
|
||||
|
||||
it('adds the step when the steps array is empty', () => {
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
const stepToAdd: WorkflowStep = {
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
};
|
||||
|
||||
const stepsUpdated = insertStep({
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepToAdd,
|
||||
parentStepId: undefined,
|
||||
});
|
||||
|
||||
const expectedUpdatedSteps: Array<WorkflowStep> = [stepToAdd];
|
||||
expect(stepsUpdated).toEqual(expectedUpdatedSteps);
|
||||
});
|
||||
|
||||
it('adds the step at the end of a non-empty steps array', () => {
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [
|
||||
{
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
id: 'step-2',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
const stepToAdd: WorkflowStep = {
|
||||
id: 'step-3',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
};
|
||||
|
||||
const stepsUpdated = insertStep({
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepToAdd,
|
||||
parentStepId: workflowVersionInitial.steps[1].id, // Note the selected step.
|
||||
});
|
||||
|
||||
const expectedUpdatedSteps: Array<WorkflowStep> = [
|
||||
workflowVersionInitial.steps[0],
|
||||
workflowVersionInitial.steps[1],
|
||||
stepToAdd,
|
||||
];
|
||||
expect(stepsUpdated).toEqual(expectedUpdatedSteps);
|
||||
});
|
||||
|
||||
it('adds the step in the middle of a non-empty steps array', () => {
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [
|
||||
{
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
id: 'step-2',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
const stepToAdd: WorkflowStep = {
|
||||
id: 'step-3',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
};
|
||||
|
||||
const stepsUpdated = insertStep({
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepToAdd,
|
||||
parentStepId: workflowVersionInitial.steps[0].id, // Note the selected step.
|
||||
});
|
||||
|
||||
const expectedUpdatedSteps: Array<WorkflowStep> = [
|
||||
workflowVersionInitial.steps[0],
|
||||
stepToAdd,
|
||||
workflowVersionInitial.steps[1],
|
||||
];
|
||||
expect(stepsUpdated).toEqual(expectedUpdatedSteps);
|
||||
});
|
||||
});
|
||||
@ -1,72 +0,0 @@
|
||||
import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram';
|
||||
import { mergeWorkflowDiagrams } from '../mergeWorkflowDiagrams';
|
||||
|
||||
it('Preserves the properties defined in the previous version but not in the next one', () => {
|
||||
const previousDiagram: WorkflowDiagram = {
|
||||
nodes: [
|
||||
{
|
||||
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||
id: '1',
|
||||
position: { x: 0, y: 0 },
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
};
|
||||
const nextDiagram: WorkflowDiagram = {
|
||||
nodes: [
|
||||
{
|
||||
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||
id: '1',
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({
|
||||
nodes: [
|
||||
{
|
||||
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||
id: '1',
|
||||
position: { x: 0, y: 0 },
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('Replaces duplicated properties with the next value', () => {
|
||||
const previousDiagram: WorkflowDiagram = {
|
||||
nodes: [
|
||||
{
|
||||
data: { nodeType: 'action', name: '', actionType: 'CODE' },
|
||||
id: '1',
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
};
|
||||
const nextDiagram: WorkflowDiagram = {
|
||||
nodes: [
|
||||
{
|
||||
data: { nodeType: 'action', name: '2', actionType: 'CODE' },
|
||||
id: '1',
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
expect(mergeWorkflowDiagrams(previousDiagram, nextDiagram)).toEqual({
|
||||
nodes: [
|
||||
{
|
||||
data: { nodeType: 'action', name: '2', actionType: 'CODE' },
|
||||
id: '1',
|
||||
position: { x: 0, y: 0 },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
});
|
||||
});
|
||||
@ -1,130 +0,0 @@
|
||||
import { WorkflowStep, WorkflowVersion } from '@/workflow/types/Workflow';
|
||||
import { removeStep } from '../removeStep';
|
||||
|
||||
it('returns a deep copy of the provided steps array instead of mutating it', () => {
|
||||
const stepToBeRemoved = {
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'first',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
} satisfies WorkflowStep;
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [stepToBeRemoved],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
|
||||
const stepsUpdated = removeStep({
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepId: stepToBeRemoved.id,
|
||||
});
|
||||
|
||||
expect(workflowVersionInitial.steps).not.toBe(stepsUpdated);
|
||||
});
|
||||
|
||||
it('removes a step in a non-empty steps array', () => {
|
||||
const stepToBeRemoved: WorkflowStep = {
|
||||
id: 'step-2',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
};
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [
|
||||
{
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
stepToBeRemoved,
|
||||
{
|
||||
id: 'step-3',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
|
||||
const stepsUpdated = removeStep({
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepId: stepToBeRemoved.id,
|
||||
});
|
||||
|
||||
const expectedUpdatedSteps: Array<WorkflowStep> = [
|
||||
workflowVersionInitial.steps[0],
|
||||
workflowVersionInitial.steps[2],
|
||||
];
|
||||
expect(stepsUpdated).toEqual(expectedUpdatedSteps);
|
||||
});
|
||||
@ -1,157 +0,0 @@
|
||||
import { WorkflowStep, WorkflowVersion } from '@/workflow/types/Workflow';
|
||||
import { replaceStep } from '../replaceStep';
|
||||
|
||||
describe('replaceStep', () => {
|
||||
it('returns a deep copy of the provided steps array instead of mutating it', () => {
|
||||
const stepToBeReplaced = {
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'first',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
} satisfies WorkflowStep;
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [stepToBeReplaced],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
|
||||
const stepsUpdated = replaceStep({
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepToReplace: {
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'second',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
stepId: stepToBeReplaced.id,
|
||||
});
|
||||
|
||||
expect(workflowVersionInitial.steps).not.toBe(stepsUpdated);
|
||||
});
|
||||
|
||||
it('replaces a step in a non-empty steps array', () => {
|
||||
const stepToBeReplaced: WorkflowStep = {
|
||||
id: 'step-2',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
};
|
||||
const workflowVersionInitial = {
|
||||
__typename: 'WorkflowVersion',
|
||||
status: 'ACTIVE',
|
||||
createdAt: '',
|
||||
id: '1',
|
||||
name: '',
|
||||
steps: [
|
||||
{
|
||||
id: 'step-1',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
stepToBeReplaced,
|
||||
{
|
||||
id: 'step-3',
|
||||
name: '',
|
||||
settings: {
|
||||
errorHandlingOptions: {
|
||||
retryOnFailure: { value: true },
|
||||
continueOnFailure: { value: false },
|
||||
},
|
||||
input: {
|
||||
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
|
||||
serverlessFunctionVersion: '1',
|
||||
serverlessFunctionInput: {},
|
||||
},
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'CODE',
|
||||
valid: true,
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
name: 'Company created',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
type: 'DATABASE_EVENT',
|
||||
},
|
||||
updatedAt: '',
|
||||
workflowId: '',
|
||||
} satisfies WorkflowVersion;
|
||||
|
||||
const updatedStepName = "that's another name";
|
||||
const stepsUpdated = replaceStep({
|
||||
stepId: stepToBeReplaced.id,
|
||||
steps: workflowVersionInitial.steps,
|
||||
stepToReplace: {
|
||||
name: updatedStepName,
|
||||
},
|
||||
});
|
||||
|
||||
const expectedUpdatedSteps: Array<WorkflowStep> = [
|
||||
workflowVersionInitial.steps[0],
|
||||
{
|
||||
...stepToBeReplaced,
|
||||
name: updatedStepName,
|
||||
},
|
||||
workflowVersionInitial.steps[2],
|
||||
];
|
||||
expect(stepsUpdated).toEqual(expectedUpdatedSteps);
|
||||
});
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
import { setNestedValue } from '@/workflow/utils/setNestedValue';
|
||||
|
||||
describe('setNestedValue', () => {
|
||||
it('should set nested value properly', () => {
|
||||
const obj = { a: { b: 'b' } };
|
||||
const path = ['a', 'b'];
|
||||
const newValue = 'bb';
|
||||
const expectedResult = { a: { b: newValue } };
|
||||
expect(setNestedValue(obj, path, newValue)).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('should not mutate the initial object', () => {
|
||||
const expectedObject = { a: { b: 'b' } };
|
||||
|
||||
const initialObject = structuredClone(expectedObject);
|
||||
const path = ['a', 'b'];
|
||||
const newValue = 'bb';
|
||||
|
||||
const updatedObject = setNestedValue(initialObject, path, newValue);
|
||||
|
||||
expect(initialObject).toEqual(expectedObject);
|
||||
|
||||
expect(updatedObject).not.toBe(initialObject);
|
||||
expect(updatedObject.a).not.toBe(initialObject.a);
|
||||
});
|
||||
});
|
||||
@ -1,47 +0,0 @@
|
||||
import {
|
||||
WorkflowDiagram,
|
||||
WorkflowDiagramEdge,
|
||||
WorkflowDiagramNode,
|
||||
} from '@/workflow/types/WorkflowDiagram';
|
||||
import { MarkerType } from '@xyflow/react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const addCreateStepNodes = ({ nodes, edges }: WorkflowDiagram) => {
|
||||
const nodesWithoutTargets = nodes.filter((node) =>
|
||||
edges.every((edge) => edge.source !== node.id),
|
||||
);
|
||||
|
||||
const updatedNodes: Array<WorkflowDiagramNode> = nodes.slice();
|
||||
const updatedEdges: Array<WorkflowDiagramEdge> = edges.slice();
|
||||
|
||||
for (const node of nodesWithoutTargets) {
|
||||
const newCreateStepNode: WorkflowDiagramNode = {
|
||||
// FIXME: We need a stable id for create step nodes to be able to preserve their selected status.
|
||||
// FIXME: In the future, we'll have conditions and loops. We'll have to set an id to each branch so we can have this stable id.
|
||||
id: 'branch-1__create-step',
|
||||
type: 'create-step',
|
||||
data: {
|
||||
nodeType: 'create-step',
|
||||
parentNodeId: node.id,
|
||||
},
|
||||
position: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
updatedNodes.push(newCreateStepNode);
|
||||
|
||||
updatedEdges.push({
|
||||
id: v4(),
|
||||
source: node.id,
|
||||
target: newCreateStepNode.id,
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
deletable: false,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
nodes: updatedNodes,
|
||||
edges: updatedEdges,
|
||||
};
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId';
|
||||
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
/**
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { findStepPosition } from '@/workflow/utils/findStepPosition';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
/**
|
||||
* This function returns the reference of the array where the step should be positioned
|
||||
* and at which index.
|
||||
*/
|
||||
export const findStepPositionOrThrow = (props: {
|
||||
steps: Array<WorkflowStep>;
|
||||
stepId: string | undefined;
|
||||
}): { steps: Array<WorkflowStep>; index: number } => {
|
||||
const result = findStepPosition(props);
|
||||
if (!isDefined(result)) {
|
||||
throw new Error(
|
||||
`Couldn't locate the step. Unreachable step id: ${props.stepId}.`,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
@ -1,125 +0,0 @@
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId';
|
||||
import { WorkflowStep, WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||
import {
|
||||
WorkflowDiagram,
|
||||
WorkflowDiagramEdge,
|
||||
WorkflowDiagramNode,
|
||||
} from '@/workflow/types/WorkflowDiagram';
|
||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
||||
import { MarkerType } from '@xyflow/react';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { v4 } from 'uuid';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
export const generateWorkflowDiagram = ({
|
||||
trigger,
|
||||
steps,
|
||||
}: {
|
||||
trigger: WorkflowTrigger | undefined;
|
||||
steps: Array<WorkflowStep>;
|
||||
}): WorkflowDiagram => {
|
||||
const nodes: Array<WorkflowDiagramNode> = [];
|
||||
const edges: Array<WorkflowDiagramEdge> = [];
|
||||
|
||||
// Helper function to generate nodes and edges recursively
|
||||
const processNode = (
|
||||
step: WorkflowStep,
|
||||
parentNodeId: string,
|
||||
xPos: number,
|
||||
yPos: number,
|
||||
) => {
|
||||
const nodeId = step.id;
|
||||
|
||||
nodes.push({
|
||||
id: nodeId,
|
||||
data: {
|
||||
nodeType: 'action',
|
||||
actionType: step.type,
|
||||
name: step.name,
|
||||
},
|
||||
position: {
|
||||
x: xPos,
|
||||
y: yPos,
|
||||
},
|
||||
});
|
||||
|
||||
// Create an edge from the parent node to this node
|
||||
edges.push({
|
||||
id: v4(),
|
||||
source: parentNodeId,
|
||||
target: nodeId,
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
deletable: false,
|
||||
selectable: false,
|
||||
});
|
||||
|
||||
return nodeId;
|
||||
};
|
||||
|
||||
// Start with the trigger node
|
||||
const triggerNodeId = TRIGGER_STEP_ID;
|
||||
|
||||
if (isDefined(trigger)) {
|
||||
let triggerLabel: string;
|
||||
|
||||
switch (trigger.type) {
|
||||
case 'MANUAL': {
|
||||
triggerLabel = 'Manual Trigger';
|
||||
|
||||
break;
|
||||
}
|
||||
case 'DATABASE_EVENT': {
|
||||
const triggerEvent = splitWorkflowTriggerEventName(
|
||||
trigger.settings.eventName,
|
||||
);
|
||||
|
||||
triggerLabel = `${capitalize(triggerEvent.objectType)} is ${capitalize(triggerEvent.event)}`;
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return assertUnreachable(
|
||||
trigger,
|
||||
`Expected the trigger "${JSON.stringify(trigger)}" to be supported.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
nodes.push({
|
||||
id: triggerNodeId,
|
||||
data: {
|
||||
nodeType: 'trigger',
|
||||
triggerType: trigger.type,
|
||||
name: isDefined(trigger.name) ? trigger.name : triggerLabel,
|
||||
},
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
nodes.push({
|
||||
id: triggerNodeId,
|
||||
type: 'empty-trigger',
|
||||
data: {} as any,
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let lastStepId = triggerNodeId;
|
||||
|
||||
for (const step of steps) {
|
||||
lastStepId = processNode(step, lastStepId, 150, 100);
|
||||
}
|
||||
|
||||
return {
|
||||
nodes,
|
||||
edges,
|
||||
};
|
||||
};
|
||||
@ -1,31 +0,0 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
WorkflowManualTriggerAvailability,
|
||||
WorkflowManualTriggerSettings,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||
|
||||
export const getManualTriggerDefaultSettings = ({
|
||||
availability,
|
||||
activeObjectMetadataItems,
|
||||
}: {
|
||||
availability: WorkflowManualTriggerAvailability;
|
||||
activeObjectMetadataItems: ObjectMetadataItem[];
|
||||
}): WorkflowManualTriggerSettings => {
|
||||
switch (availability) {
|
||||
case 'EVERYWHERE': {
|
||||
return {
|
||||
objectType: undefined,
|
||||
outputSchema: {},
|
||||
};
|
||||
}
|
||||
case 'WHEN_RECORD_SELECTED': {
|
||||
return {
|
||||
objectType: activeObjectMetadataItems[0].nameSingular,
|
||||
outputSchema: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return assertUnreachable(availability);
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram';
|
||||
import Dagre from '@dagrejs/dagre';
|
||||
|
||||
export const getOrganizedDiagram = (
|
||||
diagram: WorkflowDiagram,
|
||||
): WorkflowDiagram => {
|
||||
const graph = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
graph.setGraph({ rankdir: 'TB' });
|
||||
|
||||
diagram.edges.forEach((edge) => graph.setEdge(edge.source, edge.target));
|
||||
diagram.nodes.forEach((node) =>
|
||||
graph.setNode(node.id, {
|
||||
...node,
|
||||
width: node.measured?.width ?? 0,
|
||||
height: node.measured?.height ?? 0,
|
||||
}),
|
||||
);
|
||||
|
||||
Dagre.layout(graph);
|
||||
|
||||
return {
|
||||
nodes: diagram.nodes.map((node) => {
|
||||
const position = graph.node(node.id);
|
||||
// We are shifting the dagre node position (anchor=center center) to the top left
|
||||
// so it matches the React Flow node anchor point (top left).
|
||||
const x = position.x - (node.measured?.width ?? 0) / 2;
|
||||
const y = position.y - (node.measured?.height ?? 0) / 2;
|
||||
|
||||
return { ...node, position: { x, y } };
|
||||
}),
|
||||
edges: diagram.edges,
|
||||
};
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/constants/TriggerStepId';
|
||||
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
||||
import { findStepPosition } from '@/workflow/utils/findStepPosition';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const getStepDefinitionOrThrow = ({
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { OBJECT_EVENT_TRIGGERS } from '@/workflow/constants/ObjectEventTriggers';
|
||||
import {
|
||||
WorkflowTrigger,
|
||||
WorkflowTriggerType,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||
import { getManualTriggerDefaultSettings } from '@/workflow/utils/getManualTriggerDefaultSettings';
|
||||
|
||||
export const getTriggerDefaultDefinition = ({
|
||||
type,
|
||||
activeObjectMetadataItems,
|
||||
}: {
|
||||
type: WorkflowTriggerType;
|
||||
activeObjectMetadataItems: ObjectMetadataItem[];
|
||||
}): WorkflowTrigger => {
|
||||
if (activeObjectMetadataItems.length === 0) {
|
||||
throw new Error(
|
||||
'This function need to receive at least one object metadata item to run.',
|
||||
);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'DATABASE_EVENT': {
|
||||
return {
|
||||
type,
|
||||
settings: {
|
||||
eventName: `${activeObjectMetadataItems[0].nameSingular}.${OBJECT_EVENT_TRIGGERS[0].value}`,
|
||||
outputSchema: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'MANUAL': {
|
||||
return {
|
||||
type,
|
||||
settings: getManualTriggerDefaultSettings({
|
||||
availability: 'WHEN_RECORD_SELECTED',
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return assertUnreachable(type, `Unknown type: ${type}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,22 +0,0 @@
|
||||
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDiagram } from '@/workflow/types/WorkflowDiagram';
|
||||
import { generateWorkflowDiagram } from '@/workflow/utils/generateWorkflowDiagram';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
const EMPTY_DIAGRAM: WorkflowDiagram = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
export const getWorkflowVersionDiagram = (
|
||||
workflowVersion: WorkflowVersion | undefined,
|
||||
): WorkflowDiagram => {
|
||||
if (!isDefined(workflowVersion)) {
|
||||
return EMPTY_DIAGRAM;
|
||||
}
|
||||
|
||||
return generateWorkflowDiagram({
|
||||
trigger: workflowVersion.trigger ?? undefined,
|
||||
steps: workflowVersion.steps ?? [],
|
||||
});
|
||||
};
|
||||
@ -1,27 +0,0 @@
|
||||
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { findStepPositionOrThrow } from '@/workflow/utils/findStepPositionOrThrow';
|
||||
|
||||
export const insertStep = ({
|
||||
steps: stepsInitial,
|
||||
stepToAdd,
|
||||
parentStepId,
|
||||
}: {
|
||||
steps: Array<WorkflowStep>;
|
||||
parentStepId: string | undefined;
|
||||
stepToAdd: WorkflowStep;
|
||||
}): Array<WorkflowStep> => {
|
||||
const steps = structuredClone(stepsInitial);
|
||||
|
||||
const parentStepPosition = findStepPositionOrThrow({
|
||||
steps,
|
||||
stepId: parentStepId,
|
||||
});
|
||||
|
||||
parentStepPosition.steps.splice(
|
||||
parentStepPosition.index + 1, // The "+ 1" means that we add the step after its parent and not before.
|
||||
0,
|
||||
stepToAdd,
|
||||
);
|
||||
|
||||
return steps;
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import {
|
||||
WorkflowDiagram,
|
||||
WorkflowDiagramNode,
|
||||
} from '@/workflow/types/WorkflowDiagram';
|
||||
|
||||
const nodePropertiesToPreserve: Array<keyof WorkflowDiagramNode> = ['selected'];
|
||||
|
||||
export const mergeWorkflowDiagrams = (
|
||||
previousDiagram: WorkflowDiagram,
|
||||
nextDiagram: WorkflowDiagram,
|
||||
): WorkflowDiagram => {
|
||||
const lastNodes = nextDiagram.nodes.map((nextNode) => {
|
||||
const previousNode = previousDiagram.nodes.find(
|
||||
(previousNode) => previousNode.id === nextNode.id,
|
||||
);
|
||||
|
||||
const nodeWithPreservedProperties = nodePropertiesToPreserve.reduce(
|
||||
(nodeToSet, propertyToPreserve) => {
|
||||
return Object.assign(nodeToSet, {
|
||||
[propertyToPreserve]: previousNode?.[propertyToPreserve],
|
||||
});
|
||||
},
|
||||
{} as Partial<WorkflowDiagramNode>,
|
||||
);
|
||||
|
||||
return Object.assign(nodeWithPreservedProperties, nextNode);
|
||||
});
|
||||
|
||||
return {
|
||||
nodes: lastNodes,
|
||||
edges: nextDiagram.edges,
|
||||
};
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { findStepPositionOrThrow } from '@/workflow/utils/findStepPositionOrThrow';
|
||||
|
||||
export const removeStep = ({
|
||||
steps: stepsInitial,
|
||||
stepId,
|
||||
}: {
|
||||
steps: Array<WorkflowStep>;
|
||||
stepId: string | undefined;
|
||||
}) => {
|
||||
const steps = structuredClone(stepsInitial);
|
||||
|
||||
const parentStepPosition = findStepPositionOrThrow({
|
||||
steps,
|
||||
stepId,
|
||||
});
|
||||
|
||||
parentStepPosition.steps.splice(parentStepPosition.index, 1);
|
||||
|
||||
return steps;
|
||||
};
|
||||
@ -1,26 +0,0 @@
|
||||
import { WorkflowStep } from '@/workflow/types/Workflow';
|
||||
import { findStepPositionOrThrow } from '@/workflow/utils/findStepPositionOrThrow';
|
||||
|
||||
export const replaceStep = <T extends WorkflowStep>({
|
||||
steps: stepsInitial,
|
||||
stepId,
|
||||
stepToReplace,
|
||||
}: {
|
||||
steps: Array<WorkflowStep>;
|
||||
stepId: string;
|
||||
stepToReplace: Partial<Omit<T, 'id'>>;
|
||||
}) => {
|
||||
const steps = structuredClone(stepsInitial);
|
||||
|
||||
const parentStepPosition = findStepPositionOrThrow({
|
||||
steps,
|
||||
stepId,
|
||||
});
|
||||
|
||||
parentStepPosition.steps[parentStepPosition.index] = {
|
||||
...parentStepPosition.steps[parentStepPosition.index],
|
||||
...stepToReplace,
|
||||
};
|
||||
|
||||
return steps;
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
export const setNestedValue = (obj: any, path: string[], value: any) => {
|
||||
const newObj = structuredClone(obj);
|
||||
path.reduce((o, key, index) => {
|
||||
if (index === path.length - 1) {
|
||||
o[key] = value;
|
||||
}
|
||||
return o[key] || {};
|
||||
}, newObj);
|
||||
return newObj;
|
||||
};
|
||||
Reference in New Issue
Block a user