Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(Canvas): Extract item enable all steps #1574

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading