onChange(activeTabId, newCodeValue)}
diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx
index b2d54cbc03f9..54a565215d4b 100644
--- a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx
+++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab.tsx
@@ -2,6 +2,7 @@ import { Section } from '@/ui/layout/section/components/Section';
import { H2Title, IconPlayerPlay } from 'twenty-ui';
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
+import { SettingsServerlessFunctionCodeEditorContainer } from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditorContainer';
import { SettingsServerlessFunctionsOutputMetadataInfo } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsOutputMetadataInfo';
import { settingsServerlessFunctionCodeEditorOutputParamsState } from '@/settings/serverless-functions/states/settingsServerlessFunctionCodeEditorOutputParamsState';
import { settingsServerlessFunctionInputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionInputState';
@@ -78,37 +79,30 @@ export const SettingsServerlessFunctionTestTab = ({
/>,
]}
/>
-
+
+
+
]}
rightNodes={[]}
/>
-
+
+
+
diff --git a/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx b/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx
index 723b04a9f69b..dc846b9c0834 100644
--- a/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx
+++ b/packages/twenty-front/src/modules/ui/input/code-editor/components/CodeEditor.tsx
@@ -1,148 +1,51 @@
-import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
import { codeEditorTheme } from '@/ui/input/code-editor/utils/codeEditorTheme';
import { useTheme } from '@emotion/react';
-import styled from '@emotion/styled';
-import Editor, { EditorProps, Monaco } from '@monaco-editor/react';
-import dotenv from 'dotenv';
-import { MarkerSeverity, editor } from 'monaco-editor';
-import { AutoTypings } from 'monaco-editor-auto-typings';
-import { isDefined } from '~/utils/isDefined';
-
-const StyledEditor = styled(Editor)`
- border: 1px solid ${({ theme }) => theme.border.color.medium};
- border-top: none;
- border-radius: 0 0 ${({ theme }) => theme.border.radius.sm}
- ${({ theme }) => theme.border.radius.sm};
-`;
-
-export type File = {
- language: string;
- content: string;
- path: string;
-};
+import Editor, { EditorProps } from '@monaco-editor/react';
+import { isDefined } from 'twenty-ui';
type CodeEditorProps = Omit & {
- currentFilePath: string;
- files: File[];
onChange?: (value: string) => void;
- setIsCodeValid?: (isCodeValid: boolean) => void;
};
export const CodeEditor = ({
- currentFilePath,
- files,
+ value,
+ language,
+ onMount,
onChange,
- setIsCodeValid,
+ onValidate,
height = 450,
- options = undefined,
+ options,
}: CodeEditorProps) => {
const theme = useTheme();
- const { availablePackages } = useGetAvailablePackages();
-
- const currentFile = files.find((file) => file.path === currentFilePath);
- const environmentVariablesFile = files.find((file) => file.path === '.env');
-
- const handleEditorDidMount = async (
- editor: editor.IStandaloneCodeEditor,
- monaco: Monaco,
- ) => {
- monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme));
- monaco.editor.setTheme('codeEditorTheme');
-
- if (files.length > 1) {
- files.forEach((file) => {
- const model = monaco.editor.getModel(monaco.Uri.file(file.path));
- if (!isDefined(model)) {
- monaco.editor.createModel(
- file.content,
- file.language,
- monaco.Uri.file(file.path),
- );
+ return (
+ {
+ monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme));
+ monaco.editor.setTheme('codeEditorTheme');
+
+ onMount?.(editor, monaco);
+ }}
+ onChange={(value) => {
+ if (isDefined(value)) {
+ onChange?.(value);
}
- });
-
- monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
- ...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
- moduleResolution:
- monaco.languages.typescript.ModuleResolutionKind.NodeJs,
- baseUrl: 'file:///src',
- paths: {
- 'src/*': ['file:///src/*'],
+ }}
+ onValidate={onValidate}
+ options={{
+ overviewRulerLanes: 0,
+ scrollbar: {
+ vertical: 'hidden',
+ horizontal: 'hidden',
},
- allowSyntheticDefaultImports: true,
- esModuleInterop: true,
- noEmit: true,
- target: monaco.languages.typescript.ScriptTarget.ESNext,
- });
-
- if (isDefined(environmentVariablesFile)) {
- const environmentVariables = dotenv.parse(
- environmentVariablesFile.content,
- );
-
- const environmentDefinition = `
- declare namespace NodeJS {
- interface ProcessEnv {
- ${Object.keys(environmentVariables)
- .map((key) => `${key}: string;`)
- .join('\n')}
- }
- }
-
- declare const process: {
- env: NodeJS.ProcessEnv;
- };
- `;
-
- monaco.languages.typescript.typescriptDefaults.addExtraLib(
- environmentDefinition,
- 'ts:process-env.d.ts',
- );
- }
-
- await AutoTypings.create(editor, {
- monaco,
- preloadPackages: true,
- onlySpecifiedPackages: true,
- versions: availablePackages,
- debounceDuration: 0,
- });
- }
- };
-
- const handleEditorValidation = (markers: editor.IMarker[]) => {
- for (const marker of markers) {
- if (marker.severity === MarkerSeverity.Error) {
- setIsCodeValid?.(false);
- return;
- }
- }
- setIsCodeValid?.(true);
- };
-
- return (
- isDefined(currentFile) &&
- isDefined(availablePackages) && (
- value && onChange?.(value)}
- onValidate={handleEditorValidation}
- options={{
- ...options,
- overviewRulerLanes: 0,
- scrollbar: {
- vertical: 'hidden',
- horizontal: 'hidden',
- },
- minimap: {
- enabled: false,
- },
- }}
- />
- )
+ minimap: {
+ enabled: false,
+ },
+ ...options,
+ }}
+ />
);
};
diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx
index 04a80a1c827a..7b75faec18e5 100644
--- a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx
@@ -25,6 +25,7 @@ export type ConfirmationModalProps = {
confirmationPlaceholder?: string;
confirmationValue?: string;
confirmButtonAccent?: ButtonAccent;
+ AdditionalButtons?: React.ReactNode;
};
const StyledConfirmationModal = styled(Modal)`
@@ -33,7 +34,8 @@ const StyledConfirmationModal = styled(Modal)`
height: auto;
`;
-const StyledCenteredButton = styled(Button)`
+export const StyledCenteredButton = styled(Button)`
+ box-sizing: border-box;
justify-content: center;
margin-top: ${({ theme }) => theme.spacing(2)};
`;
@@ -68,6 +70,7 @@ export const ConfirmationModal = ({
confirmationValue,
confirmationPlaceholder,
confirmButtonAccent = 'danger',
+ AdditionalButtons,
}: ConfirmationModalProps) => {
const [inputConfirmationValue, setInputConfirmationValue] =
useState('');
@@ -138,6 +141,9 @@ export const ConfirmationModal = ({
title="Cancel"
fullWidth
/>
+
+ {AdditionalButtons}
+
>
);
+ case 'workflowRunFlow':
+ return (
+
+ );
+ case 'workflowRunOutput':
+ return (
+
+ );
default:
return <>>;
}
diff --git a/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
new file mode 100644
index 000000000000..d66b4629a732
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
@@ -0,0 +1,62 @@
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
+import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
+import {
+ ConfirmationModal,
+ StyledCenteredButton,
+} from '@/ui/layout/modal/components/ConfirmationModal';
+import { openOverrideWorkflowDraftConfirmationModalState } from '@/workflow/states/openOverrideWorkflowDraftConfirmationModalState';
+import { WorkflowVersion } from '@/workflow/types/Workflow';
+import { useRecoilState } from 'recoil';
+
+export const OverrideWorkflowDraftConfirmationModal = ({
+ draftWorkflowVersionId,
+ workflowVersionUpdateInput,
+}: {
+ draftWorkflowVersionId: string;
+ workflowVersionUpdateInput: Pick;
+}) => {
+ const [
+ openOverrideWorkflowDraftConfirmationModal,
+ setOpenOverrideWorkflowDraftConfirmationModal,
+ ] = useRecoilState(openOverrideWorkflowDraftConfirmationModalState);
+
+ const { updateOneRecord: updateOneWorkflowVersion } =
+ useUpdateOneRecord({
+ objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
+ });
+
+ const handleOverrideDraft = async () => {
+ await updateOneWorkflowVersion({
+ idToUpdate: draftWorkflowVersionId,
+ updateOneRecordInput: workflowVersionUpdateInput,
+ });
+ };
+
+ return (
+ <>
+ {
+ setOpenOverrideWorkflowDraftConfirmationModal(false);
+ }}
+ variant="secondary"
+ title="Go to Draft"
+ fullWidth
+ />
+ }
+ />
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx
index 79cbe7c27f77..e796c4a88cae 100644
--- a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx
+++ b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx
@@ -1,13 +1,15 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
-import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { Button } from '@/ui/input/button/components/Button';
+import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal';
import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion';
import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion';
import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion';
+import { openOverrideWorkflowDraftConfirmationModalState } from '@/workflow/states/openOverrideWorkflowDraftConfirmationModalState';
import { Workflow, WorkflowVersion } from '@/workflow/types/Workflow';
+import { useSetRecoilState } from 'recoil';
import { IconPencil, IconPlayerStop, IconPower, isDefined } from 'twenty-ui';
export const RecordShowPageWorkflowVersionHeader = ({
@@ -46,6 +48,8 @@ export const RecordShowPageWorkflowVersionHeader = ({
skip: !isDefined(workflowVersion),
limit: 1,
});
+ const draftWorkflowVersion: WorkflowVersion | undefined =
+ draftWorkflowVersions[0];
const showUseAsDraftButton =
!loadingDraftWorkflowVersions &&
@@ -57,7 +61,7 @@ export const RecordShowPageWorkflowVersionHeader = ({
workflowVersionRelatedWorkflowQuery.record.lastPublishedVersionId;
const hasAlreadyDraftVersion =
- !loadingDraftWorkflowVersions && draftWorkflowVersions.length > 0;
+ !loadingDraftWorkflowVersions && isDefined(draftWorkflowVersion);
const isWaitingForWorkflowVersion = !isDefined(workflowVersion);
@@ -65,10 +69,9 @@ export const RecordShowPageWorkflowVersionHeader = ({
const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion();
const { createNewWorkflowVersion } = useCreateNewWorkflowVersion();
- const { updateOneRecord: updateOneWorkflowVersion } =
- useUpdateOneRecord({
- objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
- });
+ const setOpenOverrideWorkflowDraftConfirmationModal = useSetRecoilState(
+ openOverrideWorkflowDraftConfirmationModalState,
+ );
return (
<>
@@ -80,13 +83,7 @@ export const RecordShowPageWorkflowVersionHeader = ({
disabled={isWaitingForWorkflowVersion}
onClick={async () => {
if (hasAlreadyDraftVersion) {
- await updateOneWorkflowVersion({
- idToUpdate: draftWorkflowVersions[0].id,
- updateOneRecordInput: {
- trigger: workflowVersion.trigger,
- steps: workflowVersion.steps,
- },
- });
+ setOpenOverrideWorkflowDraftConfirmationModal(true);
} else {
await createNewWorkflowVersion({
workflowId: workflowVersion.workflow.id,
@@ -125,6 +122,16 @@ export const RecordShowPageWorkflowVersionHeader = ({
}}
/>
) : null}
+
+ {isDefined(workflowVersion) && isDefined(draftWorkflowVersion) ? (
+
+ ) : null}
>
);
};
diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowRunOutputVisualizer.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowRunOutputVisualizer.tsx
new file mode 100644
index 000000000000..1a49c030ac60
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/components/WorkflowRunOutputVisualizer.tsx
@@ -0,0 +1,32 @@
+import { CodeEditor } from '@/ui/input/code-editor/components/CodeEditor';
+import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
+import styled from '@emotion/styled';
+import { isDefined } from 'twenty-ui';
+
+const StyledSourceCodeContainer = styled.div`
+ border: 1px solid ${({ theme }) => theme.border.color.medium};
+ border-radius: ${({ theme }) => theme.border.radius.sm};
+ margin: ${({ theme }) => theme.spacing(4)};
+ overflow: hidden;
+`;
+
+export const WorkflowRunOutputVisualizer = ({
+ workflowRunId,
+}: {
+ workflowRunId: string;
+}) => {
+ const workflowRun = useWorkflowRun({ workflowRunId });
+ if (!isDefined(workflowRun)) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/workflow/components/WorkflowRunVersionVisualizer.tsx b/packages/twenty-front/src/modules/workflow/components/WorkflowRunVersionVisualizer.tsx
new file mode 100644
index 000000000000..8d8f265c426c
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/components/WorkflowRunVersionVisualizer.tsx
@@ -0,0 +1,29 @@
+import { WorkflowVersionVisualizer } from '@/workflow/components/WorkflowVersionVisualizer';
+import { WorkflowVersionVisualizerEffect } from '@/workflow/components/WorkflowVersionVisualizerEffect';
+import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
+import { isDefined } from 'twenty-ui';
+
+export const WorkflowRunVersionVisualizer = ({
+ workflowRunId,
+}: {
+ workflowRunId: string;
+}) => {
+ const workflowRun = useWorkflowRun({
+ workflowRunId,
+ });
+ if (!isDefined(workflowRun)) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRun.tsx b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRun.tsx
new file mode 100644
index 000000000000..9bb6fa5642ed
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowRun.tsx
@@ -0,0 +1,16 @@
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
+import { WorkflowRun } from '@/workflow/types/Workflow';
+
+export const useWorkflowRun = ({
+ workflowRunId,
+}: {
+ workflowRunId: string;
+}) => {
+ const { record } = useFindOneRecord({
+ objectNameSingular: CoreObjectNameSingular.WorkflowRun,
+ objectRecordId: workflowRunId,
+ });
+
+ return record;
+};
diff --git a/packages/twenty-front/src/modules/workflow/states/openOverrideWorkflowDraftConfirmationModalState.ts b/packages/twenty-front/src/modules/workflow/states/openOverrideWorkflowDraftConfirmationModalState.ts
new file mode 100644
index 000000000000..1320a964207d
--- /dev/null
+++ b/packages/twenty-front/src/modules/workflow/states/openOverrideWorkflowDraftConfirmationModalState.ts
@@ -0,0 +1,7 @@
+import { createState } from 'twenty-ui';
+
+export const openOverrideWorkflowDraftConfirmationModalState =
+ createState({
+ key: 'openOverrideWorkflowDraftConfirmationModalState',
+ defaultValue: false,
+ });
diff --git a/packages/twenty-front/src/modules/workflow/types/Workflow.ts b/packages/twenty-front/src/modules/workflow/types/Workflow.ts
index 65b2e9a25a15..70e3ab197020 100644
--- a/packages/twenty-front/src/modules/workflow/types/Workflow.ts
+++ b/packages/twenty-front/src/modules/workflow/types/Workflow.ts
@@ -84,6 +84,28 @@ export type WorkflowVersion = {
__typename: 'WorkflowVersion';
};
+type StepRunOutput = {
+ id: string;
+ name: string;
+ type: string;
+ outputs: {
+ attemptCount: number;
+ result: object | undefined;
+ error: string | undefined;
+ }[];
+};
+
+export type WorkflowRunOutput = {
+ steps: Record;
+};
+
+export type WorkflowRun = {
+ __typename: 'WorkflowRun';
+ id: string;
+ workflowVersionId: string;
+ output: WorkflowRunOutput;
+};
+
export type Workflow = {
__typename: 'Workflow';
id: string;
diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
index ffef2fdeda79..5244849f69b1 100644
--- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
+++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
@@ -215,6 +215,7 @@ export {
IconTimelineEvent,
IconTool,
IconTrash,
+ IconPrinter,
IconUnlink,
IconUpload,
IconUser,
diff --git a/packages/twenty-website/public/images/user-guide/fields/deactivate-field.png b/packages/twenty-website/public/images/user-guide/fields/deactivate-field.png
index 03d263622ae3..4b55672d2197 100644
Binary files a/packages/twenty-website/public/images/user-guide/fields/deactivate-field.png and b/packages/twenty-website/public/images/user-guide/fields/deactivate-field.png differ