Skip to content

Commit f34d580

Browse files
authored
Replace Explore Links with Drawer (#52)
Instead of linking to the explore view, we now show the resources, linked in the details, within a drawer. This has the advantage that the user does not lose context of the current view and that we can render the actions for the opened resource directly in the drawer. This commit also fixes the query for events and Helm releases.
1 parent cd50fd3 commit f34d580

File tree

6 files changed

+231
-116
lines changed

6 files changed

+231
-116
lines changed

src/datasource/components/kubernetes/overview/Pod.tsx

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { ReactNode } from 'react';
1+
import React, { ReactNode, useMemo, useState } from 'react';
22
import {
33
V1ContainerPort,
44
V1ContainerState,
@@ -8,12 +8,13 @@ import {
88
V1Pod,
99
V1Probe,
1010
} from '@kubernetes/client-node';
11-
import { Badge, Box, InteractiveTable, Stack, TextLink } from '@grafana/ui';
11+
import { Badge, Box, InteractiveTable, Stack } from '@grafana/ui';
1212

1313
import {
1414
DefinitionList,
1515
DefinitionItem,
1616
} from '../../shared/definitionlist/DefinitionList';
17+
import { Resources } from '../../shared/details/Resources';
1718

1819
interface Props {
1920
datasource?: string;
@@ -22,40 +23,46 @@ interface Props {
2223
}
2324

2425
export function Pod({ datasource, namespace, manifest }: Props) {
25-
const phase =
26-
manifest.status && manifest.status.phase
27-
? manifest.status.phase
28-
: 'Unknown';
29-
let reason =
30-
manifest.status && manifest.status.reason ? manifest.status.reason : '';
31-
let shouldReady = 0;
32-
let isReady = 0;
33-
let restarts = 0;
26+
const [selectedNode, setSelectedNode] = useState<string>('');
3427

35-
if (manifest.status && manifest.status.containerStatuses) {
36-
for (const container of manifest.status.containerStatuses) {
37-
shouldReady = shouldReady + 1;
38-
if (container.ready) {
39-
isReady = isReady + 1;
40-
}
28+
const { phase, reason, isReady, shouldReady, restarts } = useMemo(() => {
29+
const phase =
30+
manifest.status && manifest.status.phase
31+
? manifest.status.phase
32+
: 'Unknown';
33+
let reason =
34+
manifest.status && manifest.status.reason ? manifest.status.reason : '';
35+
let shouldReady = 0;
36+
let isReady = 0;
37+
let restarts = 0;
4138

42-
restarts = restarts + container.restartCount;
39+
if (manifest.status && manifest.status.containerStatuses) {
40+
for (const container of manifest.status.containerStatuses) {
41+
shouldReady = shouldReady + 1;
42+
if (container.ready) {
43+
isReady = isReady + 1;
44+
}
4345

44-
if (container.state && container.state.waiting) {
45-
reason = container.state.waiting.reason
46-
? container.state.waiting.reason
47-
: '';
48-
break;
49-
}
46+
restarts = restarts + container.restartCount;
5047

51-
if (container.state && container.state.terminated) {
52-
reason = container.state.terminated.reason
53-
? container.state.terminated.reason
54-
: '';
55-
break;
48+
if (container.state && container.state.waiting) {
49+
reason = container.state.waiting.reason
50+
? container.state.waiting.reason
51+
: '';
52+
break;
53+
}
54+
55+
if (container.state && container.state.terminated) {
56+
reason = container.state.terminated.reason
57+
? container.state.terminated.reason
58+
: '';
59+
break;
60+
}
5661
}
5762
}
58-
}
63+
64+
return { phase, reason, isReady, shouldReady, restarts };
65+
}, [manifest]);
5966

6067
return (
6168
<>
@@ -79,17 +86,27 @@ export function Pod({ datasource, namespace, manifest }: Props) {
7986
</DefinitionItem>
8087
<DefinitionItem label="Node">
8188
{manifest.spec?.nodeName ? (
82-
<TextLink
83-
href={`/explore?left=${encodeURIComponent(JSON.stringify({ datasource: datasource, queries: [{ queryType: 'kubernetes-resources', namespace: namespace, resourceId: 'node', parameterName: 'fieldSelector', parameterValue: `metadata.name=${manifest.spec.nodeName}`, wide: false, refId: 'A' }] }))}`}
84-
color="secondary"
85-
variant="body"
86-
>
87-
{manifest.spec.nodeName}
88-
</TextLink>
89+
<Badge
90+
color="blue"
91+
onClick={() => setSelectedNode(manifest.spec!.nodeName!)}
92+
text={manifest.spec.nodeName}
93+
/>
8994
) : (
9095
'-'
9196
)}
9297
</DefinitionItem>
98+
99+
{selectedNode && (
100+
<Resources
101+
title="Node"
102+
datasource={datasource}
103+
resourceId="node"
104+
namespace={namespace}
105+
parameterName="fieldSelector"
106+
parameterValue={`metadata.name=${selectedNode}`}
107+
onClose={() => setSelectedNode('')}
108+
/>
109+
)}
93110
</DefinitionList>
94111

95112
<DefinitionList title="Containers">

src/datasource/components/shared/details/Events.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function Events({ datasource, namespace, name }: Props) {
2525
{
2626
refId: 'A',
2727
queryType: 'kubernetes-resources',
28-
resource: 'events',
28+
resourceId: 'events',
2929
namespace: namespace || '*',
3030
parameterName: 'fieldSelector',
3131
parameterValue: `involvedObject.name=${name}`,
Lines changed: 76 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import React from 'react';
2-
import { Badge, TextLink } from '@grafana/ui';
1+
import React, { useState } from 'react';
2+
import { Badge } from '@grafana/ui';
3+
import { V1OwnerReference } from '@kubernetes/client-node';
34

45
import {
56
DefinitionItem,
67
DefinitionList,
78
} from '../definitionlist/DefinitionList';
89
import { formatTimeString, timeDifference } from '../../../../utils/utils.time';
910
import { KubernetesManifest } from '../../../types/kubernetes';
10-
import { getResourceId } from 'utils/utils.resource';
11+
import { getResourceId } from '../../../../utils/utils.resource';
12+
import { Resources } from './Resources';
1113

1214
interface Props {
1315
datasource?: string;
@@ -17,62 +19,78 @@ interface Props {
1719
}
1820

1921
export function Metadata({ datasource, namespace, name, manifest }: Props) {
22+
const [selectedOwner, setSelectedOwner] = useState<
23+
V1OwnerReference | undefined
24+
>(undefined);
25+
2026
return (
21-
<DefinitionList title="Metadata">
22-
{name && <DefinitionItem label="Name">{name}</DefinitionItem>}
23-
{namespace && (
24-
<DefinitionItem label="Namespace">{namespace}</DefinitionItem>
25-
)}
26-
{manifest?.metadata?.labels && (
27-
<DefinitionItem label="Labels">
28-
{Object.keys(manifest?.metadata?.labels).map((key) => (
29-
<Badge
30-
key={key}
31-
color="darkgrey"
32-
text={`${key}: ${manifest?.metadata?.labels![key]}`}
33-
/>
34-
))}
35-
</DefinitionItem>
36-
)}
37-
{manifest?.metadata?.annotations && (
38-
<DefinitionItem label="Annotations">
39-
{Object.keys(manifest?.metadata?.annotations).map((key) => (
40-
<Badge
41-
key={key}
42-
color="darkgrey"
43-
text={`${key}: ${manifest?.metadata?.annotations![key]}`}
44-
/>
45-
))}
46-
</DefinitionItem>
47-
)}
48-
{manifest?.metadata?.creationTimestamp && (
49-
<DefinitionItem label="Age">
50-
{timeDifference(
51-
new Date().getTime(),
52-
new Date(manifest.metadata.creationTimestamp.toString()).getTime(),
53-
)}{' '}
54-
({formatTimeString(manifest.metadata.creationTimestamp.toString())})
55-
</DefinitionItem>
56-
)}
57-
{manifest?.metadata?.ownerReferences && (
58-
<DefinitionItem label="Owners">
59-
{manifest.metadata.ownerReferences.map((owner, index) => (
60-
<Badge
61-
key={index}
62-
color="darkgrey"
63-
text={
64-
<TextLink
65-
href={`/explore?left=${encodeURIComponent(JSON.stringify({ datasource: datasource, queries: [{ queryType: 'kubernetes-resources', namespace: namespace, resourceId: getResourceId(owner.kind, owner.apiVersion), parameterName: 'fieldSelector', parameterValue: `metadata.name=${owner.name}`, wide: false, refId: 'A' }] }))}`}
66-
color="secondary"
67-
variant="bodySmall"
68-
>
69-
{owner.kind}: {owner.name}
70-
</TextLink>
71-
}
72-
/>
73-
))}
74-
</DefinitionItem>
27+
<>
28+
<DefinitionList title="Metadata">
29+
{name && <DefinitionItem label="Name">{name}</DefinitionItem>}
30+
{namespace && (
31+
<DefinitionItem label="Namespace">{namespace}</DefinitionItem>
32+
)}
33+
{manifest?.metadata?.labels && (
34+
<DefinitionItem label="Labels">
35+
{Object.keys(manifest?.metadata?.labels).map((key) => (
36+
<Badge
37+
key={key}
38+
color="darkgrey"
39+
text={`${key}: ${manifest?.metadata?.labels![key]}`}
40+
/>
41+
))}
42+
</DefinitionItem>
43+
)}
44+
{manifest?.metadata?.annotations && (
45+
<DefinitionItem label="Annotations">
46+
{Object.keys(manifest?.metadata?.annotations).map((key) => (
47+
<Badge
48+
key={key}
49+
color="darkgrey"
50+
text={`${key}: ${manifest?.metadata?.annotations![key]}`}
51+
/>
52+
))}
53+
</DefinitionItem>
54+
)}
55+
{manifest?.metadata?.creationTimestamp && (
56+
<DefinitionItem label="Age">
57+
{timeDifference(
58+
new Date().getTime(),
59+
new Date(
60+
manifest.metadata.creationTimestamp.toString(),
61+
).getTime(),
62+
)}{' '}
63+
({formatTimeString(manifest.metadata.creationTimestamp.toString())})
64+
</DefinitionItem>
65+
)}
66+
{manifest?.metadata?.ownerReferences && (
67+
<DefinitionItem label="Owners">
68+
{manifest.metadata.ownerReferences.map((owner, index) => (
69+
<Badge
70+
key={index}
71+
color="blue"
72+
onClick={() => setSelectedOwner(owner)}
73+
text={`${owner.kind}: ${owner.name}`}
74+
/>
75+
))}
76+
</DefinitionItem>
77+
)}
78+
</DefinitionList>
79+
80+
{selectedOwner && (
81+
<Resources
82+
title={selectedOwner.kind}
83+
datasource={datasource}
84+
resourceId={getResourceId(
85+
selectedOwner.kind,
86+
selectedOwner.apiVersion,
87+
)}
88+
namespace={namespace}
89+
parameterName="fieldSelector"
90+
parameterValue={`metadata.name=${selectedOwner.name}`}
91+
onClose={() => setSelectedOwner(undefined)}
92+
/>
7593
)}
76-
</DefinitionList>
94+
</>
7795
);
7896
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import { Drawer } from '@grafana/ui';
3+
import {
4+
EmbeddedScene,
5+
PanelBuilders,
6+
SceneFlexItem,
7+
SceneFlexLayout,
8+
SceneQueryRunner,
9+
} from '@grafana/scenes';
10+
11+
import datasourcePluginJson from '../../../plugin.json';
12+
13+
interface Props {
14+
title: string;
15+
datasource?: string;
16+
resourceId?: string;
17+
namespace?: string;
18+
parameterName?: string;
19+
parameterValue?: string;
20+
onClose: () => void;
21+
}
22+
23+
export function Resources({
24+
title,
25+
datasource,
26+
resourceId,
27+
namespace,
28+
parameterName,
29+
parameterValue,
30+
onClose,
31+
}: Props) {
32+
console.log(datasource, resourceId, namespace, parameterName, parameterValue);
33+
34+
const queryRunner = new SceneQueryRunner({
35+
datasource: {
36+
type: datasourcePluginJson.id,
37+
uid: datasource || undefined,
38+
},
39+
queries: [
40+
{
41+
refId: 'A',
42+
queryType: 'kubernetes-resources',
43+
resourceId: resourceId,
44+
namespace: namespace || '*',
45+
parameterName: parameterName || '',
46+
parameterValue: parameterValue || '',
47+
},
48+
],
49+
});
50+
51+
const scene = new EmbeddedScene({
52+
$data: queryRunner,
53+
body: new SceneFlexLayout({
54+
children: [
55+
new SceneFlexItem({
56+
body: PanelBuilders.table().setTitle(title).build(),
57+
}),
58+
],
59+
}),
60+
});
61+
62+
return (
63+
<Drawer title={title} scrollableContent={false} onClose={() => onClose()}>
64+
<scene.Component model={scene} />
65+
</Drawer>
66+
);
67+
}

0 commit comments

Comments
 (0)