diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b9e0ee5c1fb..7eeba60c96a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 6015c1ff5b4..85b9d11d19f 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -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", diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index 1392b03b025..de217bf914b 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -121,7 +121,6 @@ export class PanelEditor extends SceneObjectBase { getDashboardUrl({ uid: this.state.dashboardRef.resolve().state.uid, currentQueryParams: locationService.getLocation().search, - useExperimentalURL: true, }) ); } diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 75fdf7afe91..9f56554ac8d 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -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 { uid: this.state.uid, currentQueryParams: location.search, updateQuery: { viewPanel: null, inspect: null, editview: null }, - useExperimentalURL: Boolean(config.featureToggles.dashboardSceneForViewers && meta.canEdit), }), }; diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx index 4e5a40c5fdb..1053fc43cc0 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx @@ -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= - 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= - 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); diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index caab523d936..f8e3407681c 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -71,7 +71,6 @@ export function panelMenuBehavior(menu: VizPanelMenu) { uid: dashboard.state.uid, subPath: `/panel-edit/${panelId}`, currentQueryParams: location.search, - useExperimentalURL: true, }), }); } diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx index e9e2e35a7ab..765b54bb6d8 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx @@ -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' ); }); diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index 0366ad8b838..ddaaa11bfa5 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -76,7 +76,6 @@ export class ShareLinkTab extends SceneObjectBase { currentQueryParams: location.search, updateQuery: urlParamsUpdate, absolute: true, - useExperimentalURL: Boolean(config.featureToggles.dashboardSceneForViewers && dashboard.state.meta.canEdit), }); if (useShortUrl) { diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.test.ts b/public/app/features/dashboard-scene/utils/urlBuilders.test.ts index 192f6e8f78d..85af2c90570 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.test.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.test.ts @@ -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'); }); }); diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.ts b/public/app/features/dashboard-scene/utils/urlBuilders.ts index 6c67abbc33d..247ac35929b 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.ts @@ -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 ?? ''}`; diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 830699e787c..c56ba1a44f5 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -203,8 +203,7 @@ export const DashNav = React.memo((props) => { tooltip={'View as Scene'} icon="apps" onClick={() => { - const location = locationService.getLocation(); - locationService.push(`/scenes/dashboard/${dashboard.uid}${location.search}`); + locationService.partial({ scenes: true }); }} /> ); diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index 5d895b96c90..b059b753c64 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -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 ; } diff --git a/public/app/features/dashboard/containers/types.ts b/public/app/features/dashboard/containers/types.ts index d7416f6f104..717eb8bd39e 100644 --- a/public/app/features/dashboard/containers/types.ts +++ b/public/app/features/dashboard/containers/types.ts @@ -18,4 +18,5 @@ export type DashboardPageRouteSearchParams = { to?: string; refresh?: string; kiosk?: string | true; + scenes?: boolean; }; diff --git a/public/app/features/scenes/SceneListPage.tsx b/public/app/features/scenes/SceneListPage.tsx deleted file mode 100644 index 643a38a31c9..00000000000 --- a/public/app/features/scenes/SceneListPage.tsx +++ /dev/null @@ -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 ( - - - -
Apps
- - - Grafana monitoring - - -
Test scenes
- - {scenes.map((scene) => ( - - {scene.title} - - ))} - - {results.value && ( - <> -
Starred dashboards
- - {results.value!.view.map((dash) => ( - - {dash.name} - - ))} - - - )} -
-
-
- ); -}; - -export default SceneListPage; diff --git a/public/app/features/scenes/ScenePage.tsx b/public/app/features/scenes/ScenePage.tsx deleted file mode 100644 index eacdd2ae720..00000000000 --- a/public/app/features/scenes/ScenePage.tsx +++ /dev/null @@ -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

Scene not found

; - } - - if (!isInitialized) { - return null; - } - - return ; -}; - -export default ScenePage; diff --git a/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx b/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx deleted file mode 100644 index a19e41bbdc7..00000000000 --- a/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx +++ /dev/null @@ -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({ sectionNav }); - - return ( - - - - ); -} - -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; diff --git a/public/app/features/scenes/apps/SceneRadioToggle.tsx b/public/app/features/scenes/apps/SceneRadioToggle.tsx deleted file mode 100644 index 7834ac0b2b9..00000000000 --- a/public/app/features/scenes/apps/SceneRadioToggle.tsx +++ /dev/null @@ -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>; - value: string; - onChange: (value: string) => void; -} - -export class SceneRadioToggle extends SceneObjectBase { - onChange = (value: string) => { - this.setState({ value }); - this.state.onChange(value); - }; - - static Component = ({ model }: SceneComponentProps) => { - const { options, value } = model.useState(); - - return ; - }; -} diff --git a/public/app/features/scenes/apps/SceneSearchBox.tsx b/public/app/features/scenes/apps/SceneSearchBox.tsx deleted file mode 100644 index 760799ec637..00000000000 --- a/public/app/features/scenes/apps/SceneSearchBox.tsx +++ /dev/null @@ -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 { - onChange = (evt: React.FormEvent) => { - this.setState({ value: evt.currentTarget.value }); - }; - - static Component = ({ model }: SceneComponentProps) => { - const { value } = model.useState(); - - return ; - }; -} diff --git a/public/app/features/scenes/apps/scenes.tsx b/public/app/features/scenes/apps/scenes.tsx deleted file mode 100644 index a3fe0714268..00000000000 --- a/public/app/features/scenes/apps/scenes.tsx +++ /dev/null @@ -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( - - Details - - ) - .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; -} diff --git a/public/app/features/scenes/apps/traffic.tsx b/public/app/features/scenes/apps/traffic.tsx deleted file mode 100644 index 8e561e40add..00000000000 --- a/public/app/features/scenes/apps/traffic.tsx +++ /dev/null @@ -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 { - 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( -