Skip to content

Commit 39abbe5

Browse files
moving logic to hook
1 parent 0500962 commit 39abbe5

File tree

2 files changed

+180
-168
lines changed

2 files changed

+180
-168
lines changed

src/components/Graphs/Graph.tsx

Lines changed: 8 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,22 @@
1-
import React, { useEffect, useMemo, useState } from 'react';
2-
import { ReactFlow, Background, Controls, useNodesState, useEdgesState, MarkerType, Position } from '@xyflow/react';
3-
import type { Edge, Node, NodeProps } from '@xyflow/react';
1+
import React, { useMemo, useState } from 'react';
2+
import { ReactFlow, Background, Controls, MarkerType } from '@xyflow/react';
3+
import type { NodeProps } from '@xyflow/react';
44
import styles from './Graph.module.css';
5-
import dagre from 'dagre';
65
import '@xyflow/react/dist/style.css';
7-
import { useApiResource, useProvidersConfigResource } from '../../lib/api/useApiResource';
8-
import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources';
9-
import { resourcesInterval } from '../../lib/shared/constants';
10-
import { NodeData, ManagedResourceGroup, ManagedResourceItem } from './types';
6+
import { ManagedResourceItem } from './types';
117
import CustomNode from './CustomNode';
128
import { Legend, LegendItem } from './Legend';
13-
import { extractRefs, generateColorMap, getStatusFromConditions, resolveProviderType } from './graphUtils';
149
import { YamlViewDialog } from '../Yaml/YamlViewDialog';
1510
import YamlViewer from '../Yaml/YamlViewer';
1611
import { stringify } from 'yaml';
1712
import { removeManagedFieldsProperty } from '../../utils/removeManagedFieldsProperty';
1813
import { useTranslation } from 'react-i18next';
19-
20-
const nodeWidth = 250;
21-
const nodeHeight = 60;
22-
23-
function buildGraph(
24-
treeData: NodeData[],
25-
colorBy: 'provider' | 'source',
26-
colorMap: Record<string, string>,
27-
): { nodes: Node<NodeData>[]; edges: Edge[] } {
28-
const dagreGraph = new dagre.graphlib.Graph();
29-
dagreGraph.setDefaultEdgeLabel(() => ({}));
30-
dagreGraph.setGraph({ rankdir: 'TB' });
31-
32-
const nodeMap: Record<string, Node<NodeData>> = {};
33-
treeData.forEach((n) => {
34-
const colorKey = colorBy === 'source' ? n.providerType : n.providerConfigName;
35-
const node: Node<NodeData> = {
36-
id: n.id,
37-
type: 'custom',
38-
data: { ...n },
39-
style: {
40-
border: `2px solid ${colorMap[colorKey] || '#ccc'}`,
41-
borderRadius: 8,
42-
backgroundColor: '#fff',
43-
width: nodeWidth,
44-
height: nodeHeight,
45-
},
46-
width: nodeWidth,
47-
height: nodeHeight,
48-
position: { x: 0, y: 0 },
49-
sourcePosition: Position.Bottom,
50-
targetPosition: Position.Top,
51-
};
52-
nodeMap[n.id] = node;
53-
dagreGraph.setNode(n.id, { width: nodeWidth, height: nodeHeight });
54-
});
55-
56-
const edgeList: Edge[] = [];
57-
treeData.forEach((n) => {
58-
if (n.parentId) {
59-
dagreGraph.setEdge(n.parentId, n.id);
60-
edgeList.push({
61-
id: `e-${n.parentId}-${n.id}`,
62-
source: n.parentId,
63-
target: n.id,
64-
markerEnd: { type: MarkerType.ArrowClosed },
65-
});
66-
}
67-
n.extraRefs?.forEach((refId) => {
68-
if (nodeMap[refId]) {
69-
dagreGraph.setEdge(refId, n.id);
70-
edgeList.push({
71-
id: `e-${refId}-${n.id}`,
72-
source: refId,
73-
target: n.id,
74-
markerEnd: { type: MarkerType.ArrowClosed },
75-
});
76-
}
77-
});
78-
});
79-
80-
dagre.layout(dagreGraph);
81-
Object.values(nodeMap).forEach((node) => {
82-
const pos = dagreGraph.node(node.id);
83-
node.position = { x: pos.x - nodeWidth / 2, y: pos.y - nodeHeight / 2 };
84-
});
85-
86-
return { nodes: Object.values(nodeMap), edges: edgeList };
87-
}
14+
import { useGraph } from './useGraph';
8815

8916
const Graph: React.FC = () => {
9017
const { t } = useTranslation();
91-
92-
const { data: managedResources, error: managedResourcesError } = useApiResource(ManagedResourcesRequest, {
93-
refreshInterval: resourcesInterval,
94-
});
95-
const { data: providerConfigsList, error: providerConfigsError } = useProvidersConfigResource({
96-
refreshInterval: resourcesInterval,
97-
});
98-
const [nodes, setNodes] = useNodesState<Node<NodeData>>([]);
99-
const [edges, setEdges] = useEdgesState<Edge>([]);
10018
const [colorBy, setColorBy] = useState<'provider' | 'source'>('provider');
19+
const { nodes, edges, colorMap, treeData, loading } = useGraph(colorBy);
10120

10221
const [yamlDialogOpen, setYamlDialogOpen] = useState(false);
10322
const [yamlResource, setYamlResource] = useState<ManagedResourceItem | null>(null);
@@ -128,74 +47,6 @@ const Graph: React.FC = () => {
12847
return `${kind ?? ''}${metadata?.name ? '_' : ''}${metadata?.name ?? ''}`;
12948
}, [yamlResource]);
13049

131-
const treeData = useMemo(() => {
132-
const allNodesMap = new Map<string, NodeData>();
133-
if (managedResources) {
134-
managedResources.forEach((group: ManagedResourceGroup) => {
135-
group.items?.forEach((item: ManagedResourceItem) => {
136-
const id = item?.metadata?.name;
137-
const kind = item?.kind;
138-
const providerConfigName = item?.spec?.providerConfigRef?.name ?? 'unknown';
139-
const providerType = resolveProviderType(providerConfigName, providerConfigsList);
140-
const status = getStatusFromConditions(item?.status?.conditions);
141-
142-
const {
143-
subaccountRef,
144-
serviceManagerRef,
145-
spaceRef,
146-
orgRef,
147-
cloudManagementRef,
148-
directoryRef,
149-
entitlementRef,
150-
globalAccountRef,
151-
orgRoleRef,
152-
spaceMembersRef,
153-
cloudFoundryEnvironmentRef,
154-
kymaEnvironmentRef,
155-
roleCollectionRef,
156-
roleCollectionAssignmentRef,
157-
subaccountTrustConfigurationRef,
158-
globalaccountTrustConfigurationRef,
159-
} = extractRefs(item);
160-
161-
const parentId = serviceManagerRef || subaccountRef;
162-
const extraRefs = [
163-
spaceRef,
164-
orgRef,
165-
cloudManagementRef,
166-
directoryRef,
167-
entitlementRef,
168-
globalAccountRef,
169-
orgRoleRef,
170-
spaceMembersRef,
171-
cloudFoundryEnvironmentRef,
172-
kymaEnvironmentRef,
173-
roleCollectionRef,
174-
roleCollectionAssignmentRef,
175-
subaccountTrustConfigurationRef,
176-
globalaccountTrustConfigurationRef,
177-
].filter(Boolean) as string[];
178-
179-
if (id) {
180-
allNodesMap.set(id, {
181-
id,
182-
label: id,
183-
type: kind,
184-
providerConfigName,
185-
providerType,
186-
status,
187-
parentId,
188-
extraRefs,
189-
item,
190-
});
191-
}
192-
});
193-
});
194-
}
195-
return Array.from(allNodesMap.values());
196-
}, [managedResources, providerConfigsList]);
197-
198-
const colorMap = useMemo(() => generateColorMap(treeData, colorBy), [treeData, colorBy]);
19950
const legendItems: LegendItem[] = useMemo(
20051
() =>
20152
Object.entries(colorMap).map(([name, color]) => ({
@@ -205,22 +56,11 @@ const Graph: React.FC = () => {
20556
[colorMap],
20657
);
20758

208-
useEffect(() => {
209-
if (!treeData.length) return;
210-
const { nodes, edges } = buildGraph(treeData, colorBy, colorMap);
211-
setNodes(nodes);
212-
setEdges(edges);
213-
}, [treeData, colorBy, colorMap, setNodes, setEdges]);
214-
215-
if (managedResourcesError || providerConfigsError) {
216-
return <div className={`${styles.message} ${styles.errorMessage}`}>{t('Graphs.loadingError')}</div>;
217-
}
218-
219-
if (!managedResources || !providerConfigsList) {
59+
if (loading) {
22060
return <div className={styles.message}>{t('Graphs.loadingGraph')}</div>;
22161
}
22262

223-
if (treeData.length === 0) {
63+
if (!treeData.length) {
22464
return <div className={styles.message}>{t('Graphs.noResources')}</div>;
22565
}
22666

src/components/Graphs/useGraph.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { useMemo, useEffect, useState } from 'react';
2+
import { useApiResource, useProvidersConfigResource } from '../../lib/api/useApiResource';
3+
import { ManagedResourcesRequest } from '../../lib/api/types/crossplane/listManagedResources';
4+
import { resourcesInterval } from '../../lib/shared/constants';
5+
import { Node, Edge, Position, MarkerType } from '@xyflow/react';
6+
import dagre from 'dagre';
7+
import { NodeData, ManagedResourceGroup, ManagedResourceItem } from './types';
8+
import { extractRefs, generateColorMap, getStatusFromConditions, resolveProviderType } from './graphUtils';
9+
10+
const nodeWidth = 250;
11+
const nodeHeight = 60;
12+
13+
function buildGraph(
14+
treeData: NodeData[],
15+
colorBy: 'provider' | 'source',
16+
colorMap: Record<string, string>,
17+
): { nodes: Node<NodeData>[]; edges: Edge[] } {
18+
const dagreGraph = new dagre.graphlib.Graph();
19+
dagreGraph.setDefaultEdgeLabel(() => ({}));
20+
dagreGraph.setGraph({ rankdir: 'TB' });
21+
22+
const nodeMap: Record<string, Node<NodeData>> = {};
23+
treeData.forEach((n) => {
24+
const colorKey = colorBy === 'source' ? n.providerType : n.providerConfigName;
25+
const node: Node<NodeData> = {
26+
id: n.id,
27+
type: 'custom',
28+
data: { ...n },
29+
style: {
30+
border: `2px solid ${colorMap[colorKey] || '#ccc'}`,
31+
borderRadius: 8,
32+
backgroundColor: '#fff',
33+
width: nodeWidth,
34+
height: nodeHeight,
35+
},
36+
width: nodeWidth,
37+
height: nodeHeight,
38+
position: { x: 0, y: 0 },
39+
sourcePosition: Position.Bottom,
40+
targetPosition: Position.Top,
41+
};
42+
nodeMap[n.id] = node;
43+
dagreGraph.setNode(n.id, { width: nodeWidth, height: nodeHeight });
44+
});
45+
46+
const edgeList: Edge[] = [];
47+
treeData.forEach((n) => {
48+
if (n.parentId) {
49+
dagreGraph.setEdge(n.parentId, n.id);
50+
edgeList.push({
51+
id: `e-${n.parentId}-${n.id}`,
52+
source: n.parentId,
53+
target: n.id,
54+
markerEnd: { type: MarkerType.ArrowClosed },
55+
});
56+
}
57+
n.extraRefs?.forEach((refId) => {
58+
if (nodeMap[refId]) {
59+
dagreGraph.setEdge(refId, n.id);
60+
edgeList.push({
61+
id: `e-${refId}-${n.id}`,
62+
source: refId,
63+
target: n.id,
64+
markerEnd: { type: MarkerType.ArrowClosed },
65+
});
66+
}
67+
});
68+
});
69+
70+
dagre.layout(dagreGraph);
71+
Object.values(nodeMap).forEach((node) => {
72+
const pos = dagreGraph.node(node.id);
73+
node.position = { x: pos.x - nodeWidth / 2, y: pos.y - nodeHeight / 2 };
74+
});
75+
76+
return { nodes: Object.values(nodeMap), edges: edgeList };
77+
}
78+
79+
export function useGraph(colorBy: 'provider' | 'source') {
80+
const { data: managedResources, isLoading: managedResourcesLoading } = useApiResource(ManagedResourcesRequest, {
81+
refreshInterval: resourcesInterval,
82+
});
83+
const { data: providerConfigsList, isLoading: providerConfigsLoading } = useProvidersConfigResource({
84+
refreshInterval: resourcesInterval,
85+
});
86+
87+
const loading = managedResourcesLoading || providerConfigsLoading;
88+
89+
const treeData = useMemo(() => {
90+
if (!managedResources || !providerConfigsList) return [];
91+
const allNodesMap = new Map<string, NodeData>();
92+
managedResources.forEach((group: ManagedResourceGroup) => {
93+
group.items?.forEach((item: ManagedResourceItem) => {
94+
const id = item?.metadata?.name;
95+
const kind = item?.kind;
96+
const providerConfigName = item?.spec?.providerConfigRef?.name ?? 'unknown';
97+
const providerType = resolveProviderType(providerConfigName, providerConfigsList);
98+
const status = getStatusFromConditions(item?.status?.conditions);
99+
100+
const {
101+
subaccountRef,
102+
serviceManagerRef,
103+
spaceRef,
104+
orgRef,
105+
cloudManagementRef,
106+
directoryRef,
107+
entitlementRef,
108+
globalAccountRef,
109+
orgRoleRef,
110+
spaceMembersRef,
111+
cloudFoundryEnvironmentRef,
112+
kymaEnvironmentRef,
113+
roleCollectionRef,
114+
roleCollectionAssignmentRef,
115+
subaccountTrustConfigurationRef,
116+
globalaccountTrustConfigurationRef,
117+
} = extractRefs(item);
118+
119+
const parentId = serviceManagerRef || subaccountRef;
120+
const extraRefs = [
121+
spaceRef,
122+
orgRef,
123+
cloudManagementRef,
124+
directoryRef,
125+
entitlementRef,
126+
globalAccountRef,
127+
orgRoleRef,
128+
spaceMembersRef,
129+
cloudFoundryEnvironmentRef,
130+
kymaEnvironmentRef,
131+
roleCollectionRef,
132+
roleCollectionAssignmentRef,
133+
subaccountTrustConfigurationRef,
134+
globalaccountTrustConfigurationRef,
135+
].filter(Boolean) as string[];
136+
137+
if (id) {
138+
allNodesMap.set(id, {
139+
id,
140+
label: id,
141+
type: kind,
142+
providerConfigName,
143+
providerType,
144+
status,
145+
parentId,
146+
extraRefs,
147+
item,
148+
});
149+
}
150+
});
151+
});
152+
return Array.from(allNodesMap.values());
153+
}, [managedResources, providerConfigsList]);
154+
155+
const colorMap = useMemo(() => generateColorMap(treeData, colorBy), [treeData, colorBy]);
156+
157+
const [nodes, setNodes] = useState<Node<NodeData>[]>([]);
158+
const [edges, setEdges] = useState<Edge[]>([]);
159+
160+
useEffect(() => {
161+
if (!treeData.length) {
162+
setNodes([]);
163+
setEdges([]);
164+
return;
165+
}
166+
const { nodes, edges } = buildGraph(treeData, colorBy, colorMap);
167+
setNodes(nodes);
168+
setEdges(edges);
169+
}, [treeData, colorBy, colorMap]);
170+
171+
return { nodes, edges, colorMap, treeData, loading };
172+
}

0 commit comments

Comments
 (0)