Scenes: Update to 0.7 and update monitoring demo with new form of in-place drilldown (#68123)

* Update

* Progress

* Update

* Update
This commit is contained in:
Torkel Ödegaard
2023-05-10 14:07:08 +02:00
committed by GitHub
parent 62a660c6f6
commit 51a5f9f3fd
6 changed files with 246 additions and 70 deletions

View File

@ -261,7 +261,7 @@
"@grafana/lezer-logql": "0.1.3", "@grafana/lezer-logql": "0.1.3",
"@grafana/monaco-logql": "^0.0.7", "@grafana/monaco-logql": "^0.0.7",
"@grafana/runtime": "workspace:*", "@grafana/runtime": "workspace:*",
"@grafana/scenes": "^0.6.0", "@grafana/scenes": "^0.7.0",
"@grafana/schema": "workspace:*", "@grafana/schema": "workspace:*",
"@grafana/ui": "workspace:*", "@grafana/ui": "workspace:*",
"@kusto/monaco-kusto": "5.3.6", "@kusto/monaco-kusto": "5.3.6",

View File

@ -12,6 +12,7 @@ import {
getHandlerDetailsScene, getHandlerDetailsScene,
getHandlerLogsScene, getHandlerLogsScene,
} from './scenes'; } from './scenes';
import { getTrafficScene } from './traffic';
export function GrafanaMonitoringApp() { export function GrafanaMonitoringApp() {
const appScene = useMemo( const appScene = useMemo(
@ -58,6 +59,12 @@ export function getMainPageScene() {
}, },
], ],
}), }),
new SceneAppPage({
title: 'Traffic',
url: '/scenes/grafana-monitoring/traffic',
getScene: getTrafficScene,
preserveUrlKeys: ['from', 'to', 'var-instance'],
}),
new SceneAppPage({ new SceneAppPage({
title: 'Logs', title: 'Logs',
url: '/scenes/grafana-monitoring/logs', url: '/scenes/grafana-monitoring/logs',

View File

@ -1,3 +1,5 @@
import React from 'react';
import { FieldColorModeId, getFrameDisplayName } from '@grafana/data'; import { FieldColorModeId, getFrameDisplayName } from '@grafana/data';
import { locationService } from '@grafana/runtime'; import { locationService } from '@grafana/runtime';
import { import {
@ -10,19 +12,17 @@ import {
SceneTimeRange, SceneTimeRange,
VariableValueSelectors, VariableValueSelectors,
SceneQueryRunner, SceneQueryRunner,
SceneVariableSet,
QueryVariable,
SceneControlsSpacer, SceneControlsSpacer,
SceneDataTransformer, SceneDataTransformer,
SceneRefreshPicker, SceneRefreshPicker,
SceneFlexItem, SceneFlexItem,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { LinkButton } from '@grafana/ui';
import { SceneRadioToggle } from './SceneRadioToggle'; import { SceneRadioToggle } from './SceneRadioToggle';
import { SceneSearchBox } from './SceneSearchBox'; import { SceneSearchBox } from './SceneSearchBox';
import { getTableFilterTransform, getTimeSeriesFilterTransform } from './transforms'; import { getTableFilterTransform, getTimeSeriesFilterTransform } from './transforms';
import { getLinkUrlWithAppUrlState } from './utils'; import { getInstantQuery, getLinkUrlWithAppUrlState, getTimeSeriesQuery, getVariablesDefinitions } from './utils';
export function getHttpHandlerListScene(): EmbeddedScene { export function getHttpHandlerListScene(): EmbeddedScene {
const searchBox = new SceneSearchBox({ value: '' }); const searchBox = new SceneSearchBox({ value: '' });
@ -49,7 +49,7 @@ export function getHttpHandlerListScene(): EmbeddedScene {
const httpHandlersTable = new VizPanel({ const httpHandlersTable = new VizPanel({
$data: httpHandlerQueriesFiltered, $data: httpHandlerQueriesFiltered,
pluginId: 'table', pluginId: 'table',
title: '', title: 'Handlers',
options: { options: {
footer: { footer: {
enablePagination: true, enablePagination: true,
@ -154,14 +154,23 @@ export function getHttpHandlerListScene(): EmbeddedScene {
}), }),
body: new SceneFlexLayout({ body: new SceneFlexLayout({
direction: 'row', direction: 'row',
key: `row-${frameIndex}`,
children: [ children: [
new SceneFlexItem({ new SceneFlexItem({
key: `flex1-${frameIndex}`,
body: new VizPanel({ body: new VizPanel({
key: `viz1-${frameIndex}`,
pluginId: 'timeseries', pluginId: 'timeseries',
// titleLink: { headerActions: (
// path: `/scenes/grafana-monitoring/handlers/${encodeURIComponent(frame.fields[1].labels.handler)}`, <LinkButton
// queryKeys: ['from', 'to', 'var-instance'], fill="text"
// }, size="sm"
icon="arrow-right"
href={getHandlerDrilldownUrl(frame.fields[1]!.labels!.handler)}
>
Details
</LinkButton>
),
title: getFrameDisplayName(frame), title: getFrameDisplayName(frame),
options: { options: {
legend: { showLegend: false }, legend: { showLegend: false },
@ -170,26 +179,16 @@ export function getHttpHandlerListScene(): EmbeddedScene {
}), }),
new SceneFlexItem({ new SceneFlexItem({
key: `flex1-${frameIndex}`,
width: 200, width: 200,
body: new VizPanel({ body: new VizPanel({
key: `viz3-${frameIndex}`,
title: 'Last', title: 'Last',
pluginId: 'stat', pluginId: 'stat',
fieldConfig: { fieldConfig: {
defaults: { defaults: {
displayName: 'Last', displayName: 'Last',
links: [ links: [],
{
title: 'Go to handler drilldown view',
url: ``,
onBuildUrl: () => {
const params = locationService.getSearchObject();
return getLinkUrlWithAppUrlState(
'/scenes/grafana-monitoring/handlers/${__field.labels.handler:percentencode}',
params
);
},
},
],
}, },
overrides: [], overrides: [],
}, },
@ -242,6 +241,11 @@ export function getHttpHandlerListScene(): EmbeddedScene {
return scene; return scene;
} }
function getHandlerDrilldownUrl(handler: string) {
const params = locationService.getSearchObject();
return getLinkUrlWithAppUrlState(`/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}`, params);
}
export function getHandlerDetailsScene(handler: string): EmbeddedScene { export function getHandlerDetailsScene(handler: string): EmbeddedScene {
const reqDurationTimeSeries = getTimeSeriesQuery({ const reqDurationTimeSeries = getTimeSeriesQuery({
expr: `avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval])) * 1e3`, expr: `avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval])) * 1e3`,
@ -290,36 +294,6 @@ export function getHandlerDetailsScene(handler: string): EmbeddedScene {
return scene; return scene;
} }
function getInstantQuery(query: Partial<PromQuery>): SceneQueryRunner {
return new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
{
refId: 'A',
instant: true,
format: 'table',
maxDataPoints: 500,
...query,
},
],
});
}
function getTimeSeriesQuery(query: Partial<PromQuery>): SceneQueryRunner {
return new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
{
refId: 'A',
range: true,
format: 'time_series',
maxDataPoints: 500,
...query,
},
],
});
}
export function getOverviewScene(): EmbeddedScene { export function getOverviewScene(): EmbeddedScene {
const scene = new EmbeddedScene({ const scene = new EmbeddedScene({
$variables: getVariablesDefinitions(), $variables: getVariablesDefinitions(),
@ -429,18 +403,6 @@ export function getOverviewScene(): EmbeddedScene {
return scene; return scene;
} }
function getVariablesDefinitions() {
return new SceneVariableSet({
variables: [
new QueryVariable({
name: 'instance',
datasource: { uid: 'gdev-prometheus' },
query: { query: 'label_values(grafana_http_request_duration_seconds_sum, instance)' },
}),
],
});
}
function getInstantStatPanel(query: string, title: string) { function getInstantStatPanel(query: string, title: string) {
return new VizPanel({ return new VizPanel({
$data: getInstantQuery({ expr: query }), $data: getInstantQuery({ expr: query }),

View File

@ -0,0 +1,163 @@
import React from 'react';
import {
SceneFlexLayout,
SceneTimePicker,
VizPanel,
EmbeddedScene,
SceneTimeRange,
VariableValueSelectors,
SceneControlsSpacer,
SceneRefreshPicker,
SceneFlexItem,
SceneObjectState,
SceneObjectBase,
SceneObjectUrlSyncConfig,
SceneObjectUrlValues,
} from '@grafana/scenes';
import { Button } from '@grafana/ui';
import { getInstantQuery, getTimeSeriesQuery, getVariablesDefinitions } from './utils';
export function getTrafficScene(): EmbeddedScene {
const httpHandlersTable = new VizPanel({
$data: getInstantQuery({
expr: 'sort_desc(avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum[$__rate_interval]) * 1e3)) ',
}),
pluginId: 'table',
title: 'Handlers',
options: {
footer: {
enablePagination: true,
},
},
fieldConfig: {
defaults: {},
overrides: [
{
matcher: {
id: 'byRegexp',
options: '.*',
},
properties: [{ id: 'filterable', value: false }],
},
{
matcher: {
id: 'byName',
options: 'Time',
},
properties: [{ id: 'custom.hidden', value: true }],
},
{
matcher: {
id: 'byName',
options: 'Value',
},
properties: [{ id: 'displayName', value: 'Duration (Avg)' }],
},
{
matcher: {
id: 'byName',
options: 'handler',
},
properties: [
{
id: 'links',
value: [
{
title: 'Go to handler drilldown view',
url: '/scenes/grafana-monitoring/traffic?handler=${__value.text:percentencode}',
// onBuildUrl: () => {
// const params = locationService.getSearchObject();
// return getLinkUrlWithAppUrlState('/scenes/grafana-monitoring/traffic', {
// ...params,
// handler: '${__value.text:percentencode}',
// });
// },
},
],
},
],
},
],
},
});
const scene = new EmbeddedScene({
$variables: getVariablesDefinitions(),
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }),
controls: [
new VariableValueSelectors({}),
new SceneControlsSpacer(),
new SceneTimePicker({ isOnCanvas: true }),
new SceneRefreshPicker({ isOnCanvas: true }),
],
body: new SceneFlexLayout({
$behaviors: [new HandlerDrilldownViewBehavior()],
children: [new SceneFlexItem({ body: httpHandlersTable })],
}),
});
return scene;
}
export interface HandlerDrilldownViewBehaviorState extends SceneObjectState {
handler?: string;
}
export class HandlerDrilldownViewBehavior extends SceneObjectBase<HandlerDrilldownViewBehaviorState> {
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['handler'] });
public constructor() {
super({});
this.addActivationHandler(() => {
this._subs.add(this.subscribeToState((state) => this.onHandlerChanged(state.handler)));
this.onHandlerChanged(this.state.handler);
});
}
private onHandlerChanged(handler: string | undefined) {
const layout = this.getLayout();
if (handler == null) {
layout.setState({ children: layout.state.children.slice(0, 1) });
} else {
layout.setState({ children: [layout.state.children[0], this.getDrilldownView(handler)] });
}
}
private getDrilldownView(handler: string): SceneFlexItem {
return new SceneFlexItem({
key: 'drilldown-flex',
body: new VizPanel({
$data: getTimeSeriesQuery({
expr: `rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval]) * 1e3`,
}),
pluginId: 'timeseries',
title: `Handler: ${handler} details`,
headerActions: (
<Button size="sm" variant="secondary" icon="times" onClick={() => this.setState({ handler: undefined })} />
),
}),
});
}
public getUrlState() {
return { handler: this.state.handler };
}
public updateFromUrl(values: SceneObjectUrlValues) {
if (typeof values.handler === 'string' || values.handler === undefined) {
this.setState({ handler: values.handler });
}
}
private getLayout() {
if (this.parent instanceof SceneFlexLayout) {
return this.parent;
}
throw new Error('Invalid parent');
}
}

View File

@ -2,6 +2,8 @@ import { useLocation } from 'react-router-dom';
import { UrlQueryMap, urlUtil } from '@grafana/data'; import { UrlQueryMap, urlUtil } from '@grafana/data';
import { locationSearchToObject } from '@grafana/runtime'; import { locationSearchToObject } from '@grafana/runtime';
import { QueryVariable, SceneQueryRunner, SceneVariableSet } from '@grafana/scenes';
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
export function useAppQueryParams() { export function useAppQueryParams() {
const location = useLocation(); const location = useLocation();
@ -11,3 +13,45 @@ export function useAppQueryParams() {
export function getLinkUrlWithAppUrlState(path: string, params: UrlQueryMap): string { export function getLinkUrlWithAppUrlState(path: string, params: UrlQueryMap): string {
return urlUtil.renderUrl(path, params); return urlUtil.renderUrl(path, params);
} }
export function getInstantQuery(query: Partial<PromQuery>): SceneQueryRunner {
return new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
{
refId: 'A',
instant: true,
format: 'table',
maxDataPoints: 500,
...query,
},
],
});
}
export function getTimeSeriesQuery(query: Partial<PromQuery>): SceneQueryRunner {
return new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
{
refId: 'A',
range: true,
format: 'time_series',
maxDataPoints: 500,
...query,
},
],
});
}
export function getVariablesDefinitions() {
return new SceneVariableSet({
variables: [
new QueryVariable({
name: 'instance',
datasource: { uid: 'gdev-prometheus' },
query: { query: 'label_values(grafana_http_request_duration_seconds_sum, instance)' },
}),
],
});
}

View File

@ -3060,9 +3060,9 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@grafana/scenes@npm:^0.6.0": "@grafana/scenes@npm:^0.7.0":
version: 0.6.0 version: 0.7.0
resolution: "@grafana/scenes@npm:0.6.0" resolution: "@grafana/scenes@npm:0.7.0"
dependencies: dependencies:
"@grafana/e2e-selectors": canary "@grafana/e2e-selectors": canary
"@grafana/experimental": 1.0.1 "@grafana/experimental": 1.0.1
@ -3070,7 +3070,7 @@ __metadata:
react-use: 17.4.0 react-use: 17.4.0
react-virtualized-auto-sizer: 1.0.7 react-virtualized-auto-sizer: 1.0.7
uuid: ^9.0.0 uuid: ^9.0.0
checksum: 7197abac93ba84711900b526f0caa648b0b9f0c0e2edea2fbc125c1f192d6a3dae52389007cf82cbdf06a7b8019e5c03b5efa635f12ebc51eab5852cddf6427e checksum: 78cff0b04cc1237d32b5d3fae816cc2814e6aeed11cbe443814347d73df7ec97e1ce204733ea7f4e6b39c39dd56f37c15829c530cf0ca9bba646a8b78aa7a74e
languageName: node languageName: node
linkType: hard linkType: hard
@ -18604,7 +18604,7 @@ __metadata:
"@grafana/lezer-logql": 0.1.3 "@grafana/lezer-logql": 0.1.3
"@grafana/monaco-logql": ^0.0.7 "@grafana/monaco-logql": ^0.0.7
"@grafana/runtime": "workspace:*" "@grafana/runtime": "workspace:*"
"@grafana/scenes": ^0.6.0 "@grafana/scenes": ^0.7.0
"@grafana/schema": "workspace:*" "@grafana/schema": "workspace:*"
"@grafana/toolkit": "workspace:*" "@grafana/toolkit": "workspace:*"
"@grafana/tsconfig": ^1.2.0-rc1 "@grafana/tsconfig": ^1.2.0-rc1