Skip to content

Commit c840f7b

Browse files
VikaCepckbedwell
andauthored
feat: migrate HTTP dashboard to react-scenes (#1079)
* feat: test scenes react * fix: revert summary dashboard I will be migrating HTTP scenes dashboard instead * feat: migrate HTTP dashboard to scenes react * fix: add annotations and edit check button * fix: dashboard layout * feat: override timepicker in ErrorRate panel * fix: add timeseries info at panel level This is used to demo how to use useTimeRange. Queries can be set depending on this value * feat: add panel menu * fix: edit button now takes only id as parameter * fix: update yarn.lock after merging with main * fix: typing errors after upgrading dependencies * fix: remove type castings * fix: mape error rate map not crash when loading * fix: match scenes and react-scenes version to fix ts errors * fix: remove old scenes code --------- Co-authored-by: Chris Bedwell <christopher.bedwell@grafana.com>
1 parent 5b41cad commit c840f7b

18 files changed

+1435
-194
lines changed

‎package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@
103103
"@grafana/data": "11.5.1",
104104
"@grafana/faro-web-sdk": "1.3.6",
105105
"@grafana/runtime": "11.5.1",
106-
"@grafana/scenes": "5.1.0",
106+
"@grafana/scenes": "5.42.0",
107+
"@grafana/scenes-react": "5.42.0",
107108
"@grafana/schema": "11.5.1",
108109
"@grafana/ui": "11.5.1",
109110
"@hookform/resolvers": "3.3.4",

‎src/page/DashboardPage.tsx

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { useSMDS } from 'hooks/useSMDS';
1414
import { getBrowserScene } from 'scenes/BROWSER/browserScene';
1515
import { getDNSScene } from 'scenes/DNS';
1616
import { getGRPCScene } from 'scenes/GRPC/getGRPCScene';
17-
import { getHTTPScene } from 'scenes/HTTP';
17+
import { HttpDashboard } from 'scenes/HTTP/HttpDashboard';
1818
import { getPingScene } from 'scenes/PING/pingScene';
1919
import { getScriptedScene } from 'scenes/SCRIPTED';
2020
import { getTcpScene } from 'scenes/TCP/getTcpScene';
@@ -64,17 +64,6 @@ function DashboardPageContent() {
6464
],
6565
});
6666
}
67-
case CheckType.HTTP: {
68-
return new SceneApp({
69-
pages: [
70-
new SceneAppPage({
71-
title: check.job,
72-
url,
73-
getScene: getHTTPScene(config, check),
74-
}),
75-
],
76-
});
77-
}
7867
case CheckType.Browser:
7968
return new SceneApp({
8069
pages: [
@@ -143,18 +132,26 @@ function DashboardPageContent() {
143132
],
144133
});
145134
}
135+
136+
case CheckType.HTTP: {
137+
return null;
138+
}
146139
}
147140
}, [smDS, metricsDS, logsDS, check]);
148141

149142
if (!isLoading && !check) {
150143
return <CheckNotFound />;
151144
}
152145

153-
if (!scene) {
154-
return <Spinner />;
146+
if (check && getCheckType(check.settings) === CheckType.HTTP) {
147+
return <HttpDashboard check={check} />;
148+
}
149+
150+
if (scene) {
151+
return <scene.Component model={scene} />;
155152
}
156153

157-
return <scene.Component model={scene} />;
154+
return <Spinner />;
158155
}
159156

160157
export function DashboardPage() {

‎src/scenes/Common/editButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface Props {
1111
id: Check['id'];
1212
}
1313

14-
function EditCheckButton({ id }: Props) {
14+
export function EditCheckButton({ id }: Props) {
1515
const { canWriteChecks } = getUserPermissions();
1616
const url = id ? `${generateRoutePath(ROUTES.EditCheck, { id })}` : undefined;
1717

‎src/scenes/HTTP/ErrorLogs.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
import { VizConfigBuilders } from '@grafana/scenes';
3+
import { useQueryRunner, useTimeRange, VizPanel } from '@grafana/scenes-react';
4+
import { LogsDedupStrategy, LogsSortOrder } from '@grafana/schema';
5+
6+
import { useLogsDS } from 'hooks/useLogsDS';
7+
8+
import { useVizPanelMenu } from './useVizPanelMenu';
9+
10+
export const ErrorLogs = () => {
11+
const logsDS = useLogsDS();
12+
13+
const dataProvider = useQueryRunner({
14+
queries: [
15+
{
16+
expr: '{probe=~"$probe", instance="$instance", job="$job", probe_success="0"}',
17+
refId: 'A',
18+
},
19+
],
20+
datasource: logsDS,
21+
});
22+
23+
const viz = VizConfigBuilders.logs()
24+
.setOption('showTime', true)
25+
.setOption('showLabels', true)
26+
.setOption('showCommonLabels', false)
27+
.setOption('wrapLogMessage', true)
28+
.setOption('prettifyLogMessage', false)
29+
.setOption('enableLogDetails', true)
30+
.setOption('dedupStrategy', LogsDedupStrategy.none)
31+
.setOption('sortOrder', LogsSortOrder.Descending)
32+
.build();
33+
34+
const data = dataProvider.useState();
35+
const [currentTimeRange] = useTimeRange();
36+
37+
const menu = useVizPanelMenu({
38+
data,
39+
viz,
40+
currentTimeRange,
41+
variables: ['job', 'probe', 'instance'],
42+
});
43+
44+
return (
45+
<VizPanel
46+
title="Logs for failed checks: $probe ⮕ $job / $instance"
47+
viz={viz}
48+
dataProvider={dataProvider}
49+
menu={menu}
50+
/>
51+
);
52+
};

‎src/scenes/HTTP/ErrorRateMap.tsx

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import React from 'react';
2+
import { LoadingState } from '@grafana/data';
3+
import { VizConfigBuilders } from '@grafana/scenes';
4+
import { useDataTransformer, useQueryRunner, useTimeRange, VizPanel } from '@grafana/scenes-react';
5+
import { FrameGeometrySourceMode, ThresholdsMode } from '@grafana/schema';
6+
import { LoadingPlaceholder } from '@grafana/ui';
7+
8+
import { useMetricsDS } from 'hooks/useMetricsDS';
9+
10+
import { useVizPanelMenu } from './useVizPanelMenu';
11+
12+
export const ErrorRateMap = ({ minStep }: { minStep: string }) => {
13+
const metricsDS = useMetricsDS();
14+
15+
function getErrorMapQueries(minStep: string) {
16+
return [
17+
{
18+
expr: `sum by (probe, geohash)
19+
(
20+
rate(probe_all_success_sum{instance="$instance", job="$job", probe=~"$probe"}[$__rate_interval])
21+
*
22+
on (instance, job, probe, config_version)
23+
group_left(geohash)
24+
max by (instance, job, probe, config_version, geohash)
25+
(
26+
sm_check_info{instance="$instance", job="$job"}
27+
)
28+
)`,
29+
format: 'table',
30+
interval: minStep,
31+
instant: false,
32+
legendFormat: 'numerator',
33+
refId: 'A',
34+
range: true,
35+
},
36+
{
37+
refId: 'B',
38+
expr: `sum by (probe, geohash)
39+
(
40+
rate(probe_all_success_count{instance="$instance", job="$job", probe=~"$probe"}[$__rate_interval])
41+
*
42+
on (instance, job, probe, config_version)
43+
group_left(geohash)
44+
max by (instance, job, probe, config_version, geohash)
45+
(
46+
sm_check_info{instance="$instance", job="$job"}
47+
)
48+
)`,
49+
range: true,
50+
interval: minStep,
51+
instant: false,
52+
hide: false,
53+
legendFormat: 'denominator',
54+
format: 'table',
55+
},
56+
];
57+
}
58+
59+
const dataProvider = useQueryRunner({
60+
queries: getErrorMapQueries(minStep),
61+
datasource: metricsDS,
62+
});
63+
64+
const dataTransformer = useDataTransformer({
65+
transformations: [
66+
{
67+
id: 'groupBy',
68+
options: {
69+
fields: {
70+
Value: {
71+
aggregations: ['sum'],
72+
operation: 'aggregate',
73+
},
74+
'Value #A': {
75+
aggregations: ['sum'],
76+
operation: 'aggregate',
77+
},
78+
'Value #B': {
79+
aggregations: ['sum'],
80+
operation: 'aggregate',
81+
},
82+
geohash: {
83+
aggregations: [],
84+
operation: 'groupby',
85+
},
86+
probe: {
87+
aggregations: [],
88+
operation: 'groupby',
89+
},
90+
'probe 1': {
91+
aggregations: [],
92+
operation: null,
93+
},
94+
'probe 2': {
95+
aggregations: [],
96+
operation: null,
97+
},
98+
},
99+
},
100+
},
101+
{
102+
id: 'joinByField',
103+
options: {
104+
byField: 'probe',
105+
mode: 'outerTabular',
106+
},
107+
},
108+
{
109+
id: 'calculateField',
110+
options: {
111+
alias: 'success rate',
112+
binary: {
113+
left: 'Value #A (sum)',
114+
operator: '/',
115+
right: 'Value #B (sum)',
116+
},
117+
mode: 'binary',
118+
reduce: {
119+
reducer: 'sum',
120+
},
121+
},
122+
},
123+
{
124+
id: 'calculateField',
125+
options: {
126+
alias: 'Error rate',
127+
binary: {
128+
left: '1.00',
129+
operator: '-',
130+
right: 'success rate',
131+
},
132+
mode: 'binary',
133+
reduce: {
134+
reducer: 'sum',
135+
},
136+
},
137+
},
138+
{
139+
id: 'organize',
140+
options: {
141+
excludeByName: {
142+
'Value #A (sum)': true,
143+
'Value #B (sum)': true,
144+
geohash: false,
145+
'probe 2': true,
146+
'success rate': true,
147+
'geohash 2': true,
148+
},
149+
indexByName: {},
150+
renameByName: {
151+
'error rate': '',
152+
geohash: '',
153+
'probe 1': 'Probe',
154+
'geohash 1': 'geohash',
155+
},
156+
includeByName: {},
157+
},
158+
},
159+
],
160+
data: dataProvider,
161+
});
162+
163+
const viz = VizConfigBuilders.geomap()
164+
.setUnit('percentunit')
165+
.setOption('basemap', {
166+
name: 'Basemap',
167+
type: 'default',
168+
})
169+
.setOption('controls', {
170+
mouseWheelZoom: false,
171+
showAttribution: true,
172+
showDebug: false,
173+
showScale: false,
174+
showZoom: true,
175+
})
176+
.setOption('layers', [
177+
{
178+
config: {
179+
showLegend: false,
180+
style: {
181+
color: {
182+
field: 'Error rate',
183+
fixed: 'dark-green',
184+
},
185+
opacity: 0.4,
186+
rotation: {
187+
fixed: 0,
188+
max: 360,
189+
min: -360,
190+
mode: 'mod',
191+
},
192+
size: {
193+
field: 'Error rate',
194+
fixed: 5,
195+
max: 10,
196+
min: 4,
197+
},
198+
symbol: {
199+
fixed: 'img/icons/marker/circle.svg',
200+
mode: 'fixed',
201+
},
202+
textConfig: {
203+
fontSize: 12,
204+
offsetX: 0,
205+
offsetY: 0,
206+
textAlign: 'center',
207+
textBaseline: 'middle',
208+
},
209+
},
210+
},
211+
location: {
212+
geohash: 'geohash',
213+
mode: FrameGeometrySourceMode.Geohash,
214+
latitude: 'Value',
215+
longitude: 'Value',
216+
},
217+
name: 'Error rate',
218+
type: 'markers',
219+
},
220+
])
221+
.setOption('view', {
222+
id: 'zero',
223+
lat: 0,
224+
lon: 0,
225+
zoom: 1,
226+
})
227+
.setColor({
228+
mode: 'thresholds',
229+
})
230+
.setDecimals(2)
231+
.setMin(0)
232+
.setMax(0.1)
233+
.setThresholds({
234+
mode: ThresholdsMode.Absolute,
235+
steps: [
236+
{
237+
color: 'green',
238+
value: 0,
239+
},
240+
{
241+
color: '#EAB839',
242+
value: 0.01,
243+
},
244+
{
245+
color: 'red',
246+
value: 0.015,
247+
},
248+
],
249+
})
250+
.build();
251+
252+
const data = dataProvider.useState();
253+
const [currentTimeRange] = useTimeRange();
254+
255+
const menu = useVizPanelMenu({
256+
data,
257+
viz,
258+
currentTimeRange,
259+
variables: ['job', 'probe', 'instance'],
260+
});
261+
262+
if (data?.data?.state === LoadingState.Loading) {
263+
return <LoadingPlaceholder text="Loading..." />;
264+
}
265+
266+
return <VizPanel menu={menu} title="Error rate by probe" viz={viz} dataProvider={dataTransformer} />;
267+
};

0 commit comments

Comments
 (0)