Rename serverlessFunction to logicFunction (#17494)

## Summary

Rename "Serverless Function" to "Logic Function" across the codebase for
clearer naming.

### Environment Variable Changes

| Old | New |
|-----|-----|
| `SERVERLESS_TYPE` | `LOGIC_FUNCTION_TYPE` |
| `SERVERLESS_LAMBDA_REGION` | `LOGIC_FUNCTION_LAMBDA_REGION` |
| `SERVERLESS_LAMBDA_ROLE` | `LOGIC_FUNCTION_LAMBDA_ROLE` |
| `SERVERLESS_LAMBDA_SUBHOSTING_URL` |
`LOGIC_FUNCTION_LAMBDA_SUBHOSTING_URL` |
| `SERVERLESS_LAMBDA_ACCESS_KEY_ID` |
`LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID` |
| `SERVERLESS_LAMBDA_SECRET_ACCESS_KEY` |
`LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY` |

### Breaking Changes

- Environment variables must be updated in production deployments
- Database migration renames `serverlessFunction` → `logicFunction`
tables
This commit is contained in:
Charles Bochet
2026-01-28 01:42:19 +01:00
committed by GitHub
parent 59d123d2b1
commit da6f1bbef3
351 changed files with 5054 additions and 5139 deletions
@@ -4,7 +4,7 @@ import { RouterProvider } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
export const AppRouter = () => {
// We want to disable serverless function settings but keep the code for now
// We want to disable logic function settings but keep the code for now
const isFunctionSettingsEnabled = false;
const currentUser = useRecoilValue(currentUserState);
@@ -105,12 +105,12 @@ const SettingsDevelopersApiKeysNew = lazy(() =>
})),
);
const SettingsServerlessFunctionDetail = lazy(() =>
import(
'~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail'
).then((module) => ({
default: module.SettingsServerlessFunctionDetail,
})),
const SettingsLogicFunctionDetail = lazy(() =>
import('~/pages/settings/logic-functions/SettingsLogicFunctionDetail').then(
(module) => ({
default: module.SettingsLogicFunctionDetail,
}),
),
);
const SettingsWorkspace = lazy(() =>
@@ -447,8 +447,8 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
element={<SettingsSkillForm mode="edit" />}
/>
<Route
path={SettingsPath.ServerlessFunctionDetail}
element={<SettingsServerlessFunctionDetail />}
path={SettingsPath.LogicFunctionDetail}
element={<SettingsLogicFunctionDetail />}
/>
<Route path={SettingsPath.Billing} element={<SettingsBilling />} />
<Route path={SettingsPath.Domain} element={<SettingsDomain />} />
@@ -582,8 +582,8 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
element={<SettingsApplicationDetails />}
/>
<Route
path={SettingsPath.ApplicationServerlessFunctionDetail}
element={<SettingsServerlessFunctionDetail />}
path={SettingsPath.ApplicationLogicFunctionDetail}
element={<SettingsLogicFunctionDetail />}
/>
</Route>
@@ -1,11 +1,11 @@
import { gql } from '@apollo/client';
import { AGENT_FRAGMENT } from '@/ai/graphql/fragments/agentFragment';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
import { OBJECT_METADATA_FRAGMENT } from '@/object-metadata/graphql/fragment';
export const APPLICATION_FRAGMENT = gql`
${AGENT_FRAGMENT}
${SERVERLESS_FUNCTION_FRAGMENT}
${LOGIC_FUNCTION_FRAGMENT}
${OBJECT_METADATA_FRAGMENT}
fragment ApplicationFields on Application {
id
@@ -27,8 +27,8 @@ export const APPLICATION_FRAGMENT = gql`
objects {
...ObjectMetadataFields
}
serverlessFunctions {
...ServerlessFunctionFields
logicFunctions {
...LogicFunctionFields
}
}
`;
@@ -23,8 +23,8 @@ import { emitSidePanelCloseEvent } from '@/ui/layout/right-drawer/utils/emitSide
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId';
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -114,9 +114,9 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
set(isCommandMenuClosingState, false);
set(
activeTabIdComponentState.atomFamily({
instanceId: WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
instanceId: WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
}),
WorkflowServerlessFunctionTabId.CODE,
WorkflowLogicFunctionTabId.CODE,
);
for (const [pageId, morphItems] of snapshot
@@ -0,0 +1,52 @@
import { t } from '@lingui/core/macro';
import {
type ExecutionStatus,
WorkflowStepExecutionResult,
} from '@/workflow/components/WorkflowStepExecutionResult';
import { type LogicFunctionTestData } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { LogicFunctionExecutionStatus } from '~/generated-metadata/graphql';
export const LogicFunctionExecutionResult = ({
logicFunctionTestData,
maxHeight,
isTesting = false,
}: {
logicFunctionTestData: LogicFunctionTestData;
maxHeight?: number;
isTesting?: boolean;
}) => {
const result =
logicFunctionTestData.output.data ||
logicFunctionTestData.output.error ||
'';
const isSuccess =
logicFunctionTestData.output.status ===
LogicFunctionExecutionStatus.SUCCESS;
const isError =
logicFunctionTestData.output.status === LogicFunctionExecutionStatus.ERROR;
const duration = logicFunctionTestData.output.duration;
const status: ExecutionStatus = {
isSuccess,
isError,
successMessage: isSuccess ? t`200 OK - ${duration}ms` : undefined,
errorMessage: isError ? t`500 Error - ${duration}ms` : undefined,
};
return (
<WorkflowStepExecutionResult
result={result}
language={logicFunctionTestData.language}
height={Math.min(
logicFunctionTestData.height,
maxHeight ?? logicFunctionTestData.height,
)}
status={status}
isTesting={isTesting}
loadingMessage={t`Running function`}
idleMessage={t`Output`}
/>
);
};
@@ -0,0 +1,68 @@
import { useExecuteOneLogicFunction } from '@/settings/logic-functions/hooks/useExecuteOneLogicFunction';
import { logicFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
export const useTestLogicFunction = ({
logicFunctionId,
callback,
}: {
logicFunctionId: string;
callback?: (testResult: object) => void;
}) => {
const [isTesting, setIsTesting] = useState(false);
const { executeOneLogicFunction } = useExecuteOneLogicFunction();
const [logicFunctionTestData, setLogicFunctionTestData] = useRecoilState(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const testLogicFunction = async () => {
try {
setIsTesting(true);
await sleep(200); // Delay artificially to avoid flashing the UI
const result = await executeOneLogicFunction({
id: logicFunctionId,
payload: logicFunctionTestData.input,
version: 'draft',
});
setIsTesting(false);
if (isDefined(result?.data?.executeOneLogicFunction?.data)) {
callback?.(result?.data?.executeOneLogicFunction?.data);
}
setLogicFunctionTestData((prev) => ({
...prev,
language: 'json',
height: 300,
output: {
data: result?.data?.executeOneLogicFunction?.data
? JSON.stringify(
result?.data?.executeOneLogicFunction?.data,
null,
4,
)
: undefined,
logs: result?.data?.executeOneLogicFunction?.logs || '',
duration: result?.data?.executeOneLogicFunction?.duration,
status: result?.data?.executeOneLogicFunction?.status,
error: result?.data?.executeOneLogicFunction?.error
? JSON.stringify(
result?.data?.executeOneLogicFunction?.error,
null,
4,
)
: undefined,
},
}));
} catch (error) {
setIsTesting(false);
throw error;
}
};
return { testLogicFunction, isTesting };
};
@@ -1,4 +1,4 @@
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
import { computeNewSources } from '@/logic-functions/utils/computeNewSources';
describe('computeNewSources', () => {
it('should compute new code input root 0', () => {
@@ -1,6 +1,6 @@
// IA Generated
import { flattenSources } from '@/serverless-functions/utils/flattenSources';
import { flattenSources } from '@/logic-functions/utils/flattenSources';
import { type Sources } from 'twenty-shared/types';
describe('flattenSources', () => {
@@ -1,5 +1,5 @@
import { type InputSchema } from '@/workflow/types/InputSchema';
import { getDefaultFunctionInputFromInputSchema } from '@/serverless-functions/utils/getDefaultFunctionInputFromInputSchema';
import { getDefaultFunctionInputFromInputSchema } from '@/logic-functions/utils/getDefaultFunctionInputFromInputSchema';
describe('getDefaultFunctionInputFromInputSchema', () => {
it('should init function input properly', () => {
@@ -1,4 +1,4 @@
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
describe('getFunctionInputFromSourceCode', () => {
it('should return empty input if not parameter', async () => {
@@ -1,4 +1,4 @@
import { getFunctionInputSchema } from '@/serverless-functions/utils/getFunctionInputSchema';
import { getFunctionInputSchema } from '@/logic-functions/utils/getFunctionInputSchema';
describe('getFunctionInputSchema', () => {
it('should analyze a simple function correctly', () => {
@@ -1,4 +1,4 @@
import { mergeDefaultFunctionInputAndFunctionInput } from '@/serverless-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
describe('mergeDefaultFunctionInputAndFunctionInput', () => {
it('should merge properly', () => {
@@ -1,4 +1,4 @@
import { getDefaultFunctionInputFromInputSchema } from '@/serverless-functions/utils/getDefaultFunctionInputFromInputSchema';
import { getDefaultFunctionInputFromInputSchema } from '@/logic-functions/utils/getDefaultFunctionInputFromInputSchema';
import { type FunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/types/FunctionInput';
import { isObject } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
@@ -11,7 +11,7 @@ export const getFunctionInputFromSourceCode = async (
}
const { getFunctionInputSchema } = await import(
'@/serverless-functions/utils/getFunctionInputSchema'
'@/logic-functions/utils/getFunctionInputSchema'
);
const functionInputSchema = getFunctionInputSchema(sourceCode);
@@ -6,7 +6,7 @@ export const getToolInputSchemaFromSourceCode = async (
const { getFunctionInputSchema } = await import('./getFunctionInputSchema');
const inputSchema = getFunctionInputSchema(sourceCode);
// Serverless functions take a single params object
// Logic functions take a single params object
const firstParam = inputSchema[0];
if (firstParam?.type === 'object' && isDefined(firstParam.properties)) {
@@ -28,10 +28,7 @@ export const useMetadataErrorHandler = () => {
viewGroup: t`view group`,
viewFilter: t`view filter`,
index: t`index`,
serverlessFunction: t`serverless function`,
cronTrigger: t`cron trigger`,
databaseEventTrigger: t`database trigger`,
routeTrigger: t`route trigger`,
logicFunction: t`logic function`,
role: t`role`,
roleTarget: t`role target`,
agent: t`agent`,
@@ -8,7 +8,7 @@ export const CUSTOM_WORKSPACE_APPLICATION_MOCK = {
description: 'workpace custom application',
name: 'custom',
objects: [],
serverlessFunctions: [],
logicFunctions: [],
universalIdentifier: '66a698b6-f6c1-4d35-a6e7-20aeadc3cd95',
version: '1.0.0',
} as const satisfies Application;
@@ -1,53 +0,0 @@
import { t } from '@lingui/core/macro';
import {
type ExecutionStatus,
WorkflowStepExecutionResult,
} from '@/workflow/components/WorkflowStepExecutionResult';
import { type ServerlessFunctionTestData } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { ServerlessFunctionExecutionStatus } from '~/generated-metadata/graphql';
export const ServerlessFunctionExecutionResult = ({
serverlessFunctionTestData,
maxHeight,
isTesting = false,
}: {
serverlessFunctionTestData: ServerlessFunctionTestData;
maxHeight?: number;
isTesting?: boolean;
}) => {
const result =
serverlessFunctionTestData.output.data ||
serverlessFunctionTestData.output.error ||
'';
const isSuccess =
serverlessFunctionTestData.output.status ===
ServerlessFunctionExecutionStatus.SUCCESS;
const isError =
serverlessFunctionTestData.output.status ===
ServerlessFunctionExecutionStatus.ERROR;
const duration = serverlessFunctionTestData.output.duration;
const status: ExecutionStatus = {
isSuccess,
isError,
successMessage: isSuccess ? t`200 OK - ${duration}ms` : undefined,
errorMessage: isError ? t`500 Error - ${duration}ms` : undefined,
};
return (
<WorkflowStepExecutionResult
result={result}
language={serverlessFunctionTestData.language}
height={Math.min(
serverlessFunctionTestData.height,
maxHeight ?? serverlessFunctionTestData.height,
)}
status={status}
isTesting={isTesting}
loadingMessage={t`Running function`}
idleMessage={t`Output`}
/>
);
};
@@ -1,67 +0,0 @@
import { useExecuteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useExecuteOneServerlessFunction';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
export const useTestServerlessFunction = ({
serverlessFunctionId,
callback,
}: {
serverlessFunctionId: string;
callback?: (testResult: object) => void;
}) => {
const [isTesting, setIsTesting] = useState(false);
const { executeOneServerlessFunction } = useExecuteOneServerlessFunction();
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const testServerlessFunction = async () => {
try {
setIsTesting(true);
await sleep(200); // Delay artificially to avoid flashing the UI
const result = await executeOneServerlessFunction({
id: serverlessFunctionId,
payload: serverlessFunctionTestData.input,
version: 'draft',
});
setIsTesting(false);
if (isDefined(result?.data?.executeOneServerlessFunction?.data)) {
callback?.(result?.data?.executeOneServerlessFunction?.data);
}
setServerlessFunctionTestData((prev) => ({
...prev,
language: 'json',
height: 300,
output: {
data: result?.data?.executeOneServerlessFunction?.data
? JSON.stringify(
result?.data?.executeOneServerlessFunction?.data,
null,
4,
)
: undefined,
logs: result?.data?.executeOneServerlessFunction?.logs || '',
duration: result?.data?.executeOneServerlessFunction?.duration,
status: result?.data?.executeOneServerlessFunction?.status,
error: result?.data?.executeOneServerlessFunction?.error
? JSON.stringify(
result?.data?.executeOneServerlessFunction?.error,
null,
4,
)
: undefined,
},
}));
} catch (error) {
setIsTesting(false);
throw error;
}
};
return { testServerlessFunction, isTesting };
};
@@ -1,4 +1,4 @@
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import { type EditorProps, type Monaco } from '@monaco-editor/react';
import { type editor } from 'monaco-editor';
import { AutoTypings } from 'monaco-editor-auto-typings';
@@ -12,25 +12,22 @@ export type File = {
path: string;
};
type SettingsServerlessFunctionCodeEditorProps = Omit<
EditorProps,
'onChange'
> & {
type SettingsLogicFunctionCodeEditorProps = Omit<EditorProps, 'onChange'> & {
currentFilePath: string;
files: File[];
onChange: (value: string) => void;
};
export const SettingsServerlessFunctionCodeEditor = ({
export const SettingsLogicFunctionCodeEditor = ({
currentFilePath,
files,
onChange,
height = 450,
options = undefined,
}: SettingsServerlessFunctionCodeEditorProps) => {
const { serverlessFunctionId = '' } = useParams();
}: SettingsLogicFunctionCodeEditorProps) => {
const { logicFunctionId = '' } = useParams();
const { availablePackages } = useGetAvailablePackages({
id: serverlessFunctionId,
id: logicFunctionId,
});
const currentFile = files.find((file) => file.path === currentFilePath);
@@ -13,19 +13,19 @@ const StyledHeaderTitle = styled.div`
}
`;
type SettingsServerlessFunctionLabelContainerProps = {
type SettingsLogicFunctionLabelContainerProps = {
value: string;
onChange: (value: string) => void;
};
export const SettingsServerlessFunctionLabelContainer = ({
export const SettingsLogicFunctionLabelContainer = ({
value,
onChange,
}: SettingsServerlessFunctionLabelContainerProps) => {
}: SettingsLogicFunctionLabelContainerProps) => {
return (
<StyledHeaderTitle>
<TitleInput
instanceId="serverless-function-name-input"
instanceId="logic-function-name-input"
sizeVariant="md"
value={value}
onChange={onChange}
@@ -1,4 +1,4 @@
import { type ServerlessFunctionNewFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { type LogicFunctionNewFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TextArea } from '@/ui/input/components/TextArea';
import styled from '@emotion/styled';
@@ -12,12 +12,12 @@ const StyledInputsContainer = styled.div`
gap: ${({ theme }) => theme.spacing(4)};
`;
export const SettingsServerlessFunctionNewForm = ({
export const SettingsLogicFunctionNewForm = ({
formValues,
onChange,
readonly = false,
}: {
formValues: ServerlessFunctionNewFormValues;
formValues: LogicFunctionNewFormValues;
onChange: (key: string) => (value: string) => void;
readonly?: boolean;
}) => {
@@ -7,7 +7,7 @@ import { useParams } from 'react-router-dom';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
export const SettingsServerlessFunctionTabEnvironmentVariablesSection = () => {
export const SettingsLogicFunctionTabEnvironmentVariablesSection = () => {
const { applicationId = '' } = useParams<{ applicationId: string }>();
return (
<Section>
@@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { type ServerlessFunction } from '~/generated-metadata/graphql';
import { type LogicFunction } from '~/generated-metadata/graphql';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useTheme } from '@emotion/react';
import { IconChevronRight } from 'twenty-ui/display';
import { StyledTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTable';
import { StyledTableRow } from '@/settings/logic-functions/components/SettingsLogicFunctionsTable';
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
@@ -24,21 +24,19 @@ const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
export const SettingsServerlessFunctionsFieldItemTableRow = ({
serverlessFunction,
export const SettingsLogicFunctionsFieldItemTableRow = ({
logicFunction,
to,
}: {
serverlessFunction: ServerlessFunction;
logicFunction: LogicFunction;
to: string;
}) => {
const theme = useTheme();
return (
<StyledTableRow to={to}>
<StyledNameTableCell>{serverlessFunction.name}</StyledNameTableCell>
<StyledNameTableCell>{logicFunction.name}</StyledNameTableCell>
<StyledNameTableCell></StyledNameTableCell>
<StyledRuntimeTableCell>
{serverlessFunction.runtime}
</StyledRuntimeTableCell>
<StyledRuntimeTableCell>{logicFunction.runtime}</StyledRuntimeTableCell>
<StyledIconTableCell>
<StyledIconChevronRight
size={theme.icon.size.md}
@@ -1,4 +1,4 @@
import { SettingsServerlessFunctionsFieldItemTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsFieldItemTableRow';
import { SettingsLogicFunctionsFieldItemTableRow } from '@/settings/logic-functions/components/SettingsLogicFunctionsFieldItemTableRow';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
@@ -6,7 +6,7 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { type ServerlessFunction } from '~/generated-metadata/graphql';
import { type LogicFunction } from '~/generated-metadata/graphql';
import { useLingui } from '@lingui/react/macro';
import { useParams } from 'react-router-dom';
@@ -18,16 +18,16 @@ const StyledTableBody = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
export const SettingsServerlessFunctionsTable = ({
serverlessFunctions,
export const SettingsLogicFunctionsTable = ({
logicFunctions,
}: {
serverlessFunctions: ServerlessFunction[];
logicFunctions: LogicFunction[];
}) => {
const { applicationId = '' } = useParams();
const { t } = useLingui();
if (serverlessFunctions.length === 0) {
if (logicFunctions.length === 0) {
return null;
}
@@ -40,17 +40,14 @@ export const SettingsServerlessFunctionsTable = ({
<TableHeader></TableHeader>
</StyledTableRow>
<StyledTableBody>
{serverlessFunctions.map((serverlessFunction: ServerlessFunction) => (
<SettingsServerlessFunctionsFieldItemTableRow
key={serverlessFunction.id}
serverlessFunction={serverlessFunction}
to={getSettingsPath(
SettingsPath.ApplicationServerlessFunctionDetail,
{
applicationId,
serverlessFunctionId: serverlessFunction.id,
},
)}
{logicFunctions.map((logicFunction: LogicFunction) => (
<SettingsLogicFunctionsFieldItemTableRow
key={logicFunction.id}
logicFunction={logicFunction}
to={getSettingsPath(SettingsPath.ApplicationLogicFunctionDetail, {
applicationId,
logicFunctionId: logicFunction.id,
})}
/>
))}
</StyledTableBody>
@@ -1,8 +1,8 @@
import {
type File,
SettingsServerlessFunctionCodeEditor,
} from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor';
import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId';
SettingsLogicFunctionCodeEditor,
} from '@/settings/logic-functions/components/SettingsLogicFunctionCodeEditor';
import { SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/logic-functions/constants/SettingsLogicFunctionTabListComponentId';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -16,7 +16,7 @@ const StyledTabList = styled(TabList)`
border-bottom: none;
`;
export const SettingsServerlessFunctionCodeEditorTab = ({
export const SettingsLogicFunctionCodeEditorTab = ({
files,
handleExecute,
onChange,
@@ -31,7 +31,7 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
}) => {
const activeTabId = useRecoilComponentValue(
activeTabIdComponentState,
SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
);
const TestButton = (
<Button
@@ -50,7 +50,7 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
tabs={files.map((file) => {
return { id: file.path, title: file.path.split('/').at(-1) || '' };
})}
componentInstanceId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}
componentInstanceId={SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
);
@@ -62,16 +62,18 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
/>
<CoreEditorHeader leftNodes={[HeaderTabList]} rightNodes={[TestButton]} />
{activeTabId && (
<SettingsServerlessFunctionCodeEditor
<SettingsLogicFunctionCodeEditor
files={files}
currentFilePath={activeTabId}
onChange={(newCodeValue) => onChange(activeTabId, newCodeValue)}
onChange={(newCodeValue: string) =>
onChange(activeTabId, newCodeValue)
}
options={
isManaged
? {
readOnly: true,
readOnlyMessage: {
value: t`Managed serverless functions are not editable`,
value: t`Managed logic functions are not editable`,
},
}
: undefined
@@ -0,0 +1,21 @@
import { SettingsLogicFunctionNewForm } from '@/settings/logic-functions/components/SettingsLogicFunctionNewForm';
import { SettingsLogicFunctionTabEnvironmentVariablesSection } from '@/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection';
import { type LogicFunctionFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
export const SettingsLogicFunctionSettingsTab = ({
formValues,
onChange,
}: {
formValues: LogicFunctionFormValues;
onChange: (key: string) => (value: string) => void;
}) => {
return (
<>
<SettingsLogicFunctionNewForm
formValues={formValues}
onChange={onChange}
/>
<SettingsLogicFunctionTabEnvironmentVariablesSection />
</>
);
};
@@ -28,7 +28,7 @@ const StyledTableRow = styled(TableRow)`
grid-template-columns: 180px 300px 32px;
`;
export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
export const SettingsLogicFunctionTabEnvironmentVariableTableRow = ({
envVariable,
onChange,
onDelete,
@@ -1,5 +1,8 @@
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
import {
type LogicFunctionTestData,
logicFunctionTestDataFamilyState,
} from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useRecoilState } from 'recoil';
@@ -20,27 +23,29 @@ const StyledCodeEditorContainer = styled.div`
flex-direction: column;
`;
export const SettingsServerlessFunctionTestTab = ({
export const SettingsLogicFunctionTestTab = ({
handleExecute,
serverlessFunctionId,
logicFunctionId,
isTesting = false,
}: {
handleExecute: () => void;
serverlessFunctionId: string;
logicFunctionId: string;
isTesting?: boolean;
}) => {
const { t } = useLingui();
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const [logicFunctionTestData, setLogicFunctionTestData] =
useRecoilState<LogicFunctionTestData>(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const onChange = (newInput: string) => {
setServerlessFunctionTestData((prev) => ({
setLogicFunctionTestData((prev) => ({
...prev,
input: JSON.parse(newInput),
}));
};
const testLogsTextAreaId = `${serverlessFunctionId}-test-logs`;
const testLogsTextAreaId = `${logicFunctionId}-test-logs`;
return (
<Section>
@@ -65,26 +70,26 @@ export const SettingsServerlessFunctionTestTab = ({
]}
/>
<CodeEditor
value={JSON.stringify(serverlessFunctionTestData.input, null, 4)}
value={JSON.stringify(logicFunctionTestData.input, null, 4)}
language="json"
height={100}
onChange={onChange}
variant="with-header"
/>
</StyledCodeEditorContainer>
<ServerlessFunctionExecutionResult
serverlessFunctionTestData={serverlessFunctionTestData}
<LogicFunctionExecutionResult
logicFunctionTestData={logicFunctionTestData}
maxHeight={
serverlessFunctionTestData.output.logs.length > 0 ? 200 : undefined
logicFunctionTestData.output.logs.length > 0 ? 200 : undefined
}
isTesting={isTesting}
/>
{serverlessFunctionTestData.output.logs.length > 0 && (
{logicFunctionTestData.output.logs.length > 0 && (
<StyledCodeEditorContainer>
<InputLabel>{t`Logs`}</InputLabel>
<TextArea
textAreaId={testLogsTextAreaId}
value={isTesting ? '' : serverlessFunctionTestData.output.logs}
value={isTesting ? '' : logicFunctionTestData.output.logs}
maxRows={20}
disabled
/>
@@ -7,7 +7,7 @@ import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { type ServerlessFunction } from '~/generated/graphql';
import { type LogicFunction } from '~/generated/graphql';
export const StyledRouteTriggerTableRow = styled(TableRow)`
grid-template-columns: 1fr 120px 120px;
@@ -35,10 +35,10 @@ const StyledEmptyState = styled.div`
text-align: center;
`;
export const SettingsServerlessFunctionTriggersTab = ({
serverlessFunction: _serverlessFunction,
export const SettingsLogicFunctionTriggersTab = ({
logicFunction: _logicFunction,
}: {
serverlessFunction: ServerlessFunction;
logicFunction: LogicFunction;
}) => {
const { t } = useLingui();
@@ -0,0 +1,2 @@
export const SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID =
'settings-logic-function-editor-tab-list';
@@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
export const SERVERLESS_FUNCTION_FRAGMENT = gql`
fragment ServerlessFunctionFields on ServerlessFunction {
export const LOGIC_FUNCTION_FRAGMENT = gql`
fragment LogicFunctionFields on LogicFunction {
id
name
description
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const CREATE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation CreateOneLogicFunctionItem($input: CreateLogicFunctionInput!) {
createOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const DELETE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation DeleteOneLogicFunction($input: LogicFunctionIdInput!) {
deleteOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,13 @@
import { gql } from '@apollo/client';
export const EXECUTE_ONE_LOGIC_FUNCTION = gql`
mutation ExecuteOneLogicFunction($input: ExecuteLogicFunctionInput!) {
executeOneLogicFunction(input: $input) {
data
logs
duration
status
error
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const PUBLISH_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation PublishOneLogicFunction($input: PublishLogicFunctionInput!) {
publishLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const UPDATE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation UpdateOneLogicFunction($input: UpdateLogicFunctionInput!) {
updateOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
export const FIND_MANY_AVAILABLE_PACKAGES = gql`
query FindManyAvailablePackages($input: ServerlessFunctionIdInput!) {
query FindManyAvailablePackages($input: LogicFunctionIdInput!) {
getAvailablePackages(input: $input)
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_MANY_LOGIC_FUNCTIONS = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetManyLogicFunctions {
findManyLogicFunctions {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetOneLogicFunction($input: LogicFunctionIdInput!) {
findOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE = gql`
query FindOneLogicFunctionSourceCode(
$input: GetLogicFunctionSourceCodeInput!
) {
getLogicFunctionSourceCode(input: $input)
}
`;
@@ -0,0 +1,48 @@
import { useLogicFunctionUpdateFormState } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
jest.mock('@/settings/logic-functions/hooks/useGetOneLogicFunction', () => ({
useGetOneLogicFunction: jest.fn(),
}));
jest.mock(
'@/settings/logic-functions/hooks/useGetOneLogicFunctionSourceCode',
() => ({
useGetOneLogicFunctionSourceCode: jest.fn(),
}),
);
describe('useLogicFunctionUpdateFormState', () => {
test('should return a form', () => {
const logicFunctionId = 'logicFunctionId';
const useGetOneLogicFunctionMock = jest.requireMock(
'@/settings/logic-functions/hooks/useGetOneLogicFunction',
);
useGetOneLogicFunctionMock.useGetOneLogicFunction.mockReturnValue({
logicFunction: { name: 'name' },
});
const useGetOneLogicFunctionSourceCodeMock = jest.requireMock(
'@/settings/logic-functions/hooks/useGetOneLogicFunctionSourceCode',
);
useGetOneLogicFunctionSourceCodeMock.useGetOneLogicFunctionSourceCode.mockReturnValue(
{
code: { src: { 'index.ts': 'export const handler = () => {}' } },
},
);
const { result } = renderHook(
() => useLogicFunctionUpdateFormState({ logicFunctionId }),
{
wrapper: RecoilRoot,
},
);
const { formValues } = result.current;
expect(formValues).toEqual({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
});
});
@@ -0,0 +1,27 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { EXECUTE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/executeOneLogicFunction';
import { useMutation } from '@apollo/client';
import {
type ExecuteOneLogicFunctionMutation,
type ExecuteOneLogicFunctionMutationVariables,
type ExecuteLogicFunctionInput,
} from '~/generated-metadata/graphql';
export const useExecuteOneLogicFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
ExecuteOneLogicFunctionMutation,
ExecuteOneLogicFunctionMutationVariables
>(EXECUTE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const executeOneLogicFunction = async (input: ExecuteLogicFunctionInput) => {
return await mutate({
variables: {
input,
},
});
};
return { executeOneLogicFunction };
};
@@ -1,13 +1,13 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useQuery } from '@apollo/client';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/settings/serverless-functions/graphql/queries/findManyAvailablePackages';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/settings/logic-functions/graphql/queries/findManyAvailablePackages';
import {
type FindManyAvailablePackagesQuery,
type FindManyAvailablePackagesQueryVariables,
type ServerlessFunctionIdInput,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetAvailablePackages = (input: ServerlessFunctionIdInput) => {
export const useGetAvailablePackages = (input: LogicFunctionIdInput) => {
const apolloMetadataClient = useApolloCoreClient();
const { data } = useQuery<
FindManyAvailablePackagesQuery,
@@ -0,0 +1,24 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_MANY_LOGIC_FUNCTIONS } from '@/settings/logic-functions/graphql/queries/findManyLogicFunctions';
import { useQuery } from '@apollo/client';
import {
type GetManyLogicFunctionsQuery,
type GetManyLogicFunctionsQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetManyLogicFunctions = () => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading, error } = useQuery<
GetManyLogicFunctionsQuery,
GetManyLogicFunctionsQueryVariables
>(FIND_MANY_LOGIC_FUNCTIONS, {
client: apolloMetadataClient ?? undefined,
});
return {
logicFunctions: data?.findManyLogicFunctions || [],
loading,
error,
};
};
@@ -0,0 +1,31 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/queries/findOneLogicFunction';
import { useQuery } from '@apollo/client';
import {
type GetOneLogicFunctionQuery,
type GetOneLogicFunctionQueryVariables,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetOneLogicFunction = ({
id,
onCompleted,
}: LogicFunctionIdInput & {
onCompleted?: (data: GetOneLogicFunctionQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
GetOneLogicFunctionQuery,
GetOneLogicFunctionQueryVariables
>(FIND_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id },
},
onCompleted,
});
return {
logicFunction: data?.findOneLogicFunction || null,
loading,
};
};
@@ -0,0 +1,31 @@
import { useQuery } from '@apollo/client';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE } from '@/settings/logic-functions/graphql/queries/findOneLogicFunctionSourceCode';
import {
type FindOneLogicFunctionSourceCodeQuery,
type FindOneLogicFunctionSourceCodeQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetOneLogicFunctionSourceCode = ({
id,
version,
onCompleted,
}: {
id: string;
version: string;
onCompleted?: (data: FindOneLogicFunctionSourceCodeQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
FindOneLogicFunctionSourceCodeQuery,
FindOneLogicFunctionSourceCodeQueryVariables
>(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id, version },
},
onCompleted,
fetchPolicy: 'network-only',
});
return { code: data?.getLogicFunctionSourceCode, loading };
};
@@ -0,0 +1,107 @@
import { flattenSources } from '@/logic-functions/utils/flattenSources';
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
import { useGetOneLogicFunction } from '@/settings/logic-functions/hooks/useGetOneLogicFunction';
import { useGetOneLogicFunctionSourceCode } from '@/settings/logic-functions/hooks/useGetOneLogicFunctionSourceCode';
import { logicFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { type Dispatch, type SetStateAction, useState } from 'react';
import { useRecoilState } from 'recoil';
import { type Sources } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type FindOneLogicFunctionSourceCodeQuery,
type GetOneLogicFunctionQuery,
} from '~/generated-metadata/graphql';
import { type LogicFunction } from '~/generated/graphql';
export type LogicFunctionNewFormValues = {
name: string;
description: string;
};
export type LogicFunctionFormValues = LogicFunctionNewFormValues & {
code: Sources;
};
type SetLogicFunctionFormValues = Dispatch<
SetStateAction<LogicFunctionFormValues>
>;
export const useLogicFunctionUpdateFormState = ({
logicFunctionId,
logicFunctionVersion = 'draft',
}: {
logicFunctionId: string;
logicFunctionVersion?: string;
}): {
formValues: LogicFunctionFormValues;
logicFunction: LogicFunction | null;
setFormValues: SetLogicFunctionFormValues;
loading: boolean;
} => {
const [formValues, setFormValues] = useState<LogicFunctionFormValues>({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
const [logicFunctionTestData, setLogicFunctionTestData] = useRecoilState(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const { logicFunction, loading: logicFunctionLoading } =
useGetOneLogicFunction({
id: logicFunctionId,
onCompleted: (data: GetOneLogicFunctionQuery) => {
const fn = data?.findOneLogicFunction;
if (isDefined(fn)) {
setFormValues((prevState) => ({
...prevState,
name: fn.name || '',
description: fn.description || '',
}));
}
},
});
const { loading: logicFunctionSourceCodeLoading } =
useGetOneLogicFunctionSourceCode({
id: logicFunctionId,
version: logicFunctionVersion,
onCompleted: async (data: FindOneLogicFunctionSourceCodeQuery) => {
const code = data?.getLogicFunctionSourceCode;
setFormValues((prevState) => ({
...prevState,
code: code || prevState.code,
}));
if (logicFunctionTestData.shouldInitInput) {
const flattenedCode = flattenSources(code);
const sourceCode = flattenedCode.find(
(flatCode) => flatCode.path === logicFunction?.sourceHandlerPath,
);
if (isDefined(sourceCode)) {
const functionInput = await getFunctionInputFromSourceCode(
sourceCode.content,
);
setLogicFunctionTestData((prev) => ({
...prev,
input: functionInput,
shouldInitInput: false,
}));
}
}
},
});
return {
formValues,
setFormValues,
logicFunction,
loading: logicFunctionSourceCodeLoading || logicFunctionLoading,
};
};
@@ -0,0 +1,173 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { CREATE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/createOneLogicFunction';
import { DELETE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/deleteOneLogicFunction';
import { UPDATE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/updateOneLogicFunction';
import { FIND_MANY_LOGIC_FUNCTIONS } from '@/settings/logic-functions/graphql/queries/findManyLogicFunctions';
import { FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE } from '@/settings/logic-functions/graphql/queries/findOneLogicFunctionSourceCode';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ApolloError, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import {
type CreateOneLogicFunctionItemMutation,
type CreateOneLogicFunctionItemMutationVariables,
type DeleteOneLogicFunctionMutation,
type DeleteOneLogicFunctionMutationVariables,
type UpdateOneLogicFunctionMutation,
type UpdateOneLogicFunctionMutationVariables,
} from '~/generated-metadata/graphql';
export const usePersistLogicFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const [createLogicFunctionMutation] = useMutation<
CreateOneLogicFunctionItemMutation,
CreateOneLogicFunctionItemMutationVariables
>(CREATE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const [updateLogicFunctionMutation] = useMutation<
UpdateOneLogicFunctionMutation,
UpdateOneLogicFunctionMutationVariables
>(UPDATE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const [deleteLogicFunctionMutation] = useMutation<
DeleteOneLogicFunctionMutation,
DeleteOneLogicFunctionMutationVariables
>(DELETE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const createLogicFunction = useCallback(
async (
variables: CreateOneLogicFunctionItemMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof createLogicFunctionMutation>>
>
> => {
try {
const result = await createLogicFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_LOGIC_FUNCTIONS) ?? ''],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[createLogicFunctionMutation, handleMetadataError, enqueueErrorSnackBar],
);
const updateLogicFunction = useCallback(
async (
variables: UpdateOneLogicFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updateLogicFunctionMutation>>
>
> => {
try {
const result = await updateLogicFunctionMutation({
variables,
refetchQueries: [
getOperationName(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[updateLogicFunctionMutation, handleMetadataError, enqueueErrorSnackBar],
);
const deleteLogicFunction = useCallback(
async (
variables: DeleteOneLogicFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof deleteLogicFunctionMutation>>
>
> => {
try {
const result = await deleteLogicFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[deleteLogicFunctionMutation, handleMetadataError, enqueueErrorSnackBar],
);
return {
createLogicFunction,
updateLogicFunction,
deleteLogicFunction,
};
};
@@ -0,0 +1,34 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { PUBLISH_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/publishOneLogicFunction';
import { FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE } from '@/settings/logic-functions/graphql/queries/findOneLogicFunctionSourceCode';
import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
type PublishOneLogicFunctionMutation,
type PublishOneLogicFunctionMutationVariables,
type PublishLogicFunctionInput,
} from '~/generated-metadata/graphql';
export const usePublishOneLogicFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
PublishOneLogicFunctionMutation,
PublishOneLogicFunctionMutationVariables
>(PUBLISH_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const publishOneLogicFunction = async (input: PublishLogicFunctionInput) => {
return await mutate({
variables: {
input,
},
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE) ?? '',
],
});
};
return { publishOneLogicFunction };
};
@@ -1,21 +0,0 @@
import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functions/components/SettingsServerlessFunctionNewForm';
import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/SettingsServerlessFunctionTabEnvironmentVariablesSection';
import { type ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
export const SettingsServerlessFunctionSettingsTab = ({
formValues,
onChange,
}: {
formValues: ServerlessFunctionFormValues;
onChange: (key: string) => (value: string) => void;
}) => {
return (
<>
<SettingsServerlessFunctionNewForm
formValues={formValues}
onChange={onChange}
/>
<SettingsServerlessFunctionTabEnvironmentVariablesSection />
</>
);
};
@@ -1,2 +0,0 @@
export const SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID =
'settings-serverless-function-editor-tab-list';
@@ -1,13 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const CREATE_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation CreateOneServerlessFunctionItem(
$input: CreateServerlessFunctionInput!
) {
createOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const DELETE_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {
deleteOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,15 +0,0 @@
import { gql } from '@apollo/client';
export const EXECUTE_ONE_SERVERLESS_FUNCTION = gql`
mutation ExecuteOneServerlessFunction(
$input: ExecuteServerlessFunctionInput!
) {
executeOneServerlessFunction(input: $input) {
data
logs
duration
status
error
}
}
`;
@@ -1,13 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const PUBLISH_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation PublishOneServerlessFunction(
$input: PublishServerlessFunctionInput!
) {
publishServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const UPDATE_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation UpdateOneServerlessFunction($input: UpdateServerlessFunctionInput!) {
updateOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const FIND_MANY_SERVERLESS_FUNCTIONS = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
query GetManyServerlessFunctions {
findManyServerlessFunctions {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const FIND_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
query GetOneServerlessFunction($input: ServerlessFunctionIdInput!) {
findOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE = gql`
query FindOneServerlessFunctionSourceCode(
$input: GetServerlessFunctionSourceCodeInput!
) {
getServerlessFunctionSourceCode(input: $input)
}
`;
@@ -1,53 +0,0 @@
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
jest.mock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunction',
() => ({
useGetOneServerlessFunction: jest.fn(),
}),
);
jest.mock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode',
() => ({
useGetOneServerlessFunctionSourceCode: jest.fn(),
}),
);
describe('useServerlessFunctionUpdateFormState', () => {
test('should return a form', () => {
const serverlessFunctionId = 'serverlessFunctionId';
const useGetOneServerlessFunctionMock = jest.requireMock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunction',
);
useGetOneServerlessFunctionMock.useGetOneServerlessFunction.mockReturnValue(
{
serverlessFunction: { name: 'name' },
},
);
const useGetOneServerlessFunctionSourceCodeMock = jest.requireMock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode',
);
useGetOneServerlessFunctionSourceCodeMock.useGetOneServerlessFunctionSourceCode.mockReturnValue(
{
code: { src: { 'index.ts': 'export const handler = () => {}' } },
},
);
const { result } = renderHook(
() => useServerlessFunctionUpdateFormState({ serverlessFunctionId }),
{
wrapper: RecoilRoot,
},
);
const { formValues } = result.current;
expect(formValues).toEqual({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
});
});
@@ -1,29 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { EXECUTE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/executeOneServerlessFunction';
import { useMutation } from '@apollo/client';
import {
type ExecuteOneServerlessFunctionMutation,
type ExecuteOneServerlessFunctionMutationVariables,
type ExecuteServerlessFunctionInput,
} from '~/generated-metadata/graphql';
export const useExecuteOneServerlessFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
ExecuteOneServerlessFunctionMutation,
ExecuteOneServerlessFunctionMutationVariables
>(EXECUTE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const executeOneServerlessFunction = async (
input: ExecuteServerlessFunctionInput,
) => {
return await mutate({
variables: {
input,
},
});
};
return { executeOneServerlessFunction };
};
@@ -1,24 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_MANY_SERVERLESS_FUNCTIONS } from '@/settings/serverless-functions/graphql/queries/findManyServerlessFunctions';
import { useQuery } from '@apollo/client';
import {
type GetManyServerlessFunctionsQuery,
type GetManyServerlessFunctionsQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetManyServerlessFunctions = () => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading, error } = useQuery<
GetManyServerlessFunctionsQuery,
GetManyServerlessFunctionsQueryVariables
>(FIND_MANY_SERVERLESS_FUNCTIONS, {
client: apolloMetadataClient ?? undefined,
});
return {
serverlessFunctions: data?.findManyServerlessFunctions || [],
loading,
error,
};
};
@@ -1,31 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunction';
import { useQuery } from '@apollo/client';
import {
type GetOneServerlessFunctionQuery,
type GetOneServerlessFunctionQueryVariables,
type ServerlessFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetOneServerlessFunction = ({
id,
onCompleted,
}: ServerlessFunctionIdInput & {
onCompleted?: (data: GetOneServerlessFunctionQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
GetOneServerlessFunctionQuery,
GetOneServerlessFunctionQueryVariables
>(FIND_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id },
},
onCompleted,
});
return {
serverlessFunction: data?.findOneServerlessFunction || null,
loading,
};
};
@@ -1,31 +0,0 @@
import { useQuery } from '@apollo/client';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
import {
type FindOneServerlessFunctionSourceCodeQuery,
type FindOneServerlessFunctionSourceCodeQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetOneServerlessFunctionSourceCode = ({
id,
version,
onCompleted,
}: {
id: string;
version: string;
onCompleted?: (data: FindOneServerlessFunctionSourceCodeQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
FindOneServerlessFunctionSourceCodeQuery,
FindOneServerlessFunctionSourceCodeQueryVariables
>(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id, version },
},
onCompleted,
fetchPolicy: 'network-only',
});
return { code: data?.getServerlessFunctionSourceCode, loading };
};
@@ -1,187 +0,0 @@
import { useCallback } from 'react';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { CREATE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/createOneServerlessFunction';
import { DELETE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/deleteOneServerlessFunction';
import { UPDATE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/updateOneServerlessFunction';
import { FIND_MANY_SERVERLESS_FUNCTIONS } from '@/settings/serverless-functions/graphql/queries/findManyServerlessFunctions';
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ApolloError, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import {
type CreateOneServerlessFunctionItemMutation,
type CreateOneServerlessFunctionItemMutationVariables,
type DeleteOneServerlessFunctionMutation,
type DeleteOneServerlessFunctionMutationVariables,
type UpdateOneServerlessFunctionMutation,
type UpdateOneServerlessFunctionMutationVariables,
} from '~/generated-metadata/graphql';
export const usePersistServerlessFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const [createServerlessFunctionMutation] = useMutation<
CreateOneServerlessFunctionItemMutation,
CreateOneServerlessFunctionItemMutationVariables
>(CREATE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const [updateServerlessFunctionMutation] = useMutation<
UpdateOneServerlessFunctionMutation,
UpdateOneServerlessFunctionMutationVariables
>(UPDATE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const [deleteServerlessFunctionMutation] = useMutation<
DeleteOneServerlessFunctionMutation,
DeleteOneServerlessFunctionMutationVariables
>(DELETE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const createServerlessFunction = useCallback(
async (
variables: CreateOneServerlessFunctionItemMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof createServerlessFunctionMutation>>
>
> => {
try {
const result = await createServerlessFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_MANY_SERVERLESS_FUNCTIONS) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[
createServerlessFunctionMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const updateServerlessFunction = useCallback(
async (
variables: UpdateOneServerlessFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updateServerlessFunctionMutation>>
>
> => {
try {
const result = await updateServerlessFunctionMutation({
variables,
refetchQueries: [
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[
updateServerlessFunctionMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const deleteServerlessFunction = useCallback(
async (
variables: DeleteOneServerlessFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof deleteServerlessFunctionMutation>>
>
> => {
try {
const result = await deleteServerlessFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[
deleteServerlessFunctionMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
return {
createServerlessFunction,
updateServerlessFunction,
deleteServerlessFunction,
};
};
@@ -1,36 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { PUBLISH_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/publishOneServerlessFunction';
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
type PublishOneServerlessFunctionMutation,
type PublishOneServerlessFunctionMutationVariables,
type PublishServerlessFunctionInput,
} from '~/generated-metadata/graphql';
export const usePublishOneServerlessFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
PublishOneServerlessFunctionMutation,
PublishOneServerlessFunctionMutationVariables
>(PUBLISH_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const publishOneServerlessFunction = async (
input: PublishServerlessFunctionInput,
) => {
return await mutate({
variables: {
input,
},
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
],
});
};
return { publishOneServerlessFunction };
};
@@ -1,107 +0,0 @@
import { flattenSources } from '@/serverless-functions/utils/flattenSources';
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { useGetOneServerlessFunction } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunction';
import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { type Dispatch, type SetStateAction, useState } from 'react';
import { useRecoilState } from 'recoil';
import { type Sources } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type FindOneServerlessFunctionSourceCodeQuery,
type GetOneServerlessFunctionQuery,
} from '~/generated-metadata/graphql';
import { type ServerlessFunction } from '~/generated/graphql';
export type ServerlessFunctionNewFormValues = {
name: string;
description: string;
};
export type ServerlessFunctionFormValues = ServerlessFunctionNewFormValues & {
code: Sources;
};
type SetServerlessFunctionFormValues = Dispatch<
SetStateAction<ServerlessFunctionFormValues>
>;
export const useServerlessFunctionUpdateFormState = ({
serverlessFunctionId,
serverlessFunctionVersion = 'draft',
}: {
serverlessFunctionId: string;
serverlessFunctionVersion?: string;
}): {
formValues: ServerlessFunctionFormValues;
serverlessFunction: ServerlessFunction | null;
setFormValues: SetServerlessFunctionFormValues;
loading: boolean;
} => {
const [formValues, setFormValues] = useState<ServerlessFunctionFormValues>({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const { serverlessFunction, loading: serverlessFunctionLoading } =
useGetOneServerlessFunction({
id: serverlessFunctionId,
onCompleted: (data: GetOneServerlessFunctionQuery) => {
const fn = data?.findOneServerlessFunction;
if (isDefined(fn)) {
setFormValues((prevState) => ({
...prevState,
name: fn.name || '',
description: fn.description || '',
}));
}
},
});
const { loading: serverlessFunctionSourceCodeLoading } =
useGetOneServerlessFunctionSourceCode({
id: serverlessFunctionId,
version: serverlessFunctionVersion,
onCompleted: async (data: FindOneServerlessFunctionSourceCodeQuery) => {
const code = data?.getServerlessFunctionSourceCode;
setFormValues((prevState) => ({
...prevState,
code: code || prevState.code,
}));
if (serverlessFunctionTestData.shouldInitInput) {
const flattenedCode = flattenSources(code);
const sourceCode = flattenedCode.find(
(flatCode) =>
flatCode.path === serverlessFunction?.sourceHandlerPath,
);
if (isDefined(sourceCode)) {
const functionInput = await getFunctionInputFromSourceCode(
sourceCode.content,
);
setServerlessFunctionTestData((prev) => ({
...prev,
input: functionInput,
shouldInitInput: false,
}));
}
}
},
});
return {
formValues,
setFormValues,
serverlessFunction,
loading: serverlessFunctionSourceCodeLoading || serverlessFunctionLoading,
};
};
@@ -54,9 +54,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -73,9 +73,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -133,9 +133,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -152,9 +152,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -200,9 +200,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -219,9 +219,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -267,9 +267,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -286,9 +286,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -305,9 +305,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -35,9 +35,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -54,9 +54,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -73,9 +73,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -272,9 +272,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -291,9 +291,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -310,9 +310,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -511,9 +511,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -530,9 +530,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -549,9 +549,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -750,9 +750,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -769,9 +769,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -788,9 +788,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -807,9 +807,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -126,9 +126,9 @@ describe('getWorkflowVersionDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -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 { WorkflowActionServerlessFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionServerlessFunction';
import { WorkflowActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionLogicFunction';
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';
@@ -106,7 +106,7 @@ export const WorkflowRunStepNodeDetail = ({
switch (stepDefinition.definition.type) {
case 'CODE': {
return (
<WorkflowActionServerlessFunction
<WorkflowActionLogicFunction
key={stepId}
action={stepDefinition.definition}
actionOptions={{
@@ -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 { WorkflowActionServerlessFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionServerlessFunction';
import { WorkflowActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionLogicFunction';
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';
@@ -108,7 +108,7 @@ export const WorkflowStepDetail = ({
switch (stepDefinition.definition.type) {
case 'CODE': {
return (
<WorkflowActionServerlessFunction
<WorkflowActionLogicFunction
key={stepId}
action={stepDefinition.definition}
actionOptions={props}
@@ -41,9 +41,9 @@ describe('useUpdateStep', () => {
type: 'CODE' as const,
settings: {
input: {
serverlessFunctionId: 'id',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'id',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -28,9 +28,9 @@ describe('getIsDescendantOfIterator', () => {
nextStepIds: ['iterator1'],
settings: {
input: {
serverlessFunctionId: 'func2',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func2',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -48,9 +48,9 @@ describe('getIsDescendantOfIterator', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func3',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func3',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -89,9 +89,9 @@ describe('getIsDescendantOfIterator', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func4',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func4',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -27,9 +27,9 @@ describe('getStepInfoHistoryItem', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -12,9 +12,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: ['step2', 'step3'],
settings: {
input: {
serverlessFunctionId: 'func1',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func1',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -31,9 +31,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: ['step4'],
settings: {
input: {
serverlessFunctionId: 'func2',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func2',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -50,9 +50,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: ['step4'],
settings: {
input: {
serverlessFunctionId: 'func3',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func3',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -69,9 +69,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func4',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func4',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -17,9 +17,9 @@ const mockFlow = {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -36,9 +36,9 @@ const mockFlow = {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -139,9 +139,9 @@ describe('getWorkflowRunStepContext', () => {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -176,9 +176,9 @@ describe('getWorkflowRunStepContext', () => {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -21,18 +21,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
steps: [
{
id: stepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -87,18 +86,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
steps: [
{
id: stepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -154,18 +152,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
steps: [
{
id: stepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -188,18 +185,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
},
{
id: secondStepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -5,16 +5,16 @@ describe('getWorkflowVariablesUsedInStep', () => {
it('returns the variables used in a one-level object', () => {
const step: WorkflowStep = {
id: '42e8b60e-dd44-417a-875f-823d63f16819',
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
logicFunctionInput: {
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a.b.c.d}}',
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {},
errorHandlingOptions: {
@@ -97,16 +97,16 @@ Set {
it('returns all the variables used in a single field', () => {
const step: WorkflowStep = {
id: '42e8b60e-dd44-417a-875f-823d63f16819',
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
logicFunctionInput: {
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.b}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.c}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.d}}',
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {},
errorHandlingOptions: {
@@ -137,16 +137,16 @@ Set {
it('returns the variables used many times only once', () => {
const step: WorkflowStep = {
id: '42e8b60e-dd44-417a-875f-823d63f16819',
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
logicFunctionInput: {
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}}',
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {},
errorHandlingOptions: {
@@ -2,7 +2,7 @@ import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
import { lazy, Suspense } from 'react';
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
type WorkflowActionServerlessFunctionProps = {
type WorkflowActionLogicFunctionProps = {
action: WorkflowCodeAction;
actionOptions:
| {
@@ -14,32 +14,32 @@ type WorkflowActionServerlessFunctionProps = {
};
};
const WorkflowEditActionServerlessFunction = lazy(() =>
const WorkflowEditActionLogicFunction = lazy(() =>
import(
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunction'
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunction'
).then((module) => ({
default: module.WorkflowEditActionServerlessFunction,
default: module.WorkflowEditActionLogicFunction,
})),
);
const WorkflowReadonlyActionServerlessFunction = lazy(() =>
const WorkflowReadonlyActionLogicFunction = lazy(() =>
import(
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowReadonlyActionServerlessFunction'
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowReadonlyActionLogicFunction'
).then((module) => ({
default: module.WorkflowReadonlyActionServerlessFunction,
default: module.WorkflowReadonlyActionLogicFunction,
})),
);
export const WorkflowActionServerlessFunction = ({
export const WorkflowActionLogicFunction = ({
action,
actionOptions,
}: WorkflowActionServerlessFunctionProps) => {
}: WorkflowActionLogicFunctionProps) => {
return (
<Suspense fallback={<RightDrawerSkeletonLoader />}>
{actionOptions.readonly ? (
<WorkflowReadonlyActionServerlessFunction action={action} />
<WorkflowReadonlyActionLogicFunction action={action} />
) : (
<WorkflowEditActionServerlessFunction
<WorkflowEditActionLogicFunction
action={action}
actionOptions={actionOptions}
/>
@@ -1,5 +1,8 @@
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import {
type LogicFunctionFormValues,
useLogicFunctionUpdateFormState,
} from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { useFullScreenModal } from '@/ui/layout/fullscreen/hooks/useFullScreenModal';
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
@@ -9,11 +12,11 @@ import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue';
import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton';
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
import { INDEX_FILE_NAME } from '@/serverless-functions/constants/IndexFileName';
import { useTestServerlessFunction } from '@/serverless-functions/hooks/useTestServerlessFunction';
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/serverless-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
import { INDEX_FILE_NAME } from '@/logic-functions/constants/IndexFileName';
import { useTestLogicFunction } from '@/logic-functions/hooks/useTestLogicFunction';
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { TextArea } from '@/ui/input/components/TextArea';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -22,19 +25,22 @@ 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 { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunctionFields';
import { WorkflowServerlessFunctionCodeEditor } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowServerlessFunctionCodeEditor';
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
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 { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
import {
type LogicFunctionTestData,
logicFunctionTestDataFamilyState,
} from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { SOURCE_FOLDER_NAME } from '@/serverless-functions/constants/SourceFolderName';
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
import { usePersistServerlessFunction } from '@/settings/serverless-functions/hooks/usePersistServerlessFunction';
import { SOURCE_FOLDER_NAME } from '@/logic-functions/constants/SourceFolderName';
import { computeNewSources } from '@/logic-functions/utils/computeNewSources';
import { usePersistLogicFunction } from '@/settings/logic-functions/hooks/usePersistLogicFunction';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { CODE_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/CodeAction';
import { type Monaco } from '@monaco-editor/react';
@@ -71,7 +77,7 @@ const StyledFullScreenCodeEditorContainer = styled.div`
min-height: 0;
`;
type WorkflowEditActionServerlessFunctionProps = {
type WorkflowEditActionLogicFunctionProps = {
action: WorkflowCodeAction;
actionOptions:
| {
@@ -83,24 +89,24 @@ type WorkflowEditActionServerlessFunctionProps = {
};
};
type ServerlessFunctionInputFormData = {
[field: string]: string | ServerlessFunctionInputFormData;
type LogicFunctionInputFormData = {
[field: string]: string | LogicFunctionInputFormData;
};
export const WorkflowEditActionServerlessFunction = ({
export const WorkflowEditActionLogicFunction = ({
action,
actionOptions,
}: WorkflowEditActionServerlessFunctionProps) => {
}: WorkflowEditActionLogicFunctionProps) => {
const { t } = useLingui();
const [isFullScreen, setIsFullScreen] = useState(false);
const isMobile = useIsMobile();
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
const fullScreenFocusId = `code-editor-fullscreen-${serverlessFunctionId}`;
const logicFunctionId = action.settings.input.logicFunctionId;
const fullScreenFocusId = `code-editor-fullscreen-${logicFunctionId}`;
const activeTabId = useRecoilComponentValue(
activeTabIdComponentState,
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
);
const { updateServerlessFunction } = usePersistServerlessFunction();
const { updateLogicFunction } = usePersistLogicFunction();
const { getUpdatableWorkflowVersion } =
useGetUpdatableWorkflowVersionOrThrow();
@@ -109,21 +115,23 @@ export const WorkflowEditActionServerlessFunction = ({
);
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
const { availablePackages } = useGetAvailablePackages({
id: serverlessFunctionId,
id: logicFunctionId,
});
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const [logicFunctionTestData, setLogicFunctionTestData] =
useRecoilState<LogicFunctionTestData>(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const [functionInput, setFunctionInput] =
useState<ServerlessFunctionInputFormData>(
action.settings.input.serverlessFunctionInput,
useState<LogicFunctionInputFormData>(
action.settings.input.logicFunctionInput,
);
const { formValues, setFormValues, loading } =
useServerlessFunctionUpdateFormState({
serverlessFunctionId,
serverlessFunctionVersion: 'draft',
useLogicFunctionUpdateFormState({
logicFunctionId,
logicFunctionVersion: 'draft',
});
const updateOutputSchemaFromTestResult = async (testResult: object) => {
@@ -137,15 +145,15 @@ export const WorkflowEditActionServerlessFunction = ({
});
};
const { testServerlessFunction, isTesting } = useTestServerlessFunction({
serverlessFunctionId,
const { testLogicFunction, isTesting } = useTestLogicFunction({
logicFunctionId,
callback: updateOutputSchemaFromTestResult,
});
const handleSave = useDebouncedCallback(async () => {
await updateServerlessFunction({
await updateLogicFunction({
input: {
id: serverlessFunctionId,
id: logicFunctionId,
update: {
name: formValues.name,
description: formValues.description,
@@ -159,7 +167,7 @@ export const WorkflowEditActionServerlessFunction = ({
if (actionOptions.readonly === true) {
return;
}
setFormValues((prevState) => {
setFormValues((prevState: LogicFunctionFormValues) => {
return {
...prevState,
code: computeNewSources({
@@ -186,15 +194,15 @@ export const WorkflowEditActionServerlessFunction = ({
const newFunctionInput = await getFunctionInputFromSourceCode(sourceCode);
const newMergedInput = mergeDefaultFunctionInputAndFunctionInput({
newInput: newFunctionInput,
oldInput: action.settings.input.serverlessFunctionInput,
oldInput: action.settings.input.logicFunctionInput,
});
const newMergedTestInput = mergeDefaultFunctionInputAndFunctionInput({
newInput: newFunctionInput,
oldInput: serverlessFunctionTestData.input,
oldInput: logicFunctionTestData.input,
});
setFunctionInput(newMergedInput);
setServerlessFunctionTestData((prev) => ({
setLogicFunctionTestData((prev) => ({
...prev,
input: newMergedTestInput,
}));
@@ -214,7 +222,7 @@ export const WorkflowEditActionServerlessFunction = ({
},
input: {
...action.settings.input,
serverlessFunctionInput: newMergedInput,
logicFunctionInput: newMergedInput,
},
},
});
@@ -233,7 +241,7 @@ export const WorkflowEditActionServerlessFunction = ({
...action.settings,
input: {
...action.settings.input,
serverlessFunctionInput: updatedFunctionInput,
logicFunctionInput: updatedFunctionInput,
},
},
});
@@ -245,11 +253,11 @@ export const WorkflowEditActionServerlessFunction = ({
}
const updatedTestFunctionInput = setNestedValue(
serverlessFunctionTestData.input,
logicFunctionTestData.input,
path,
value,
);
setServerlessFunctionTestData((prev) => ({
setLogicFunctionTestData((prev) => ({
...prev,
input: updatedTestFunctionInput,
}));
@@ -261,7 +269,7 @@ export const WorkflowEditActionServerlessFunction = ({
}
if (!isTesting) {
await testServerlessFunction();
await testLogicFunction();
}
};
@@ -302,19 +310,19 @@ export const WorkflowEditActionServerlessFunction = ({
const tabs = [
{
id: WorkflowServerlessFunctionTabId.CODE,
id: WorkflowLogicFunctionTabId.CODE,
title: t`Code`,
Icon: IconCode,
},
{
id: WorkflowServerlessFunctionTabId.TEST,
id: WorkflowLogicFunctionTabId.TEST,
title: t`Test`,
Icon: IconPlayerPlay,
},
];
useEffect(() => {
setFunctionInput(action.settings.input.serverlessFunctionInput);
setFunctionInput(action.settings.input.logicFunctionInput);
}, [action]);
useHotkeysOnFocusedElement({
@@ -328,7 +336,7 @@ export const WorkflowEditActionServerlessFunction = ({
dependencies: [isFullScreen],
});
const testLogsTextAreaId = `${serverlessFunctionId}-test-logs`;
const testLogsTextAreaId = `${logicFunctionId}-test-logs`;
const breadcrumbLinks: BreadcrumbProps['links'] = [
{
@@ -358,7 +366,7 @@ export const WorkflowEditActionServerlessFunction = ({
handleExitFullScreen();
}
},
listenerId: `full-screen-overlay-${serverlessFunctionId}`,
listenerId: `full-screen-overlay-${logicFunctionId}`,
enabled: isFullScreen,
});
@@ -383,7 +391,7 @@ export const WorkflowEditActionServerlessFunction = ({
const fullScreenOverlay = renderFullScreenModal(
<div data-globally-prevent-click-outside="true">
<WorkflowEditActionServerlessFunctionFields
<WorkflowEditActionLogicFunctionFields
functionInput={functionInput}
VariablePicker={WorkflowVariablePicker}
onInputChange={handleInputChange}
@@ -415,20 +423,18 @@ export const WorkflowEditActionServerlessFunction = ({
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID
}
componentInstanceId={WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
<WorkflowStepBody>
{activeTabId === WorkflowServerlessFunctionTabId.CODE && (
{activeTabId === WorkflowLogicFunctionTabId.CODE && (
<>
<WorkflowEditActionServerlessFunctionFields
<WorkflowEditActionLogicFunctionFields
functionInput={functionInput}
VariablePicker={WorkflowVariablePicker}
onInputChange={handleInputChange}
readonly={actionOptions.readonly}
/>
<WorkflowServerlessFunctionCodeEditor
<WorkflowLogicFunctionCodeEditor
value={indexFileContent}
onChange={handleCodeChange}
onMount={handleEditorDidMount}
@@ -444,28 +450,26 @@ export const WorkflowEditActionServerlessFunction = ({
/>
</>
)}
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
{activeTabId === WorkflowLogicFunctionTabId.TEST && (
<>
<WorkflowEditActionServerlessFunctionFields
functionInput={serverlessFunctionTestData.input}
<WorkflowEditActionLogicFunctionFields
functionInput={logicFunctionTestData.input}
onInputChange={handleTestInputChange}
readonly={actionOptions.readonly}
/>
<StyledCodeEditorContainer>
<InputLabel>{t`Result`}</InputLabel>
<ServerlessFunctionExecutionResult
serverlessFunctionTestData={serverlessFunctionTestData}
<LogicFunctionExecutionResult
logicFunctionTestData={logicFunctionTestData}
isTesting={isTesting}
/>
</StyledCodeEditorContainer>
{serverlessFunctionTestData.output.logs.length > 0 && (
{logicFunctionTestData.output.logs.length > 0 && (
<StyledCodeEditorContainer>
<InputLabel>{t`Logs`}</InputLabel>
<TextArea
textAreaId={testLogsTextAreaId}
value={
isTesting ? '' : serverlessFunctionTestData.output.logs
}
value={isTesting ? '' : logicFunctionTestData.output.logs}
maxRows={20}
disabled
/>
@@ -478,7 +482,7 @@ export const WorkflowEditActionServerlessFunction = ({
<WorkflowStepFooter
stepId={action.id}
additionalActions={
activeTabId === WorkflowServerlessFunctionTabId.TEST
activeTabId === WorkflowLogicFunctionTabId.TEST
? [
<CmdEnterActionButton
title={t`Test`}
@@ -18,7 +18,7 @@ const StyledContainer = styled.div`
}
`;
type WorkflowEditActionServerlessFunctionFieldsProps = {
type WorkflowEditActionLogicFunctionFieldsProps = {
functionInput: FunctionInput;
path?: string[];
readonly?: boolean;
@@ -26,13 +26,13 @@ type WorkflowEditActionServerlessFunctionFieldsProps = {
VariablePicker?: VariablePickerComponent;
};
export const WorkflowEditActionServerlessFunctionFields = ({
export const WorkflowEditActionLogicFunctionFields = ({
functionInput,
path = [],
readonly,
onInputChange,
VariablePicker,
}: WorkflowEditActionServerlessFunctionFieldsProps) => {
}: WorkflowEditActionLogicFunctionFieldsProps) => {
return (
<StyledContainer>
{Object.entries(functionInput).map(([inputKey, inputValue]) => {
@@ -44,7 +44,7 @@ export const WorkflowEditActionServerlessFunctionFields = ({
<div key={pathKey}>
<InputLabel>{inputKey}</InputLabel>
<FormNestedFieldInputContainer>
<WorkflowEditActionServerlessFunctionFields
<WorkflowEditActionLogicFunctionFields
functionInput={inputValue}
path={currentPath}
readonly={readonly}
@@ -24,7 +24,7 @@ const StyledFullScreenButtonContainer = styled.div`
z-index: 1;
`;
type WorkflowServerlessFunctionCodeEditorProps = {
type WorkflowLogicFunctionCodeEditorProps = {
value?: string;
onChange: (value: string) => void;
onMount: (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void;
@@ -34,7 +34,7 @@ type WorkflowServerlessFunctionCodeEditorProps = {
onEnterFullScreen?: () => void;
};
export const WorkflowServerlessFunctionCodeEditor = ({
export const WorkflowLogicFunctionCodeEditor = ({
value,
onChange,
onMount,
@@ -42,7 +42,7 @@ export const WorkflowServerlessFunctionCodeEditor = ({
readonly = false,
fullScreenMode = false,
onEnterFullScreen,
}: WorkflowServerlessFunctionCodeEditorProps) => {
}: WorkflowLogicFunctionCodeEditorProps) => {
const { t } = useLingui();
return (
@@ -1,41 +1,40 @@
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import { useLogicFunctionUpdateFormState } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
import { INDEX_FILE_NAME } from '@/serverless-functions/constants/IndexFileName';
import { INDEX_FILE_NAME } from '@/logic-functions/constants/IndexFileName';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunctionFields';
import { WorkflowEditActionLogicFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunctionFields';
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 '@/serverless-functions/constants/SourceFolderName';
import { SOURCE_FOLDER_NAME } from '@/logic-functions/constants/SourceFolderName';
const StyledCodeEditorContainer = styled.div`
display: flex;
flex-direction: column;
`;
type WorkflowReadonlyActionServerlessFunctionProps = {
type WorkflowReadonlyActionLogicFunctionProps = {
action: WorkflowCodeAction;
};
export const WorkflowReadonlyActionServerlessFunction = ({
export const WorkflowReadonlyActionLogicFunction = ({
action,
}: WorkflowReadonlyActionServerlessFunctionProps) => {
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
const serverlessFunctionVersion =
action.settings.input.serverlessFunctionVersion;
}: WorkflowReadonlyActionLogicFunctionProps) => {
const logicFunctionId = action.settings.input.logicFunctionId;
const logicFunctionVersion = action.settings.input.logicFunctionVersion;
const { availablePackages } = useGetAvailablePackages({
id: serverlessFunctionId,
id: logicFunctionId,
});
const { formValues, loading } = useServerlessFunctionUpdateFormState({
serverlessFunctionId,
serverlessFunctionVersion,
const { formValues, loading } = useLogicFunctionUpdateFormState({
logicFunctionId,
logicFunctionVersion,
});
const handleEditorDidMount = async (
@@ -64,8 +63,8 @@ export const WorkflowReadonlyActionServerlessFunction = ({
return (
<>
<WorkflowStepBody>
<WorkflowEditActionServerlessFunctionFields
functionInput={action.settings.input.serverlessFunctionInput}
<WorkflowEditActionLogicFunctionFields
functionInput={action.settings.input.logicFunctionInput}
readonly
/>
<StyledCodeEditorContainer>
@@ -1,5 +1,5 @@
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
import { WorkflowEditActionServerlessFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunction';
import { WorkflowEditActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunction';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { graphql, HttpResponse } from 'msw';
import { fn } from 'storybook/test';
@@ -19,9 +19,9 @@ const DEFAULT_ACTION: WorkflowCodeAction = {
valid: false,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionVersion: 'draft',
serverlessFunctionInput: {},
logicFunctionId: '',
logicFunctionVersion: 'draft',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -42,9 +42,9 @@ const CONFIGURED_ACTION: WorkflowCodeAction = {
valid: true,
settings: {
input: {
serverlessFunctionId: 'test-function-id',
serverlessFunctionVersion: 'draft',
serverlessFunctionInput: {
logicFunctionId: 'test-function-id',
logicFunctionVersion: 'draft',
logicFunctionInput: {
name: 'John Doe',
email: 'john@example.com',
score: 95,
@@ -75,21 +75,21 @@ const CONFIGURED_ACTION: WorkflowCodeAction = {
},
};
const meta: Meta<typeof WorkflowEditActionServerlessFunction> = {
const meta: Meta<typeof WorkflowEditActionLogicFunction> = {
title: 'Modules/Workflow/Actions/Code/EditAction',
component: WorkflowEditActionServerlessFunction,
component: WorkflowEditActionLogicFunction,
parameters: {
msw: {
handlers: [
...graphqlMocks.handlers,
graphql.query('FindManyServerlessFunctions', () => {
graphql.query('GetManyLogicFunctions', () => {
return HttpResponse.json({
data: {
findManyServerlessFunctions: [
findManyLogicFunctions: [
{
id: 'test-function-id',
name: 'Test Function',
description: 'A test serverless function',
description: 'A test logic function',
runtime: 'nodejs22.x',
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
@@ -98,13 +98,13 @@ const meta: Meta<typeof WorkflowEditActionServerlessFunction> = {
},
});
}),
graphql.query('FindOneServerlessFunction', () => {
graphql.query('GetOneLogicFunction', () => {
return HttpResponse.json({
data: {
findOneServerlessFunction: {
findOneLogicFunction: {
id: 'test-function-id',
name: 'Test Function',
description: 'A test serverless function',
description: 'A test logic function',
runtime: 'nodejs22.x',
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
@@ -138,7 +138,7 @@ const meta: Meta<typeof WorkflowEditActionServerlessFunction> = {
export default meta;
type Story = StoryObj<typeof WorkflowEditActionServerlessFunction>;
type Story = StoryObj<typeof WorkflowEditActionLogicFunction>;
export const Default: Story = {
args: {
@@ -207,9 +207,9 @@ export const EmptyFunction: Story = {
settings: {
...DEFAULT_ACTION.settings,
input: {
serverlessFunctionId: '',
serverlessFunctionVersion: 'draft',
serverlessFunctionInput: {},
logicFunctionId: '',
logicFunctionVersion: 'draft',
logicFunctionInput: {},
},
},
},
@@ -0,0 +1,2 @@
export const WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID =
'workflow-logic-function-tab-list-component-id';
@@ -1,2 +0,0 @@
export const WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID =
'workflow-serverless-function-tab-list-component-id';
@@ -1,14 +1,14 @@
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
import { ServerlessFunctionExecutionStatus } from '~/generated-metadata/graphql';
import { LogicFunctionExecutionStatus } from '~/generated-metadata/graphql';
export type ServerlessFunctionTestData = {
export type LogicFunctionTestData = {
input: { [field: string]: any };
shouldInitInput: boolean;
output: {
data?: string;
logs: string;
duration?: number;
status?: ServerlessFunctionExecutionStatus;
status?: LogicFunctionExecutionStatus;
error?: string;
};
language: 'plaintext' | 'json';
@@ -18,14 +18,14 @@ export type ServerlessFunctionTestData = {
export const DEFAULT_OUTPUT_VALUE = {
data: 'Enter an input above then press "Test"',
logs: '',
status: ServerlessFunctionExecutionStatus.IDLE,
status: LogicFunctionExecutionStatus.IDLE,
};
export const serverlessFunctionTestDataFamilyState = createFamilyState<
ServerlessFunctionTestData,
export const logicFunctionTestDataFamilyState = createFamilyState<
LogicFunctionTestData,
string
>({
key: 'serverlessFunctionTestDataFamilyState',
key: 'logicFunctionTestDataFamilyState',
defaultValue: {
language: 'plaintext',
height: 64,
@@ -0,0 +1,6 @@
export type WorkflowLogicFunctionTabIdType = 'code' | 'test';
export enum WorkflowLogicFunctionTabId {
CODE = 'code',
TEST = 'test',
}
@@ -1,6 +0,0 @@
export type WorkflowServerlessFunctionTabIdType = 'code' | 'test';
export enum WorkflowServerlessFunctionTabId {
CODE = 'code',
TEST = 'test',
}
@@ -5,7 +5,7 @@ export const CODE_ACTION: {
type: Extract<WorkflowActionType, 'CODE'>;
icon: string;
} = {
defaultLabel: 'Code - Serverless Function',
defaultLabel: 'Code - Logic Function',
type: 'CODE',
icon: 'IconCode',
};
@@ -57,7 +57,7 @@ export const useVariableDropdown = ({
);
const setActiveTabId = useSetRecoilComponentState(
activeTabIdComponentState,
'workflow-serverless-function-tab-list-component-id',
'workflow-logic-function-tab-list-component-id',
);
const setWorkflowDiagram = useSetRecoilComponentState(
workflowDiagramComponentState,