Fix use as draft (#9718)

- remove delete serverless function when archiving workflow version
- update copy serverless function to reset serverless function to old
version
- remove createNewWorkflowVersion and use createDraftFromWorkflowVersion
- fix step update issue and optimistic rendering when generate draft
from active version
This commit is contained in:
martmull
2025-01-21 15:44:52 +01:00
committed by GitHub
parent d8815d7ebf
commit ed7c48e12a
22 changed files with 207 additions and 309 deletions

View File

@ -551,7 +551,7 @@ export type Mutation = {
challenge: LoginToken; challenge: LoginToken;
checkoutSession: SessionEntity; checkoutSession: SessionEntity;
computeStepOutputSchema: Scalars['JSON']['output']; computeStepOutputSchema: Scalars['JSON']['output'];
createDraftFromWorkflowVersion: Scalars['Boolean']['output']; createDraftFromWorkflowVersion: WorkflowVersion;
createOIDCIdentityProvider: SetupSsoOutput; createOIDCIdentityProvider: SetupSsoOutput;
createOneAppToken: AppToken; createOneAppToken: AppToken;
createOneField: Field; createOneField: Field;
@ -1711,6 +1711,11 @@ export type WorkflowRun = {
workflowRunId: Scalars['UUID']['output']; workflowRunId: Scalars['UUID']['output'];
}; };
export type WorkflowVersion = {
__typename?: 'WorkflowVersion';
workflowVersionId: Scalars['UUID']['output'];
};
export type Workspace = { export type Workspace = {
__typename?: 'Workspace'; __typename?: 'Workspace';
activationStatus: WorkspaceActivationStatus; activationStatus: WorkspaceActivationStatus;

View File

@ -477,7 +477,7 @@ export type Mutation = {
challenge: LoginToken; challenge: LoginToken;
checkoutSession: SessionEntity; checkoutSession: SessionEntity;
computeStepOutputSchema: Scalars['JSON']; computeStepOutputSchema: Scalars['JSON'];
createDraftFromWorkflowVersion: Scalars['Boolean']; createDraftFromWorkflowVersion: WorkflowVersion;
createOIDCIdentityProvider: SetupSsoOutput; createOIDCIdentityProvider: SetupSsoOutput;
createOneAppToken: AppToken; createOneAppToken: AppToken;
createOneField: Field; createOneField: Field;
@ -1508,6 +1508,11 @@ export type WorkflowRun = {
workflowRunId: Scalars['UUID']; workflowRunId: Scalars['UUID'];
}; };
export type WorkflowVersion = {
__typename?: 'WorkflowVersion';
id: Scalars['UUID'];
};
export type Workspace = { export type Workspace = {
__typename?: 'Workspace'; __typename?: 'Workspace';
activationStatus: WorkspaceActivationStatus; activationStatus: WorkspaceActivationStatus;
@ -2131,6 +2136,13 @@ export type ComputeStepOutputSchemaMutationVariables = Exact<{
export type ComputeStepOutputSchemaMutation = { __typename?: 'Mutation', computeStepOutputSchema: any }; export type ComputeStepOutputSchemaMutation = { __typename?: 'Mutation', computeStepOutputSchema: any };
export type CreateDraftFromWorkflowVersionMutationVariables = Exact<{
input: CreateDraftFromWorkflowVersionInput;
}>;
export type CreateDraftFromWorkflowVersionMutation = { __typename?: 'Mutation', createDraftFromWorkflowVersion: { __typename?: 'WorkflowVersion', id: any } };
export type CreateWorkflowVersionStepMutationVariables = Exact<{ export type CreateWorkflowVersionStepMutationVariables = Exact<{
input: CreateWorkflowVersionStepInput; input: CreateWorkflowVersionStepInput;
}>; }>;
@ -2152,13 +2164,6 @@ export type DeleteWorkflowVersionStepMutationVariables = Exact<{
export type DeleteWorkflowVersionStepMutation = { __typename?: 'Mutation', deleteWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean } }; export type DeleteWorkflowVersionStepMutation = { __typename?: 'Mutation', deleteWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean } };
export type CreateDraftFromWorkflowVersionMutationVariables = Exact<{
input: CreateDraftFromWorkflowVersionInput;
}>;
export type CreateDraftFromWorkflowVersionMutation = { __typename?: 'Mutation', createDraftFromWorkflowVersion: boolean };
export type RunWorkflowVersionMutationVariables = Exact<{ export type RunWorkflowVersionMutationVariables = Exact<{
input: RunWorkflowVersionInput; input: RunWorkflowVersionInput;
}>; }>;
@ -4092,6 +4097,39 @@ export function useComputeStepOutputSchemaMutation(baseOptions?: Apollo.Mutation
export type ComputeStepOutputSchemaMutationHookResult = ReturnType<typeof useComputeStepOutputSchemaMutation>; export type ComputeStepOutputSchemaMutationHookResult = ReturnType<typeof useComputeStepOutputSchemaMutation>;
export type ComputeStepOutputSchemaMutationResult = Apollo.MutationResult<ComputeStepOutputSchemaMutation>; export type ComputeStepOutputSchemaMutationResult = Apollo.MutationResult<ComputeStepOutputSchemaMutation>;
export type ComputeStepOutputSchemaMutationOptions = Apollo.BaseMutationOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>; export type ComputeStepOutputSchemaMutationOptions = Apollo.BaseMutationOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>;
export const CreateDraftFromWorkflowVersionDocument = gql`
mutation CreateDraftFromWorkflowVersion($input: CreateDraftFromWorkflowVersionInput!) {
createDraftFromWorkflowVersion(input: $input) {
id
}
}
`;
export type CreateDraftFromWorkflowVersionMutationFn = Apollo.MutationFunction<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>;
/**
* __useCreateDraftFromWorkflowVersionMutation__
*
* To run a mutation, you first call `useCreateDraftFromWorkflowVersionMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateDraftFromWorkflowVersionMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createDraftFromWorkflowVersionMutation, { data, loading, error }] = useCreateDraftFromWorkflowVersionMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useCreateDraftFromWorkflowVersionMutation(baseOptions?: Apollo.MutationHookOptions<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>(CreateDraftFromWorkflowVersionDocument, options);
}
export type CreateDraftFromWorkflowVersionMutationHookResult = ReturnType<typeof useCreateDraftFromWorkflowVersionMutation>;
export type CreateDraftFromWorkflowVersionMutationResult = Apollo.MutationResult<CreateDraftFromWorkflowVersionMutation>;
export type CreateDraftFromWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>;
export const CreateWorkflowVersionStepDocument = gql` export const CreateWorkflowVersionStepDocument = gql`
mutation CreateWorkflowVersionStep($input: CreateWorkflowVersionStepInput!) { mutation CreateWorkflowVersionStep($input: CreateWorkflowVersionStepInput!) {
createWorkflowVersionStep(input: $input) { createWorkflowVersionStep(input: $input) {
@ -4197,37 +4235,6 @@ export function useDeleteWorkflowVersionStepMutation(baseOptions?: Apollo.Mutati
export type DeleteWorkflowVersionStepMutationHookResult = ReturnType<typeof useDeleteWorkflowVersionStepMutation>; export type DeleteWorkflowVersionStepMutationHookResult = ReturnType<typeof useDeleteWorkflowVersionStepMutation>;
export type DeleteWorkflowVersionStepMutationResult = Apollo.MutationResult<DeleteWorkflowVersionStepMutation>; export type DeleteWorkflowVersionStepMutationResult = Apollo.MutationResult<DeleteWorkflowVersionStepMutation>;
export type DeleteWorkflowVersionStepMutationOptions = Apollo.BaseMutationOptions<DeleteWorkflowVersionStepMutation, DeleteWorkflowVersionStepMutationVariables>; export type DeleteWorkflowVersionStepMutationOptions = Apollo.BaseMutationOptions<DeleteWorkflowVersionStepMutation, DeleteWorkflowVersionStepMutationVariables>;
export const CreateDraftFromWorkflowVersionDocument = gql`
mutation CreateDraftFromWorkflowVersion($input: CreateDraftFromWorkflowVersionInput!) {
createDraftFromWorkflowVersion(input: $input)
}
`;
export type CreateDraftFromWorkflowVersionMutationFn = Apollo.MutationFunction<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>;
/**
* __useCreateDraftFromWorkflowVersionMutation__
*
* To run a mutation, you first call `useCreateDraftFromWorkflowVersionMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateDraftFromWorkflowVersionMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createDraftFromWorkflowVersionMutation, { data, loading, error }] = useCreateDraftFromWorkflowVersionMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useCreateDraftFromWorkflowVersionMutation(baseOptions?: Apollo.MutationHookOptions<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>(CreateDraftFromWorkflowVersionDocument, options);
}
export type CreateDraftFromWorkflowVersionMutationHookResult = ReturnType<typeof useCreateDraftFromWorkflowVersionMutation>;
export type CreateDraftFromWorkflowVersionMutationResult = Apollo.MutationResult<CreateDraftFromWorkflowVersionMutation>;
export type CreateDraftFromWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<CreateDraftFromWorkflowVersionMutation, CreateDraftFromWorkflowVersionMutationVariables>;
export const RunWorkflowVersionDocument = gql` export const RunWorkflowVersionDocument = gql`
mutation RunWorkflowVersion($input: RunWorkflowVersionInput!) { mutation RunWorkflowVersion($input: RunWorkflowVersionInput!) {
runWorkflowVersion(input: $input) { runWorkflowVersion(input: $input) {

View File

@ -1,9 +1,11 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
export const OVERRIDE_WORKFLOW_DRAFT_VERSION = gql` export const CREATE_DRAFT_FROM_WORKFLOW_VERSION = gql`
mutation CreateDraftFromWorkflowVersion( mutation CreateDraftFromWorkflowVersion(
$input: CreateDraftFromWorkflowVersionInput! $input: CreateDraftFromWorkflowVersionInput!
) { ) {
createDraftFromWorkflowVersion(input: $input) createDraftFromWorkflowVersion(input: $input) {
id
}
} }
`; `;

View File

@ -1,36 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { WorkflowVersion } from '@/workflow/types/Workflow';
import { renderHook } from '@testing-library/react';
import { useCreateNewWorkflowVersion } from '../useCreateNewWorkflowVersion';
jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({
useCreateOneRecord: jest.fn(),
}));
describe('useCreateNewWorkflowVersion', () => {
it('should create workflow version', async () => {
const mockCreateOneRecord = jest.fn();
(useCreateOneRecord as jest.Mock).mockImplementation(() => ({
createOneRecord: mockCreateOneRecord,
}));
const workflowVersionData = {
workflowId: '123',
name: 'Test Version',
status: 'draft',
trigger: { type: 'manual' },
steps: [],
};
const { result } = renderHook(() => useCreateNewWorkflowVersion());
await result.current.createNewWorkflowVersion(
workflowVersionData as unknown as WorkflowVersion,
);
expect(useCreateOneRecord).toHaveBeenCalledWith({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
expect(mockCreateOneRecord).toHaveBeenCalledWith(workflowVersionData);
});
});

View File

@ -2,21 +2,11 @@ import { renderHook } from '@testing-library/react';
import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion'; import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
const mockCreateNewWorkflowVersion = jest.fn().mockResolvedValue({ const mockCreateDraftFromWorkflowVersion = jest.fn().mockResolvedValue('457');
id: '457',
name: 'toto',
createdAt: '2024-07-03T20:03:35.064Z',
updatedAt: '2024-07-03T20:03:35.064Z',
workflowId: '123',
__typename: 'WorkflowVersion',
status: 'DRAFT',
steps: [],
trigger: null,
});
jest.mock('@/workflow/hooks/useCreateNewWorkflowVersion', () => ({ jest.mock('@/workflow/hooks/useCreateDraftFromWorkflowVersion', () => ({
useCreateNewWorkflowVersion: () => ({ useCreateDraftFromWorkflowVersion: () => ({
createNewWorkflowVersion: mockCreateNewWorkflowVersion, createDraftFromWorkflowVersion: mockCreateDraftFromWorkflowVersion,
}), }),
})); }));
@ -44,19 +34,19 @@ describe('useGetUpdatableWorkflowVersion', () => {
it('should not create workflow version if draft version exists', async () => { it('should not create workflow version if draft version exists', async () => {
const { result } = renderHook(() => useGetUpdatableWorkflowVersion()); const { result } = renderHook(() => useGetUpdatableWorkflowVersion());
const workflowVersion = await result.current.getUpdatableWorkflowVersion( const workflowVersionId = await result.current.getUpdatableWorkflowVersion(
mockWorkflow('DRAFT'), mockWorkflow('DRAFT'),
); );
expect(mockCreateNewWorkflowVersion).not.toHaveBeenCalled(); expect(mockCreateDraftFromWorkflowVersion).not.toHaveBeenCalled();
expect(workflowVersion.id === '456'); expect(workflowVersionId).toEqual('456');
}); });
it('should create workflow version if no draft version exists', async () => { it('should create workflow version if no draft version exists', async () => {
const { result } = renderHook(() => useGetUpdatableWorkflowVersion()); const { result } = renderHook(() => useGetUpdatableWorkflowVersion());
const workflowVersion = await result.current.getUpdatableWorkflowVersion( const workflowVersionId = await result.current.getUpdatableWorkflowVersion(
mockWorkflow('ACTIVE'), mockWorkflow('ACTIVE'),
); );
expect(mockCreateNewWorkflowVersion).toHaveBeenCalled(); expect(mockCreateDraftFromWorkflowVersion).toHaveBeenCalled();
expect(workflowVersion.id === '457'); expect(workflowVersionId).toEqual('457');
}); });
}); });

View File

@ -28,7 +28,7 @@ export const useCreateDraftFromWorkflowVersion = () => {
const createDraftFromWorkflowVersion = async ( const createDraftFromWorkflowVersion = async (
input: CreateDraftFromWorkflowVersionInput, input: CreateDraftFromWorkflowVersionInput,
) => { ) => {
await mutate({ const result = await mutate({
variables: { input }, variables: { input },
awaitRefetchQueries: true, awaitRefetchQueries: true,
refetchQueries: [ refetchQueries: [
@ -40,6 +40,8 @@ export const useCreateDraftFromWorkflowVersion = () => {
}, },
], ],
}); });
return result?.data?.createDraftFromWorkflowVersion.id;
}; };
return { return {

View File

@ -1,23 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { WorkflowVersion } from '@/workflow/types/Workflow';
export const useCreateNewWorkflowVersion = () => {
const { createOneRecord: createOneWorkflowVersion } =
useCreateOneRecord<WorkflowVersion>({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const createNewWorkflowVersion = async (
workflowVersionData: Pick<
WorkflowVersion,
'workflowId' | 'name' | 'status' | 'trigger' | 'steps'
>,
) => {
return await createOneWorkflowVersion(workflowVersionData);
};
return {
createNewWorkflowVersion,
};
};

View File

@ -1,21 +1,18 @@
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion'; import { useCreateDraftFromWorkflowVersion } from '@/workflow/hooks/useCreateDraftFromWorkflowVersion';
export const useGetUpdatableWorkflowVersion = () => { export const useGetUpdatableWorkflowVersion = () => {
const { createNewWorkflowVersion } = useCreateNewWorkflowVersion(); const { createDraftFromWorkflowVersion } =
useCreateDraftFromWorkflowVersion();
const getUpdatableWorkflowVersion = async ( const getUpdatableWorkflowVersion = async (
workflow: WorkflowWithCurrentVersion, workflow: WorkflowWithCurrentVersion,
) => { ) => {
if (workflow.currentVersion.status === 'DRAFT') { if (workflow.currentVersion.status === 'DRAFT') {
return workflow.currentVersion; return workflow.currentVersion.id;
} }
return await createDraftFromWorkflowVersion({
return await createNewWorkflowVersion({
workflowId: workflow.id, workflowId: workflow.id,
name: `v${workflow.versions.length + 1}`, workflowVersionIdToCopy: workflow.currentVersion.id,
status: 'DRAFT',
trigger: workflow.currentVersion.trigger,
steps: workflow.currentVersion.steps,
}); });
}; };

View File

@ -3,7 +3,7 @@ import { renderHook } from '@testing-library/react';
import { useCreateStep } from '../useCreateStep'; import { useCreateStep } from '../useCreateStep';
const mockOpenRightDrawer = jest.fn(); const mockOpenRightDrawer = jest.fn();
const mockCreateNewWorkflowVersion = jest.fn(); const mockCreateDraftFromWorkflowVersion = jest.fn().mockResolvedValue('457');
const mockCreateWorkflowVersionStep = jest.fn().mockResolvedValue({ const mockCreateWorkflowVersionStep = jest.fn().mockResolvedValue({
data: { createWorkflowVersionStep: { id: '1' } }, data: { createWorkflowVersionStep: { id: '1' } },
}); });
@ -29,9 +29,9 @@ jest.mock(
}), }),
); );
jest.mock('@/workflow/hooks/useCreateNewWorkflowVersion', () => ({ jest.mock('@/workflow/hooks/useCreateDraftFromWorkflowVersion', () => ({
useCreateNewWorkflowVersion: () => ({ useCreateDraftFromWorkflowVersion: () => ({
createNewWorkflowVersion: mockCreateNewWorkflowVersion, createDraftFromWorkflowVersion: mockCreateDraftFromWorkflowVersion,
}), }),
})); }));

View File

@ -4,9 +4,9 @@ import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
const mockCloseRightDrawer = jest.fn(); const mockCloseRightDrawer = jest.fn();
const mockCreateNewWorkflowVersion = jest.fn();
const mockDeleteWorkflowVersionStep = jest.fn(); const mockDeleteWorkflowVersionStep = jest.fn();
const updateOneRecordMock = jest.fn(); const updateOneRecordMock = jest.fn();
const mockCreateDraftFromWorkflowVersion = jest.fn().mockResolvedValue('457');
jest.mock('@/object-record/hooks/useUpdateOneRecord', () => ({ jest.mock('@/object-record/hooks/useUpdateOneRecord', () => ({
useUpdateOneRecord: () => ({ useUpdateOneRecord: () => ({
@ -26,9 +26,9 @@ jest.mock('@/workflow/hooks/useDeleteWorkflowVersionStep', () => ({
}), }),
})); }));
jest.mock('@/workflow/hooks/useCreateNewWorkflowVersion', () => ({ jest.mock('@/workflow/hooks/useCreateDraftFromWorkflowVersion', () => ({
useCreateNewWorkflowVersion: () => ({ useCreateDraftFromWorkflowVersion: () => ({
createNewWorkflowVersion: mockCreateNewWorkflowVersion, createDraftFromWorkflowVersion: mockCreateDraftFromWorkflowVersion,
}), }),
})); }));

View File

@ -2,8 +2,8 @@ import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep'; import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
const mockCreateNewWorkflowVersion = jest.fn();
const mockUpdateWorkflowVersionStep = jest.fn(); const mockUpdateWorkflowVersionStep = jest.fn();
const mockCreateDraftFromWorkflowVersion = jest.fn().mockResolvedValue('457');
jest.mock('recoil', () => ({ jest.mock('recoil', () => ({
useRecoilValue: () => 'parent-step-id', useRecoilValue: () => 'parent-step-id',
@ -20,9 +20,9 @@ jest.mock(
}), }),
); );
jest.mock('@/workflow/hooks/useCreateNewWorkflowVersion', () => ({ jest.mock('@/workflow/hooks/useCreateDraftFromWorkflowVersion', () => ({
useCreateNewWorkflowVersion: () => ({ useCreateDraftFromWorkflowVersion: () => ({
createNewWorkflowVersion: mockCreateNewWorkflowVersion, createDraftFromWorkflowVersion: mockCreateDraftFromWorkflowVersion,
}), }),
})); }));

View File

@ -35,11 +35,11 @@ export const useCreateStep = ({
throw new Error('Select a step to create a new step from first.'); throw new Error('Select a step to create a new step from first.');
} }
const workflowVersion = await getUpdatableWorkflowVersion(workflow); const workflowVersionId = await getUpdatableWorkflowVersion(workflow);
const createdStep = ( const createdStep = (
await createWorkflowVersionStep({ await createWorkflowVersionStep({
workflowVersionId: workflowVersion.id, workflowVersionId,
stepType: newStepType, stepType: newStepType,
}) })
)?.data?.createWorkflowVersionStep; )?.data?.createWorkflowVersionStep;

View File

@ -28,10 +28,10 @@ export const useDeleteStep = ({
const deleteStep = async (stepId: string) => { const deleteStep = async (stepId: string) => {
closeRightDrawer(); closeRightDrawer();
closeCommandMenu(); closeCommandMenu();
const workflowVersion = await getUpdatableWorkflowVersion(workflow); const workflowVersionId = await getUpdatableWorkflowVersion(workflow);
if (stepId === TRIGGER_STEP_ID) { if (stepId === TRIGGER_STEP_ID) {
await updateOneWorkflowVersion({ await updateOneWorkflowVersion({
idToUpdate: workflowVersion.id, idToUpdate: workflowVersionId,
updateOneRecordInput: { updateOneRecordInput: {
trigger: null, trigger: null,
}, },
@ -39,7 +39,7 @@ export const useDeleteStep = ({
return; return;
} }
await deleteWorkflowVersionStep({ await deleteWorkflowVersionStep({
workflowVersionId: workflowVersion.id, workflowVersionId,
stepId, stepId,
}); });
}; };

View File

@ -19,9 +19,10 @@ export const useUpdateStep = ({
throw new Error('Can not update an undefined workflow version.'); throw new Error('Can not update an undefined workflow version.');
} }
const workflowVersion = await getUpdatableWorkflowVersion(workflow); const workflowVersionId = await getUpdatableWorkflowVersion(workflow);
await updateWorkflowVersionStep({ await updateWorkflowVersionStep({
workflowVersionId: workflowVersion.id, workflowVersionId,
step: updatedStep, step: updatedStep,
}); });
}; };

View File

@ -28,7 +28,7 @@ export const useUpdateWorkflowVersionTrigger = ({
throw new Error('Can not update an undefined workflow version.'); throw new Error('Can not update an undefined workflow version.');
} }
const workflowVersion = await getUpdatableWorkflowVersion(workflow); const workflowVersionId = await getUpdatableWorkflowVersion(workflow);
const outputSchema = ( const outputSchema = (
await computeStepOutputSchema({ await computeStepOutputSchema({
@ -42,7 +42,7 @@ export const useUpdateWorkflowVersionTrigger = ({
}; };
await updateOneWorkflowVersion({ await updateOneWorkflowVersion({
idToUpdate: workflowVersion.id, idToUpdate: workflowVersionId,
updateOneRecordInput: { updateOneRecordInput: {
trigger: updatedTrigger, trigger: updatedTrigger,
}, },

View File

@ -6,7 +6,7 @@ import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.t
export class DeleteVariablesFactory { export class DeleteVariablesFactory {
create(id: string): QueryVariables { create(id: string): QueryVariables {
return { return {
id: id, id,
}; };
} }
} }

View File

@ -0,0 +1,9 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@ObjectType('WorkflowVersion')
export class WorkflowVersionDTO {
@Field(() => UUIDScalarType)
id: string;
}

View File

@ -12,6 +12,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-version-step.workspace-service'; import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-version-step.workspace-service';
import { WorkflowVersionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version.dto';
@Resolver() @Resolver()
@UseGuards(WorkspaceAuthGuard, UserAuthGuard) @UseGuards(WorkspaceAuthGuard, UserAuthGuard)
@ -60,7 +61,7 @@ export class WorkflowVersionStepResolver {
}); });
} }
@Mutation(() => Boolean) @Mutation(() => WorkflowVersionDTO)
async createDraftFromWorkflowVersion( async createDraftFromWorkflowVersion(
@AuthWorkspace() { id: workspaceId }: Workspace, @AuthWorkspace() { id: workspaceId }: Workspace,
@Args('input') @Args('input')
@ -68,15 +69,15 @@ export class WorkflowVersionStepResolver {
workflowId, workflowId,
workflowVersionIdToCopy, workflowVersionIdToCopy,
}: CreateDraftFromWorkflowVersionInput, }: CreateDraftFromWorkflowVersionInput,
) { ): Promise<WorkflowVersionDTO> {
await this.workflowVersionStepWorkspaceService.createDraftFromWorkflowVersion( return {
{ id: await this.workflowVersionStepWorkspaceService.createDraftFromWorkflowVersion(
workspaceId, {
workflowId, workspaceId,
workflowVersionIdToCopy, workflowId,
}, workflowVersionIdToCopy,
); },
),
return true; };
} }
} }

View File

@ -58,6 +58,29 @@ export class ServerlessFunctionService {
return this.serverlessFunctionRepository.findBy(where); return this.serverlessFunctionRepository.findBy(where);
} }
async findOneOrFail({
workspaceId,
id,
}: {
workspaceId: string;
id: string;
}) {
const serverlessFunction =
await this.serverlessFunctionRepository.findOneBy({
id,
workspaceId,
});
if (!serverlessFunction) {
throw new ServerlessFunctionException(
`Function does not exist`,
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
);
}
return serverlessFunction;
}
async hasServerlessFunctionPublishedVersion(serverlessFunctionId: string) { async hasServerlessFunctionPublishedVersion(serverlessFunctionId: string) {
return await this.serverlessFunctionRepository.exists({ return await this.serverlessFunctionRepository.exists({
where: { where: {
@ -72,18 +95,10 @@ export class ServerlessFunctionService {
id: string, id: string,
version: string, version: string,
): Promise<{ [filePath: string]: string } | undefined> { ): Promise<{ [filePath: string]: string } | undefined> {
const serverlessFunction = const serverlessFunction = await this.findOneOrFail({
await this.serverlessFunctionRepository.findOneBy({ id,
id, workspaceId,
workspaceId, });
});
if (!serverlessFunction) {
throw new ServerlessFunctionException(
`Function does not exist`,
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
);
}
try { try {
const folderPath = getServerlessFolder({ const folderPath = getServerlessFolder({
@ -121,19 +136,10 @@ export class ServerlessFunctionService {
): Promise<ServerlessExecuteResult> { ): Promise<ServerlessExecuteResult> {
await this.throttleExecution(workspaceId); await this.throttleExecution(workspaceId);
const functionToExecute = await this.serverlessFunctionRepository.findOneBy( const functionToExecute = await this.findOneOrFail({
{ id,
id, workspaceId,
workspaceId, });
},
);
if (!functionToExecute) {
throw new ServerlessFunctionException(
`Function does not exist`,
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
);
}
const resultServerlessFunction = await this.serverlessService.execute( const resultServerlessFunction = await this.serverlessService.execute(
functionToExecute, functionToExecute,
@ -163,15 +169,10 @@ export class ServerlessFunctionService {
} }
async publishOneServerlessFunction(id: string, workspaceId: string) { async publishOneServerlessFunction(id: string, workspaceId: string) {
const existingServerlessFunction = const existingServerlessFunction = await this.findOneOrFail({
await this.serverlessFunctionRepository.findOneBy({ id, workspaceId }); id,
workspaceId,
if (!existingServerlessFunction) { });
throw new ServerlessFunctionException(
`Function does not exist`,
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
);
}
if (isDefined(existingServerlessFunction.latestVersion)) { if (isDefined(existingServerlessFunction.latestVersion)) {
const latestCode = await this.getServerlessFunctionSourceCode( const latestCode = await this.getServerlessFunctionSourceCode(
@ -223,18 +224,10 @@ export class ServerlessFunctionService {
workspaceId: string; workspaceId: string;
isHardDeletion?: boolean; isHardDeletion?: boolean;
}) { }) {
const existingServerlessFunction = const existingServerlessFunction = await this.findOneOrFail({
await this.serverlessFunctionRepository.findOneBy({ id,
id, workspaceId,
workspaceId, });
});
if (!existingServerlessFunction) {
throw new ServerlessFunctionException(
`Function does not exist`,
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
);
}
if (isHardDeletion) { if (isHardDeletion) {
await this.serverlessFunctionRepository.delete(id); await this.serverlessFunctionRepository.delete(id);
@ -254,18 +247,10 @@ export class ServerlessFunctionService {
serverlessFunctionInput: UpdateServerlessFunctionInput, serverlessFunctionInput: UpdateServerlessFunctionInput,
workspaceId: string, workspaceId: string,
) { ) {
const existingServerlessFunction = const existingServerlessFunction = await this.findOneOrFail({
await this.serverlessFunctionRepository.findOneBy({ id: serverlessFunctionInput.id,
id: serverlessFunctionInput.id, workspaceId,
workspaceId, });
});
if (!existingServerlessFunction) {
throw new ServerlessFunctionException(
`Function does not exist`,
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
);
}
await this.serverlessFunctionRepository.update( await this.serverlessFunctionRepository.update(
existingServerlessFunction.id, existingServerlessFunction.id,
@ -367,67 +352,40 @@ export class ServerlessFunctionService {
}); });
} }
async copyOneServerlessFunction({ async usePublishedVersionAsDraft({
serverlessFunctionToCopyId, id,
serverlessFunctionToCopyVersion, version,
workspaceId, workspaceId,
}: { }: {
serverlessFunctionToCopyId: string; id: string;
serverlessFunctionToCopyVersion: string; version: string;
workspaceId: string; workspaceId: string;
}) { }) {
const serverlessFunctionToCopy = const serverlessFunction = await this.findOneOrFail({
await this.serverlessFunctionRepository.findOneBy({ id,
workspaceId, workspaceId,
id: serverlessFunctionToCopyId,
latestVersion: serverlessFunctionToCopyVersion,
});
if (!serverlessFunctionToCopy) {
throw new ServerlessFunctionException(
'Function does not exist',
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
);
}
const serverlessFunctionToCreate = this.serverlessFunctionRepository.create(
{
name: serverlessFunctionToCopy?.name,
description: serverlessFunctionToCopy?.description,
timeoutSeconds: serverlessFunctionToCopy?.timeoutSeconds,
workspaceId,
layerVersion: LAST_LAYER_VERSION,
},
);
const copiedServerlessFunction =
await this.serverlessFunctionRepository.save(serverlessFunctionToCreate);
const serverlessFunctionToCopyFileFolder = getServerlessFolder({
serverlessFunction: serverlessFunctionToCopy,
version: 'latest',
});
const copiedServerlessFunctionFileFolder = getServerlessFolder({
serverlessFunction: copiedServerlessFunction,
version: 'draft',
}); });
await this.fileStorageService.copy({ await this.fileStorageService.copy({
from: { from: {
folderPath: serverlessFunctionToCopyFileFolder, folderPath: getServerlessFolder({
serverlessFunction: serverlessFunction,
version,
}),
}, },
to: { to: {
folderPath: copiedServerlessFunctionFileFolder, folderPath: getServerlessFolder({
serverlessFunction: serverlessFunction,
version: 'draft',
}),
}, },
}); });
await this.buildServerlessFunction({ await this.buildServerlessFunction({
serverlessFunctionId: copiedServerlessFunction.id, serverlessFunctionId: id,
serverlessFunctionVersion: 'draft', serverlessFunctionVersion: 'draft',
workspaceId, workspaceId,
}); });
return copiedServerlessFunction;
} }
private async throttleExecution(workspaceId: string) { private async throttleExecution(workspaceId: string) {

View File

@ -198,37 +198,27 @@ export class WorkflowVersionStepWorkspaceService {
step: WorkflowAction; step: WorkflowAction;
workspaceId: string; workspaceId: string;
}): Promise<WorkflowAction> { }): Promise<WorkflowAction> {
const newStepId = v4();
switch (step.type) { switch (step.type) {
case WorkflowActionType.CODE: { case WorkflowActionType.CODE: {
const copiedServerlessFunction = await this.serverlessFunctionService.usePublishedVersionAsDraft({
await this.serverlessFunctionService.copyOneServerlessFunction({ id: step.settings.input.serverlessFunctionId,
serverlessFunctionToCopyId: version: step.settings.input.serverlessFunctionVersion,
step.settings.input.serverlessFunctionId, workspaceId,
serverlessFunctionToCopyVersion: });
step.settings.input.serverlessFunctionVersion,
workspaceId,
});
return { return {
...step, ...step,
id: newStepId,
settings: { settings: {
...step.settings, ...step.settings,
input: { input: {
...step.settings.input, ...step.settings.input,
serverlessFunctionId: copiedServerlessFunction.id, serverlessFunctionVersion: 'draft',
serverlessFunctionVersion: copiedServerlessFunction.latestVersion,
}, },
}, },
}; };
} }
default: { default: {
return { return step;
...step,
id: newStepId,
};
} }
} }
} }
@ -473,17 +463,6 @@ export class WorkflowVersionStepWorkspaceService {
assertWorkflowVersionIsDraft(draftWorkflowVersion); assertWorkflowVersionIsDraft(draftWorkflowVersion);
if (Array.isArray(draftWorkflowVersion.steps)) {
await Promise.all(
draftWorkflowVersion.steps.map((step) =>
this.runWorkflowVersionStepDeletionSideEffects({
step,
workspaceId,
}),
),
);
}
const newWorkflowVersionTrigger = workflowVersionToCopy.trigger; const newWorkflowVersionTrigger = workflowVersionToCopy.trigger;
const newWorkflowVersionSteps: WorkflowAction[] = []; const newWorkflowVersionSteps: WorkflowAction[] = [];
@ -500,6 +479,8 @@ export class WorkflowVersionStepWorkspaceService {
steps: newWorkflowVersionSteps, steps: newWorkflowVersionSteps,
trigger: newWorkflowVersionTrigger, trigger: newWorkflowVersionTrigger,
}); });
return draftWorkflowVersion.id;
} }
private async runWorkflowVersionStepDeletionSideEffects({ private async runWorkflowVersionStepDeletionSideEffects({

View File

@ -161,26 +161,30 @@ export class WorkflowStatusesUpdateJob {
const newStep = { ...step }; const newStep = { ...step };
if (step.type === WorkflowActionType.CODE) { if (step.type === WorkflowActionType.CODE) {
let serverlessFunction;
try { try {
serverlessFunction = await this.serverlessFunctionService.publishOneServerlessFunction(
await this.serverlessFunctionService.publishOneServerlessFunction( step.settings.input.serverlessFunctionId,
step.settings.input.serverlessFunctionId, workspaceId,
workspaceId, );
);
} catch (e) { } catch (e) {
serverlessFunction = null; // publishOneServerlessFunction throws if no change have been
// applied between draft and lastPublished version.
// If no change have been applied, we just use the same
// serverless function version
} }
if (serverlessFunction) { const serverlessFunction =
const newStepSettings = { ...step.settings }; await this.serverlessFunctionService.findOneOrFail({
id: step.settings.input.serverlessFunctionId,
workspaceId,
});
newStepSettings.input.serverlessFunctionVersion = const newStepSettings = { ...step.settings };
serverlessFunction.latestVersion;
newStep.settings = newStepSettings; newStepSettings.input.serverlessFunctionVersion =
} serverlessFunction.latestVersion;
newStep.settings = newStepSettings;
} }
newSteps.push(newStep); newSteps.push(newStep);
} }

View File

@ -24,7 +24,7 @@ export const wrapHeadingsWithAnchor = (children: ReactNode): ReactNode => {
.replace(/\s+/g, '-') .replace(/\s+/g, '-')
.toLowerCase(); .toLowerCase();
return cloneElement(child as ReactElement<any>, { return cloneElement(child as ReactElement<any>, {
id: id, id,
className: 'anchor', className: 'anchor',
children: <a href={`#${id}`}>{child.props.children}</a>, children: <a href={`#${id}`}>{child.props.children}</a>,
}); });