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  Fixes https://discord.com/channels/1130383047699738754/1351906809417568376 --------- Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
This commit is contained in:
committed by
GitHub
parent
1c5f3ef5fa
commit
e6dec51ca6
@ -4,6 +4,13 @@ import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hook
|
||||
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
||||
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
|
||||
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 = ({
|
||||
workflow,
|
||||
@ -19,12 +26,14 @@ export const CommandMenuWorkflowEditStepContent = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<WorkflowStepDetail
|
||||
stepId={workflowSelectedNode}
|
||||
trigger={flow.trigger}
|
||||
steps={flow.steps}
|
||||
onActionUpdate={updateStep}
|
||||
onTriggerUpdate={updateTrigger}
|
||||
/>
|
||||
<StyledContainer>
|
||||
<WorkflowStepDetail
|
||||
stepId={workflowSelectedNode}
|
||||
trigger={flow.trigger}
|
||||
steps={flow.steps}
|
||||
onActionUpdate={updateStep}
|
||||
onTriggerUpdate={updateTrigger}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -20,17 +20,17 @@ import styled from '@emotion/styled';
|
||||
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui';
|
||||
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`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledTabList = styled(TabList)`
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type TabId = WorkflowRunTabIdType;
|
||||
|
||||
export const CommandMenuWorkflowRunViewStep = () => {
|
||||
|
||||
@ -2,20 +2,30 @@ import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
|
||||
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
||||
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 = () => {
|
||||
const flow = useFlowOrThrow();
|
||||
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
|
||||
|
||||
return (
|
||||
<WorkflowStepContextProvider
|
||||
value={{ workflowVersionId: flow.workflowVersionId }}
|
||||
>
|
||||
<WorkflowStepDetail
|
||||
stepId={workflowSelectedNode}
|
||||
trigger={flow.trigger}
|
||||
steps={flow.steps}
|
||||
readonly
|
||||
/>
|
||||
<StyledContainer>
|
||||
<WorkflowStepDetail
|
||||
stepId={workflowSelectedNode}
|
||||
trigger={flow.trigger}
|
||||
steps={flow.steps}
|
||||
readonly
|
||||
/>
|
||||
</StyledContainer>
|
||||
</WorkflowStepContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,27 +1,29 @@
|
||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||
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 { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
|
||||
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 {
|
||||
IconBrackets,
|
||||
JsonNestedNode,
|
||||
JsonTreeContextProvider,
|
||||
ShouldExpandNodeInitiallyProps,
|
||||
useIcons,
|
||||
} from 'twenty-ui';
|
||||
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 }) => {
|
||||
const { t } = useLingui();
|
||||
const { t, i18n } = useLingui();
|
||||
const { getIcon } = useIcons();
|
||||
const theme = useTheme();
|
||||
|
||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||
@ -49,6 +51,23 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
||||
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({
|
||||
step,
|
||||
});
|
||||
@ -69,30 +88,40 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
||||
keyPath.startsWith(previousStepId) && depth < 2;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<JsonTreeContextProvider
|
||||
value={{
|
||||
emptyArrayLabel: t`Empty Array`,
|
||||
emptyObjectLabel: t`Empty Object`,
|
||||
emptyStringLabel: t`[empty string]`,
|
||||
arrowButtonCollapsedLabel: t`Expand`,
|
||||
arrowButtonExpandedLabel: t`Collapse`,
|
||||
shouldHighlightNode: (keyPath) => variablesUsedInStep.has(keyPath),
|
||||
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
|
||||
}}
|
||||
>
|
||||
<JsonNestedNode
|
||||
elements={stepContext.map(({ id, name, context }) => ({
|
||||
id,
|
||||
label: name,
|
||||
value: context,
|
||||
}))}
|
||||
Icon={IconBrackets}
|
||||
depth={0}
|
||||
keyPath=""
|
||||
emptyElementsText=""
|
||||
/>
|
||||
</JsonTreeContextProvider>
|
||||
</StyledContainer>
|
||||
<>
|
||||
<WorkflowStepHeader
|
||||
disabled
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType={i18n._(headerType)}
|
||||
/>
|
||||
|
||||
<WorkflowRunStepJsonContainer>
|
||||
<JsonTreeContextProvider
|
||||
value={{
|
||||
emptyArrayLabel: t`Empty Array`,
|
||||
emptyObjectLabel: t`Empty Object`,
|
||||
emptyStringLabel: t`[empty string]`,
|
||||
arrowButtonCollapsedLabel: t`Expand`,
|
||||
arrowButtonExpandedLabel: t`Collapse`,
|
||||
shouldHighlightNode: (keyPath) => variablesUsedInStep.has(keyPath),
|
||||
shouldExpandNodeInitially: isFirstNodeDepthOfPreviousStep,
|
||||
}}
|
||||
>
|
||||
<JsonNestedNode
|
||||
elements={stepContext.map(({ id, name, context }) => ({
|
||||
id,
|
||||
label: name,
|
||||
value: context,
|
||||
}))}
|
||||
Icon={IconBrackets}
|
||||
depth={0}
|
||||
keyPath=""
|
||||
emptyElementsText=""
|
||||
/>
|
||||
</JsonTreeContextProvider>
|
||||
</WorkflowRunStepJsonContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 };
|
||||
@ -1,40 +1,68 @@
|
||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||
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 { isTwoFirstDepths, JsonTree } from 'twenty-ui';
|
||||
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)};
|
||||
`;
|
||||
import { isTwoFirstDepths, JsonTree, useIcons } from 'twenty-ui';
|
||||
|
||||
export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
||||
const { t, i18n } = useLingui();
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
if (!isDefined(workflowRun?.output?.stepsOutput)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 (
|
||||
<StyledContainer>
|
||||
<JsonTree
|
||||
value={stepOutput}
|
||||
shouldExpandNodeInitially={isTwoFirstDepths}
|
||||
emptyArrayLabel={t`Empty Array`}
|
||||
emptyObjectLabel={t`Empty Object`}
|
||||
emptyStringLabel={t`[empty string]`}
|
||||
arrowButtonCollapsedLabel={t`Expand`}
|
||||
arrowButtonExpandedLabel={t`Collapse`}
|
||||
<>
|
||||
<WorkflowStepHeader
|
||||
disabled
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType={i18n._(headerType)}
|
||||
/>
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,7 +7,8 @@ const StyledWorkflowStepBody = styled.div`
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
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)};
|
||||
`;
|
||||
|
||||
|
||||
@ -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 { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
|
||||
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 { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Monaco } from '@monaco-editor/react';
|
||||
import { editor } from 'monaco-editor';
|
||||
@ -40,12 +41,6 @@ import { CodeEditor, IconCode, IconPlayerPlay, useIcons } from 'twenty-ui';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledCodeEditorContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -76,7 +71,6 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionServerlessFunctionProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
@ -287,10 +281,12 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
? action.name
|
||||
: 'Code - Serverless Function';
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
return (
|
||||
!loading && (
|
||||
<StyledContainer>
|
||||
<>
|
||||
<StyledTabList
|
||||
tabs={tabs}
|
||||
behaveAsLinks={false}
|
||||
@ -303,9 +299,9 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
updateAction({ name: newName });
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.color.orange}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Code"
|
||||
headerType={headerType}
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
@ -373,7 +369,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,8 +7,9 @@ import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath'
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
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 { 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 { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Monaco } from '@monaco-editor/react';
|
||||
import { editor } from 'monaco-editor';
|
||||
@ -16,12 +17,6 @@ import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||
import { CodeEditor, useIcons } from 'twenty-ui';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledCodeEditorContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -34,7 +29,6 @@ type WorkflowReadonlyActionServerlessFunctionProps = {
|
||||
export const WorkflowReadonlyActionServerlessFunction = ({
|
||||
action,
|
||||
}: WorkflowReadonlyActionServerlessFunctionProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
||||
const serverlessFunctionVersion =
|
||||
@ -66,18 +60,20 @@ export const WorkflowReadonlyActionServerlessFunction = ({
|
||||
? action.name
|
||||
: 'Code - Serverless Function';
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<>
|
||||
<WorkflowStepHeader
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.color.orange}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Code"
|
||||
headerType={headerType}
|
||||
disabled
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
@ -99,6 +95,6 @@ export const WorkflowReadonlyActionServerlessFunction = ({
|
||||
/>
|
||||
</StyledCodeEditorContainer>
|
||||
</WorkflowStepBody>
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,9 +7,10 @@ import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOr
|
||||
import { WorkflowCreateRecordAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
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 { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { HorizontalSeparator, useIcons } from 'twenty-ui';
|
||||
import { JsonValue } from 'type-fest';
|
||||
@ -57,7 +58,6 @@ export const WorkflowEditActionCreateRecord = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionCreateRecordProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
@ -159,6 +159,8 @@ export const WorkflowEditActionCreateRecord = ({
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : `Create Record`;
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -174,9 +176,9 @@ export const WorkflowEditActionCreateRecord = ({
|
||||
});
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Action"
|
||||
headerType={headerType}
|
||||
disabled={isFormDisabled}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
|
||||
@ -3,10 +3,11 @@ import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowDeleteRecordAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||
import { WorkflowSingleRecordPicker } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowSingleRecordPicker';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
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 { HorizontalSeparator, useIcons } from 'twenty-ui';
|
||||
import { JsonValue } from 'type-fest';
|
||||
@ -34,7 +35,6 @@ export const WorkflowEditActionDeleteRecord = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionDeleteRecordProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
@ -108,6 +108,8 @@ export const WorkflowEditActionDeleteRecord = ({
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : `Delete Record`;
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -123,9 +125,9 @@ export const WorkflowEditActionDeleteRecord = ({
|
||||
});
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Action"
|
||||
headerType={headerType}
|
||||
disabled={isFormDisabled}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
|
||||
@ -2,11 +2,12 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowFindRecordsAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput';
|
||||
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 { HorizontalSeparator, useIcons } from 'twenty-ui';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
@ -33,7 +34,6 @@ export const WorkflowEditActionFindRecords = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionFindRecordsProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
@ -90,6 +90,8 @@ export const WorkflowEditActionFindRecords = ({
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : `Search Records`;
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -105,9 +107,9 @@ export const WorkflowEditActionFindRecords = ({
|
||||
});
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Action"
|
||||
headerType={headerType}
|
||||
disabled={isFormDisabled}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
|
||||
@ -12,9 +12,10 @@ import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||
import { WorkflowSendEmailAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
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 { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconPlus, useIcons } from 'twenty-ui';
|
||||
@ -47,7 +48,6 @@ export const WorkflowEditActionSendEmail = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionSendEmailProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { triggerApisOAuth } = useTriggerApisOAuth();
|
||||
@ -188,6 +188,9 @@ export const WorkflowEditActionSendEmail = ({
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : 'Send Email';
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
@ -206,9 +209,9 @@ export const WorkflowEditActionSendEmail = ({
|
||||
});
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.color.blue}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Email"
|
||||
headerType={headerType}
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { Select, SelectOption } from '@/ui/input/components/Select';
|
||||
import { WorkflowUpdateRecordAction } from '@/workflow/types/Workflow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
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 { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||
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 { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { HorizontalSeparator, useIcons } from 'twenty-ui';
|
||||
@ -59,7 +60,6 @@ export const WorkflowEditActionUpdateRecord = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionUpdateRecordProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
@ -160,6 +160,8 @@ export const WorkflowEditActionUpdateRecord = ({
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : `Update Record`;
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -175,9 +177,9 @@ export const WorkflowEditActionUpdateRecord = ({
|
||||
});
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Action"
|
||||
headerType={headerType}
|
||||
disabled={isFormDisabled}
|
||||
/>
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflo
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, fn, userEvent, within } from '@storybook/test';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||
@ -50,6 +51,7 @@ const meta: Meta<typeof WorkflowEditActionFindRecords> = {
|
||||
SnackBarDecorator,
|
||||
RouterDecorator,
|
||||
WorkspaceDecorator,
|
||||
I18nFrontDecorator,
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { WorkflowStepType } from '@/workflow/types/Workflow';
|
||||
import { WorkflowActionType } from '@/workflow/types/Workflow';
|
||||
|
||||
export const OTHER_ACTIONS: Array<{
|
||||
label: string;
|
||||
type: WorkflowStepType;
|
||||
type: Exclude<
|
||||
WorkflowActionType,
|
||||
'CREATE_RECORD' | 'UPDATE_RECORD' | 'DELETE_RECORD' | 'FIND_RECORDS'
|
||||
>;
|
||||
icon: string;
|
||||
}> = [
|
||||
{
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { WorkflowStepType } from '@/workflow/types/Workflow';
|
||||
import { WorkflowActionType } from '@/workflow/types/Workflow';
|
||||
|
||||
export const RECORD_ACTIONS: Array<{
|
||||
label: string;
|
||||
type: WorkflowStepType;
|
||||
type: Extract<
|
||||
WorkflowActionType,
|
||||
'CREATE_RECORD' | 'UPDATE_RECORD' | 'DELETE_RECORD' | 'FIND_RECORDS'
|
||||
>;
|
||||
icon: string;
|
||||
}> = [
|
||||
{
|
||||
|
||||
@ -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 { WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
|
||||
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 { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
@ -99,6 +101,9 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
|
||||
const headerTitle = isDefined(action.name) ? action.name : `Form`;
|
||||
const headerIcon = getActionIcon(action.type);
|
||||
const headerIconColor = useActionIconColorOrThrow(action.type);
|
||||
const headerType = useActionHeaderTypeOrThrow(action.type);
|
||||
|
||||
const [selectedField, setSelectedField] = useState<string | null>(null);
|
||||
const isFieldSelected = (fieldName: string) => selectedField === fieldName;
|
||||
const handleFieldClick = (fieldName: string) => {
|
||||
@ -161,9 +166,9 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
});
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType="Action"
|
||||
headerType={headerType}
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
|
||||
@ -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));
|
||||
};
|
||||
@ -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 });
|
||||
};
|
||||
@ -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}`);
|
||||
}
|
||||
};
|
||||
@ -1,7 +1,8 @@
|
||||
import { WorkflowActionType } from '@/workflow/types/Workflow';
|
||||
import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions';
|
||||
import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions';
|
||||
|
||||
export const getActionIcon = (actionType: string) => {
|
||||
export const getActionIcon = (actionType: WorkflowActionType) => {
|
||||
switch (actionType) {
|
||||
case 'CREATE_RECORD':
|
||||
case 'UPDATE_RECORD':
|
||||
|
||||
@ -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}`);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user