From 455ce191e47bc71de13d15c23a411f259419b167 Mon Sep 17 00:00:00 2001 From: ahmedobaid23 <111188459+ahmedobaid23@users.noreply.github.com> Date: Thu, 14 Aug 2025 14:03:35 +0500 Subject: [PATCH] feat: ability to pin a manual trigger in the navbar (#13904) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #13333 This PR adds the ability to pin a manual trigger in the navbar. As suggested, the following changes have been made: **Changes Made** - **Schema updates** - Updated the `workflowSchema.ts` and added `isPinned` to the `workflowManualTriggerSchema` as an optional boolean. - **Defaults** - Updated the `getManualTriggerDefaultSettings.ts` to add `isPinned` to the `WorkflowManualTriggerSettings`, defaulting to false. - **UI updates** - Updated the `ManualTriggerAvailabilityOptions.ts` to improve the Availability labels as suggested in the figma files. - Created a new file `ManualTriggerIsPinnedOptions.ts` that contains the options (label, value and icon) for the `isPinned` select component. - Updated the `WorkflowEditTriggerManualForm.tsx` to incorporate the added `isPinned` select component. - **Action handling** - Modified `useRunWorkflowRecordActions.tsx` to add `isPinned: activeWorkflowVersion.trigger?.settings?.isPinned` to the returned action config. --------- Co-authored-by: Félix Malfait Co-authored-by: Félix Malfait Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: bosiraphael --- .../hooks/useRunWorkflowRecordActions.tsx | 2 + .../contexts/ActionMenuContextProvider.tsx | 22 +-- ...MenuContextProviderWorkflowsNotEnabled.tsx | 40 ------ .../validation-schemas/workflowSchema.ts | 1 + .../WorkflowEditTriggerManualForm.tsx | 130 +++++++++++------- .../ManualTriggerAvailabilityOptions.ts | 4 +- .../constants/ManualTriggerIsPinnedOptions.ts | 22 +++ .../getManualTriggerDefaultSettings.test.ts | 3 + .../getTriggerDefaultDefinition.test.ts | 1 + .../utils/getManualTriggerDefaultSettings.ts | 4 + .../display/icon/components/TablerIcons.ts | 2 + packages/twenty-ui/src/display/index.ts | 2 + 12 files changed, 123 insertions(+), 110 deletions(-) delete mode 100644 packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProviderWorkflowsNotEnabled.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerIsPinnedOptions.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx index fdd73cad16..71b35ebb19 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx @@ -82,8 +82,10 @@ export const useRunWorkflowRecordActions = ({ key: `workflow-run-${activeWorkflowVersion.id}`, scope: ActionScope.RecordSelection, label: msg`${name}`, + shortLabel: msg`${name}`, position: index, Icon, + isPinned: activeWorkflowVersion.trigger?.settings?.isPinned, shouldBeRegistered: () => true, component: ( - {children} - - ); + if (!isDefined(objectMetadataItem)) { + return null; } return ( - {children} - + ); }; diff --git a/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProviderWorkflowsNotEnabled.tsx b/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProviderWorkflowsNotEnabled.tsx deleted file mode 100644 index 6738d4a4b1..0000000000 --- a/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProviderWorkflowsNotEnabled.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - ActionMenuContext, - type ActionMenuContextType, -} from '@/action-menu/contexts/ActionMenuContext'; -import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions'; -import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const ActionMenuContextProviderWorkflowsNotEnabled = ({ - objectMetadataItem, - isInRightDrawer, - displayType, - actionMenuType, - children, -}: { - objectMetadataItem?: ObjectMetadataItem; - isInRightDrawer: ActionMenuContextType['isInRightDrawer']; - displayType: ActionMenuContextType['displayType']; - actionMenuType: ActionMenuContextType['actionMenuType']; - children: React.ReactNode; -}) => { - const params = useShouldActionBeRegisteredParams({ - objectMetadataItem, - }); - - const actions = useRegisteredActions(params); - - return ( - - {children} - - ); -}; diff --git a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts index 070532713a..6aa4838e7c 100644 --- a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts +++ b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts @@ -240,6 +240,7 @@ export const workflowManualTriggerSchema = baseTriggerSchema.extend({ objectType: z.string().optional(), outputSchema: z.object({}).passthrough(), icon: z.string().optional(), + isPinned: z.boolean().optional(), }), }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx index 7944214308..698fc867cc 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm.tsx @@ -1,5 +1,8 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { IconPicker } from '@/ui/input/components/IconPicker'; import { Select } from '@/ui/input/components/Select'; +import { SelectControl } from '@/ui/input/components/SelectControl'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { type WorkflowManualTrigger, type WorkflowManualTriggerAvailability, @@ -7,19 +10,17 @@ import { import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { MANUAL_TRIGGER_AVAILABILITY_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions'; +import { MANUAL_TRIGGER_IS_PINNED_OPTIONS } from '@/workflow/workflow-trigger/constants/ManualTriggerIsPinnedOptions'; import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings'; +import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel'; import { getTriggerHeaderType } from '@/workflow/workflow-trigger/utils/getTriggerHeaderType'; import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon'; -import { getTriggerDefaultLabel } from '@/workflow/workflow-trigger/utils/getTriggerDefaultLabel'; import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useLingui } from '@lingui/react/macro'; import { isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; -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 = { trigger: WorkflowManualTrigger; @@ -84,7 +85,7 @@ export const WorkflowEditTriggerManualForm = ({ const headerType = getTriggerHeaderType(trigger); const availabilityDescriptions = { - WHEN_RECORD_SELECTED: t`Select a record then open the ⌘K to trigger this workflow`, + WHEN_RECORD_SELECTED: t`The selected record(s) will be passed to your workflow`, EVERYWHERE: t`Open the ⌘K to trigger this workflow`, }; @@ -108,9 +109,52 @@ export const WorkflowEditTriggerManualForm = ({ disabled={triggerOptions.readonly} /> + { + if (triggerOptions.readonly === true) { + e.stopPropagation(); + e.preventDefault(); + } + }} + > + {t`Command Icon`} + + {t`The icon your workflow trigger will display in the command menu`} + + } + onChange={({ iconKey }) => { + if (triggerOptions.readonly === true) { + return; + } + + triggerOptions.onTriggerUpdate({ + ...trigger, + settings: { + ...trigger.settings, + icon: iconKey, + }, + }); + }} + /> + { + if (triggerOptions.readonly === true) { + return; + } + + triggerOptions.onTriggerUpdate({ + ...trigger, + settings: { + ...trigger.settings, + isPinned: updatedValue, + outputSchema: {}, + }, + }); + }} + dropdownOffset={{ y: parseInt(theme.spacing(1), 10) }} + dropdownWidth={GenericDropdownContentWidth.ExtraLarge} + /> + ) : null} ); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions.ts index 8b00731470..6ba6cb5628 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerAvailabilityOptions.ts @@ -11,12 +11,12 @@ export const MANUAL_TRIGGER_AVAILABILITY_OPTIONS: Array<{ Icon: IconComponent; }> = [ { - label: 'When record is selected', + label: 'When records are selected', value: 'WHEN_RECORD_SELECTED', Icon: IconCheckbox, }, { - label: 'When no record is selected', + label: 'When no records are selected', value: 'EVERYWHERE', Icon: IconSquare, }, diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerIsPinnedOptions.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerIsPinnedOptions.ts new file mode 100644 index 0000000000..50e6248fcf --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/constants/ManualTriggerIsPinnedOptions.ts @@ -0,0 +1,22 @@ +import { + type IconComponent, + IconPinned, + IconPinnedOff, +} from 'twenty-ui/display'; + +export const MANUAL_TRIGGER_IS_PINNED_OPTIONS: Array<{ + label: string; + value: boolean; + Icon: IconComponent; +}> = [ + { + label: 'Not Pinned', + value: false, + Icon: IconPinnedOff, + }, + { + label: 'Pinned', + value: true, + Icon: IconPinned, + }, +]; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettings.test.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettings.test.ts index e23500ce16..34f4c35267 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettings.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettings.test.ts @@ -13,6 +13,7 @@ it('returns settings for a manual trigger that can be activated from any where', objectType: undefined, outputSchema: {}, icon: COMMAND_MENU_DEFAULT_ICON, + isPinned: false, }); }); @@ -27,6 +28,7 @@ it('returns settings for a manual trigger that can be activated from any where', objectType: generatedMockObjectMetadataItems[0].nameSingular, outputSchema: {}, icon: 'IconTest', + isPinned: false, }); }); @@ -40,6 +42,7 @@ it('returns settings for WHEN_RECORD_SELECTED with default icon when no custom i objectType: generatedMockObjectMetadataItems[0].nameSingular, outputSchema: {}, icon: COMMAND_MENU_DEFAULT_ICON, + isPinned: false, }); }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts index 9b2e2a6580..53520ed1b1 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts @@ -116,6 +116,7 @@ describe('getTriggerDefaultDefinition', () => { objectType: generatedMockObjectMetadataItems[0].nameSingular, outputSchema: {}, icon: COMMAND_MENU_DEFAULT_ICON, + isPinned: false, }, nextStepIds: [], position: { diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts index 32e53f79ae..7a10f1ab3d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts @@ -10,10 +10,12 @@ export const getManualTriggerDefaultSettings = ({ availability, activeNonSystemObjectMetadataItems, icon, + isPinned, }: { availability: WorkflowManualTriggerAvailability; activeNonSystemObjectMetadataItems: ObjectMetadataItem[]; icon?: string; + isPinned?: boolean; }): WorkflowManualTriggerSettings => { switch (availability) { case 'EVERYWHERE': { @@ -21,6 +23,7 @@ export const getManualTriggerDefaultSettings = ({ objectType: undefined, outputSchema: {}, icon: icon || COMMAND_MENU_DEFAULT_ICON, + isPinned: isPinned || false, }; } case 'WHEN_RECORD_SELECTED': { @@ -28,6 +31,7 @@ export const getManualTriggerDefaultSettings = ({ objectType: activeNonSystemObjectMetadataItems[0].nameSingular, outputSchema: {}, icon: icon || COMMAND_MENU_DEFAULT_ICON, + isPinned: isPinned || false, }; } } diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 581b4869f9..78734af118 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -327,6 +327,8 @@ export { IconWebhook, IconWorld, IconX, + IconPinned, + IconPinnedOff, } from '@tabler/icons-react'; export type { IconProps as TablerIconsProps } from '@tabler/icons-react'; diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts index e95bb6f6ff..bd550e50a9 100644 --- a/packages/twenty-ui/src/display/index.ts +++ b/packages/twenty-ui/src/display/index.ts @@ -389,6 +389,8 @@ export { IconWebhook, IconWorld, IconX, + IconPinned, + IconPinnedOff, } from './icon/components/TablerIcons'; export { useIcons } from './icon/hooks/useIcons'; export { IconsProvider } from './icon/providers/IconsProvider';