feat(workflow): dedicated updateWorkflowVersionTrigger mutation + close version CRUD holes (#23207)
Prerequisite for the workflow-core soft-ref migration: every
`workflowVersion` content write must go through a dedicated,
draft-guarded server mutation so it can later be wrapped in a
transactional core mirror. This closes the generic-CRUD holes that let
writes bypass that path.
## Part A - dedicated `updateWorkflowVersionTrigger` mutation
The builder saved a version's trigger through generic
`updateOneWorkflowVersion` - the only content write not going through a
dedicated mutation. Added:
- Server: `updateWorkflowVersionTrigger(input: { workflowVersionId,
trigger })` resolver +
`WorkflowVersionStepWorkspaceService.updateWorkflowVersionTrigger`,
draft-guarded via `getValidatedDraftWorkflowVersion` then
`updateWorkflowVersionStepsAndTrigger` (reuses existing write logic).
- Front: `useUpdateWorkflowVersionTrigger` now calls the dedicated
mutation instead of `useUpdateOneRecord`.
## Part B - restrict generic `updateOneWorkflowVersion`
`validateWorkflowVersionForUpdateOne` previously allowed writing
`trigger`, `position`, `workflowId` (re-parenting),
`coreWorkflowVersionId`, and let `steps: null` slip through on a draft.
It now rejects any update that sets `steps`, `trigger`, `status`,
`workflowId`, or `coreWorkflowVersionId`, or that clears the `name`,
while still allowing a plain rename. (A name-only allowlist was tried
first but blocked legitimate renames - at the pre-hook the generic
update payload is not single-key - so it was replaced by this denylist,
verified live.)
## Part C - close the destroy/restore hole
`workflowVersion` had no `destroyOne/destroyMany/restoreOne/restoreMany`
query hooks, so a caller with object permission could hard-destroy any
version (including active) or resurrect one with no validation. Added
pre-hooks that forbid all four via the API ("Method not allowed"),
matching the existing forbidden generic mutations (`createOne`,
`deleteMany`, ...). Rationale: there is no legitimate API use for
standalone version destroy/restore - retention purging happens through
the trash-cleanup cron (internal, not hook-gated) and restore happens
through the workflow-restore cascade or create-draft-from-version.
## Tests
Integration specs that set a trigger through the generic mutation were
migrated to the new `updateWorkflowVersionTrigger` mutation (new
`update-workflow-version-trigger.util.ts`). Unit test for the front hook
updated.
## Verification
- `twenty-server` + `twenty-front` typecheck: clean.
- oxlint + oxfmt on all changed files: clean.
- `graphql.ts` regenerated for the new mutation; its types match the
server DTOs exactly. Local `graphql:generate` introspects a running
server, so it only succeeds against a server built from this branch - CI
regenerates against the PR server and verifies.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23207?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@@ -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<Array<ObjectRecordFilterInput>>;
|
||||
createdAt?: InputMaybe<DateTimeFilter>;
|
||||
@@ -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<Scalars['JSON']['output']>;
|
||||
};
|
||||
|
||||
export type WorkflowVersionTrigger = {
|
||||
__typename?: 'WorkflowVersionTrigger';
|
||||
trigger?: Maybe<Scalars['JSON']['output']>;
|
||||
};
|
||||
|
||||
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<any> | 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<StopWorkflowRunMutation, StopWorkflowRunMutationVariables>;
|
||||
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<UpdateWorkflowRunStepMutation, UpdateWorkflowRunStepMutationVariables>;
|
||||
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<UpdateWorkflowVersionStepMutation, UpdateWorkflowVersionStepMutationVariables>;
|
||||
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<UpdateWorkflowVersionTriggerMutation, UpdateWorkflowVersionTriggerMutationVariables>;
|
||||
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<WorkflowStepConnectedAccountHandleQuery, WorkflowStepConnectedAccountHandleQueryVariables>;
|
||||
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<SubmitFormStepMutation, SubmitFormStepMutationVariables>;
|
||||
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<TestHttpRequestMutation, TestHttpRequestMutationVariables>;
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_WORKFLOW_VERSION_TRIGGER = gql`
|
||||
mutation UpdateWorkflowVersionTrigger(
|
||||
$input: UpdateWorkflowVersionTriggerInput!
|
||||
) {
|
||||
updateWorkflowVersionTrigger(input: $input) {
|
||||
trigger
|
||||
}
|
||||
}
|
||||
`;
|
||||
+51
-16
@@ -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());
|
||||
|
||||
|
||||
+69
-10
@@ -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<WorkflowVersion>(workflowVersionId);
|
||||
if (!isDefined(cachedRecord)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: {
|
||||
...cachedRecord,
|
||||
trigger: updatedTrigger,
|
||||
},
|
||||
recordGqlFields: {
|
||||
trigger: true,
|
||||
},
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+11
@@ -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;
|
||||
}
|
||||
+17
@@ -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<WorkflowVersionTriggerDTO> {
|
||||
return this.workflowVersionStepWorkspaceService.updateWorkflowVersionTrigger(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
trigger,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => WorkflowVersionStepChangesDTO)
|
||||
async deleteWorkflowVersionStep(
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
|
||||
+8
@@ -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,
|
||||
|
||||
+23
@@ -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<DestroyManyResolverArgs> {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'Method not allowed.',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
}
|
||||
+23
@@ -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<DestroyOneResolverArgs> {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'Method not allowed.',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
}
|
||||
+23
@@ -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<RestoreManyResolverArgs> {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'Method not allowed.',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
}
|
||||
+23
@@ -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<RestoreOneResolverArgs> {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'Method not allowed.',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
}
|
||||
+24
-19
@@ -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<WorkflowVersionWorkspaceEntity>;
|
||||
}) {
|
||||
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(
|
||||
|
||||
+31
@@ -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<WorkflowVersionTriggerDTO> {
|
||||
await this.workflowVersionStepHelpersWorkspaceService.getValidatedDraftWorkflowVersion(
|
||||
{
|
||||
workflowVersionId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
await this.workflowVersionStepHelpersWorkspaceService.updateWorkflowVersionStepsAndTrigger(
|
||||
{
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
trigger,
|
||||
},
|
||||
);
|
||||
|
||||
return { trigger };
|
||||
}
|
||||
|
||||
async deleteWorkflowVersionStep({
|
||||
workspaceId,
|
||||
workflowVersionId,
|
||||
|
||||
+5
-16
@@ -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();
|
||||
|
||||
|
||||
+8
-7
@@ -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,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
+11
-22
@@ -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')
|
||||
|
||||
+5
-19
@@ -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();
|
||||
|
||||
|
||||
+34
-45
@@ -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(
|
||||
`
|
||||
|
||||
+11
-24
@@ -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')
|
||||
|
||||
+11
-24
@@ -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();
|
||||
|
||||
|
||||
+42
@@ -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;
|
||||
};
|
||||
+11
-24
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user