From 3ec247dee3ecf695b9e1ee6251f4e5fbce88db93 Mon Sep 17 00:00:00 2001 From: "Ricardo M." Date: Mon, 14 Oct 2024 21:23:04 +0000 Subject: [PATCH] chore(Canvas): Extract ItemEnableAllSteps --- .../Custom/ContextMenu/ItemDisableStep.tsx | 65 +++---------- .../ContextMenu/ItemEnableAllSteps.test.tsx | 95 +++++++++++++++++++ .../Custom/ContextMenu/ItemEnableAllSteps.tsx | 37 ++++++++ .../ContextMenu/NodeContextMenu.test.tsx | 75 +++++++++++++-- .../Custom/ContextMenu/NodeContextMenu.tsx | 3 + 5 files changed, 213 insertions(+), 62 deletions(-) create mode 100644 packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.test.tsx create mode 100644 packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.tsx diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDisableStep.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDisableStep.tsx index 20f49cdd4..d3a20b93b 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDisableStep.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDisableStep.tsx @@ -1,6 +1,6 @@ -import { BanIcon, CheckIcon, PowerOffIcon } from '@patternfly/react-icons'; -import { ContextMenuItem, useVisualizationController } from '@patternfly/react-topology'; -import { FunctionComponent, PropsWithChildren, useCallback, useContext, useMemo } from 'react'; +import { BanIcon, CheckIcon } from '@patternfly/react-icons'; +import { ContextMenuItem } from '@patternfly/react-topology'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react'; import { IDataTestID } from '../../../../models'; import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity'; import { EntitiesContext } from '../../../../providers/entities.provider'; @@ -10,32 +10,10 @@ interface ItemDisableStepProps extends PropsWithChildren { vizNode: IVisualizationNode; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function findAllDisabledNodes(node: any): IVisualizationNode[] | null { - const allDisabledNodes = []; - if (node?.data?.vizNode?.getComponentSchema()?.definition?.disabled) { - allDisabledNodes.push(node.data.vizNode); - } - if (node?.children) { - for (const child of node.children) { - const result = findAllDisabledNodes(child); - allDisabledNodes.push(...(result || [])); - } - } - return allDisabledNodes; -} - export const ItemDisableStep: FunctionComponent = (props) => { const entitiesContext = useContext(EntitiesContext); - const controller = useVisualizationController(); - const isDisabled = !!props.vizNode.getComponentSchema()?.definition?.disabled; - const allDisabledNodes = useMemo(() => { - return findAllDisabledNodes(controller?.getGraph()) || []; - }, [controller]); - const isMultiDisabled = allDisabledNodes.length > 1; - const onToggleDisableNode = useCallback(() => { const newModel = props.vizNode.getComponentSchema()?.definition || {}; setValue(newModel, 'disabled', !isDisabled); @@ -44,34 +22,17 @@ export const ItemDisableStep: FunctionComponent = (props) entitiesContext?.updateEntitiesFromCamelResource(); }, [entitiesContext, isDisabled, props.vizNode]); - const onEnableAllNodes = useCallback(() => { - allDisabledNodes.forEach((node) => { - const newModel = node.getComponentSchema()?.definition || {}; - setValue(newModel, 'disabled', false); - node.updateModel(newModel); - }); - - entitiesContext?.updateEntitiesFromCamelResource(); - }, [allDisabledNodes, entitiesContext]); - return ( - <> - - {isDisabled ? ( - <> - Enable - - ) : ( - <> - Disable - - )} - - {isDisabled && isMultiDisabled && ( - - Enable All - + + {isDisabled ? ( + <> + Enable + + ) : ( + <> + Disable + )} - + ); }; diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.test.tsx new file mode 100644 index 000000000..db58f5b07 --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.test.tsx @@ -0,0 +1,95 @@ +import catalogLibrary from '@kaoto/camel-catalog/index.json'; +import { CatalogLibrary } from '@kaoto/camel-catalog/types'; +import { Model, VisualizationProvider } from '@patternfly/react-topology'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { CamelCatalogService, CatalogKind } from '../../../../models'; +import { CamelRouteResource } from '../../../../models/camel/camel-route-resource'; +import { camelRouteJson, camelRouteWithDisabledSteps } from '../../../../stubs/camel-route'; +import { getFirstCatalogMap } from '../../../../stubs/test-load-catalog'; +import { TestProvidersWrapper } from '../../../../stubs/TestProvidersWrapper'; +import { getVisualizationNodesFromGraph } from '../../../../utils'; +import { ControllerService } from '../../Canvas/controller.service'; +import { FlowService } from '../../Canvas/flow.service'; +import { ItemEnableAllSteps } from './ItemEnableAllSteps'; + +describe('ItemEnableAllSteps', () => { + beforeAll(async () => { + const catalogsMap = await getFirstCatalogMap(catalogLibrary as CatalogLibrary); + CamelCatalogService.setCatalogKey(CatalogKind.Pattern, catalogsMap.patternCatalogMap); + CamelCatalogService.setCatalogKey(CatalogKind.Component, catalogsMap.componentCatalogMap); + }); + + it('should NOT render an ItemEnableAllSteps if there are not at least 2 or more disabled steps', () => { + const camelResource = new CamelRouteResource(camelRouteJson); + const visualEntity = camelResource.getVisualEntities()[0]; + const { nodes, edges } = FlowService.getFlowDiagram(visualEntity.toVizNode()); + + const model: Model = { + nodes, + edges, + graph: { + id: 'g1', + type: 'graph', + }, + }; + const visualizationController = ControllerService.createController(); + visualizationController.fromModel(model); + + const { Provider } = TestProvidersWrapper({ camelResource }); + const wrapper = render( + + + + + , + ); + + const item = wrapper.queryByTestId('context-menu-item-enable-all'); + + expect(item).not.toBeInTheDocument(); + }); + + it('should call updateModel and updateEntitiesFromCamelResource on click', async () => { + const camelResource = new CamelRouteResource(camelRouteWithDisabledSteps); + const visualEntity = camelResource.getVisualEntities()[0]; + const { nodes, edges } = FlowService.getFlowDiagram(visualEntity.toVizNode()); + + const model: Model = { + nodes, + edges, + graph: { + id: 'g1', + type: 'graph', + }, + }; + const visualizationController = ControllerService.createController(); + visualizationController.fromModel(model); + const disabledNodes = getVisualizationNodesFromGraph(visualizationController.getGraph(), (node) => { + return node.getComponentSchema()?.definition?.disabled; + }); + + const { Provider, updateEntitiesFromCamelResourceSpy } = TestProvidersWrapper({ camelResource }); + const wrapper = render( + + + + + , + ); + + act(() => { + const item = wrapper.getByText('Enable All'); + fireEvent.click(item); + }); + + await waitFor(async () => { + disabledNodes.forEach((node) => { + expect(node.getComponentSchema()?.definition?.disabled).toBe(false); + }); + }); + + await waitFor(async () => { + expect(updateEntitiesFromCamelResourceSpy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.tsx new file mode 100644 index 000000000..c3bcad07e --- /dev/null +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemEnableAllSteps.tsx @@ -0,0 +1,37 @@ +import { PowerOffIcon } from '@patternfly/react-icons'; +import { ContextMenuItem, useVisualizationController } from '@patternfly/react-topology'; +import { FunctionComponent, PropsWithChildren, useCallback, useContext, useMemo } from 'react'; +import { IDataTestID } from '../../../../models'; +import { EntitiesContext } from '../../../../providers/entities.provider'; +import { getVisualizationNodesFromGraph, setValue } from '../../../../utils'; + +export const ItemEnableAllSteps: FunctionComponent> = (props) => { + const entitiesContext = useContext(EntitiesContext); + const controller = useVisualizationController(); + const disabledNodes = useMemo(() => { + return getVisualizationNodesFromGraph(controller.getGraph(), (node) => { + return node.getComponentSchema()?.definition?.disabled; + }); + }, [controller]); + const isMultiDisabled = disabledNodes.length > 1; + + const onClick = useCallback(() => { + disabledNodes.forEach((node) => { + const newModel = node.getComponentSchema()?.definition || {}; + setValue(newModel, 'disabled', false); + node.updateModel(newModel); + }); + + entitiesContext?.updateEntitiesFromCamelResource(); + }, [disabledNodes, entitiesContext]); + + if (!isMultiDisabled) { + return null; + } + + return ( + + Enable All + + ); +}; diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.test.tsx index 9d2dd1bf3..a1a979037 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.test.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.test.tsx @@ -1,14 +1,34 @@ -import { ElementModel, GraphElement } from '@patternfly/react-topology'; +import catalogLibrary from '@kaoto/camel-catalog/index.json'; +import { CatalogLibrary } from '@kaoto/camel-catalog/types'; +import { ElementModel, GraphElement, Model, VisualizationProvider } from '@patternfly/react-topology'; import { render } from '@testing-library/react'; +import { FunctionComponent, PropsWithChildren } from 'react'; +import { + CamelCatalogService, + CatalogKind, + createVisualizationNode, + IVisualizationNode, + NodeInteraction, +} from '../../../../models'; +import { CamelRouteResource } from '../../../../models/camel'; +import { camelRouteWithDisabledSteps, TestProvidersWrapper } from '../../../../stubs'; +import { getFirstCatalogMap } from '../../../../stubs/test-load-catalog'; import { CanvasNode } from '../../Canvas'; +import { ControllerService } from '../../Canvas/controller.service'; +import { FlowService } from '../../Canvas/flow.service'; import { NodeContextMenu } from './NodeContextMenu'; -import { createVisualizationNode, IVisualizationNode, NodeInteraction } from '../../../../models'; describe('NodeContextMenu', () => { let element: GraphElement; let vizNode: IVisualizationNode | undefined; let nodeInteractions: NodeInteraction; + beforeAll(async () => { + const catalogsMap = await getFirstCatalogMap(catalogLibrary as CatalogLibrary); + CamelCatalogService.setCatalogKey(CatalogKind.Pattern, catalogsMap.patternCatalogMap); + CamelCatalogService.setCatalogKey(CatalogKind.Component, catalogsMap.componentCatalogMap); + }); + beforeEach(() => { nodeInteractions = { canHavePreviousStep: false, @@ -29,6 +49,11 @@ describe('NodeContextMenu', () => { } as unknown as GraphElement; }); + const TestWrapper: FunctionComponent = ({ children }) => { + const visualizationController = ControllerService.createController(); + return {children}; + }; + it('should render an empty component when there is no vizNode', () => { vizNode = undefined; const { container } = render(); @@ -38,7 +63,7 @@ describe('NodeContextMenu', () => { it('should render a PrependStep item if canHavePreviousStep is true', () => { nodeInteractions.canHavePreviousStep = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-prepend'); @@ -47,7 +72,7 @@ describe('NodeContextMenu', () => { it('should render an AppendStep item if canHaveNextStep is true', () => { nodeInteractions.canHaveNextStep = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-append'); @@ -56,7 +81,7 @@ describe('NodeContextMenu', () => { it('should render an InsertStep item if canHaveChildren is true', () => { nodeInteractions.canHaveChildren = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-insert'); @@ -65,7 +90,7 @@ describe('NodeContextMenu', () => { it('should render an InsertSpecialStep item if canHaveSpecialChildren is true', () => { nodeInteractions.canHaveSpecialChildren = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-insert-special'); @@ -74,16 +99,46 @@ describe('NodeContextMenu', () => { it('should render an ItemDisableStep item if canBeDisabled is true', () => { nodeInteractions.canBeDisabled = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-disable'); expect(item).toBeInTheDocument(); }); + it('should render an ItemEnableAllSteps', () => { + const camelResource = new CamelRouteResource(camelRouteWithDisabledSteps); + const visualEntity = camelResource.getVisualEntities()[0]; + const { nodes, edges } = FlowService.getFlowDiagram(visualEntity.toVizNode()); + + const model: Model = { + nodes, + edges, + graph: { + id: 'g1', + type: 'graph', + }, + }; + const visualizationController = ControllerService.createController(); + visualizationController.fromModel(model); + + const { Provider } = TestProvidersWrapper({ camelResource }); + const wrapper = render( + + + + + , + ); + + const item = wrapper.queryByTestId('context-menu-item-enable-all'); + + expect(item).toBeInTheDocument(); + }); + it('should render an ItemReplaceStep item if canReplaceStep is true', () => { nodeInteractions.canReplaceStep = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-replace'); @@ -92,7 +147,7 @@ describe('NodeContextMenu', () => { it('should render an ItemDeleteStep item if canRemoveStep is true', () => { nodeInteractions.canRemoveStep = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-delete'); @@ -101,7 +156,7 @@ describe('NodeContextMenu', () => { it('should render an ItemDeleteGroup item if canRemoveFlow is true', () => { nodeInteractions.canRemoveFlow = true; - const wrapper = render(); + const wrapper = render(, { wrapper: TestWrapper }); const item = wrapper.getByTestId('context-menu-item-container-remove'); diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.tsx index 25d27b617..d8bdf82e7 100644 --- a/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.tsx +++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/NodeContextMenu.tsx @@ -7,6 +7,7 @@ import { ItemAddStep } from './ItemAddStep'; import { ItemDeleteGroup } from './ItemDeleteGroup'; import { ItemDeleteStep } from './ItemDeleteStep'; import { ItemDisableStep } from './ItemDisableStep'; +import { ItemEnableAllSteps } from './ItemEnableAllSteps'; import { ItemInsertStep } from './ItemInsertStep'; import { ItemReplaceStep } from './ItemReplaceStep'; @@ -80,6 +81,8 @@ export const NodeContextMenuFn = (element: GraphElement, ); } + items.push(); + if (nodeInteractions.canReplaceStep) { items.push(