Implement branch front end (#13489)

First PR to support workflow branches on frontend
This commit is contained in:
martmull
2025-08-04 16:11:46 +02:00
committed by GitHub
parent 2c87c6fc1b
commit 9a05673093
124 changed files with 3735 additions and 938 deletions
@@ -6,7 +6,9 @@ import { useCreateStep } from '../useCreateStep';
const mockCreateDraftFromWorkflowVersion = jest.fn().mockResolvedValue('457');
const mockCreateWorkflowVersionStep = jest.fn().mockResolvedValue({
data: { createWorkflowVersionStep: { id: '1', type: 'CODE' } },
data: {
createWorkflowVersionStep: { createdStep: { id: '1', type: 'CODE' } },
},
});
jest.mock(
@@ -0,0 +1,50 @@
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useCreateWorkflowVersionEdge } from '@/workflow/workflow-steps/hooks/useCreateWorkflowVersionEdge';
import { useState } from 'react';
import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
export const useCreateEdge = ({
workflow,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const [isLoading, setIsLoading] = useState(false);
const { createWorkflowVersionEdge } = useCreateWorkflowVersionEdge();
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
const createEdge = async ({
source,
target,
}: {
source: string;
target: string;
}) => {
if (isLoading === true) {
return;
}
setIsLoading(true);
try {
const workflowVersionId = await getUpdatableWorkflowVersion(workflow);
const createdEdge = (
await createWorkflowVersionEdge({
workflowVersionId,
source,
target,
})
)?.data?.createWorkflowVersionEdge;
if (!createdEdge) {
return;
}
} finally {
setIsLoading(false);
}
};
return { createEdge };
};
@@ -36,10 +36,12 @@ export const useCreateStep = ({
newStepType,
parentStepId,
nextStepId,
position,
}: {
newStepType: WorkflowStepType;
parentStepId: string;
parentStepId: string | undefined;
nextStepId: string | undefined;
position?: { x: number; y: number };
}) => {
if (isLoading === true) {
return;
@@ -50,15 +52,18 @@ export const useCreateStep = ({
try {
const workflowVersionId = await getUpdatableWorkflowVersion(workflow);
const createdStep = (
const workflowVersionStepChanges = (
await createWorkflowVersionStep({
workflowVersionId,
stepType: newStepType,
parentStepId,
nextStepId,
position,
})
)?.data?.createWorkflowVersionStep;
const createdStep = workflowVersionStepChanges?.createdStep;
if (!isDefined(createdStep)) {
throw new Error("Couldn't create step");
}
@@ -0,0 +1,37 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useMutation } from '@apollo/client';
import {
CreateWorkflowVersionEdgeMutation,
CreateWorkflowVersionEdgeMutationVariables,
} from '~/generated-metadata/graphql';
import { CREATE_WORKFLOW_VERSION_EDGE } from '@/workflow/graphql/mutations/createWorkflowVersionEdge';
import { CreateWorkflowVersionEdgeInput } from '~/generated/graphql';
import { useUpdateWorkflowVersionCache } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionCache';
export const useCreateWorkflowVersionEdge = () => {
const apolloCoreClient = useApolloCoreClient();
const { updateWorkflowVersionCache } = useUpdateWorkflowVersionCache();
const [mutate] = useMutation<
CreateWorkflowVersionEdgeMutation,
CreateWorkflowVersionEdgeMutationVariables
>(CREATE_WORKFLOW_VERSION_EDGE, { client: apolloCoreClient });
const createWorkflowVersionEdge = async (
input: CreateWorkflowVersionEdgeInput,
) => {
const result = await mutate({ variables: { input } });
const workflowVersionStepChanges = result?.data?.createWorkflowVersionEdge;
updateWorkflowVersionCache({
workflowVersionStepChanges,
workflowVersionId: input.workflowVersionId,
});
return result;
};
return { createWorkflowVersionEdge };
};
@@ -1,36 +1,25 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { CREATE_WORKFLOW_VERSION_STEP } from '@/workflow/graphql/mutations/createWorkflowVersionStep';
import { WorkflowVersion } from '@/workflow/types/Workflow';
import { useMutation } from '@apollo/client';
import { isDefined } from 'twenty-shared/utils';
import {
CreateWorkflowVersionStepInput,
CreateWorkflowVersionStepMutation,
CreateWorkflowVersionStepMutationVariables,
} from '~/generated-metadata/graphql';
import { useUpdateWorkflowVersionCache } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionCache';
export const useCreateWorkflowVersionStep = () => {
const apolloCoreClient = useApolloCoreClient();
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const { updateWorkflowVersionCache } = useUpdateWorkflowVersionCache();
const [mutate] = useMutation<
CreateWorkflowVersionStepMutation,
CreateWorkflowVersionStepMutationVariables
>(CREATE_WORKFLOW_VERSION_STEP, {
client: apolloCoreClient,
});
const createWorkflowVersionStep = async (
input: CreateWorkflowVersionStepInput,
) => {
@@ -38,57 +27,13 @@ export const useCreateWorkflowVersionStep = () => {
variables: { input },
});
const insertedStep = result?.data?.createWorkflowVersionStep;
const workflowVersionStepChanges = result?.data?.createWorkflowVersionStep;
if (!isDefined(insertedStep)) {
return;
}
const cachedRecord = getRecordFromCache<WorkflowVersion>(
input.workflowVersionId,
);
if (!isDefined(cachedRecord)) {
return;
}
const { parentStepId, nextStepId } = input;
const updatedExistingSteps =
cachedRecord.steps?.map((existingStep) => {
if (existingStep.id === parentStepId) {
return {
...existingStep,
nextStepIds: [
...new Set([
...(existingStep.nextStepIds?.filter(
(id) => id !== nextStepId,
) || []),
insertedStep.id,
]),
],
};
}
return existingStep;
}) ?? [];
const newCachedRecord = {
...cachedRecord,
steps: [...updatedExistingSteps, insertedStep],
};
const recordGqlFields = {
steps: true,
};
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloCoreClient.cache,
record: newCachedRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
updateWorkflowVersionCache({
workflowVersionStepChanges,
workflowVersionId: input.workflowVersionId,
});
return result;
};
@@ -0,0 +1,50 @@
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useDeleteWorkflowVersionEdge } from '@/workflow/workflow-steps/hooks/useDeleteWorkflowVersionEdge';
import { useState } from 'react';
import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
export const useDeleteEdge = ({
workflow,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const [isLoading, setIsLoading] = useState(false);
const { deleteWorkflowVersionEdge } = useDeleteWorkflowVersionEdge();
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
const deleteEdge = async ({
source,
target,
}: {
source: string;
target: string;
}) => {
if (isLoading === true) {
return;
}
setIsLoading(true);
try {
const workflowVersionId = await getUpdatableWorkflowVersion(workflow);
const deletedEdge = (
await deleteWorkflowVersionEdge({
workflowVersionId,
source,
target,
})
)?.data?.deleteWorkflowVersionEdge;
if (!deletedEdge) {
return;
}
} finally {
setIsLoading(false);
}
};
return { deleteEdge };
};
@@ -0,0 +1,37 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useMutation } from '@apollo/client';
import { CreateWorkflowVersionEdgeInput } from '~/generated/graphql';
import { DELETE_WORKFLOW_VERSION_EDGE } from '@/workflow/graphql/mutations/deleteWorkflowVersionEdge';
import {
DeleteWorkflowVersionEdgeMutation,
DeleteWorkflowVersionEdgeMutationVariables,
} from '~/generated-metadata/graphql';
import { useUpdateWorkflowVersionCache } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionCache';
export const useDeleteWorkflowVersionEdge = () => {
const apolloCoreClient = useApolloCoreClient();
const { updateWorkflowVersionCache } = useUpdateWorkflowVersionCache();
const [mutate] = useMutation<
DeleteWorkflowVersionEdgeMutation,
DeleteWorkflowVersionEdgeMutationVariables
>(DELETE_WORKFLOW_VERSION_EDGE, { client: apolloCoreClient });
const deleteWorkflowVersionEdge = async (
input: CreateWorkflowVersionEdgeInput,
) => {
const result = await mutate({ variables: { input } });
const workflowVersionStepChanges = result?.data?.deleteWorkflowVersionEdge;
updateWorkflowVersionCache({
workflowVersionStepChanges,
workflowVersionId: input.workflowVersionId,
});
return result;
};
return { deleteWorkflowVersionEdge };
};
@@ -0,0 +1,95 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { isDefined } from 'twenty-shared/utils';
import { WorkflowVersion } from '@/workflow/types/Workflow';
import {
WorkflowAction,
WorkflowVersionStepChanges,
} from '~/generated/graphql';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
export const useUpdateWorkflowVersionCache = () => {
const apolloCoreClient = useApolloCoreClient();
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
const updateWorkflowVersionCache = ({
workflowVersionStepChanges,
workflowVersionId,
}: {
workflowVersionStepChanges: WorkflowVersionStepChanges | undefined;
workflowVersionId: string;
}) => {
if (!isDefined(workflowVersionStepChanges)) {
return;
}
const cachedRecord = getRecordFromCache<WorkflowVersion>(workflowVersionId);
if (!isDefined(cachedRecord)) {
return;
}
const { triggerNextStepIds, stepsNextStepIds, createdStep, deletedStepId } =
workflowVersionStepChanges;
const newCachedRecord = {
...cachedRecord,
trigger: isDefined(cachedRecord.trigger)
? {
...cachedRecord.trigger,
nextStepIds: triggerNextStepIds,
}
: cachedRecord.trigger,
steps: (cachedRecord.steps || []).map((step: WorkflowAction) => ({
...step,
nextStepIds: stepsNextStepIds[step.id] ?? step.nextStepIds,
})),
};
if (isDefined(createdStep)) {
const formattedCreatedStep = {
...createdStep,
nextStepIds: createdStep.nextStepIds || [],
};
newCachedRecord.steps.push(formattedCreatedStep);
}
if (isDefined(deletedStepId)) {
newCachedRecord.steps = newCachedRecord.steps.filter(
(step) => step.id !== deletedStepId,
);
}
const recordGqlFields = {
steps: true,
trigger: true,
};
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloCoreClient.cache,
record: newCachedRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
};
return { updateWorkflowVersionCache };
};
@@ -4,11 +4,16 @@ import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-
type WorkflowInsertStepIdsState = {
parentStepId: string | undefined;
nextStepId: string | undefined;
position?: { x: number; y: number };
};
export const workflowInsertStepIdsComponentState =
createComponentStateV2<WorkflowInsertStepIdsState>({
key: 'workflowInsertStepIdsComponentState',
defaultValue: { parentStepId: undefined, nextStepId: undefined },
defaultValue: {
parentStepId: undefined,
nextStepId: undefined,
position: undefined,
},
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
});