Skip to content

Commit

Permalink
chore(Canvas): Extract ItemEnableAllSteps
Browse files Browse the repository at this point in the history
  • Loading branch information
lordrip committed Oct 15, 2024
1 parent d985f82 commit 3ec247d
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -10,32 +10,10 @@ interface ItemDisableStepProps extends PropsWithChildren<IDataTestID> {
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<ItemDisableStepProps> = (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);
Expand All @@ -44,34 +22,17 @@ export const ItemDisableStep: FunctionComponent<ItemDisableStepProps> = (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 (
<>
<ContextMenuItem onClick={onToggleDisableNode} data-testid={props['data-testid']}>
{isDisabled ? (
<>
<CheckIcon /> Enable
</>
) : (
<>
<BanIcon /> Disable
</>
)}
</ContextMenuItem>
{isDisabled && isMultiDisabled && (
<ContextMenuItem onClick={onEnableAllNodes}>
<PowerOffIcon /> Enable All
</ContextMenuItem>
<ContextMenuItem onClick={onToggleDisableNode} data-testid={props['data-testid']}>
{isDisabled ? (
<>
<CheckIcon /> Enable
</>
) : (
<>
<BanIcon /> Disable
</>
)}
</>
</ContextMenuItem>
);
};
Original file line number Diff line number Diff line change
@@ -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(
<Provider>
<VisualizationProvider controller={visualizationController}>
<ItemEnableAllSteps data-testid="context-menu-item-enable-all" />
</VisualizationProvider>
</Provider>,
);

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(
<Provider>
<VisualizationProvider controller={visualizationController}>
<ItemEnableAllSteps />
</VisualizationProvider>
</Provider>,
);

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);
});
});
});
Original file line number Diff line number Diff line change
@@ -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<PropsWithChildren<IDataTestID>> = (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 (
<ContextMenuItem onClick={onClick} data-testid={props['data-testid']}>
<PowerOffIcon /> Enable All
</ContextMenuItem>
);
};
Original file line number Diff line number Diff line change
@@ -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<ElementModel, CanvasNode['data']>;
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,
Expand All @@ -29,6 +49,11 @@ describe('NodeContextMenu', () => {
} as unknown as GraphElement<ElementModel, CanvasNode['data']>;
});

const TestWrapper: FunctionComponent<PropsWithChildren> = ({ children }) => {
const visualizationController = ControllerService.createController();
return <VisualizationProvider controller={visualizationController}>{children}</VisualizationProvider>;
};

it('should render an empty component when there is no vizNode', () => {
vizNode = undefined;
const { container } = render(<NodeContextMenu element={element} />);
Expand All @@ -38,7 +63,7 @@ describe('NodeContextMenu', () => {

it('should render a PrependStep item if canHavePreviousStep is true', () => {
nodeInteractions.canHavePreviousStep = true;
const wrapper = render(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { wrapper: TestWrapper });

const item = wrapper.getByTestId('context-menu-item-prepend');

Expand All @@ -47,7 +72,7 @@ describe('NodeContextMenu', () => {

it('should render an AppendStep item if canHaveNextStep is true', () => {
nodeInteractions.canHaveNextStep = true;
const wrapper = render(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { wrapper: TestWrapper });

const item = wrapper.getByTestId('context-menu-item-append');

Expand All @@ -56,7 +81,7 @@ describe('NodeContextMenu', () => {

it('should render an InsertStep item if canHaveChildren is true', () => {
nodeInteractions.canHaveChildren = true;
const wrapper = render(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { wrapper: TestWrapper });

const item = wrapper.getByTestId('context-menu-item-insert');

Expand All @@ -65,7 +90,7 @@ describe('NodeContextMenu', () => {

it('should render an InsertSpecialStep item if canHaveSpecialChildren is true', () => {
nodeInteractions.canHaveSpecialChildren = true;
const wrapper = render(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { wrapper: TestWrapper });

const item = wrapper.getByTestId('context-menu-item-insert-special');

Expand All @@ -74,16 +99,46 @@ describe('NodeContextMenu', () => {

it('should render an ItemDisableStep item if canBeDisabled is true', () => {
nodeInteractions.canBeDisabled = true;
const wrapper = render(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { 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(
<Provider>
<VisualizationProvider controller={visualizationController}>
<NodeContextMenu element={element} />
</VisualizationProvider>
</Provider>,
);

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(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { wrapper: TestWrapper });

const item = wrapper.getByTestId('context-menu-item-replace');

Expand All @@ -92,7 +147,7 @@ describe('NodeContextMenu', () => {

it('should render an ItemDeleteStep item if canRemoveStep is true', () => {
nodeInteractions.canRemoveStep = true;
const wrapper = render(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { wrapper: TestWrapper });

const item = wrapper.getByTestId('context-menu-item-delete');

Expand All @@ -101,7 +156,7 @@ describe('NodeContextMenu', () => {

it('should render an ItemDeleteGroup item if canRemoveFlow is true', () => {
nodeInteractions.canRemoveFlow = true;
const wrapper = render(<NodeContextMenu element={element} />);
const wrapper = render(<NodeContextMenu element={element} />, { wrapper: TestWrapper });

const item = wrapper.getByTestId('context-menu-item-container-remove');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -80,6 +81,8 @@ export const NodeContextMenuFn = (element: GraphElement<ElementModel, CanvasNode
<ItemDisableStep key="context-menu-item-disable" data-testid="context-menu-item-disable" vizNode={vizNode} />,
);
}
items.push(<ItemEnableAllSteps key="context-menu-item-enable-all" data-testid="context-menu-item-enable-all" />);

if (nodeInteractions.canReplaceStep) {
items.push(
<ItemReplaceStep
Expand Down

0 comments on commit 3ec247d

Please sign in to comment.