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: {
phones: {
primaryPhoneNumber: {
ilike: '%1234567890%',
and: [
{
not: {
phones: {
primaryPhoneNumber: {
ilike: '%1234567890%',
},
},
},
},
},
],
},
{
and: [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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