Skip to content

Commit 396515d

Browse files
authored
[Kiali]Fix graph load and detail links (#5273)
1 parent 725fba6 commit 396515d

File tree

4 files changed

+159
-19
lines changed

4 files changed

+159
-19
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@backstage-community/plugin-kiali-react': patch
3+
'@backstage-community/plugin-kiali': patch
4+
---
5+
6+
Fix for graph loading issue – it was not rendering when the page was first displayed, only after a refresh.
7+
Added detail pages as a drawer that opens when clicking on each workload, service, or application link.

workspaces/kiali/plugins/kiali-react/src/components/TrafficGraph/TrafficGraph.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,29 @@ const getVisualization = (): Visualization => {
4040

4141
export const TrafficGraph = (props: { model: Model }) => {
4242
const [controller] = useState(getVisualization());
43+
const [renderKey, setRenderKey] = useState(0);
44+
4345
useEffect(() => {
44-
controller.fromModel(props.model, false);
46+
if (
47+
(props.model.nodes && props.model.nodes.length > 0) ||
48+
(props.model.edges && props.model.edges.length > 0)
49+
) {
50+
// Force layout when updating the model
51+
controller.fromModel(props.model, true);
52+
// Auto-fit the graph to screen after loading
53+
setTimeout(() => {
54+
if (controller.getGraph()) {
55+
controller.getGraph().fit(80);
56+
}
57+
}, 50);
58+
// Force re-render by updating the key
59+
setRenderKey(prev => prev + 1);
60+
}
4561
}, [props.model, controller]);
4662

4763
return (
4864
<TopologyView
65+
key={renderKey}
4966
controlBar={
5067
<TopologyControlBar
5168
controlButtons={createTopologyControlButtons({

workspaces/kiali/plugins/kiali/src/components/VirtualList/Renderers.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export const item: Renderer<TResource> = (
201201
}
202202
key={key}
203203
className={linkColor}
204+
useDrawer
204205
>
205206
{resource.name}
206207
</BackstageObjectLink>

workspaces/kiali/plugins/kiali/src/utils/backstageLinks.tsx

Lines changed: 133 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
import { GroupVersionKind } from '@backstage-community/plugin-kiali-common/types';
1717
import { Link } from '@backstage/core-components';
1818
import { SubRouteRef } from '@backstage/core-plugin-api';
19+
import { Drawer, IconButton } from '@material-ui/core';
20+
import Close from '@material-ui/icons/Close';
1921
import { default as React } from 'react';
22+
import { AppDetailsDrawer } from '../components/Drawers/AppDetailsDrawer';
23+
import { ServiceDetailsDrawer } from '../components/Drawers/ServiceDetailsDrawer';
24+
import { WorkloadDetailsDrawer } from '../components/Drawers/WorkloadDetailsDrawer';
2025
import { isMultiCluster } from '../config';
2126
import {
2227
appDetailRouteRef,
@@ -68,6 +73,7 @@ interface BackstageLinkProps {
6873
query?: string;
6974
children?: React.ReactNode;
7075
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
76+
useDrawer?: boolean; // New prop to control drawer behavior
7177
}
7278

7379
/* const getRef = (type: string, entity?: boolean, root?: boolean) => {
@@ -82,29 +88,138 @@ interface BackstageLinkProps {
8288
return backstageRoutesObject[type].ref;
8389
};*/
8490

91+
// Component for drawer content
92+
const DrawerContent = ({
93+
toggleDrawer,
94+
type,
95+
name,
96+
namespace,
97+
}: {
98+
toggleDrawer: (isOpen: boolean) => void;
99+
type: string;
100+
name: string;
101+
namespace: string;
102+
}) => {
103+
return (
104+
<div style={{ padding: '10px', minWidth: '400px' }} data-test="drawer">
105+
<div style={{ paddingBottom: '30px' }}>
106+
<IconButton
107+
key="dismiss"
108+
id="close_drawer"
109+
title="Close the drawer"
110+
onClick={() => toggleDrawer(false)}
111+
color="inherit"
112+
style={{ right: '0', position: 'absolute', top: '5px' }}
113+
>
114+
<Close />
115+
</IconButton>
116+
</div>
117+
<div />
118+
<div>
119+
{type === 'workloads' && (
120+
<WorkloadDetailsDrawer namespace={namespace} workload={name} />
121+
)}
122+
{type === 'services' && (
123+
<ServiceDetailsDrawer namespace={namespace} service={name} />
124+
)}
125+
{type === 'applications' && (
126+
<AppDetailsDrawer namespace={namespace} app={name} />
127+
)}
128+
</div>
129+
</div>
130+
);
131+
};
132+
85133
export const BackstageObjectLink = (props: BackstageLinkProps) => {
86-
const { name, type, query, cluster } = props;
87-
/* const link: RouteFunc<routeRefParams> = useRouteRef(
88-
getRef(type, props.entity, props.root),
89-
);*/
90-
const to = '';
91-
/*
92-
if (!props.root) {
93-
const items: { [key: string]: string } = { namespace: '' };
134+
const { name, type, query, cluster, namespace, objectGVK, useDrawer } = props;
135+
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
94136

95-
if (type && name) {
96-
items[backstageRoutesObject[type].id] = name;
97-
}
98-
if (objectType) {
99-
items.objectType = objectType;
137+
// If useDrawer is true and we have the required props, show drawer behavior
138+
if (useDrawer && name && namespace && type && !props.root) {
139+
return (
140+
<>
141+
<Link
142+
to="#"
143+
component="button"
144+
onClick={e => {
145+
e.preventDefault();
146+
setIsDrawerOpen(true);
147+
}}
148+
data-test={`${
149+
type ? backstageRoutesObject[type].id : ''
150+
}-namespace-${name}`}
151+
style={{
152+
textDecoration: 'underline',
153+
color: 'inherit',
154+
background: 'none',
155+
border: 'none',
156+
cursor: 'pointer',
157+
font: 'inherit',
158+
}}
159+
>
160+
{props.children || name}
161+
</Link>
162+
<Drawer
163+
anchor="right"
164+
open={isDrawerOpen}
165+
onClose={() => setIsDrawerOpen(false)}
166+
>
167+
<DrawerContent
168+
toggleDrawer={setIsDrawerOpen}
169+
type={type}
170+
name={name}
171+
namespace={namespace}
172+
/>
173+
</Drawer>
174+
</>
175+
);
176+
}
177+
178+
// Original link behavior
179+
let to = '';
180+
181+
if (!props.root) {
182+
if (type && name && namespace) {
183+
// Generate detail page URLs
184+
switch (type) {
185+
case 'workloads':
186+
to = `/workloads/${namespace}/${name}`;
187+
break;
188+
case 'services':
189+
to = `/services/${namespace}/${name}`;
190+
break;
191+
case 'applications':
192+
to = `/applications/${namespace}/${name}`;
193+
break;
194+
case 'istio':
195+
if (objectGVK) {
196+
to = `/istio/${namespace}/${objectGVK.Kind}/${name}`;
197+
}
198+
break;
199+
default:
200+
to = '';
201+
}
100202
}
101-
// link(items)
102-
to = link();
103203
} else {
104-
to = link();
204+
// Generate section URLs
205+
switch (type) {
206+
case 'workloads':
207+
to = '/workloads';
208+
break;
209+
case 'services':
210+
to = '/services';
211+
break;
212+
case 'applications':
213+
to = '/applications';
214+
break;
215+
case 'istio':
216+
to = '/istio';
217+
break;
218+
default:
219+
to = '';
220+
}
105221
}
106-
console.log(to)
107-
*/
222+
108223
const href = addQuery(to, cluster, query);
109224
return (
110225
<Link

0 commit comments

Comments
 (0)