From 521ef5ea4c7f5ab52ec205ed04d4baf210d22412 Mon Sep 17 00:00:00 2001 From: P Harshith Rao Date: Wed, 20 Aug 2025 15:01:03 +0530 Subject: [PATCH] fix: workflow node classification (#13945) closes #13391 As the solution said to classify workflow nodes based on colors I have updated them with the following colors and new categories to better classify and identify different node types as told: - Trigger Colors: Data (palette blue), Other (palette purple) - Action Colors: Data (text/tertiary), AI (palette pink), Flow (tag green), Human Input (palette orange) image image image image --------- Co-authored-by: Thomas Trompette --- ...CommandMenuWorkflowSelectActionContent.tsx | 51 ++++---- .../components/WorkflowActionMenuItems.tsx | 32 +++++ ...ndMenuWorkflowSelectTriggerTypeContent.tsx | 42 ++++--- .../WorkflowDiagramStepNodeIcon.tsx | 23 ++-- .../WorkflowRunStepOutputDetail.tsx | 5 +- .../workflow-actions/constants/AiActions.ts | 13 ++ .../workflow-actions/constants/CoreActions.ts | 23 ++++ .../constants/HumanInputActions.ts | 13 ++ .../__tests__/useFilteredOtherActions.test.ts | 111 ------------------ .../hooks/useFilteredOtherActions.ts | 11 -- .../utils/__tests__/getActionIcon.test.ts | 18 ++- .../getActionIconColorOrThrow.test.ts | 57 +++++---- .../workflow-actions/utils/getActionIcon.ts | 14 ++- .../utils/getActionIconColorOrThrow.ts | 7 +- .../WorkflowEditTriggerCronForm.tsx | 3 +- .../WorkflowEditTriggerDatabaseEventForm.tsx | 3 +- .../WorkflowEditTriggerManualForm.tsx | 3 +- .../WorkflowEditTriggerWebhookForm.tsx | 3 +- .../__tests__/getTriggerIconColor.test.ts | 66 ++++++----- .../utils/getTriggerIconColor.ts | 20 +++- 20 files changed, 279 insertions(+), 239 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/WorkflowActionMenuItems.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/AiActions.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/HumanInputActions.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/__tests__/useFilteredOtherActions.test.ts delete mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/useFilteredOtherActions.ts diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx index a51f89997ac..b355721b73f 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent.tsx @@ -1,4 +1,5 @@ import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu'; +import { WorkflowActionMenuItems } from '@/command-menu/pages/workflow/action/components/WorkflowActionMenuItems'; import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -12,13 +13,18 @@ import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/componen import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle'; import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep'; import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState'; +import { AI_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/AiActions'; +import { CORE_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/CoreActions'; +import { HUMAN_INPUT_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/HumanInputActions'; import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions'; -import { useFilteredOtherActions } from '@/workflow/workflow-steps/workflow-actions/hooks/useFilteredOtherActions'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useTheme } from '@emotion/react'; +import { useLingui } from '@lingui/react/macro'; import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; -import { MenuItemCommand } from 'twenty-ui/navigation'; +import { FeatureFlagKey } from '~/generated/graphql'; export const CommandMenuWorkflowSelectActionContent = ({ workflow, @@ -30,7 +36,6 @@ export const CommandMenuWorkflowSelectActionContent = ({ const { createStep } = useCreateStep({ workflow, }); - const filteredOtherActions = useFilteredOtherActions(); const { closeRightClickMenu } = useCloseRightClickMenu(); @@ -83,30 +88,34 @@ export const CommandMenuWorkflowSelectActionContent = ({ ); }; + const theme = useTheme(); + + const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED); + + const { t } = useLingui(); + return ( - Records + {t`Data`} - {RECORD_ACTIONS.map((action) => ( - handleCreateStep(action.type)} - /> - ))} + {WorkflowActionMenuItems(RECORD_ACTIONS, theme, handleCreateStep)} + {isAiEnabled && ( + <> + + {t`AI`} + + {WorkflowActionMenuItems(AI_ACTIONS, theme, handleCreateStep)} + + )} - Other + {t`Core`} - {filteredOtherActions.map((action) => ( - handleCreateStep(action.type)} - /> - ))} + {WorkflowActionMenuItems(CORE_ACTIONS, theme, handleCreateStep)} + + {t`Human Input`} + + {WorkflowActionMenuItems(HUMAN_INPUT_ACTIONS, theme, handleCreateStep)} ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/WorkflowActionMenuItems.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/WorkflowActionMenuItems.tsx new file mode 100644 index 00000000000..ea006188f8a --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/action/components/WorkflowActionMenuItems.tsx @@ -0,0 +1,32 @@ +import { type WorkflowActionType } from '@/workflow/types/Workflow'; +import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow'; +import { useIcons } from 'twenty-ui/display'; +import { MenuItemCommand } from 'twenty-ui/navigation'; + +type Action = { type: WorkflowActionType; label: string; icon: string }; + +export const WorkflowActionMenuItems = ( + actions: Action[], + theme: any, + onClick: (actionType: WorkflowActionType) => void, +) => { + const { getIcon } = useIcons(); + return actions.map((action) => { + const Icon = getIcon(action.icon); + return ( + ( + + )} + text={action.label} + onClick={() => onClick(action.type)} + /> + ); + }); +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx index 7c405bfb597..182d9657bba 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent.tsx @@ -13,6 +13,7 @@ import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/Other import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useTheme } from '@emotion/react'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; import { useIcons } from 'twenty-ui/display'; import { MenuItemCommand } from 'twenty-ui/navigation'; @@ -67,30 +68,39 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({ }; }; + const theme = useTheme(); + return ( Data - {DATABASE_TRIGGER_TYPES.map((action) => ( - - ))} + {DATABASE_TRIGGER_TYPES.map((action) => { + const Icon = getIcon(action.icon); + return ( + } + text={action.defaultLabel} + onClick={handleTriggerTypeClick(action)} + /> + ); + })} + Others - {OTHER_TRIGGER_TYPES.map((action) => ( - - ))} + {OTHER_TRIGGER_TYPES.map((action) => { + const Icon = getIcon(action.icon); + return ( + } + text={action.defaultLabel} + onClick={handleTriggerTypeClick(action)} + /> + ); + })} ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx index e59ce72c135..11314a43136 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon.tsx @@ -16,13 +16,13 @@ export const WorkflowDiagramStepNodeIcon = ({ switch (data.nodeType) { case 'trigger': { switch (data.triggerType) { - case 'DATABASE_EVENT': + case 'DATABASE_EVENT': { + return ; + } case 'MANUAL': case 'CRON': case 'WEBHOOK': { - return ( - - ); + return ; } } @@ -31,25 +31,26 @@ export const WorkflowDiagramStepNodeIcon = ({ case 'action': { switch (data.actionType) { case 'CODE': - case 'HTTP_REQUEST': { + case 'HTTP_REQUEST': + case 'SEND_EMAIL': { return ( ); } - case 'SEND_EMAIL': { - return ; + case 'FORM': { + return ; } case 'AI_AGENT': { - return ; + return ; } default: { return ( diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx index ccc7f4c970a..b1d555accab 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowRunStepOutputDetail.tsx @@ -56,7 +56,10 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => { : getActionIcon(stepDefinition.definition.type); const headerIconColor = stepDefinition.type === 'trigger' - ? getTriggerIconColor({ theme }) + ? getTriggerIconColor({ + theme, + triggerType: stepDefinition.definition.type, + }) : getActionIconColorOrThrow({ theme, actionType: stepDefinition.definition.type, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/AiActions.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/AiActions.ts new file mode 100644 index 00000000000..537c66fee67 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/AiActions.ts @@ -0,0 +1,13 @@ +import { type WorkflowActionType } from '@/workflow/types/Workflow'; + +export const AI_ACTIONS: Array<{ + label: string; + type: Extract; + icon: string; +}> = [ + { + label: 'AI Agent', + type: 'AI_AGENT', + icon: 'IconBrain', + }, +]; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts new file mode 100644 index 00000000000..e7179f89e17 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/CoreActions.ts @@ -0,0 +1,23 @@ +import { type WorkflowActionType } from '@/workflow/types/Workflow'; + +export const CORE_ACTIONS: Array<{ + label: string; + type: Extract; + icon: string; +}> = [ + { + label: 'Send Email', + type: 'SEND_EMAIL', + icon: 'IconSend', + }, + { + label: 'Code', + type: 'CODE', + icon: 'IconCode', + }, + { + label: 'HTTP Request', + type: 'HTTP_REQUEST', + icon: 'IconWorld', + }, +]; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/HumanInputActions.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/HumanInputActions.ts new file mode 100644 index 00000000000..ca7cc7722ed --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/constants/HumanInputActions.ts @@ -0,0 +1,13 @@ +import { type WorkflowActionType } from '@/workflow/types/Workflow'; + +export const HUMAN_INPUT_ACTIONS: Array<{ + label: string; + type: Extract; + icon: string; +}> = [ + { + label: 'Form', + type: 'FORM', + icon: 'IconForms', + }, +]; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/__tests__/useFilteredOtherActions.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/__tests__/useFilteredOtherActions.test.ts deleted file mode 100644 index e70d284ee15..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/__tests__/useFilteredOtherActions.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { useFilteredOtherActions } from '../useFilteredOtherActions'; - -jest.mock('@/workspace/hooks/useIsFeatureEnabled', () => ({ - useIsFeatureEnabled: jest.fn(), -})); - -jest.mock('../../constants/OtherActions', () => ({ - OTHER_ACTIONS: [ - { type: 'CODE', icon: 'IconCode', label: 'Code' }, - { type: 'HTTP_REQUEST', icon: 'IconHttp', label: 'HTTP Request' }, - { type: 'SEND_EMAIL', icon: 'IconMail', label: 'Send Email' }, - { type: 'AI_AGENT', icon: 'IconBrain', label: 'AI Agent' }, - { type: 'FORM', icon: 'IconForm', label: 'Form' }, - ], -})); - -describe('useFilteredOtherActions', () => { - const mockUseIsFeatureEnabled = jest.mocked( - jest.requireMock('@/workspace/hooks/useIsFeatureEnabled') - .useIsFeatureEnabled, - ); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return all actions when AI is enabled', () => { - mockUseIsFeatureEnabled.mockReturnValue(true); - - const { result } = renderHook(() => useFilteredOtherActions()); - - expect(result.current).toHaveLength(5); - expect(result.current).toEqual([ - { type: 'CODE', icon: 'IconCode', label: 'Code' }, - { type: 'HTTP_REQUEST', icon: 'IconHttp', label: 'HTTP Request' }, - { type: 'SEND_EMAIL', icon: 'IconMail', label: 'Send Email' }, - { type: 'AI_AGENT', icon: 'IconBrain', label: 'AI Agent' }, - { type: 'FORM', icon: 'IconForm', label: 'Form' }, - ]); - }); - - it('should filter out AI_AGENT when AI is disabled', () => { - mockUseIsFeatureEnabled.mockReturnValue(false); - - const { result } = renderHook(() => useFilteredOtherActions()); - - expect(result.current).toHaveLength(4); - expect(result.current).toEqual([ - { type: 'CODE', icon: 'IconCode', label: 'Code' }, - { type: 'HTTP_REQUEST', icon: 'IconHttp', label: 'HTTP Request' }, - { type: 'SEND_EMAIL', icon: 'IconMail', label: 'Send Email' }, - { type: 'FORM', icon: 'IconForm', label: 'Form' }, - ]); - expect( - result.current.find((action) => action.type === 'AI_AGENT'), - ).toBeUndefined(); - }); - - it('should call useIsFeatureEnabled with correct feature flag', () => { - mockUseIsFeatureEnabled.mockReturnValue(true); - - renderHook(() => useFilteredOtherActions()); - - expect(mockUseIsFeatureEnabled).toHaveBeenCalledWith('IS_AI_ENABLED'); - expect(mockUseIsFeatureEnabled).toHaveBeenCalledTimes(1); - }); - - it('should handle feature flag hook returning undefined', () => { - mockUseIsFeatureEnabled.mockReturnValue(undefined); - - const { result } = renderHook(() => useFilteredOtherActions()); - - expect(result.current).toHaveLength(4); - expect( - result.current.find((action) => action.type === 'AI_AGENT'), - ).toBeUndefined(); - }); - - it('should handle feature flag hook returning null', () => { - mockUseIsFeatureEnabled.mockReturnValue(null); - - const { result } = renderHook(() => useFilteredOtherActions()); - - expect(result.current).toHaveLength(4); - expect( - result.current.find((action) => action.type === 'AI_AGENT'), - ).toBeUndefined(); - }); - - it('should handle feature flag hook returning false string', () => { - mockUseIsFeatureEnabled.mockReturnValue('false'); - - const { result } = renderHook(() => useFilteredOtherActions()); - - expect(result.current).toHaveLength(5); - expect( - result.current.find((action) => action.type === 'AI_AGENT'), - ).toBeDefined(); - }); - - it('should handle feature flag hook throwing error', () => { - mockUseIsFeatureEnabled.mockImplementation(() => { - throw new Error('Feature flag error'); - }); - - expect(() => { - renderHook(() => useFilteredOtherActions()); - }).toThrow('Feature flag error'); - }); -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/useFilteredOtherActions.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/useFilteredOtherActions.ts deleted file mode 100644 index 347507f1e63..00000000000 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/hooks/useFilteredOtherActions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { FeatureFlagKey } from '~/generated/graphql'; -import { OTHER_ACTIONS } from '../constants/OtherActions'; - -export const useFilteredOtherActions = () => { - const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED); - - return OTHER_ACTIONS.filter((action) => { - return action.type !== 'AI_AGENT' || isAiEnabled; - }); -}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIcon.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIcon.test.ts index 7794a03ba0e..e087a5daf8f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIcon.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIcon.test.ts @@ -1,4 +1,6 @@ -import { OTHER_ACTIONS } from '../../constants/OtherActions'; +import { AI_ACTIONS } from '../../constants/AiActions'; +import { CORE_ACTIONS } from '../../constants/CoreActions'; +import { HUMAN_INPUT_ACTIONS } from '../../constants/HumanInputActions'; import { RECORD_ACTIONS } from '../../constants/RecordActions'; import { getActionIcon } from '../getActionIcon'; @@ -8,13 +10,21 @@ describe('getActionIcon', () => { expect(getActionIcon(action.type)).toBe(action.icon); }); - OTHER_ACTIONS.forEach((action) => { + AI_ACTIONS.forEach((action) => { + expect(getActionIcon(action.type)).toBe(action.icon); + }); + + CORE_ACTIONS.forEach((action) => { + expect(getActionIcon(action.type)).toBe(action.icon); + }); + + HUMAN_INPUT_ACTIONS.forEach((action) => { expect(getActionIcon(action.type)).toBe(action.icon); }); }); - it('should return undefined for unknown action type', () => { + it('should return IconDefault for unknown action type', () => { // @ts-expect-error Testing invalid action type - expect(getActionIcon('UNKNOWN_ACTION')).toBeUndefined(); + expect(getActionIcon('UNKNOWN_ACTION')).toBe('IconDefault'); }); }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts index 38e8a60976b..5b1bcd617a3 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/__tests__/getActionIconColorOrThrow.test.ts @@ -6,8 +6,8 @@ import { getActionIconColorOrThrow } from '../getActionIconColorOrThrow'; const mockTheme: Theme = { color: { orange: COLOR.orange, - blue: COLOR.blue, pink: COLOR.pink, + red: COLOR.red, }, font: { color: { @@ -17,10 +17,23 @@ const mockTheme: Theme = { } as Theme; describe('getActionIconColorOrThrow', () => { - it('should return orange color for CODE action type', () => { - expect( - getActionIconColorOrThrow({ theme: mockTheme, actionType: 'CODE' }), - ).toBe(mockTheme.color.orange); + describe('action types that return red color', () => { + const coreActionTypes: WorkflowActionType[] = [ + 'CODE', + 'HTTP_REQUEST', + 'SEND_EMAIL', + ]; + + coreActionTypes.forEach((actionType) => { + it(`should return red color for ${actionType} action type`, () => { + const result = getActionIconColorOrThrow({ + theme: mockTheme, + actionType, + }); + + expect(result).toBe(mockTheme.color.red); + }); + }); }); describe('action types that return tertiary font color', () => { @@ -29,7 +42,6 @@ describe('getActionIconColorOrThrow', () => { 'UPDATE_RECORD', 'DELETE_RECORD', 'FIND_RECORDS', - 'FORM', ]; recordActionTypes.forEach((actionType) => { @@ -44,14 +56,14 @@ describe('getActionIconColorOrThrow', () => { }); }); - describe('action types that return blue color', () => { - it('should return blue color for SEND_EMAIL action type', () => { + describe('action types that return orange color', () => { + it('should return orange color for FORM action type', () => { const result = getActionIconColorOrThrow({ theme: mockTheme, - actionType: 'SEND_EMAIL', + actionType: 'FORM', }); - expect(result).toBe(mockTheme.color.blue); + expect(result).toBe(mockTheme.color.orange); }); }); @@ -81,8 +93,8 @@ describe('getActionIconColorOrThrow', () => { it('should use the provided theme colors correctly', () => { const customTheme: Theme = { color: { - orange: COLOR.red, - blue: COLOR.purple, + red: COLOR.red, + orange: COLOR.orange, pink: COLOR.turquoise, }, font: { @@ -104,7 +116,7 @@ describe('getActionIconColorOrThrow', () => { theme: customTheme, actionType: 'SEND_EMAIL', }), - ).toBe(COLOR.purple); + ).toBe(COLOR.red); expect( getActionIconColorOrThrow({ @@ -158,12 +170,12 @@ describe('getActionIconColorOrThrow', () => { }); expect(result1).toBe(result2); - expect(result1).toBe(mockTheme.color.orange); + expect(result1).toBe(mockTheme.color.red); }); }); describe('color grouping logic', () => { - it('should group CODE and HTTP_REQUEST actions with orange color', () => { + it('should group CODE and HTTP_REQUEST actions with red color', () => { const orangeActions: WorkflowActionType[] = ['CODE', 'HTTP_REQUEST']; orangeActions.forEach((actionType) => { @@ -171,7 +183,7 @@ describe('getActionIconColorOrThrow', () => { theme: mockTheme, actionType, }); - expect(result).toBe(mockTheme.color.orange); + expect(result).toBe(mockTheme.color.red); }); }); @@ -181,7 +193,6 @@ describe('getActionIconColorOrThrow', () => { 'UPDATE_RECORD', 'DELETE_RECORD', 'FIND_RECORDS', - 'FORM', ]; recordActions.forEach((actionType) => { @@ -202,13 +213,13 @@ describe('getActionIconColorOrThrow', () => { expect(tertiaryResult).toBe(mockTheme.font.color.tertiary); }); - it('should return blue color for SEND_EMAIL action type', () => { + it('should return red color for SEND_EMAIL action type', () => { expect( getActionIconColorOrThrow({ theme: mockTheme, actionType: 'SEND_EMAIL', }), - ).toBe(mockTheme.color.blue); + ).toBe(mockTheme.color.red); }); it('should return pink color for AI_AGENT action type', () => { @@ -220,8 +231,8 @@ describe('getActionIconColorOrThrow', () => { it('should use the provided theme colors correctly', () => { const customTheme: Theme = { color: { - orange: COLOR.red, - blue: COLOR.purple, + red: COLOR.red, + orange: COLOR.orange, pink: COLOR.turquoise, }, font: { @@ -239,7 +250,7 @@ describe('getActionIconColorOrThrow', () => { theme: customTheme, actionType: 'SEND_EMAIL', }), - ).toBe(COLOR.purple); + ).toBe(COLOR.red); expect( getActionIconColorOrThrow({ theme: customTheme, @@ -254,7 +265,7 @@ describe('getActionIconColorOrThrow', () => { ).toBe(GRAY_SCALE.gray50); }); - it('should return undefined when blue color is missing for SEND_EMAIL action', () => { + it('should return undefined when red color is missing for SEND_EMAIL action', () => { const themeWithoutBlue: Theme = { color: { orange: COLOR.orange, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts index 23fee0554d7..1da2091338e 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIcon.ts @@ -1,5 +1,7 @@ import { type WorkflowActionType } from '@/workflow/types/Workflow'; -import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions'; +import { AI_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/AiActions'; +import { CORE_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/CoreActions'; +import { HUMAN_INPUT_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/HumanInputActions'; import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions'; export const getActionIcon = (actionType: WorkflowActionType) => { @@ -11,7 +13,15 @@ export const getActionIcon = (actionType: WorkflowActionType) => { return RECORD_ACTIONS.find((item) => item.type === actionType)?.icon; case 'FILTER': return 'IconFilter'; + case 'AI_AGENT': + return AI_ACTIONS.find((item) => item.type === actionType)?.icon; + case 'CODE': + case 'HTTP_REQUEST': + case 'SEND_EMAIL': + return CORE_ACTIONS.find((item) => item.type === actionType)?.icon; + case 'FORM': + return HUMAN_INPUT_ACTIONS.find((item) => item.type === actionType)?.icon; default: - return OTHER_ACTIONS.find((item) => item.type === actionType)?.icon; + return 'IconDefault'; } }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts index c0773c06ce9..9adfb9eff8a 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow.ts @@ -12,16 +12,17 @@ export const getActionIconColorOrThrow = ({ switch (actionType) { case 'CODE': case 'HTTP_REQUEST': - return theme.color.orange; + case 'SEND_EMAIL': + return theme.color.red; case 'CREATE_RECORD': case 'UPDATE_RECORD': case 'DELETE_RECORD': case 'FIND_RECORDS': + return theme.font.color.tertiary; case 'FORM': + return theme.color.orange; case 'FILTER': return theme.font.color.tertiary; - case 'SEND_EMAIL': - return theme.color.blue; case 'AI_AGENT': return theme.color.pink; default: diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx index 075e35c1a34..a75ffaf4023 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx @@ -10,6 +10,7 @@ import { getCronTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils 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 { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor'; import { useTheme } from '@emotion/react'; import { t } from '@lingui/core/macro'; import { isNumber } from '@sniptt/guards'; @@ -75,7 +76,7 @@ export const WorkflowEditTriggerCronForm = ({ }); }} Icon={getIcon(headerIcon)} - iconColor={theme.font.color.tertiary} + iconColor={getTriggerIconColor({ theme, triggerType: trigger.type })} initialTitle={headerTitle} headerType={headerType} disabled={triggerOptions.readonly} diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx index 430b94c6531..6e14786e71c 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm.tsx @@ -18,6 +18,7 @@ import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/Workflo 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 { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { Trans } from '@lingui/react/macro'; @@ -181,7 +182,7 @@ export const WorkflowEditTriggerDatabaseEventForm = ({ }); }} Icon={getIcon(headerIcon)} - iconColor={theme.font.color.tertiary} + iconColor={getTriggerIconColor({ theme, triggerType: trigger.type })} initialTitle={defaultLabel} headerType={headerType} disabled={triggerOptions.readonly} 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 698fc867cc6..7ba332eb32f 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 @@ -15,6 +15,7 @@ import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/uti 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 { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; @@ -103,7 +104,7 @@ export const WorkflowEditTriggerManualForm = ({ }); }} Icon={getIcon(headerIcon)} - iconColor={theme.font.color.tertiary} + iconColor={getTriggerIconColor({ theme, triggerType: trigger.type })} initialTitle={headerTitle} headerType={headerType} disabled={triggerOptions.readonly} diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx index 861094a68d4..512b708f23b 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx @@ -15,6 +15,7 @@ import { WEBHOOK_TRIGGER_HTTP_METHOD_OPTIONS } from '@/workflow/workflow-trigger 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 { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor'; import { getWebhookTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getWebhookTriggerDefaultSettings'; import { useTheme } from '@emotion/react'; import { isNonEmptyString } from '@sniptt/guards'; @@ -95,7 +96,7 @@ export const WorkflowEditTriggerWebhookForm = ({ }); }} Icon={getIcon(headerIcon)} - iconColor={theme.font.color.tertiary} + iconColor={getTriggerIconColor({ theme, triggerType: trigger.type })} initialTitle={headerTitle} headerType={headerType} disabled={triggerOptions.readonly} diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerIconColor.test.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerIconColor.test.ts index 9fe70855624..b9bb791c2a2 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerIconColor.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerIconColor.test.ts @@ -1,56 +1,62 @@ -/* eslint-disable @nx/workspace-no-hardcoded-colors */ import { type Theme } from '@emotion/react'; +import { COLOR } from 'twenty-ui/theme'; import { getTriggerIconColor } from '../getTriggerIconColor'; describe('getTriggerIconColor', () => { const mockTheme: Theme = { - font: { - color: { - primary: '#2c2c2c', - secondary: '#666666', - tertiary: '#999999', - light: '#cccccc', - }, + color: { + blue: COLOR.blue, + purple: COLOR.purple, }, } as unknown as Theme; - it('returns the tertiary font color from theme', () => { - const result = getTriggerIconColor({ theme: mockTheme }); + it('returns the blue color for database event from theme', () => { + const result = getTriggerIconColor({ + theme: mockTheme, + triggerType: 'DATABASE_EVENT', + }); - expect(result).toBe('#999999'); + expect(result).toBe(COLOR.blue); + }); + + it('returns the purple color for cron from theme', () => { + const result = getTriggerIconColor({ + theme: mockTheme, + triggerType: 'CRON', + }); + + expect(result).toBe(COLOR.purple); }); it('works with different theme configurations', () => { const differentTheme: Theme = { - font: { - color: { - primary: '#000000', - secondary: '#444444', - tertiary: '#888888', - light: '#ffffff', - }, + color: { + blue: COLOR.blue, + purple: COLOR.purple, }, } as unknown as Theme; - const result = getTriggerIconColor({ theme: differentTheme }); + const result = getTriggerIconColor({ + theme: differentTheme, + triggerType: 'DATABASE_EVENT', + }); - expect(result).toBe('#888888'); + expect(result).toBe(COLOR.blue); }); - it('maintains reference to theme.font.color.tertiary', () => { + it('maintains reference to theme.color.blue', () => { const customTheme: Theme = { - font: { - color: { - primary: '#111111', - secondary: '#333333', - tertiary: '#custom-tertiary-color', - light: '#eeeeee', - }, + color: { + blue: COLOR.blue, + purple: COLOR.purple, }, } as unknown as Theme; - const result = getTriggerIconColor({ theme: customTheme }); + const result = getTriggerIconColor({ + theme: customTheme, + triggerType: 'DATABASE_EVENT', + }); - expect(result).toBe('#custom-tertiary-color'); + expect(result).toBe(COLOR.blue); }); }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIconColor.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIconColor.ts index 36c6df88970..eebbdf8f8a2 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIconColor.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getTriggerIconColor.ts @@ -1,5 +1,21 @@ import { type Theme } from '@emotion/react'; +import { type WorkflowTriggerType } from '@/workflow/types/Workflow'; -export const getTriggerIconColor = ({ theme }: { theme: Theme }) => { - return theme.font.color.tertiary; +export const getTriggerIconColor = ({ + theme, + triggerType, +}: { + theme: Theme; + triggerType: WorkflowTriggerType; +}) => { + switch (triggerType) { + case 'DATABASE_EVENT': + return theme.color.blue; + case 'CRON': + case 'MANUAL': + case 'WEBHOOK': + return theme.color.purple; + default: + return theme.color.purple; + } };