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:
@ -0,0 +1,16 @@
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||
import { RightDrawerWorkflowSelectTriggerTypeContent } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerTypeContent';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const RightDrawerWorkflowSelectTriggerType = () => {
|
||||
const workflowId = useRecoilValue(workflowIdState);
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowId);
|
||||
|
||||
if (!isDefined(workflow)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RightDrawerWorkflowSelectTriggerTypeContent workflow={workflow} />;
|
||||
};
|
||||
@ -0,0 +1,59 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useUpdateWorkflowVersionTrigger } from '@/workflow/hooks/useUpdateWorkflowVersionTrigger';
|
||||
import { workflowSelectedNodeState } from '@/workflow/states/workflowSelectedNodeState';
|
||||
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import { TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/TriggerTypes';
|
||||
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
|
||||
const StyledActionListContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
padding-block: ${({ theme }) => theme.spacing(1)};
|
||||
padding-inline: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const RightDrawerWorkflowSelectTriggerTypeContent = ({
|
||||
workflow,
|
||||
}: {
|
||||
workflow: WorkflowWithCurrentVersion;
|
||||
}) => {
|
||||
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
||||
|
||||
return (
|
||||
<StyledActionListContainer>
|
||||
{TRIGGER_TYPES.map((action) => (
|
||||
<MenuItem
|
||||
key={action.type}
|
||||
LeftIcon={action.icon}
|
||||
text={action.name}
|
||||
onClick={async () => {
|
||||
await updateTrigger(
|
||||
getTriggerDefaultDefinition({
|
||||
type: action.type,
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
);
|
||||
|
||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||
|
||||
openRightDrawer(RightDrawerPages.WorkflowStepEdit);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</StyledActionListContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,151 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow';
|
||||
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-step/components/WorkflowStepBody';
|
||||
import { WorkflowStepHeader } from '@/workflow/workflow-step/components/WorkflowStepHeader';
|
||||
import { OBJECT_EVENT_TRIGGERS } from '@/workflow/workflow-trigger/constants/ObjectEventTriggers';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconPlaylistAdd, isDefined } from 'twenty-ui';
|
||||
|
||||
type WorkflowEditTriggerDatabaseEventFormProps = {
|
||||
trigger: WorkflowDatabaseEventTrigger;
|
||||
triggerOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
onTriggerUpdate?: undefined;
|
||||
}
|
||||
| {
|
||||
readonly?: false;
|
||||
onTriggerUpdate: (trigger: WorkflowDatabaseEventTrigger) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const WorkflowEditTriggerDatabaseEventForm = ({
|
||||
trigger,
|
||||
triggerOptions,
|
||||
}: WorkflowEditTriggerDatabaseEventFormProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
const triggerEvent = isDefined(trigger)
|
||||
? splitWorkflowTriggerEventName(trigger.settings.eventName)
|
||||
: undefined;
|
||||
|
||||
const availableMetadata: Array<SelectOption<string>> =
|
||||
activeObjectMetadataItems.map((item) => ({
|
||||
label: item.labelPlural,
|
||||
value: item.nameSingular,
|
||||
}));
|
||||
const recordTypeMetadata = isDefined(triggerEvent)
|
||||
? activeObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === triggerEvent.objectType,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const selectedEvent = isDefined(triggerEvent)
|
||||
? OBJECT_EVENT_TRIGGERS.find(
|
||||
(availableEvent) => availableEvent.value === triggerEvent.event,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const headerTitle = isDefined(trigger.name)
|
||||
? trigger.name
|
||||
: isDefined(recordTypeMetadata) && isDefined(selectedEvent)
|
||||
? `When a ${recordTypeMetadata.labelSingular} is ${selectedEvent.label}`
|
||||
: '-';
|
||||
|
||||
const headerType = isDefined(selectedEvent)
|
||||
? `Trigger · Record is ${selectedEvent.label}`
|
||||
: '-';
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkflowStepHeader
|
||||
onTitleChange={(newName: string) => {
|
||||
if (triggerOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerOptions.onTriggerUpdate({
|
||||
...trigger,
|
||||
name: newName,
|
||||
});
|
||||
}}
|
||||
Icon={IconPlaylistAdd}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
initialTitle={headerTitle}
|
||||
headerType={headerType}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
<Select
|
||||
dropdownId="workflow-edit-trigger-record-type"
|
||||
label="Record Type"
|
||||
fullWidth
|
||||
disabled={triggerOptions.readonly}
|
||||
value={triggerEvent?.objectType}
|
||||
emptyOption={{ label: 'Select an option', value: '' }}
|
||||
options={availableMetadata}
|
||||
onChange={(updatedRecordType) => {
|
||||
if (triggerOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerOptions.onTriggerUpdate(
|
||||
isDefined(trigger) && isDefined(triggerEvent)
|
||||
? {
|
||||
...trigger,
|
||||
settings: {
|
||||
...trigger.settings,
|
||||
eventName: `${updatedRecordType}.${triggerEvent.event}`,
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: headerTitle,
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: `${updatedRecordType}.${OBJECT_EVENT_TRIGGERS[0].value}`,
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
dropdownId="workflow-edit-trigger-event-type"
|
||||
label="Event type"
|
||||
fullWidth
|
||||
value={triggerEvent?.event}
|
||||
emptyOption={{ label: 'Select an option', value: '' }}
|
||||
options={OBJECT_EVENT_TRIGGERS}
|
||||
disabled={triggerOptions.readonly}
|
||||
onChange={(updatedEvent) => {
|
||||
if (triggerOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerOptions.onTriggerUpdate(
|
||||
isDefined(trigger) && isDefined(triggerEvent)
|
||||
? {
|
||||
...trigger,
|
||||
settings: {
|
||||
...trigger.settings,
|
||||
eventName: `${triggerEvent.objectType}.${updatedEvent}`,
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: headerTitle,
|
||||
type: 'DATABASE_EVENT',
|
||||
settings: {
|
||||
eventName: `${availableMetadata?.[0].value}.${updatedEvent}`,
|
||||
outputSchema: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</WorkflowStepBody>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,117 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import {
|
||||
WorkflowManualTrigger,
|
||||
WorkflowManualTriggerAvailability,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-step/components/WorkflowStepBody';
|
||||
import { WorkflowStepHeader } from '@/workflow/workflow-step/components/WorkflowStepHeader';
|
||||
import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions';
|
||||
import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconHandMove, isDefined, useIcons } from 'twenty-ui';
|
||||
|
||||
type WorkflowEditTriggerManualFormProps = {
|
||||
trigger: WorkflowManualTrigger;
|
||||
triggerOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
onTriggerUpdate?: undefined;
|
||||
}
|
||||
| {
|
||||
readonly?: false;
|
||||
onTriggerUpdate: (trigger: WorkflowManualTrigger) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const WorkflowEditTriggerManualForm = ({
|
||||
trigger,
|
||||
triggerOptions,
|
||||
}: WorkflowEditTriggerManualFormProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
const availableMetadata: Array<SelectOption<string>> =
|
||||
activeObjectMetadataItems.map((item) => ({
|
||||
label: item.labelPlural,
|
||||
value: item.nameSingular,
|
||||
Icon: getIcon(item.icon),
|
||||
}));
|
||||
|
||||
const manualTriggerAvailability: WorkflowManualTriggerAvailability =
|
||||
isDefined(trigger.settings.objectType)
|
||||
? 'WHEN_RECORD_SELECTED'
|
||||
: 'EVERYWHERE';
|
||||
|
||||
const headerTitle = isDefined(trigger.name) ? trigger.name : 'Manual Trigger';
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkflowStepHeader
|
||||
onTitleChange={(newName: string) => {
|
||||
if (triggerOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerOptions.onTriggerUpdate({
|
||||
...trigger,
|
||||
name: newName,
|
||||
});
|
||||
}}
|
||||
Icon={IconHandMove}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Trigger · Manual"
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
<Select
|
||||
dropdownId="workflow-edit-manual-trigger-availability"
|
||||
label="Available"
|
||||
fullWidth
|
||||
disabled={triggerOptions.readonly}
|
||||
value={manualTriggerAvailability}
|
||||
options={MANUAL_TRIGGER_AVAILABILITY_OPTIONS}
|
||||
onChange={(updatedTriggerType) => {
|
||||
if (triggerOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerOptions.onTriggerUpdate({
|
||||
...trigger,
|
||||
settings: getManualTriggerDefaultSettings({
|
||||
availability: updatedTriggerType,
|
||||
activeObjectMetadataItems,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{manualTriggerAvailability === 'WHEN_RECORD_SELECTED' ? (
|
||||
<Select
|
||||
dropdownId="workflow-edit-manual-trigger-object"
|
||||
label="Object"
|
||||
fullWidth
|
||||
value={trigger.settings.objectType}
|
||||
options={availableMetadata}
|
||||
disabled={triggerOptions.readonly}
|
||||
onChange={(updatedObject) => {
|
||||
if (triggerOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerOptions.onTriggerUpdate({
|
||||
...trigger,
|
||||
settings: {
|
||||
objectType: updatedObject,
|
||||
outputSchema: {},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</WorkflowStepBody>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { WorkflowManualTriggerAvailability } from '@/workflow/types/Workflow';
|
||||
import { IconCheckbox, IconComponent, IconSquare } from 'twenty-ui';
|
||||
|
||||
export const MANUAL_TRIGGER_AVAILABILITY_OPTIONS: Array<{
|
||||
label: string;
|
||||
value: WorkflowManualTriggerAvailability;
|
||||
Icon: IconComponent;
|
||||
}> = [
|
||||
{
|
||||
label: 'When record(s) are selected',
|
||||
value: 'WHEN_RECORD_SELECTED',
|
||||
Icon: IconCheckbox,
|
||||
},
|
||||
{
|
||||
label: 'When no record(s) are selected',
|
||||
value: 'EVERYWHERE',
|
||||
Icon: IconSquare,
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,16 @@
|
||||
import { SelectOption } from '@/ui/input/components/Select';
|
||||
|
||||
export const OBJECT_EVENT_TRIGGERS: Array<SelectOption<string>> = [
|
||||
{
|
||||
label: 'Created',
|
||||
value: 'created',
|
||||
},
|
||||
{
|
||||
label: 'Updated',
|
||||
value: 'updated',
|
||||
},
|
||||
{
|
||||
label: 'Deleted',
|
||||
value: 'deleted',
|
||||
},
|
||||
];
|
||||
@ -0,0 +1 @@
|
||||
export const TRIGGER_STEP_ID = 'trigger';
|
||||
@ -0,0 +1,19 @@
|
||||
import { WorkflowTriggerType } from '@/workflow/types/Workflow';
|
||||
import { IconComponent, IconSettingsAutomation } from 'twenty-ui';
|
||||
|
||||
export const TRIGGER_TYPES: Array<{
|
||||
name: string;
|
||||
type: WorkflowTriggerType;
|
||||
icon: IconComponent;
|
||||
}> = [
|
||||
{
|
||||
name: 'Database Event',
|
||||
type: 'DATABASE_EVENT',
|
||||
icon: IconSettingsAutomation,
|
||||
},
|
||||
{
|
||||
name: 'Manual Trigger',
|
||||
type: 'MANUAL',
|
||||
icon: IconSettingsAutomation,
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,26 @@
|
||||
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: {},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,50 @@
|
||||
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');
|
||||
});
|
||||
@ -0,0 +1,31 @@
|
||||
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);
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
WorkflowTrigger,
|
||||
WorkflowTriggerType,
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||
import { OBJECT_EVENT_TRIGGERS } from '@/workflow/workflow-trigger/constants/ObjectEventTriggers';
|
||||
import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/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}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user