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

Commit

Permalink
fix(refactor): Clearer separation of concerns for visualization (#1183)
Browse files Browse the repository at this point in the history
* From now on the components are expected to only initialize the stores and forward them directly to the services. In This way the store details, which often touches the business logic, are isolated from components and encapsulated into services
  * In this changeset, only Visualization.tsx and VisualizationStep.tsx are addressed. More will be expected later
* Made services class and let them hold references to the stores, mainly for reducing the arguments on business logic methods invoked from components
* Splitted StepsService from VisualizationService, trying to isolate the logical model handling (e.g. IStepProps) from pure visualization handling (React Flow nodes and edges) as much as possible
  • Loading branch information
igarashitm committed Feb 9, 2023
1 parent 01e1e16 commit b394613
Show file tree
Hide file tree
Showing 17 changed files with 1,339 additions and 1,217 deletions.
8 changes: 4 additions & 4 deletions src/components/KaotoToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
DeploymentsModal,
SettingsModal,
} from '@kaoto/components';
import { isNameValidCheck } from '@kaoto/services';
import { ValidationService } from '@kaoto/services';
import {
useDeploymentStore,
useIntegrationJsonStore,
Expand Down Expand Up @@ -267,7 +267,7 @@ export const KaotoToolbar = ({ toggleCatalog, toggleCodeEditor, hideLeftPanel }:
onChange={(val) => {
// save to local state while typing
setLocalName(val);
if (isNameValidCheck(val)) {
if (ValidationService.isNameValidCheck(val)) {
setNameValidation('success');
} else {
setNameValidation('error');
Expand All @@ -280,7 +280,7 @@ export const KaotoToolbar = ({ toggleCatalog, toggleCodeEditor, hideLeftPanel }:
onKeyUp={(e) => {
// allow users to save by pressing enter
if (e.key !== 'Enter') return;
if (isNameValidCheck(localName)) {
if (ValidationService.isNameValidCheck(localName)) {
setIsEditingName(false);
// only issue change if different from current settings
if (localName !== settings.name) {
Expand All @@ -293,7 +293,7 @@ export const KaotoToolbar = ({ toggleCatalog, toggleCodeEditor, hideLeftPanel }:
variant="plain"
aria-label="save button for editing integration name"
onClick={() => {
if (isNameValidCheck(localName)) {
if (ValidationService.isNameValidCheck(localName)) {
setIsEditingName(false);
// only issue change if different from current settings
if (localName !== settings.name) {
Expand Down
22 changes: 12 additions & 10 deletions src/components/PlusButtonEdge.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './PlusButtonEdge.css';
import { MiniCatalog } from '@kaoto/components';
import { findStepIdxWithUUID, insertableStepTypes, insertStep } from '@kaoto/services';
import { useIntegrationJsonStore, useNestedStepsStore } from '@kaoto/store';
import { StepsService, ValidationService } from '@kaoto/services';
import {useIntegrationJsonStore, useNestedStepsStore, useVisualizationStore} from '@kaoto/store';
import { IStepProps, IVizStepNode } from '@kaoto/types';
import { Popover } from '@patternfly/react-core';
import { PlusIcon } from '@patternfly/react-icons';
Expand Down Expand Up @@ -40,9 +40,11 @@ const PlusButtonEdge = ({
const nodeIds = id.substring(2).split('>');
const sourceNode: IVizStepNode | undefined = useReactFlow().getNode(nodeIds[0]);
const targetNode: IVizStepNode | undefined = useReactFlow().getNode(nodeIds[1]);
const currentIdx = findStepIdxWithUUID(targetNode?.data.step.UUID);
const { integrationJson } = useIntegrationJsonStore();
const { nestedSteps } = useNestedStepsStore();
const integrationJsonStore = useIntegrationJsonStore();
const nestedStepsStore = useNestedStepsStore();
const visualizationStore = useVisualizationStore();
const stepsService = new StepsService(integrationJsonStore, nestedStepsStore, visualizationStore);
const currentIdx = stepsService.findStepIdxWithUUID(targetNode?.data.step.UUID);

const [edgePath, edgeCenterX, edgeCenterY] = getBezierPath({
sourceX,
Expand All @@ -55,18 +57,18 @@ const PlusButtonEdge = ({

const onMiniCatalogClickInsert = (selectedStep: IStepProps) => {
if (targetNode?.data.branchInfo) {
const rootStepIdx = findStepIdxWithUUID(targetNode?.data.branchInfo.parentUuid);
const currentStepNested = nestedSteps.map((ns) => ns.stepUuid === targetNode?.data.step.UUID);
const rootStepIdx = stepsService.findStepIdxWithUUID(targetNode?.data.branchInfo.parentUuid);
const currentStepNested = nestedStepsStore.nestedSteps.map((ns) => ns.stepUuid === targetNode?.data.step.UUID);

if (currentStepNested) {
// 1. make a copy of the steps, get the root step
const newStep = integrationJson.steps.slice()[rootStepIdx];
const newStep = integrationJsonStore.integrationJson.steps.slice()[rootStepIdx];
// 2. find the correct branch, insert new step there
newStep.branches?.forEach((b, bIdx) => {
b.steps.map((bs, bsIdx) => {
if (bs.UUID === targetNode?.data.step.UUID) {
// 3. assign the new steps back to the branch
newStep.branches![bIdx].steps = insertStep(b.steps, bsIdx, selectedStep);
newStep.branches![bIdx].steps = StepsService.insertStep(b.steps, bsIdx, selectedStep);
}
});
});
Expand Down Expand Up @@ -103,7 +105,7 @@ const PlusButtonEdge = ({
<MiniCatalog
handleSelectStep={onMiniCatalogClickInsert}
queryParams={{
type: insertableStepTypes(sourceNode?.data.step, targetNode?.data.step),
type: ValidationService.insertableStepTypes(sourceNode?.data.step, targetNode?.data.step),
}}
/>
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fetchCapabilities, fetchCompatibleDSLs, fetchIntegrationSourceCode } from '@kaoto/api';
import { isNameValidCheck } from '@kaoto/services';
import { ValidationService } from '@kaoto/services';
import { useIntegrationJsonStore, useIntegrationSourceStore, useSettingsStore } from '@kaoto/store';
import { ICapabilities, ISettings } from '@kaoto/types';
import { usePrevious } from '@kaoto/utils';
Expand Down Expand Up @@ -87,7 +87,7 @@ export const SettingsModal = ({ handleCloseModal, isModalOpen }: ISettingsModal)
const onChangeName = (newName: string) => {
setLocalSettings({ ...localSettings, name: newName });

if (isNameValidCheck(newName)) {
if (ValidationService.isNameValidCheck(newName)) {
setNameValidation('success');
} else {
setNameValidation('error');
Expand All @@ -110,7 +110,7 @@ export const SettingsModal = ({ handleCloseModal, isModalOpen }: ISettingsModal)
const onChangeNamespace = (newNamespace: string) => {
setLocalSettings({ ...localSettings, namespace: newNamespace });

if (isNameValidCheck(newNamespace)) {
if (ValidationService.isNameValidCheck(newNamespace)) {
setNamespaceValidation('success');
} else {
setNamespaceValidation('error');
Expand Down
154 changes: 27 additions & 127 deletions src/components/Visualization.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import './Visualization.css';
import { fetchViews } from '@kaoto/api';
import {
KaotoDrawer,
PlusButtonEdge,
Expand All @@ -8,17 +7,9 @@ import {
VisualizationStep,
VisualizationStepViews,
} from '@kaoto/components';
import {
buildBranchSpecialEdges,
buildEdges,
buildNodesFromSteps,
findStepIdxWithUUID,
flattenSteps,
getLayoutedElements,
filterStepWithBranches,
} from '@kaoto/services';
import { StepsService, VisualizationService } from '@kaoto/services';
import { useIntegrationJsonStore, useNestedStepsStore, useVisualizationStore } from '@kaoto/store';
import { IStepProps, IViewData, IVizStepPropsEdge, IVizStepNode } from '@kaoto/types';
import { IStepProps, IViewData, IVizStepNode } from '@kaoto/types';
import { useEffect, useMemo, useRef, useState } from 'react';
import ReactFlow, { Background, Viewport } from 'reactflow';

Expand Down Expand Up @@ -47,31 +38,17 @@ const Visualization = ({ toggleCatalog }: IVisualization) => {
type: '',
UUID: '',
});
const {
deleteBranchStep,
deleteStep,
integrationJson,
replaceBranchStep,
replaceStep,
setViews,
} = useIntegrationJsonStore();
const { nestedSteps } = useNestedStepsStore();
const layout = useVisualizationStore((state) => state.layout);
const previousIntegrationJson = useRef(integrationJson);
const previousLayout = useRef(layout);
const { nodes, setNodes, onNodesChange, edges, setEdges, onEdgesChange, deleteNode } =
useVisualizationStore();
const integrationJsonStore = useIntegrationJsonStore();
const nestedStepsStore = useNestedStepsStore();
const visualizationStore = useVisualizationStore();
const previousLayout = useRef(visualizationStore.layout);
const previousIntegrationJson = useRef(integrationJsonStore.integrationJson);
const visualizationService = new VisualizationService(integrationJsonStore, visualizationStore);
const stepsService = new StepsService(integrationJsonStore, nestedStepsStore, visualizationStore);

// initial loading of visualization steps
useEffect(() => {
const { stepNodes, stepEdges } = buildNodesAndEdges(integrationJson.steps);

getLayoutedElements(stepNodes, stepEdges, layout)
.then((res) => {
const { layoutedNodes, layoutedEdges } = res;
setNodes(layoutedNodes);
setEdges(layoutedEdges);
})
visualizationService.redrawDiagram(handleDeleteStep, true)
.catch((e) => console.error(e));
}, []);

Expand All @@ -80,37 +57,23 @@ const Visualization = ({ toggleCatalog }: IVisualization) => {
* which causes Visualization nodes to all be redrawn
*/
useEffect(() => {
if (previousIntegrationJson.current === integrationJson) return;

fetchViews(integrationJson.steps).then((views) => {
setViews(views);
});
if (previousIntegrationJson.current === integrationJsonStore.integrationJson) return;

const { stepNodes, stepEdges } = buildNodesAndEdges(integrationJson.steps);
getLayoutedElements(stepNodes, stepEdges, layout)
.then((res) => {
const { layoutedNodes, layoutedEdges } = res;
setNodes(layoutedNodes);
setEdges(layoutedEdges);
})
stepsService.updateViews();
visualizationService.redrawDiagram(handleDeleteStep, true)
.catch((e) => console.error(e));

previousIntegrationJson.current = integrationJson;
}, [integrationJson]);
previousIntegrationJson.current = integrationJsonStore.integrationJson;
}, [integrationJsonStore.integrationJson]);

useEffect(() => {
if (previousLayout.current === layout) return;
if (previousLayout.current === visualizationStore.layout) return;

getLayoutedElements(nodes, edges, layout)
.then((res) => {
const { layoutedNodes, layoutedEdges } = res;
setNodes(layoutedNodes);
setEdges(layoutedEdges);
})
visualizationService.redrawDiagram(handleDeleteStep, false)
.catch((e) => console.error(e));

previousLayout.current = layout;
}, [layout]);
previousLayout.current = visualizationStore.layout;
}, [visualizationStore.layout]);

const nodeTypes = useMemo(() => ({ step: VisualizationStep }), []);
const edgeTypes = useMemo(
Expand All @@ -120,52 +83,13 @@ const Visualization = ({ toggleCatalog }: IVisualization) => {
[]
);

function buildNodesAndEdges(steps: IStepProps[]) {
// build all nodes
const stepNodes = buildNodesFromSteps(steps, layout, {
handleDeleteStep,
});

// build edges only for main nodes
const filteredNodes = stepNodes.filter((node) => !node.data.branchInfo?.branchStep);
let stepEdges: IVizStepPropsEdge[] = buildEdges(filteredNodes);

// build edges for branch nodes
const branchSpecialEdges: IVizStepPropsEdge[] = buildBranchSpecialEdges(stepNodes);

stepEdges = stepEdges.concat(...branchSpecialEdges);

return { stepNodes, stepEdges };
}

const handleDeleteStep = (UUID?: string) => {
if (!UUID) return;

setSelectedStep({ maxBranches: 0, minBranches: 0, name: '', type: '', UUID: '' });
if (isPanelExpanded) setIsPanelExpanded(false);

// check if the step being modified is nested
const currentStepNested = nestedSteps.find((ns) => ns.stepUuid === UUID);
if (currentStepNested) {
const parentStepIdx = findStepIdxWithUUID(
currentStepNested.originStepUuid,
integrationJson.steps
);

// update the original parent step, without the child step
const updatedParentStep = filterStepWithBranches(
integrationJson.steps[parentStepIdx],
(step: { UUID: string }) => step.UUID !== UUID
);

deleteBranchStep(updatedParentStep, parentStepIdx);
} else {
// `deleteStep` requires the index to be from `integrationJson`, not `nodes`
const stepsIndex = findStepIdxWithUUID(UUID, integrationJson.steps);

deleteNode(stepsIndex);
deleteStep(stepsIndex);
}
stepsService.deleteStep(UUID);
};

const onClosePanelClick = () => {
Expand Down Expand Up @@ -200,10 +124,8 @@ const Visualization = ({ toggleCatalog }: IVisualization) => {
return;
}

const flatSteps = flattenSteps(integrationJson.steps);
const stepIdx = flatSteps.map((s: IStepProps) => s.UUID).indexOf(node.data.step.UUID);

if (stepIdx !== -1) setSelectedStep(flatSteps[stepIdx]);
const step = stepsService.findStepWithUUID(node.data.step.UUID);
if (step) setSelectedStep(step);

if (!isPanelExpanded) setIsPanelExpanded(true);
};
Expand All @@ -217,29 +139,7 @@ const Visualization = ({ toggleCatalog }: IVisualization) => {
* @param newValues
*/
const saveConfig = (newValues: { [s: string]: unknown } | ArrayLike<unknown>) => {
let newStep: IStepProps = selectedStep;
const newStepParameters = newStep.parameters?.slice();

if (newStepParameters && newStepParameters.length > 0) {
Object.entries(newValues).map(([key, value]) => {
const paramIndex = newStepParameters.findIndex((p) => p.id === key);
newStepParameters[paramIndex!].value = value;
});

// check if the step being modified is nested
if (nestedSteps.some((ns) => ns.stepUuid === newStep.UUID)) {
// use its path to replace only this part of the original step
const currentStepNested = nestedSteps.find((ns) => ns.stepUuid === newStep.UUID);
if (currentStepNested) {
replaceBranchStep(newStep, currentStepNested.pathToStep);
}
} else {
const oldStepIdx = findStepIdxWithUUID(newStep.UUID, integrationJson.steps);
replaceStep(newStep, oldStepIdx);
}
} else {
return;
}
stepsService.updateStepParameters(selectedStep, newValues);
};

return (
Expand Down Expand Up @@ -271,15 +171,15 @@ const Visualization = ({ toggleCatalog }: IVisualization) => {
}}
>
<ReactFlow
nodes={nodes}
edges={edges}
nodes={visualizationStore.nodes}
edges={visualizationStore.edges}
defaultViewport={defaultViewport}
edgeTypes={edgeTypes}
nodeTypes={nodeTypes}
onDragOver={onDragOver}
onNodeClick={onNodeClick}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodesChange={visualizationStore.onNodesChange}
onEdgesChange={visualizationStore.onEdgesChange}
onLoad={onLoad}
snapToGrid={true}
snapGrid={[15, 15]}
Expand Down
Loading

0 comments on commit b394613

Please sign in to comment.