Display workflow step header in workflow run input and output tabs (#11102)

- Wrap the content of Workflow View, Workflow Edit, and Workflow Run
side panels with a container making them take all the available height
- Remove the `StyledContainer` of code steps as it's redundant with the
global container
- Add the WorkflowStepHeader to the input and output tabs
- Make the JSON visualizer take all the available height in input and
output tabs
- Reuse the WorkflowStepBody component in the input and output tabs as
it applies proper background color

## Demo

![CleanShot 2025-03-21 at 18 30
26@2x](https://github.com/user-attachments/assets/c3fa512b-c371-4d0b-9bf6-a5f84d333dda)

Fixes
https://discord.com/channels/1130383047699738754/1351906809417568376

---------

Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
This commit is contained in:
Baptiste Devessier
2025-03-24 14:06:26 +01:00
committed by GitHub
parent 1c5f3ef5fa
commit e6dec51ca6
23 changed files with 294 additions and 125 deletions

View File

@ -4,6 +4,13 @@ import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hook
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep'; import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger'; import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
import styled from '@emotion/styled';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
export const CommandMenuWorkflowEditStepContent = ({ export const CommandMenuWorkflowEditStepContent = ({
workflow, workflow,
@ -19,12 +26,14 @@ export const CommandMenuWorkflowEditStepContent = ({
}); });
return ( return (
<WorkflowStepDetail <StyledContainer>
stepId={workflowSelectedNode} <WorkflowStepDetail
trigger={flow.trigger} stepId={workflowSelectedNode}
steps={flow.steps} trigger={flow.trigger}
onActionUpdate={updateStep} steps={flow.steps}
onTriggerUpdate={updateTrigger} onActionUpdate={updateStep}
/> onTriggerUpdate={updateTrigger}
/>
</StyledContainer>
); );
}; };

View File

@ -20,17 +20,17 @@ import styled from '@emotion/styled';
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui'; import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
const StyledTabList = styled(TabList)`
background-color: ${({ theme }) => theme.background.secondary};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledContainer = styled.div` const StyledContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
`; `;
const StyledTabList = styled(TabList)`
background-color: ${({ theme }) => theme.background.secondary};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
type TabId = WorkflowRunTabIdType; type TabId = WorkflowRunTabIdType;
export const CommandMenuWorkflowRunViewStep = () => { export const CommandMenuWorkflowRunViewStep = () => {

View File

@ -2,20 +2,30 @@ import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext'; import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow'; import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail'; import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
import styled from '@emotion/styled';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
export const CommandMenuWorkflowViewStep = () => { export const CommandMenuWorkflowViewStep = () => {
const flow = useFlowOrThrow(); const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow(); const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
return ( return (
<WorkflowStepContextProvider <WorkflowStepContextProvider
value={{ workflowVersionId: flow.workflowVersionId }} value={{ workflowVersionId: flow.workflowVersionId }}
> >
<WorkflowStepDetail <StyledContainer>
stepId={workflowSelectedNode} <WorkflowStepDetail
trigger={flow.trigger} stepId={workflowSelectedNode}
steps={flow.steps} trigger={flow.trigger}
readonly steps={flow.steps}
/> readonly
/>
</StyledContainer>
</WorkflowStepContextProvider> </WorkflowStepContextProvider>
); );
}; };

View File

@ -1,27 +1,29 @@
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { getWorkflowPreviousStepId } from '@/workflow/workflow-steps/utils/getWorkflowPreviousStep'; import { getWorkflowPreviousStepId } from '@/workflow/workflow-steps/utils/getWorkflowPreviousStep';
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext'; import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep'; import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
import styled from '@emotion/styled'; import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { import {
IconBrackets, IconBrackets,
JsonNestedNode, JsonNestedNode,
JsonTreeContextProvider, JsonTreeContextProvider,
ShouldExpandNodeInitiallyProps, ShouldExpandNodeInitiallyProps,
useIcons,
} from 'twenty-ui'; } from 'twenty-ui';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div`
display: grid;
overflow-x: auto;
padding-block: ${({ theme }) => theme.spacing(4)};
padding-inline: ${({ theme }) => theme.spacing(3)};
`;
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => { export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
const { t } = useLingui(); const { t, i18n } = useLingui();
const { getIcon } = useIcons();
const theme = useTheme();
const workflowRunId = useWorkflowRunIdOrThrow(); const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId }); const workflowRun = useWorkflowRun({ workflowRunId });
@ -49,6 +51,23 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
return null; return null;
} }
const stepDefinition = getStepDefinitionOrThrow({
stepId,
trigger: workflowRun.output.flow.trigger,
steps: workflowRun.output.flow.steps,
});
if (stepDefinition?.type !== 'action') {
throw new Error('The input tab must be rendered with an action step.');
}
const headerTitle = stepDefinition.definition.name;
const headerIcon = getActionIcon(stepDefinition.definition.type);
const headerIconColor = getActionIconColorOrThrow({
theme,
actionType: stepDefinition.definition.type,
});
const headerType = getActionHeaderTypeOrThrow(stepDefinition.definition.type);
const variablesUsedInStep = getWorkflowVariablesUsedInStep({ const variablesUsedInStep = getWorkflowVariablesUsedInStep({
step, step,
}); });
@ -69,30 +88,40 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
keyPath.startsWith(previousStepId) && depth < 2; keyPath.startsWith(previousStepId) && depth < 2;
return ( return (
<StyledContainer> <>
<JsonTreeContextProvider <WorkflowStepHeader
value={{ disabled
emptyArrayLabel: t`Empty Array`, Icon={getIcon(headerIcon)}
emptyObjectLabel: t`Empty Object`, iconColor={headerIconColor}
emptyStringLabel: t`[empty string]`, initialTitle={headerTitle}
arrowButtonCollapsedLabel: t`Expand`, headerType={i18n._(headerType)}
arrowButtonExpandedLabel: t`Collapse`, />
shouldHighlightNode: (keyPath) => variablesUsedInStep.has(keyPath),
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep, <WorkflowRunStepJsonContainer>
}} <JsonTreeContextProvider
> value={{
<JsonNestedNode emptyArrayLabel: t`Empty Array`,
elements={stepContext.map(({ id, name, context }) => ({ emptyObjectLabel: t`Empty Object`,
id, emptyStringLabel: t`[empty string]`,
label: name, arrowButtonCollapsedLabel: t`Expand`,
value: context, arrowButtonExpandedLabel: t`Collapse`,
}))} shouldHighlightNode: (keyPath) => variablesUsedInStep.has(keyPath),
Icon={IconBrackets} shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
depth={0} }}
keyPath="" >
emptyElementsText="" <JsonNestedNode
/> elements={stepContext.map(({ id, name, context }) => ({
</JsonTreeContextProvider> id,
</StyledContainer> label: name,
value: context,
}))}
Icon={IconBrackets}
depth={0}
keyPath=""
emptyElementsText=""
/>
</JsonTreeContextProvider>
</WorkflowRunStepJsonContainer>
</>
); );
}; };

View File

@ -0,0 +1,11 @@
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import styled from '@emotion/styled';
const StyledWorkflowRunStepJsonContainer = styled(WorkflowStepBody)`
grid-template-rows: max-content;
gap: 0;
display: grid;
overflow: auto;
`;
export { StyledWorkflowRunStepJsonContainer as WorkflowRunStepJsonContainer };

View File

@ -1,40 +1,68 @@
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun'; import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow'; import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import styled from '@emotion/styled'; import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
import { WorkflowRunStepJsonContainer } from '@/workflow/workflow-steps/components/WorkflowRunStepJsonContainer';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { isTwoFirstDepths, JsonTree } from 'twenty-ui';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { isTwoFirstDepths, JsonTree, useIcons } from 'twenty-ui';
const StyledContainer = styled.div`
display: grid;
overflow-x: auto;
padding-block: ${({ theme }) => theme.spacing(4)};
padding-inline: ${({ theme }) => theme.spacing(3)};
`;
export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => { export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
const { t, i18n } = useLingui();
const theme = useTheme();
const { getIcon } = useIcons();
const workflowRunId = useWorkflowRunIdOrThrow(); const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId }); const workflowRun = useWorkflowRun({ workflowRunId });
const { t } = useLingui();
if (!isDefined(workflowRun?.output?.stepsOutput)) { if (!isDefined(workflowRun?.output?.stepsOutput)) {
return null; return null;
} }
const stepOutput = workflowRun.output.stepsOutput[stepId]; const stepOutput = workflowRun.output.stepsOutput[stepId];
const stepDefinition = getStepDefinitionOrThrow({
stepId,
trigger: workflowRun.output.flow.trigger,
steps: workflowRun.output.flow.steps,
});
if (stepDefinition?.type !== 'action') {
throw new Error('The output tab must be rendered with an action step.');
}
const headerTitle = stepDefinition.definition.name;
const headerIcon = getActionIcon(stepDefinition.definition.type);
const headerIconColor = getActionIconColorOrThrow({
theme,
actionType: stepDefinition.definition.type,
});
const headerType = getActionHeaderTypeOrThrow(stepDefinition.definition.type);
return ( return (
<StyledContainer> <>
<JsonTree <WorkflowStepHeader
value={stepOutput} disabled
shouldExpandNodeInitially={isTwoFirstDepths} Icon={getIcon(headerIcon)}
emptyArrayLabel={t`Empty Array`} iconColor={headerIconColor}
emptyObjectLabel={t`Empty Object`} initialTitle={headerTitle}
emptyStringLabel={t`[empty string]`} headerType={i18n._(headerType)}
arrowButtonCollapsedLabel={t`Expand`}
arrowButtonExpandedLabel={t`Collapse`}
/> />
</StyledContainer>
<WorkflowRunStepJsonContainer>
<JsonTree
value={stepOutput}
shouldExpandNodeInitially={isTwoFirstDepths}
emptyArrayLabel={t`Empty Array`}
emptyObjectLabel={t`Empty Object`}
emptyStringLabel={t`[empty string]`}
arrowButtonCollapsedLabel={t`Expand`}
arrowButtonExpandedLabel={t`Collapse`}
/>
</WorkflowRunStepJsonContainer>
</>
); );
}; };

View File

@ -7,7 +7,8 @@ const StyledWorkflowStepBody = styled.div`
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
overflow-y: scroll; overflow-y: scroll;
padding: ${({ theme }) => theme.spacing(4)}; padding-block: ${({ theme }) => theme.spacing(4)};
padding-inline: ${({ theme }) => theme.spacing(3)};
row-gap: ${({ theme }) => theme.spacing(6)}; row-gap: ${({ theme }) => theme.spacing(6)};
`; `;

View File

@ -27,9 +27,10 @@ import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId'; import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId';
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId'; import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers'; import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Monaco } from '@monaco-editor/react'; import { Monaco } from '@monaco-editor/react';
import { editor } from 'monaco-editor'; import { editor } from 'monaco-editor';
@ -40,12 +41,6 @@ import { CodeEditor, IconCode, IconPlayerPlay, useIcons } from 'twenty-ui';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
const StyledCodeEditorContainer = styled.div` const StyledCodeEditorContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -76,7 +71,6 @@ export const WorkflowEditActionServerlessFunction = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionServerlessFunctionProps) => { }: WorkflowEditActionServerlessFunctionProps) => {
const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const serverlessFunctionId = action.settings.input.serverlessFunctionId; const serverlessFunctionId = action.settings.input.serverlessFunctionId;
const activeTabId = useRecoilComponentValueV2( const activeTabId = useRecoilComponentValueV2(
@ -287,10 +281,12 @@ export const WorkflowEditActionServerlessFunction = ({
? action.name ? action.name
: 'Code - Serverless Function'; : 'Code - Serverless Function';
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
return ( return (
!loading && ( !loading && (
<StyledContainer> <>
<StyledTabList <StyledTabList
tabs={tabs} tabs={tabs}
behaveAsLinks={false} behaveAsLinks={false}
@ -303,9 +299,9 @@ export const WorkflowEditActionServerlessFunction = ({
updateAction({ name: newName }); updateAction({ name: newName });
}} }}
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.color.orange} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Code" headerType={headerType}
disabled={actionOptions.readonly} disabled={actionOptions.readonly}
/> />
<WorkflowStepBody> <WorkflowStepBody>
@ -373,7 +369,7 @@ export const WorkflowEditActionServerlessFunction = ({
]} ]}
/> />
)} )}
</StyledContainer> </>
) )
); );
}; };

View File

@ -7,8 +7,9 @@ import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath'
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunctionFields'; import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunctionFields';
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers'; import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Monaco } from '@monaco-editor/react'; import { Monaco } from '@monaco-editor/react';
import { editor } from 'monaco-editor'; import { editor } from 'monaco-editor';
@ -16,12 +17,6 @@ import { AutoTypings } from 'monaco-editor-auto-typings';
import { CodeEditor, useIcons } from 'twenty-ui'; import { CodeEditor, useIcons } from 'twenty-ui';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
const StyledCodeEditorContainer = styled.div` const StyledCodeEditorContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -34,7 +29,6 @@ type WorkflowReadonlyActionServerlessFunctionProps = {
export const WorkflowReadonlyActionServerlessFunction = ({ export const WorkflowReadonlyActionServerlessFunction = ({
action, action,
}: WorkflowReadonlyActionServerlessFunctionProps) => { }: WorkflowReadonlyActionServerlessFunctionProps) => {
const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const serverlessFunctionId = action.settings.input.serverlessFunctionId; const serverlessFunctionId = action.settings.input.serverlessFunctionId;
const serverlessFunctionVersion = const serverlessFunctionVersion =
@ -66,18 +60,20 @@ export const WorkflowReadonlyActionServerlessFunction = ({
? action.name ? action.name
: 'Code - Serverless Function'; : 'Code - Serverless Function';
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
if (loading) { if (loading) {
return null; return null;
} }
return ( return (
<StyledContainer> <>
<WorkflowStepHeader <WorkflowStepHeader
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.color.orange} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Code" headerType={headerType}
disabled disabled
/> />
<WorkflowStepBody> <WorkflowStepBody>
@ -99,6 +95,6 @@ export const WorkflowReadonlyActionServerlessFunction = ({
/> />
</StyledCodeEditorContainer> </StyledCodeEditorContainer>
</WorkflowStepBody> </WorkflowStepBody>
</StyledContainer> </>
); );
}; };

View File

@ -7,9 +7,10 @@ import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOr
import { WorkflowCreateRecordAction } from '@/workflow/types/Workflow'; import { WorkflowCreateRecordAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { HorizontalSeparator, useIcons } from 'twenty-ui'; import { HorizontalSeparator, useIcons } from 'twenty-ui';
import { JsonValue } from 'type-fest'; import { JsonValue } from 'type-fest';
@ -57,7 +58,6 @@ export const WorkflowEditActionCreateRecord = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionCreateRecordProps) => { }: WorkflowEditActionCreateRecordProps) => {
const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
@ -159,6 +159,8 @@ export const WorkflowEditActionCreateRecord = ({
const headerTitle = isDefined(action.name) ? action.name : `Create Record`; const headerTitle = isDefined(action.name) ? action.name : `Create Record`;
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
return ( return (
<> <>
@ -174,9 +176,9 @@ export const WorkflowEditActionCreateRecord = ({
}); });
}} }}
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.font.color.tertiary} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Action" headerType={headerType}
disabled={isFormDisabled} disabled={isFormDisabled}
/> />
<WorkflowStepBody> <WorkflowStepBody>

View File

@ -3,10 +3,11 @@ import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowDeleteRecordAction } from '@/workflow/types/Workflow'; import { WorkflowDeleteRecordAction } from '@/workflow/types/Workflow';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { WorkflowSingleRecordPicker } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker'; import { WorkflowSingleRecordPicker } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker';
import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { HorizontalSeparator, useIcons } from 'twenty-ui'; import { HorizontalSeparator, useIcons } from 'twenty-ui';
import { JsonValue } from 'type-fest'; import { JsonValue } from 'type-fest';
@ -34,7 +35,6 @@ export const WorkflowEditActionDeleteRecord = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionDeleteRecordProps) => { }: WorkflowEditActionDeleteRecordProps) => {
const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
@ -108,6 +108,8 @@ export const WorkflowEditActionDeleteRecord = ({
const headerTitle = isDefined(action.name) ? action.name : `Delete Record`; const headerTitle = isDefined(action.name) ? action.name : `Delete Record`;
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
return ( return (
<> <>
@ -123,9 +125,9 @@ export const WorkflowEditActionDeleteRecord = ({
}); });
}} }}
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.font.color.tertiary} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Action" headerType={headerType}
disabled={isFormDisabled} disabled={isFormDisabled}
/> />
<WorkflowStepBody> <WorkflowStepBody>

View File

@ -2,11 +2,12 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
import { Select, SelectOption } from '@/ui/input/components/Select'; import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowFindRecordsAction } from '@/workflow/types/Workflow'; import { WorkflowFindRecordsAction } from '@/workflow/types/Workflow';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput'; import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { HorizontalSeparator, useIcons } from 'twenty-ui'; import { HorizontalSeparator, useIcons } from 'twenty-ui';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
@ -33,7 +34,6 @@ export const WorkflowEditActionFindRecords = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionFindRecordsProps) => { }: WorkflowEditActionFindRecordsProps) => {
const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
@ -90,6 +90,8 @@ export const WorkflowEditActionFindRecords = ({
const headerTitle = isDefined(action.name) ? action.name : `Search Records`; const headerTitle = isDefined(action.name) ? action.name : `Search Records`;
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
return ( return (
<> <>
@ -105,9 +107,9 @@ export const WorkflowEditActionFindRecords = ({
}); });
}} }}
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.font.color.tertiary} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Action" headerType={headerType}
disabled={isFormDisabled} disabled={isFormDisabled}
/> />
<WorkflowStepBody> <WorkflowStepBody>

View File

@ -12,9 +12,10 @@ import { workflowIdState } from '@/workflow/states/workflowIdState';
import { WorkflowSendEmailAction } from '@/workflow/types/Workflow'; import { WorkflowSendEmailAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { IconPlus, useIcons } from 'twenty-ui'; import { IconPlus, useIcons } from 'twenty-ui';
@ -47,7 +48,6 @@ export const WorkflowEditActionSendEmail = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionSendEmailProps) => { }: WorkflowEditActionSendEmailProps) => {
const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { triggerApisOAuth } = useTriggerApisOAuth(); const { triggerApisOAuth } = useTriggerApisOAuth();
@ -188,6 +188,9 @@ export const WorkflowEditActionSendEmail = ({
const headerTitle = isDefined(action.name) ? action.name : 'Send Email'; const headerTitle = isDefined(action.name) ? action.name : 'Send Email';
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
const navigate = useNavigateSettings(); const navigate = useNavigateSettings();
const { closeCommandMenu } = useCommandMenu(); const { closeCommandMenu } = useCommandMenu();
@ -206,9 +209,9 @@ export const WorkflowEditActionSendEmail = ({
}); });
}} }}
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.color.blue} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Email" headerType={headerType}
disabled={actionOptions.readonly} disabled={actionOptions.readonly}
/> />
<WorkflowStepBody> <WorkflowStepBody>

View File

@ -1,7 +1,6 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { Select, SelectOption } from '@/ui/input/components/Select'; import { Select, SelectOption } from '@/ui/input/components/Select';
import { WorkflowUpdateRecordAction } from '@/workflow/types/Workflow'; import { WorkflowUpdateRecordAction } from '@/workflow/types/Workflow';
import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition'; import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
@ -10,6 +9,8 @@ import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-typ
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { WorkflowSingleRecordPicker } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker'; import { WorkflowSingleRecordPicker } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { HorizontalSeparator, useIcons } from 'twenty-ui'; import { HorizontalSeparator, useIcons } from 'twenty-ui';
@ -59,7 +60,6 @@ export const WorkflowEditActionUpdateRecord = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionUpdateRecordProps) => { }: WorkflowEditActionUpdateRecordProps) => {
const theme = useTheme();
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems(); const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
@ -160,6 +160,8 @@ export const WorkflowEditActionUpdateRecord = ({
const headerTitle = isDefined(action.name) ? action.name : `Update Record`; const headerTitle = isDefined(action.name) ? action.name : `Update Record`;
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
return ( return (
<> <>
@ -175,9 +177,9 @@ export const WorkflowEditActionUpdateRecord = ({
}); });
}} }}
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.font.color.tertiary} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Action" headerType={headerType}
disabled={isFormDisabled} disabled={isFormDisabled}
/> />

View File

@ -3,6 +3,7 @@ import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflo
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { expect, fn, userEvent, within } from '@storybook/test'; import { expect, fn, userEvent, within } from '@storybook/test';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui'; import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
@ -50,6 +51,7 @@ const meta: Meta<typeof WorkflowEditActionFindRecords> = {
SnackBarDecorator, SnackBarDecorator,
RouterDecorator, RouterDecorator,
WorkspaceDecorator, WorkspaceDecorator,
I18nFrontDecorator,
], ],
}; };

View File

@ -1,8 +1,11 @@
import { WorkflowStepType } from '@/workflow/types/Workflow'; import { WorkflowActionType } from '@/workflow/types/Workflow';
export const OTHER_ACTIONS: Array<{ export const OTHER_ACTIONS: Array<{
label: string; label: string;
type: WorkflowStepType; type: Exclude<
WorkflowActionType,
'CREATE_RECORD' | 'UPDATE_RECORD' | 'DELETE_RECORD' | 'FIND_RECORDS'
>;
icon: string; icon: string;
}> = [ }> = [
{ {

View File

@ -1,8 +1,11 @@
import { WorkflowStepType } from '@/workflow/types/Workflow'; import { WorkflowActionType } from '@/workflow/types/Workflow';
export const RECORD_ACTIONS: Array<{ export const RECORD_ACTIONS: Array<{
label: string; label: string;
type: WorkflowStepType; type: Extract<
WorkflowActionType,
'CREATE_RECORD' | 'UPDATE_RECORD' | 'DELETE_RECORD' | 'FIND_RECORDS'
>;
icon: string; icon: string;
}> = [ }> = [
{ {

View File

@ -8,6 +8,8 @@ import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/Workflo
import { WorkflowEditActionFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFieldSettings'; import { WorkflowEditActionFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFieldSettings';
import { WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField'; import { WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings'; import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow';
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
@ -99,6 +101,9 @@ export const WorkflowEditActionFormBuilder = ({
const headerTitle = isDefined(action.name) ? action.name : `Form`; const headerTitle = isDefined(action.name) ? action.name : `Form`;
const headerIcon = getActionIcon(action.type); const headerIcon = getActionIcon(action.type);
const headerIconColor = useActionIconColorOrThrow(action.type);
const headerType = useActionHeaderTypeOrThrow(action.type);
const [selectedField, setSelectedField] = useState<string | null>(null); const [selectedField, setSelectedField] = useState<string | null>(null);
const isFieldSelected = (fieldName: string) => selectedField === fieldName; const isFieldSelected = (fieldName: string) => selectedField === fieldName;
const handleFieldClick = (fieldName: string) => { const handleFieldClick = (fieldName: string) => {
@ -161,9 +166,9 @@ export const WorkflowEditActionFormBuilder = ({
}); });
}} }}
Icon={getIcon(headerIcon)} Icon={getIcon(headerIcon)}
iconColor={theme.font.color.tertiary} iconColor={headerIconColor}
initialTitle={headerTitle} initialTitle={headerTitle}
headerType="Action" headerType={headerType}
disabled={actionOptions.readonly} disabled={actionOptions.readonly}
/> />
<WorkflowStepBody> <WorkflowStepBody>

View File

@ -0,0 +1,9 @@
import { WorkflowActionType } from '@/workflow/types/Workflow';
import { getActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionHeaderTypeOrThrow';
import { useLingui } from '@lingui/react';
export const useActionHeaderTypeOrThrow = (actionType: WorkflowActionType) => {
const { _ } = useLingui();
return _(getActionHeaderTypeOrThrow(actionType));
};

View File

@ -0,0 +1,9 @@
import { WorkflowActionType } from '@/workflow/types/Workflow';
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
import { useTheme } from '@emotion/react';
export const useActionIconColorOrThrow = (actionType: WorkflowActionType) => {
const theme = useTheme();
return getActionIconColorOrThrow({ theme, actionType });
};

View File

@ -0,0 +1,20 @@
import { WorkflowActionType } from '@/workflow/types/Workflow';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
export const getActionHeaderTypeOrThrow = (actionType: WorkflowActionType) => {
switch (actionType) {
case 'CODE':
return msg`Code`;
case 'CREATE_RECORD':
case 'UPDATE_RECORD':
case 'DELETE_RECORD':
case 'FIND_RECORDS':
case 'FORM':
return msg`Action`;
case 'SEND_EMAIL':
return msg`Email`;
default:
assertUnreachable(actionType, `Unsupported action type: ${actionType}`);
}
};

View File

@ -1,7 +1,8 @@
import { WorkflowActionType } from '@/workflow/types/Workflow';
import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions'; import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions';
import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions'; import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions';
export const getActionIcon = (actionType: string) => { export const getActionIcon = (actionType: WorkflowActionType) => {
switch (actionType) { switch (actionType) {
case 'CREATE_RECORD': case 'CREATE_RECORD':
case 'UPDATE_RECORD': case 'UPDATE_RECORD':

View File

@ -0,0 +1,26 @@
import { WorkflowActionType } from '@/workflow/types/Workflow';
import { Theme } from '@emotion/react';
import { assertUnreachable } from 'twenty-shared/utils';
export const getActionIconColorOrThrow = ({
theme,
actionType,
}: {
theme: Theme;
actionType: WorkflowActionType;
}) => {
switch (actionType) {
case 'CODE':
return theme.color.orange;
case 'CREATE_RECORD':
case 'UPDATE_RECORD':
case 'DELETE_RECORD':
case 'FIND_RECORDS':
case 'FORM':
return theme.font.color.tertiary;
case 'SEND_EMAIL':
return theme.color.blue;
default:
assertUnreachable(actionType, `Unsupported action type: ${actionType}`);
}
};