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:
@@ -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.",
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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:*"
|
||||
|
||||
Reference in New Issue
Block a user