mirror of
https://github.com/grafana/grafana.git
synced 2025-09-22 10:32:49 +08:00
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:
@ -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",
|
||||||
|
@ -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',
|
||||||
|
@ -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 }),
|
163
public/app/features/scenes/apps/traffic.tsx
Normal file
163
public/app/features/scenes/apps/traffic.tsx
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
@ -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)' },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -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
|
||||||
|
Reference in New Issue
Block a user