Remove serverless functions on version archivation (#9535)

Fixes https://github.com/twentyhq/core-team-issues/issues/52
- contrary to title, we do not remove serverless functions on workflow
version archivation because serverless fucntion might be used in another
workflow version
- we fix the serverless funciton version displayed in the code step
- we allow test function version in step display right drawer
- we delete serverless function only when serverless function has no
published version
This commit is contained in:
martmull
2025-01-13 14:09:57 +01:00
committed by GitHub
parent 8643eaa28f
commit 5783d68d62
10 changed files with 64 additions and 33 deletions

View File

@ -591,13 +591,17 @@ describe('should work as expected for the different field types', () => {
], ],
}, },
{ {
not: { and: [
phones: { {
primaryPhoneNumber: { not: {
ilike: '%1234567890%', phones: {
primaryPhoneNumber: {
ilike: '%1234567890%',
},
},
}, },
}, },
}, ],
}, },
{ {
and: [ and: [

View File

@ -848,7 +848,7 @@ const computeFilterRecordGqlOperationFilter = (
], ],
}; };
case ViewFilterOperand.DoesNotContain: case ViewFilterOperand.DoesNotContain:
return { return {
and: [ and: [
{ {
not: { not: {

View File

@ -323,9 +323,7 @@ export const isRecordMatchingFilter = ({
case FieldMetadataType.Phones: { case FieldMetadataType.Phones: {
const phonesFilter = filterValue as PhonesFilter; const phonesFilter = filterValue as PhonesFilter;
const keys: (keyof PhonesFilter)[] = [ const keys: (keyof PhonesFilter)[] = ['primaryPhoneNumber'];
'primaryPhoneNumber'
];
return keys.some((key) => { return keys.some((key) => {
const value = phonesFilter[key]; const value = phonesFilter[key];

View File

@ -3,10 +3,15 @@ import { useRecoilState } from 'recoil';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState'; import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
export const useTestServerlessFunction = ( export const useTestServerlessFunction = ({
serverlessFunctionId: string, serverlessFunctionId,
callback?: (testResult: object) => void, serverlessFunctionVersion = 'draft',
) => { callback,
}: {
serverlessFunctionId: string;
serverlessFunctionVersion?: string;
callback?: (testResult: object) => void;
}) => {
const { executeOneServerlessFunction } = useExecuteOneServerlessFunction(); const { executeOneServerlessFunction } = useExecuteOneServerlessFunction();
const [serverlessFunctionTestData, setServerlessFunctionTestData] = const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId)); useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
@ -15,7 +20,7 @@ export const useTestServerlessFunction = (
const result = await executeOneServerlessFunction({ const result = await executeOneServerlessFunction({
id: serverlessFunctionId, id: serverlessFunctionId,
payload: serverlessFunctionTestData.input, payload: serverlessFunctionTestData.input,
version: 'draft', version: serverlessFunctionVersion,
}); });
if (isDefined(result?.data?.executeOneServerlessFunction?.data)) { if (isDefined(result?.data?.executeOneServerlessFunction?.data)) {

View File

@ -36,7 +36,7 @@ describe('useServerlessFunctionUpdateFormState', () => {
}, },
); );
const { result } = renderHook( const { result } = renderHook(
() => useServerlessFunctionUpdateFormState(serverlessFunctionId), () => useServerlessFunctionUpdateFormState({ serverlessFunctionId }),
{ {
wrapper: RecoilRoot, wrapper: RecoilRoot,
}, },

View File

@ -20,9 +20,13 @@ type SetServerlessFunctionFormValues = Dispatch<
SetStateAction<ServerlessFunctionFormValues> SetStateAction<ServerlessFunctionFormValues>
>; >;
export const useServerlessFunctionUpdateFormState = ( export const useServerlessFunctionUpdateFormState = ({
serverlessFunctionId: string, serverlessFunctionId,
): { serverlessFunctionVersion = 'draft',
}: {
serverlessFunctionId: string;
serverlessFunctionVersion?: string;
}): {
formValues: ServerlessFunctionFormValues; formValues: ServerlessFunctionFormValues;
setFormValues: SetServerlessFunctionFormValues; setFormValues: SetServerlessFunctionFormValues;
loading: boolean; loading: boolean;
@ -43,7 +47,7 @@ export const useServerlessFunctionUpdateFormState = (
const { loading } = useGetOneServerlessFunctionSourceCode({ const { loading } = useGetOneServerlessFunctionSourceCode({
id: serverlessFunctionId, id: serverlessFunctionId,
version: 'draft', version: serverlessFunctionVersion,
onCompleted: (data: FindOneServerlessFunctionSourceCodeQuery) => { onCompleted: (data: FindOneServerlessFunctionSourceCodeQuery) => {
const newState = { const newState = {
code: data?.getServerlessFunctionSourceCode || undefined, code: data?.getServerlessFunctionSourceCode || undefined,

View File

@ -77,6 +77,8 @@ export const WorkflowEditActionFormServerlessFunction = ({
actionOptions, actionOptions,
}: WorkflowEditActionFormServerlessFunctionProps) => { }: WorkflowEditActionFormServerlessFunctionProps) => {
const serverlessFunctionId = action.settings.input.serverlessFunctionId; const serverlessFunctionId = action.settings.input.serverlessFunctionId;
const serverlessFunctionVersion =
action.settings.input.serverlessFunctionVersion;
const theme = useTheme(); const theme = useTheme();
const tabListId = `${WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}_${serverlessFunctionId}`; const tabListId = `${WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}_${serverlessFunctionId}`;
const { activeTabId, setActiveTabId } = useTabList(tabListId); const { activeTabId, setActiveTabId } = useTabList(tabListId);
@ -99,7 +101,10 @@ export const WorkflowEditActionFormServerlessFunction = ({
); );
const { formValues, setFormValues, loading } = const { formValues, setFormValues, loading } =
useServerlessFunctionUpdateFormState(serverlessFunctionId); useServerlessFunctionUpdateFormState({
serverlessFunctionId,
serverlessFunctionVersion,
});
const updateOutputSchemaFromTestResult = async (testResult: object) => { const updateOutputSchemaFromTestResult = async (testResult: object) => {
if (actionOptions.readonly === true) { if (actionOptions.readonly === true) {
@ -112,10 +117,11 @@ export const WorkflowEditActionFormServerlessFunction = ({
}); });
}; };
const { testServerlessFunction } = useTestServerlessFunction( const { testServerlessFunction } = useTestServerlessFunction({
serverlessFunctionId, serverlessFunctionId,
updateOutputSchemaFromTestResult, serverlessFunctionVersion,
); callback: updateOutputSchemaFromTestResult,
});
const handleSave = useDebouncedCallback(async () => { const handleSave = useDebouncedCallback(async () => {
await updateOneServerlessFunction({ await updateOneServerlessFunction({
@ -314,7 +320,6 @@ export const WorkflowEditActionFormServerlessFunction = ({
<WorkflowEditActionFormServerlessFunctionFields <WorkflowEditActionFormServerlessFunctionFields
functionInput={serverlessFunctionTestData.input} functionInput={serverlessFunctionTestData.input}
onInputChange={handleTestInputChange} onInputChange={handleTestInputChange}
readonly={actionOptions.readonly}
/> />
<StyledCodeEditorContainer> <StyledCodeEditorContainer>
<InputLabel>Result</InputLabel> <InputLabel>Result</InputLabel>

View File

@ -37,9 +37,10 @@ export const SettingsServerlessFunctionDetail = () => {
useUpdateOneServerlessFunction(serverlessFunctionId); useUpdateOneServerlessFunction(serverlessFunctionId);
const { publishOneServerlessFunction } = usePublishOneServerlessFunction(); const { publishOneServerlessFunction } = usePublishOneServerlessFunction();
const { formValues, setFormValues, loading } = const { formValues, setFormValues, loading } =
useServerlessFunctionUpdateFormState(serverlessFunctionId); useServerlessFunctionUpdateFormState({ serverlessFunctionId });
const { testServerlessFunction } = const { testServerlessFunction } = useTestServerlessFunction({
useTestServerlessFunction(serverlessFunctionId); serverlessFunctionId,
});
const { code: latestVersionCode } = useGetOneServerlessFunctionSourceCode({ const { code: latestVersionCode } = useGetOneServerlessFunctionSourceCode({
id: serverlessFunctionId, id: serverlessFunctionId,
version: 'latest', version: 'latest',

View File

@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { basename, dirname, join } from 'path'; import { basename, dirname, join } from 'path';
import deepEqual from 'deep-equal'; import deepEqual from 'deep-equal';
import { Repository } from 'typeorm'; import { IsNull, Not, Repository } from 'typeorm';
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface'; import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
@ -58,6 +58,15 @@ export class ServerlessFunctionService {
return this.serverlessFunctionRepository.findBy(where); return this.serverlessFunctionRepository.findBy(where);
} }
async hasServerlessFunctionPublishedVersion(serverlessFunctionId: string) {
return await this.serverlessFunctionRepository.exists({
where: {
id: serverlessFunctionId,
latestVersion: Not(IsNull()),
},
});
}
async getServerlessFunctionSourceCode( async getServerlessFunctionSourceCode(
workspaceId: string, workspaceId: string,
id: string, id: string,

View File

@ -511,11 +511,16 @@ export class WorkflowVersionStepWorkspaceService {
}) { }) {
switch (step.type) { switch (step.type) {
case WorkflowActionType.CODE: { case WorkflowActionType.CODE: {
await this.serverlessFunctionService.deleteOneServerlessFunction({ if (
id: step.settings.input.serverlessFunctionId, !(await this.serverlessFunctionService.hasServerlessFunctionPublishedVersion(
workspaceId, step.settings.input.serverlessFunctionId,
}); ))
) {
await this.serverlessFunctionService.deleteOneServerlessFunction({
id: step.settings.input.serverlessFunctionId,
workspaceId,
});
}
break; break;
} }
} }