8726 workflow add a test button in workflow code step (#9016)

- add test button to workflow code step
- add test tab to workflow code step


https://github.com/user-attachments/assets/e180a827-7321-49a2-8026-88490c557da2



![image](https://github.com/user-attachments/assets/cacbd756-de3f-4141-a84c-8e1853f6556b)

![image](https://github.com/user-attachments/assets/ee170d81-8a22-4178-bd6d-11a0e8c73365)
This commit is contained in:
martmull
2024-12-13 11:16:29 +01:00
committed by GitHub
parent 07aaf0801c
commit b10d831371
95 changed files with 1537 additions and 1611 deletions

View File

@ -1,39 +0,0 @@
import { useRecoilValue } from 'recoil';
import {
DEFAULT_OUTPUT_VALUE,
settingsServerlessFunctionOutputState,
} from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState';
import styled from '@emotion/styled';
import { IconSquareRoundedCheck } from 'twenty-ui';
import { useTheme } from '@emotion/react';
import { ServerlessFunctionExecutionStatus } from '~/generated-metadata/graphql';
const StyledOutput = styled.div<{ status?: ServerlessFunctionExecutionStatus }>`
align-items: center;
gap: ${({ theme }) => theme.spacing(1)};
color: ${({ theme, status }) =>
status === ServerlessFunctionExecutionStatus.Success
? theme.color.turquoise
: theme.color.red};
display: flex;
`;
export const SettingsServerlessFunctionsOutputMetadataInfo = () => {
const theme = useTheme();
const settingsServerlessFunctionOutput = useRecoilValue(
settingsServerlessFunctionOutputState,
);
return settingsServerlessFunctionOutput.data === DEFAULT_OUTPUT_VALUE ? (
'Output'
) : (
<StyledOutput status={settingsServerlessFunctionOutput.status}>
<IconSquareRoundedCheck size={theme.icon.size.md} />
{settingsServerlessFunctionOutput.status ===
ServerlessFunctionExecutionStatus.Success
? '200 OK'
: '500 Error'}
{' - '}
{settingsServerlessFunctionOutput.duration}ms
</StyledOutput>
);
};

View File

@ -11,7 +11,6 @@ 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 {
Button,
@ -47,10 +46,9 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
onChange: (filePath: string, value: string) => void;
setIsCodeValid: (isCodeValid: boolean) => void;
}) => {
const { activeTabIdState } = useTabList(
const { activeTabId } = useTabList(
SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
);
const activeTabId = useRecoilValue(activeTabIdState);
const TestButton = (
<Button
title="Test"

View File

@ -7,20 +7,17 @@ import {
Section,
} from 'twenty-ui';
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
import { SettingsServerlessFunctionsOutputMetadataInfo } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsOutputMetadataInfo';
import { settingsServerlessFunctionCodeEditorOutputParamsState } from '@/settings/serverless-functions/states/settingsServerlessFunctionCodeEditorOutputParamsState';
import { settingsServerlessFunctionInputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionInputState';
import { settingsServerlessFunctionOutputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState';
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import styled from '@emotion/styled';
import { useNavigate } from 'react-router-dom';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
const StyledInputsContainer = styled.div`
display: flex;
@ -28,24 +25,27 @@ const StyledInputsContainer = styled.div`
gap: ${({ theme }) => theme.spacing(4)};
`;
const StyledCodeEditorContainer = styled.div`
display: flex;
flex-direction: column;
`;
export const SettingsServerlessFunctionTestTab = ({
handleExecute,
serverlessFunctionId,
}: {
handleExecute: () => void;
serverlessFunctionId: string;
}) => {
const settingsServerlessFunctionCodeEditorOutputParams = useRecoilValue(
settingsServerlessFunctionCodeEditorOutputParamsState,
);
const settingsServerlessFunctionOutput = useRecoilValue(
settingsServerlessFunctionOutputState,
);
const [settingsServerlessFunctionInput, setSettingsServerlessFunctionInput] =
useRecoilState(settingsServerlessFunctionInputState);
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const result =
settingsServerlessFunctionOutput.data ||
settingsServerlessFunctionOutput.error ||
'';
const onChange = (newInput: string) => {
setServerlessFunctionTestData((prev) => ({
...prev,
input: JSON.parse(newInput),
}));
};
const navigate = useNavigate();
useHotkeyScopeOnMount(
@ -67,7 +67,7 @@ export const SettingsServerlessFunctionTestTab = ({
description='Insert a JSON input, then press "Run" to test your function.'
/>
<StyledInputsContainer>
<div>
<StyledCodeEditorContainer>
<CoreEditorHeader
title={'Input'}
rightNodes={[
@ -82,26 +82,16 @@ export const SettingsServerlessFunctionTestTab = ({
]}
/>
<CodeEditor
value={settingsServerlessFunctionInput}
value={JSON.stringify(serverlessFunctionTestData.input, null, 4)}
language="json"
height={200}
onChange={setSettingsServerlessFunctionInput}
onChange={onChange}
withHeader
/>
</div>
<div>
<CoreEditorHeader
leftNodes={[<SettingsServerlessFunctionsOutputMetadataInfo />]}
rightNodes={[<LightCopyIconButton copyText={result} />]}
/>
<CodeEditor
value={result}
language={settingsServerlessFunctionCodeEditorOutputParams.language}
height={settingsServerlessFunctionCodeEditorOutputParams.height}
options={{ readOnly: true, domReadOnly: true }}
withHeader
/>
</div>
</StyledCodeEditorContainer>
<ServerlessFunctionExecutionResult
serverlessFunctionTestData={serverlessFunctionTestData}
/>
</StyledInputsContainer>
</Section>
);

View File

@ -1,28 +0,0 @@
import React, { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import {
DEFAULT_OUTPUT_VALUE,
settingsServerlessFunctionOutputState,
} from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState';
import { settingsServerlessFunctionCodeEditorOutputParamsState } from '@/settings/serverless-functions/states/settingsServerlessFunctionCodeEditorOutputParamsState';
export const SettingsServerlessFunctionTestTabEffect = () => {
const settingsServerlessFunctionOutput = useRecoilValue(
settingsServerlessFunctionOutputState,
);
const setSettingsServerlessFunctionCodeEditorOutputParams = useSetRecoilState(
settingsServerlessFunctionCodeEditorOutputParamsState,
);
useEffect(() => {
if (settingsServerlessFunctionOutput.data !== DEFAULT_OUTPUT_VALUE) {
setSettingsServerlessFunctionCodeEditorOutputParams({
language: 'json',
height: 300,
});
}
}, [
settingsServerlessFunctionOutput.data,
setSettingsServerlessFunctionCodeEditorOutputParams,
]);
return <></>;
};

View File

@ -2,6 +2,10 @@ import { useGetOneServerlessFunction } from '@/settings/serverless-functions/hoo
import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode';
import { Dispatch, SetStateAction, useState } from 'react';
import { FindOneServerlessFunctionSourceCodeQuery } from '~/generated-metadata/graphql';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
import { useSetRecoilState } from 'recoil';
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
export type ServerlessFunctionNewFormValues = {
name: string;
@ -29,6 +33,10 @@ export const useServerlessFunctionUpdateFormState = (
code: undefined,
});
const setServerlessFunctionTestData = useSetRecoilState(
serverlessFunctionTestDataFamilyState(serverlessFunctionId),
);
const { serverlessFunction } = useGetOneServerlessFunction({
id: serverlessFunctionId,
});
@ -46,6 +54,12 @@ export const useServerlessFunctionUpdateFormState = (
...prevState,
...newState,
}));
const sourceCode =
data?.getServerlessFunctionSourceCode?.[INDEX_FILE_PATH];
setServerlessFunctionTestData((prev) => ({
...prev,
input: getFunctionInputFromSourceCode(sourceCode),
}));
},
});

View File

@ -1,7 +0,0 @@
import { createState } from 'twenty-ui';
export const settingsServerlessFunctionCodeEditorOutputParamsState =
createState<{ language: string; height: number }>({
key: 'settingsServerlessFunctionCodeEditorOutputParamsState',
defaultValue: { language: 'plaintext', height: 64 },
});

View File

@ -1,6 +0,0 @@
import { createState } from 'twenty-ui';
export const settingsServerlessFunctionInputState = createState<string>({
key: 'settingsServerlessFunctionInputState',
defaultValue: '{}',
});

View File

@ -1,18 +0,0 @@
import { createState } from 'twenty-ui';
import { ServerlessFunctionExecutionStatus } from '~/generated-metadata/graphql';
type settingsServerlessFunctionOutput = {
data?: string;
duration?: number;
status?: ServerlessFunctionExecutionStatus;
error?: string;
};
export const DEFAULT_OUTPUT_VALUE =
'Enter an input above then press "run Function"';
export const settingsServerlessFunctionOutputState =
createState<settingsServerlessFunctionOutput>({
key: 'settingsServerlessFunctionOutputState',
defaultValue: { data: DEFAULT_OUTPUT_VALUE },
});