Add Workflow Run show page (#7719)
In this PR: - Display a workflow version visualizer for the version of the workflow the run was executed on. - Display the output of the run as code. https://github.com/user-attachments/assets/d617300a-bff4-4328-a35c-291dc86d81cf
This commit is contained in:
committed by
GitHub
parent
b914182b78
commit
e7eeb3b820
@ -0,0 +1,130 @@
|
||||
import { SettingsServerlessFunctionCodeEditorContainer } from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditorContainer';
|
||||
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
|
||||
import { CodeEditor } from '@/ui/input/code-editor/components/CodeEditor';
|
||||
import { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import dotenv from 'dotenv';
|
||||
import { editor, MarkerSeverity } from 'monaco-editor';
|
||||
import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export type File = {
|
||||
language: string;
|
||||
content: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
type SettingsServerlessFunctionCodeEditorProps = Omit<
|
||||
EditorProps,
|
||||
'onChange'
|
||||
> & {
|
||||
currentFilePath: string;
|
||||
files: File[];
|
||||
onChange: (value: string) => void;
|
||||
setIsCodeValid: (isCodeValid: boolean) => void;
|
||||
};
|
||||
|
||||
export const SettingsServerlessFunctionCodeEditor = ({
|
||||
currentFilePath,
|
||||
files,
|
||||
onChange,
|
||||
setIsCodeValid,
|
||||
height = 450,
|
||||
options = undefined,
|
||||
}: SettingsServerlessFunctionCodeEditorProps) => {
|
||||
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,
|
||||
) => {
|
||||
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),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
|
||||
moduleResolution:
|
||||
monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||
baseUrl: 'file:///src',
|
||||
paths: {
|
||||
'src/*': ['file:///src/*'],
|
||||
},
|
||||
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) && (
|
||||
<SettingsServerlessFunctionCodeEditorContainer>
|
||||
<CodeEditor
|
||||
height={height}
|
||||
value={currentFile.content}
|
||||
language={currentFile.language}
|
||||
onMount={handleEditorDidMount}
|
||||
onChange={onChange}
|
||||
onValidate={handleEditorValidation}
|
||||
options={options}
|
||||
/>
|
||||
</SettingsServerlessFunctionCodeEditorContainer>
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledEditorContainer = styled.div`
|
||||
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 const SettingsServerlessFunctionCodeEditorContainer =
|
||||
StyledEditorContainer;
|
||||
@ -1,20 +1,23 @@
|
||||
import {
|
||||
File,
|
||||
SettingsServerlessFunctionCodeEditor,
|
||||
} from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor';
|
||||
import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId';
|
||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { CodeEditor, File } from '@/ui/input/code-editor/components/CodeEditor';
|
||||
import { CoreEditorHeader } from '@/ui/input/code-editor/components/CodeEditorHeader';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import styled from '@emotion/styled';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { H2Title, IconGitCommit, IconPlayerPlay, IconRestore } from 'twenty-ui';
|
||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
||||
import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
const StyledTabList = styled(TabList)`
|
||||
border-bottom: none;
|
||||
@ -107,7 +110,7 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
|
||||
rightNodes={[ResetButton, PublishButton, TestButton]}
|
||||
/>
|
||||
{activeTabId && (
|
||||
<CodeEditor
|
||||
<SettingsServerlessFunctionCodeEditor
|
||||
files={files}
|
||||
currentFilePath={activeTabId}
|
||||
onChange={(newCodeValue) => onChange(activeTabId, newCodeValue)}
|
||||
|
||||
@ -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 = ({
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
<CodeEditor
|
||||
files={[
|
||||
{
|
||||
content: settingsServerlessFunctionInput,
|
||||
language: 'json',
|
||||
path: 'input.json',
|
||||
},
|
||||
]}
|
||||
currentFilePath={'input.json'}
|
||||
height={200}
|
||||
onChange={setSettingsServerlessFunctionInput}
|
||||
/>
|
||||
<SettingsServerlessFunctionCodeEditorContainer>
|
||||
<CodeEditor
|
||||
value={settingsServerlessFunctionInput}
|
||||
language="json"
|
||||
height={200}
|
||||
onChange={setSettingsServerlessFunctionInput}
|
||||
/>
|
||||
</SettingsServerlessFunctionCodeEditorContainer>
|
||||
</div>
|
||||
<div>
|
||||
<CoreEditorHeader
|
||||
leftNodes={[<SettingsServerlessFunctionsOutputMetadataInfo />]}
|
||||
rightNodes={[<LightCopyIconButton copyText={result} />]}
|
||||
/>
|
||||
<CodeEditor
|
||||
files={[
|
||||
{
|
||||
content: result,
|
||||
language:
|
||||
settingsServerlessFunctionCodeEditorOutputParams.language,
|
||||
path: 'result.any',
|
||||
},
|
||||
]}
|
||||
currentFilePath={'result.any'}
|
||||
height={settingsServerlessFunctionCodeEditorOutputParams.height}
|
||||
options={{ readOnly: true, domReadOnly: true }}
|
||||
/>
|
||||
<SettingsServerlessFunctionCodeEditorContainer>
|
||||
<CodeEditor
|
||||
value={result}
|
||||
language={
|
||||
settingsServerlessFunctionCodeEditorOutputParams.language
|
||||
}
|
||||
height={settingsServerlessFunctionCodeEditorOutputParams.height}
|
||||
options={{ readOnly: true, domReadOnly: true }}
|
||||
/>
|
||||
</SettingsServerlessFunctionCodeEditorContainer>
|
||||
</div>
|
||||
</StyledInputsContainer>
|
||||
</Section>
|
||||
|
||||
Reference in New Issue
Block a user