App logic function as step (#17525)
- Add logic function as step - Rename previous one as Code <img width="494" height="573" alt="Capture d’écran 2026-01-28 à 16 15 29" src="https://github.com/user-attachments/assets/82f7e720-20da-4926-b5bc-94a33cd1f53a" /> <img width="494" height="264" alt="Capture d’écran 2026-01-28 à 16 15 39" src="https://github.com/user-attachments/assets/a0444ae9-8c50-418d-8c5f-68c4da08fce7" /> <img width="494" height="264" alt="Capture d’écran 2026-01-28 à 16 15 58" src="https://github.com/user-attachments/assets/3025db09-4e59-421e-8dc5-f3a7a7326554" /> --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
+15
-3
@@ -5,7 +5,7 @@ import {
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
||||
import { WorkflowEditActionAiAgent } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent';
|
||||
import { WorkflowActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionLogicFunction';
|
||||
import { WorkflowActionCode } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionCode';
|
||||
import { WorkflowEditActionCreateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord';
|
||||
import { WorkflowEditActionDeleteRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord';
|
||||
import { WorkflowEditActionEmpty } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty';
|
||||
@@ -15,10 +15,11 @@ import { WorkflowEditActionUpsertRecord } from '@/workflow/workflow-steps/workfl
|
||||
import { WorkflowEditActionDelay } from '@/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay';
|
||||
import { WorkflowEditActionFilter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilter';
|
||||
import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords';
|
||||
import { WorkflowEditActionIfElse } from '@/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElse';
|
||||
import { WorkflowEditActionFormFiller } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFiller';
|
||||
import { WorkflowEditActionHttpRequest } from '@/workflow/workflow-steps/workflow-actions/http-request-action/components/WorkflowEditActionHttpRequest';
|
||||
import { WorkflowEditActionIfElse } from '@/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElse';
|
||||
import { WorkflowEditActionIterator } from '@/workflow/workflow-steps/workflow-actions/iterator-action/components/WorkflowEditActionIterator';
|
||||
import { WorkflowEditActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/logic-function-action/components/WorkflowEditActionLogicFunction';
|
||||
import { WorkflowEditTriggerCronForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm';
|
||||
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm';
|
||||
import { WorkflowEditTriggerManual } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManual';
|
||||
@@ -106,7 +107,18 @@ export const WorkflowRunStepNodeDetail = ({
|
||||
switch (stepDefinition.definition.type) {
|
||||
case 'CODE': {
|
||||
return (
|
||||
<WorkflowActionLogicFunction
|
||||
<WorkflowActionCode
|
||||
key={stepId}
|
||||
action={stepDefinition.definition}
|
||||
actionOptions={{
|
||||
readonly: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'LOGIC_FUNCTION': {
|
||||
return (
|
||||
<WorkflowEditActionLogicFunction
|
||||
key={stepId}
|
||||
action={stepDefinition.definition}
|
||||
actionOptions={{
|
||||
|
||||
+13
-3
@@ -4,7 +4,7 @@ import {
|
||||
} from '@/workflow/types/Workflow';
|
||||
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
||||
import { WorkflowEditActionAiAgent } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent';
|
||||
import { WorkflowActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionLogicFunction';
|
||||
import { WorkflowActionCode } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionCode';
|
||||
import { WorkflowEditActionCreateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord';
|
||||
import { WorkflowEditActionDeleteRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord';
|
||||
import { WorkflowEditActionEmpty } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty';
|
||||
@@ -14,10 +14,11 @@ import { WorkflowEditActionUpsertRecord } from '@/workflow/workflow-steps/workfl
|
||||
import { WorkflowEditActionDelay } from '@/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay';
|
||||
import { WorkflowEditActionFilter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilter';
|
||||
import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowEditActionFindRecords';
|
||||
import { WorkflowEditActionIfElse } from '@/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElse';
|
||||
import { WorkflowEditActionFormBuilder } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormBuilder';
|
||||
import { WorkflowEditActionHttpRequest } from '@/workflow/workflow-steps/workflow-actions/http-request-action/components/WorkflowEditActionHttpRequest';
|
||||
import { WorkflowEditActionIfElse } from '@/workflow/workflow-steps/workflow-actions/if-else-action/components/WorkflowEditActionIfElse';
|
||||
import { WorkflowEditActionIterator } from '@/workflow/workflow-steps/workflow-actions/iterator-action/components/WorkflowEditActionIterator';
|
||||
import { WorkflowEditActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/logic-function-action/components/WorkflowEditActionLogicFunction';
|
||||
import { WorkflowEditTriggerCronForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm';
|
||||
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm';
|
||||
import { WorkflowEditTriggerManual } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManual';
|
||||
@@ -108,7 +109,16 @@ export const WorkflowStepDetail = ({
|
||||
switch (stepDefinition.definition.type) {
|
||||
case 'CODE': {
|
||||
return (
|
||||
<WorkflowActionLogicFunction
|
||||
<WorkflowActionCode
|
||||
key={stepId}
|
||||
action={stepDefinition.definition}
|
||||
actionOptions={props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'LOGIC_FUNCTION': {
|
||||
return (
|
||||
<WorkflowEditActionLogicFunction
|
||||
key={stepId}
|
||||
action={stepDefinition.definition}
|
||||
actionOptions={props}
|
||||
|
||||
@@ -36,6 +36,8 @@ export const useCreateStep = () => {
|
||||
connectionOptions,
|
||||
shouldSelectNode = true,
|
||||
workflowVersionId: providedWorkflowVersionId,
|
||||
logicFunctionId,
|
||||
name,
|
||||
}: {
|
||||
newStepType: WorkflowActionType;
|
||||
parentStepId: string | undefined;
|
||||
@@ -44,6 +46,8 @@ export const useCreateStep = () => {
|
||||
connectionOptions?: WorkflowStepConnectionOptions;
|
||||
shouldSelectNode?: boolean;
|
||||
workflowVersionId?: string;
|
||||
logicFunctionId?: string;
|
||||
name?: string;
|
||||
}) => {
|
||||
if (isLoading === true) {
|
||||
return;
|
||||
@@ -65,6 +69,8 @@ export const useCreateStep = () => {
|
||||
nextStepId,
|
||||
position,
|
||||
parentStepConnectionOptions: connectionOptions,
|
||||
logicFunctionId,
|
||||
name,
|
||||
})
|
||||
)?.data?.createWorkflowVersionStep;
|
||||
|
||||
|
||||
+11
-14
@@ -2,7 +2,7 @@ import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
|
||||
|
||||
type WorkflowActionLogicFunctionProps = {
|
||||
type WorkflowActionCodeProps = {
|
||||
action: WorkflowCodeAction;
|
||||
actionOptions:
|
||||
| {
|
||||
@@ -14,35 +14,32 @@ type WorkflowActionLogicFunctionProps = {
|
||||
};
|
||||
};
|
||||
|
||||
const WorkflowEditActionLogicFunction = lazy(() =>
|
||||
const WorkflowEditActionCode = lazy(() =>
|
||||
import(
|
||||
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunction'
|
||||
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCode'
|
||||
).then((module) => ({
|
||||
default: module.WorkflowEditActionLogicFunction,
|
||||
default: module.WorkflowEditActionCode,
|
||||
})),
|
||||
);
|
||||
|
||||
const WorkflowReadonlyActionLogicFunction = lazy(() =>
|
||||
const WorkflowReadonlyActionCode = lazy(() =>
|
||||
import(
|
||||
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowReadonlyActionLogicFunction'
|
||||
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowReadonlyActionCode'
|
||||
).then((module) => ({
|
||||
default: module.WorkflowReadonlyActionLogicFunction,
|
||||
default: module.WorkflowReadonlyActionCode,
|
||||
})),
|
||||
);
|
||||
|
||||
export const WorkflowActionLogicFunction = ({
|
||||
export const WorkflowActionCode = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowActionLogicFunctionProps) => {
|
||||
}: WorkflowActionCodeProps) => {
|
||||
return (
|
||||
<Suspense fallback={<RightDrawerSkeletonLoader />}>
|
||||
{actionOptions.readonly ? (
|
||||
<WorkflowReadonlyActionLogicFunction action={action} />
|
||||
<WorkflowReadonlyActionCode action={action} />
|
||||
) : (
|
||||
<WorkflowEditActionLogicFunction
|
||||
action={action}
|
||||
actionOptions={actionOptions}
|
||||
/>
|
||||
<WorkflowEditActionCode action={action} actionOptions={actionOptions} />
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
+3
-3
@@ -24,7 +24,7 @@ const StyledFullScreenButtonContainer = styled.div`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type WorkflowLogicFunctionCodeEditorProps = {
|
||||
type WorkflowCodeEditorProps = {
|
||||
value?: string;
|
||||
onChange: (value: string) => void;
|
||||
onMount: (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void;
|
||||
@@ -34,7 +34,7 @@ type WorkflowLogicFunctionCodeEditorProps = {
|
||||
onEnterFullScreen?: () => void;
|
||||
};
|
||||
|
||||
export const WorkflowLogicFunctionCodeEditor = ({
|
||||
export const WorkflowCodeEditor = ({
|
||||
value,
|
||||
onChange,
|
||||
onMount,
|
||||
@@ -42,7 +42,7 @@ export const WorkflowLogicFunctionCodeEditor = ({
|
||||
readonly = false,
|
||||
fullScreenMode = false,
|
||||
onEnterFullScreen,
|
||||
}: WorkflowLogicFunctionCodeEditorProps) => {
|
||||
}: WorkflowCodeEditorProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
+9
-9
@@ -25,8 +25,8 @@ import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotke
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowEditActionLogicFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunctionFields';
|
||||
import { WorkflowLogicFunctionCodeEditor } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowLogicFunctionCodeEditor';
|
||||
import { WorkflowCodeEditor } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowCodeEditor';
|
||||
import { WorkflowEditActionCodeFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields';
|
||||
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
|
||||
import {
|
||||
type LogicFunctionTestData,
|
||||
@@ -77,7 +77,7 @@ const StyledFullScreenCodeEditorContainer = styled.div`
|
||||
min-height: 0;
|
||||
`;
|
||||
|
||||
type WorkflowEditActionLogicFunctionProps = {
|
||||
type WorkflowEditActionCodeProps = {
|
||||
action: WorkflowCodeAction;
|
||||
actionOptions:
|
||||
| {
|
||||
@@ -93,10 +93,10 @@ type LogicFunctionInputFormData = {
|
||||
[field: string]: string | LogicFunctionInputFormData;
|
||||
};
|
||||
|
||||
export const WorkflowEditActionLogicFunction = ({
|
||||
export const WorkflowEditActionCode = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionLogicFunctionProps) => {
|
||||
}: WorkflowEditActionCodeProps) => {
|
||||
const { t } = useLingui();
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
@@ -391,7 +391,7 @@ export const WorkflowEditActionLogicFunction = ({
|
||||
|
||||
const fullScreenOverlay = renderFullScreenModal(
|
||||
<div data-globally-prevent-click-outside="true">
|
||||
<WorkflowEditActionLogicFunctionFields
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={functionInput}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
onInputChange={handleInputChange}
|
||||
@@ -428,13 +428,13 @@ export const WorkflowEditActionLogicFunction = ({
|
||||
<WorkflowStepBody>
|
||||
{activeTabId === WorkflowLogicFunctionTabId.CODE && (
|
||||
<>
|
||||
<WorkflowEditActionLogicFunctionFields
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={functionInput}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
onInputChange={handleInputChange}
|
||||
readonly={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowLogicFunctionCodeEditor
|
||||
<WorkflowCodeEditor
|
||||
value={indexFileContent}
|
||||
onChange={handleCodeChange}
|
||||
onMount={handleEditorDidMount}
|
||||
@@ -452,7 +452,7 @@ export const WorkflowEditActionLogicFunction = ({
|
||||
)}
|
||||
{activeTabId === WorkflowLogicFunctionTabId.TEST && (
|
||||
<>
|
||||
<WorkflowEditActionLogicFunctionFields
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={logicFunctionTestData.input}
|
||||
onInputChange={handleTestInputChange}
|
||||
readonly={actionOptions.readonly}
|
||||
+11
-8
@@ -3,38 +3,40 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
|
||||
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { type FunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/types/FunctionInput';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isObject } from '@sniptt/guards';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
const StyledContainer = styled.div<{ fullWidth?: boolean }>`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
min-width: ${({ fullWidth }) => (fullWidth ? '100%' : '200px')};
|
||||
}
|
||||
`;
|
||||
|
||||
type WorkflowEditActionLogicFunctionFieldsProps = {
|
||||
type WorkflowEditActionCodeFieldsProps = {
|
||||
functionInput: FunctionInput;
|
||||
path?: string[];
|
||||
readonly?: boolean;
|
||||
onInputChange?: (value: any, path: string[]) => void | Promise<void>;
|
||||
VariablePicker?: VariablePickerComponent;
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
export const WorkflowEditActionLogicFunctionFields = ({
|
||||
export const WorkflowEditActionCodeFields = ({
|
||||
functionInput,
|
||||
path = [],
|
||||
readonly,
|
||||
onInputChange,
|
||||
VariablePicker,
|
||||
}: WorkflowEditActionLogicFunctionFieldsProps) => {
|
||||
fullWidth,
|
||||
}: WorkflowEditActionCodeFieldsProps) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledContainer fullWidth={fullWidth}>
|
||||
{Object.entries(functionInput).map(([inputKey, inputValue]) => {
|
||||
const currentPath = [...path, inputKey];
|
||||
const pathKey = currentPath.join('.');
|
||||
@@ -44,12 +46,13 @@ export const WorkflowEditActionLogicFunctionFields = ({
|
||||
<div key={pathKey}>
|
||||
<InputLabel>{inputKey}</InputLabel>
|
||||
<FormNestedFieldInputContainer>
|
||||
<WorkflowEditActionLogicFunctionFields
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={inputValue}
|
||||
path={currentPath}
|
||||
readonly={readonly}
|
||||
onInputChange={onInputChange}
|
||||
VariablePicker={VariablePicker}
|
||||
fullWidth={fullWidth}
|
||||
/>
|
||||
</FormNestedFieldInputContainer>
|
||||
</div>
|
||||
+6
-6
@@ -3,28 +3,28 @@ import { useLogicFunctionUpdateFormState } from '@/settings/logic-functions/hook
|
||||
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
|
||||
import { INDEX_FILE_NAME } from '@/logic-functions/constants/IndexFileName';
|
||||
import { SOURCE_FOLDER_NAME } from '@/logic-functions/constants/SourceFolderName';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowEditActionLogicFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunctionFields';
|
||||
import { WorkflowEditActionCodeFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields';
|
||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers';
|
||||
import styled from '@emotion/styled';
|
||||
import { type Monaco } from '@monaco-editor/react';
|
||||
import { type editor } from 'monaco-editor';
|
||||
import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||
import { CodeEditor } from 'twenty-ui/input';
|
||||
import { SOURCE_FOLDER_NAME } from '@/logic-functions/constants/SourceFolderName';
|
||||
|
||||
const StyledCodeEditorContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
type WorkflowReadonlyActionLogicFunctionProps = {
|
||||
type WorkflowReadonlyActionCodeProps = {
|
||||
action: WorkflowCodeAction;
|
||||
};
|
||||
|
||||
export const WorkflowReadonlyActionLogicFunction = ({
|
||||
export const WorkflowReadonlyActionCode = ({
|
||||
action,
|
||||
}: WorkflowReadonlyActionLogicFunctionProps) => {
|
||||
}: WorkflowReadonlyActionCodeProps) => {
|
||||
const logicFunctionId = action.settings.input.logicFunctionId;
|
||||
const logicFunctionVersion = action.settings.input.logicFunctionVersion;
|
||||
|
||||
@@ -63,7 +63,7 @@ export const WorkflowReadonlyActionLogicFunction = ({
|
||||
return (
|
||||
<>
|
||||
<WorkflowStepBody>
|
||||
<WorkflowEditActionLogicFunctionFields
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={action.settings.input.logicFunctionInput}
|
||||
readonly
|
||||
/>
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowEditActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunction';
|
||||
import { WorkflowEditActionCode } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCode';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { graphql, HttpResponse } from 'msw';
|
||||
import { fn } from 'storybook/test';
|
||||
@@ -75,9 +75,9 @@ const CONFIGURED_ACTION: WorkflowCodeAction = {
|
||||
},
|
||||
};
|
||||
|
||||
const meta: Meta<typeof WorkflowEditActionLogicFunction> = {
|
||||
const meta: Meta<typeof WorkflowEditActionCode> = {
|
||||
title: 'Modules/Workflow/Actions/Code/EditAction',
|
||||
component: WorkflowEditActionLogicFunction,
|
||||
component: WorkflowEditActionCode,
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
@@ -138,7 +138,7 @@ const meta: Meta<typeof WorkflowEditActionLogicFunction> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof WorkflowEditActionLogicFunction>;
|
||||
type Story = StoryObj<typeof WorkflowEditActionCode>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
+12
-9
@@ -2,7 +2,6 @@ import { FormFieldInputContainer } from '@/object-record/record-field/ui/form-ty
|
||||
import { FormFieldInputInnerContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputInnerContainer';
|
||||
import { FormFieldInputRowContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputRowContainer';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
const StyledMessageContainer = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
@@ -40,24 +39,28 @@ const StyledFieldContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const WorkflowFormEmptyMessage = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
export const WorkflowMessage = ({
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
}) => {
|
||||
return (
|
||||
<StyledMessageContainer>
|
||||
<FormFieldInputContainer>
|
||||
<FormFieldInputRowContainer multiline maxHeight={124}>
|
||||
<FormFieldInputInnerContainer
|
||||
formFieldInputInstanceId="empty-form-message"
|
||||
formFieldInputInstanceId="workflow-message"
|
||||
hasRightElement={false}
|
||||
>
|
||||
<StyledFieldContainer>
|
||||
<StyledMessageContentContainer>
|
||||
<StyledMessageTitle data-testid="empty-form-message-title">
|
||||
{t`Add inputs to your form`}
|
||||
<StyledMessageTitle data-testid="workflow-message-title">
|
||||
{title}
|
||||
</StyledMessageTitle>
|
||||
<StyledMessageDescription data-testid="empty-form-message-description">
|
||||
{t`Click on "Add Field" below to add the first input to your form. The form will pop up on the user's screen when the workflow is launched from a manual trigger. For other types of triggers, it will be displayed in the Workflow run record page.`}
|
||||
<StyledMessageDescription data-testid="workflow-message-description">
|
||||
{description}
|
||||
</StyledMessageDescription>
|
||||
</StyledMessageContentContainer>
|
||||
</StyledFieldContainer>
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
import { WorkflowFormEmptyMessage } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormEmptyMessage';
|
||||
import { WorkflowMessage } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowMessage';
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { expect, within } from 'storybook/test';
|
||||
|
||||
const meta: Meta<typeof WorkflowFormEmptyMessage> = {
|
||||
title: 'Modules/Workflow/Actions/Form/WorkflowFormEmptyMessage',
|
||||
component: WorkflowFormEmptyMessage,
|
||||
const meta: Meta<typeof WorkflowMessage> = {
|
||||
title: 'Modules/Workflow/Actions/Form/WorkflowMessage',
|
||||
component: WorkflowMessage,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
@@ -12,17 +12,17 @@ const meta: Meta<typeof WorkflowFormEmptyMessage> = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof WorkflowFormEmptyMessage>;
|
||||
type Story = StoryObj<typeof WorkflowMessage>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const messageTitleContainer = await canvas.findByTestId(
|
||||
'empty-form-message-title',
|
||||
'workflow-message-title',
|
||||
);
|
||||
const messageDescriptionContainer = await canvas.findByTestId(
|
||||
'empty-form-message-description',
|
||||
'workflow-message-description',
|
||||
);
|
||||
|
||||
expect(messageTitleContainer).toBeVisible();
|
||||
+5
-2
@@ -8,8 +8,8 @@ import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableLi
|
||||
import { type WorkflowFormAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
|
||||
import { WorkflowMessage } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowMessage';
|
||||
import { WorkflowEditActionFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFieldSettings';
|
||||
import { WorkflowFormEmptyMessage } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormEmptyMessage';
|
||||
import { type WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
|
||||
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
@@ -214,7 +214,10 @@ export const WorkflowEditActionFormBuilder = ({
|
||||
<>
|
||||
<StyledWorkflowStepBody>
|
||||
{formData.length === 0 && (
|
||||
<WorkflowFormEmptyMessage data-testid="empty-form-message" />
|
||||
<WorkflowMessage
|
||||
title={t`Add inputs to your form`}
|
||||
description={t`Click on "Add Field" below to add the first input to your form. The form will pop up on the user's screen when the workflow is launched from a manual trigger. For other types of triggers, it will be displayed in the Workflow run record page.`}
|
||||
/>
|
||||
)}
|
||||
<DraggableList
|
||||
onDragEnd={handleDragEnd}
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ export const EmptyForm: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const messageContainer = await canvas.findByTestId(
|
||||
'empty-form-message-title',
|
||||
'workflow-message-title',
|
||||
);
|
||||
|
||||
expect(messageContainer).toBeVisible();
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import { getDefaultFunctionInputFromInputSchema } from '@/logic-functions/utils/getDefaultFunctionInputFromInputSchema';
|
||||
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
|
||||
import { useGetOneLogicFunction } from '@/settings/logic-functions/hooks/useGetOneLogicFunction';
|
||||
import { type WorkflowLogicFunctionAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
|
||||
import { WorkflowEditActionCodeFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields';
|
||||
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue';
|
||||
import { WorkflowMessage } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowMessage';
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isObject } from '@sniptt/guards';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type WorkflowEditActionLogicFunctionProps = {
|
||||
action: WorkflowLogicFunctionAction;
|
||||
actionOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
}
|
||||
| {
|
||||
readonly?: false;
|
||||
onActionUpdate: (action: WorkflowLogicFunctionAction) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export const WorkflowEditActionLogicFunction = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionLogicFunctionProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const logicFunctionId = action.settings.input.logicFunctionId;
|
||||
|
||||
const { logicFunction, loading } = useGetOneLogicFunction({
|
||||
id: logicFunctionId,
|
||||
});
|
||||
|
||||
const functionInput = useMemo(() => {
|
||||
const toolInputSchema = logicFunction?.toolInputSchema;
|
||||
|
||||
if (!isDefined(toolInputSchema)) {
|
||||
return action.settings.input.logicFunctionInput ?? {};
|
||||
}
|
||||
|
||||
const schemaArray = Array.isArray(toolInputSchema)
|
||||
? toolInputSchema
|
||||
: [toolInputSchema];
|
||||
|
||||
const defaultInput = getDefaultFunctionInputFromInputSchema(schemaArray)[0];
|
||||
|
||||
if (!isObject(defaultInput)) {
|
||||
return action.settings.input.logicFunctionInput ?? {};
|
||||
}
|
||||
|
||||
return mergeDefaultFunctionInputAndFunctionInput({
|
||||
newInput: defaultInput,
|
||||
oldInput: action.settings.input.logicFunctionInput ?? {},
|
||||
});
|
||||
}, [
|
||||
logicFunction?.toolInputSchema,
|
||||
action.settings.input.logicFunctionInput,
|
||||
]);
|
||||
|
||||
const updateAction = useDebouncedCallback(
|
||||
(actionUpdate: Partial<WorkflowLogicFunctionAction>) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
...actionUpdate,
|
||||
});
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
const handleInputChange = (value: unknown, path: string[]) => {
|
||||
const updatedFunctionInput = setNestedValue(functionInput, path, value);
|
||||
|
||||
updateAction({
|
||||
settings: {
|
||||
...action.settings,
|
||||
input: {
|
||||
...action.settings.input,
|
||||
logicFunctionInput: updatedFunctionInput,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasInputFields = Object.keys(functionInput).length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkflowStepBody>
|
||||
<StyledContainer>
|
||||
{hasInputFields ? (
|
||||
<WorkflowEditActionCodeFields
|
||||
functionInput={functionInput}
|
||||
readonly={actionOptions.readonly}
|
||||
onInputChange={handleInputChange}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
fullWidth
|
||||
/>
|
||||
) : (
|
||||
<WorkflowMessage
|
||||
title={t`No input fields for this action`}
|
||||
description={t`You can see the function logic in your application settings.`}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
</WorkflowStepBody>
|
||||
{!actionOptions.readonly && <WorkflowStepFooter stepId={action.id} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+4
@@ -27,5 +27,9 @@ export const getActionHeaderTypeOrThrow = (actionType: WorkflowActionType) => {
|
||||
return msg`AI`;
|
||||
}
|
||||
|
||||
if (actionType === 'LOGIC_FUNCTION') {
|
||||
return msg`Application`;
|
||||
}
|
||||
|
||||
return msg`Action`;
|
||||
};
|
||||
|
||||
+2
@@ -19,6 +19,8 @@ export const getActionIcon = (actionType: WorkflowActionType) => {
|
||||
case 'HTTP_REQUEST':
|
||||
case 'SEND_EMAIL':
|
||||
return CORE_ACTIONS.find((item) => item.type === actionType)?.icon;
|
||||
case 'LOGIC_FUNCTION':
|
||||
return 'IconFunction';
|
||||
case 'FORM':
|
||||
return HUMAN_INPUT_ACTIONS.find((item) => item.type === actionType)?.icon;
|
||||
case 'ITERATOR':
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ export const getActionIconColorOrThrow = ({
|
||||
}) => {
|
||||
switch (actionType) {
|
||||
case 'CODE':
|
||||
case 'LOGIC_FUNCTION':
|
||||
case 'HTTP_REQUEST':
|
||||
case 'SEND_EMAIL':
|
||||
return theme.color.red;
|
||||
|
||||
Reference in New Issue
Block a user