Remove is branch enabled feature flag (#14357)

As title
This commit is contained in:
Thomas Trompette
2025-09-09 10:07:20 +02:00
committed by GitHub
parent 7bcbaee0c8
commit 7094f0ee08
29 changed files with 35 additions and 403 deletions
@@ -1012,7 +1012,6 @@ export enum FeatureFlagKey {
IS_RELATION_CONNECT_ENABLED = 'IS_RELATION_CONNECT_ENABLED',
IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
IS_WORKFLOW_BRANCH_ENABLED = 'IS_WORKFLOW_BRANCH_ENABLED',
IS_WORKFLOW_ITERATOR_ENABLED = 'IS_WORKFLOW_ITERATOR_ENABLED',
IS_WORKSPACE_MIGRATION_V2_ENABLED = 'IS_WORKSPACE_MIGRATION_V2_ENABLED'
}
@@ -976,7 +976,6 @@ export enum FeatureFlagKey {
IS_RELATION_CONNECT_ENABLED = 'IS_RELATION_CONNECT_ENABLED',
IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
IS_WORKFLOW_BRANCH_ENABLED = 'IS_WORKFLOW_BRANCH_ENABLED',
IS_WORKFLOW_ITERATOR_ENABLED = 'IS_WORKFLOW_ITERATOR_ENABLED',
IS_WORKSPACE_MIGRATION_V2_ENABLED = 'IS_WORKSPACE_MIGRATION_V2_ENABLED'
}
@@ -12,12 +12,10 @@ import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/Da
import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { useIcons } from 'twenty-ui/display';
import { MenuItemCommand } from 'twenty-ui/navigation';
import { FeatureFlagKey } from '~/generated/graphql';
export const CommandMenuWorkflowSelectTriggerTypeContent = ({
workflow,
@@ -35,10 +33,6 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
);
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
const handleTriggerTypeClick = ({
type,
defaultLabel,
@@ -54,7 +48,6 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
defaultLabel,
type,
activeNonSystemObjectMetadataItems,
steps: !isWorkflowBranchEnabled ? workflow.currentVersion.steps : [],
}),
);
@@ -14,11 +14,9 @@ import { workflowRunDiagramAutomaticallyOpenedStepsComponentState } from '@/work
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { FeatureFlagKey } from '~/generated/graphql';
export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -27,10 +25,6 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => {
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
const runWorkflowRunOpeningInCommandMenuSideEffects = useRecoilCallback(
({ snapshot, set }) =>
({
@@ -62,7 +56,6 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => {
steps: workflowRunRecord.state.flow.steps,
stepInfos: workflowRunRecord.state.stepInfos,
trigger: workflowRunRecord.state.flow.trigger,
isWorkflowBranchEnabled,
});
if (!isDefined(stepToOpenByDefault)) {
@@ -132,7 +125,6 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => {
[
apolloCoreClient.cache,
objectPermissionsByObjectMetadataId,
isWorkflowBranchEnabled,
openWorkflowRunViewStepInCommandMenu,
getIcon,
],
@@ -5,7 +5,6 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramCustomMarkers';
import { WorkflowDiagramRightClickCommandMenu } from '@/workflow/workflow-diagram/components/WorkflowDiagramRightClickCommandMenu';
import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
@@ -19,9 +18,10 @@ import {
type WorkflowDiagramNode,
type WorkflowDiagramNodeType,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getOrganizedDiagram } from '@/workflow/workflow-diagram/utils/getOrganizedDiagram';
import { WorkflowDiagramConnection } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramConnection';
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramCustomMarkers';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
@@ -53,9 +53,6 @@ import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { Tag, type TagColor } from 'twenty-ui/components';
import { THEME_COMMON } from 'twenty-ui/theme';
import { FeatureFlagKey } from '~/generated/graphql';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { WorkflowDiagramConnection } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramConnection';
const StyledResetReactflowStyles = styled.div`
height: 100%;
@@ -181,23 +178,15 @@ export const WorkflowDiagramCanvasBase = ({
const { setEdgeHovered, clearEdgeHover } = useEdgeState();
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
const [workflowDiagramFlowInitialized, setWorkflowDiagramFlowInitialized] =
useState<boolean>(false);
const { nodes, edges } = useMemo(() => {
if (isDefined(workflowDiagram)) {
if (isWorkflowBranchEnabled) {
return workflowDiagram;
}
return getOrganizedDiagram(workflowDiagram);
return workflowDiagram;
}
return { nodes: [], edges: [] };
}, [workflowDiagram, isWorkflowBranchEnabled]);
}, [workflowDiagram]);
const { rightDrawerState } = useRightDrawerState();
const { isInRightDrawer } = useContext(ActionMenuContext);
@@ -407,10 +396,6 @@ export const WorkflowDiagramCanvasBase = ({
WorkflowDiagramNode,
WorkflowDiagramEdge
> = async ({ nodes, edges }) => {
if (!isWorkflowBranchEnabled) {
return false;
}
if (nodes.length === 0 && edges.length > 0) {
return true;
}
@@ -421,7 +406,7 @@ export const WorkflowDiagramCanvasBase = ({
const onDelete: OnDelete<WorkflowDiagramNode, WorkflowDiagramEdge> = async ({
edges,
}) => {
if (!isWorkflowBranchEnabled || !isDefined(onDeleteEdge)) {
if (!isDefined(onDeleteEdge)) {
return;
}
@@ -479,23 +464,19 @@ export const WorkflowDiagramCanvasBase = ({
onEdgeMouseLeave={onEdgeMouseLeave}
onNodesChange={handleNodesChanges}
onEdgesChange={handleEdgesChange}
onConnect={isWorkflowBranchEnabled ? onConnect : undefined}
onNodeDragStop={isWorkflowBranchEnabled ? onNodeDragStop : undefined}
onConnect={onConnect}
onNodeDragStop={onNodeDragStop}
onBeforeDelete={onBeforeDelete}
onDelete={onDelete}
selectNodesOnDrag={false}
proOptions={{ hideAttribution: true }}
multiSelectionKeyCode={null}
nodesFocusable={false}
nodesDraggable={isWorkflowBranchEnabled ? nodesDraggable : false}
edgesFocusable={
isWorkflowBranchEnabled ? isDefined(onDeleteEdge) : false
}
nodesDraggable={nodesDraggable}
edgesFocusable={isDefined(onDeleteEdge)}
panOnDrag={workflowDiagramPanOnDrag}
onPaneContextMenu={
isWorkflowBranchEnabled ? onPaneContextMenu : undefined
}
nodesConnectable={isWorkflowBranchEnabled ? nodesConnectable : false}
onPaneContextMenu={onPaneContextMenu}
nodesConnectable={nodesConnectable}
paneClickDistance={10} // Fix small unwanted user dragging does not select node
preventScrolling={false}
connectionLineComponent={WorkflowDiagramConnection}
@@ -506,7 +487,7 @@ export const WorkflowDiagramCanvasBase = ({
{children}
</ReactFlow>
{isDefined(handlePaneContextMenu) && isWorkflowBranchEnabled && (
{isDefined(handlePaneContextMenu) && (
<WorkflowDiagramRightClickCommandMenu />
)}
@@ -1,26 +1,16 @@
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import {
type OnSelectionChangeParams,
useOnSelectionChange,
} from '@xyflow/react';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated/graphql';
export const WorkflowDiagramCanvasEditableEffect = () => {
const { setEdgeSelected, clearEdgeSelected } = useEdgeState();
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
const handleSelectedEdges = useCallback(
({ edges }: OnSelectionChangeParams) => {
if (!isWorkflowBranchEnabled) {
return;
}
const selectedEdge = edges?.[0];
if (!isDefined(selectedEdge)) {
@@ -34,7 +24,7 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
target: selectedEdge.target,
});
},
[isWorkflowBranchEnabled, setEdgeSelected, clearEdgeSelected],
[setEdgeSelected, clearEdgeSelected],
);
useOnSelectionChange({
@@ -12,11 +12,9 @@ import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/state
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
import { mergeWorkflowDiagrams } from '@/workflow/workflow-diagram/utils/mergeWorkflowDiagrams';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useEffect } from 'react';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated/graphql';
export const WorkflowDiagramEffect = () => {
const workflowVisualizerWorkflowId = useRecoilComponentValue(
@@ -40,10 +38,6 @@ export const WorkflowDiagramEffect = () => {
workflowLastCreatedStepIdComponentState,
);
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
const computeAndMergeNewWorkflowDiagram = useRecoilCallback(
({ snapshot, set }) => {
return (currentVersion: WorkflowVersion) => {
@@ -54,7 +48,6 @@ export const WorkflowDiagramEffect = () => {
const nextWorkflowDiagram = getWorkflowVersionDiagram({
workflowVersion: currentVersion,
isWorkflowBranchEnabled,
isEditable: true,
});
@@ -88,11 +81,7 @@ export const WorkflowDiagramEffect = () => {
set(workflowDiagramState, mergedWorkflowDiagram);
};
},
[
workflowDiagramState,
isWorkflowBranchEnabled,
workflowLastCreatedStepIdState,
],
[workflowDiagramState, workflowLastCreatedStepIdState],
);
const currentVersion = workflowWithCurrentVersion?.currentVersion;
@@ -18,12 +18,10 @@ import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/
import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { selectWorkflowDiagramNode } from '@/workflow/workflow-diagram/utils/selectWorkflowDiagramNode';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext, useEffect } from 'react';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { FeatureFlagKey } from '~/generated/graphql';
export const WorkflowRunVisualizerEffect = ({
workflowRunId,
@@ -71,10 +69,6 @@ export const WorkflowRunVisualizerEffect = ({
const { isInRightDrawer } = useContext(ActionMenuContext);
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
useEffect(() => {
setWorkflowRunId(workflowRunId);
}, [setWorkflowRunId, workflowRunId]);
@@ -133,7 +127,6 @@ export const WorkflowRunVisualizerEffect = ({
trigger: workflowRunState.flow.trigger,
steps: workflowRunState.flow.steps,
stepInfos: workflowRunState.stepInfos,
isWorkflowBranchEnabled,
});
if (workflowDiagramStatus !== 'done') {
@@ -204,7 +197,6 @@ export const WorkflowRunVisualizerEffect = ({
[
flowState,
getIcon,
isWorkflowBranchEnabled,
openWorkflowRunViewStepInCommandMenu,
workflowDiagramState,
workflowDiagramStatusState,
@@ -6,10 +6,8 @@ import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/wo
import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState';
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useEffect } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated/graphql';
export const WorkflowVersionVisualizerEffect = ({
workflowVersionId,
@@ -31,10 +29,6 @@ export const WorkflowVersionVisualizerEffect = ({
const { populateStepsOutputSchema } = useStepsOutputSchema();
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
useEffect(() => {
if (!isDefined(workflowVersion)) {
setFlow(undefined);
@@ -66,12 +60,11 @@ export const WorkflowVersionVisualizerEffect = ({
const nextWorkflowDiagram = getWorkflowVersionDiagram({
workflowVersion,
isWorkflowBranchEnabled,
isEditable: false,
});
setWorkflowDiagram(nextWorkflowDiagram);
}, [isWorkflowBranchEnabled, setWorkflowDiagram, workflowVersion]);
}, [setWorkflowDiagram, workflowVersion]);
useEffect(() => {
if (!isDefined(workflowVersion)) {
@@ -104,8 +104,6 @@ describe('generateWorkflowRunDiagram', () => {
trigger,
steps,
stepInfos,
isWorkflowBranchEnabled: true,
});
expect(result).toMatchInlineSnapshot(`
@@ -337,8 +335,6 @@ describe('generateWorkflowRunDiagram', () => {
trigger,
steps,
stepInfos,
isWorkflowBranchEnabled: true,
});
expect(result).toMatchInlineSnapshot(`
@@ -570,8 +566,6 @@ describe('generateWorkflowRunDiagram', () => {
trigger,
steps,
stepInfos,
isWorkflowBranchEnabled: true,
});
expect(result).toMatchInlineSnapshot(`
@@ -822,8 +816,6 @@ describe('generateWorkflowRunDiagram', () => {
trigger,
steps,
stepInfos,
isWorkflowBranchEnabled: true,
});
expect(result).toMatchInlineSnapshot(`
@@ -1055,8 +1047,6 @@ describe('generateWorkflowRunDiagram', () => {
trigger,
steps,
stepInfos,
isWorkflowBranchEnabled: true,
});
expect(result).toMatchInlineSnapshot(`
@@ -46,7 +46,6 @@ describe('transformFilterNodesAsEdges', () => {
nodes: diagram.nodes,
edges: diagram.edges,
defaultFilterEdgeType: 'filter--editable',
isWorkflowBranchEnabled: true,
});
expect(result.nodes).toEqual(diagram.nodes);
@@ -113,7 +112,6 @@ describe('transformFilterNodesAsEdges', () => {
nodes: diagram.nodes,
edges: diagram.edges,
defaultFilterEdgeType: 'filter--editable',
isWorkflowBranchEnabled: true,
});
// Should only have nodes A and C
@@ -259,7 +257,6 @@ describe('transformFilterNodesAsEdges', () => {
nodes: diagram.nodes,
edges: diagram.edges,
defaultFilterEdgeType: 'filter--editable',
isWorkflowBranchEnabled: true,
});
// Should only have nodes A, C, and D
@@ -352,7 +349,6 @@ describe('transformFilterNodesAsEdges', () => {
nodes: diagram.nodes,
edges: diagram.edges,
defaultFilterEdgeType: 'filter--editable',
isWorkflowBranchEnabled: true,
});
// Should only have node A (filter node B is removed)
@@ -435,7 +431,6 @@ describe('transformFilterNodesAsEdges', () => {
nodes: diagram.nodes,
edges: diagram.edges,
defaultFilterEdgeType: 'filter--editable',
isWorkflowBranchEnabled: true,
});
// Should have trigger and C nodes
@@ -485,86 +480,4 @@ describe('transformFilterNodesAsEdges', () => {
},
]);
});
it('should set selectable and deletable to false when isWorkflowBranchEnabled is false', () => {
const diagram: WorkflowDiagram = {
nodes: [
{
id: 'A',
data: {
nodeType: 'action',
actionType: 'CODE',
name: 'Step A',
hasNextStepIds: true,
position: { x: 0, y: 0 },
stepId: 'A',
},
position: { x: 0, y: 0 },
},
{
id: 'B',
data: {
nodeType: 'action',
actionType: 'FILTER',
name: 'Filter B',
hasNextStepIds: true,
position: { x: 0, y: 150 },
stepId: 'B',
},
position: { x: 0, y: 150 },
},
{
id: 'C',
data: {
nodeType: 'action',
actionType: 'SEND_EMAIL',
name: 'Step C',
hasNextStepIds: false,
position: { x: 0, y: 300 },
stepId: 'C',
},
position: { x: 0, y: 300 },
},
],
edges: [
{
id: 'A-B',
source: 'A',
target: 'B',
data: { edgeType: 'default' },
},
{
id: 'B-C',
source: 'B',
target: 'C',
data: { edgeType: 'default' },
},
],
};
const result = transformFilterNodesAsEdges({
nodes: diagram.nodes,
edges: diagram.edges,
defaultFilterEdgeType: 'filter--editable',
isWorkflowBranchEnabled: false,
});
// Should have one edge with filter data
expect(result.edges).toHaveLength(1);
expect(result.edges[0]).toEqual({
id: 'A-C-filter-B',
type: 'filter--editable',
source: 'A',
target: 'C',
selectable: false,
deletable: false,
data: {
edgeType: 'filter',
stepId: 'B',
name: 'Filter B',
runStatus: undefined,
filterSettings: {},
},
});
});
});
@@ -4,7 +4,6 @@ import {
} from '@/workflow/types/Workflow';
import { FIRST_NODE_POSITION } from '@/workflow/workflow-diagram/constants/FirstNodePosition';
import { VERTICAL_DISTANCE_BETWEEN_TWO_NODES } from '@/workflow/workflow-diagram/constants/VerticalDistanceBetweenTwoNodes';
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowVisualizerEdgeDefaultConfiguration';
import {
type WorkflowDiagram,
type WorkflowDiagramEdge,
@@ -13,9 +12,9 @@ import {
type WorkflowDiagramStepNodeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowDiagramTriggerNode } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramTriggerNode';
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowVisualizerEdgeDefaultConfiguration';
import { WORKFLOW_DIAGRAM_EMPTY_TRIGGER_NODE_DEFINITION } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEmptyTriggerNodeDefinition';
import { getRootStepIds } from '@/workflow/workflow-trigger/utils/getRootStepIds';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { v4 } from 'uuid';
@@ -24,12 +23,10 @@ export const generateWorkflowDiagram = ({
trigger,
steps,
defaultEdgeType,
isWorkflowBranchEnabled = false,
}: {
trigger: WorkflowTrigger | undefined;
steps: Array<WorkflowStep>;
defaultEdgeType: WorkflowDiagramEdgeType;
isWorkflowBranchEnabled?: boolean;
}): WorkflowDiagram => {
const nodes: Array<WorkflowDiagramNode> = [];
const edges: Array<WorkflowDiagramEdge> = [];
@@ -38,19 +35,6 @@ export const generateWorkflowDiagram = ({
nodes.push(getWorkflowDiagramTriggerNode({ trigger }));
} else {
nodes.push(WORKFLOW_DIAGRAM_EMPTY_TRIGGER_NODE_DEFINITION);
const triggerNextStepIds =
isDefined(steps) && !isWorkflowBranchEnabled ? getRootStepIds(steps) : [];
triggerNextStepIds.forEach((stepId) => {
edges.push({
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION,
type: 'blank',
id: v4(),
source: 'trigger',
target: stepId,
});
});
}
let levelYPos = FIRST_NODE_POSITION.y;
@@ -19,12 +19,10 @@ export const generateWorkflowRunDiagram = ({
trigger,
steps,
stepInfos,
isWorkflowBranchEnabled,
}: {
trigger: WorkflowTrigger;
steps: Array<WorkflowStep>;
stepInfos: WorkflowRunStepInfos | undefined;
isWorkflowBranchEnabled: boolean;
}): {
diagram: WorkflowRunDiagram;
stepToOpenByDefault:
@@ -107,7 +105,6 @@ export const generateWorkflowRunDiagram = ({
nodes: workflowRunDiagramNodes,
edges: workflowRunDiagramEdges,
defaultFilterEdgeType: 'filter--run',
isWorkflowBranchEnabled,
}),
stepToOpenByDefault,
};
@@ -22,11 +22,9 @@ const getEdgeTypeToCreateByDefault = ({
export const getWorkflowVersionDiagram = ({
workflowVersion,
isWorkflowBranchEnabled,
isEditable,
}: {
workflowVersion: WorkflowVersion | undefined;
isWorkflowBranchEnabled?: boolean;
isEditable: boolean;
}): WorkflowDiagram => {
if (!isDefined(workflowVersion)) {
@@ -39,13 +37,11 @@ export const getWorkflowVersionDiagram = ({
defaultEdgeType: getEdgeTypeToCreateByDefault({
isEditable,
}),
isWorkflowBranchEnabled,
});
return transformFilterNodesAsEdges({
nodes: diagram.nodes,
edges: diagram.edges,
defaultFilterEdgeType: isEditable ? 'filter--editable' : 'filter--readonly',
isWorkflowBranchEnabled: isWorkflowBranchEnabled === true,
});
};
@@ -12,12 +12,10 @@ export const transformFilterNodesAsEdges = <
nodes,
edges,
defaultFilterEdgeType,
isWorkflowBranchEnabled,
}: {
nodes: T[];
edges: U[];
defaultFilterEdgeType: WorkflowDiagramEdgeType;
isWorkflowBranchEnabled: boolean;
}): { nodes: T[]; edges: U[] } => {
const filterNodes = nodes.filter(
(node) =>
@@ -55,8 +53,8 @@ export const transformFilterNodesAsEdges = <
type: defaultFilterEdgeType,
id: `${incomingEdge.source}-${outgoingEdge.target}-filter-${filterNode.id}`,
target: outgoingEdge.target,
selectable: isWorkflowBranchEnabled === true,
deletable: isWorkflowBranchEnabled === true,
selectable: true,
deletable: true,
data: {
...incomingEdge.data,
edgeType: 'filter',
@@ -3,17 +3,17 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBaseEdge';
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeButtonGroup';
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { type WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import {
EdgeLabelRenderer,
type EdgeProps,
@@ -23,8 +23,6 @@ import { type MouseEvent, useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFilter, IconPlus, IconTrash } from 'twenty-ui/display';
import { FeatureFlagKey } from '~/generated/graphql';
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
type WorkflowDiagramDefaultEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
@@ -38,10 +36,6 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
markerStart,
markerEnd,
}: WorkflowDiagramDefaultEdgeEditableProps) => {
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
const { isInRightDrawer } = useContext(ActionMenuContext);
const { isEdgeHovered } = useEdgeState();
@@ -142,14 +136,10 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
Icon: IconPlus,
onClick: handleNodeButtonClick,
},
...(isWorkflowBranchEnabled
? [
{
Icon: IconTrash,
onClick: handleDeleteBranch,
},
]
: []),
{
Icon: IconTrash,
onClick: handleDeleteBranch,
},
]}
selected={nodeCreationStarted}
/>
@@ -28,7 +28,6 @@ import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
import { useFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterCounter';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
@@ -50,7 +49,6 @@ import {
} from 'twenty-ui/display';
import { IconButtonGroup } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
import { FeatureFlagKey } from '~/generated/graphql';
type WorkflowDiagramFilterEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
@@ -98,9 +96,6 @@ export const WorkflowDiagramFilterEdgeEditable = ({
const { t } = useLingui();
const theme = useTheme();
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
const [edgePath, labelX, labelY] = getBezierPath({
sourceX,
@@ -274,13 +269,11 @@ export const WorkflowDiagramFilterEdgeEditable = ({
LeftIcon={IconPlus}
onClick={handleAddNodeButtonClick}
/>
{isWorkflowBranchEnabled && (
<MenuItem
text={t`Delete branch`}
LeftIcon={IconTrash}
onClick={handleDeleteBranchClick}
/>
)}
<MenuItem
text={t`Delete branch`}
LeftIcon={IconTrash}
onClick={handleDeleteBranchClick}
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
@@ -2,11 +2,9 @@ import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
import { NODE_HANDLE_HEIGHT_PX } from '@/workflow/workflow-diagram/constants/NodeHandleHeightPx';
import { NODE_HANDLE_WIDTH_PX } from '@/workflow/workflow-diagram/constants/NodeHandleWidthPx';
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { Handle, Position, type HandleProps } from '@xyflow/react';
import { FeatureFlagKey } from '~/generated/graphql';
type WorkflowDiagramHandleSourceProps = {
selected: boolean;
@@ -89,15 +87,11 @@ export const WorkflowDiagramHandleSource = ({
readOnly = false,
runStatus,
}: WorkflowDiagramHandleSourceProps) => {
const isWorkflowBranchEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
);
return (
<StyledHandle
type="source"
position={Position.Bottom}
disableHoverEffect={!isWorkflowBranchEnabled || readOnly}
disableHoverEffect={readOnly}
selected={selected}
hovered={hovered}
runStatus={runStatus}
@@ -28,7 +28,6 @@ describe('getTriggerDefaultDefinition', () => {
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.created`,
outputSchema: {},
},
nextStepIds: [],
position: {
x: 0,
y: 0,
@@ -50,7 +49,6 @@ describe('getTriggerDefaultDefinition', () => {
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.updated`,
outputSchema: {},
},
nextStepIds: [],
position: {
x: 0,
y: 0,
@@ -72,7 +70,6 @@ describe('getTriggerDefaultDefinition', () => {
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.deleted`,
outputSchema: {},
},
nextStepIds: [],
position: {
x: 0,
y: 0,
@@ -94,7 +91,6 @@ describe('getTriggerDefaultDefinition', () => {
eventName: `${generatedMockObjectMetadataItems[0].nameSingular}.created`,
outputSchema: {},
},
nextStepIds: [],
position: {
x: 0,
y: 0,
@@ -118,7 +114,6 @@ describe('getTriggerDefaultDefinition', () => {
icon: COMMAND_MENU_DEFAULT_ICON,
isPinned: false,
},
nextStepIds: [],
position: {
x: 0,
y: 0,
@@ -141,7 +136,6 @@ describe('getTriggerDefaultDefinition', () => {
schedule: { day: 1, hour: 0, minute: 0 },
outputSchema: {},
},
nextStepIds: [],
position: {
x: 0,
y: 0,
@@ -164,7 +158,6 @@ describe('getTriggerDefaultDefinition', () => {
httpMethod: 'GET',
authentication: null,
},
nextStepIds: [],
position: {
x: 0,
y: 0,
@@ -1,25 +1,21 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import {
type WorkflowAction,
type WorkflowTrigger,
type WorkflowTriggerType,
} from '@/workflow/types/Workflow';
import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes';
import { getManualTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings';
import { getRootStepIds } from '@/workflow/workflow-trigger/utils/getRootStepIds';
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
import { assertUnreachable } from 'twenty-shared/utils';
// TODO: This needs to be migrated to the server
export const getTriggerDefaultDefinition = ({
defaultLabel,
type,
activeNonSystemObjectMetadataItems,
steps,
}: {
defaultLabel: string;
type: WorkflowTriggerType;
activeNonSystemObjectMetadataItems: ObjectMetadataItem[];
steps?: WorkflowAction[] | null;
}): WorkflowTrigger => {
if (activeNonSystemObjectMetadataItems.length === 0) {
throw new Error(
@@ -27,12 +23,9 @@ export const getTriggerDefaultDefinition = ({
);
}
const nextStepIds = isDefined(steps) ? getRootStepIds(steps) : [];
const baseTriggerDefinition = {
name: defaultLabel,
position: { x: 0, y: 0 },
nextStepIds,
};
switch (type) {
@@ -22,14 +22,6 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
'https://twenty.com/images/lab/is-imap-smtp-caldav-enabled.png',
},
},
{
key: FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
metadata: {
label: 'Workflow Branches',
description: 'Create multiple branches on your workflows',
imagePath: 'https://twenty.com/images/lab/is-workflow-branch-enabled.png',
},
},
{
key: FeatureFlagKey.IS_MESSAGE_FOLDER_CONTROL_ENABLED,
metadata: {
@@ -7,7 +7,6 @@ export enum FeatureFlagKey {
IS_AI_ENABLED = 'IS_AI_ENABLED',
IS_IMAP_SMTP_CALDAV_ENABLED = 'IS_IMAP_SMTP_CALDAV_ENABLED',
IS_MORPH_RELATION_ENABLED = 'IS_MORPH_RELATION_ENABLED',
IS_WORKFLOW_BRANCH_ENABLED = 'IS_WORKFLOW_BRANCH_ENABLED',
IS_RELATION_CONNECT_ENABLED = 'IS_RELATION_CONNECT_ENABLED',
IS_CORE_VIEW_SYNCING_ENABLED = 'IS_CORE_VIEW_SYNCING_ENABLED',
IS_CORE_VIEW_ENABLED = 'IS_CORE_VIEW_ENABLED',
@@ -130,7 +130,6 @@ describe('WorkspaceEntityManager', () => {
IS_AI_ENABLED: false,
IS_IMAP_SMTP_CALDAV_ENABLED: false,
IS_MORPH_RELATION_ENABLED: false,
IS_WORKFLOW_BRANCH_ENABLED: false,
IS_RELATION_CONNECT_ENABLED: false,
IS_CORE_VIEW_SYNCING_ENABLED: false,
IS_CORE_VIEW_ENABLED: false,
@@ -158,7 +157,6 @@ describe('WorkspaceEntityManager', () => {
IS_AI_ENABLED: false,
IS_IMAP_SMTP_CALDAV_ENABLED: false,
IS_MORPH_RELATION_ENABLED: false,
IS_WORKFLOW_BRANCH_ENABLED: false,
IS_RELATION_CONNECT_ENABLED: false,
IS_CORE_VIEW_SYNCING_ENABLED: false,
IS_CORE_VIEW_ENABLED: false,
@@ -40,11 +40,6 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
workspaceId: workspaceId,
value: false,
},
{
key: FeatureFlagKey.IS_IMAP_SMTP_CALDAV_ENABLED,
workspaceId: workspaceId,
@@ -2,7 +2,6 @@ import { Scope } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
@@ -18,7 +17,6 @@ import {
WorkflowRunException,
WorkflowRunExceptionCode,
} from 'src/modules/workflow/workflow-runner/exceptions/workflow-run.exception';
import { getRootSteps } from 'src/modules/workflow/workflow-runner/utils/get-root-steps.utils';
import { WorkflowRunQueueWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-run-queue.workspace-service';
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
@@ -113,17 +111,7 @@ export class RunWorkflowJob {
workspaceId,
});
const rootSteps = getRootSteps(workflowVersion.steps);
const isWorkflowBranchEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
workspaceId,
);
const stepIds = isWorkflowBranchEnabled
? (workflowVersion.trigger.nextStepIds ?? [])
: (rootSteps.map((step) => step.id) ?? []);
const stepIds = workflowVersion.trigger.nextStepIds ?? [];
await this.workflowExecutorWorkspaceService.executeFromSteps({
stepIds,
@@ -1,85 +0,0 @@
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { getRootSteps } from 'src/modules/workflow/workflow-runner/utils/get-root-steps.utils';
describe('getRootSteps', () => {
it('should return the root steps', () => {
const steps = [
{
id: 'step1',
nextStepIds: ['step2'],
},
{ id: 'step2', nextStepIds: undefined },
] as WorkflowAction[];
const expectedRootSteps = [
{
id: 'step1',
nextStepIds: ['step2'],
},
] as WorkflowAction[];
expect(getRootSteps(steps)).toEqual(expectedRootSteps);
});
it('should not consider step order', () => {
const steps = [
{ id: 'step2', nextStepIds: undefined },
{
id: 'step1',
nextStepIds: ['step2'],
},
] as WorkflowAction[];
const expectedRootSteps = [
{
id: 'step1',
nextStepIds: ['step2'],
},
] as WorkflowAction[];
expect(getRootSteps(steps)).toEqual(expectedRootSteps);
});
it('should handle multiple root steps', () => {
const steps = [
{
id: 'step1',
nextStepIds: ['step3'],
},
{
id: 'step2',
nextStepIds: ['step3'],
},
{ id: 'step3', nextStepIds: ['step4'] },
{ id: 'step4', nextStepIds: undefined },
] as WorkflowAction[];
const expectedRootSteps = [
{
id: 'step1',
nextStepIds: ['step3'],
},
{
id: 'step2',
nextStepIds: ['step3'],
},
] as WorkflowAction[];
expect(getRootSteps(steps)).toEqual(expectedRootSteps);
});
it('should throw if buggy steps provided', () => {
const steps = [
{
id: 'step1',
nextStepIds: ['step2'],
},
{
id: 'step2',
nextStepIds: ['step1'],
},
] as WorkflowAction[];
expect(() => getRootSteps(steps)).toThrow('No root step found');
});
});
@@ -1,24 +0,0 @@
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import {
WorkflowRunException,
WorkflowRunExceptionCode,
} from 'src/modules/workflow/workflow-runner/exceptions/workflow-run.exception';
export const getRootSteps = (steps: WorkflowAction[]): WorkflowAction[] => {
const childIds = new Set<string>();
for (const step of steps) {
step.nextStepIds?.forEach((id) => childIds.add(id));
}
const rootSteps = steps.filter((step) => !childIds.has(step.id));
if (rootSteps.length === 0) {
throw new WorkflowRunException(
'No root step found',
WorkflowRunExceptionCode.WORKFLOW_ROOT_STEP_NOT_FOUND,
);
}
return rootSteps;
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB