Skip to content

Commit 6bf30e5

Browse files
authored
Merge pull request #221 from keithchong/9329-PartA-DifferentNodeShape
Toggle resource node shape to be OpenShift (classic PatternFly) or Argo CD (#9329)
2 parents fc45e03 + a5febf3 commit 6bf30e5

15 files changed

Lines changed: 353 additions & 87 deletions

locales/en/plugin__gitops-plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"Delete Application": "Delete Application",
121121
"Show {{x}}": "Show {{x}}",
122122
"Hide {{x}}": "Hide {{x}}",
123+
"Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}": "Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}",
123124
"Group resources of the same kind into one node": "Group resources of the same kind into one node",
124125
"Group Nodes": "Group Nodes",
125126
"There is no health status for this resource": "There is no health status for this resource",

locales/ja/plugin__gitops-plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"Delete Application": "Delete Application",
121121
"Show {{x}}": "Show {{x}}",
122122
"Hide {{x}}": "Hide {{x}}",
123+
"Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}": "Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}",
123124
"Group resources of the same kind into one node": "Group resources of the same kind into one node",
124125
"Group Nodes": "Group Nodes",
125126
"There is no health status for this resource": "There is no health status for this resource",

locales/ko/plugin__gitops-plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"Delete Application": "Delete Application",
121121
"Show {{x}}": "Show {{x}}",
122122
"Hide {{x}}": "Hide {{x}}",
123+
"Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}": "Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}",
123124
"Group resources of the same kind into one node": "Group resources of the same kind into one node",
124125
"Group Nodes": "Group Nodes",
125126
"There is no health status for this resource": "There is no health status for this resource",

locales/zh/plugin__gitops-plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"Delete Application": "Delete Application",
121121
"Show {{x}}": "Show {{x}}",
122122
"Hide {{x}}": "Hide {{x}}",
123+
"Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}": "Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}",
123124
"Group resources of the same kind into one node": "Group resources of the same kind into one node",
124125
"Group Nodes": "Group Nodes",
125126
"There is no health status for this resource": "There is no health status for this resource",

src/gitops/components/application/graph/ApplicationGraphView.scss

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,53 @@
3232
// .pf-topology__edge__background {
3333
// stroke: var(--pf-t--global--dark--background--color--100);
3434
// }
35-
35+
36+
.pf-topology__node.pf-m-selected {
37+
.pf-topology__node__label__badge > rect {
38+
stroke-width: 1;
39+
}
40+
.pf-topology__node__action-icon__icon .pf-v6-svg{
41+
stroke: var(--pf-topology__node--Color);
42+
fill: var(--pf-topology__node__label__background--Stroke);
43+
}
44+
.gitops-node-layout {
45+
.pf-topology__node__action-icon__icon .pf-v6-svg{
46+
stroke: var(--pf-t--global--border--color--default);
47+
fill: var(--pf-topology__node__action-icon__icon--Color);
48+
}
49+
}
50+
}
51+
52+
.gitops-resource-node-label-badge-opaque {
53+
opacity: 0;
54+
}
55+
56+
.gitops-resource-node-label {
57+
.pf-topology__node__separator {
58+
opacity: 0;
59+
}
60+
61+
.pf-topology__node__label__background {
62+
width: 0px;
63+
height: 0px;
64+
stroke: var(--pf-topology__node--Color);
65+
fill: var(--pf-topology__node--Color);
66+
}
67+
}
68+
69+
.gitops-resource-node-menu {
70+
transform: translate(243px,10px);
71+
}
72+
73+
.gitops-resource-group-node-menu {
74+
transform: translate(243px,25px);
75+
}
76+
77+
.gitops-application-node-menu {
78+
transform: translate(263px,25px);
79+
fill: var(--pf-topology__node__label__background--Stroke);
80+
}
81+
3682
.step-edge {
3783
&.step-edge-healthy {
3884
stroke: var(--pf-v5-global--success-color--100);
@@ -74,7 +120,7 @@
74120
padding-right: 6px;
75121
}
76122

77-
.pf-v6-c-toolbar__item:has(button#setting-owner-reference-layout) {
123+
.pf-v6-c-toolbar__item:has(button#toggle-node-layout) {
78124
padding-left: 8px;
79125
border-left: 2px solid var(--pf-t--global--border--color--default);
80126
}

src/gitops/components/application/graph/ApplicationGraphView.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
useUserSettings,
1919
} from '@openshift-console/dynamic-plugin-sdk';
2020
import { useK8sModel } from '@openshift-console/dynamic-plugin-sdk/lib/utils/k8s/hooks/useK8sModel';
21-
import { ObjectGroupIcon } from '@patternfly/react-icons';
21+
import { ObjectGroupIcon, ToggleOffIcon, ToggleOnIcon } from '@patternfly/react-icons';
2222
import {
2323
action,
2424
ComponentFactory,
@@ -66,7 +66,7 @@ import './ApplicationGraphView.scss';
6666
const customLayoutFactory: LayoutFactory = (type: string, graph: Graph): Layout | undefined => {
6767
return new DagreLayout(graph, {
6868
rankdir: 'LR',
69-
ranksep: 1,
69+
ranksep: 0,
7070
nodesep: 0,
7171
edgesep: 0,
7272
ranker: 'network-simplex',
@@ -308,6 +308,11 @@ export const ApplicationGraphView: React.FC<{
308308
false,
309309
false,
310310
);
311+
const [resourceNodeLayout, setResourceNodeLayout] = useUserSettings(
312+
'redhat.gitops.resourceNodeLayout',
313+
true,
314+
false,
315+
);
311316
const [argoServer, setArgoServer] = React.useState<ArgoServer>({ host: '', protocol: '' });
312317
const hrefRef = React.useRef<string>('');
313318
React.useEffect(() => {
@@ -363,6 +368,7 @@ export const ApplicationGraphView: React.FC<{
363368
allK8sModels,
364369
groupNodeState,
365370
groupNodeStates,
371+
resourceNodeLayout,
366372
);
367373
const initialEdges = getInitialEdges(application, initialNodes, groupNodeState);
368374
const nodes = [...initialNodes];
@@ -373,12 +379,16 @@ export const ApplicationGraphView: React.FC<{
373379
// Track the previous node count to detect structural changes
374380
const previousNodeCountRef = React.useRef<number>(0);
375381
const groupNodeRef = React.useRef<boolean>(groupNodeState);
382+
const resourceNodeLayoutRef = React.useRef<boolean>(resourceNodeLayout);
376383

377384
const currentNodeCount = nodes.length;
378385
const previousNodeCount = previousNodeCountRef.current;
379386
const previousGroupNodeState = groupNodeRef.current;
387+
const previousResourceNodeLayout = resourceNodeLayoutRef.current;
380388
const isStructuralChange =
381-
currentNodeCount !== previousNodeCount || previousGroupNodeState != groupNodeState;
389+
currentNodeCount !== previousNodeCount ||
390+
previousGroupNodeState != groupNodeState ||
391+
previousResourceNodeLayout != resourceNodeLayout;
382392

383393
if (isStructuralChange || previousNodeCount === 0) {
384394
// Structural change: Create model WITH layout
@@ -395,6 +405,7 @@ export const ApplicationGraphView: React.FC<{
395405
controller.fromModel(modelWithLayout, false);
396406
previousNodeCountRef.current = currentNodeCount;
397407
groupNodeRef.current = groupNodeState;
408+
resourceNodeLayoutRef.current = resourceNodeLayout;
398409
} else {
399410
// Data change only: Update ONLY changed nodes (no layout, no position changes)
400411
let updateCount = 0;
@@ -462,6 +473,22 @@ export const ApplicationGraphView: React.FC<{
462473
controller.getGraph().layout();
463474
}),
464475
customButtons: [
476+
{
477+
id: 'toggle-node-layout',
478+
icon: resourceNodeLayout ? <ToggleOnIcon /> : <ToggleOffIcon />,
479+
tooltip: t(
480+
'Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}',
481+
{ x: resourceNodeLayout ? 'Argo CD' : 'OpenShift' },
482+
),
483+
ariaLabel: t(
484+
'Toggle between OpenShift shapes and Argo CD shapes for tree nodes. Current setting: {{x}}',
485+
{ x: resourceNodeLayout ? 'Argo CD' : 'OpenShift' },
486+
),
487+
callback: () => {
488+
setResourceNodeLayout(!resourceNodeLayout);
489+
controller.getGraph().layout();
490+
},
491+
},
465492
{
466493
id: 'use-group-nodes',
467494
icon: <ObjectGroupIcon />,

src/gitops/components/application/graph/graph-utils.tsx

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ const NODE_TYPE_APPLICATION = 'application-node';
2424
const NODE_TYPE_APPLICATION_LABEL = 'Application';
2525

2626
// Map application health status with topology node status
27-
const createApplicationNode = (application: ApplicationKind): NodeModel => {
27+
const createApplicationNode = (
28+
application: ApplicationKind,
29+
resourceNodeLayout: boolean,
30+
): NodeModel => {
2831
const nodeStatus = getTopologyNodeStatus(application.status?.health?.status);
2932
return {
3033
id:
@@ -34,7 +37,7 @@ const createApplicationNode = (application: ApplicationKind): NodeModel => {
3437
'-' +
3538
application?.metadata?.namespace,
3639
type: NODE_TYPE_APPLICATION,
37-
label: NODE_TYPE_APPLICATION_LABEL,
40+
label: resourceNodeLayout ? ' ' : NODE_TYPE_APPLICATION_LABEL,
3841
status: nodeStatus,
3942
width: APP_NODE_WIDTH,
4043
height: APP_NODE_HEIGHT,
@@ -47,7 +50,9 @@ const createApplicationNode = (application: ApplicationKind): NodeModel => {
4750
badgeBorderColor: RESOURCE_COLORS.get(
4851
RESOURCE_BADGE_COLORS.get('.co-m-resource-' + application?.kind.toLowerCase()),
4952
),
53+
badgeTextColor: 'white',
5054
rank: 0,
55+
resourceNodeLayout: resourceNodeLayout,
5156
nodeStatus: nodeStatus,
5257
resourceHealthStatus: application?.status?.health?.status,
5358
appHealthStatus: application?.status?.health?.status,
@@ -105,6 +110,7 @@ const createGroupResourceNode = (
105110
resources: ApplicationResourceStatus[],
106111
allK8sModels: { [key: string]: K8sModel },
107112
resourceGroupExpandState: boolean,
113+
resourceNodeLayout: boolean,
108114
): Map<string, NodeModel> => {
109115
const resourceCount = resources.filter((res) => res.kind === resource.kind).length;
110116
const resourceHealthyCount = resource.health
@@ -186,7 +192,7 @@ const createGroupResourceNode = (
186192
groupResourceNode = {
187193
id: kind + '-node-group',
188194
type: 'node-group',
189-
label: allK8sModels[kind]?.labelPlural || kind + 's',
195+
label: resourceNodeLayout ? ' ' : allK8sModels[kind]?.labelPlural || kind + 's',
190196
shape: NodeShape.stadium,
191197
status: groupStatus,
192198
width: 280,
@@ -196,6 +202,7 @@ const createGroupResourceNode = (
196202
kind: kind, // Group's kind
197203
kindPlural: allK8sModels[kind]?.labelPlural || kind + 's',
198204
resourceGroupExpandState: resourceGroupExpandState,
205+
resourceNodeLayout: resourceNodeLayout,
199206
nodeStatus: groupStatus,
200207
healthStatus: groupStatus,
201208
healthyCount: resourceHealthyCount,
@@ -217,6 +224,7 @@ const createGroupResourceNode = (
217224
badgeColor:
218225
RESOURCE_COLORS.get(RESOURCE_BADGE_COLORS.get('.co-m-resource-' + kind.toLowerCase())) ||
219226
RESOURCE_COLORS.get('color-container-dark'),
227+
badgeTextColor: 'white',
220228
icon: kind,
221229
resourceChildrenIds: [],
222230
},
@@ -246,29 +254,30 @@ export const getInitialNodes = (
246254
allK8sModels: { [key: string]: K8sModel },
247255
showGroupNodes: boolean,
248256
groupNodeStates: string[],
257+
resourceNodeLayout: boolean,
249258
) => {
250259
// This contains all the nodes we want to add to the graph view
251260
const initialNodes: NodeModel[] = [];
252261
// This contains all the group nodes for each resource kind: kind to model map
253262
let groupResourceNodeMap = new Map<string, NodeModel>();
254263

255264
// Step 1. Create the Application Node
256-
initialNodes.push(createApplicationNode(application));
265+
initialNodes.push(createApplicationNode(application, resourceNodeLayout));
257266

258267
// Step 2: Proceed with adding more nodes only if the application has resources
259268
// If we use the resource tree in the future, this will change
260269
if (resources && resources.length > 0) {
261270
// Spacer node to the right of the application node. Fixed.
262271
initialNodes.push(createSpacerNode(1, 'application-node-spacer'));
263272
// Add child resources
264-
resources.forEach((resource) => {
273+
resources.forEach((resource, count) => {
265274
const kind = resource.kind;
266275
const badgeLabel = allK8sModels[kind]?.abbr || kindToAbbr(kind);
267276
const color =
268277
RESOURCE_COLORS.get(
269278
RESOURCE_BADGE_COLORS.get('.co-m-resource-' + resource.kind.toLowerCase()),
270279
) || RESOURCE_COLORS.get('color-container-dark');
271-
const nodeId = resource.kind + '-' + resource.name + '-' + resource.namespace;
280+
const nodeId = count + '-' + resource.kind + '-' + resource.name + '-' + resource.namespace;
272281
const key = resource.kind + 's';
273282
const resourceGroupExpandState = groupNodeStates.includes(key);
274283

@@ -280,6 +289,7 @@ export const getInitialNodes = (
280289
resources,
281290
allK8sModels,
282291
resourceGroupExpandState,
292+
resourceNodeLayout,
283293
);
284294

285295
if (!initialNodes.includes(groupResourceNodeMap.get(kind))) {
@@ -295,7 +305,7 @@ export const getInitialNodes = (
295305
initialNodes.push({
296306
id: nodeId,
297307
type: 'node',
298-
label: resource.kind,
308+
label: resourceNodeLayout ? ' ' : resource.kind,
299309
width: 280,
300310
height: NODE_DIAMETER,
301311
labelPosition: LabelPosition.bottom,
@@ -305,6 +315,7 @@ export const getInitialNodes = (
305315
name: resource.name,
306316
group: resource.group,
307317
kind: resource.kind,
318+
resourceNodeLayout: resourceNodeLayout,
308319
version: resource.version,
309320
namespace: resource.namespace,
310321
indent: 100,
@@ -313,6 +324,7 @@ export const getInitialNodes = (
313324
syncStatus: resource.status,
314325
rank: 5,
315326
badgeColor: color,
327+
badgeTextColor: 'white',
316328
badge: badgeLabel,
317329
icon: kind,
318330
},
@@ -348,7 +360,6 @@ export const getInitialNodes = (
348360
selectable: false,
349361
hideContextMenuKebab: true,
350362
hulledOutline: false,
351-
style: { padding: 40 },
352363
data: {
353364
kind: groupNode.data.kind,
354365
},
@@ -382,7 +393,6 @@ export const getInitialNodes = (
382393
selectable: false,
383394
hideContextMenuKebab: true,
384395
hulledOutline: false,
385-
style: { padding: 40 },
386396
};
387397
initialNodes.push(transparentGroupsOfGroups);
388398
}
@@ -420,7 +430,7 @@ export const getInitialEdges = (
420430
target: node.id,
421431
edgeStyle: EdgeStyle.dotted,
422432
data: {
423-
indent: 100,
433+
indent: 0,
424434
},
425435
});
426436
}
@@ -432,7 +442,7 @@ export const getInitialEdges = (
432442
target: node.data.kind + '-node-spacer',
433443
edgeStyle: EdgeStyle.dotted,
434444
data: {
435-
indent: 100,
445+
indent: 0,
436446
},
437447
});
438448
}
@@ -445,17 +455,18 @@ export const getInitialEdges = (
445455
if (node.type === 'node') {
446456
const b =
447457
nodes.filter(
448-
(res) => res.type === 'node-group' && res.id === node.label + '-node-group',
458+
(res) => res.type === 'node-group' && res.id === node.data.kind + '-node-group',
449459
).length > 0;
450460
initialEdges.push({
451-
id: 'e-' + node.label + '-' + index,
461+
id: 'e-' + node.data.kind + '-' + index,
452462
type: 'edge',
453463
nodeSeparation: 0,
454-
source: showGroupNodes && b ? node.label + '-node-spacer' : 'application-node-spacer',
464+
source:
465+
showGroupNodes && b ? node.data.kind + '-node-spacer' : 'application-node-spacer',
455466
target: node.id,
456467
edgeStyle: EdgeStyle.default,
457468
data: {
458-
indent: 100,
469+
indent: 0,
459470
},
460471
});
461472
}

0 commit comments

Comments
 (0)