diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 7d72f99720..6510a6ca70 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -217,6 +217,7 @@ export type Mutation = { updateWorkflowRunStep: WorkflowAction; updateWorkflowVersionPositions: Scalars['Boolean']['output']; updateWorkflowVersionStep: WorkflowAction; + updateWorkflowVersionTrigger: WorkflowVersionTrigger; }; @@ -319,6 +320,11 @@ export type MutationUpdateWorkflowVersionStepArgs = { input: UpdateWorkflowVersionStepInput; }; + +export type MutationUpdateWorkflowVersionTriggerArgs = { + input: UpdateWorkflowVersionTriggerInput; +}; + export type ObjectRecordFilterInput = { and?: InputMaybe>; createdAt?: InputMaybe; @@ -605,6 +611,13 @@ export type UpdateWorkflowVersionStepInput = { workflowVersionId: Scalars['UUID']['input']; }; +export type UpdateWorkflowVersionTriggerInput = { + /** Trigger to update in JSON format */ + trigger: Scalars['JSON']['input']; + /** Workflow version ID */ + workflowVersionId: Scalars['UUID']['input']; +}; + export type WorkflowAction = { __typename?: 'WorkflowAction'; id: Scalars['UUID']['output']; @@ -691,6 +704,11 @@ export type WorkflowVersionStepChanges = { triggerDiff?: Maybe; }; +export type WorkflowVersionTrigger = { + __typename?: 'WorkflowVersionTrigger'; + trigger?: Maybe; +}; + export type TimelineCalendarEventFragmentFragment = { __typename?: 'TimelineCalendarEvent', id: any, title: string, description: string, location: string, startsAt: string, endsAt: string, isFullDay: boolean, visibility: CalendarChannelVisibility, participants: Array<{ __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }; export type TimelineCalendarEventParticipantFragmentFragment = { __typename?: 'TimelineCalendarEventParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }; @@ -842,6 +860,13 @@ export type UpdateWorkflowVersionStepMutationVariables = Exact<{ export type UpdateWorkflowVersionStepMutation = { __typename?: 'Mutation', updateWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: WorkflowActionType, settings: any, valid: boolean, nextStepIds?: Array | null, position?: { __typename?: 'WorkflowStepPosition', x: number, y: number } | null } }; +export type UpdateWorkflowVersionTriggerMutationVariables = Exact<{ + input: UpdateWorkflowVersionTriggerInput; +}>; + + +export type UpdateWorkflowVersionTriggerMutation = { __typename?: 'Mutation', updateWorkflowVersionTrigger: { __typename?: 'WorkflowVersionTrigger', trigger?: any | null } }; + export type WorkflowStepConnectedAccountHandleQueryVariables = Exact<{ connectedAccountId: Scalars['UUID']['input']; }>; @@ -895,6 +920,7 @@ export const RunWorkflowVersionDocument = {"kind":"Document","definitions":[{"ki export const StopWorkflowRunDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StopWorkflowRun"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workflowRunId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stopWorkflowRun"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workflowRunId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workflowRunId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}}]}}]} as unknown as DocumentNode; export const UpdateWorkflowRunStepDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkflowRunStep"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWorkflowRunStepInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkflowRunStep"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"nextStepIds"}},{"kind":"Field","name":{"kind":"Name","value":"position"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode; export const UpdateWorkflowVersionStepDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkflowVersionStep"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWorkflowVersionStepInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkflowVersionStep"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"nextStepIds"}},{"kind":"Field","name":{"kind":"Name","value":"position"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode; +export const UpdateWorkflowVersionTriggerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkflowVersionTrigger"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWorkflowVersionTriggerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkflowVersionTrigger"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"trigger"}}]}}]}}]} as unknown as DocumentNode; export const WorkflowStepConnectedAccountHandleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkflowStepConnectedAccountHandle"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workflowStepConnectedAccountHandle"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}}]}}]}}]} as unknown as DocumentNode; export const SubmitFormStepDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SubmitFormStep"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubmitFormStepInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"submitFormStep"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const TestHttpRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TestHttpRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TestHttpRequestInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"testHttpRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"result"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusText"}},{"kind":"Field","name":{"kind":"Name","value":"headers"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/workflow/graphql/mutations/updateWorkflowVersionTrigger.ts b/packages/twenty-front/src/modules/workflow/graphql/mutations/updateWorkflowVersionTrigger.ts new file mode 100644 index 0000000000..ed0ea40f59 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/graphql/mutations/updateWorkflowVersionTrigger.ts @@ -0,0 +1,11 @@ +import { gql } from '@apollo/client'; + +export const UPDATE_WORKFLOW_VERSION_TRIGGER = gql` + mutation UpdateWorkflowVersionTrigger( + $input: UpdateWorkflowVersionTriggerInput! + ) { + updateWorkflowVersionTrigger(input: $input) { + trigger + } + } +`; diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/__tests__/useUpdateWorkflowVersionTrigger.test.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/__tests__/useUpdateWorkflowVersionTrigger.test.ts index 9ed5a202c4..03d055d86f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/__tests__/useUpdateWorkflowVersionTrigger.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/__tests__/useUpdateWorkflowVersionTrigger.test.ts @@ -3,14 +3,38 @@ import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hoo import { act, renderHook } from '@testing-library/react'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; -const mockUpdateOneRecord = jest.fn(); +const mockMutate = jest.fn(); const mockGetUpdatableWorkflowVersion = jest.fn(); +const mockGetRecordFromCache = jest.fn(); const mockMarkStepForRecomputation = jest.fn(); +const mockEnqueueErrorSnackBar = jest.fn(); -jest.mock('@/object-record/hooks/useUpdateOneRecord', () => ({ - useUpdateOneRecord: jest.fn(() => ({ - updateOneRecord: mockUpdateOneRecord, - })), +jest.mock('@/object-metadata/hooks/useApolloCoreClient', () => ({ + useApolloCoreClient: () => ({ cache: {} }), +})); + +jest.mock('@/object-metadata/hooks/useObjectMetadataItems', () => ({ + useObjectMetadataItems: () => ({ objectMetadataItems: [] }), +})); + +jest.mock('@/object-metadata/hooks/useObjectMetadataItem', () => ({ + useObjectMetadataItem: () => ({ objectMetadataItem: {} }), +})); + +jest.mock('@/object-record/hooks/useObjectPermissions', () => ({ + useObjectPermissions: () => ({ objectPermissionsByObjectMetadataId: {} }), +})); + +jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({ + useSnackBar: () => ({ enqueueErrorSnackBar: mockEnqueueErrorSnackBar }), +})); + +jest.mock('@/object-record/cache/hooks/useGetRecordFromCache', () => ({ + useGetRecordFromCache: () => mockGetRecordFromCache, +})); + +jest.mock('@/object-record/cache/utils/updateRecordFromCache', () => ({ + updateRecordFromCache: jest.fn(), })); jest.mock('@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow', () => ({ @@ -25,6 +49,10 @@ jest.mock('@/workflow/workflow-variables/hooks/useStepsOutputSchema', () => ({ })), })); +jest.mock('@apollo/client/react', () => ({ + useMutation: () => [mockMutate], +})); + describe('useUpdateWorkflowVersionTrigger', () => { const trigger: WorkflowTrigger = { name: 'Company created', @@ -38,9 +66,13 @@ describe('useUpdateWorkflowVersionTrigger', () => { beforeEach(() => { jest.clearAllMocks(); + mockMutate.mockResolvedValue({ + data: { updateWorkflowVersionTrigger: { trigger } }, + }); + mockGetRecordFromCache.mockReturnValue(undefined); }); - it('updates the trigger and marks it for recomputation for frontend-computed types', async () => { + it('updates the trigger via the dedicated mutation and marks it for recomputation', async () => { mockGetUpdatableWorkflowVersion.mockResolvedValue('version-id'); const { result } = renderHook(() => useUpdateWorkflowVersionTrigger()); @@ -50,17 +82,20 @@ describe('useUpdateWorkflowVersionTrigger', () => { }); expect(mockGetUpdatableWorkflowVersion).toHaveBeenCalled(); + expect(mockMutate).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { + input: { + workflowVersionId: 'version-id', + trigger, + }, + }, + }), + ); expect(mockMarkStepForRecomputation).toHaveBeenCalledWith({ stepId: TRIGGER_STEP_ID, workflowVersionId: 'version-id', }); - expect(mockUpdateOneRecord).toHaveBeenCalledWith({ - idToUpdate: 'version-id', - objectNameSingular: 'workflowVersion', - updateOneRecordInput: { - trigger, - }, - }); }); it('marks for recomputation for all trigger types', async () => { @@ -70,14 +105,14 @@ describe('useUpdateWorkflowVersionTrigger', () => { mockMarkStepForRecomputation.mockClear(); mockGetUpdatableWorkflowVersion.mockResolvedValue('version-id'); - const testTrigger: WorkflowTrigger = { + const testTrigger = { name: `${triggerType} Trigger`, - type: triggerType as any, + type: triggerType, settings: { outputSchema: {}, }, nextStepIds: [], - }; + } as unknown as WorkflowTrigger; const { result } = renderHook(() => useUpdateWorkflowVersionTrigger()); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger.ts index 48f98876c9..58625799b0 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger.ts @@ -1,34 +1,93 @@ -import { CoreObjectNameSingular } from 'twenty-shared/types'; -import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; +import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; +import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { UPDATE_WORKFLOW_VERSION_TRIGGER } from '@/workflow/graphql/mutations/updateWorkflowVersionTrigger'; import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow'; -import { type WorkflowTrigger } from '@/workflow/types/Workflow'; - +import { + type WorkflowTrigger, + type WorkflowVersion, +} from '@/workflow/types/Workflow'; import { useStepsOutputSchema } from '@/workflow/workflow-variables/hooks/useStepsOutputSchema'; +import { useMutation } from '@apollo/client/react'; +import { CoreObjectNameSingular } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; +import { + type UpdateWorkflowVersionTriggerMutation, + type UpdateWorkflowVersionTriggerMutationVariables, +} from '~/generated/graphql'; export const useUpdateWorkflowVersionTrigger = () => { - const { updateOneRecord: updateOneWorkflowVersion } = useUpdateOneRecord(); + const apolloCoreClient = useApolloCoreClient(); + const { objectMetadataItems } = useObjectMetadataItems(); + const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); + const { enqueueErrorSnackBar } = useSnackBar(); const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersionOrThrow(); const { markStepForRecomputation } = useStepsOutputSchema(); + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + const getRecordFromCache = useGetRecordFromCache({ + objectNameSingular: CoreObjectNameSingular.WorkflowVersion, + }); + + const [mutate] = useMutation< + UpdateWorkflowVersionTriggerMutation, + UpdateWorkflowVersionTriggerMutationVariables + >(UPDATE_WORKFLOW_VERSION_TRIGGER, { + client: apolloCoreClient, + }); + const updateTrigger = async (updatedTrigger: WorkflowTrigger) => { const workflowVersionId = await getUpdatableWorkflowVersion(); - await updateOneWorkflowVersion({ - objectNameSingular: CoreObjectNameSingular.WorkflowVersion, - idToUpdate: workflowVersionId, - updateOneRecordInput: { - trigger: updatedTrigger, + const { data } = await mutate({ + variables: { + input: { + workflowVersionId, + trigger: updatedTrigger, + }, + }, + onError: (error) => { + enqueueErrorSnackBar({ apolloError: error }); }, }); + if (!isDefined(data?.updateWorkflowVersionTrigger)) { + return; + } + markStepForRecomputation({ stepId: TRIGGER_STEP_ID, workflowVersionId, }); + + const cachedRecord = getRecordFromCache(workflowVersionId); + if (!isDefined(cachedRecord)) { + return; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloCoreClient.cache, + record: { + ...cachedRecord, + trigger: updatedTrigger, + }, + recordGqlFields: { + trigger: true, + }, + objectPermissionsByObjectMetadataId, + }); }; return { diff --git a/packages/twenty-server/src/engine/core-modules/workflow/dtos/workflow-version-trigger.dto.ts b/packages/twenty-server/src/engine/core-modules/workflow/dtos/workflow-version-trigger.dto.ts new file mode 100644 index 0000000000..99e0897a08 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/workflow/dtos/workflow-version-trigger.dto.ts @@ -0,0 +1,11 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import graphqlTypeJson from 'graphql-type-json'; + +import { WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; + +@ObjectType('WorkflowVersionTrigger') +export class WorkflowVersionTriggerDTO { + @Field(() => graphqlTypeJson, { nullable: true }) + trigger: WorkflowTrigger | null; +} diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts index 058f68fe96..9307302288 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts @@ -16,8 +16,10 @@ import { TestHttpRequestInput } from 'src/engine/core-modules/workflow/dtos/test import { TestHttpRequestDTO } from 'src/engine/core-modules/workflow/dtos/test-http-request.dto'; import { UpdateWorkflowRunStepInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-run-step.input'; import { UpdateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-step.input'; +import { UpdateWorkflowVersionTriggerInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-trigger.input'; import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-action.dto'; import { WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto'; +import { WorkflowVersionTriggerDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-trigger.dto'; import { WorkflowVersionStepGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-version-step-graphql-api-exception.filter'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; @@ -99,6 +101,21 @@ export class WorkflowVersionStepResolver { }); } + @Mutation(() => WorkflowVersionTriggerDTO) + async updateWorkflowVersionTrigger( + @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, + @Args('input') + { trigger, workflowVersionId }: UpdateWorkflowVersionTriggerInput, + ): Promise { + return this.workflowVersionStepWorkspaceService.updateWorkflowVersionTrigger( + { + workspaceId, + workflowVersionId, + trigger, + }, + ); + } + @Mutation(() => WorkflowVersionStepChangesDTO) async deleteWorkflowVersionStep( @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts index a2f75f7733..4943ac3e72 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts @@ -31,6 +31,10 @@ import { WorkflowVersionCreateManyPreQueryHook } from 'src/modules/workflow/comm import { WorkflowVersionCreateOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-create-one.pre-query.hook'; import { WorkflowVersionDeleteManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-delete-many.pre-query.hook'; import { WorkflowVersionDeleteOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-delete-one.pre-query.hook'; +import { WorkflowVersionDestroyManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-destroy-many.pre-query.hook'; +import { WorkflowVersionDestroyOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-destroy-one.pre-query.hook'; +import { WorkflowVersionRestoreManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-restore-many.pre-query.hook'; +import { WorkflowVersionRestoreOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-restore-one.pre-query.hook'; import { WorkflowVersionUpdateManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-update-many.pre-query.hook'; import { WorkflowVersionUpdateOnePreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-version-update-one.pre-query.hook'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; @@ -67,6 +71,10 @@ import { WorkflowVersionValidationWorkspaceService } from 'src/modules/workflow/ WorkflowVersionUpdateManyPreQueryHook, WorkflowVersionDeleteOnePreQueryHook, WorkflowVersionDeleteManyPreQueryHook, + WorkflowVersionDestroyOnePreQueryHook, + WorkflowVersionDestroyManyPreQueryHook, + WorkflowVersionRestoreOnePreQueryHook, + WorkflowVersionRestoreManyPreQueryHook, WorkflowCreateOnePostQueryHook, WorkflowCreateManyPostQueryHook, WorkflowVersionValidationWorkspaceService, diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-destroy-many.pre-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-destroy-many.pre-query.hook.ts new file mode 100644 index 0000000000..eac3578099 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-destroy-many.pre-query.hook.ts @@ -0,0 +1,23 @@ +import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { type DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { + WorkflowQueryValidationException, + WorkflowQueryValidationExceptionCode, +} from 'src/modules/workflow/common/exceptions/workflow-query-validation.exception'; + +@WorkspaceQueryHook(`workflowVersion.destroyMany`) +export class WorkflowVersionDestroyManyPreQueryHook implements WorkspacePreQueryHookInstance { + async execute( + _authContext: WorkspaceAuthContext, + _objectName: string, + _payload: DestroyManyResolverArgs, + ): Promise { + throw new WorkflowQueryValidationException( + 'Method not allowed.', + WorkflowQueryValidationExceptionCode.FORBIDDEN, + ); + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-destroy-one.pre-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-destroy-one.pre-query.hook.ts new file mode 100644 index 0000000000..d15e0765c2 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-destroy-one.pre-query.hook.ts @@ -0,0 +1,23 @@ +import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { type DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { + WorkflowQueryValidationException, + WorkflowQueryValidationExceptionCode, +} from 'src/modules/workflow/common/exceptions/workflow-query-validation.exception'; + +@WorkspaceQueryHook(`workflowVersion.destroyOne`) +export class WorkflowVersionDestroyOnePreQueryHook implements WorkspacePreQueryHookInstance { + async execute( + _authContext: WorkspaceAuthContext, + _objectName: string, + _payload: DestroyOneResolverArgs, + ): Promise { + throw new WorkflowQueryValidationException( + 'Method not allowed.', + WorkflowQueryValidationExceptionCode.FORBIDDEN, + ); + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-restore-many.pre-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-restore-many.pre-query.hook.ts new file mode 100644 index 0000000000..09964c6e2d --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-restore-many.pre-query.hook.ts @@ -0,0 +1,23 @@ +import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { type RestoreManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { + WorkflowQueryValidationException, + WorkflowQueryValidationExceptionCode, +} from 'src/modules/workflow/common/exceptions/workflow-query-validation.exception'; + +@WorkspaceQueryHook(`workflowVersion.restoreMany`) +export class WorkflowVersionRestoreManyPreQueryHook implements WorkspacePreQueryHookInstance { + async execute( + _authContext: WorkspaceAuthContext, + _objectName: string, + _payload: RestoreManyResolverArgs, + ): Promise { + throw new WorkflowQueryValidationException( + 'Method not allowed.', + WorkflowQueryValidationExceptionCode.FORBIDDEN, + ); + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-restore-one.pre-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-restore-one.pre-query.hook.ts new file mode 100644 index 0000000000..c45c2a6ab6 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-version-restore-one.pre-query.hook.ts @@ -0,0 +1,23 @@ +import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { type RestoreOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { + WorkflowQueryValidationException, + WorkflowQueryValidationExceptionCode, +} from 'src/modules/workflow/common/exceptions/workflow-query-validation.exception'; + +@WorkspaceQueryHook(`workflowVersion.restoreOne`) +export class WorkflowVersionRestoreOnePreQueryHook implements WorkspacePreQueryHookInstance { + async execute( + _authContext: WorkspaceAuthContext, + _objectName: string, + _payload: RestoreOneResolverArgs, + ): Promise { + throw new WorkflowQueryValidationException( + 'Method not allowed.', + WorkflowQueryValidationExceptionCode.FORBIDDEN, + ); + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts index 130715207b..7482955801 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { msg } from '@lingui/core/macro'; +import { isNonEmptyString } from '@sniptt/guards'; import { IsNull, Not } from 'typeorm'; import { @@ -84,33 +85,37 @@ export class WorkflowVersionValidationWorkspaceService { workspaceId: string; payload: UpdateOneResolverArgs; }) { - const workflowVersion = - await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({ - workspaceId, - workflowVersionId: payload.id, - }); + await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({ + workspaceId, + workflowVersionId: payload.id, + }); - if (!(Object.keys(payload.data).length === 1 && payload.data.name)) { - assertWorkflowVersionIsDraft(workflowVersion); - } + const protectedFieldNames = [ + 'steps', + 'trigger', + 'status', + 'position', + 'workflowId', + 'coreWorkflowVersionId', + ]; + const setsProtectedField = protectedFieldNames.some( + (fieldName) => fieldName in payload.data, + ); + const clearsName = + 'name' in payload.data && !isNonEmptyString(payload.data.name); - if (payload.data.status && payload.data.status !== workflowVersion.status) { + if (setsProtectedField || clearsName) { throw new WorkflowQueryValidationException( - 'Cannot update workflow version status manually', + 'Updating a workflowVersion through the generic mutation is restricted. ' + + 'steps, trigger, status, position, workflowId and coreWorkflowVersionId cannot be changed, and the name cannot be cleared. ' + + 'Use the dedicated workflowVersion mutations (createWorkflowVersionStep, updateWorkflowVersionStep, ' + + 'deleteWorkflowVersionStep, updateWorkflowVersionTrigger, activateWorkflowVersion, ...) instead.', WorkflowQueryValidationExceptionCode.FORBIDDEN, { - userFriendlyMessage: msg`Cannot update workflow version status manually`, + userFriendlyMessage: msg`This field cannot be updated directly on a workflow version`, }, ); } - - if (payload.data.steps) { - throw new WorkflowQueryValidationException( - 'Updating workflowVersion steps directly is forbidden. ' + - 'Use createWorkflowVersionStep, updateWorkflowVersionStep or deleteWorkflowVersionStep endpoint instead.', - WorkflowQueryValidationExceptionCode.FORBIDDEN, - ); - } } async validateWorkflowVersionForDeleteOne( diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts index 1c29e91b7b..4dc5462f3a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service.ts @@ -3,10 +3,13 @@ import { Injectable } from '@nestjs/common'; import { type CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step.input'; import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-action.dto'; import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto'; +import { type WorkflowVersionTriggerDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-trigger.dto'; import { WorkflowVersionStepCreationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-creation.workspace-service'; import { WorkflowVersionStepDeletionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-deletion.workspace-service'; +import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service'; import { WorkflowVersionStepUpdateWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-update.workspace-service'; import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; +import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; @Injectable() export class WorkflowVersionStepWorkspaceService { @@ -14,6 +17,7 @@ export class WorkflowVersionStepWorkspaceService { private readonly workflowVersionStepCreationWorkspaceService: WorkflowVersionStepCreationWorkspaceService, private readonly workflowVersionStepUpdateWorkspaceService: WorkflowVersionStepUpdateWorkspaceService, private readonly workflowVersionStepDeletionWorkspaceService: WorkflowVersionStepDeletionWorkspaceService, + private readonly workflowVersionStepHelpersWorkspaceService: WorkflowVersionStepHelpersWorkspaceService, ) {} async createWorkflowVersionStep({ @@ -49,6 +53,33 @@ export class WorkflowVersionStepWorkspaceService { ); } + async updateWorkflowVersionTrigger({ + workspaceId, + workflowVersionId, + trigger, + }: { + workspaceId: string; + workflowVersionId: string; + trigger: WorkflowTrigger; + }): Promise { + await this.workflowVersionStepHelpersWorkspaceService.getValidatedDraftWorkflowVersion( + { + workflowVersionId, + workspaceId, + }, + ); + + await this.workflowVersionStepHelpersWorkspaceService.updateWorkflowVersionStepsAndTrigger( + { + workspaceId, + workflowVersionId, + trigger, + }, + ); + + return { trigger }; + } + async deleteWorkflowVersionStep({ workspaceId, workflowVersionId, diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/code-step-prebuilt-workflow.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/code-step-prebuilt-workflow.integration-spec.ts index bb5cbcf845..d6bc16fcef 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/code-step-prebuilt-workflow.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/code-step-prebuilt-workflow.integration-spec.ts @@ -5,6 +5,7 @@ import { runWorkflowVersion, waitForWorkflowCompletion, } from 'test/integration/graphql/suites/workflow/utils/workflow-run-test.util'; +import { updateWorkflowVersionTrigger } from 'test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util'; import { updateLogicFunctionSource } from 'test/integration/metadata/suites/logic-function/utils/update-logic-function-source.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; @@ -93,22 +94,10 @@ describe('Code step workflow with PREBUILT logic function (e2e)', () => { position: { x: 0, y: 0 }, }; - const updateTriggerResponse = await client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send({ - query: ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { - id - } - } - `, - variables: { - id: createdWorkflowVersionId, - data: { trigger: manualTrigger }, - }, - }); + const updateTriggerResponse = await updateWorkflowVersionTrigger({ + workflowVersionId: createdWorkflowVersionId!, + trigger: manualTrigger, + }); expect(updateTriggerResponse.body.errors).toBeUndefined(); diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/database-event-trigger-filter.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/database-event-trigger-filter.integration-spec.ts index 0a2bd94af7..f899d992e6 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/database-event-trigger-filter.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/database-event-trigger-filter.integration-spec.ts @@ -90,18 +90,19 @@ describe('Database event trigger filter (e2e)', () => { const updateTriggerResponse = await graphql( ` - mutation UpdateWorkflowVersion( - $id: UUID! - $data: WorkflowVersionUpdateInput! + mutation UpdateWorkflowVersionTrigger( + $input: UpdateWorkflowVersionTriggerInput! ) { - updateWorkflowVersion(id: $id, data: $data) { - id + updateWorkflowVersionTrigger(input: $input) { + trigger } } `, { - id: createdWorkflowVersionId, - data: { trigger: databaseEventTrigger }, + input: { + workflowVersionId: createdWorkflowVersionId, + trigger: databaseEventTrigger, + }, }, ); diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/find-records-relation-traversal-workflow.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/find-records-relation-traversal-workflow.integration-spec.ts index 3efc463b78..632000012d 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/find-records-relation-traversal-workflow.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/find-records-relation-traversal-workflow.integration-spec.ts @@ -3,6 +3,7 @@ import request from 'supertest'; import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util'; import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { updateWorkflowVersionTrigger } from 'test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util'; import { destroyWorkflowRun, runWorkflowVersion, @@ -153,28 +154,16 @@ describe('FindRecords workflow action with relation-traversal filter (e2e)', () createdWorkflowVersionId = getWorkflowResponse.body.data.workflow.versions.edges[0].node.id; - await client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send({ - query: ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { id } - } - `, - variables: { - id: createdWorkflowVersionId, - data: { - trigger: { - name: 'Manual Trigger', - type: 'MANUAL', - settings: { outputSchema: {} }, - nextStepIds: [], - position: { x: 0, y: 0 }, - }, - }, - }, - }); + await updateWorkflowVersionTrigger({ + workflowVersionId: createdWorkflowVersionId!, + trigger: { + name: 'Manual Trigger', + type: 'MANUAL', + settings: { outputSchema: {} }, + nextStepIds: [], + position: { x: 0, y: 0 }, + }, + }); const createStepResponse = await client .post('/graphql') diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/if-else-workflow.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/if-else-workflow.integration-spec.ts index e8cd5618f5..13c923b7b3 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/if-else-workflow.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/if-else-workflow.integration-spec.ts @@ -9,6 +9,7 @@ import { type StepIfElseBranch } from 'twenty-shared/workflow'; import { v4 } from 'uuid'; import { type WorkflowIfElseAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; +import { updateWorkflowVersionTrigger } from 'test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util'; const client = request(`http://localhost:${APP_PORT}`); @@ -85,25 +86,10 @@ describe('If/Else Workflow (e2e)', () => { position: { x: 0, y: 0 }, }; - const updateWorkflowVersionResponse = await client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send({ - query: ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { - id - trigger - } - } - `, - variables: { - id: createdWorkflowVersionId, - data: { - trigger: manualTrigger, - }, - }, - }); + const updateWorkflowVersionResponse = await updateWorkflowVersionTrigger({ + workflowVersionId: createdWorkflowVersionId!, + trigger: manualTrigger, + }); expect(updateWorkflowVersionResponse.body.errors).toBeUndefined(); diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-load-balanced-workflow.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-load-balanced-workflow.integration-spec.ts index 098f662615..19cde9fed7 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-load-balanced-workflow.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-load-balanced-workflow.integration-spec.ts @@ -1,4 +1,5 @@ import request from 'supertest'; +import { updateWorkflowVersionTrigger } from 'test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util'; import { destroyWorkflowRun, runWorkflowVersion, @@ -57,31 +58,22 @@ describe('Pick Record Workflow - load balanced (e2e)', () => { createdWorkflowVersionId = getWorkflowData.workflow.versions.edges[0].node.id; - await graphql( - ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { - id - } - } - `, - { - id: createdWorkflowVersionId, - data: { - trigger: { - name: 'Manual Trigger', - type: 'MANUAL', - settings: { outputSchema: {} }, - nextStepIds: [], - position: { x: 0, y: 0 }, - }, - }, + await updateWorkflowVersionTrigger({ + workflowVersionId: createdWorkflowVersionId!, + trigger: { + name: 'Manual Trigger', + type: 'MANUAL', + settings: { outputSchema: {} }, + nextStepIds: [], + position: { x: 0, y: 0 }, }, - ); + }); await graphql( ` - mutation CreateWorkflowVersionStep($input: CreateWorkflowVersionStepInput!) { + mutation CreateWorkflowVersionStep( + $input: CreateWorkflowVersionStepInput! + ) { createWorkflowVersionStep(input: $input) { stepsDiff } @@ -161,7 +153,9 @@ describe('Pick Record Workflow - load balanced (e2e)', () => { await graphql( ` - mutation UpdateWorkflowVersionStep($input: UpdateWorkflowVersionStepInput!) { + mutation UpdateWorkflowVersionStep( + $input: UpdateWorkflowVersionStepInput! + ) { updateWorkflowVersionStep(input: $input) { id } @@ -294,31 +288,22 @@ describe('Pick Record Workflow - load balanced (e2e)', () => { const misroutedWorkflowVersionId = workflowData.workflow.versions.edges[0].node.id; - await graphql( - ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { - id - } - } - `, - { - id: misroutedWorkflowVersionId, - data: { - trigger: { - name: 'Manual Trigger', - type: 'MANUAL', - settings: { outputSchema: {} }, - nextStepIds: [], - position: { x: 0, y: 0 }, - }, - }, + await updateWorkflowVersionTrigger({ + workflowVersionId: misroutedWorkflowVersionId, + trigger: { + name: 'Manual Trigger', + type: 'MANUAL', + settings: { outputSchema: {} }, + nextStepIds: [], + position: { x: 0, y: 0 }, }, - ); + }); await graphql( ` - mutation CreateWorkflowVersionStep($input: CreateWorkflowVersionStepInput!) { + mutation CreateWorkflowVersionStep( + $input: CreateWorkflowVersionStepInput! + ) { createWorkflowVersionStep(input: $input) { stepsDiff } @@ -353,7 +338,9 @@ describe('Pick Record Workflow - load balanced (e2e)', () => { // to person, not the company pool — the silent misrouting the guard blocks. await graphql( ` - mutation UpdateWorkflowVersionStep($input: UpdateWorkflowVersionStepInput!) { + mutation UpdateWorkflowVersionStep( + $input: UpdateWorkflowVersionStepInput! + ) { updateWorkflowVersionStep(input: $input) { id } @@ -397,7 +384,9 @@ describe('Pick Record Workflow - load balanced (e2e)', () => { expect(activateResponse.body.errors[0].message).toContain( 'many-to-one relation', ); - expect(activateResponse.body.data?.activateWorkflowVersion).not.toBe(true); + expect(activateResponse.body.data?.activateWorkflowVersion).not.toBe( + true, + ); } finally { await graphql( ` diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-round-robin-workflow.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-round-robin-workflow.integration-spec.ts index 8ba2e924a3..1ba34247a7 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-round-robin-workflow.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-round-robin-workflow.integration-spec.ts @@ -1,4 +1,5 @@ import request from 'supertest'; +import { updateWorkflowVersionTrigger } from 'test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util'; import { destroyWorkflowRun, runWorkflowVersion, @@ -52,30 +53,16 @@ describe('Pick Record Workflow - round robin (e2e)', () => { createdWorkflowVersionId = getWorkflowResponse.body.data.workflow.versions.edges[0].node.id; - await client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send({ - query: ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { - id - } - } - `, - variables: { - id: createdWorkflowVersionId, - data: { - trigger: { - name: 'Manual Trigger', - type: 'MANUAL', - settings: { outputSchema: {} }, - nextStepIds: [], - position: { x: 0, y: 0 }, - }, - }, - }, - }); + await updateWorkflowVersionTrigger({ + workflowVersionId: createdWorkflowVersionId!, + trigger: { + name: 'Manual Trigger', + type: 'MANUAL', + settings: { outputSchema: {} }, + nextStepIds: [], + position: { x: 0, y: 0 }, + }, + }); await client .post('/graphql') diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-workflow.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-workflow.integration-spec.ts index 5447ae1f22..0bd90bfe48 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-workflow.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/pick-record-workflow.integration-spec.ts @@ -1,4 +1,5 @@ import request from 'supertest'; +import { updateWorkflowVersionTrigger } from 'test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util'; import { destroyWorkflowRun, runWorkflowVersion, @@ -54,30 +55,16 @@ describe('Pick Record Workflow (e2e)', () => { createdWorkflowVersionId = getWorkflowResponse.body.data.workflow.versions.edges[0].node.id; - const updateTriggerResponse = await client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send({ - query: ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { - id - } - } - `, - variables: { - id: createdWorkflowVersionId, - data: { - trigger: { - name: 'Manual Trigger', - type: 'MANUAL', - settings: { outputSchema: {} }, - nextStepIds: [], - position: { x: 0, y: 0 }, - }, - }, - }, - }); + const updateTriggerResponse = await updateWorkflowVersionTrigger({ + workflowVersionId: createdWorkflowVersionId!, + trigger: { + name: 'Manual Trigger', + type: 'MANUAL', + settings: { outputSchema: {} }, + nextStepIds: [], + position: { x: 0, y: 0 }, + }, + }); expect(updateTriggerResponse.body.errors).toBeUndefined(); diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util.ts new file mode 100644 index 0000000000..c201aab13a --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util.ts @@ -0,0 +1,42 @@ +import request from 'supertest'; + +const client = request(`http://localhost:${APP_PORT}`); + +export const updateWorkflowVersionTrigger = async ({ + workflowVersionId, + trigger, +}: { + workflowVersionId: string; + trigger: object; +}) => { + const response = await client + .post('/graphql') + .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) + .send({ + query: ` + mutation UpdateWorkflowVersionTrigger( + $input: UpdateWorkflowVersionTriggerInput! + ) { + updateWorkflowVersionTrigger(input: $input) { + trigger + } + } + `, + variables: { + input: { + workflowVersionId, + trigger, + }, + }, + }); + + if (response.body.errors) { + throw new Error( + `Failed to update workflow version trigger: ${JSON.stringify( + response.body.errors, + )}`, + ); + } + + return response; +}; diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/workflow-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/workflow-resolver.integration-spec.ts index dd49d54aa1..880a62396e 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workflow/workflow-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/workflow-resolver.integration-spec.ts @@ -1,4 +1,5 @@ import request from 'supertest'; +import { updateWorkflowVersionTrigger } from 'test/integration/graphql/suites/workflow/utils/update-workflow-version-trigger.util'; import { findCommandMenuItems } from 'test/integration/metadata/suites/command-menu-item/utils/find-command-menu-items.util'; import { type CommandMenuItemDTO } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item.dto'; @@ -304,30 +305,16 @@ describe('workflowResolver command menu item label', () => { createdWorkflowVersionId = getWorkflowResponse.body.data.workflow.versions.edges[0].node.id; - await client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send({ - query: ` - mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) { - updateWorkflowVersion(id: $id, data: $data) { - id - } - } - `, - variables: { - id: createdWorkflowVersionId, - data: { - trigger: { - name: 'Manual Trigger', - type: 'MANUAL', - settings: { outputSchema: {} }, - nextStepIds: [], - position: { x: 0, y: 0 }, - }, - }, - }, - }); + await updateWorkflowVersionTrigger({ + workflowVersionId: createdWorkflowVersionId!, + trigger: { + name: 'Manual Trigger', + type: 'MANUAL', + settings: { outputSchema: {} }, + nextStepIds: [], + position: { x: 0, y: 0 }, + }, + }); const createStepResponse = await client .post('/graphql')