Dashboard Controls: Add annotations to the dashboard controls menu (#112816)

* feat: add a placement property to annotations model v2

* chore: update scenes to `v6.42.1`

* chore: run `make gen-apps`

* fix: cater for cases when there is no data layer

* chore: swagger clean

* chore: update api clients

* fix: correct type guard

* fix: display control labels in the default renderer as well for DashboardDataLayerSet
This commit is contained in:
Levente Balogh
2025-11-03 11:33:44 +01:00
committed by GitHub
parent 6d9e28a59f
commit 3c0409fe05
25 changed files with 288 additions and 53 deletions
@@ -117,6 +117,10 @@ DashboardLink: {
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
DashboardLinkPlacement: "inControlsMenu"
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
AnnotationQueryPlacement: "inControlsMenu"
// A topic is attached to DataFrame metadata in query results.
// This specifies where the data should be used.
DataTopic: "series" | "annotations" | "alertStates" @cog(kind="enum",memberNames="Series|Annotations|AlertStates")
@@ -436,6 +440,8 @@ AnnotationQuerySpec: {
name: string
builtIn?: bool | *false
filter?: AnnotationPanelFilter
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
placement?: AnnotationQueryPlacement
legacyOptions?: [string]: _ // Catch-all field for datasource-specific properties. Should not be available in as code tooling.
}
@@ -177,6 +177,9 @@ lineage: schemas: [{
// Set to 1 for the standard annotation query all dashboards have by default.
builtIn?: number | *0
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
placement?: #AnnotationQueryPlacement
// unless datasources have migrated to the target+mapping,
// they just spread their query into the base object :(
...
@@ -302,6 +305,10 @@ lineage: schemas: [{
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
#DashboardLinkPlacement: "inControlsMenu" @cuetsy(kind="type")
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
#AnnotationQueryPlacement: "inControlsMenu" @cuetsy(kind="type")
// Dashboard action type
#ActionType: "fetch" | "infinity" @cuetsy(kind="type")
@@ -177,6 +177,9 @@ lineage: schemas: [{
// Set to 1 for the standard annotation query all dashboards have by default.
builtIn?: number | *0
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
placement?: #AnnotationQueryPlacement
// unless datasources have migrated to the target+mapping,
// they just spread their query into the base object :(
...
@@ -302,6 +305,10 @@ lineage: schemas: [{
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
#DashboardLinkPlacement: "inControlsMenu" @cuetsy(kind="type")
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
#AnnotationQueryPlacement: "inControlsMenu" @cuetsy(kind="type")
// Dashboard action type
#ActionType: "fetch" | "infinity" @cuetsy(kind="type")
@@ -121,6 +121,10 @@ DashboardLink: {
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
DashboardLinkPlacement: "inControlsMenu"
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
AnnotationQueryPlacement: "inControlsMenu"
// A topic is attached to DataFrame metadata in query results.
// This specifies where the data should be used.
DataTopic: "series" | "annotations" | "alertStates" @cog(kind="enum",memberNames="Series|Annotations|AlertStates")
@@ -440,6 +444,8 @@ AnnotationQuerySpec: {
name: string
builtIn?: bool | *false
filter?: AnnotationPanelFilter
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
placement?: AnnotationQueryPlacement
legacyOptions?: [string]: _ // Catch-all field for datasource-specific properties. Should not be available in as code tooling.
}
@@ -30,6 +30,8 @@ type DashboardAnnotationQuerySpec struct {
Name string `json:"name"`
BuiltIn *bool `json:"builtIn,omitempty"`
Filter *DashboardAnnotationPanelFilter `json:"filter,omitempty"`
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
Placement *string `json:"placement,omitempty"`
// Catch-all field for datasource-specific properties. Should not be available in as code tooling.
LegacyOptions map[string]interface{} `json:"legacyOptions,omitempty"`
}
@@ -37,8 +39,9 @@ type DashboardAnnotationQuerySpec struct {
// NewDashboardAnnotationQuerySpec creates a new DashboardAnnotationQuerySpec object.
func NewDashboardAnnotationQuerySpec() *DashboardAnnotationQuerySpec {
return &DashboardAnnotationQuerySpec{
Query: *NewDashboardDataQueryKind(),
BuiltIn: (func(input bool) *bool { return &input })(false),
Query: *NewDashboardDataQueryKind(),
BuiltIn: (func(input bool) *bool { return &input })(false),
Placement: (func(input string) *string { return &input })(DashboardAnnotationQueryPlacement),
}
}
@@ -78,6 +81,11 @@ func NewDashboardAnnotationPanelFilter() *DashboardAnnotationPanelFilter {
}
}
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
// +k8s:openapi-gen=true
const DashboardAnnotationQueryPlacement = "inControlsMenu"
// "Off" for no shared crosshair or tooltip (default).
// "Crosshair" for shared crosshair.
// "Tooltip" for shared crosshair AND shared tooltip.
@@ -760,6 +760,13 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardAnnotationQuerySpec(ref common.R
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAnnotationPanelFilter"),
},
},
"placement": {
SchemaProps: spec.SchemaProps{
Description: "Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.",
Type: []string{"string"},
Format: "",
},
},
"legacyOptions": {
SchemaProps: spec.SchemaProps{
Description: "Catch-all field for datasource-specific properties. Should not be available in as code tooling.",
+7
View File
@@ -173,6 +173,9 @@ lineage: schemas: [{
// Set to 1 for the standard annotation query all dashboards have by default.
builtIn?: number | *0
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
placement?: #AnnotationQueryPlacement
// unless datasources have migrated to the target+mapping,
// they just spread their query into the base object :(
...
@@ -298,6 +301,10 @@ lineage: schemas: [{
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
#DashboardLinkPlacement: "inControlsMenu" @cuetsy(kind="type")
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
#AnnotationQueryPlacement: "inControlsMenu" @cuetsy(kind="type")
// Dashboard action type
#ActionType: "fetch" | "infinity" @cuetsy(kind="type")
+2 -2
View File
@@ -296,8 +296,8 @@
"@grafana/plugin-ui": "^0.10.10",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "^6.41.0",
"@grafana/scenes-react": "^6.41.0",
"@grafana/scenes": "^6.42.1",
"@grafana/scenes-react": "^6.42.1",
"@grafana/schema": "workspace:*",
"@grafana/sql": "workspace:*",
"@grafana/ui": "workspace:*",
@@ -5481,6 +5481,8 @@ export type AnnotationQuery = {
iconColor?: string;
/** Name of annotation. */
name?: string;
/** Placement can be used to display the annotation query somewhere else on the dashboard other than the default location. */
placement?: string;
target?: AnnotationTarget;
/** TODO -- this should not exist here, it is based on the --grafana-- datasource */
type?: string;
+1
View File
@@ -15,6 +15,7 @@ export type {
DashboardLink,
DashboardLinkType,
DashboardLinkPlacement,
AnnotationQueryPlacement,
ActionType,
FetchOptions,
InfinityOptions,
@@ -106,6 +106,10 @@ export interface AnnotationQuery {
* Name of annotation.
*/
name: string;
/**
* Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
*/
placement?: AnnotationQueryPlacement;
/**
* TODO.. this should just be a normal query target
*/
@@ -363,6 +367,12 @@ export type DashboardLinkType = ('link' | 'dashboards');
*/
export type DashboardLinkPlacement = 'inControlsMenu';
/**
* Annotation Query placement. Defines where the annotation query should be displayed.
* - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
*/
export type AnnotationQueryPlacement = 'inControlsMenu';
/**
* Dashboard action type
*/
@@ -18,6 +18,8 @@ export interface AnnotationQuerySpec {
name: string;
builtIn?: boolean;
filter?: AnnotationPanelFilter;
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
placement?: "inControlsMenu";
// Catch-all field for datasource-specific properties. Should not be available in as code tooling.
legacyOptions?: Record<string, any>;
}
@@ -29,6 +31,7 @@ export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({
iconColor: "",
name: "",
builtIn: false,
placement: AnnotationQueryPlacement,
});
export interface DataQueryKind {
@@ -62,6 +65,10 @@ export const defaultAnnotationPanelFilter = (): AnnotationPanelFilter => ({
ids: [],
});
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
export const AnnotationQueryPlacement = "inControlsMenu";
// "Off" for no shared crosshair or tooltip (default).
// "Crosshair" for shared crosshair.
// "Tooltip" for shared crosshair AND shared tooltip.
@@ -968,6 +968,8 @@ type AnnotationQuery struct {
Type *string `json:"type,omitempty"`
// Set to 1 for the standard annotation query all dashboards have by default.
BuiltIn *float64 `json:"builtIn,omitempty"`
// Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.
Placement *string `json:"placement,omitempty"`
}
// NewAnnotationQuery creates a new AnnotationQuery object.
@@ -977,6 +979,7 @@ func NewAnnotationQuery() *AnnotationQuery {
Enable: true,
Hide: (func(input bool) *bool { return &input })(false),
BuiltIn: (func(input float64) *float64 { return &input })(0),
Placement: (func(input string) *string { return &input })(AnnotationQueryPlacement),
}
}
@@ -1019,6 +1022,10 @@ func NewAnnotationTarget() *AnnotationTarget {
}
}
// Annotation Query placement. Defines where the annotation query should be displayed.
// - "inControlsMenu" renders the annotation query in the dashboard controls dropdown menu
const AnnotationQueryPlacement = "inControlsMenu"
// A dashboard snapshot shares an interactive dashboard publicly.
// It is a read-only version of a dashboard, and is not editable.
// It is possible to create a snapshot of a snapshot.
+4
View File
@@ -13735,6 +13735,10 @@
"description": "Name of annotation.",
"type": "string"
},
"placement": {
"description": "Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.",
"type": "string"
},
"target": {
"$ref": "#/definitions/AnnotationTarget"
},
@@ -21,6 +21,7 @@ import { PanelEditControls } from '../panel-edit/PanelEditControls';
import { getDashboardSceneFor } from '../utils/utils';
import { DashboardControlsButton } from './DashboardControlsMenu';
import { DashboardDataLayerControls } from './DashboardDataLayerControls';
import { DashboardLinksControls } from './DashboardLinksControls';
import { DashboardScene } from './DashboardScene';
import { VariableControls } from './VariableControls';
@@ -166,7 +167,7 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
{!hideVariableControls && (
<>
<VariableControls dashboard={dashboard} />
<DataLayerControls dashboard={dashboard} />
<DashboardDataLayerControls dashboard={dashboard} />
</>
)}
<Box grow={1} />
@@ -189,18 +190,6 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
);
}
function DataLayerControls({ dashboard }: { dashboard: DashboardScene }) {
const layers = sceneGraph.getDataLayers(dashboard, true);
return (
<>
{layers.map((layer) => (
<layer.Component model={layer} key={layer.state.key} />
))}
</>
);
}
function renderHiddenVariables(dashboard: DashboardScene) {
const { variables } = sceneGraph.getVariables(dashboard).useState();
const renderAsHiddenVariables = variables.filter((v) => v.UNSAFE_renderAsHidden);
@@ -2,12 +2,14 @@ import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { sceneGraph, SceneVariable } from '@grafana/scenes';
import { SceneDataLayerProvider, sceneGraph, SceneVariable } from '@grafana/scenes';
import { DashboardLink, VariableHide } from '@grafana/schema';
import { Box, Dropdown, Menu, ToolbarButton, useStyles2 } from '@grafana/ui';
import { isDashboardDataLayerSetState } from './DashboardDataLayerSet';
import { DashboardLinkRenderer } from './DashboardLinkRenderer';
import { DashboardScene } from './DashboardScene';
import { DataLayerControl } from './DataLayerControl';
import { VariableValueSelectWrapper } from './VariableControls';
export const DASHBOARD_CONTROLS_MENU_ARIA_LABEL = 'Dashboard controls menu';
@@ -24,15 +26,25 @@ export function DashboardControlsButton({ dashboard }: { dashboard: DashboardSce
.getVariables(dashboard)!
.useState()
.variables.filter((v) => v.state.hide === VariableHide.inControlsMenu);
const dataState = sceneGraph.getData(dashboard).useState();
const annotationLayers = isDashboardDataLayerSetState(dataState) ? dataState.annotationLayers : [];
const filteredAnnotationLayers = annotationLayers.filter((layer) => layer.state.placement === 'inControlsMenu');
if ((variables.length === 0 && filteredLinks.length === 0) || !uid) {
if ((variables.length === 0 && filteredLinks.length === 0 && filteredAnnotationLayers.length === 0) || !uid) {
return null;
}
return (
<Dropdown
placement="bottom-end"
overlay={<DashboardControlsMenu variables={variables} links={filteredLinks} dashboardUID={uid} />}
overlay={
<DashboardControlsMenu
variables={variables}
links={filteredLinks}
annotationLayers={filteredAnnotationLayers}
dashboardUID={uid}
/>
}
>
<ToolbarButton
aria-label={t('dashboard.controls.menu.aria-label', DASHBOARD_CONTROLS_MENU_ARIA_LABEL)}
@@ -49,10 +61,11 @@ export function DashboardControlsButton({ dashboard }: { dashboard: DashboardSce
interface DashboardControlsMenuProps {
variables: SceneVariable[];
links: DashboardLink[];
annotationLayers: SceneDataLayerProvider[];
dashboardUID: string;
}
function DashboardControlsMenu({ variables, links, dashboardUID }: DashboardControlsMenuProps) {
function DashboardControlsMenu({ variables, links, annotationLayers, dashboardUID }: DashboardControlsMenuProps) {
const styles = useStyles2(getStyles);
return (
@@ -80,22 +93,39 @@ function DashboardControlsMenu({ variables, links, dashboardUID }: DashboardCont
</div>
))}
{variables.length > 0 && links.length > 0 && (
<div className={styles.divider}>
<Menu.Divider />
</div>
)}
{/* Annotation layers */}
{annotationLayers.length > 0 &&
annotationLayers.map((layer, index) => (
<div className={cx(index > 0 && styles.variableItem)} key={layer.state.key}>
<DataLayerControl layer={layer} inMenu />
</div>
))}
{/* Links */}
{links.map((link, index) => (
<div key={`${link.title}-${index}`}>
<DashboardLinkRenderer link={link} dashboardUID={dashboardUID} inMenu />
</div>
))}
{links.length > 0 && (
<>
{(variables.length > 0 || annotationLayers.length > 0) && <MenuDivider />}
{links.map((link, index) => (
<div key={`${link.title}-${index}`}>
<DashboardLinkRenderer link={link} dashboardUID={dashboardUID} inMenu />
</div>
))}
</>
)}
</Box>
);
}
function MenuDivider() {
const styles = useStyles2(getStyles);
return (
<div className={styles.divider}>
<Menu.Divider />
</div>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
divider: css({
marginTop: theme.spacing(2),
@@ -0,0 +1,27 @@
import { SceneDataLayerProvider, sceneGraph } from '@grafana/scenes';
import { isDashboardDataLayerSetState } from './DashboardDataLayerSet';
import { DashboardScene } from './DashboardScene';
import { DataLayerControl } from './DataLayerControl';
// Renders data layer controls for a dashboard
export function DashboardDataLayerControls({ dashboard }: { dashboard: DashboardScene }) {
// We are not using the default renderer of the data objects here, because the information of where the controls
// should be rendered (`.placement`) are set on the underlying annotation layer objects.
const state = sceneGraph.getData(dashboard).useState();
// It is possible to render the controls for the annotation data layers in separate places using the `placement` property.
// In case it's not specified, we are rendering the controls here (default).
const isDefaultPlacement = (layer: SceneDataLayerProvider) => layer.state.placement === undefined;
if (isDashboardDataLayerSetState(state)) {
return (
<>
{state.annotationLayers.filter(isDefaultPlacement).map((layer) => (
<DataLayerControl layer={layer} key={layer.state.key} />
))}
</>
);
}
return null;
}
@@ -6,6 +6,7 @@ import {
} from '@grafana/scenes';
import { AlertStatesDataLayer } from './AlertStatesDataLayer';
import { DataLayerControl } from './DataLayerControl';
export interface DashboardDataLayerSetState extends SceneDataLayerProviderState {
alertStatesLayer?: AlertStatesDataLayer;
@@ -16,6 +17,8 @@ export class DashboardDataLayerSet
extends SceneDataLayerSetBase<DashboardDataLayerSetState>
implements SceneDataLayerProvider
{
public static Component = DashboardDataLayerSetRenderer;
public constructor(state: Partial<DashboardDataLayerSetState>) {
super({
...state,
@@ -56,16 +59,24 @@ export class DashboardDataLayerSet
return layers;
}
}
public static Component = ({ model }: SceneComponentProps<DashboardDataLayerSet>) => {
const { annotationLayers } = model.useState();
return (
<>
{annotationLayers.map((layer) => (
<layer.Component model={layer} key={layer.state.key} />
))}
</>
);
};
function DashboardDataLayerSetRenderer({ model }: SceneComponentProps<DashboardDataLayerSet>) {
const { annotationLayers } = model.useState();
return (
<>
{annotationLayers.map((layer) => (
<DataLayerControl layer={layer} key={layer.state.key} />
))}
</>
);
}
export function isDashboardDataLayerSetState(data: unknown): data is DashboardDataLayerSetState {
if (data && typeof data === 'object') {
return 'annotationLayers' in data;
}
return false;
}
@@ -0,0 +1,81 @@
import { css } from '@emotion/css';
import { LoadingState, GrafanaTheme2 } from '@grafana/data';
import { ControlsLabel, SceneDataLayerProvider } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
export type Props = {
layer: SceneDataLayerProvider;
// Set to true if the control is rendered inside a drop-down menu
inMenu?: boolean;
};
// Renders the controls for a single data layer
export function DataLayerControl({ layer, inMenu }: Props) {
const elementId = `data-layer-${layer.state.key}`;
const { data, isHidden } = layer.useState();
const showLoading = Boolean(data && data.state === LoadingState.Loading);
const styles = useStyles2(getStyles);
if (isHidden) {
return null;
}
if (inMenu) {
return (
<div className={styles.menuContainer}>
<div className={styles.controlWrapper}>
<layer.Component model={layer} />
</div>
<ControlsLabel
htmlFor={elementId}
isLoading={showLoading}
onCancel={() => layer.cancelQuery?.()}
label={layer.state.name}
description={layer.state.description}
error={layer.state.data?.errors?.[0].message}
layout={'vertical'}
className={styles.menuLabel}
/>
</div>
);
}
return (
<div className={styles.container}>
<ControlsLabel
htmlFor={elementId}
isLoading={showLoading}
onCancel={() => layer.cancelQuery?.()}
label={layer.state.name}
description={layer.state.description}
error={layer.state.data?.errors?.[0].message}
/>
<layer.Component model={layer} />
</div>
);
}
const getStyles = (theme: GrafanaTheme2) => ({
container: css({
display: 'flex',
}),
menuContainer: css({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
}),
controlWrapper: css({
'& > div': {
border: 'none',
background: 'transparent',
'&:hover': {
border: 'none',
background: 'transparent',
},
},
}),
menuLabel: css({
marginTop: theme.spacing(0.5),
}),
});
@@ -26,6 +26,7 @@ export function transformV1ToV2AnnotationQuery(
target,
snapshotData,
type,
placement,
// unknown properties that are still available for configuration through API
...legacyOptions
@@ -49,6 +50,11 @@ export function transformV1ToV2AnnotationQuery(
},
};
// Add placement if it exists
if (annotation.placement) {
result.spec.placement = annotation.placement;
}
if (dsUID) {
result.spec.query.datasource = {
name: dsUID,
@@ -81,6 +87,11 @@ export function transformV2ToV1AnnotationQuery(annotation: AnnotationQueryKind):
// TOOO: mappings
};
// Add placement if it exists
if (annotation.spec.placement) {
annoQuerySpec.placement = annotation.spec.placement;
}
if (Object.keys(dataQuery.spec).length > 0) {
// @ts-expect-error DataQueryKind spec should be typed as DataQuery interface
annoQuerySpec.target = {
@@ -12,6 +12,7 @@ export function dataLayersToAnnotations(layers: SceneDataLayerProvider[]) {
...layer.state.query,
enable: Boolean(layer.state.isEnabled),
hide: Boolean(layer.state.isHidden),
placement: layer.state.placement,
};
annotations.push(result);
@@ -117,6 +117,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
name: annotation.spec.name,
isEnabled: Boolean(annotation.spec.enable),
isHidden: Boolean(annotation.spec.hide),
placement: annotation.spec.placement,
};
return new DashboardAnnotationsDataLayer(layerState);
@@ -272,6 +272,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
name: a.name,
isEnabled: Boolean(a.enable),
isHidden: Boolean(a.hide),
placement: a.placement,
});
});
}
+4
View File
@@ -3241,6 +3241,10 @@
"description": "Name of annotation.",
"type": "string"
},
"placement": {
"description": "Placement can be used to display the annotation query somewhere else on the dashboard other than the default location.",
"type": "string"
},
"target": {
"$ref": "#/components/schemas/AnnotationTarget"
},
+11 -11
View File
@@ -3555,11 +3555,11 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/scenes-react@npm:^6.41.0":
version: 6.42.0
resolution: "@grafana/scenes-react@npm:6.42.0"
"@grafana/scenes-react@npm:^6.42.1":
version: 6.42.1
resolution: "@grafana/scenes-react@npm:6.42.1"
dependencies:
"@grafana/scenes": "npm:6.42.0"
"@grafana/scenes": "npm:6.42.1"
lru-cache: "npm:^10.2.2"
react-use: "npm:^17.4.0"
peerDependencies:
@@ -3571,7 +3571,7 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
checksum: 10/05db719566e8499b2f9f46ac7da83b2c669bc29d3169b42cb800ad0bb099ad7d5dead4109975d5c65bb711a98965f4c8eb20ea3e60d8f46c958be0508346f84e
checksum: 10/190289561d343a7a2d9d036f9b23abb063cee20f3a3e9e9275d8f11ef2e0df9fbd256e4687bf5a7b8f59aa79ae2bbc05bbb009876f582ebaeed97a0f52492907
languageName: node
linkType: hard
@@ -3601,9 +3601,9 @@ __metadata:
languageName: node
linkType: hard
"@grafana/scenes@npm:6.42.0, @grafana/scenes@npm:^6.41.0":
version: 6.42.0
resolution: "@grafana/scenes@npm:6.42.0"
"@grafana/scenes@npm:6.42.1, @grafana/scenes@npm:^6.42.1":
version: 6.42.1
resolution: "@grafana/scenes@npm:6.42.1"
dependencies:
"@floating-ui/react": "npm:^0.26.16"
"@leeoniya/ufuzzy": "npm:^1.0.16"
@@ -3623,7 +3623,7 @@ __metadata:
react: ^18.0.0
react-dom: ^18.0.0
react-router-dom: ^6.28.0
checksum: 10/790afb142b6aa78a8a4f1359ec363e5a6c6c198873eafd39398b125d65881fb37514cf0c2f424e020dedca53a11a378f1dc87bab8ebc69fa76f5d10d59837e9f
checksum: 10/f1f455f823c01324942fef9feb256b04cb209d486a43e6dd683e984abb88f078d8e2a9d85e31619e37c9c3ee4356c97707d258a608f9b64ae8df097813bf178f
languageName: node
linkType: hard
@@ -18865,8 +18865,8 @@ __metadata:
"@grafana/plugin-ui": "npm:^0.10.10"
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/scenes": "npm:^6.41.0"
"@grafana/scenes-react": "npm:^6.41.0"
"@grafana/scenes": "npm:^6.42.1"
"@grafana/scenes-react": "npm:^6.42.1"
"@grafana/schema": "workspace:*"
"@grafana/sql": "workspace:*"
"@grafana/test-utils": "workspace:*"