diff --git a/package.json b/package.json index f6531baebc3..3f77100ddd2 100644 --- a/package.json +++ b/package.json @@ -267,7 +267,7 @@ "@grafana/lezer-logql": "0.1.2", "@grafana/monaco-logql": "^0.0.7", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^0.0.19", + "@grafana/scenes": "^0.0.21", "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "5.3.6", diff --git a/packages/grafana-data/src/types/fieldOverrides.ts b/packages/grafana-data/src/types/fieldOverrides.ts index 46e9c8b1689..090d37bfd01 100644 --- a/packages/grafana-data/src/types/fieldOverrides.ts +++ b/packages/grafana-data/src/types/fieldOverrides.ts @@ -132,4 +132,5 @@ export enum FieldConfigProperty { Mappings = 'mappings', Links = 'links', Color = 'color', + Filterable = 'filterable', } diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index a24289f01db..19519613225 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -140,8 +140,8 @@ const getStyles = (theme: GrafanaTheme2) => { const defaultTopNav = css` color: ${theme.colors.text.secondary}; - background-color: transparent; - border-color: transparent; + background: transparent; + border: 1px solid transparent; &:hover { color: ${theme.colors.text.primary}; diff --git a/public/app/features/scenes/SceneEmbeddedPage.tsx b/public/app/features/scenes/SceneEmbeddedPage.tsx deleted file mode 100644 index 7b7b2c06500..00000000000 --- a/public/app/features/scenes/SceneEmbeddedPage.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// Libraries -import React from 'react'; - -import { NavModelItem } from '@grafana/data'; -import { Page } from 'app/core/components/Page/Page'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; - -import { getSceneByTitle } from './scenes'; - -export interface Props extends GrafanaRouteComponentProps<{ name: string }> {} - -export const SceneEmbeddedPage = (props: Props) => { - const scene = getSceneByTitle(props.match.params.name); - - if (!scene) { - return

Scene not found

; - } - - const pageNav: NavModelItem = { - text: 'Embedded Scene', - }; - - return ( - - - - - - ); -}; - -export default SceneEmbeddedPage; diff --git a/public/app/features/scenes/SceneListPage.tsx b/public/app/features/scenes/SceneListPage.tsx index 33078f91313..9310fd2802b 100644 --- a/public/app/features/scenes/SceneListPage.tsx +++ b/public/app/features/scenes/SceneListPage.tsx @@ -23,6 +23,12 @@ export const SceneListPage = ({}: Props) => { +
Apps
+ + + Grafana monitoring + +
Test scenes
{scenes.map((scene) => ( diff --git a/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx b/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx new file mode 100644 index 00000000000..e42b2cd068c --- /dev/null +++ b/public/app/features/scenes/apps/GrafanaMonitoringApp.tsx @@ -0,0 +1,144 @@ +// Libraries +import React, { useMemo, useState } from 'react'; + +import { + SceneCanvasText, + SceneFlexLayout, + SceneApp, + SceneAppPage, + SceneRouteMatch, + EmbeddedScene, + 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'; + +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: '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'], + drilldowns: [ + { + routePath: '/scenes/grafana-monitoring/handlers/:handler/logs/:secondLevel', + getPage: getSecondLevelDrilldown, + }, + ], + }), + ], + }); +} + +export function getSecondLevelDrilldown( + match: SceneRouteMatch<{ handler: string; secondLevel: string }>, + parent: SceneAppPageLike +) { + const handler = decodeURIComponent(match.params.handler); + const secondLevel = decodeURIComponent(match.params.secondLevel); + const baseUrl = `/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}/logs/${secondLevel}`; + + return new SceneAppPage({ + title: secondLevel, + subTitle: 'Second level dynamic drilldown', + url: baseUrl, + getParentPage: () => parent, + getScene: () => { + return new EmbeddedScene({ + body: new SceneFlexLayout({ + children: [ + new SceneCanvasText({ + text: 'Drilldown: ' + secondLevel, + }), + ], + }), + }); + }, + }); +} + +export default GrafanaMonitoringApp; diff --git a/public/app/features/scenes/apps/SceneRadioToggle.tsx b/public/app/features/scenes/apps/SceneRadioToggle.tsx new file mode 100644 index 00000000000..060b9327de2 --- /dev/null +++ b/public/app/features/scenes/apps/SceneRadioToggle.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { SceneComponentProps, SceneObjectBase, SceneObjectStatePlain } from '@grafana/scenes'; +import { RadioButtonGroup } from '@grafana/ui'; + +export interface SceneRadioToggleState extends SceneObjectStatePlain { + options: Array>; + value: string; + onChange: (value: string) => void; +} + +export class SceneRadioToggle extends SceneObjectBase { + public onChange = (value: string) => { + this.setState({ value }); + this.state.onChange(value); + }; + + public 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 new file mode 100644 index 00000000000..0a4a5dfb57c --- /dev/null +++ b/public/app/features/scenes/apps/SceneSearchBox.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { SceneComponentProps, SceneObjectStatePlain, SceneObjectBase } from '@grafana/scenes'; +import { Input } from '@grafana/ui'; + +export interface SceneSearchBoxState extends SceneObjectStatePlain { + value: string; +} + +export class SceneSearchBox extends SceneObjectBase { + public onChange = (evt: React.FormEvent) => { + this.setState({ value: evt.currentTarget.value }); + }; + + public static Component = ({ model }: SceneComponentProps) => { + const { value } = model.useState(); + + return ; + }; +} diff --git a/public/app/features/scenes/apps/scenes.ts b/public/app/features/scenes/apps/scenes.ts new file mode 100644 index 00000000000..40aafdc3280 --- /dev/null +++ b/public/app/features/scenes/apps/scenes.ts @@ -0,0 +1,536 @@ +import { FieldColorModeId, getFrameDisplayName } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; +import { + SceneFlexLayout, + SceneByFrameRepeater, + SceneTimePicker, + VizPanel, + EmbeddedScene, + SceneDataNode, + SceneTimeRange, + VariableValueSelectors, + SceneQueryRunner, + SceneVariableSet, + QueryVariable, + SceneControlsSpacer, + SceneDataTransformer, + SceneRefreshPicker, +} from '@grafana/scenes'; +import { PromQuery } from 'app/plugins/datasource/prometheus/types'; + +import { SceneRadioToggle } from './SceneRadioToggle'; +import { SceneSearchBox } from './SceneSearchBox'; +import { getTableFilterTransform, getTimeSeriesFilterTransform } from './transforms'; +import { getLinkUrlWithAppUrlState } 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 = new VizPanel({ + $data: httpHandlerQueriesFiltered, + pluginId: 'table', + title: '', + 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', + onBuildUrl: () => { + const params = locationService.getSearchObject(); + return getLinkUrlWithAppUrlState( + '/scenes/grafana-monitoring/handlers/${__value.text:percentencode}', + params + ); + }, + }, + ], + }, + ], + }, + ], + }, + }); + + 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 SceneFlexLayout({ + key: `panel-${frameIndex}`, + direction: 'row', + placement: { minHeight: 200 }, + $data: new SceneDataNode({ + data: { + ...data, + series: [frame], + }, + }), + children: [ + new VizPanel({ + pluginId: 'timeseries', + // titleLink: { + // path: `/scenes/grafana-monitoring/handlers/${encodeURIComponent(frame.fields[1].labels.handler)}`, + // queryKeys: ['from', 'to', 'var-instance'], + // }, + title: getFrameDisplayName(frame), + options: { + legend: { displayMode: 'hidden' }, + }, + }), + new VizPanel({ + placement: { width: 200 }, + title: 'Last', + pluginId: 'stat', + fieldConfig: { + defaults: { + displayName: 'Last', + 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: [], + }, + options: { + graphMode: 'none', + textMode: 'value', + }, + }), + ], + }); + }, + }); + + const layout = new SceneFlexLayout({ + children: [httpHandlersTable], + }); + + const sceneToggle = new SceneRadioToggle({ + options: [ + { value: 'table', label: 'Table' }, + { value: 'graphs', label: 'Graphs' }, + ], + value: 'table', + onChange: (value) => { + if (value === 'table') { + layout.setState({ children: [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; +} + +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 VizPanel({ + $data: reqDurationTimeSeries, + pluginId: 'timeseries', + title: 'Request duration avg (ms)', + placement: {}, + //displayMode: 'transparent', + options: {}, + }), + new VizPanel({ + $data: reqCountTimeSeries, + pluginId: 'timeseries', + title: 'Request count/s', + //displayMode: 'transparent', + options: {}, + }), + ], + }), + }); + + return scene; +} + +function getInstantQuery(query: Partial): SceneQueryRunner { + return new SceneQueryRunner({ + datasource: { uid: 'gdev-prometheus' }, + queries: [ + { + refId: 'A', + instant: true, + format: 'table', + maxDataPoints: 500, + ...query, + }, + ], + }); +} + +function getTimeSeriesQuery(query: Partial): SceneQueryRunner { + return new SceneQueryRunner({ + datasource: { uid: 'gdev-prometheus' }, + queries: [ + { + refId: 'A', + range: true, + format: 'time_series', + maxDataPoints: 500, + ...query, + }, + ], + }); +} + +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 SceneFlexLayout({ + placement: { height: 150 }, + children: [ + getInstantStatPanel('grafana_stat_totals_dashboard', 'Dashboards'), + getInstantStatPanel('grafana_stat_total_users', 'Users'), + getInstantStatPanel('sum(grafana_stat_totals_datasource)', 'Data sources'), + getInstantStatPanel('grafana_stat_total_service_account_tokens', 'Service account tokens'), + ], + }), + new VizPanel({ + $data: 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, + }, + ], + }), + pluginId: 'timeseries', + title: 'Memory usage', + options: { + legend: { + showLegend: false, + }, + }, + fieldConfig: { + defaults: { + unit: 'bytes', + min: 0, + custom: { + lineWidth: 2, + fillOpacity: 6, + //gradientMode: 'opacity', + }, + }, + overrides: [], + }, + }), + new VizPanel({ + $data: new SceneQueryRunner({ + datasource: { uid: 'gdev-prometheus' }, + queries: [ + { + refId: 'A', + expr: `sum(go_goroutines{job="grafana", instance=~"$instance"})`, + range: true, + format: 'time_series', + maxDataPoints: 500, + }, + ], + }), + pluginId: 'timeseries', + title: 'Go routines', + options: { + legend: { + showLegend: false, + }, + }, + fieldConfig: { + defaults: { + min: 0, + custom: { + lineWidth: 2, + fillOpacity: 6, + //gradientMode: 'opacity', + }, + }, + overrides: [], + }, + }), + ], + }), + }); + + 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) { + return new VizPanel({ + $data: getInstantQuery({ expr: query }), + pluginId: 'stat', + title, + options: {}, + fieldConfig: { + defaults: { + color: { fixedColor: 'text', mode: FieldColorModeId.Fixed }, + }, + overrides: [], + }, + }); +} + +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 VizPanel({ + pluginId: 'text', + title: '', + options: { + mode: 'markdown', + content: ` +[mupp](/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}/logs/mupp) +[mapp](/scenes/grafana-monitoring/handlers/${encodeURIComponent(handler)}/logs/mapp) +`, + }, + }), + new VizPanel({ + $data: logsQuery, + pluginId: 'logs', + title: '', + options: { + showTime: true, + showLabels: false, + showCommonLabels: false, + wrapLogMessage: true, + prettifyLogMessage: false, + enableLogDetails: true, + dedupStrategy: 'none', + sortOrder: 'Descending', + }, + }), + ], + }), + }); + + 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 VizPanel({ + $data: logsQuery, + pluginId: 'logs', + title: '', + options: { + showTime: true, + showLabels: false, + showCommonLabels: false, + wrapLogMessage: true, + prettifyLogMessage: false, + enableLogDetails: true, + dedupStrategy: 'none', + sortOrder: 'Descending', + }, + }), + ], + }), + }); + + return scene; +} diff --git a/public/app/features/scenes/apps/transforms.ts b/public/app/features/scenes/apps/transforms.ts new file mode 100644 index 00000000000..3bbdca4a394 --- /dev/null +++ b/public/app/features/scenes/apps/transforms.ts @@ -0,0 +1,42 @@ +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> = { + 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())); + }) + ); + }; +} diff --git a/public/app/features/scenes/apps/utils.ts b/public/app/features/scenes/apps/utils.ts new file mode 100644 index 00000000000..3a3a1e35f25 --- /dev/null +++ b/public/app/features/scenes/apps/utils.ts @@ -0,0 +1,13 @@ +import { useLocation } from 'react-router-dom'; + +import { UrlQueryMap, urlUtil } from '@grafana/data'; +import { locationSearchToObject } from '@grafana/runtime'; + +export function useAppQueryParams() { + const location = useLocation(); + return locationSearchToObject(location.search || ''); +} + +export function getLinkUrlWithAppUrlState(path: string, params: UrlQueryMap): string { + return urlUtil.renderUrl(path, params); +} diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index ea620348f38..c3bb8ac060d 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -580,9 +580,10 @@ export function getDynamicDashboardRoutes(cfg = config): RouteDescriptor[] { ), }, { - path: '/scenes/embedded/:name', + path: '/scenes/grafana-monitoring', + exact: false, component: SafeDynamicImport( - () => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/SceneEmbeddedPage') + () => import(/* webpackChunkName: "scenes"*/ 'app/features/scenes/apps/GrafanaMonitoringApp') ), }, { diff --git a/yarn.lock b/yarn.lock index 2e901b98303..786797f0bc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5209,9 +5209,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^0.0.19": - version: 0.0.19 - resolution: "@grafana/scenes@npm:0.0.19" +"@grafana/scenes@npm:^0.0.21": + version: 0.0.21 + resolution: "@grafana/scenes@npm:0.0.21" dependencies: "@grafana/e2e-selectors": ^9.4.3 "@grafana/experimental": 1.0.1 @@ -5219,7 +5219,7 @@ __metadata: react-use: 17.4.0 react-virtualized-auto-sizer: 1.0.7 uuid: ^9.0.0 - checksum: e71451751523c33a72d1fe777ac37795166da44c4316a8c1a00ffff936167d49eca91cbe1557a7ffbbfa220c58112dc5c84d45058725efd5c1bf3ba32c2cfb36 + checksum: a36d6b19884a240df1bc3048f72a2a5b7a505371ae30ae29f70f77d6c590d39282a027322b7d49233cb62fa9410eefedb6cb2cb53d938d9394d142ba5734a070 languageName: node linkType: hard @@ -22120,7 +22120,7 @@ __metadata: "@grafana/lezer-logql": 0.1.2 "@grafana/monaco-logql": ^0.0.7 "@grafana/runtime": "workspace:*" - "@grafana/scenes": ^0.0.19 + "@grafana/scenes": ^0.0.21 "@grafana/schema": "workspace:*" "@grafana/toolkit": "workspace:*" "@grafana/tsconfig": ^1.2.0-rc1