Add icon select to manual trigger (#12724)
## After <img width="1220" alt="image" src="https://github.com/user-attachments/assets/98a73aae-80d7-4e92-93d3-be13210da88b" /> <img width="1131" alt="image" src="https://github.com/user-attachments/assets/9919e415-4355-4995-8979-9055b821f1e9" /> <img width="1300" alt="image" src="https://github.com/user-attachments/assets/27f11cb3-d72c-468a-a641-8414172b9b54" /> <img width="1353" alt="image" src="https://github.com/user-attachments/assets/2f0037f2-fe17-48b6-b7e6-c7528687a5fd" />
This commit is contained in:
@ -11,7 +11,8 @@ import { msg } from '@lingui/core/macro';
|
|||||||
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
import { IconSettingsAutomation } from 'twenty-ui/display';
|
import { useIcons } from 'twenty-ui/display';
|
||||||
|
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||||
|
|
||||||
export const useRunWorkflowRecordActions = ({
|
export const useRunWorkflowRecordActions = ({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
@ -20,6 +21,7 @@ export const useRunWorkflowRecordActions = ({
|
|||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { getIcon } = useIcons();
|
||||||
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
||||||
contextStoreTargetedRecordsRuleComponentState,
|
contextStoreTargetedRecordsRuleComponentState,
|
||||||
);
|
);
|
||||||
@ -48,13 +50,18 @@ export const useRunWorkflowRecordActions = ({
|
|||||||
.map((activeWorkflowVersion, index) => {
|
.map((activeWorkflowVersion, index) => {
|
||||||
const name = capitalize(activeWorkflowVersion.workflow.name);
|
const name = capitalize(activeWorkflowVersion.workflow.name);
|
||||||
|
|
||||||
|
const Icon = getIcon(
|
||||||
|
activeWorkflowVersion.trigger?.settings.icon,
|
||||||
|
COMMAND_MENU_DEFAULT_ICON,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: ActionType.WorkflowRun,
|
type: ActionType.WorkflowRun,
|
||||||
key: `workflow-run-${activeWorkflowVersion.id}`,
|
key: `workflow-run-${activeWorkflowVersion.id}`,
|
||||||
scope: ActionScope.RecordSelection,
|
scope: ActionScope.RecordSelection,
|
||||||
label: msg`${name}`,
|
label: msg`${name}`,
|
||||||
position: index,
|
position: index,
|
||||||
Icon: IconSettingsAutomation,
|
Icon,
|
||||||
shouldBeRegistered: () => true,
|
shouldBeRegistered: () => true,
|
||||||
component: (
|
component: (
|
||||||
<Action
|
<Action
|
||||||
|
|||||||
@ -7,9 +7,12 @@ import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
import { IconSettingsAutomation } from 'twenty-ui/display';
|
import { useIcons } from 'twenty-ui/display';
|
||||||
|
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||||
|
|
||||||
export const useRunWorkflowRecordAgnosticActions = () => {
|
export const useRunWorkflowRecordAgnosticActions = () => {
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
const { actionMenuType } = useContext(ActionMenuContext);
|
const { actionMenuType } = useContext(ActionMenuContext);
|
||||||
|
|
||||||
const { records: activeWorkflowVersions } =
|
const { records: activeWorkflowVersions } =
|
||||||
@ -29,13 +32,18 @@ export const useRunWorkflowRecordAgnosticActions = () => {
|
|||||||
|
|
||||||
const name = capitalize(activeWorkflowVersion.workflow.name);
|
const name = capitalize(activeWorkflowVersion.workflow.name);
|
||||||
|
|
||||||
|
const Icon = getIcon(
|
||||||
|
activeWorkflowVersion.trigger?.settings.icon,
|
||||||
|
COMMAND_MENU_DEFAULT_ICON,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: ActionType.WorkflowRun,
|
type: ActionType.WorkflowRun,
|
||||||
key: `workflow-run-${activeWorkflowVersion.id}`,
|
key: `workflow-run-${activeWorkflowVersion.id}`,
|
||||||
scope: ActionScope.Global,
|
scope: ActionScope.Global,
|
||||||
label: msg`${name}`,
|
label: msg`${name}`,
|
||||||
position: index,
|
position: index,
|
||||||
Icon: IconSettingsAutomation,
|
Icon,
|
||||||
shouldBeRegistered: () => true,
|
shouldBeRegistered: () => true,
|
||||||
component: (
|
component: (
|
||||||
<Action
|
<Action
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useMemo, useState } from 'react';
|
import { ReactNode, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
@ -25,6 +25,8 @@ import {
|
|||||||
LightIconButton,
|
LightIconButton,
|
||||||
} from 'twenty-ui/input';
|
} from 'twenty-ui/input';
|
||||||
import { IconPickerHotkeyScope } from '../types/IconPickerHotkeyScope';
|
import { IconPickerHotkeyScope } from '../types/IconPickerHotkeyScope';
|
||||||
|
import { DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
|
||||||
|
|
||||||
export type IconPickerProps = {
|
export type IconPickerProps = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
dropdownId?: string;
|
dropdownId?: string;
|
||||||
@ -36,6 +38,10 @@ export type IconPickerProps = {
|
|||||||
variant?: IconButtonVariant;
|
variant?: IconButtonVariant;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: IconButtonSize;
|
size?: IconButtonSize;
|
||||||
|
clickableComponent?: ReactNode;
|
||||||
|
dropdownWidth?: number;
|
||||||
|
dropdownOffset?: DropdownOffset;
|
||||||
|
maxIconsVisible?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledMenuIconItemsContainer = styled.div`
|
const StyledMenuIconItemsContainer = styled.div`
|
||||||
@ -102,6 +108,10 @@ export const IconPicker = ({
|
|||||||
variant = 'secondary',
|
variant = 'secondary',
|
||||||
className,
|
className,
|
||||||
size = 'medium',
|
size = 'medium',
|
||||||
|
clickableComponent,
|
||||||
|
dropdownWidth,
|
||||||
|
dropdownOffset,
|
||||||
|
maxIconsVisible = 25,
|
||||||
}: IconPickerProps) => {
|
}: IconPickerProps) => {
|
||||||
const [searchString, setSearchString] = useState('');
|
const [searchString, setSearchString] = useState('');
|
||||||
const {
|
const {
|
||||||
@ -171,9 +181,9 @@ export const IconPicker = ({
|
|||||||
...filteredAndSortedIconKeys.filter(
|
...filteredAndSortedIconKeys.filter(
|
||||||
(iconKey) => iconKey !== selectedIconKey,
|
(iconKey) => iconKey !== selectedIconKey,
|
||||||
),
|
),
|
||||||
].slice(0, 25)
|
].slice(0, maxIconsVisible)
|
||||||
: filteredAndSortedIconKeys.slice(0, 25);
|
: filteredAndSortedIconKeys.slice(0, maxIconsVisible);
|
||||||
}, [icons, searchString, selectedIconKey]);
|
}, [icons, searchString, selectedIconKey, maxIconsVisible]);
|
||||||
|
|
||||||
const iconKeys2d = useMemo(
|
const iconKeys2d = useMemo(
|
||||||
() => arrayToChunks(matchingSearchIconKeys.slice(), 5),
|
() => arrayToChunks(matchingSearchIconKeys.slice(), 5),
|
||||||
@ -186,21 +196,26 @@ export const IconPicker = ({
|
|||||||
<div className={className}>
|
<div className={className}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
|
dropdownOffset={dropdownOffset}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
<IconButton
|
clickableComponent || (
|
||||||
ariaLabel={`Click to select icon ${
|
<IconButton
|
||||||
selectedIconKey
|
ariaLabel={`Click to select icon ${
|
||||||
? `(selected: ${selectedIconKey})`
|
selectedIconKey
|
||||||
: `(no icon selected)`
|
? `(selected: ${selectedIconKey})`
|
||||||
}`}
|
: `(no icon selected)`
|
||||||
disabled={disabled}
|
}`}
|
||||||
Icon={icon}
|
disabled={disabled}
|
||||||
variant={variant}
|
Icon={icon}
|
||||||
size={size}
|
variant={variant}
|
||||||
/>
|
size={size}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<DropdownContent widthInPixels={ICON_PICKER_DROPDOWN_CONTENT_WIDTH}>
|
<DropdownContent
|
||||||
|
widthInPixels={dropdownWidth || ICON_PICKER_DROPDOWN_CONTENT_WIDTH}
|
||||||
|
>
|
||||||
<SelectableList
|
<SelectableList
|
||||||
selectableListInstanceId="icon-list"
|
selectableListInstanceId="icon-list"
|
||||||
selectableItemIdMatrix={iconKeys2d}
|
selectableItemIdMatrix={iconKeys2d}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export type SelectProps<Value extends SelectValue> = {
|
|||||||
emptyOption?: SelectOption<Value>;
|
emptyOption?: SelectOption<Value>;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
description?: string;
|
||||||
onChange?: (value: Value) => void;
|
onChange?: (value: Value) => void;
|
||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
options: SelectOption<Value>[];
|
options: SelectOption<Value>[];
|
||||||
@ -64,6 +65,11 @@ const StyledLabel = styled.span`
|
|||||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledDescription = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
`;
|
||||||
|
|
||||||
export const Select = <Value extends SelectValue>({
|
export const Select = <Value extends SelectValue>({
|
||||||
className,
|
className,
|
||||||
disabled: disabledFromProps,
|
disabled: disabledFromProps,
|
||||||
@ -74,6 +80,7 @@ export const Select = <Value extends SelectValue>({
|
|||||||
emptyOption,
|
emptyOption,
|
||||||
fullWidth,
|
fullWidth,
|
||||||
label,
|
label,
|
||||||
|
description,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
options,
|
options,
|
||||||
@ -214,6 +221,7 @@ export const Select = <Value extends SelectValue>({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!!description && <StyledDescription>{description}</StyledDescription>}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,10 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
import { Workflow, WorkflowVersion } from '@/workflow/types/Workflow';
|
import {
|
||||||
|
ManualTriggerWorkflowVersion,
|
||||||
|
Workflow,
|
||||||
|
} from '@/workflow/types/Workflow';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const useActiveWorkflowVersionsWithManualTrigger = ({
|
export const useActiveWorkflowVersionsWithManualTrigger = ({
|
||||||
@ -40,7 +43,7 @@ export const useActiveWorkflowVersionsWithManualTrigger = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { records } = useFindManyRecords<
|
const { records } = useFindManyRecords<
|
||||||
WorkflowVersion & { workflow: Workflow }
|
ManualTriggerWorkflowVersion & { workflow: Workflow }
|
||||||
>({
|
>({
|
||||||
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
||||||
filter: {
|
filter: {
|
||||||
|
|||||||
@ -114,6 +114,10 @@ export type WorkflowVersion = {
|
|||||||
__typename: 'WorkflowVersion';
|
__typename: 'WorkflowVersion';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ManualTriggerWorkflowVersion = WorkflowVersion & {
|
||||||
|
trigger: WorkflowManualTrigger | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type WorkflowRunOutput = z.infer<typeof workflowRunOutputSchema>;
|
export type WorkflowRunOutput = z.infer<typeof workflowRunOutputSchema>;
|
||||||
export type WorkflowExecutorOutput = z.infer<
|
export type WorkflowExecutorOutput = z.infer<
|
||||||
typeof workflowExecutorOutputSchema
|
typeof workflowExecutorOutputSchema
|
||||||
|
|||||||
@ -206,6 +206,7 @@ export const workflowManualTriggerSchema = baseTriggerSchema.extend({
|
|||||||
settings: z.object({
|
settings: z.object({
|
||||||
objectType: z.string().optional(),
|
objectType: z.string().optional(),
|
||||||
outputSchema: z.object({}).passthrough(),
|
outputSchema: z.object({}).passthrough(),
|
||||||
|
icon: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,10 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { useIcons } from 'twenty-ui/display';
|
import { useIcons } from 'twenty-ui/display';
|
||||||
import { SelectOption } from 'twenty-ui/input';
|
import { SelectOption } from 'twenty-ui/input';
|
||||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||||
|
import { SelectControl } from '@/ui/input/components/SelectControl';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
type WorkflowEditTriggerManualFormProps = {
|
type WorkflowEditTriggerManualFormProps = {
|
||||||
trigger: WorkflowManualTrigger;
|
trigger: WorkflowManualTrigger;
|
||||||
@ -30,11 +34,32 @@ type WorkflowEditTriggerManualFormProps = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const StyledLabel = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
font-size: ${({ theme }) => theme.font.size.xs};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledDescription = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
margin-top: ${({ theme }) => theme.spacing(0.25)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledIconPickerContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
export const WorkflowEditTriggerManualForm = ({
|
export const WorkflowEditTriggerManualForm = ({
|
||||||
trigger,
|
trigger,
|
||||||
triggerOptions,
|
triggerOptions,
|
||||||
}: WorkflowEditTriggerManualFormProps) => {
|
}: WorkflowEditTriggerManualFormProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
const { activeNonSystemObjectMetadataItems } =
|
const { activeNonSystemObjectMetadataItems } =
|
||||||
@ -47,16 +72,22 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
Icon: getIcon(item.icon),
|
Icon: getIcon(item.icon),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const objectType = trigger.settings.objectType;
|
||||||
|
|
||||||
const manualTriggerAvailability: WorkflowManualTriggerAvailability =
|
const manualTriggerAvailability: WorkflowManualTriggerAvailability =
|
||||||
isDefined(trigger.settings.objectType)
|
isDefined(objectType) ? 'WHEN_RECORD_SELECTED' : 'EVERYWHERE';
|
||||||
? 'WHEN_RECORD_SELECTED'
|
|
||||||
: 'EVERYWHERE';
|
|
||||||
|
|
||||||
const headerTitle = trigger.name ?? getTriggerDefaultLabel(trigger);
|
const headerTitle = trigger.name ?? getTriggerDefaultLabel(trigger);
|
||||||
|
|
||||||
const headerIcon = getTriggerIcon(trigger);
|
const headerIcon = getTriggerIcon(trigger);
|
||||||
|
|
||||||
const headerType = getTriggerHeaderType(trigger);
|
const headerType = getTriggerHeaderType(trigger);
|
||||||
|
|
||||||
|
const availabilityDescriptions = {
|
||||||
|
WHEN_RECORD_SELECTED: t`Select a record then open the ⌘K to trigger this workflow`,
|
||||||
|
EVERYWHERE: t`Open the ⌘K to trigger this workflow`,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WorkflowStepHeader
|
<WorkflowStepHeader
|
||||||
@ -78,8 +109,9 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
/>
|
/>
|
||||||
<WorkflowStepBody>
|
<WorkflowStepBody>
|
||||||
<Select
|
<Select
|
||||||
dropdownId="workflow-edit-manual-trigger-availability"
|
dropdownId={'workflow-edit-manual-trigger-availability'}
|
||||||
label="Available"
|
label={t`Available`}
|
||||||
|
description={availabilityDescriptions[manualTriggerAvailability]}
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={triggerOptions.readonly}
|
disabled={triggerOptions.readonly}
|
||||||
value={manualTriggerAvailability}
|
value={manualTriggerAvailability}
|
||||||
@ -94,6 +126,7 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
settings: getManualTriggerDefaultSettings({
|
settings: getManualTriggerDefaultSettings({
|
||||||
availability: updatedTriggerType,
|
availability: updatedTriggerType,
|
||||||
activeNonSystemObjectMetadataItems,
|
activeNonSystemObjectMetadataItems,
|
||||||
|
icon: trigger.settings.icon,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -103,10 +136,11 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
|
|
||||||
{manualTriggerAvailability === 'WHEN_RECORD_SELECTED' ? (
|
{manualTriggerAvailability === 'WHEN_RECORD_SELECTED' ? (
|
||||||
<Select
|
<Select
|
||||||
dropdownId="workflow-edit-manual-trigger-object"
|
dropdownId={'workflow-edit-manual-trigger-object'}
|
||||||
label="Object"
|
label={t`Object`}
|
||||||
|
description={t`Will return one ${objectType} to the next step of this workflow`}
|
||||||
fullWidth
|
fullWidth
|
||||||
value={trigger.settings.objectType}
|
value={objectType}
|
||||||
options={availableMetadata}
|
options={availableMetadata}
|
||||||
disabled={triggerOptions.readonly}
|
disabled={triggerOptions.readonly}
|
||||||
onChange={(updatedObject) => {
|
onChange={(updatedObject) => {
|
||||||
@ -117,6 +151,7 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
triggerOptions.onTriggerUpdate({
|
triggerOptions.onTriggerUpdate({
|
||||||
...trigger,
|
...trigger,
|
||||||
settings: {
|
settings: {
|
||||||
|
...trigger.settings,
|
||||||
objectType: updatedObject,
|
objectType: updatedObject,
|
||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
},
|
},
|
||||||
@ -126,6 +161,48 @@ export const WorkflowEditTriggerManualForm = ({
|
|||||||
dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
|
dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<IconPicker
|
||||||
|
dropdownId={'workflow-edit-manual-trigger-icon'}
|
||||||
|
selectedIconKey={trigger.settings.icon}
|
||||||
|
dropdownOffset={{ y: -parseInt(theme.spacing(3), 10) }}
|
||||||
|
dropdownWidth={GenericDropdownContentWidth.ExtraLarge}
|
||||||
|
maxIconsVisible={9 * 8} // 9 columns * 8 lines
|
||||||
|
disabled={triggerOptions.readonly}
|
||||||
|
clickableComponent={
|
||||||
|
<StyledIconPickerContainer
|
||||||
|
onClick={(e) => {
|
||||||
|
if (triggerOptions.readonly === true) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledLabel>{t`Command menu icon`}</StyledLabel>
|
||||||
|
<SelectControl
|
||||||
|
isDisabled={triggerOptions.readonly}
|
||||||
|
selectedOption={{
|
||||||
|
Icon: getIcon(trigger.settings.icon),
|
||||||
|
value: trigger.settings.icon || null,
|
||||||
|
label: '',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<StyledDescription>{t`The icon your workflow trigger will display in the command menu`}</StyledDescription>
|
||||||
|
</StyledIconPickerContainer>
|
||||||
|
}
|
||||||
|
onChange={({ iconKey }) => {
|
||||||
|
if (triggerOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerOptions.onTriggerUpdate({
|
||||||
|
...trigger,
|
||||||
|
settings: {
|
||||||
|
...trigger.settings,
|
||||||
|
icon: iconKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</WorkflowStepBody>
|
</WorkflowStepBody>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export const COMMAND_MENU_DEFAULT_ICON = 'IconHandMove';
|
||||||
@ -7,12 +7,12 @@ export const MANUAL_TRIGGER_AVAILABILITY_OPTIONS: Array<{
|
|||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
}> = [
|
}> = [
|
||||||
{
|
{
|
||||||
label: 'When record(s) are selected',
|
label: 'When record is selected',
|
||||||
value: 'WHEN_RECORD_SELECTED',
|
value: 'WHEN_RECORD_SELECTED',
|
||||||
Icon: IconCheckbox,
|
Icon: IconCheckbox,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'When no record(s) are selected',
|
label: 'When no record is selected',
|
||||||
value: 'EVERYWHERE',
|
value: 'EVERYWHERE',
|
||||||
Icon: IconSquare,
|
Icon: IconSquare,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
import { getManualTriggerDefaultSettings } from '../getManualTriggerDefaultSettings';
|
import { getManualTriggerDefaultSettings } from '../getManualTriggerDefaultSettings';
|
||||||
|
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||||
|
|
||||||
it('returns settings for a manual trigger that can be activated from any where', () => {
|
it('returns settings for a manual trigger that can be activated from any where', () => {
|
||||||
expect(
|
expect(
|
||||||
@ -10,6 +11,7 @@ it('returns settings for a manual trigger that can be activated from any where',
|
|||||||
).toStrictEqual({
|
).toStrictEqual({
|
||||||
objectType: undefined,
|
objectType: undefined,
|
||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
|
icon: COMMAND_MENU_DEFAULT_ICON,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -18,9 +20,11 @@ it('returns settings for a manual trigger that can be activated from any where',
|
|||||||
getManualTriggerDefaultSettings({
|
getManualTriggerDefaultSettings({
|
||||||
availability: 'WHEN_RECORD_SELECTED',
|
availability: 'WHEN_RECORD_SELECTED',
|
||||||
activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems,
|
activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
|
icon: 'IconTest',
|
||||||
}),
|
}),
|
||||||
).toStrictEqual({
|
).toStrictEqual({
|
||||||
objectType: generatedMockObjectMetadataItems[0].nameSingular,
|
objectType: generatedMockObjectMetadataItems[0].nameSingular,
|
||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
|
icon: 'IconTest',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { DatabaseTriggerDefaultLabel } from '@/workflow/workflow-trigger/constants/DatabaseTriggerDefaultLabel';
|
import { DatabaseTriggerDefaultLabel } from '@/workflow/workflow-trigger/constants/DatabaseTriggerDefaultLabel';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
import { getTriggerDefaultDefinition } from '../getTriggerDefaultDefinition';
|
import { getTriggerDefaultDefinition } from '../getTriggerDefaultDefinition';
|
||||||
|
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||||
|
|
||||||
describe('getTriggerDefaultDefinition', () => {
|
describe('getTriggerDefaultDefinition', () => {
|
||||||
it('throws if the activeNonSystemObjectMetadataItems list is empty', () => {
|
it('throws if the activeNonSystemObjectMetadataItems list is empty', () => {
|
||||||
@ -94,6 +95,7 @@ describe('getTriggerDefaultDefinition', () => {
|
|||||||
settings: {
|
settings: {
|
||||||
objectType: generatedMockObjectMetadataItems[0].nameSingular,
|
objectType: generatedMockObjectMetadataItems[0].nameSingular,
|
||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
|
icon: COMMAND_MENU_DEFAULT_ICON,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,25 +4,30 @@ import {
|
|||||||
WorkflowManualTriggerSettings,
|
WorkflowManualTriggerSettings,
|
||||||
} from '@/workflow/types/Workflow';
|
} from '@/workflow/types/Workflow';
|
||||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||||
|
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||||
|
|
||||||
export const getManualTriggerDefaultSettings = ({
|
export const getManualTriggerDefaultSettings = ({
|
||||||
availability,
|
availability,
|
||||||
activeNonSystemObjectMetadataItems,
|
activeNonSystemObjectMetadataItems,
|
||||||
|
icon,
|
||||||
}: {
|
}: {
|
||||||
availability: WorkflowManualTriggerAvailability;
|
availability: WorkflowManualTriggerAvailability;
|
||||||
activeNonSystemObjectMetadataItems: ObjectMetadataItem[];
|
activeNonSystemObjectMetadataItems: ObjectMetadataItem[];
|
||||||
|
icon?: string;
|
||||||
}): WorkflowManualTriggerSettings => {
|
}): WorkflowManualTriggerSettings => {
|
||||||
switch (availability) {
|
switch (availability) {
|
||||||
case 'EVERYWHERE': {
|
case 'EVERYWHERE': {
|
||||||
return {
|
return {
|
||||||
objectType: undefined,
|
objectType: undefined,
|
||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
|
icon: icon || COMMAND_MENU_DEFAULT_ICON,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'WHEN_RECORD_SELECTED': {
|
case 'WHEN_RECORD_SELECTED': {
|
||||||
return {
|
return {
|
||||||
objectType: activeNonSystemObjectMetadataItems[0].nameSingular,
|
objectType: activeNonSystemObjectMetadataItems[0].nameSingular,
|
||||||
outputSchema: {},
|
outputSchema: {},
|
||||||
|
icon: icon || COMMAND_MENU_DEFAULT_ICON,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export const prefillWorkflows = async (
|
|||||||
trigger: JSON.stringify({
|
trigger: JSON.stringify({
|
||||||
name: 'Launch manually',
|
name: 'Launch manually',
|
||||||
type: 'MANUAL',
|
type: 'MANUAL',
|
||||||
settings: { outputSchema: {} },
|
settings: { outputSchema: {}, icon: 'IconUserPlus' },
|
||||||
}),
|
}),
|
||||||
steps: JSON.stringify([
|
steps: JSON.stringify([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,7 +20,7 @@ type BaseTrigger = {
|
|||||||
|
|
||||||
export type WorkflowDatabaseEventTrigger = BaseTrigger & {
|
export type WorkflowDatabaseEventTrigger = BaseTrigger & {
|
||||||
type: WorkflowTriggerType.DATABASE_EVENT;
|
type: WorkflowTriggerType.DATABASE_EVENT;
|
||||||
settings: {
|
settings: BaseWorkflowTriggerSettings & {
|
||||||
eventName: string;
|
eventName: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -32,45 +32,49 @@ export enum WorkflowManualTriggerAvailability {
|
|||||||
|
|
||||||
export type WorkflowManualTrigger = BaseTrigger & {
|
export type WorkflowManualTrigger = BaseTrigger & {
|
||||||
type: WorkflowTriggerType.MANUAL;
|
type: WorkflowTriggerType.MANUAL;
|
||||||
settings: {
|
settings: BaseWorkflowTriggerSettings & {
|
||||||
objectType?: string;
|
objectType?: string;
|
||||||
|
icon?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowCronTrigger = BaseTrigger & {
|
export type WorkflowCronTrigger = BaseTrigger & {
|
||||||
type: WorkflowTriggerType.CRON;
|
type: WorkflowTriggerType.CRON;
|
||||||
settings: (
|
settings: BaseWorkflowTriggerSettings &
|
||||||
| {
|
(
|
||||||
type: 'DAYS';
|
| {
|
||||||
schedule: { day: number; hour: number; minute: number };
|
type: 'DAYS';
|
||||||
}
|
schedule: { day: number; hour: number; minute: number };
|
||||||
| {
|
}
|
||||||
type: 'HOURS';
|
| {
|
||||||
schedule: { hour: number; minute: number };
|
type: 'HOURS';
|
||||||
}
|
schedule: { hour: number; minute: number };
|
||||||
| {
|
}
|
||||||
type: 'MINUTES';
|
| {
|
||||||
schedule: { minute: number };
|
type: 'MINUTES';
|
||||||
}
|
schedule: { minute: number };
|
||||||
| {
|
}
|
||||||
type: 'CUSTOM';
|
| {
|
||||||
pattern: string;
|
type: 'CUSTOM';
|
||||||
}
|
pattern: string;
|
||||||
) & { outputSchema: object };
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowWebhookTrigger = BaseTrigger & {
|
export type WorkflowWebhookTrigger = BaseTrigger & {
|
||||||
type: WorkflowTriggerType.WEBHOOK;
|
type: WorkflowTriggerType.WEBHOOK;
|
||||||
settings:
|
settings: BaseWorkflowTriggerSettings &
|
||||||
| {
|
(
|
||||||
httpMethod: 'GET';
|
| {
|
||||||
authentication: 'API_KEY' | null;
|
httpMethod: 'GET';
|
||||||
}
|
authentication: 'API_KEY' | null;
|
||||||
| ({
|
}
|
||||||
httpMethod: 'POST';
|
| {
|
||||||
authentication: 'API_KEY' | null;
|
httpMethod: 'POST';
|
||||||
expectedBody: object;
|
authentication: 'API_KEY' | null;
|
||||||
} & { outputSchema: object });
|
expectedBody: object;
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings'];
|
export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings'];
|
||||||
|
|||||||
@ -11,12 +11,10 @@ export const useIcons = () => {
|
|||||||
return icons;
|
return icons;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIcon = (iconKey?: string | null) => {
|
const getIcon = (iconKey?: string | null, customDefaultIcon?: string) => {
|
||||||
if (!iconKey) {
|
return (
|
||||||
return defaultIcon;
|
icons[iconKey ?? ''] || icons[customDefaultIcon ?? ''] || defaultIcon
|
||||||
}
|
);
|
||||||
|
|
||||||
return icons[iconKey] ?? defaultIcon;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { getIcons, getIcon };
|
return { getIcons, getIcon };
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { FunctionComponent } from 'react';
|
import { FunctionComponent } from 'react';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
export type IconComponentProps = {
|
export type IconComponentProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
size?: number | string;
|
size?: number | string;
|
||||||
stroke?: number | string;
|
stroke?: number | string;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user