Scenes: Remove old scenes stuff (#79760)

* Scenes: Remove old scenes stuff

* Fixes

* Fixes

* update
pull/80023/head
Torkel Ödegaard 2 years ago committed by GitHub
parent 692e8958a3
commit f0c38611a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/CODEOWNERS
  2. 9
      pkg/services/navtree/navtreeimpl/navtree.go
  3. 1
      public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx
  4. 3
      public/app/features/dashboard-scene/scene/DashboardScene.tsx
  5. 8
      public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx
  6. 1
      public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx
  7. 8
      public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx
  8. 1
      public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx
  9. 10
      public/app/features/dashboard-scene/utils/urlBuilders.test.ts
  10. 7
      public/app/features/dashboard-scene/utils/urlBuilders.ts
  11. 3
      public/app/features/dashboard/components/DashNav/DashNav.tsx
  12. 2
      public/app/features/dashboard/containers/DashboardPageProxy.tsx
  13. 1
      public/app/features/dashboard/containers/types.ts
  14. 57
      public/app/features/scenes/SceneListPage.tsx
  15. 33
      public/app/features/scenes/ScenePage.tsx
  16. 110
      public/app/features/scenes/apps/GrafanaMonitoringApp.tsx
  17. 24
      public/app/features/scenes/apps/SceneRadioToggle.tsx
  18. 20
      public/app/features/scenes/apps/SceneSearchBox.tsx
  19. 428
      public/app/features/scenes/apps/scenes.tsx
  20. 127
      public/app/features/scenes/apps/traffic.tsx
  21. 42
      public/app/features/scenes/apps/transforms.ts
  22. 57
      public/app/features/scenes/apps/utils.ts
  23. 73
      public/app/features/scenes/scenes/gridMultiTimeRange.tsx
  24. 117
      public/app/features/scenes/scenes/gridMultiple.tsx
  25. 105
      public/app/features/scenes/scenes/gridWithMultipleData.tsx
  26. 45
      public/app/features/scenes/scenes/index.tsx
  27. 22
      public/app/features/scenes/scenes/queries.ts
  28. 68
      public/app/features/scenes/scenes/queryVariableDemo.tsx
  29. 189
      public/app/features/scenes/scenes/repeatingPanels.tsx
  30. 58
      public/app/features/scenes/scenes/sceneWithRows.tsx
  31. 79
      public/app/features/scenes/scenes/transformations.tsx
  32. 203
      public/app/features/scenes/scenes/variablesDemo.tsx
  33. 42
      public/app/routes/routes.tsx

@ -413,7 +413,6 @@ cypress.config.js @grafana/grafana-frontend-platform
/public/app/features/runtime/ @ryantxu
/public/app/features/query/ @grafana/dashboards-squad
/public/app/features/sandbox/ @grafana/grafana-frontend-platform
/public/app/features/scenes/ @grafana/dashboards-squad
/public/app/features/browse-dashboards/ @grafana/grafana-frontend-platform
/public/app/features/search/ @grafana/grafana-frontend-platform
/public/app/features/serviceaccounts/ @grafana/identity-access-team

@ -368,15 +368,6 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt
}
}
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagScenes) {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Scenes",
Id: "scenes",
Url: s.cfg.AppSubURL + "/scenes",
Icon: "apps",
})
}
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagDatatrails) {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Data trails",

@ -121,7 +121,6 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
getDashboardUrl({
uid: this.state.dashboardRef.resolve().state.uid,
currentQueryParams: locationService.getLocation().search,
useExperimentalURL: true,
})
);
}

@ -2,7 +2,7 @@ import * as H from 'history';
import { Unsubscribable } from 'rxjs';
import { CoreApp, DataQueryRequest, NavIndex, NavModelItem } from '@grafana/data';
import { config, locationService } from '@grafana/runtime';
import { locationService } from '@grafana/runtime';
import {
getUrlSyncManager,
SceneFlexLayout,
@ -195,7 +195,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
uid: this.state.uid,
currentQueryParams: location.search,
updateQuery: { viewPanel: null, inspect: null, editview: null },
useExperimentalURL: Boolean(config.featureToggles.dashboardSceneForViewers && meta.canEdit),
}),
};

@ -54,7 +54,7 @@ describe('panelMenuBehavior', () => {
});
beforeAll(() => {
locationService.push('/scenes/dashboard/dash-1?from=now-5m&to=now');
locationService.push('/d/dash-1?from=now-5m&to=now');
});
it('Given standard panel', async () => {
@ -71,9 +71,9 @@ describe('panelMenuBehavior', () => {
expect(menu.state.items?.length).toBe(6);
// verify view panel url keeps url params and adds viewPanel=<panel-key>
expect(menu.state.items?.[0].href).toBe('/scenes/dashboard/dash-1?from=now-5m&to=now&viewPanel=panel-12');
expect(menu.state.items?.[0].href).toBe('/d/dash-1?from=now-5m&to=now&viewPanel=panel-12');
// verify edit url keeps url time range
expect(menu.state.items?.[1].href).toBe('/scenes/dashboard/dash-1/panel-edit/12?from=now-5m&to=now');
expect(menu.state.items?.[1].href).toBe('/d/dash-1/panel-edit/12?from=now-5m&to=now');
// verify share
expect(menu.state.items?.[2].text).toBe('Share');
// verify explore url
@ -86,7 +86,7 @@ describe('panelMenuBehavior', () => {
expect(getExploreArgs.scopedVars?.__sceneObject?.value).toBe(panel);
// verify inspect url keeps url params and adds inspect=<panel-key>
expect(menu.state.items?.[4].href).toBe('/scenes/dashboard/dash-1?from=now-5m&to=now&inspect=panel-12');
expect(menu.state.items?.[4].href).toBe('/d/dash-1?from=now-5m&to=now&inspect=panel-12');
expect(menu.state.items?.[4].subMenu).toBeDefined();
expect(menu.state.items?.[4].subMenu?.length).toBe(3);

@ -71,7 +71,6 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
uid: dashboard.state.uid,
subPath: `/panel-edit/${panelId}`,
currentQueryParams: location.search,
useExperimentalURL: true,
}),
});
}

@ -30,7 +30,7 @@ describe('ShareLinkTab', () => {
config.rendererAvailable = true;
config.bootData.user.orgId = 1;
config.featureToggles.dashboardSceneForViewers = true;
locationService.push('/scenes/dashboard/dash-1?from=now-6h&to=now');
locationService.push('/d/dash-1?from=now-6h&to=now');
});
describe('with locked time range (absolute) range', () => {
@ -38,7 +38,7 @@ describe('ShareLinkTab', () => {
buildAndRenderScenario({});
expect(await screen.findByRole('textbox', { name: 'Link URL' })).toHaveValue(
'http://dashboards.grafana.com/grafana/scenes/dashboard/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&viewPanel=panel-12'
'http://dashboards.grafana.com/grafana/d/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&viewPanel=panel-12'
);
});
});
@ -49,7 +49,7 @@ describe('ShareLinkTab', () => {
act(() => tab.onToggleLockedTime());
expect(await screen.findByRole('textbox', { name: 'Link URL' })).toHaveValue(
'http://dashboards.grafana.com/grafana/scenes/dashboard/dash-1?from=now-6h&to=now&viewPanel=panel-12'
'http://dashboards.grafana.com/grafana/d/dash-1?from=now-6h&to=now&viewPanel=panel-12'
);
});
});
@ -59,7 +59,7 @@ describe('ShareLinkTab', () => {
act(() => tab.onThemeChange('light'));
expect(await screen.findByRole('textbox', { name: 'Link URL' })).toHaveValue(
'http://dashboards.grafana.com/grafana/scenes/dashboard/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&viewPanel=panel-12&theme=light'
'http://dashboards.grafana.com/grafana/d/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&viewPanel=panel-12&theme=light'
);
});

@ -76,7 +76,6 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
currentQueryParams: location.search,
updateQuery: urlParamsUpdate,
absolute: true,
useExperimentalURL: Boolean(config.featureToggles.dashboardSceneForViewers && dashboard.state.meta.canEdit),
});
if (useShortUrl) {

@ -2,9 +2,9 @@ import { getDashboardUrl } from './urlBuilders';
describe('dashboard utils', () => {
it('Can getUrl', () => {
const url = getDashboardUrl({ uid: 'dash-1', currentQueryParams: '?orgId=1&filter=A', useExperimentalURL: true });
const url = getDashboardUrl({ uid: 'dash-1', currentQueryParams: '?orgId=1&filter=A' });
expect(url).toBe('/scenes/dashboard/dash-1?orgId=1&filter=A');
expect(url).toBe('/d/dash-1?orgId=1&filter=A');
});
it('Can getUrl with subpath', () => {
@ -12,10 +12,9 @@ describe('dashboard utils', () => {
uid: 'dash-1',
subPath: '/panel-edit/2',
currentQueryParams: '?orgId=1&filter=A',
useExperimentalURL: true,
});
expect(url).toBe('/scenes/dashboard/dash-1/panel-edit/2?orgId=1&filter=A');
expect(url).toBe('/d/dash-1/panel-edit/2?orgId=1&filter=A');
});
it('Can getUrl with params removed and addded', () => {
@ -23,9 +22,8 @@ describe('dashboard utils', () => {
uid: 'dash-1',
currentQueryParams: '?orgId=1&filter=A',
updateQuery: { filter: null, new: 'A' },
useExperimentalURL: true,
});
expect(url).toBe('/scenes/dashboard/dash-1?orgId=1&new=A');
expect(url).toBe('/d/dash-1?orgId=1&new=A');
});
});

@ -21,15 +21,10 @@ export interface DashboardUrlOptions {
absolute?: boolean;
// Add tz to query params
timeZone?: string;
// Add tz to query params
useExperimentalURL?: boolean;
}
export function getDashboardUrl(options: DashboardUrlOptions) {
let path = options.useExperimentalURL
? `/scenes/dashboard/${options.uid}${options.subPath ?? ''}`
: `/d/${options.uid}${options.subPath ?? ''}`;
let path = `/d/${options.uid}${options.subPath ?? ''}`;
if (options.soloRoute) {
path = `/d-solo/${options.uid}${options.subPath ?? ''}`;

@ -203,8 +203,7 @@ export const DashNav = React.memo<Props>((props) => {
tooltip={'View as Scene'}
icon="apps"
onClick={() => {
const location = locationService.getLocation();
locationService.push(`/scenes/dashboard/${dashboard.uid}${location.search}`);
locationService.partial({ scenes: true });
}}
/>
);

@ -18,7 +18,7 @@ export type DashboardPageProxyProps = GrafanaRouteComponentProps<
// This proxy component is used for Dashboard -> Scenes migration.
// It will render DashboardScenePage if the user is only allowed to view the dashboard.
function DashboardPageProxy(props: DashboardPageProxyProps) {
if (config.featureToggles.dashboardScene) {
if (config.featureToggles.dashboardScene || props.queryParams.scenes) {
return <DashboardScenePage {...props} />;
}

@ -18,4 +18,5 @@ export type DashboardPageRouteSearchParams = {
to?: string;
refresh?: string;
kiosk?: string | true;
scenes?: boolean;
};

@ -1,57 +0,0 @@
// Libraries
import React from 'react';
import { useAsync } from 'react-use';
import { Card, Stack } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
// Types
import { getGrafanaSearcher } from '../search/service';
import { getScenes } from './scenes';
export interface Props {}
export const SceneListPage = ({}: Props) => {
const scenes = getScenes();
const results = useAsync(() => {
return getGrafanaSearcher().starred({ starred: true });
}, []);
return (
<Page navId="scenes" subTitle="Experimental new runtime and state model for dashboards">
<Page.Contents>
<Stack direction="column" gap={1}>
<h5>Apps</h5>
<Stack direction="column" gap={0}>
<Card href={`/scenes/grafana-monitoring`}>
<Card.Heading>Grafana monitoring</Card.Heading>
</Card>
</Stack>
<h5>Test scenes</h5>
<Stack direction="column" gap={0}>
{scenes.map((scene) => (
<Card key={scene.title} href={`/scenes/${scene.title}`}>
<Card.Heading>{scene.title}</Card.Heading>
</Card>
))}
</Stack>
{results.value && (
<>
<h5>Starred dashboards</h5>
<Stack direction="column" gap={0}>
{results.value!.view.map((dash) => (
<Card href={`/scenes/dashboard/${dash.uid}`} key={dash.uid}>
<Card.Heading>{dash.name}</Card.Heading>
</Card>
))}
</Stack>
</>
)}
</Stack>
</Page.Contents>
</Page>
);
};
export default SceneListPage;

@ -1,33 +0,0 @@
// Libraries
import React, { useEffect, useState } from 'react';
import { getUrlSyncManager } from '@grafana/scenes';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { getSceneByTitle } from './scenes';
export interface Props extends GrafanaRouteComponentProps<{ name: string }> {}
export const ScenePage = (props: Props) => {
const scene = getSceneByTitle(props.match.params.name);
const [isInitialized, setInitialized] = useState(false);
useEffect(() => {
if (scene && !isInitialized) {
getUrlSyncManager().initSync(scene);
setInitialized(true);
}
}, [isInitialized, scene]);
if (!scene) {
return <h2>Scene not found</h2>;
}
if (!isInitialized) {
return null;
}
return <scene.Component model={scene} />;
};
export default ScenePage;

@ -1,110 +0,0 @@
// Libraries
import React, { useMemo, useState } from 'react';
import { SceneApp, SceneAppPage, SceneRouteMatch, SceneAppPageLike } from '@grafana/scenes';
import { usePageNav } from 'app/core/components/Page/usePageNav';
import { PluginPageContext, PluginPageContextType } from 'app/features/plugins/components/PluginPageContext';
import {
getOverviewScene,
getHttpHandlerListScene,
getOverviewLogsScene,
getHandlerDetailsScene,
getHandlerLogsScene,
} from './scenes';
import { getTrafficScene } from './traffic';
export function GrafanaMonitoringApp() {
const appScene = useMemo(
() =>
new SceneApp({
pages: [getMainPageScene()],
}),
[]
);
const sectionNav = usePageNav('scenes')!;
const [pluginContext] = useState<PluginPageContextType>({ sectionNav });
return (
<PluginPageContext.Provider value={pluginContext}>
<appScene.Component model={appScene} />
</PluginPageContext.Provider>
);
}
export function getMainPageScene() {
return new SceneAppPage({
title: 'Grafana Monitoring',
subTitle: 'A custom app with embedded scenes to monitor your Grafana server',
url: '/scenes/grafana-monitoring',
hideFromBreadcrumbs: false,
getScene: getOverviewScene,
tabs: [
new SceneAppPage({
title: 'Overview',
url: '/scenes/grafana-monitoring',
getScene: getOverviewScene,
preserveUrlKeys: ['from', 'to', 'var-instance'],
}),
new SceneAppPage({
title: 'HTTP handlers',
url: '/scenes/grafana-monitoring/handlers',
getScene: getHttpHandlerListScene,
preserveUrlKeys: ['from', 'to', 'var-instance'],
drilldowns: [
{
routePath: '/scenes/grafana-monitoring/handlers/:handler',
getPage: getHandlerDrilldownPage,
},
],
}),
new SceneAppPage({
title: 'Traffic',
url: '/scenes/grafana-monitoring/traffic',
getScene: getTrafficScene,
preserveUrlKeys: ['from', 'to', 'var-instance'],
}),
new SceneAppPage({
title: 'Logs',
url: '/scenes/grafana-monitoring/logs',
getScene: getOverviewLogsScene,
preserveUrlKeys: ['from', 'to', 'var-instance'],
}),
],
});
}
export function getHandlerDrilldownPage(
match: SceneRouteMatch<{ handler: string; tab?: string }>,
parent: SceneAppPageLike
) {
const handler = decodeURIComponent(match.params.handler);
const baseUrl = `/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}`;
return new SceneAppPage({
title: handler,
subTitle: 'A grafana http handler is responsible for service a specific API request',
url: baseUrl,
getParentPage: () => parent,
getScene: () => getHandlerDetailsScene(handler),
tabs: [
new SceneAppPage({
title: 'Metrics',
url: baseUrl,
routePath: '/scenes/grafana-monitoring/handlers/:handler',
getScene: () => getHandlerDetailsScene(handler),
preserveUrlKeys: ['from', 'to', 'var-instance'],
}),
new SceneAppPage({
title: 'Logs',
url: baseUrl + '/logs',
routePath: '/scenes/grafana-monitoring/handlers/:handler/logs',
getScene: () => getHandlerLogsScene(handler),
preserveUrlKeys: ['from', 'to', 'var-instance'],
}),
],
});
}
export default GrafanaMonitoringApp;

@ -1,24 +0,0 @@
import React from 'react';
import { SelectableValue } from '@grafana/data';
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
import { RadioButtonGroup } from '@grafana/ui';
export interface SceneRadioToggleState extends SceneObjectState {
options: Array<SelectableValue<string>>;
value: string;
onChange: (value: string) => void;
}
export class SceneRadioToggle extends SceneObjectBase<SceneRadioToggleState> {
onChange = (value: string) => {
this.setState({ value });
this.state.onChange(value);
};
static Component = ({ model }: SceneComponentProps<SceneRadioToggle>) => {
const { options, value } = model.useState();
return <RadioButtonGroup options={options} value={value} onChange={model.onChange} />;
};
}

@ -1,20 +0,0 @@
import React from 'react';
import { SceneComponentProps, SceneObjectState, SceneObjectBase } from '@grafana/scenes';
import { Input } from '@grafana/ui';
export interface SceneSearchBoxState extends SceneObjectState {
value: string;
}
export class SceneSearchBox extends SceneObjectBase<SceneSearchBoxState> {
onChange = (evt: React.FormEvent<HTMLInputElement>) => {
this.setState({ value: evt.currentTarget.value });
};
static Component = ({ model }: SceneComponentProps<SceneSearchBox>) => {
const { value } = model.useState();
return <Input width={25} placeholder="Search..." value={value} onChange={model.onChange} />;
};
}

@ -1,428 +0,0 @@
import React from 'react';
import { FieldColorModeId, getFrameDisplayName } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
SceneFlexLayout,
SceneByFrameRepeater,
SceneTimePicker,
EmbeddedScene,
SceneDataNode,
SceneTimeRange,
VariableValueSelectors,
SceneQueryRunner,
SceneControlsSpacer,
SceneDataTransformer,
SceneRefreshPicker,
SceneFlexItem,
PanelBuilders,
} from '@grafana/scenes';
import { BigValueGraphMode, BigValueTextMode, LogsDedupStrategy, LogsSortOrder } from '@grafana/schema';
import { LinkButton } from '@grafana/ui';
import { SceneRadioToggle } from './SceneRadioToggle';
import { SceneSearchBox } from './SceneSearchBox';
import { getTableFilterTransform, getTimeSeriesFilterTransform } from './transforms';
import { getInstantQuery, getLinkUrlWithAppUrlState, getTimeSeriesQuery, getVariablesDefinitions } from './utils';
export function getHttpHandlerListScene(): EmbeddedScene {
const searchBox = new SceneSearchBox({ value: '' });
const httpHandlerQueries = getInstantQuery({
expr: 'sort_desc(avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum[$__rate_interval]) * 1e3)) ',
});
const httpHandlerQueriesFiltered = new SceneDataTransformer({
$data: httpHandlerQueries,
transformations: [getTableFilterTransform('')],
});
httpHandlerQueriesFiltered.addActivationHandler(() => {
const sub = searchBox.subscribeToState((state) => {
// Update transform and re-process them
httpHandlerQueriesFiltered.setState({ transformations: [getTableFilterTransform(state.value)] });
httpHandlerQueriesFiltered.reprocessTransformations();
});
return () => sub.unsubscribe();
});
const httpHandlersTable = PanelBuilders.table()
.setTitle('Handlers')
.setData(httpHandlerQueriesFiltered)
.setOption('footer', {
enablePagination: true,
})
.setOverrides((b) =>
b
.matchFieldsWithNameByRegex('.*')
.overrideFilterable(false)
.matchFieldsWithName('Time')
.overrideCustomFieldConfig('hidden', true)
.matchFieldsWithName('Value')
.overrideDisplayName('Duration (Avg)')
.matchFieldsWithName('handler')
.overrideLinks([
{
title: 'Go to handler drilldown view',
url: '',
onBuildUrl: () => {
const params = locationService.getSearchObject();
return getLinkUrlWithAppUrlState(
'/scenes/grafana-monitoring/handlers/${__value.text:percentencode}',
params
);
},
},
])
)
.build();
const reqDurationTimeSeries = new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
{
refId: 'A',
//expr: ``,
expr: 'topk(20, avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum[$__rate_interval])) * 1e3)',
range: true,
format: 'time_series',
legendFormat: '{{method}} {{handler}} (status = {{status_code}})',
maxDataPoints: 500,
},
],
});
const reqDurationTimeSeriesFiltered = new SceneDataTransformer({
$data: reqDurationTimeSeries,
transformations: [getTimeSeriesFilterTransform('')],
});
reqDurationTimeSeriesFiltered.addActivationHandler(() => {
const sub = searchBox.subscribeToState((state) => {
// Update transform and re-process them
reqDurationTimeSeriesFiltered.setState({ transformations: [getTimeSeriesFilterTransform(state.value)] });
reqDurationTimeSeriesFiltered.reprocessTransformations();
});
return () => sub.unsubscribe();
});
const graphsScene = new SceneByFrameRepeater({
$data: reqDurationTimeSeriesFiltered,
body: new SceneFlexLayout({
direction: 'column',
children: [],
}),
getLayoutChild: (data, frame, frameIndex) => {
return new SceneFlexItem({
key: `panel-${frameIndex}`,
minHeight: 200,
$data: new SceneDataNode({
data: {
...data,
series: [frame],
},
}),
body: new SceneFlexLayout({
direction: 'row',
key: `row-${frameIndex}`,
children: [
new SceneFlexItem({
key: `flex1-${frameIndex}`,
body: PanelBuilders.timeseries()
.setTitle(getFrameDisplayName(frame))
.setOption('legend', { showLegend: false })
.setHeaderActions(
<LinkButton
fill="text"
size="sm"
icon="arrow-right"
href={getHandlerDrilldownUrl(frame.fields[1]!.labels!.handler)}
>
Details
</LinkButton>
)
.build(),
}),
new SceneFlexItem({
key: `flex2-${frameIndex}`,
width: 200,
body: PanelBuilders.stat()
.setTitle('Last')
.setOption('graphMode', BigValueGraphMode.None)
.setOption('textMode', BigValueTextMode.Value)
.setDisplayName('Last')
.build(),
}),
],
}),
});
},
});
const layout = new SceneFlexLayout({
children: [new SceneFlexItem({ body: httpHandlersTable })],
});
const sceneToggle = new SceneRadioToggle({
options: [
{ value: 'table', label: 'Table' },
{ value: 'graphs', label: 'Graphs' },
],
value: 'table',
onChange: (value) => {
if (value === 'table') {
layout.setState({ children: [new SceneFlexItem({ body: httpHandlersTable })] });
} else {
layout.setState({ children: [graphsScene] });
}
},
});
const scene = new EmbeddedScene({
$variables: getVariablesDefinitions(),
$data: httpHandlerQueries,
$timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }),
controls: [
new VariableValueSelectors({}),
searchBox,
new SceneControlsSpacer(),
sceneToggle,
new SceneTimePicker({ isOnCanvas: true }),
new SceneRefreshPicker({ isOnCanvas: true }),
],
body: layout,
});
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 {
const reqDurationTimeSeries = getTimeSeriesQuery({
expr: `avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval])) * 1e3`,
legendFormat: '{{method}} {{handler}} (status = {{status_code}})',
});
const reqCountTimeSeries = getTimeSeriesQuery({
expr: `sum without(job, instance) (rate(grafana_http_request_duration_seconds_count{handler="${handler}"}[$__rate_interval])) `,
legendFormat: '{{method}} {{handler}} (status = {{status_code}})',
});
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({
direction: 'column',
children: [
new SceneFlexItem({
body: PanelBuilders.timeseries().setData(reqDurationTimeSeries).setTitle('Request duration avg (ms)').build(),
}),
new SceneFlexItem({
body: PanelBuilders.timeseries().setData(reqCountTimeSeries).setTitle('Request count/s').build(),
}),
],
}),
});
return scene;
}
export function getOverviewScene(): EmbeddedScene {
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({
direction: 'column',
children: [
new SceneFlexItem({
height: 150,
body: new SceneFlexLayout({
children: [
new SceneFlexItem({
body: getInstantStatPanel('grafana_stat_totals_dashboard', 'Dashboards'),
}),
new SceneFlexItem({
body: getInstantStatPanel('grafana_stat_total_users', 'Users'),
}),
new SceneFlexItem({
body: getInstantStatPanel('sum(grafana_stat_totals_datasource)', 'Data sources'),
}),
new SceneFlexItem({
body: getInstantStatPanel('grafana_stat_total_service_account_tokens', 'Service account tokens'),
}),
],
}),
}),
new SceneFlexItem({
body: PanelBuilders.timeseries()
.setData(
new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
{
refId: 'A',
expr: `sum(process_resident_memory_bytes{job="grafana", instance=~"$instance"})`,
range: true,
format: 'time_series',
maxDataPoints: 500,
},
],
})
)
.setTitle('Memory usage')
.setOption('legend', { showLegend: false })
.setUnit('bytes')
.setMin(0)
.setCustomFieldConfig('lineWidth', 2)
.setCustomFieldConfig('fillOpacity', 6)
.build(),
}),
new SceneFlexItem({
body: PanelBuilders.timeseries()
.setData(
new SceneQueryRunner({
datasource: { uid: 'gdev-prometheus' },
queries: [
{
refId: 'A',
expr: `sum(go_goroutines{job="grafana", instance=~"$instance"})`,
range: true,
format: 'time_series',
maxDataPoints: 500,
},
],
})
)
.setOption('legend', { showLegend: false })
.setMin(0)
.setCustomFieldConfig('lineWidth', 2)
.setCustomFieldConfig('fillOpacity', 6)
.setTitle('Go routines')
.build(),
}),
],
}),
});
return scene;
}
function getInstantStatPanel(query: string, title: string) {
return PanelBuilders.stat()
.setData(getInstantQuery({ expr: query }))
.setTitle(title)
.setColor({ fixedColor: 'text', mode: FieldColorModeId.Fixed })
.build();
}
export function getHandlerLogsScene(handler: string): EmbeddedScene {
const logsQuery = new SceneQueryRunner({
datasource: { uid: 'gdev-loki' },
queries: [
{
refId: 'A',
expr: `{job="grafana"} | logfmt | handler=\`${handler}\` | __error__=\`\``,
queryType: 'range',
maxDataPoints: 5000,
},
],
});
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({
direction: 'column',
children: [
new SceneFlexItem({
body: PanelBuilders.logs()
.setData(logsQuery)
.setTitle('')
.setOption('showTime', true)
.setOption('showLabels', false)
.setOption('showCommonLabels', false)
.setOption('wrapLogMessage', true)
.setOption('prettifyLogMessage', false)
.setOption('enableLogDetails', true)
.setOption('dedupStrategy', LogsDedupStrategy.none)
.setOption('sortOrder', LogsSortOrder.Descending)
.build(),
}),
],
}),
});
return scene;
}
export function getOverviewLogsScene(): EmbeddedScene {
const logsQuery = new SceneQueryRunner({
datasource: { uid: 'gdev-loki' },
queries: [
{
refId: 'A',
expr: `{job="grafana"} | logfmt | __error__=\`\``,
queryType: 'range',
maxDataPoints: 5000,
},
],
});
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({
direction: 'column',
children: [
new SceneFlexItem({
body: PanelBuilders.logs()
.setTitle('')
.setData(logsQuery)
.setOption('showTime', true)
.setOption('showLabels', false)
.setOption('showCommonLabels', false)
.setOption('wrapLogMessage', true)
.setOption('prettifyLogMessage', false)
.setOption('enableLogDetails', true)
.setOption('dedupStrategy', LogsDedupStrategy.none)
.setOption('sortOrder', LogsSortOrder.Descending)
.build(),
}),
],
}),
});
return scene;
}

@ -1,127 +0,0 @@
import React from 'react';
import {
SceneFlexLayout,
SceneTimePicker,
EmbeddedScene,
SceneTimeRange,
VariableValueSelectors,
SceneControlsSpacer,
SceneRefreshPicker,
SceneFlexItem,
SceneObjectState,
SceneObjectBase,
SceneObjectUrlSyncConfig,
SceneObjectUrlValues,
PanelBuilders,
} from '@grafana/scenes';
import { Button } from '@grafana/ui';
import { getInstantQuery, getTimeSeriesQuery, getVariablesDefinitions } from './utils';
export function getTrafficScene(): EmbeddedScene {
const httpHandlersTable = PanelBuilders.table()
.setData(
getInstantQuery({
expr: 'sort_desc(avg without(job, instance) (rate(grafana_http_request_duration_seconds_sum[$__rate_interval]) * 1e3)) ',
})
)
.setTitle('Handlers')
.setOption('footer', { enablePagination: true })
.setOverrides((b) =>
b
.matchFieldsWithNameByRegex('.*')
.overrideFilterable(false)
.matchFieldsWithName('Time')
.overrideCustomFieldConfig('hidden', true)
.matchFieldsWithName('Value')
.overrideDisplayName('Duration (Avg)')
.matchFieldsWithName('handler')
.overrideLinks([
{
title: 'Go to handler drilldown view',
url: '/scenes/grafana-monitoring/traffic?handler=${__value.text:percentencode}',
},
])
)
.build();
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'] });
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: PanelBuilders.timeseries()
.setData(
getTimeSeriesQuery({
expr: `rate(grafana_http_request_duration_seconds_sum{handler="${handler}"}[$__rate_interval]) * 1e3`,
})
)
.setTitle(`Handler: ${handler} details`)
.setHeaderActions(
<Button size="sm" variant="secondary" icon="times" onClick={() => this.setState({ handler: undefined })} />
)
.build(),
});
}
getUrlState() {
return { handler: this.state.handler };
}
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');
}
}

@ -1,42 +0,0 @@
import { map } from 'rxjs';
import {
BasicValueMatcherOptions,
CustomTransformOperator,
DataTransformerID,
getFrameDisplayName,
ValueMatcherID,
} from '@grafana/data';
import { FilterByValueMatch, FilterByValueType } from '@grafana/data/src/transformations/transformers/filterByValue';
import { DataTransformerConfig, MatcherConfig } from '@grafana/schema';
export function getTableFilterTransform(query: string): DataTransformerConfig {
const regex: MatcherConfig<BasicValueMatcherOptions<string>> = {
id: ValueMatcherID.regex,
options: { value: query },
};
return {
id: DataTransformerID.filterByValue,
options: {
type: FilterByValueType.include,
match: FilterByValueMatch.all,
filters: [
{
fieldName: 'handler',
config: regex,
},
],
},
};
}
export function getTimeSeriesFilterTransform(query: string): CustomTransformOperator {
return () => (source) => {
return source.pipe(
map((data) => {
return data.filter((frame) => getFrameDisplayName(frame).toLowerCase().includes(query.toLowerCase()));
})
);
};
}

@ -1,57 +0,0 @@
import { useLocation } from 'react-router-dom';
import { UrlQueryMap, urlUtil } from '@grafana/data';
import { locationSearchToObject } from '@grafana/runtime';
import { QueryVariable, SceneQueryRunner, SceneVariableSet } from '@grafana/scenes';
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
export function useAppQueryParams() {
const location = useLocation();
return locationSearchToObject(location.search || '');
}
export function getLinkUrlWithAppUrlState(path: string, params: UrlQueryMap): string {
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)', refId: 'A' },
}),
],
});
}

@ -1,73 +0,0 @@
import {
SceneGridRow,
SceneTimePicker,
SceneGridLayout,
SceneTimeRange,
SceneRefreshPicker,
SceneGridItem,
PanelBuilders,
} from '@grafana/scenes';
import { TestDataQueryType } from '@grafana-plugins/grafana-testdata-datasource/dataquery.gen';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getGridWithMultipleTimeRanges(): DashboardScene {
const globalTimeRange = new SceneTimeRange();
const row1TimeRange = new SceneTimeRange({
from: 'now-1y',
to: 'now',
});
return new DashboardScene({
title: 'Grid with rows and different queries and time ranges',
body: new SceneGridLayout({
children: [
new SceneGridRow({
$timeRange: row1TimeRange,
$data: getQueryRunnerWithRandomWalkQuery({ scenarioId: TestDataQueryType.RandomWalkTable }),
title: 'Row A - has its own query, last year time range',
key: 'Row A',
isCollapsed: true,
y: 0,
children: [
new SceneGridItem({
x: 0,
y: 1,
width: 12,
height: 5,
isResizable: true,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Row A Child1').build(),
}),
new SceneGridItem({
x: 0,
y: 5,
width: 6,
height: 5,
isResizable: true,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Row A Child2').build(),
}),
],
}),
new SceneGridItem({
x: 0,
y: 12,
width: 6,
height: 10,
isResizable: true,
isDraggable: true,
body: PanelBuilders.timeseries()
.setTitle('Outsider, has its own query')
.setData(getQueryRunnerWithRandomWalkQuery())
.build(),
}),
],
}),
$timeRange: globalTimeRange,
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
});
}

@ -1,117 +0,0 @@
import {
SceneTimePicker,
SceneFlexLayout,
SceneGridLayout,
SceneTimeRange,
SceneRefreshPicker,
SceneGridItem,
SceneFlexItem,
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getMultipleGridLayoutTest(): DashboardScene {
return new DashboardScene({
title: 'Multiple grid layouts test',
body: new SceneFlexLayout({
children: [
new SceneFlexItem({
body: new SceneGridLayout({
children: [
new SceneGridItem({
x: 0,
y: 0,
width: 12,
height: 10,
isDraggable: true,
isResizable: true,
body: PanelBuilders.timeseries().setTitle('Dragabble and resizable').build(),
}),
new SceneGridItem({
x: 12,
y: 0,
width: 12,
height: 10,
isResizable: false,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Draggable only').build(),
}),
new SceneGridItem({
x: 6,
y: 11,
width: 12,
height: 10,
isResizable: false,
isDraggable: true,
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexItem({
ySizing: 'fill',
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
new SceneFlexItem({
ySizing: 'fill',
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
],
}),
}),
],
}),
}),
new SceneFlexItem({
body: new SceneGridLayout({
children: [
new SceneGridItem({
x: 0,
y: 0,
width: 12,
height: 10,
isDraggable: true,
isResizable: true,
body: PanelBuilders.timeseries().setTitle('Dragabble and resizable').build(),
}),
new SceneGridItem({
x: 12,
y: 0,
width: 12,
height: 10,
isResizable: false,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Draggable only').build(),
}),
new SceneGridItem({
x: 6,
y: 11,
width: 12,
height: 10,
isResizable: false,
isDraggable: true,
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexItem({
ySizing: 'fill',
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
new SceneFlexItem({
ySizing: 'fill',
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
],
}),
}),
],
}),
}),
],
}),
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
});
}

@ -1,105 +0,0 @@
import {
SceneGridRow,
SceneTimePicker,
SceneGridLayout,
SceneTimeRange,
SceneRefreshPicker,
SceneGridItem,
PanelBuilders,
} from '@grafana/scenes';
import { TestDataQueryType } from '@grafana-plugins/grafana-testdata-datasource/dataquery.gen';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getGridWithMultipleData(): DashboardScene {
return new DashboardScene({
title: 'Grid with rows and different queries',
body: new SceneGridLayout({
children: [
new SceneGridRow({
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery({ scenarioId: TestDataQueryType.RandomWalkTable }),
title: 'Row A - has its own query',
key: 'Row A',
isCollapsed: true,
y: 0,
children: [
new SceneGridItem({
x: 0,
y: 1,
width: 12,
height: 5,
isResizable: true,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Row A Child1').build(),
}),
new SceneGridItem({
x: 0,
y: 5,
width: 6,
height: 5,
isResizable: true,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Row A Child2').build(),
}),
],
}),
new SceneGridRow({
title: 'Row B - uses global query',
key: 'Row B',
isCollapsed: true,
y: 1,
children: [
new SceneGridItem({
x: 0,
y: 2,
width: 12,
height: 5,
isResizable: false,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Row B Child1').build(),
}),
new SceneGridItem({
x: 0,
y: 7,
width: 6,
height: 5,
isResizable: false,
isDraggable: true,
body: PanelBuilders.timeseries()
.setTitle('Row B Child2 with data')
.setData(getQueryRunnerWithRandomWalkQuery({ seriesCount: 10 }))
.build(),
}),
],
}),
new SceneGridItem({
x: 0,
y: 12,
width: 6,
height: 10,
isResizable: true,
isDraggable: true,
body: PanelBuilders.timeseries()
.setTitle('Outsider, has its own query')
.setData(getQueryRunnerWithRandomWalkQuery({ seriesCount: 10 }))
.build(),
}),
new SceneGridItem({
x: 6,
y: 12,
width: 12,
height: 10,
isResizable: true,
isDraggable: true,
body: PanelBuilders.timeseries().setTitle('Outsider, uses global query').build(),
}),
],
}),
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
});
}

@ -1,45 +0,0 @@
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getGridWithMultipleTimeRanges } from './gridMultiTimeRange';
import { getMultipleGridLayoutTest } from './gridMultiple';
import { getGridWithMultipleData } from './gridWithMultipleData';
import { getQueryVariableDemo } from './queryVariableDemo';
import { getRepeatingPanelsDemo, getRepeatingRowsDemo } from './repeatingPanels';
import { getSceneWithRows } from './sceneWithRows';
import { getTransformationsDemo } from './transformations';
import { getVariablesDemo, getVariablesDemoWithAll } from './variablesDemo';
interface SceneDef {
title: string;
getScene: () => DashboardScene;
}
export function getScenes(): SceneDef[] {
return [
{ title: 'Scene with rows', getScene: getSceneWithRows },
{ title: 'Grid with rows and different queries', getScene: getGridWithMultipleData },
{ title: 'Grid with rows and different queries and time ranges', getScene: getGridWithMultipleTimeRanges },
{ title: 'Multiple grid layouts test', getScene: getMultipleGridLayoutTest },
{ title: 'Variables', getScene: getVariablesDemo },
{ title: 'Variables with All values', getScene: getVariablesDemoWithAll },
{ title: 'Variables - Repeating panels', getScene: getRepeatingPanelsDemo },
{ title: 'Variables - Repeating rows', getScene: getRepeatingRowsDemo },
{ title: 'Query variable', getScene: getQueryVariableDemo },
{ title: 'Transformations demo', getScene: getTransformationsDemo },
];
}
const cache: Record<string, DashboardScene> = {};
export function getSceneByTitle(title: string) {
if (cache[title]) {
return cache[title];
}
const scene = getScenes().find((x) => x.title === title);
if (scene) {
cache[title] = scene.getScene();
}
return cache[title];
}

@ -1,22 +0,0 @@
import { QueryRunnerState, SceneQueryRunner } from '@grafana/scenes';
import { TestData } from '@grafana-plugins/grafana-testdata-datasource/dataquery.gen';
export function getQueryRunnerWithRandomWalkQuery(
overrides?: Partial<TestData>,
queryRunnerOverrides?: Partial<QueryRunnerState>
) {
return new SceneQueryRunner({
queries: [
{
refId: 'A',
datasource: {
uid: 'gdev-testdata',
type: 'testdata',
},
scenarioId: 'random_walk',
...overrides,
},
],
...queryRunnerOverrides,
});
}

@ -1,68 +0,0 @@
import { VariableRefresh } from '@grafana/data';
import {
SceneCanvasText,
SceneTimePicker,
SceneFlexLayout,
SceneTimeRange,
VariableValueSelectors,
SceneVariableSet,
CustomVariable,
DataSourceVariable,
QueryVariable,
SceneRefreshPicker,
SceneFlexItem,
} from '@grafana/scenes';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
export function getQueryVariableDemo(): DashboardScene {
return new DashboardScene({
title: 'Query variable',
$variables: new SceneVariableSet({
variables: [
new CustomVariable({
name: 'metric',
query: 'job : job, instance : instance',
}),
new DataSourceVariable({
name: 'datasource',
pluginId: 'prometheus',
}),
new QueryVariable({
name: 'instance (using datasource variable)',
refresh: VariableRefresh.onTimeRangeChanged,
query: { query: 'label_values(go_gc_duration_seconds, ${metric})', refId: 'A' },
datasource: { uid: '${datasource}' },
}),
new QueryVariable({
name: 'label values (on time range refresh)',
refresh: VariableRefresh.onTimeRangeChanged,
query: { query: 'label_values(go_gc_duration_seconds, ${metric})', refId: 'B' },
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
}),
new QueryVariable({
name: 'legacy (graphite)',
refresh: VariableRefresh.onTimeRangeChanged,
query: { queryType: 'Default', target: 'stats.response.*', refId: 'C' },
datasource: { uid: 'gdev-graphite', type: 'graphite' },
}),
],
}),
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
width: '40%',
body: new SceneCanvasText({
text: 'metric: ${metric}',
fontSize: 20,
align: 'center',
}),
}),
],
}),
$timeRange: new SceneTimeRange(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
controls: [new VariableValueSelectors({})],
});
}

@ -1,189 +0,0 @@
import {
SceneTimePicker,
SceneTimeRange,
VariableValueSelectors,
SceneVariableSet,
TestVariable,
SceneRefreshPicker,
PanelBuilders,
SceneGridLayout,
SceneControlsSpacer,
SceneGridRow,
} from '@grafana/scenes';
import { VariableRefresh } from '@grafana/schema';
import { PanelRepeaterGridItem } from 'app/features/dashboard-scene/scene/PanelRepeaterGridItem';
import { RowRepeaterBehavior } from 'app/features/dashboard-scene/scene/RowRepeaterBehavior';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
/**
* Repeat panels by variable that changes with time refresh. This tries to setup a very specific scenario
* where a variable that is slow (2s) and constantly changing it's result is used to repeat panels. This
* can be used to verify that when the time range change the repeated panels with locally scoped variable value
* still wait for the top level variable to finish loading and the repeat process to complete.
*/
export function getRepeatingPanelsDemo(): DashboardScene {
return new DashboardScene({
title: 'Variables - Repeating panels',
$variables: new SceneVariableSet({
variables: [
new TestVariable({
name: 'server',
query: 'AB',
value: 'server',
text: '',
delayMs: 2000,
isMulti: true,
includeAll: true,
refresh: VariableRefresh.onTimeRangeChanged,
optionsToReturn: [
{ label: 'A', value: 'A' },
{ label: 'B', value: 'B' },
],
options: [],
$behaviors: [changeVariable],
}),
],
}),
body: new SceneGridLayout({
isDraggable: true,
isResizable: true,
children: [
new PanelRepeaterGridItem({
variableName: 'server',
x: 0,
y: 0,
width: 24,
height: 8,
itemHeight: 8,
source: PanelBuilders.timeseries()
.setTitle('server = $server')
.setData(getQueryRunnerWithRandomWalkQuery({ alias: 'server = $server' }))
.build(),
}),
],
}),
$timeRange: new SceneTimeRange(),
actions: [],
controls: [
new VariableValueSelectors({}),
new SceneControlsSpacer(),
new SceneTimePicker({}),
new SceneRefreshPicker({}),
],
});
}
function changeVariable(variable: TestVariable) {
const sub = variable.subscribeToState((state, old) => {
if (!state.loading && old.loading) {
if (variable.state.optionsToReturn?.length === 2) {
variable.setState({
query: 'ABC',
optionsToReturn: [
{ label: 'A', value: 'A' },
{ label: 'B', value: 'B' },
{ label: 'C', value: 'C' },
],
});
} else {
variable.setState({
query: 'AB',
optionsToReturn: [
{ label: 'A', value: 'A' },
{ label: 'B', value: 'B' },
],
});
}
}
});
return () => {
sub.unsubscribe();
};
}
export function getRepeatingRowsDemo(): DashboardScene {
return new DashboardScene({
title: 'Variables - Repeating rows',
$variables: new SceneVariableSet({
variables: [
new TestVariable({
name: 'server',
query: 'AB',
value: ['A', 'B', 'C'],
text: ['A', 'B', 'C'],
delayMs: 2000,
isMulti: true,
includeAll: true,
refresh: VariableRefresh.onTimeRangeChanged,
optionsToReturn: [
{ label: 'A', value: 'A' },
{ label: 'B', value: 'B' },
{ label: 'C', value: 'C' },
],
options: [],
//$behaviors: [changeVariable],
}),
new TestVariable({
name: 'pod',
query: 'AB',
value: ['Mu', 'Ma', 'Mi'],
text: ['Mu', 'Ma', 'Mi'],
delayMs: 2000,
isMulti: true,
includeAll: true,
refresh: VariableRefresh.onTimeRangeChanged,
optionsToReturn: [
{ label: 'Mu', value: 'Mu' },
{ label: 'Ma', value: 'Ma' },
{ label: 'Mi', value: 'Mi' },
],
options: [],
}),
],
}),
body: new SceneGridLayout({
isDraggable: true,
isResizable: true,
children: [
new SceneGridRow({
title: 'Row $server',
key: 'Row A',
isCollapsed: false,
y: 0,
x: 0,
$behaviors: [
new RowRepeaterBehavior({
variableName: 'server',
sources: [
new PanelRepeaterGridItem({
variableName: 'pod',
x: 0,
y: 0,
width: 24,
height: 5,
itemHeight: 5,
source: PanelBuilders.timeseries()
.setTitle('server = $server, pod = $pod')
.setData(getQueryRunnerWithRandomWalkQuery({ alias: 'server = $server, pod = $pod' }))
.build(),
}),
],
}),
],
}),
],
}),
$timeRange: new SceneTimeRange(),
actions: [],
controls: [
new VariableValueSelectors({}),
new SceneControlsSpacer(),
new SceneTimePicker({}),
new SceneRefreshPicker({}),
],
});
}

@ -1,58 +0,0 @@
import {
NestedScene,
SceneTimePicker,
SceneFlexLayout,
SceneTimeRange,
SceneRefreshPicker,
SceneFlexItem,
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getSceneWithRows(): DashboardScene {
return new DashboardScene({
title: 'Scene with rows',
body: new SceneFlexLayout({
direction: 'column',
children: [
new NestedScene({
title: 'Overview',
canCollapse: true,
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
new SceneFlexItem({
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
],
}),
}),
new NestedScene({
title: 'More server details',
canCollapse: true,
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
new SceneFlexItem({
body: PanelBuilders.timeseries().setTitle('Fill height').build(),
}),
],
}),
}),
],
}),
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
});
}

@ -1,79 +0,0 @@
import {
SceneTimePicker,
SceneFlexLayout,
SceneDataTransformer,
SceneTimeRange,
SceneRefreshPicker,
SceneFlexItem,
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getTransformationsDemo(): DashboardScene {
return new DashboardScene({
title: 'Transformations demo',
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexItem({
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: PanelBuilders.timeseries().setTitle('Source data (global query)').build(),
}),
new SceneFlexItem({
body: PanelBuilders.stat()
.setTitle('Transformed data')
.setData(
new SceneDataTransformer({
transformations: [
{
id: 'reduce',
options: {
reducers: ['last', 'mean'],
},
},
],
})
)
.build(),
}),
],
}),
}),
new SceneFlexItem({
body: PanelBuilders.stat()
.setTitle('Query with predefined transformations')
.setData(
new SceneDataTransformer({
$data: getQueryRunnerWithRandomWalkQuery(),
transformations: [
{
id: 'reduce',
options: {
reducers: ['mean'],
},
},
],
})
)
.build(),
}),
],
}),
}),
],
}),
$timeRange: new SceneTimeRange(),
$data: getQueryRunnerWithRandomWalkQuery(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
});
}

@ -1,203 +0,0 @@
import {
SceneCanvasText,
SceneTimePicker,
SceneFlexLayout,
SceneTimeRange,
VariableValueSelectors,
SceneVariableSet,
CustomVariable,
DataSourceVariable,
TestVariable,
NestedScene,
SceneRefreshPicker,
TextBoxVariable,
SceneFlexItem,
PanelBuilders,
} from '@grafana/scenes';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
import { getQueryRunnerWithRandomWalkQuery } from './queries';
export function getVariablesDemo(): DashboardScene {
return new DashboardScene({
title: 'Variables',
$variables: new SceneVariableSet({
variables: [
new TestVariable({
name: 'server',
query: 'A.*',
value: 'server',
text: '',
delayMs: 1000,
options: [],
}),
new TestVariable({
name: 'pod',
query: 'A.$server.*',
value: 'pod',
delayMs: 1000,
isMulti: true,
text: '',
options: [],
}),
new TestVariable({
name: 'handler',
query: 'A.$server.$pod.*',
value: 'handler',
delayMs: 1000,
//isMulti: true,
text: '',
options: [],
}),
new CustomVariable({
name: 'custom',
query: 'A : 10,B : 20',
}),
new DataSourceVariable({
name: 'ds',
pluginId: 'testdata',
}),
new TextBoxVariable({
name: 'textbox',
value: 'default value',
}),
],
}),
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexItem({
body: new SceneFlexLayout({
children: [
new SceneFlexItem({
body: PanelBuilders.timeseries()
.setTitle('handler: $handler')
.setData(
getQueryRunnerWithRandomWalkQuery({
alias: 'handler: $handler',
})
)
.build(),
}),
new SceneFlexItem({
body: new SceneCanvasText({
text: 'Text: ${textbox}',
fontSize: 20,
align: 'center',
}),
}),
new SceneFlexItem({
width: '40%',
body: new SceneCanvasText({
text: 'server: ${server} pod:${pod}',
fontSize: 20,
align: 'center',
}),
}),
],
}),
}),
new SceneFlexItem({
body: new NestedScene({
title: 'Collapsable inner scene',
canCollapse: true,
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: PanelBuilders.timeseries()
.setTitle('handler: $handler')
.setData(
getQueryRunnerWithRandomWalkQuery({
alias: 'handler: $handler',
})
)
.build(),
}),
],
}),
}),
}),
],
}),
}),
],
}),
$timeRange: new SceneTimeRange(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
controls: [new VariableValueSelectors({})],
});
}
export function getVariablesDemoWithAll(): DashboardScene {
return new DashboardScene({
title: 'Variables with All values',
$variables: new SceneVariableSet({
variables: [
new TestVariable({
name: 'server',
query: 'A.*',
value: 'AA',
text: 'AA',
includeAll: true,
defaultToAll: true,
delayMs: 1000,
options: [],
}),
new TestVariable({
name: 'pod',
query: 'A.$server.*',
value: [],
delayMs: 1000,
isMulti: true,
includeAll: true,
defaultToAll: true,
text: '',
options: [],
}),
new TestVariable({
name: 'handler',
query: 'A.$server.$pod.*',
value: [],
delayMs: 1000,
includeAll: true,
defaultToAll: false,
isMulti: true,
text: '',
options: [],
}),
],
}),
body: new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
body: PanelBuilders.timeseries()
.setTitle('handler: $handler')
.setData(
getQueryRunnerWithRandomWalkQuery({
alias: 'handler: $handler',
})
)
.build(),
}),
new SceneFlexItem({
width: '40%',
body: new SceneCanvasText({
text: 'server: ${server} pod:${pod}',
fontSize: 20,
align: 'center',
}),
}),
],
}),
$timeRange: new SceneTimeRange(),
actions: [new SceneTimePicker({}), new SceneRefreshPicker({})],
controls: [new VariableValueSelectors({})],
});
}

@ -38,6 +38,12 @@ export function getAppRoutes(): RouteDescriptor[] {
() => import(/* webpackChunkName: "DashboardPageProxy" */ '../features/dashboard/containers/DashboardPageProxy')
),
},
{
path: '/d/:uid/panel-edit/:panelId',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "scenes"*/ 'app/features/dashboard-scene/pages/PanelEditPage')
),
},
{
path: '/d/:uid/:slug?',
pageClass: 'page-dashboard',
@ -488,7 +494,6 @@ export function getAppRoutes(): RouteDescriptor[] {
() => import(/* webpackChunkName: "DataTrailsPage"*/ 'app/features/trails/DataTrailsPage')
),
},
...getDynamicDashboardRoutes(),
...getPluginCatalogRoutes(),
...getSupportBundleRoutes(),
...getAlertingRoutes(),
@ -523,38 +528,3 @@ export function getSupportBundleRoutes(cfg = config): RouteDescriptor[] {
},
];
}
export function getDynamicDashboardRoutes(cfg = config): RouteDescriptor[] {
if (!cfg.featureToggles.scenes) {
return [];
}
return [
{
path: '/scenes',
component: SafeDynamicImport(() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/SceneListPage')),
},
{
path: '/scenes/dashboard/:uid',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "scenes"*/ 'app/features/dashboard-scene/pages/DashboardScenePage')
),
},
{
path: '/scenes/dashboard/:uid/panel-edit/:panelId',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "scenes"*/ 'app/features/dashboard-scene/pages/PanelEditPage')
),
},
{
path: '/scenes/grafana-monitoring',
exact: false,
component: SafeDynamicImport(
() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/apps/GrafanaMonitoringApp')
),
},
{
path: '/scenes/:name',
component: SafeDynamicImport(() => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/ScenePage')),
},
];
}

Loading…
Cancel
Save