8726 workflow add a test button in workflow code step (#9016)

- add test button to workflow code step
- add test tab to workflow code step


https://github.com/user-attachments/assets/e180a827-7321-49a2-8026-88490c557da2



![image](https://github.com/user-attachments/assets/cacbd756-de3f-4141-a84c-8e1853f6556b)

![image](https://github.com/user-attachments/assets/ee170d81-8a22-4178-bd6d-11a0e8c73365)
This commit is contained in:
martmull
2024-12-13 11:16:29 +01:00
committed by GitHub
parent 07aaf0801c
commit b10d831371
95 changed files with 1537 additions and 1611 deletions

View File

@ -1,11 +1,12 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
import { WorkflowStepHeader } from '@/workflow/components/WorkflowStepHeader';
import { OBJECT_EVENT_TRIGGERS } from '@/workflow/constants/ObjectEventTriggers';
import { WorkflowDatabaseEventTrigger } from '@/workflow/types/Workflow';
import { splitWorkflowTriggerEventName } from '@/workflow/utils/splitWorkflowTriggerEventName';
import { useTheme } from '@emotion/react';
import { IconPlaylistAdd, isDefined } from 'twenty-ui';
import { WorkflowStepBody } from '@/workflow/components/WorkflowStepBody';
type WorkflowEditTriggerDatabaseEventFormProps = {
trigger: WorkflowDatabaseEventTrigger;
@ -60,88 +61,91 @@ export const WorkflowEditTriggerDatabaseEventForm = ({
: '-';
return (
<WorkflowEditGenericFormBase
onTitleChange={(newName: string) => {
if (triggerOptions.readonly === true) {
return;
}
triggerOptions.onTriggerUpdate({
...trigger,
name: newName,
});
}}
Icon={IconPlaylistAdd}
iconColor={theme.font.color.tertiary}
initialTitle={headerTitle}
headerType={headerType}
>
<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) => {
<>
<WorkflowStepHeader
onTitleChange={(newName: string) => {
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: {},
},
},
);
triggerOptions.onTriggerUpdate({
...trigger,
name: newName,
});
}}
Icon={IconPlaylistAdd}
iconColor={theme.font.color.tertiary}
initialTitle={headerTitle}
headerType={headerType}
/>
<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;
}
<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: `${triggerEvent.objectType}.${updatedEvent}`,
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: {},
},
},
}
: {
name: headerTitle,
type: 'DATABASE_EVENT',
settings: {
eventName: `${availableMetadata[0].value}.${updatedEvent}`,
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: {},
},
},
},
);
}}
/>
</WorkflowEditGenericFormBase>
);
}}
/>
</WorkflowStepBody>
</>
);
};

View File

@ -1,6 +1,6 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowEditGenericFormBase } from '@/workflow/components/WorkflowEditGenericFormBase';
import { WorkflowStepHeader } from '@/workflow/components/WorkflowStepHeader';
import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/constants/ManualTriggerAvailabilityOptions';
import {
WorkflowManualTrigger,
@ -9,6 +9,7 @@ import {
import { getManualTriggerDefaultSettings } from '@/workflow/utils/getManualTriggerDefaultSettings';
import { useTheme } from '@emotion/react';
import { IconHandMove, isDefined, useIcons } from 'twenty-ui';
import { WorkflowStepBody } from '@/workflow/components/WorkflowStepBody';
type WorkflowEditTriggerManualFormProps = {
trigger: WorkflowManualTrigger;
@ -47,67 +48,70 @@ export const WorkflowEditTriggerManualForm = ({
const headerTitle = isDefined(trigger.name) ? trigger.name : 'Manual Trigger';
return (
<WorkflowEditGenericFormBase
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"
>
<Select
dropdownId="workflow-edit-manual-trigger-availability"
label="Available"
fullWidth
disabled={triggerOptions.readonly}
value={manualTriggerAvailability}
options={MANUAL_TRIGGER_AVAILABILITY_OPTIONS}
onChange={(updatedTriggerType) => {
<>
<WorkflowStepHeader
onTitleChange={(newName: string) => {
if (triggerOptions.readonly === true) {
return;
}
triggerOptions.onTriggerUpdate({
...trigger,
settings: getManualTriggerDefaultSettings({
availability: updatedTriggerType,
activeObjectMetadataItems,
}),
name: newName,
});
}}
Icon={IconHandMove}
iconColor={theme.font.color.tertiary}
initialTitle={headerTitle}
headerType="Trigger · Manual"
/>
{manualTriggerAvailability === 'WHEN_RECORD_SELECTED' ? (
<WorkflowStepBody>
<Select
dropdownId="workflow-edit-manual-trigger-object"
label="Object"
dropdownId="workflow-edit-manual-trigger-availability"
label="Available"
fullWidth
value={trigger.settings.objectType}
options={availableMetadata}
disabled={triggerOptions.readonly}
onChange={(updatedObject) => {
value={manualTriggerAvailability}
options={MANUAL_TRIGGER_AVAILABILITY_OPTIONS}
onChange={(updatedTriggerType) => {
if (triggerOptions.readonly === true) {
return;
}
triggerOptions.onTriggerUpdate({
...trigger,
settings: {
objectType: updatedObject,
outputSchema: {},
},
settings: getManualTriggerDefaultSettings({
availability: updatedTriggerType,
activeObjectMetadataItems,
}),
});
}}
/>
) : null}
</WorkflowEditGenericFormBase>
{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>
</>
);
};

View File

@ -6,8 +6,8 @@ import {
} from 'twenty-ui';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { StyledFormFieldInputContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputContainer';
import { StyledFormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/StyledFormFieldInputRowContainer';
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
import { SingleRecordSelect } from '@/object-record/relation-picker/components/SingleRecordSelect';
import { useRecordPicker } from '@/object-record/relation-picker/hooks/useRecordPicker';
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
@ -137,9 +137,9 @@ export const WorkflowSingleRecordPicker = ({
};
return (
<StyledFormFieldInputContainer>
<FormFieldInputContainer>
{label ? <InputLabel>{label}</InputLabel> : null}
<StyledFormFieldInputRowContainer>
<FormFieldInputRowContainer>
<StyledFormSelectContainer>
<WorkflowSingleRecordFieldChip
draftValue={draftValue}
@ -193,7 +193,7 @@ export const WorkflowSingleRecordPicker = ({
objectNameSingularToSelect={objectNameSingular}
/>
</StyledSearchVariablesDropdownContainer>
</StyledFormFieldInputRowContainer>
</StyledFormFieldInputContainer>
</FormFieldInputRowContainer>
</FormFieldInputContainer>
);
};

View File

@ -0,0 +1,12 @@
import styled from '@emotion/styled';
const StyledWorkflowStepBody = styled.div`
display: flex;
flex-direction: column;
overflow-y: scroll;
padding: ${({ theme }) => theme.spacing(6)};
row-gap: ${({ theme }) => theme.spacing(6)};
flex: 1 1 auto;
`;
export { StyledWorkflowStepBody as WorkflowStepBody };

View File

@ -10,8 +10,8 @@ import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrTh
import { WorkflowEditActionFormCreateRecord } from '@/workflow/workflow-actions/components/WorkflowEditActionFormCreateRecord';
import { WorkflowEditActionFormDeleteRecord } from '@/workflow/workflow-actions/components/WorkflowEditActionFormDeleteRecord';
import { WorkflowEditActionFormSendEmail } from '@/workflow/workflow-actions/components/WorkflowEditActionFormSendEmail';
import { Suspense, lazy } from 'react';
import { WorkflowEditActionFormUpdateRecord } from '@/workflow/workflow-actions/components/WorkflowEditActionFormUpdateRecord';
import { lazy, Suspense } from 'react';
import { isDefined } from 'twenty-ui';
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';

View File

@ -43,27 +43,18 @@ const StyledHeaderIconContainer = styled.div`
padding: ${({ theme }) => theme.spacing(2)};
`;
const StyledContentContainer = styled.div`
padding: ${({ theme }) => theme.spacing(6)};
display: flex;
flex-direction: column;
row-gap: ${({ theme }) => theme.spacing(4)};
`;
export const WorkflowEditGenericFormBase = ({
export const WorkflowStepHeader = ({
onTitleChange,
Icon,
iconColor,
initialTitle,
headerType,
children,
}: {
onTitleChange: (newTitle: string) => void;
Icon: IconComponent;
iconColor: string;
initialTitle: string;
headerType: string;
children: React.ReactNode;
}) => {
const theme = useTheme();
const [title, setTitle] = useState(initialTitle);
@ -74,33 +65,30 @@ export const WorkflowEditGenericFormBase = ({
};
return (
<>
<StyledHeader>
<StyledHeaderIconContainer>
{
<Icon
color={iconColor}
stroke={theme.icon.stroke.sm}
size={theme.icon.size.lg}
/>
}
</StyledHeaderIconContainer>
<StyledHeaderInfo>
<StyledHeaderTitle>
<TextInput
value={title}
copyButton={false}
hotkeyScope="workflow-step-title"
onEnter={onTitleChange}
onEscape={onTitleChange}
onChange={handleChange}
shouldTrim={false}
/>
</StyledHeaderTitle>
<StyledHeaderType>{headerType}</StyledHeaderType>
</StyledHeaderInfo>
</StyledHeader>
<StyledContentContainer>{children}</StyledContentContainer>
</>
<StyledHeader>
<StyledHeaderIconContainer>
{
<Icon
color={iconColor}
stroke={theme.icon.stroke.sm}
size={theme.icon.size.lg}
/>
}
</StyledHeaderIconContainer>
<StyledHeaderInfo>
<StyledHeaderTitle>
<TextInput
value={title}
copyButton={false}
hotkeyScope="workflow-step-title"
onEnter={onTitleChange}
onEscape={onTitleChange}
onChange={handleChange}
shouldTrim={false}
/>
</StyledHeaderTitle>
<StyledHeaderType>{headerType}</StyledHeaderType>
</StyledHeaderInfo>
</StyledHeader>
);
};