Add workflow runner (#6458)
- add workflow runner module
- add an endpoint to trigger a workflow via api
- improve error handling for serverless functions
## Testing
- create 2 serverless functions
- create a workflow
- create this workflow Version
```
{
"type": "MANUAL",
"input": {"b": "bb"},
"nextAction": {
"name": "step_1",
"displayName": "Code",
"type": "CODE",
"valid": true,
"settings": {
"serverlessFunctionId": "Serverless function 1 Id",
"errorHandlingOptions": {
"retryOnFailure": {
"value": false
},
"continueOnFailure": {
"value": false
}
}
},
"nextAction": {
"name": "step_1",
"displayName": "Code",
"type": "CODE",
"valid": true,
"settings": {
"serverlessFunctionId": "Serverless function 1 Id",
"errorHandlingOptions": {
"retryOnFailure": {
"value": false
},
"continueOnFailure": {
"value": false
}
}
},
"nextAction": {
"name": "step_1",
"displayName": "Code",
"type": "CODE",
"valid": true,
"settings": {
"serverlessFunctionId": "Serverless function 2 Id",
"errorHandlingOptions": {
"retryOnFailure": {
"value": false
},
"continueOnFailure": {
"value": false
}
}
}
}
}
}
}
`
``
- call
```
mutation Trigger {
triggerWorkflow(workflowVersionId: "WORKFLOW_VERSION_ID") {
result
}
}
```
- try when errors are injected in serverless function
This commit is contained in:
@ -0,0 +1,39 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -10,6 +10,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { settingsServerlessFunctionOutputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState';
|
||||
import { settingsServerlessFunctionInputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionInputState';
|
||||
import { settingsServerlessFunctionCodeEditorOutputParamsState } from '@/settings/serverless-functions/states/settingsServerlessFunctionCodeEditorOutputParamsState';
|
||||
import { SettingsServerlessFunctionsOutputMetadataInfo } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsOutputMetadataInfo';
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
display: flex;
|
||||
@ -31,27 +32,32 @@ export const SettingsServerlessFunctionTestTab = ({
|
||||
const [settingsServerlessFunctionInput, setSettingsServerlessFunctionInput] =
|
||||
useRecoilState(settingsServerlessFunctionInputState);
|
||||
|
||||
const InputHeaderButton = (
|
||||
<Button
|
||||
title="Run Function"
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconPlayerPlay}
|
||||
onClick={handleExecute}
|
||||
const result =
|
||||
settingsServerlessFunctionOutput.data ||
|
||||
settingsServerlessFunctionOutput.error ||
|
||||
'';
|
||||
|
||||
const InputHeader = (
|
||||
<CoreEditorHeader
|
||||
title={'Input'}
|
||||
rightNodes={[
|
||||
<Button
|
||||
title="Run Function"
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconPlayerPlay}
|
||||
onClick={handleExecute}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const InputHeader = (
|
||||
<CoreEditorHeader title={'Input'} rightNodes={[InputHeaderButton]} />
|
||||
);
|
||||
|
||||
const OutputHeaderButton = (
|
||||
<LightCopyIconButton copyText={settingsServerlessFunctionOutput} />
|
||||
);
|
||||
|
||||
const OutputHeader = (
|
||||
<CoreEditorHeader title={'Output'} rightNodes={[OutputHeaderButton]} />
|
||||
<CoreEditorHeader
|
||||
leftNodes={[<SettingsServerlessFunctionsOutputMetadataInfo />]}
|
||||
rightNodes={[<LightCopyIconButton copyText={result} />]}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
@ -69,7 +75,7 @@ export const SettingsServerlessFunctionTestTab = ({
|
||||
header={InputHeader}
|
||||
/>
|
||||
<CodeEditor
|
||||
value={settingsServerlessFunctionOutput}
|
||||
value={result}
|
||||
height={settingsServerlessFunctionCodeEditorOutputParams.height}
|
||||
language={settingsServerlessFunctionCodeEditorOutputParams.language}
|
||||
options={{ readOnly: true, domReadOnly: true }}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import React from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { settingsServerlessFunctionOutputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState';
|
||||
import {
|
||||
DEFAULT_OUTPUT_VALUE,
|
||||
settingsServerlessFunctionOutputState,
|
||||
} from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState';
|
||||
import { settingsServerlessFunctionCodeEditorOutputParamsState } from '@/settings/serverless-functions/states/settingsServerlessFunctionCodeEditorOutputParamsState';
|
||||
|
||||
export const SettingsServerlessFunctionTestTabEffect = () => {
|
||||
@ -10,14 +13,11 @@ export const SettingsServerlessFunctionTestTabEffect = () => {
|
||||
const setSettingsServerlessFunctionCodeEditorOutputParams = useSetRecoilState(
|
||||
settingsServerlessFunctionCodeEditorOutputParamsState,
|
||||
);
|
||||
try {
|
||||
JSON.parse(settingsServerlessFunctionOutput);
|
||||
if (settingsServerlessFunctionOutput.data !== DEFAULT_OUTPUT_VALUE) {
|
||||
setSettingsServerlessFunctionCodeEditorOutputParams({
|
||||
language: 'json',
|
||||
height: 300,
|
||||
});
|
||||
} catch {
|
||||
return <></>;
|
||||
}
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -3,7 +3,10 @@ import { gql } from '@apollo/client';
|
||||
export const EXECUTE_ONE_SERVERLESS_FUNCTION = gql`
|
||||
mutation ExecuteOneServerlessFunction($id: UUID!, $payload: JSON!) {
|
||||
executeOneServerlessFunction(id: $id, payload: $payload) {
|
||||
result
|
||||
data
|
||||
duration
|
||||
status
|
||||
error
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -1,6 +1,18 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
import { ServerlessFunctionExecutionStatus } from '~/generated-metadata/graphql';
|
||||
|
||||
export const settingsServerlessFunctionOutputState = createState<string>({
|
||||
key: 'settingsServerlessFunctionOutputState',
|
||||
defaultValue: 'Enter an input above then press "run Function"',
|
||||
});
|
||||
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 },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user