Implement branch front end (#13489)
First PR to support workflow branches on frontend
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ import { CRON_TRIGGER_INTERVAL_OPTIONS } from '@/workflow/workflow-trigger/const
|
||||
import { getCronTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getCronTriggerDefaultSettings';
|
||||
import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType';
|
||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNumber } from '@sniptt/guards';
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS
|
||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||
import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType';
|
||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/workflow-trigger
|
||||
import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings';
|
||||
import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType';
|
||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { WEBHOOK_TRIGGER_AUTHENTICATION_OPTIONS } from '@/workflow/workflow-trig
|
||||
import { WEBHOOK_TRIGGER_HTTP_METHOD_OPTIONS } from '@/workflow/workflow-trigger/constants/WebhookTriggerHttpMethodOptions';
|
||||
import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType';
|
||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel';
|
||||
import { getWebhookTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getWebhookTriggerDefaultSettings';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
|
||||
import {
|
||||
WorkflowTrigger,
|
||||
WorkflowWithCurrentVersion,
|
||||
} from '@/workflow/types/Workflow';
|
||||
|
||||
const mockUpdateOneRecord = jest.fn();
|
||||
const mockGetUpdatableWorkflowVersion = jest.fn();
|
||||
const mockComputeStepOutputSchema = jest.fn();
|
||||
|
||||
jest.mock('@/object-record/hooks/useUpdateOneRecord', () => ({
|
||||
useUpdateOneRecord: jest.fn(() => ({
|
||||
updateOneRecord: mockUpdateOneRecord,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('@/workflow/hooks/useGetUpdatableWorkflowVersion', () => ({
|
||||
useGetUpdatableWorkflowVersion: jest.fn(() => ({
|
||||
getUpdatableWorkflowVersion: mockGetUpdatableWorkflowVersion,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('@/workflow/hooks/useComputeStepOutputSchema', () => ({
|
||||
useComputeStepOutputSchema: jest.fn(() => ({
|
||||
computeStepOutputSchema: mockComputeStepOutputSchema,
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('useUpdateWorkflowVersionTrigger', () => {
|
||||
const mockWorkflow = {
|
||||
id: 'workflow-id',
|
||||
currentVersion: { id: 'version-id' },
|
||||
} as WorkflowWithCurrentVersion;
|
||||
|
||||
const trigger: WorkflowTrigger = {
|
||||
name: 'Company created',
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: 'company.created',
|
||||
outputSchema: {},
|
||||
},
|
||||
nextStepIds: ['step1'],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('updates the trigger with computed output schema', async () => {
|
||||
mockGetUpdatableWorkflowVersion.mockResolvedValue('version-id');
|
||||
mockComputeStepOutputSchema.mockResolvedValue({
|
||||
data: { computeStepOutputSchema: { field1: 'string' } },
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useUpdateWorkflowVersionTrigger({ workflow: mockWorkflow }),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.updateTrigger(trigger);
|
||||
});
|
||||
|
||||
expect(mockGetUpdatableWorkflowVersion).toHaveBeenCalledWith(mockWorkflow);
|
||||
expect(mockComputeStepOutputSchema).toHaveBeenCalledWith({ step: trigger });
|
||||
expect(mockUpdateOneRecord).toHaveBeenCalledWith({
|
||||
idToUpdate: 'version-id',
|
||||
updateOneRecordInput: {
|
||||
trigger: {
|
||||
...trigger,
|
||||
settings: { ...trigger.settings, outputSchema: { field1: 'string' } },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('skips output schema computation when disabled', async () => {
|
||||
mockGetUpdatableWorkflowVersion.mockResolvedValue('version-id');
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useUpdateWorkflowVersionTrigger({ workflow: mockWorkflow }),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.updateTrigger(trigger, {
|
||||
computeOutputSchema: false,
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockComputeStepOutputSchema).not.toHaveBeenCalled();
|
||||
expect(mockUpdateOneRecord).toHaveBeenCalledWith({
|
||||
idToUpdate: 'version-id',
|
||||
updateOneRecordInput: {
|
||||
trigger,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
+35
@@ -28,6 +28,11 @@ describe('getTriggerDefaultDefinition', () => {
|
||||
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.created`,
|
||||
outputSchema: {},
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +50,11 @@ describe('getTriggerDefaultDefinition', () => {
|
||||
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.updated`,
|
||||
outputSchema: {},
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +72,11 @@ describe('getTriggerDefaultDefinition', () => {
|
||||
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.deleted`,
|
||||
outputSchema: {},
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,6 +94,11 @@ describe('getTriggerDefaultDefinition', () => {
|
||||
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.created`,
|
||||
outputSchema: {},
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,6 +117,11 @@ describe('getTriggerDefaultDefinition', () => {
|
||||
outputSchema: {},
|
||||
icon: COMMAND_MENU_DEFAULT_ICON,
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,6 +140,11 @@ describe('getTriggerDefaultDefinition', () => {
|
||||
schedule: { day: 1, hour: 0, minute: 0 },
|
||||
outputSchema: {},
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,6 +163,11 @@ describe('getTriggerDefaultDefinition', () => {
|
||||
httpMethod: 'GET',
|
||||
authentication: null,
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||
import { DatabaseTriggerDefaultLabel } from '@/workflow/workflow-trigger/constants/DatabaseTriggerDefaultLabel';
|
||||
import { getTriggerDefaultLabel } from '../getTriggerLabel';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel';
|
||||
|
||||
describe('getTriggerDefaultLabel', () => {
|
||||
describe('DATABASE_EVENT triggers', () => {
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||
|
||||
describe('getTriggerIcon', () => {
|
||||
it('should return proper Icon for DATABASE_EVENT', () => {
|
||||
expect(
|
||||
getTriggerIcon({
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: { eventName: 'company.created', outputSchema: {} },
|
||||
}),
|
||||
).toBe('IconPlaylistAdd');
|
||||
});
|
||||
|
||||
it('should return proper Icon for MANUAL', () => {
|
||||
expect(
|
||||
getTriggerIcon({
|
||||
type: 'MANUAL',
|
||||
settings: { outputSchema: {} },
|
||||
}),
|
||||
).toBe('IconHandMove');
|
||||
});
|
||||
|
||||
it('should return proper Icon for CRON', () => {
|
||||
expect(
|
||||
getTriggerIcon({
|
||||
type: 'CRON',
|
||||
settings: { outputSchema: {}, type: 'CUSTOM', pattern: '' },
|
||||
}),
|
||||
).toBe('IconClock');
|
||||
});
|
||||
|
||||
it('should return proper Icon for CRON', () => {
|
||||
expect(
|
||||
getTriggerIcon({
|
||||
type: 'WEBHOOK',
|
||||
settings: { outputSchema: {}, httpMethod: 'GET', authentication: null },
|
||||
}),
|
||||
).toBe('IconWebhook');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { WorkflowAction } from '@/workflow/types/Workflow';
|
||||
|
||||
export const getRootStepIds = (steps: WorkflowAction[]): string[] => {
|
||||
const childIds = new Set<string>();
|
||||
|
||||
for (const step of steps) {
|
||||
step.nextStepIds?.forEach((id) => childIds.add(id));
|
||||
}
|
||||
|
||||
return steps.filter((step) => !childIds.has(step.id)).map((step) => step.id);
|
||||
};
|
||||
+18
-4
@@ -1,20 +1,26 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
WorkflowAction,
|
||||
WorkflowTrigger,
|
||||
WorkflowTriggerType,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||
import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes';
|
||||
import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getRootStepIds } from '@/workflow/workflow-trigger/utils/getRootStepIds';
|
||||
|
||||
// TODO: This needs to be migrated to the server
|
||||
export const getTriggerDefaultDefinition = ({
|
||||
defaultLabel,
|
||||
type,
|
||||
activeNonSystemObjectMetadataItems,
|
||||
steps,
|
||||
}: {
|
||||
defaultLabel: string;
|
||||
type: WorkflowTriggerType;
|
||||
activeNonSystemObjectMetadataItems: ObjectMetadataItem[];
|
||||
steps?: WorkflowAction[] | null;
|
||||
}): WorkflowTrigger => {
|
||||
if (activeNonSystemObjectMetadataItems.length === 0) {
|
||||
throw new Error(
|
||||
@@ -22,11 +28,19 @@ export const getTriggerDefaultDefinition = ({
|
||||
);
|
||||
}
|
||||
|
||||
const nextStepIds = isDefined(steps) ? getRootStepIds(steps) : [];
|
||||
|
||||
const baseTriggerDefinition = {
|
||||
name: defaultLabel,
|
||||
position: { x: 0, y: 0 },
|
||||
nextStepIds,
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'DATABASE_EVENT': {
|
||||
return {
|
||||
...baseTriggerDefinition,
|
||||
type,
|
||||
name: defaultLabel,
|
||||
settings: {
|
||||
eventName: `${activeNonSystemObjectMetadataItems[0].nameSingular}.${
|
||||
DATABASE_TRIGGER_TYPES.find(
|
||||
@@ -39,8 +53,8 @@ export const getTriggerDefaultDefinition = ({
|
||||
}
|
||||
case 'MANUAL': {
|
||||
return {
|
||||
...baseTriggerDefinition,
|
||||
type,
|
||||
name: defaultLabel,
|
||||
settings: getManualTriggerDefaultSettings({
|
||||
availability: 'WHEN_RECORD_SELECTED',
|
||||
activeNonSystemObjectMetadataItems,
|
||||
@@ -49,8 +63,8 @@ export const getTriggerDefaultDefinition = ({
|
||||
}
|
||||
case 'CRON': {
|
||||
return {
|
||||
...baseTriggerDefinition,
|
||||
type,
|
||||
name: defaultLabel,
|
||||
settings: {
|
||||
type: 'DAYS',
|
||||
schedule: { day: 1, hour: 0, minute: 0 },
|
||||
@@ -60,8 +74,8 @@ export const getTriggerDefaultDefinition = ({
|
||||
}
|
||||
case 'WEBHOOK': {
|
||||
return {
|
||||
...baseTriggerDefinition,
|
||||
type,
|
||||
name: defaultLabel,
|
||||
settings: {
|
||||
outputSchema: {},
|
||||
httpMethod: 'GET',
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerLabel';
|
||||
import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
export const getTriggerHeaderType = (trigger: WorkflowTrigger) => {
|
||||
|
||||
Reference in New Issue
Block a user