Skip to content

Commit

Permalink
feat(IVizNode): Get All Visualization nodes
Browse files Browse the repository at this point in the history
A frequent use case for the Canvas is to identify nodes, unfortunately, there's no easy way to find nodes using a predicate.

This commit allows finding nodes given a predicate or retrieving all nodes if no predicate has been provided.

* How to use?
```typescript
// First we obtain the `VisualizationController`
const controller = useVisualizationController();

// Second, we define a predicate
const predicate = (vizNode: IVisualizationNode) => {
  return vizNode.getComponentSchema()?.definition?.disabled;
};

// Lastly, we use the `getVisualizationNodesFromGraph` function to find the nodes
const vizNodes = getVisualizationNodesFromGraph(visualizationController.getGraph(), predicate);
```
  • Loading branch information
lordrip committed Oct 15, 2024
1 parent 6823d5a commit d985f82
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 74 deletions.
82 changes: 16 additions & 66 deletions packages/ui/src/stubs/camel-route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CamelRouteVisualEntity } from '../models/visualization/flows';
import { RouteDefinition } from '@kaoto/camel-catalog/types';
import { parse } from 'yaml';

/**
* This is a stub Camel Route in YAML format.
Expand Down Expand Up @@ -41,69 +42,18 @@ export const camelRouteYaml = `
* This is a stub Camel Route in JSON format.
* It is used to test the Canvas component.
*/
export const camelRouteJson = {
route: {
id: 'route-8888',
from: {
uri: 'timer',
parameters: {
timerName: 'tutorial',
},
steps: [
{
'set-header': {
simple: '${random(2)}',
name: 'myChoice',
},
},
{
choice: {
when: [
{
simple: '${header.myChoice} == 1',
steps: [
{
log: {
id: 'log-1',
message: 'We got a one.',
},
},
],
},
],
otherwise: {
steps: [
{
to: {
uri: 'amqp:queue:',
},
},
{
to: {
uri: 'amqp:queue:',
},
},
{
log: {
id: 'log-2',
message: 'We got a ${body}',
},
},
],
},
},
},
{
to: {
uri: 'direct:my-route',
parameters: {
bridgeErrorHandler: true,
},
},
},
],
},
},
};
export const camelRouteJson: { route: RouteDefinition } = parse(camelRouteYaml)[0];

export const camelRoute = new CamelRouteVisualEntity(camelRouteJson);
export const camelRouteWithDisabledSteps: { route: RouteDefinition } = parse(`
route:
id: route-8888
from:
uri: timer
steps:
- log:
disabled: true
message: \${body}
- to:
uri: direct
disabled: true
`);
107 changes: 107 additions & 0 deletions packages/ui/src/utils/get-viznodes-from-graph.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Graph, Model } from '@patternfly/react-topology';
import { ControllerService } from '../components/Visualization/Canvas/controller.service';
import { FlowService } from '../components/Visualization/Canvas/flow.service';
import { CamelRouteVisualEntity, IVisualizationNode } from '../models';
import { camelRouteWithDisabledSteps } from '../stubs';
import { getVisualizationNodesFromGraph } from './get-viznodes-from-graph';

describe('getVisualizationNodesFromGraph', () => {
it('should return an empty array if there are no nodes in the graph', () => {
const graph = {
getNodes: jest.fn().mockReturnValue([]),
} as unknown as Graph;

const result = getVisualizationNodesFromGraph(graph);

expect(result).toEqual([]);
});

it('should return all visualization nodes from the graph', () => {
const visualEntity = new CamelRouteVisualEntity(camelRouteWithDisabledSteps);
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 vizNodes = getVisualizationNodesFromGraph(visualizationController.getGraph());

expect(vizNodes).toHaveLength(4);
expect(vizNodes[0].getNodeLabel()).toEqual('route-8888');
expect(vizNodes[1].getNodeLabel()).toEqual('timer');
expect(vizNodes[2].getNodeLabel()).toEqual('log');
expect(vizNodes[3].getNodeLabel()).toEqual('direct');
});

it('should return all visualization nodes matching the predicate', () => {
const visualEntity = new CamelRouteVisualEntity(camelRouteWithDisabledSteps);
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 predicate = (vizNode: IVisualizationNode) => vizNode.getNodeLabel() !== 'timer';
const vizNodes = getVisualizationNodesFromGraph(visualizationController.getGraph(), predicate);

expect(vizNodes).toHaveLength(3);
expect(vizNodes[0].getNodeLabel()).toEqual('route-8888');
expect(vizNodes[1].getNodeLabel()).toEqual('log');
expect(vizNodes[2].getNodeLabel()).toEqual('direct');
});

it('should return all visualization nodes matching a complex predicate', () => {
const visualEntity = new CamelRouteVisualEntity(camelRouteWithDisabledSteps);
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 predicate = (vizNode: IVisualizationNode) => {
return vizNode.getComponentSchema()?.definition?.disabled;
};
const vizNodes = getVisualizationNodesFromGraph(visualizationController.getGraph(), predicate);

expect(vizNodes).toHaveLength(2);
expect(vizNodes[0].getNodeLabel()).toEqual('log');
expect(vizNodes[1].getNodeLabel()).toEqual('direct');
});
});
50 changes: 50 additions & 0 deletions packages/ui/src/utils/get-viznodes-from-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { Graph, GraphElement } from '@patternfly/react-topology';
import { CanvasNode } from '../components/Visualization/Canvas/canvas.models';
import { IVisualizationNode } from '../models/visualization/base-visual-entity';
import { isDefined } from './is-defined';

const getVisualizationNodeFromCanvasNode = (
node: GraphElement<CanvasNode, CanvasNode['data']>,
predicate: (vizNode: IVisualizationNode) => boolean,
accumulator: IVisualizationNode[],
): IVisualizationNode[] => {
const vizNode = node.getData()?.vizNode;
if (isDefined(vizNode) && predicate(vizNode)) {
accumulator.push(vizNode);
}

node.getChildren().forEach((child) => {
getVisualizationNodeFromCanvasNode(child, predicate, accumulator);
});

return accumulator;
};

export const getVisualizationNodesFromGraph = (
graph: Graph,
predicate: (vizNode: IVisualizationNode) => boolean = () => true,
): IVisualizationNode[] => {
const vizNodes: IVisualizationNode[] = [];

graph.getNodes().forEach((node) => {
getVisualizationNodeFromCanvasNode(node, predicate, vizNodes);
});

return vizNodes;
};
17 changes: 9 additions & 8 deletions packages/ui/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
export * from './create-camel-properties-sorter';
export * from './camel-uri-helper';
export * from './catalog-schema-loader';
export * from './create-camel-properties-sorter';
export * from './event-notifier';
export * from './get-array-property';
export * from './get-custom-schema-from-kamelet';
export * from './get-field-groups';
export * from './get-required-properties-schema';
export * from './get-serialized-model';
export * from './get-user-updated-properties-schema';
export * from './get-value';
export * from './get-viznodes-from-graph';
export * from './init-visible-flows';
export * from './is-defined';
export * from './is-enum-type';
export * from './join-path';
export * from './node-icon-resolver';
export * from './pipe-custom-schema';
export * from './promise-timeout';
export * from './resolve-ref-if-needed';
export * from './set-value';
export * from './get-custom-schema-from-kamelet';
export * from './update-kamelet-from-custom-schema';
export * from './pipe-custom-schema';
export * from './promise-timeout';
export * from './get-field-groups';
export * from './get-serialized-model';
export * from './get-user-updated-properties-schema';
export * from './get-required-properties-schema';
export * from './weight-schema-properties';

0 comments on commit d985f82

Please sign in to comment.