Skip to content

Commit ee9b15c

Browse files
Add pipeline groups (#8278)
* Add pipeline groups * Breakout topology package options for readability
1 parent 873cff7 commit ee9b15c

File tree

20 files changed

+915
-918
lines changed

20 files changed

+915
-918
lines changed
Lines changed: 96 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,121 @@
11
import React from 'react';
22
import {
3-
Model,
4-
ModelKind,
5-
withPanZoom,
6-
GraphComponent,
7-
useComponentFactory,
8-
useModel,
9-
ComponentFactory,
10-
useLayoutFactory,
113
Graph,
124
Layout,
135
PipelineDagreLayout,
14-
PipelineNodeModel,
6+
Visualization,
7+
VisualizationProvider,
8+
useEventListener,
9+
SelectionEventListener,
10+
SELECTION_EVENT,
11+
TopologyView,
12+
VisualizationSurface,
13+
useVisualizationController,
14+
NODE_SEPARATION_HORIZONTAL,
15+
GRAPH_LAYOUT_END_EVENT,
1516
getSpacerNodes,
1617
getEdgesFromNodes,
17-
useVisualizationController
18+
DEFAULT_EDGE_TYPE
1819
} from '@patternfly/react-topology';
1920
import '@patternfly/react-styles/css/components/Topology/topology-components.css';
20-
import withTopologySetup from './utils/withTopologySetup';
21-
import pipelineComponentFactory from './components/pipelineComponentFactory';
22-
import { createFinallyTasks, createParallelTasks, createStatusTasks, setWhenStatus } from './utils/pipelineUtils';
21+
import pipelineComponentFactory, { GROUPED_EDGE_TYPE } from './components/pipelineComponentFactory';
22+
import { usePipelineOptions } from './usePipelineOptions';
23+
import { useDemoPipelineNodes } from './useDemoPipelineNodes';
24+
import { GROUPED_PIPELINE_NODE_SEPARATION_HORIZONTAL } from './components/DemoTaskGroupEdge';
25+
26+
export const PIPELINE_NODE_SEPARATION_VERTICAL = 65;
2327

2428
export const LAYOUT_TITLE = 'Layout';
2529

26-
const getModel = (layout: string): Model => {
27-
const tasks: PipelineNodeModel[] = createStatusTasks('task', 4, undefined, false, true, true, true);
30+
const PIPELINE_LAYOUT = 'PipelineLayout';
31+
const GROUPED_PIPELINE_LAYOUT = 'GroupedPipelineLayout';
2832

29-
const whenTasks = tasks.reduce((acc, task, index) => {
30-
if (index % (Math.floor(tasks.length / 3) + 1) !== 0) {
31-
acc.push(task);
32-
}
33-
return acc;
34-
}, []);
35-
setWhenStatus(whenTasks);
33+
const TopologyPipelineLayout: React.FC = () => {
34+
const [selectedIds, setSelectedIds] = React.useState<string[]>();
3635

37-
for (let i = 0; i < tasks.length; i++) {
38-
tasks[i + 1].runAfterTasks.push(tasks[i].id);
39-
i++;
40-
if (i + 1 < tasks.length) {
41-
tasks[i + 1].runAfterTasks.push(tasks[i].id);
42-
}
43-
i++;
44-
if (i + 1 < tasks.length) {
45-
tasks[i + 1].runAfterTasks.push(tasks[i].id);
46-
}
47-
i++;
48-
}
36+
const controller = useVisualizationController();
37+
const { contextToolbar, showContextMenu, showBadges, showIcons, showGroups, badgeTooltips } = usePipelineOptions(
38+
true
39+
);
40+
const pipelineNodes = useDemoPipelineNodes(
41+
showContextMenu,
42+
showBadges,
43+
showIcons,
44+
badgeTooltips,
45+
'PipelineDagreLayout',
46+
showGroups
47+
);
4948

50-
const parallelTasks = createParallelTasks('parallelTasks', tasks[9].id, 3, 2, true, true);
51-
tasks.push(...parallelTasks);
49+
React.useEffect(() => {
50+
const spacerNodes = getSpacerNodes(pipelineNodes);
51+
const nodes = [...pipelineNodes, ...spacerNodes];
52+
const edges = getEdgesFromNodes(
53+
nodes.filter(n => !n.group),
54+
showGroups ? GROUPED_EDGE_TYPE : DEFAULT_EDGE_TYPE
55+
);
5256

53-
const finallyNodes = createFinallyTasks('finally', 2, tasks, true);
54-
const finallyGroup = {
55-
id: 'finally-group',
56-
type: 'finally-group',
57-
children: finallyNodes.map(n => n.id),
58-
group: true,
59-
style: { padding: [35, 17] }
60-
};
57+
controller.fromModel(
58+
{
59+
graph: {
60+
id: 'g1',
61+
type: 'graph',
62+
x: 25,
63+
y: 25,
64+
layout: showGroups ? GROUPED_PIPELINE_LAYOUT : PIPELINE_LAYOUT
65+
},
66+
nodes,
67+
edges
68+
},
69+
true
70+
);
71+
controller.getGraph().layout();
72+
}, [controller, pipelineNodes, showGroups]);
6173

62-
const spacerNodes = getSpacerNodes([...tasks, ...finallyNodes]);
63-
const edges = getEdgesFromNodes([...tasks, ...finallyNodes, ...spacerNodes]);
74+
useEventListener<SelectionEventListener>(SELECTION_EVENT, ids => {
75+
setSelectedIds(ids);
76+
});
6477

65-
return {
66-
graph: {
67-
id: 'g1',
68-
type: 'graph',
69-
x: 25,
70-
y: 25,
71-
layout
72-
},
73-
nodes: [...tasks, ...finallyNodes, ...spacerNodes, finallyGroup],
74-
edges
75-
};
78+
return (
79+
<TopologyView contextToolbar={contextToolbar}>
80+
<VisualizationSurface state={{ selectedIds }} />
81+
</TopologyView>
82+
);
7683
};
7784

78-
export const PipelineLayout = withTopologySetup(() => {
79-
useLayoutFactory((type: string, graph: Graph): Layout | undefined => new PipelineDagreLayout(graph, { nodesep: 95 }));
80-
useComponentFactory(pipelineComponentFactory);
81-
const controller = useVisualizationController();
82-
controller.setFitToScreenOnLayout(true);
85+
TopologyPipelineLayout.displayName = 'TopologyPipelineLayout';
8386

84-
// support pan zoom and drag
85-
useComponentFactory(
86-
React.useCallback<ComponentFactory>(kind => {
87-
if (kind === ModelKind.graph) {
88-
return withPanZoom()(GraphComponent);
87+
export const PipelineLayout = React.memo(() => {
88+
const controller = new Visualization();
89+
controller.setFitToScreenOnLayout(true);
90+
controller.registerComponentFactory(pipelineComponentFactory);
91+
controller.registerLayoutFactory(
92+
(type: string, graph: Graph): Layout | undefined =>
93+
new PipelineDagreLayout(graph, {
94+
nodesep: PIPELINE_NODE_SEPARATION_VERTICAL,
95+
ranksep:
96+
type === GROUPED_PIPELINE_LAYOUT ? GROUPED_PIPELINE_NODE_SEPARATION_HORIZONTAL : NODE_SEPARATION_HORIZONTAL,
97+
ignoreGroups: true
98+
})
99+
);
100+
controller.fromModel(
101+
{
102+
graph: {
103+
id: 'g1',
104+
type: 'graph',
105+
x: 25,
106+
y: 25,
107+
layout: PIPELINE_LAYOUT
89108
}
90-
return undefined;
91-
}, [])
109+
},
110+
false
92111
);
112+
controller.addEventListener(GRAPH_LAYOUT_END_EVENT, () => {
113+
controller.getGraph().fit(75);
114+
});
93115

94-
useModel(getModel('PipelineDagreLayout'));
95-
return null;
116+
return (
117+
<VisualizationProvider controller={controller}>
118+
<TopologyPipelineLayout />
119+
</VisualizationProvider>
120+
);
96121
});

packages/react-integration/demo-app-ts/src/components/demos/TopologyDemo/PipelineTasks.tsx

Lines changed: 17 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react';
2-
import { Checkbox, Flex, FlexItem, Radio, ToolbarItem } from '@patternfly/react-core';
32
import {
43
TopologyView,
54
Visualization,
@@ -12,150 +11,44 @@ import {
1211
} from '@patternfly/react-topology';
1312
import '@patternfly/react-styles/css/components/Topology/topology-components.css';
1413
import pipelineComponentFactory from './components/pipelineComponentFactory';
15-
import { createFinallyTasks, createStatusTasks, setWhenStatus } from './utils/pipelineUtils';
14+
import { usePipelineOptions } from './usePipelineOptions';
15+
import { useDemoPipelineNodes } from './useDemoPipelineNodes';
1616

1717
export const TASKS_TITLE = 'Tasks';
1818

1919
export const PipelineTasks: React.FC = () => {
20-
const [showContext, setShowContext] = React.useState<boolean>(false);
21-
const [showBadges, setShowBadges] = React.useState<boolean>(false);
22-
const [showIcons, setShowIcons] = React.useState<boolean>(false);
2320
const [selectedIds, setSelectedIds] = React.useState<string[]>();
24-
const [badgeTooltips, setBadgeTooltips] = React.useState<boolean>(false);
2521

2622
const controller = useVisualizationController();
23+
const { contextToolbar, showContextMenu, showBadges, showIcons, badgeTooltips } = usePipelineOptions();
24+
const pipelineNodes = useDemoPipelineNodes(showContextMenu, showBadges, showIcons, badgeTooltips);
2725

2826
React.useEffect(() => {
29-
const tasks = createStatusTasks(
30-
'task',
31-
4,
32-
undefined,
33-
false,
34-
false,
35-
showContext,
36-
showBadges,
37-
showIcons,
38-
badgeTooltips
39-
);
40-
setWhenStatus(tasks);
41-
const finallyNodes = createFinallyTasks('finally', 2, tasks);
42-
const finallyGroup = {
43-
id: 'finally-group',
44-
type: 'finally-group',
45-
children: finallyNodes.map(n => n.id),
46-
group: true,
47-
style: { padding: 30 }
48-
};
49-
const model = {
50-
graph: {
51-
id: 'g1',
52-
type: 'graph',
53-
x: 25,
54-
y: 25
27+
controller.fromModel(
28+
{
29+
graph: {
30+
id: 'g1',
31+
type: 'graph',
32+
x: 25,
33+
y: 25
34+
},
35+
nodes: pipelineNodes
5536
},
56-
nodes: [...tasks, ...finallyNodes, finallyGroup]
57-
};
58-
controller.fromModel(model, false);
59-
}, [badgeTooltips, controller, showBadges, showContext, showIcons]);
37+
false
38+
);
39+
}, [controller, pipelineNodes]);
6040

6141
useEventListener<SelectionEventListener>(SELECTION_EVENT, ids => {
6242
setSelectedIds(ids);
6343
});
6444

65-
const contextToolbar = (
66-
<>
67-
<ToolbarItem>
68-
<Flex alignItems={{ default: 'alignItemsCenter' }}>
69-
<FlexItem>Icons:</FlexItem>
70-
<FlexItem>
71-
<Radio
72-
id="show-icons"
73-
aria-label="Show icons"
74-
label="Show"
75-
name="show-icons"
76-
isChecked={showIcons}
77-
onChange={checked => checked && setShowIcons(true)}
78-
/>
79-
</FlexItem>
80-
<FlexItem>
81-
<Radio
82-
id="hide-icons"
83-
aria-label="Hide icons"
84-
label="Hide"
85-
name="hide-icons"
86-
isChecked={!showIcons}
87-
onChange={checked => checked && setShowIcons(false)}
88-
/>
89-
</FlexItem>
90-
</Flex>
91-
</ToolbarItem>
92-
<ToolbarItem>
93-
<Flex alignItems={{ default: 'alignItemsCenter' }}>
94-
<FlexItem>Badges:</FlexItem>
95-
<FlexItem>
96-
<Radio
97-
id="show-badges"
98-
aria-label="Show badges"
99-
label="Show"
100-
name="show-badges"
101-
isChecked={showBadges}
102-
onChange={checked => checked && setShowBadges(true)}
103-
/>
104-
</FlexItem>
105-
<FlexItem>
106-
<Radio
107-
id="hide-badges"
108-
aria-label="Hide badges"
109-
label="Hide"
110-
name="hide-badges"
111-
isChecked={!showBadges}
112-
onChange={checked => checked && setShowBadges(false)}
113-
/>
114-
</FlexItem>
115-
</Flex>
116-
</ToolbarItem>
117-
<ToolbarItem>
118-
<Flex alignItems={{ default: 'alignItemsCenter' }}>
119-
<FlexItem>Context menus:</FlexItem>
120-
<FlexItem>
121-
<Radio
122-
id="show-menus"
123-
aria-label="Show context menus"
124-
label="Show"
125-
name="show-menus"
126-
isChecked={showContext}
127-
onChange={checked => checked && setShowContext(true)}
128-
/>
129-
</FlexItem>
130-
<FlexItem>
131-
<Radio
132-
id="hide-context"
133-
aria-label="Hide context menus"
134-
label="Hide"
135-
name="hide-context"
136-
isChecked={!showContext}
137-
onChange={checked => checked && setShowContext(false)}
138-
/>
139-
</FlexItem>
140-
<FlexItem className="pf-u-ml-2xl">
141-
<Checkbox
142-
id="badge-tips-checkbox"
143-
label="Use tooltips for badges"
144-
isChecked={badgeTooltips}
145-
onChange={setBadgeTooltips}
146-
/>
147-
</FlexItem>
148-
</Flex>
149-
</ToolbarItem>
150-
</>
151-
);
152-
15345
return (
15446
<TopologyView contextToolbar={contextToolbar}>
15547
<VisualizationSurface state={{ selectedIds }} />
15648
</TopologyView>
15749
);
15850
};
51+
15952
PipelineTasks.displayName = 'PipelineTasks';
16053

16154
export const TopologyPipelineTasks = React.memo(() => {

packages/react-integration/demo-app-ts/src/components/demos/TopologyDemo/TopologyDemo.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@
2222
.pf-ri__topology-demo .pf-c-toolbar__item .pf-l-flex {
2323
--pf-l-flex--FlexWrap: no-wrap;
2424
}
25-
.pf-ri__topology-demo .pf-c-toolbar__item .pf-l-flex > * {
26-
--pf-l-flex--spacer: 8px;
27-
}
28-
2925
.pf-ri__topology-demo .pf-c-toolbar__item .pf-c-form-control {
3026
width: 85px;
3127
}

0 commit comments

Comments
 (0)