Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

Commit

Permalink
feat(multiple-flows): Enable experimental support for multiple flows
Browse files Browse the repository at this point in the history
In order to support multiple flows in the UI, we need to switch our
model from a single flow to an array of flows.

In practical terms, there are at least two alternatives:
1. Refactor existing `IIntegrationJSONStore` to support multiple flows
2. Move from `IIntegrationJSONStore` to another store that supports
multiple flows.

In this commit, the latter was chosen, and for that purpose, the `useFlowsStore`
was created.

In this commit, also an additional setting was added to switch between single and
multiple flows during runtime, to avoid any potential big-bang effect while
developing this feature.

Relates to: #801
  • Loading branch information
lordrip committed May 8, 2023
1 parent dd16b5b commit 8c916e7
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 23 deletions.
19 changes: 18 additions & 1 deletion src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import {
Modal,
ModalVariant,
Popover,
Switch,
TextArea,
TextInput,
} from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';
import { useAlert } from '@rhoas/app-services-ui-shared';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';

export interface ISettingsModal {
handleCloseModal: () => void;
Expand Down Expand Up @@ -176,6 +177,10 @@ export const SettingsModal = ({ handleCloseModal, isModalOpen }: ISettingsModal)
handleCloseModal();
};

const onToggleUseMultipleFlowsSupport = useCallback((useMultipleFlows: boolean) => {
setLocalSettings({ ...localSettings, useMultipleFlows });
}, [localSettings]);

return (
<Modal
actions={[
Expand Down Expand Up @@ -326,6 +331,18 @@ export const SettingsModal = ({ handleCloseModal, isModalOpen }: ISettingsModal)
})}
</FormSelect>
</FormGroup>

{/* TODO: Temporary toggle to enable experimental support for Multiple Flows */}
<FormGroup
fieldId="useMultipleFlows"
label="Enable experimental support for multiple flows"
>
<Switch
aria-label="enable multiple flows support"
isChecked={localSettings.useMultipleFlows}
onChange={onToggleUseMultipleFlowsSupport}
/>
</FormGroup>
</Form>
</Modal>
);
Expand Down
6 changes: 4 additions & 2 deletions src/components/Visualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
VisualizationStepViews,
} from '@kaoto/components';
import { StepsService, VisualizationService } from '@kaoto/services';
import { useIntegrationJsonStore, useVisualizationStore } from '@kaoto/store';
import { useIntegrationJsonStore, useSettingsStore, useVisualizationStore } from '@kaoto/store';
import { IStepProps, IVizStepNode } from '@kaoto/types';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import ReactFlow, { Background, Viewport } from 'reactflow';
Expand All @@ -33,6 +33,8 @@ const Visualization = () => {
UUID: '',
integrationId: '',
});

const useMultipleFlows = useSettingsStore((state) => state.settings.useMultipleFlows);
const visualizationStore = useVisualizationStore.getState();
const layout = useVisualizationStore((state) => state.layout);
const nodes = useVisualizationStore((state) => state.nodes);
Expand Down Expand Up @@ -70,7 +72,7 @@ const Visualization = () => {

useEffect(() => {
visualizationService.redrawDiagram(handleDeleteStep, true).catch((e) => console.error(e));
}, [integrationJson, layout]);
}, [integrationJson, layout, useMultipleFlows]);

const nodeTypes = useMemo(() => ({ step: VisualizationStep }), []);
const edgeTypes = useMemo(
Expand Down
40 changes: 37 additions & 3 deletions src/services/stepsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
IIntegrationJsonStore,
INestedStepStore,
RFState,
useFlowsStore,
useIntegrationJsonStore,
useNestedStepsStore,
useVisualizationStore,
Expand Down Expand Up @@ -301,6 +302,8 @@ export class StepsService {
async handleAppendStep(currentStep: IStepProps, selectedStep: IStepProps) {
// fetch parameters and other details
fetchStepDetails(selectedStep.id).then((newStep) => {
const integrationId = currentStep.integrationId;

newStep.UUID = selectedStep.UUID;
const currentStepNested = this.getStepNested(currentStep.UUID);
if (currentStepNested) {
Expand All @@ -312,8 +315,13 @@ export class StepsService {
useIntegrationJsonStore
.getState()
.replaceBranchParentStep(newParentStep, currentStepNested.pathToParentStep);

useFlowsStore
.getState()
.insertStep(integrationId, newParentStep, { mode: 'replace', path: currentStepNested.pathToParentStep });
} else {
useIntegrationJsonStore.getState().appendStep(newStep);
useFlowsStore.getState().insertStep(integrationId, newStep, { mode: 'append' });
}
});
}
Expand All @@ -332,6 +340,8 @@ export class StepsService {
) {
// fetch parameters and other details
return fetchStepDetails(proposedStep.id).then((newStep) => {
const integrationId = node.step.integrationId;

const validation = ValidationService.canStepBeReplaced(node, newStep);
// Replace step
if (validation.isValid) {
Expand All @@ -341,10 +351,18 @@ export class StepsService {
useIntegrationJsonStore
.getState()
.replaceBranchParentStep(newStep, currentStepNested.pathToStep);

useFlowsStore
.getState()
.insertStep(integrationId, newStep, { mode: 'replace', path: currentStepNested.pathToStep });
}
} else {
const currentIdx = this.findStepIdxWithUUID(currentStep.UUID);
let currentIdx = this.findStepIdxWithUUID(currentStep.UUID);
useIntegrationJsonStore.getState().replaceStep(newStep, currentIdx);

const integration = this.getIntegration(integrationId);
currentIdx = this.findStepIdxWithUUID(currentStep.UUID, integration?.steps);
useFlowsStore.getState().insertStep(integrationId, newStep, { mode: 'replace', index: currentIdx });
}
}
return validation;
Expand All @@ -353,12 +371,14 @@ export class StepsService {

/**
* Handler for inserting a step, where `targetNode` is the
* right-hand node. Ex: source -> {insert step here} -> target
* right-hand node. Ex: source -> {insert step here} -> targetNode
* @param targetNode
* @param step
*/
async handleInsertStep(targetNode: IVizStepNode | undefined, step: IStepProps) {
return fetchStepDetails(step.id).then((newStep) => {
const integrationId = targetNode?.data.step.integrationId;

if (targetNode?.data.branchInfo) {
const currentStepNested = this.getStepNested(targetNode.data.step.UUID);

Expand All @@ -376,14 +396,26 @@ export class StepsService {
useIntegrationJsonStore
.getState()
.replaceBranchParentStep(newParentStep, currentStepNested.pathToParentStep);

useFlowsStore
.getState()
.insertStep(integrationId, newParentStep, { mode: 'replace', path: currentStepNested.pathToParentStep });
}
} else {
const currentIdx = this.findStepIdxWithUUID(targetNode?.data.step.UUID);
let currentIdx = this.findStepIdxWithUUID(targetNode?.data.step.UUID);
useIntegrationJsonStore.getState().insertStep(newStep, currentIdx);

const integration = this.getIntegration(integrationId);
currentIdx = this.findStepIdxWithUUID(targetNode?.data.step.UUID, integration?.steps);
useFlowsStore.getState().insertStep(integrationId, newStep, { mode: 'insert', index: currentIdx });
}
});
}

getIntegration(integrationId: string): IIntegration | undefined {
return useFlowsStore.getState().flows.find((integration) => integration.id === integrationId);
}

/**
* Prepends a selected step to the current step.
* @param currentStep
Expand Down Expand Up @@ -503,9 +535,11 @@ export class StepsService {
);
const newPath = pathToBranch?.concat('steps', '0');
useIntegrationJsonStore.getState().replaceBranchParentStep(step, newPath);
useFlowsStore.getState().insertStep(node.step.integrationId, step, { mode: 'replace', path: newPath });
}
} else {
useIntegrationJsonStore.getState().replaceStep(step);
useFlowsStore.getState().insertStep(node.step.integrationId, step, { mode: 'append' });
}
}
return validation;
Expand Down
8 changes: 4 additions & 4 deletions src/services/visualizationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ describe('visualizationService', () => {
expect(VisualizationService.buildEdges(nodes)).toHaveLength(1);

// let's test that it works for branching too
const stepNodes = VisualizationService.buildNodesFromSteps(branchSteps, 'RIGHT');
const stepNodes = VisualizationService.buildNodesFromSteps('Camel Route-1', branchSteps, 'RIGHT');

expect(VisualizationService.buildEdges(stepNodes)).toHaveLength(branchSteps.length - 1);
});
Expand Down Expand Up @@ -260,13 +260,13 @@ describe('visualizationService', () => {
});

it('buildNodesFromSteps(): should build visualization nodes from an array of steps', () => {
const stepNodes = VisualizationService.buildNodesFromSteps(steps, 'RIGHT');
const stepNodes = VisualizationService.buildNodesFromSteps('Camel Route-1', steps, 'RIGHT');
expect(stepNodes[0].data.step.UUID).toBeDefined();
expect(stepNodes[0].id).toContain(stepNodes[0].data.step.UUID);
});

it.skip('buildNodesFromSteps(): should build visualization nodes from an array of steps with branches', () => {
const stepNodes = VisualizationService.buildNodesFromSteps(branchSteps, 'RIGHT');
const stepNodes = VisualizationService.buildNodesFromSteps('Camel Route-1', branchSteps, 'RIGHT');
expect(stepNodes[0].data.step.UUID).toBeDefined();
expect(stepNodes).toHaveLength(branchSteps.length);
});
Expand Down Expand Up @@ -480,7 +480,7 @@ describe('visualizationService', () => {

it('insertAddStepPlaceholder(): should add an ADD STEP placeholder to the beginning of the array', () => {
const nodes: IVizStepNode[] = [];
VisualizationService.insertAddStepPlaceholder(nodes, '', 'START', { nextStepUuid: '' });
VisualizationService.insertAddStepPlaceholder('Camel Route-1', nodes, '', 'START', { nextStepUuid: '' });
expect(nodes).toHaveLength(1);
});

Expand Down
41 changes: 33 additions & 8 deletions src/services/visualizationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
IIntegrationJsonStore,
RFState,
useIntegrationJsonStore,
useFlowsStore,
useVisualizationStore,
useSettingsStore,
} from '@kaoto/store';
import {
IStepProps,
Expand Down Expand Up @@ -279,12 +281,32 @@ export class VisualizationService {
* @param handleDeleteStep
*/
buildNodesAndEdges(handleDeleteStep: (uuid: string) => void) {
const steps = useIntegrationJsonStore.getState().integrationJson.steps;
const { id: singleFlowId, steps: singleFlowSteps } = useIntegrationJsonStore.getState().integrationJson;
const layout = useVisualizationStore.getState().layout;

// build all nodes
const stepNodes = VisualizationService.buildNodesFromSteps(steps, layout, {
handleDeleteStep,
});
let stepNodes: IVizStepNode[] = [];

/**
* TODO: The following check is meant to be a temporary one
* while the Multiple Flows support is completed.
*
* This will be removed once all the existing functionality
* it's ported to the new "multi-flow" approach
*/
if (!useSettingsStore.getState().settings.useMultipleFlows) {
stepNodes = VisualizationService.buildNodesFromSteps(singleFlowId, singleFlowSteps, layout, { handleDeleteStep });
} else {
const integrations = useFlowsStore.getState().flows;
stepNodes = integrations.reduce((acc, currentIntegration) => acc.concat(
VisualizationService.buildNodesFromSteps(
currentIntegration.id,
currentIntegration.steps,
layout,
{ handleDeleteStep },
),
), [] as IVizStepNode[]);
}

// build edges only for main nodes
const filteredNodes = stepNodes.filter((node) => !node.data.branchInfo);
Expand All @@ -304,28 +326,29 @@ export class VisualizationService {
* Data is stored in the `nodes` hook.
*/
static buildNodesFromSteps(
integrationId: string,
steps: IStepProps[],
layout: string,
props?: { [prop: string]: any },
branchInfo?: IVizStepNodeDataBranch
): IVizStepNode[] {
let stepNodes: IVizStepNode[] = [];
let id = 0;
let getId = (uuid: string) => `node_${id++}-${uuid}-${getRandomArbitraryNumber()}`;
let getId = (UUID: string) => `node_${id++}-${UUID}-${getRandomArbitraryNumber()}`;

// if no steps or first step isn't START, create a dummy placeholder step
if (
(steps.length === 0 && !branchInfo) ||
(!StepsService.isFirstStepStart(steps) && !branchInfo)
) {
VisualizationService.insertAddStepPlaceholder(stepNodes, getId(''), 'START', {
VisualizationService.insertAddStepPlaceholder(integrationId, stepNodes, getId(''), 'START', {
nextStepUuid: steps[0]?.UUID,
});
}

// build EIP placeholder
if (branchInfo && steps.length === 0 && !StepsService.isFirstStepStart(steps)) {
VisualizationService.insertAddStepPlaceholder(stepNodes, getId(''), 'MIDDLE', {
VisualizationService.insertAddStepPlaceholder(integrationId, stepNodes, getId(''), 'MIDDLE', {
branchInfo,
nextStepUuid: steps[0]?.UUID,
});
Expand Down Expand Up @@ -360,7 +383,7 @@ export class VisualizationService {
if (step.branches && step.branches.length > 0 && step.maxBranches !== 0) {
step.branches.forEach((branch) => {
stepNodes = stepNodes.concat(
VisualizationService.buildNodesFromSteps(branch.steps, layout, props, {
VisualizationService.buildNodesFromSteps(integrationId, branch.steps, layout, props, {
branchIdentifier: branch.condition !== null ? branch.condition : branch.identifier,

// rootStepUuid is the parent for a first branch step,
Expand Down Expand Up @@ -487,6 +510,7 @@ export class VisualizationService {
}

static insertAddStepPlaceholder(
integrationId: string,
stepNodes: IVizStepNode[],
id: string,
type: string,
Expand All @@ -499,6 +523,7 @@ export class VisualizationService {
name: '',
type: type,
UUID: `placeholder-${getRandomArbitraryNumber()}`,
integrationId,
},
isPlaceholder: true,
...props,
Expand Down
Loading

0 comments on commit 8c916e7

Please sign in to comment.