Skip to content

Commit 1e727aa

Browse files
authored
feat(kiali): add card for resources (janus-idp#1565)
* Add entity resources card * Update details * Add drawers * lint * Update styles. Add context * Update Renderer * Update links for drawer view * Update routes * Update merged file * lint * Update drawer * Add tests * Add test * Tests * Update tests * Update test * Revert await
1 parent 981c7c9 commit 1e727aa

25 files changed

+880
-126
lines changed

plugins/kiali/dev/index.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { TestApiProvider } from '@backstage/test-utils';
1414

1515
import { Grid } from '@material-ui/core';
1616

17-
import { kialiPlugin } from '../src';
17+
import { EntityKialiResourcesCard, kialiPlugin } from '../src';
1818
import { getEntityRoutes, getRoutes } from '../src/components/Router';
1919
import { KialiHeader } from '../src/pages/Kiali/Header/KialiHeader';
2020
import { KialiHelper } from '../src/pages/Kiali/KialiHelper';
@@ -530,6 +530,30 @@ const MockProvider = (props: Props) => {
530530
);
531531
};
532532

533+
const MockEntityCard = () => {
534+
const content = (
535+
<EntityProvider entity={mockEntity}>
536+
<BrowserRouter>
537+
<div style={{ padding: '20px' }}>
538+
<TestApiProvider apis={[[kialiApiRef, new MockKialiClient()]]}>
539+
<Grid container spacing={3} alignItems="stretch">
540+
<Grid item md={8} xs={12}>
541+
<EntityKialiResourcesCard />
542+
</Grid>
543+
</Grid>
544+
</TestApiProvider>
545+
</div>
546+
</BrowserRouter>
547+
</EntityProvider>
548+
);
549+
550+
return (
551+
<TestApiProvider apis={[[kialiApiRef, new MockKialiClient()]]}>
552+
{content}
553+
</TestApiProvider>
554+
);
555+
};
556+
533557
const MockKialiError = () => {
534558
const errorsTypes: KialiChecker[] = [
535559
{
@@ -619,4 +643,9 @@ createDevApp()
619643
title: 'No Annotation',
620644
path: '/no-annotation',
621645
})
646+
.addPage({
647+
element: <MockEntityCard />,
648+
title: 'Resources card',
649+
path: '/kiali-entity-card',
650+
})
622651
.render();

plugins/kiali/src/components/DetailDescription/DetailDescription.tsx

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import { Link } from 'react-router-dom';
23

34
import { Tooltip } from '@material-ui/core';
45

@@ -10,6 +11,7 @@ import { kialiStyle } from '../../styles/StyleUtils';
1011
import { AppWorkload } from '../../types/App';
1112
import * as H from '../../types/Health';
1213
import { HealthSubItem } from '../../types/Health';
14+
import { DRAWER } from '../../types/types';
1315
import { Workload } from '../../types/Workload';
1416
import { JanusObjectLink } from '../../utils/janusLinks';
1517
import { renderTrafficStatus } from '../Health/HealthDetails';
@@ -25,6 +27,7 @@ type Props = {
2527
services?: string[];
2628
waypointWorkloads?: Workload[];
2729
workloads?: AppWorkload[];
30+
view?: string;
2831
};
2932

3033
const iconStyle = kialiStyle({
@@ -74,7 +77,9 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
7477
namespace: string,
7578
appName: string,
7679
): React.ReactNode => {
77-
const link = (
80+
let link: React.ReactNode;
81+
82+
link = (
7883
<JanusObjectLink
7984
entity={props.entity}
8085
namespace={namespace}
@@ -88,6 +93,18 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
8893
</JanusObjectLink>
8994
);
9095

96+
let href = `/namespaces/${namespace}/applications/${appName}`;
97+
98+
if (props.cluster && isMultiCluster) {
99+
href = `${href}?clusterName=${props.cluster}`;
100+
}
101+
102+
if (props.view === DRAWER) {
103+
href = `#application/${namespace}_${appName}`;
104+
105+
link = <Link to={href}>{appName}</Link>;
106+
}
107+
91108
return (
92109
<li key={`App_${namespace}_${appName}`} className={itemStyle}>
93110
<div className={iconStyle}>
@@ -103,7 +120,9 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
103120
namespace: string,
104121
serviceName: string,
105122
): React.ReactNode => {
106-
const link = (
123+
let link: React.ReactNode;
124+
125+
link = (
107126
<JanusObjectLink
108127
entity={props.entity}
109128
namespace={namespace}
@@ -117,6 +136,17 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
117136
</JanusObjectLink>
118137
);
119138

139+
if (props.view === DRAWER) {
140+
let href = `/namespaces/${namespace}/services/${serviceName}`;
141+
142+
if (props.cluster && isMultiCluster) {
143+
href = `${href}?clusterName=${props.cluster}`;
144+
}
145+
146+
href = `#service/${namespace}_${serviceName}`;
147+
link = <Link to={href}>{serviceName}</Link>;
148+
}
149+
120150
return (
121151
<li key={`Service_${serviceName}`} className={itemStyle}>
122152
<div className={iconStyle}>
@@ -184,7 +214,9 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
184214
};
185215

186216
const renderWorkloadItem = (workload: AppWorkload): React.ReactNode => {
187-
const link = (
217+
let link: React.ReactNode;
218+
219+
link = (
188220
<JanusObjectLink
189221
entity={props.entity}
190222
namespace={props.namespace}
@@ -197,6 +229,18 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
197229
{workload.workloadName}
198230
</JanusObjectLink>
199231
);
232+
233+
if (props.view === DRAWER) {
234+
let href = `/namespaces/${props.namespace}/workloads/${workload.workloadName}`;
235+
236+
if (props.cluster && isMultiCluster) {
237+
href = `${href}?clusterName=${props.cluster}`;
238+
}
239+
240+
href = `#workload/${props.namespace}_${workload.workloadName}`;
241+
link = <Link to={href}>{workload.workloadName}</Link>;
242+
}
243+
200244
return (
201245
<span key={`WorkloadItem_${workload.workloadName}`}>
202246
<div className={iconStyle}>
@@ -237,21 +281,34 @@ export const DetailDescription: React.FC<Props> = (props: Props) => {
237281
}
238282

239283
if (workload) {
240-
const link = (
241-
<JanusObjectLink
242-
entity={props.entity}
243-
namespace={props.namespace}
244-
type="workloads"
245-
query={
246-
props.cluster && isMultiCluster
247-
? `clusterName=${props.cluster}`
248-
: ''
249-
}
250-
name={workload.workloadName}
251-
>
252-
{workload.workloadName}
253-
</JanusObjectLink>
254-
);
284+
let link: React.ReactNode;
285+
286+
if (props.view === DRAWER) {
287+
let href = `/namespaces/${props.namespace}/workloads/${workload.workloadName}`;
288+
289+
if (props.cluster && isMultiCluster) {
290+
href = `${href}?clusterName=${props.cluster}`;
291+
}
292+
293+
href = `#workload/${props.namespace}_${workload.workloadName}`;
294+
link = <Link to={href}>{workload.workloadName}</Link>;
295+
} else {
296+
link = (
297+
<JanusObjectLink
298+
entity={props.entity}
299+
namespace={props.namespace}
300+
type="workloads"
301+
query={
302+
props.cluster && isMultiCluster
303+
? `clusterName=${props.cluster}`
304+
: ''
305+
}
306+
name={workload.workloadName}
307+
>
308+
{workload.workloadName}
309+
</JanusObjectLink>
310+
);
311+
}
255312

256313
return (
257314
<span key={`WorkloadItem_${workload.workloadName}`}>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as React from 'react';
2+
import { useAsyncFn, useDebounce } from 'react-use';
3+
4+
import { useApi } from '@backstage/core-plugin-api';
5+
6+
import { CircularProgress } from '@material-ui/core';
7+
import { AxiosError } from 'axios';
8+
9+
import { HistoryManager } from '../../app/History';
10+
import { AppInfo } from '../../pages/AppDetails/AppInfo';
11+
import { kialiApiRef } from '../../services/Api';
12+
import { App, AppQuery } from '../../types/App';
13+
import { AppHealth } from '../../types/Health';
14+
import { DRAWER } from '../../types/types';
15+
16+
type Props = {
17+
namespace: string;
18+
app: string;
19+
};
20+
export const AppDetailsDrawer = (props: Props) => {
21+
const kialiClient = useApi(kialiApiRef);
22+
const [appItem, setAppItem] = React.useState<App>();
23+
const [health, setHealth] = React.useState<AppHealth>();
24+
const cluster = HistoryManager.getClusterName();
25+
26+
const fetchApp = async () => {
27+
const params: AppQuery = {
28+
rateInterval: `60s`,
29+
health: 'true',
30+
};
31+
32+
kialiClient
33+
.getApp(props.namespace, props.app, params, cluster)
34+
.then((appResponse: App) => {
35+
const healthR = AppHealth.fromJson(
36+
props.namespace,
37+
props.app,
38+
appResponse.health,
39+
{
40+
rateInterval: 60,
41+
hasSidecar: appResponse.workloads.some(w => w.istioSidecar),
42+
hasAmbient: appResponse.workloads.some(w => w.istioAmbient),
43+
},
44+
);
45+
setAppItem(appResponse);
46+
setHealth(healthR);
47+
})
48+
.catch((err: AxiosError<unknown, any>) => {
49+
// eslint-disable-next-line no-console
50+
console.log(err);
51+
});
52+
};
53+
54+
const [{ loading }, refresh] = useAsyncFn(
55+
async () => {
56+
// Check if the config is loaded
57+
await fetchApp();
58+
},
59+
[],
60+
{ loading: true },
61+
);
62+
useDebounce(refresh, 10);
63+
64+
if (loading) {
65+
return <CircularProgress />;
66+
}
67+
68+
return (
69+
<>
70+
{appItem && (
71+
<AppInfo app={appItem} duration={60} health={health} view={DRAWER} />
72+
)}
73+
</>
74+
);
75+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import * as React from 'react';
2+
import { useAsyncFn, useDebounce } from 'react-use';
3+
4+
import { useApi } from '@backstage/core-plugin-api';
5+
6+
import { CircularProgress } from '@material-ui/core';
7+
8+
import { HistoryManager } from '../../app/History';
9+
import { ServiceInfo } from '../../pages/ServiceDetails/ServiceInfo';
10+
import { kialiApiRef } from '../../services/Api';
11+
import { Validations } from '../../types/IstioObjects';
12+
import { ServiceDetailsInfo } from '../../types/ServiceInfo';
13+
import { DRAWER } from '../../types/types';
14+
15+
type Props = {
16+
namespace: string;
17+
service: string;
18+
};
19+
export const ServiceDetailsDrawer = (props: Props) => {
20+
const kialiClient = useApi(kialiApiRef);
21+
const [serviceItem, setServiceItem] = React.useState<ServiceDetailsInfo>();
22+
const cluster = HistoryManager.getClusterName();
23+
const [validations, setValidations] = React.useState<Validations>({});
24+
25+
const fetchService = async () => {
26+
kialiClient
27+
.getServiceDetail(props.namespace, props.service, true, cluster, 60)
28+
.then((serviceResponse: ServiceDetailsInfo) => {
29+
setServiceItem(serviceResponse);
30+
setValidations(serviceResponse.validations);
31+
})
32+
.catch(err => {
33+
// eslint-disable-next-line no-console
34+
console.log(err);
35+
});
36+
};
37+
38+
const [{ loading }, refresh] = useAsyncFn(
39+
async () => {
40+
// Check if the config is loaded
41+
await fetchService();
42+
},
43+
[],
44+
{ loading: true },
45+
);
46+
useDebounce(refresh, 10);
47+
48+
if (loading) {
49+
return <CircularProgress />;
50+
}
51+
52+
return (
53+
<>
54+
{serviceItem && (
55+
<ServiceInfo
56+
service={props.service}
57+
duration={60}
58+
namespace={props.namespace}
59+
validations={validations}
60+
cluster={cluster}
61+
serviceDetails={serviceItem}
62+
gateways={[]}
63+
k8sGateways={[]}
64+
peerAuthentications={[]}
65+
istioAPIEnabled
66+
view={DRAWER}
67+
/>
68+
)}
69+
</>
70+
);
71+
};

0 commit comments

Comments
 (0)