From 8a8b1a0743eeb1067757504abfd47de217aec9a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 21 Mar 2025 14:28:44 +0100 Subject: [PATCH] DashboardScene: Take selected object into account when adding objects (#102423) * Dashboard: Add object to selected object * Try not to nest rows * Update * Update * rows in rows, tabs in tabs * Update schema to allow nested rows and nested tabs * fix lint issue * reset v2alpha1 types from main * reset dashboard_object_gen.go to main --------- Co-authored-by: oscarkilhed --- .../kinds/v2alpha1/dashboard_spec.cue | 4 +- .../dashboard/v2alpha1/dashboard_spec_gen.go | 159 +- .../dashboard/v2alpha0/dashboard.schema.cue | 4 +- .../schema/dashboard/v2alpha0/types.gen.ts | 1942 ++++++++--------- .../edit-pane/DashboardEditPane.tsx | 4 + .../dashboard-scene/scene/DashboardScene.tsx | 21 +- .../DefaultGridLayoutManager.tsx | 1 + .../scene/layout-rows/RowsLayoutManager.tsx | 9 +- .../scene/layout-tabs/TabsLayoutManager.tsx | 9 +- .../scene/layouts-shared/addNew.ts | 47 +- .../layoutSerializers/RowsLayoutSerializer.ts | 3 - .../layoutSerializers/TabsLayoutSerializer.ts | 3 - 12 files changed, 1091 insertions(+), 1115 deletions(-) diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index 26ca2d35eec..2396c0cc1aa 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -554,7 +554,7 @@ RowsLayoutRowSpec: { collapsed: bool conditionalRendering?: ConditionalRenderingGroupKind repeat?: RowRepeatOptions - layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind + layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind } ResponsiveGridLayoutKind: { @@ -595,7 +595,7 @@ TabsLayoutTabKind: { TabsLayoutTabSpec: { title?: string - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind } PanelSpec: { diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index b491bd8510d..801cd807bd3 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -836,17 +836,17 @@ func NewDashboardRowsLayoutRowKind() *DashboardRowsLayoutRowKind { // +k8s:openapi-gen=true type DashboardRowsLayoutRowSpec struct { - Title *string `json:"title,omitempty"` - Collapsed bool `json:"collapsed"` - ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"` - Repeat *DashboardRowRepeatOptions `json:"repeat,omitempty"` - Layout DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind `json:"layout"` + Title *string `json:"title,omitempty"` + Collapsed bool `json:"collapsed"` + ConditionalRendering *DashboardConditionalRenderingGroupKind `json:"conditionalRendering,omitempty"` + Repeat *DashboardRowRepeatOptions `json:"repeat,omitempty"` + Layout DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind `json:"layout"` } // NewDashboardRowsLayoutRowSpec creates a new DashboardRowsLayoutRowSpec object. func NewDashboardRowsLayoutRowSpec() *DashboardRowsLayoutRowSpec { return &DashboardRowsLayoutRowSpec{ - Layout: *NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind(), + Layout: *NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind(), } } @@ -1054,14 +1054,14 @@ func NewDashboardTabsLayoutTabKind() *DashboardTabsLayoutTabKind { // +k8s:openapi-gen=true type DashboardTabsLayoutTabSpec struct { - Title *string `json:"title,omitempty"` - Layout DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind `json:"layout"` + Title *string `json:"title,omitempty"` + Layout DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind `json:"layout"` } // NewDashboardTabsLayoutTabSpec creates a new DashboardTabsLayoutTabSpec object. func NewDashboardTabsLayoutTabSpec() *DashboardTabsLayoutTabSpec { return &DashboardTabsLayoutTabSpec{ - Layout: *NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind(), + Layout: *NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind(), } } @@ -2012,19 +2012,20 @@ func (resource *DashboardGridLayoutItemKindOrGridLayoutRowKind) UnmarshalJSON(ra } // +k8s:openapi-gen=true -type DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind struct { +type DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind struct { GridLayoutKind *DashboardGridLayoutKind `json:"GridLayoutKind,omitempty"` ResponsiveGridLayoutKind *DashboardResponsiveGridLayoutKind `json:"ResponsiveGridLayoutKind,omitempty"` TabsLayoutKind *DashboardTabsLayoutKind `json:"TabsLayoutKind,omitempty"` + RowsLayoutKind *DashboardRowsLayoutKind `json:"RowsLayoutKind,omitempty"` } -// NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind creates a new DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind object. -func NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind() *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind { - return &DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind{} +// NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind creates a new DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind object. +func NewDashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind() *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind { + return &DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{} } -// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` as JSON. -func (resource DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) MarshalJSON() ([]byte, error) { +// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind` as JSON. +func (resource DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind) MarshalJSON() ([]byte, error) { if resource.GridLayoutKind != nil { return json.Marshal(resource.GridLayoutKind) } @@ -2034,11 +2035,14 @@ func (resource DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind if resource.TabsLayoutKind != nil { return json.Marshal(resource.TabsLayoutKind) } + if resource.RowsLayoutKind != nil { + return json.Marshal(resource.RowsLayoutKind) + } return []byte("null"), nil } -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` from JSON. -func (resource *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) UnmarshalJSON(raw []byte) error { +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind` from JSON. +func (resource *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind) UnmarshalJSON(raw []byte) error { if raw == nil { return nil } @@ -2071,6 +2075,14 @@ func (resource *DashboardGridLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKin resource.ResponsiveGridLayoutKind = &dashboardResponsiveGridLayoutKind return nil + case "RowsLayout": + var dashboardRowsLayoutKind DashboardRowsLayoutKind + if err := json.Unmarshal(raw, &dashboardRowsLayoutKind); err != nil { + return err + } + + resource.RowsLayoutKind = &dashboardRowsLayoutKind + return nil case "TabsLayout": var dashboardTabsLayoutKind DashboardTabsLayoutKind if err := json.Unmarshal(raw, &dashboardTabsLayoutKind); err != nil { @@ -2158,19 +2170,20 @@ func (resource *DashboardConditionalRenderingVariableKindOrConditionalRenderingD } // +k8s:openapi-gen=true -type DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind struct { +type DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind struct { GridLayoutKind *DashboardGridLayoutKind `json:"GridLayoutKind,omitempty"` RowsLayoutKind *DashboardRowsLayoutKind `json:"RowsLayoutKind,omitempty"` ResponsiveGridLayoutKind *DashboardResponsiveGridLayoutKind `json:"ResponsiveGridLayoutKind,omitempty"` + TabsLayoutKind *DashboardTabsLayoutKind `json:"TabsLayoutKind,omitempty"` } -// NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind creates a new DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind object. -func NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind() *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind { - return &DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind{} +// NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind creates a new DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind object. +func NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind() *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind { + return &DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind{} } -// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind` as JSON. -func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind) MarshalJSON() ([]byte, error) { +// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` as JSON. +func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) MarshalJSON() ([]byte, error) { if resource.GridLayoutKind != nil { return json.Marshal(resource.GridLayoutKind) } @@ -2180,11 +2193,14 @@ func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind if resource.ResponsiveGridLayoutKind != nil { return json.Marshal(resource.ResponsiveGridLayoutKind) } + if resource.TabsLayoutKind != nil { + return json.Marshal(resource.TabsLayoutKind) + } return []byte("null"), nil } -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind` from JSON. -func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKind) UnmarshalJSON(raw []byte) error { +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` from JSON. +func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) UnmarshalJSON(raw []byte) error { if raw == nil { return nil } @@ -2225,6 +2241,14 @@ func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKin resource.RowsLayoutKind = &dashboardRowsLayoutKind return nil + case "TabsLayout": + var dashboardTabsLayoutKind DashboardTabsLayoutKind + if err := json.Unmarshal(raw, &dashboardTabsLayoutKind); err != nil { + return err + } + + resource.TabsLayoutKind = &dashboardTabsLayoutKind + return nil } return fmt.Errorf("could not unmarshal resource with `kind = %v`", discriminator) @@ -2472,88 +2496,3 @@ func (resource *DashboardStringOrFloat64) UnmarshalJSON(raw []byte) error { return errors.Join(errList...) } - -// +k8s:openapi-gen=true -type DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind struct { - GridLayoutKind *DashboardGridLayoutKind `json:"GridLayoutKind,omitempty"` - RowsLayoutKind *DashboardRowsLayoutKind `json:"RowsLayoutKind,omitempty"` - ResponsiveGridLayoutKind *DashboardResponsiveGridLayoutKind `json:"ResponsiveGridLayoutKind,omitempty"` - TabsLayoutKind *DashboardTabsLayoutKind `json:"TabsLayoutKind,omitempty"` -} - -// NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind creates a new DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind object. -func NewDashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind() *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind { - return &DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind{} -} - -// MarshalJSON implements a custom JSON marshalling logic to encode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` as JSON. -func (resource DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) MarshalJSON() ([]byte, error) { - if resource.GridLayoutKind != nil { - return json.Marshal(resource.GridLayoutKind) - } - if resource.RowsLayoutKind != nil { - return json.Marshal(resource.RowsLayoutKind) - } - if resource.ResponsiveGridLayoutKind != nil { - return json.Marshal(resource.ResponsiveGridLayoutKind) - } - if resource.TabsLayoutKind != nil { - return json.Marshal(resource.TabsLayoutKind) - } - return []byte("null"), nil -} - -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind` from JSON. -func (resource *DashboardGridLayoutKindOrRowsLayoutKindOrResponsiveGridLayoutKindOrTabsLayoutKind) UnmarshalJSON(raw []byte) error { - if raw == nil { - return nil - } - - // FIXME: this is wasteful, we need to find a more efficient way to unmarshal this. - parsedAsMap := make(map[string]interface{}) - if err := json.Unmarshal(raw, &parsedAsMap); err != nil { - return err - } - - discriminator, found := parsedAsMap["kind"] - if !found { - return errors.New("discriminator field 'kind' not found in payload") - } - - switch discriminator { - case "GridLayout": - var dashboardGridLayoutKind DashboardGridLayoutKind - if err := json.Unmarshal(raw, &dashboardGridLayoutKind); err != nil { - return err - } - - resource.GridLayoutKind = &dashboardGridLayoutKind - return nil - case "ResponsiveGridLayout": - var dashboardResponsiveGridLayoutKind DashboardResponsiveGridLayoutKind - if err := json.Unmarshal(raw, &dashboardResponsiveGridLayoutKind); err != nil { - return err - } - - resource.ResponsiveGridLayoutKind = &dashboardResponsiveGridLayoutKind - return nil - case "RowsLayout": - var dashboardRowsLayoutKind DashboardRowsLayoutKind - if err := json.Unmarshal(raw, &dashboardRowsLayoutKind); err != nil { - return err - } - - resource.RowsLayoutKind = &dashboardRowsLayoutKind - return nil - case "TabsLayout": - var dashboardTabsLayoutKind DashboardTabsLayoutKind - if err := json.Unmarshal(raw, &dashboardTabsLayoutKind); err != nil { - return err - } - - resource.TabsLayoutKind = &dashboardTabsLayoutKind - return nil - } - - return fmt.Errorf("could not unmarshal resource with `kind = %v`", discriminator) -} diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 846d0cabd8f..e0fdc845ec3 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -554,7 +554,7 @@ RowsLayoutRowSpec: { collapsed: bool repeat?: RowRepeatOptions conditionalRendering?: ConditionalRenderingGroupKind - layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind + layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind } ResponsiveGridLayoutKind: { @@ -595,7 +595,7 @@ TabsLayoutTabKind: { TabsLayoutTabSpec: { title?: string - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind } PanelSpec: { diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts index 895014f6b6c..022ee9f3bc8 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts @@ -51,49 +51,54 @@ export const defaultDashboardV2Spec = (): DashboardV2Spec => ({ variables: [], }); -// Supported dashboard elements -// |* more element types in the future -export type Element = PanelKind | LibraryPanelKind; - -export const defaultElement = (): Element => (defaultPanelKind()); - -export interface LibraryPanelKind { - kind: "LibraryPanel"; - spec: LibraryPanelSpec; +export interface AnnotationQueryKind { + kind: "AnnotationQuery"; + spec: AnnotationQuerySpec; } -export const defaultLibraryPanelKind = (): LibraryPanelKind => ({ - kind: "LibraryPanel", - spec: defaultLibraryPanelSpec(), +export const defaultAnnotationQueryKind = (): AnnotationQueryKind => ({ + kind: "AnnotationQuery", + spec: defaultAnnotationQuerySpec(), }); -export interface LibraryPanelSpec { - // Panel ID for the library panel in the dashboard - id: number; - // Title for the library panel in the dashboard - title: string; - libraryPanel: LibraryPanelRef; -} - -export const defaultLibraryPanelSpec = (): LibraryPanelSpec => ({ - id: 0, - title: "", - libraryPanel: defaultLibraryPanelRef(), -}); - -// A library panel is a reusable panel that you can use in any dashboard. -// When you make a change to a library panel, that change propagates to all instances of where the panel is used. -// Library panels streamline reuse of panels across multiple dashboards. -export interface LibraryPanelRef { - // Library panel name +export interface AnnotationQuerySpec { + datasource?: DataSourceRef; + query?: DataQueryKind; + enable: boolean; + hide: boolean; + iconColor: string; name: string; - // Library panel uid - uid: string; + builtIn?: boolean; + filter?: AnnotationPanelFilter; } -export const defaultLibraryPanelRef = (): LibraryPanelRef => ({ +export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ + enable: false, + hide: false, + iconColor: "", name: "", - uid: "", + builtIn: false, +}); + +export interface DataSourceRef { + // The plugin type-id + type?: string; + // Specific datasource instance + uid?: string; +} + +export const defaultDataSourceRef = (): DataSourceRef => ({ +}); + +export interface DataQueryKind { + // The kind of a DataQueryKind is the datasource type + kind: string; + spec: Record; +} + +export const defaultDataQueryKind = (): DataQueryKind => ({ + kind: "", + spec: {}, }); export interface AnnotationPanelFilter { @@ -115,51 +120,106 @@ export type DashboardCursorSync = "Off" | "Crosshair" | "Tooltip"; export const defaultDashboardCursorSync = (): DashboardCursorSync => ("Off"); -// Links with references to other dashboards or external resources -export interface DashboardLink { - // Title to display with the link - title: string; - // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) - // FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType` - type: DashboardLinkType; - // Icon name to be displayed with the link - icon: string; - // Tooltip to display when the user hovers their mouse over it - tooltip: string; - // Link URL. Only required/valid if the type is link - url?: string; - // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards - tags: string[]; - // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards - asDropdown: boolean; - // If true, the link will be opened in a new tab - targetBlank: boolean; - // If true, includes current template variables values in the link as query params - includeVars: boolean; - // If true, includes current time range in the link as query params - keepTime: boolean; +// Supported dashboard elements +// |* more element types in the future +export type Element = PanelKind | LibraryPanelKind; + +export const defaultElement = (): Element => (defaultPanelKind()); + +export interface PanelKind { + kind: "Panel"; + spec: PanelSpec; } -export const defaultDashboardLink = (): DashboardLink => ({ - title: "", - type: "link", - icon: "", - tooltip: "", - tags: [], - asDropdown: false, - targetBlank: false, - includeVars: false, - keepTime: false, +export const defaultPanelKind = (): PanelKind => ({ + kind: "Panel", + spec: defaultPanelSpec(), }); -export interface DataSourceRef { - // The plugin type-id - type?: string; - // Specific datasource instance - uid?: string; +export interface PanelSpec { + id: number; + title: string; + description: string; + links: DataLink[]; + data: QueryGroupKind; + vizConfig: VizConfigKind; + transparent?: boolean; } -export const defaultDataSourceRef = (): DataSourceRef => ({ +export const defaultPanelSpec = (): PanelSpec => ({ + id: 0, + title: "", + description: "", + links: [], + data: defaultQueryGroupKind(), + vizConfig: defaultVizConfigKind(), +}); + +export interface DataLink { + title: string; + url: string; + targetBlank?: boolean; +} + +export const defaultDataLink = (): DataLink => ({ + title: "", + url: "", +}); + +export interface QueryGroupKind { + kind: "QueryGroup"; + spec: QueryGroupSpec; +} + +export const defaultQueryGroupKind = (): QueryGroupKind => ({ + kind: "QueryGroup", + spec: defaultQueryGroupSpec(), +}); + +export interface QueryGroupSpec { + queries: PanelQueryKind[]; + transformations: TransformationKind[]; + queryOptions: QueryOptionsSpec; +} + +export const defaultQueryGroupSpec = (): QueryGroupSpec => ({ + queries: [], + transformations: [], + queryOptions: defaultQueryOptionsSpec(), +}); + +export interface PanelQueryKind { + kind: "PanelQuery"; + spec: PanelQuerySpec; +} + +export const defaultPanelQueryKind = (): PanelQueryKind => ({ + kind: "PanelQuery", + spec: defaultPanelQuerySpec(), +}); + +export interface PanelQuerySpec { + query: DataQueryKind; + datasource?: DataSourceRef; + refId: string; + hidden: boolean; +} + +export const defaultPanelQuerySpec = (): PanelQuerySpec => ({ + query: defaultDataQueryKind(), + refId: "", + hidden: false, +}); + +export interface TransformationKind { + // The kind of a TransformationKind is the transformation ID + kind: string; + spec: DataTransformerConfig; +} + +export const defaultTransformationKind = (): TransformationKind => ({ + kind: "", + spec: defaultDataTransformerConfig(), }); // Transformations allow to manipulate data returned by a query before the system applies a visualization. @@ -184,15 +244,54 @@ export const defaultDataTransformerConfig = (): DataTransformerConfig => ({ options: {}, }); -export interface DataLink { - title: string; - url: string; - targetBlank?: boolean; +// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation. +// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type. +export interface MatcherConfig { + // The matcher id. This is used to find the matcher implementation from registry. + id: string; + // The matcher options. This is specific to the matcher implementation. + options?: any; } -export const defaultDataLink = (): DataLink => ({ - title: "", - url: "", +export const defaultMatcherConfig = (): MatcherConfig => ({ + id: "", +}); + +export interface QueryOptionsSpec { + timeFrom?: string; + maxDataPoints?: number; + timeShift?: string; + queryCachingTTL?: number; + interval?: string; + cacheTimeout?: string; + hideTimeOverride?: boolean; +} + +export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({ +}); + +export interface VizConfigKind { + // The kind of a VizConfigKind is the plugin ID + kind: string; + spec: VizConfigSpec; +} + +export const defaultVizConfigKind = (): VizConfigKind => ({ + kind: "", + spec: defaultVizConfigSpec(), +}); + +// --- Kinds --- +export interface VizConfigSpec { + pluginVersion: string; + options: Record; + fieldConfig: FieldConfigSource; +} + +export const defaultVizConfigSpec = (): VizConfigSpec => ({ + pluginVersion: "", + options: {}, + fieldConfig: defaultFieldConfigSource(), }); // The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. @@ -272,65 +371,10 @@ export interface FieldConfig { export const defaultFieldConfig = (): FieldConfig => ({ }); -export interface DynamicConfigValue { - id: string; - value?: any; -} - -export const defaultDynamicConfigValue = (): DynamicConfigValue => ({ - id: "", -}); - -// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation. -// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type. -export interface MatcherConfig { - // The matcher id. This is used to find the matcher implementation from registry. - id: string; - // The matcher options. This is specific to the matcher implementation. - options?: any; -} - -export const defaultMatcherConfig = (): MatcherConfig => ({ - id: "", -}); - -export interface Threshold { - value: number; - color: string; -} - -export const defaultThreshold = (): Threshold => ({ - value: 0, - color: "", -}); - -export type ThresholdsMode = "absolute" | "percentage"; - -export const defaultThresholdsMode = (): ThresholdsMode => ("absolute"); - -export interface ThresholdsConfig { - mode: ThresholdsMode; - steps: Threshold[]; -} - -export const defaultThresholdsConfig = (): ThresholdsConfig => ({ - mode: "absolute", - steps: [], -}); - export type ValueMapping = ValueMap | RangeMap | RegexMap | SpecialValueMap; export const defaultValueMapping = (): ValueMapping => (defaultValueMap()); -// Supported value mapping types -// `value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. -// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. -// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. -// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. -export type MappingType = "value" | "range" | "regex" | "special"; - -export const defaultMappingType = (): MappingType => ("value"); - // Maps text values to a color or different display text and color. // For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. export interface ValueMap { @@ -344,6 +388,21 @@ export const defaultValueMap = (): ValueMap => ({ options: {}, }); +// Result used as replacement with text and color when the value matches +export interface ValueMappingResult { + // Text to display when the value matches + text?: string; + // Text to use when the value matches + color?: string; + // Icon to display when the value matches. Only specific visualizations. + icon?: string; + // Position in the mapping array. Only used internally. + index?: number; +} + +export const defaultValueMappingResult = (): ValueMappingResult => ({ +}); + // Maps numerical ranges to a display text and color. // For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. export interface RangeMap { @@ -415,19 +474,42 @@ export type SpecialValueMatch = "true" | "false" | "null" | "nan" | "null+nan" | export const defaultSpecialValueMatch = (): SpecialValueMatch => ("true"); -// Result used as replacement with text and color when the value matches -export interface ValueMappingResult { - // Text to display when the value matches - text?: string; - // Text to use when the value matches - color?: string; - // Icon to display when the value matches. Only specific visualizations. - icon?: string; - // Position in the mapping array. Only used internally. - index?: number; +export interface ThresholdsConfig { + mode: ThresholdsMode; + steps: Threshold[]; } -export const defaultValueMappingResult = (): ValueMappingResult => ({ +export const defaultThresholdsConfig = (): ThresholdsConfig => ({ + mode: "absolute", + steps: [], +}); + +export type ThresholdsMode = "absolute" | "percentage"; + +export const defaultThresholdsMode = (): ThresholdsMode => ("absolute"); + +export interface Threshold { + value: number; + color: string; +} + +export const defaultThreshold = (): Threshold => ({ + value: 0, + color: "", +}); + +// Map a field to a color. +export interface FieldColor { + // The main color scheme mode. + mode: FieldColorModeId; + // The fixed color value for fixed or shades color modes. + fixedColor?: string; + // Some visualizations need to know how to assign a series color from by value color schemes. + seriesBy?: FieldColorSeriesByMode; +} + +export const defaultFieldColor = (): FieldColor => ({ + mode: "thresholds", }); // Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value. @@ -457,268 +539,80 @@ export type FieldColorSeriesByMode = "min" | "max" | "last"; export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => ("min"); -// Map a field to a color. -export interface FieldColor { - // The main color scheme mode. - mode: FieldColorModeId; - // The fixed color value for fixed or shades color modes. - fixedColor?: string; - // Some visualizations need to know how to assign a series color from by value color schemes. - seriesBy?: FieldColorSeriesByMode; +export interface DynamicConfigValue { + id: string; + value?: any; } -export const defaultFieldColor = (): FieldColor => ({ - mode: "thresholds", +export const defaultDynamicConfigValue = (): DynamicConfigValue => ({ + id: "", }); -// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) -export type DashboardLinkType = "link" | "dashboards"; - -export const defaultDashboardLinkType = (): DashboardLinkType => ("link"); - -// --- Common types --- -export interface Kind { - kind: string; - spec: any; - metadata?: any; +export interface LibraryPanelKind { + kind: "LibraryPanel"; + spec: LibraryPanelSpec; } -export const defaultKind = (): Kind => ({ - kind: "", - spec: {}, +export const defaultLibraryPanelKind = (): LibraryPanelKind => ({ + kind: "LibraryPanel", + spec: defaultLibraryPanelSpec(), }); -// --- Kinds --- -export interface VizConfigSpec { - pluginVersion: string; - options: Record; - fieldConfig: FieldConfigSource; +export interface LibraryPanelSpec { + // Panel ID for the library panel in the dashboard + id: number; + // Title for the library panel in the dashboard + title: string; + libraryPanel: LibraryPanelRef; } -export const defaultVizConfigSpec = (): VizConfigSpec => ({ - pluginVersion: "", - options: {}, - fieldConfig: defaultFieldConfigSource(), +export const defaultLibraryPanelSpec = (): LibraryPanelSpec => ({ + id: 0, + title: "", + libraryPanel: defaultLibraryPanelRef(), }); -export interface VizConfigKind { - // The kind of a VizConfigKind is the plugin ID - kind: string; - spec: VizConfigSpec; -} - -export const defaultVizConfigKind = (): VizConfigKind => ({ - kind: "", - spec: defaultVizConfigSpec(), -}); - -export interface AnnotationQuerySpec { - datasource?: DataSourceRef; - query?: DataQueryKind; - enable: boolean; - hide: boolean; - iconColor: string; +// A library panel is a reusable panel that you can use in any dashboard. +// When you make a change to a library panel, that change propagates to all instances of where the panel is used. +// Library panels streamline reuse of panels across multiple dashboards. +export interface LibraryPanelRef { + // Library panel name name: string; - builtIn?: boolean; - filter?: AnnotationPanelFilter; + // Library panel uid + uid: string; } -export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ - enable: false, - hide: false, - iconColor: "", +export const defaultLibraryPanelRef = (): LibraryPanelRef => ({ name: "", - builtIn: false, + uid: "", }); -export interface AnnotationQueryKind { - kind: "AnnotationQuery"; - spec: AnnotationQuerySpec; +export interface GridLayoutKind { + kind: "GridLayout"; + spec: GridLayoutSpec; } -export const defaultAnnotationQueryKind = (): AnnotationQueryKind => ({ - kind: "AnnotationQuery", - spec: defaultAnnotationQuerySpec(), +export const defaultGridLayoutKind = (): GridLayoutKind => ({ + kind: "GridLayout", + spec: defaultGridLayoutSpec(), }); -export interface QueryOptionsSpec { - timeFrom?: string; - maxDataPoints?: number; - timeShift?: string; - queryCachingTTL?: number; - interval?: string; - cacheTimeout?: string; - hideTimeOverride?: boolean; +export interface GridLayoutSpec { + items: (GridLayoutItemKind | GridLayoutRowKind)[]; } -export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({ +export const defaultGridLayoutSpec = (): GridLayoutSpec => ({ + items: [], }); -export interface DataQueryKind { - // The kind of a DataQueryKind is the datasource type - kind: string; - spec: Record; +export interface GridLayoutItemKind { + kind: "GridLayoutItem"; + spec: GridLayoutItemSpec; } -export const defaultDataQueryKind = (): DataQueryKind => ({ - kind: "", - spec: {}, -}); - -export interface PanelQuerySpec { - query: DataQueryKind; - datasource?: DataSourceRef; - refId: string; - hidden: boolean; -} - -export const defaultPanelQuerySpec = (): PanelQuerySpec => ({ - query: defaultDataQueryKind(), - refId: "", - hidden: false, -}); - -export interface PanelQueryKind { - kind: "PanelQuery"; - spec: PanelQuerySpec; -} - -export const defaultPanelQueryKind = (): PanelQueryKind => ({ - kind: "PanelQuery", - spec: defaultPanelQuerySpec(), -}); - -export interface TransformationKind { - // The kind of a TransformationKind is the transformation ID - kind: string; - spec: DataTransformerConfig; -} - -export const defaultTransformationKind = (): TransformationKind => ({ - kind: "", - spec: defaultDataTransformerConfig(), -}); - -export interface QueryGroupSpec { - queries: PanelQueryKind[]; - transformations: TransformationKind[]; - queryOptions: QueryOptionsSpec; -} - -export const defaultQueryGroupSpec = (): QueryGroupSpec => ({ - queries: [], - transformations: [], - queryOptions: defaultQueryOptionsSpec(), -}); - -export interface QueryGroupKind { - kind: "QueryGroup"; - spec: QueryGroupSpec; -} - -export const defaultQueryGroupKind = (): QueryGroupKind => ({ - kind: "QueryGroup", - spec: defaultQueryGroupSpec(), -}); - -export interface TimeRangeOption { - display: string; - from: string; - to: string; -} - -export const defaultTimeRangeOption = (): TimeRangeOption => ({ - display: "Last 6 hours", - from: "now-6h", - to: "now", -}); - -// Time configuration -// It defines the default time config for the time picker, the refresh picker for the specific dashboard. -export interface TimeSettingsSpec { - // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc". - timezone?: string; - // Start time range for dashboard. - // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". - from: string; - // End time range for dashboard. - // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". - to: string; - // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". - // v1: refresh - autoRefresh: string; - // Interval options available in the refresh picker dropdown. - // v1: timepicker.refresh_intervals - autoRefreshIntervals: string[]; - // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. - // v1: timepicker.quick_ranges , not exposed in the UI - quickRanges?: TimeRangeOption[]; - // Whether timepicker is visible or not. - // v1: timepicker.hidden - hideTimepicker: boolean; - // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday". - weekStart?: "saturday" | "monday" | "sunday"; - // The month that the fiscal year starts on. 0 = January, 11 = December - fiscalYearStartMonth: number; - // Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. - // v1: timepicker.nowDelay - nowDelay?: string; -} - -export const defaultTimeSettingsSpec = (): TimeSettingsSpec => ({ - timezone: "browser", - from: "now-6h", - to: "now", - autoRefresh: "", - autoRefreshIntervals: [ -"5s", -"10s", -"30s", -"1m", -"5m", -"15m", -"30m", -"1h", -"2h", -"1d", -], - hideTimepicker: false, - fiscalYearStartMonth: 0, -}); - -// other repeat modes will be added in the future: label, frame -export const RepeatMode = "variable"; - -export interface RepeatOptions { - mode: "variable"; - value: string; - direction?: "h" | "v"; - maxPerRow?: number; -} - -export const defaultRepeatOptions = (): RepeatOptions => ({ - mode: RepeatMode, - value: "", -}); - -export interface RowRepeatOptions { - mode: "variable"; - value: string; -} - -export const defaultRowRepeatOptions = (): RowRepeatOptions => ({ - mode: RepeatMode, - value: "", -}); - -export interface ResponsiveGridRepeatOptions { - mode: "variable"; - value: string; -} - -export const defaultResponsiveGridRepeatOptions = (): ResponsiveGridRepeatOptions => ({ - mode: RepeatMode, - value: "", +export const defaultGridLayoutItemKind = (): GridLayoutItemKind => ({ + kind: "GridLayoutItem", + spec: defaultGridLayoutItemSpec(), }); export interface GridLayoutItemSpec { @@ -739,16 +633,31 @@ export const defaultGridLayoutItemSpec = (): GridLayoutItemSpec => ({ element: defaultElementReference(), }); -export interface GridLayoutItemKind { - kind: "GridLayoutItem"; - spec: GridLayoutItemSpec; +export interface ElementReference { + kind: "ElementReference"; + name: string; } -export const defaultGridLayoutItemKind = (): GridLayoutItemKind => ({ - kind: "GridLayoutItem", - spec: defaultGridLayoutItemSpec(), +export const defaultElementReference = (): ElementReference => ({ + kind: "ElementReference", + name: "", }); +export interface RepeatOptions { + mode: "variable"; + value: string; + direction?: "h" | "v"; + maxPerRow?: number; +} + +export const defaultRepeatOptions = (): RepeatOptions => ({ + mode: RepeatMode, + value: "", +}); + +// other repeat modes will be added in the future: label, frame +export const RepeatMode = "variable"; + export interface GridLayoutRowKind { kind: "GridLayoutRow"; spec: GridLayoutRowSpec; @@ -775,22 +684,14 @@ export const defaultGridLayoutRowSpec = (): GridLayoutRowSpec => ({ elements: [], }); -export interface GridLayoutSpec { - items: (GridLayoutItemKind | GridLayoutRowKind)[]; +export interface RowRepeatOptions { + mode: "variable"; + value: string; } -export const defaultGridLayoutSpec = (): GridLayoutSpec => ({ - items: [], -}); - -export interface GridLayoutKind { - kind: "GridLayout"; - spec: GridLayoutSpec; -} - -export const defaultGridLayoutKind = (): GridLayoutKind => ({ - kind: "GridLayout", - spec: defaultGridLayoutSpec(), +export const defaultRowRepeatOptions = (): RowRepeatOptions => ({ + mode: RepeatMode, + value: "", }); export interface RowsLayoutKind { @@ -826,7 +727,7 @@ export interface RowsLayoutRowSpec { collapsed: boolean; repeat?: RowRepeatOptions; conditionalRendering?: ConditionalRenderingGroupKind; - layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind; + layout: GridLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind | RowsLayoutKind; } export const defaultRowsLayoutRowSpec = (): RowsLayoutRowSpec => ({ @@ -834,575 +735,6 @@ export const defaultRowsLayoutRowSpec = (): RowsLayoutRowSpec => ({ layout: defaultGridLayoutKind(), }); -export interface ResponsiveGridLayoutKind { - kind: "ResponsiveGridLayout"; - spec: ResponsiveGridLayoutSpec; -} - -export const defaultResponsiveGridLayoutKind = (): ResponsiveGridLayoutKind => ({ - kind: "ResponsiveGridLayout", - spec: defaultResponsiveGridLayoutSpec(), -}); - -export interface ResponsiveGridLayoutSpec { - row: string; - col: string; - items: ResponsiveGridLayoutItemKind[]; -} - -export const defaultResponsiveGridLayoutSpec = (): ResponsiveGridLayoutSpec => ({ - row: "", - col: "", - items: [], -}); - -export interface ResponsiveGridLayoutItemKind { - kind: "ResponsiveGridLayoutItem"; - spec: ResponsiveGridLayoutItemSpec; -} - -export const defaultResponsiveGridLayoutItemKind = (): ResponsiveGridLayoutItemKind => ({ - kind: "ResponsiveGridLayoutItem", - spec: defaultResponsiveGridLayoutItemSpec(), -}); - -export interface ResponsiveGridLayoutItemSpec { - element: ElementReference; - repeat?: ResponsiveGridRepeatOptions; - conditionalRendering?: ConditionalRenderingGroupKind; -} - -export const defaultResponsiveGridLayoutItemSpec = (): ResponsiveGridLayoutItemSpec => ({ - element: defaultElementReference(), -}); - -export interface TabsLayoutKind { - kind: "TabsLayout"; - spec: TabsLayoutSpec; -} - -export const defaultTabsLayoutKind = (): TabsLayoutKind => ({ - kind: "TabsLayout", - spec: defaultTabsLayoutSpec(), -}); - -export interface TabsLayoutSpec { - tabs: TabsLayoutTabKind[]; -} - -export const defaultTabsLayoutSpec = (): TabsLayoutSpec => ({ - tabs: [], -}); - -export interface TabsLayoutTabKind { - kind: "TabsLayoutTab"; - spec: TabsLayoutTabSpec; -} - -export const defaultTabsLayoutTabKind = (): TabsLayoutTabKind => ({ - kind: "TabsLayoutTab", - spec: defaultTabsLayoutTabSpec(), -}); - -export interface TabsLayoutTabSpec { - title?: string; - layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind; -} - -export const defaultTabsLayoutTabSpec = (): TabsLayoutTabSpec => ({ - layout: defaultGridLayoutKind(), -}); - -export interface PanelSpec { - id: number; - title: string; - description: string; - links: DataLink[]; - data: QueryGroupKind; - vizConfig: VizConfigKind; - transparent?: boolean; -} - -export const defaultPanelSpec = (): PanelSpec => ({ - id: 0, - title: "", - description: "", - links: [], - data: defaultQueryGroupKind(), - vizConfig: defaultVizConfigKind(), -}); - -export interface PanelKind { - kind: "Panel"; - spec: PanelSpec; -} - -export const defaultPanelKind = (): PanelKind => ({ - kind: "Panel", - spec: defaultPanelSpec(), -}); - -export interface ElementReference { - kind: "ElementReference"; - name: string; -} - -export const defaultElementReference = (): ElementReference => ({ - kind: "ElementReference", - name: "", -}); - -// Variable types -export type VariableValue = VariableValueSingle | VariableValueSingle[]; - -export const defaultVariableValue = (): VariableValue => (defaultVariableValueSingle()); - -export type VariableValueSingle = string | boolean | number | CustomVariableValue; - -export const defaultVariableValueSingle = (): VariableValueSingle => (""); - -// Custom formatter variable -export interface CustomFormatterVariable { - name: string; - type: VariableType; - multi: boolean; - includeAll: boolean; -} - -export const defaultCustomFormatterVariable = (): CustomFormatterVariable => ({ - name: "", - type: "query", - multi: false, - includeAll: false, -}); - -// Custom variable value -export interface CustomVariableValue { - // The format name or function used in the expression - formatter: string | VariableCustomFormatterFn; -} - -export const defaultCustomVariableValue = (): CustomVariableValue => ({ - formatter: "", -}); - -// Custom formatter function -export interface VariableCustomFormatterFn { - value: any; - legacyVariableModel: { - name: string; - type: VariableType; - multi: boolean; - includeAll: boolean; - }; - legacyDefaultFormatter?: VariableCustomFormatterFn; -} - -export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn => ({ - value: {}, - legacyVariableModel: { - name: "", - type: "query", - multi: false, - includeAll: false, -}, -}); - -// Dashboard variable type -// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on. -// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only). -// `constant`: Define a hidden constant. -// `datasource`: Quickly change the data source for an entire dashboard. -// `interval`: Interval variables represent time spans. -// `textbox`: Display a free text input field with an optional default value. -// `custom`: Define the variable options manually using a comma-separated list. -// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables -export type VariableType = "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" | "snapshot"; - -export const defaultVariableType = (): VariableType => ("query"); - -export type VariableKind = QueryVariableKind | TextVariableKind | ConstantVariableKind | DatasourceVariableKind | IntervalVariableKind | CustomVariableKind | GroupByVariableKind | AdhocVariableKind; - -export const defaultVariableKind = (): VariableKind => (defaultQueryVariableKind()); - -// Sort variable options -// Accepted values are: -// `disabled`: No sorting -// `alphabeticalAsc`: Alphabetical ASC -// `alphabeticalDesc`: Alphabetical DESC -// `numericalAsc`: Numerical ASC -// `numericalDesc`: Numerical DESC -// `alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC -// `alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC -// `naturalAsc`: Natural ASC -// `naturalDesc`: Natural DESC -// VariableSort enum with default value -export type VariableSort = "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc"; - -export const defaultVariableSort = (): VariableSort => ("disabled"); - -// Options to config when to refresh a variable -// `never`: Never refresh the variable -// `onDashboardLoad`: Queries the data source every time the dashboard loads. -// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes. -export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged"; - -export const defaultVariableRefresh = (): VariableRefresh => ("never"); - -// Determine if the variable shows on dashboard -// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing). -export type VariableHide = "dontHide" | "hideLabel" | "hideVariable"; - -export const defaultVariableHide = (): VariableHide => ("dontHide"); - -// FIXME: should we introduce this? --- Variable value option -export interface VariableValueOption { - label: string; - value: VariableValueSingle; - group?: string; -} - -export const defaultVariableValueOption = (): VariableValueOption => ({ - label: "", - value: defaultVariableValueSingle(), -}); - -// Variable option specification -export interface VariableOption { - // Whether the option is selected or not - selected?: boolean; - // Text to be displayed for the option - text: string | string[]; - // Value of the option - value: string | string[]; -} - -export const defaultVariableOption = (): VariableOption => ({ - text: "", - value: "", -}); - -// Query variable specification -export interface QueryVariableSpec { - name: string; - current: VariableOption; - label?: string; - hide: VariableHide; - refresh: VariableRefresh; - skipUrlSync: boolean; - description?: string; - datasource?: DataSourceRef; - query: DataQueryKind; - regex: string; - sort: VariableSort; - definition?: string; - options: VariableOption[]; - multi: boolean; - includeAll: boolean; - allValue?: string; - placeholder?: string; -} - -export const defaultQueryVariableSpec = (): QueryVariableSpec => ({ - name: "", - current: { text: "", value: "", }, - hide: "dontHide", - refresh: "never", - skipUrlSync: false, - query: defaultDataQueryKind(), - regex: "", - sort: "disabled", - options: [], - multi: false, - includeAll: false, -}); - -// Query variable kind -export interface QueryVariableKind { - kind: "QueryVariable"; - spec: QueryVariableSpec; -} - -export const defaultQueryVariableKind = (): QueryVariableKind => ({ - kind: "QueryVariable", - spec: defaultQueryVariableSpec(), -}); - -// Text variable specification -export interface TextVariableSpec { - name: string; - current: VariableOption; - query: string; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultTextVariableSpec = (): TextVariableSpec => ({ - name: "", - current: { text: "", value: "", }, - query: "", - hide: "dontHide", - skipUrlSync: false, -}); - -// Text variable kind -export interface TextVariableKind { - kind: "TextVariable"; - spec: TextVariableSpec; -} - -export const defaultTextVariableKind = (): TextVariableKind => ({ - kind: "TextVariable", - spec: defaultTextVariableSpec(), -}); - -// Constant variable specification -export interface ConstantVariableSpec { - name: string; - query: string; - current: VariableOption; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultConstantVariableSpec = (): ConstantVariableSpec => ({ - name: "", - query: "", - current: { text: "", value: "", }, - hide: "dontHide", - skipUrlSync: false, -}); - -// Constant variable kind -export interface ConstantVariableKind { - kind: "ConstantVariable"; - spec: ConstantVariableSpec; -} - -export const defaultConstantVariableKind = (): ConstantVariableKind => ({ - kind: "ConstantVariable", - spec: defaultConstantVariableSpec(), -}); - -// Datasource variable specification -export interface DatasourceVariableSpec { - name: string; - pluginId: string; - refresh: VariableRefresh; - regex: string; - current: VariableOption; - options: VariableOption[]; - multi: boolean; - includeAll: boolean; - allValue?: string; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({ - name: "", - pluginId: "", - refresh: "never", - regex: "", - current: { text: "", value: "", }, - options: [], - multi: false, - includeAll: false, - hide: "dontHide", - skipUrlSync: false, -}); - -// Datasource variable kind -export interface DatasourceVariableKind { - kind: "DatasourceVariable"; - spec: DatasourceVariableSpec; -} - -export const defaultDatasourceVariableKind = (): DatasourceVariableKind => ({ - kind: "DatasourceVariable", - spec: defaultDatasourceVariableSpec(), -}); - -// Interval variable specification -export interface IntervalVariableSpec { - name: string; - query: string; - current: VariableOption; - options: VariableOption[]; - auto: boolean; - auto_min: string; - auto_count: number; - refresh: VariableRefresh; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({ - name: "", - query: "", - current: { text: "", value: "", }, - options: [], - auto: false, - auto_min: "", - auto_count: 0, - refresh: "never", - hide: "dontHide", - skipUrlSync: false, -}); - -// Interval variable kind -export interface IntervalVariableKind { - kind: "IntervalVariable"; - spec: IntervalVariableSpec; -} - -export const defaultIntervalVariableKind = (): IntervalVariableKind => ({ - kind: "IntervalVariable", - spec: defaultIntervalVariableSpec(), -}); - -// Custom variable specification -export interface CustomVariableSpec { - name: string; - query: string; - current: VariableOption; - options: VariableOption[]; - multi: boolean; - includeAll: boolean; - allValue?: string; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultCustomVariableSpec = (): CustomVariableSpec => ({ - name: "", - query: "", - current: defaultVariableOption(), - options: [], - multi: false, - includeAll: false, - hide: "dontHide", - skipUrlSync: false, -}); - -// Custom variable kind -export interface CustomVariableKind { - kind: "CustomVariable"; - spec: CustomVariableSpec; -} - -export const defaultCustomVariableKind = (): CustomVariableKind => ({ - kind: "CustomVariable", - spec: defaultCustomVariableSpec(), -}); - -// GroupBy variable specification -export interface GroupByVariableSpec { - name: string; - datasource?: DataSourceRef; - current: VariableOption; - options: VariableOption[]; - multi: boolean; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ - name: "", - current: { text: "", value: "", }, - options: [], - multi: false, - hide: "dontHide", - skipUrlSync: false, -}); - -// Group variable kind -export interface GroupByVariableKind { - kind: "GroupByVariable"; - spec: GroupByVariableSpec; -} - -export const defaultGroupByVariableKind = (): GroupByVariableKind => ({ - kind: "GroupByVariable", - spec: defaultGroupByVariableSpec(), -}); - -// Adhoc variable specification -export interface AdhocVariableSpec { - name: string; - datasource?: DataSourceRef; - baseFilters: AdHocFilterWithLabels[]; - filters: AdHocFilterWithLabels[]; - defaultKeys: MetricFindValue[]; - label?: string; - hide: VariableHide; - skipUrlSync: boolean; - description?: string; -} - -export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({ - name: "", - baseFilters: [], - filters: [], - defaultKeys: [], - hide: "dontHide", - skipUrlSync: false, -}); - -// Define the MetricFindValue type -export interface MetricFindValue { - text: string; - value?: string | number; - group?: string; - expandable?: boolean; -} - -export const defaultMetricFindValue = (): MetricFindValue => ({ - text: "", -}); - -// Define the AdHocFilterWithLabels type -export interface AdHocFilterWithLabels { - key: string; - operator: string; - value: string; - values?: string[]; - keyLabel?: string; - valueLabels?: string[]; - forceEdit?: boolean; - // @deprecated - condition?: string; -} - -export const defaultAdHocFilterWithLabels = (): AdHocFilterWithLabels => ({ - key: "", - operator: "", - value: "", -}); - -// Adhoc variable kind -export interface AdhocVariableKind { - kind: "AdhocVariable"; - spec: AdhocVariableSpec; -} - -export const defaultAdhocVariableKind = (): AdhocVariableKind => ({ - kind: "AdhocVariable", - spec: defaultAdhocVariableSpec(), -}); - export interface ConditionalRenderingGroupKind { kind: "ConditionalRenderingGroup"; spec: ConditionalRenderingGroupSpec; @@ -1481,3 +813,671 @@ export const defaultConditionalRenderingTimeIntervalSpec = (): ConditionalRender value: "", }); +export interface ResponsiveGridLayoutKind { + kind: "ResponsiveGridLayout"; + spec: ResponsiveGridLayoutSpec; +} + +export const defaultResponsiveGridLayoutKind = (): ResponsiveGridLayoutKind => ({ + kind: "ResponsiveGridLayout", + spec: defaultResponsiveGridLayoutSpec(), +}); + +export interface ResponsiveGridLayoutSpec { + row: string; + col: string; + items: ResponsiveGridLayoutItemKind[]; +} + +export const defaultResponsiveGridLayoutSpec = (): ResponsiveGridLayoutSpec => ({ + row: "", + col: "", + items: [], +}); + +export interface ResponsiveGridLayoutItemKind { + kind: "ResponsiveGridLayoutItem"; + spec: ResponsiveGridLayoutItemSpec; +} + +export const defaultResponsiveGridLayoutItemKind = (): ResponsiveGridLayoutItemKind => ({ + kind: "ResponsiveGridLayoutItem", + spec: defaultResponsiveGridLayoutItemSpec(), +}); + +export interface ResponsiveGridLayoutItemSpec { + element: ElementReference; + repeat?: ResponsiveGridRepeatOptions; + conditionalRendering?: ConditionalRenderingGroupKind; +} + +export const defaultResponsiveGridLayoutItemSpec = (): ResponsiveGridLayoutItemSpec => ({ + element: defaultElementReference(), +}); + +export interface ResponsiveGridRepeatOptions { + mode: "variable"; + value: string; +} + +export const defaultResponsiveGridRepeatOptions = (): ResponsiveGridRepeatOptions => ({ + mode: RepeatMode, + value: "", +}); + +export interface TabsLayoutKind { + kind: "TabsLayout"; + spec: TabsLayoutSpec; +} + +export const defaultTabsLayoutKind = (): TabsLayoutKind => ({ + kind: "TabsLayout", + spec: defaultTabsLayoutSpec(), +}); + +export interface TabsLayoutSpec { + tabs: TabsLayoutTabKind[]; +} + +export const defaultTabsLayoutSpec = (): TabsLayoutSpec => ({ + tabs: [], +}); + +export interface TabsLayoutTabKind { + kind: "TabsLayoutTab"; + spec: TabsLayoutTabSpec; +} + +export const defaultTabsLayoutTabKind = (): TabsLayoutTabKind => ({ + kind: "TabsLayoutTab", + spec: defaultTabsLayoutTabSpec(), +}); + +export interface TabsLayoutTabSpec { + title?: string; + layout: GridLayoutKind | RowsLayoutKind | ResponsiveGridLayoutKind | TabsLayoutKind; +} + +export const defaultTabsLayoutTabSpec = (): TabsLayoutTabSpec => ({ + layout: defaultGridLayoutKind(), +}); + +// Links with references to other dashboards or external resources +export interface DashboardLink { + // Title to display with the link + title: string; + // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) + // FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType` + type: DashboardLinkType; + // Icon name to be displayed with the link + icon: string; + // Tooltip to display when the user hovers their mouse over it + tooltip: string; + // Link URL. Only required/valid if the type is link + url?: string; + // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards + tags: string[]; + // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards + asDropdown: boolean; + // If true, the link will be opened in a new tab + targetBlank: boolean; + // If true, includes current template variables values in the link as query params + includeVars: boolean; + // If true, includes current time range in the link as query params + keepTime: boolean; +} + +export const defaultDashboardLink = (): DashboardLink => ({ + title: "", + type: "link", + icon: "", + tooltip: "", + tags: [], + asDropdown: false, + targetBlank: false, + includeVars: false, + keepTime: false, +}); + +// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) +export type DashboardLinkType = "link" | "dashboards"; + +export const defaultDashboardLinkType = (): DashboardLinkType => ("link"); + +// Time configuration +// It defines the default time config for the time picker, the refresh picker for the specific dashboard. +export interface TimeSettingsSpec { + // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc". + timezone?: string; + // Start time range for dashboard. + // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". + from: string; + // End time range for dashboard. + // Accepted values are relative time strings like "now-6h" or absolute time strings like "2020-07-10T08:00:00.000Z". + to: string; + // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". + // v1: refresh + autoRefresh: string; + // Interval options available in the refresh picker dropdown. + // v1: timepicker.refresh_intervals + autoRefreshIntervals: string[]; + // Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. + // v1: timepicker.quick_ranges , not exposed in the UI + quickRanges?: TimeRangeOption[]; + // Whether timepicker is visible or not. + // v1: timepicker.hidden + hideTimepicker: boolean; + // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday". + weekStart?: "saturday" | "monday" | "sunday"; + // The month that the fiscal year starts on. 0 = January, 11 = December + fiscalYearStartMonth: number; + // Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. + // v1: timepicker.nowDelay + nowDelay?: string; +} + +export const defaultTimeSettingsSpec = (): TimeSettingsSpec => ({ + timezone: "browser", + from: "now-6h", + to: "now", + autoRefresh: "", + autoRefreshIntervals: [ +"5s", +"10s", +"30s", +"1m", +"5m", +"15m", +"30m", +"1h", +"2h", +"1d", +], + hideTimepicker: false, + fiscalYearStartMonth: 0, +}); + +export interface TimeRangeOption { + display: string; + from: string; + to: string; +} + +export const defaultTimeRangeOption = (): TimeRangeOption => ({ + display: "Last 6 hours", + from: "now-6h", + to: "now", +}); + +export type VariableKind = QueryVariableKind | TextVariableKind | ConstantVariableKind | DatasourceVariableKind | IntervalVariableKind | CustomVariableKind | GroupByVariableKind | AdhocVariableKind; + +export const defaultVariableKind = (): VariableKind => (defaultQueryVariableKind()); + +// Query variable kind +export interface QueryVariableKind { + kind: "QueryVariable"; + spec: QueryVariableSpec; +} + +export const defaultQueryVariableKind = (): QueryVariableKind => ({ + kind: "QueryVariable", + spec: defaultQueryVariableSpec(), +}); + +// Query variable specification +export interface QueryVariableSpec { + name: string; + current: VariableOption; + label?: string; + hide: VariableHide; + refresh: VariableRefresh; + skipUrlSync: boolean; + description?: string; + datasource?: DataSourceRef; + query: DataQueryKind; + regex: string; + sort: VariableSort; + definition?: string; + options: VariableOption[]; + multi: boolean; + includeAll: boolean; + allValue?: string; + placeholder?: string; +} + +export const defaultQueryVariableSpec = (): QueryVariableSpec => ({ + name: "", + current: { text: "", value: "", }, + hide: "dontHide", + refresh: "never", + skipUrlSync: false, + query: defaultDataQueryKind(), + regex: "", + sort: "disabled", + options: [], + multi: false, + includeAll: false, +}); + +// Variable option specification +export interface VariableOption { + // Whether the option is selected or not + selected?: boolean; + // Text to be displayed for the option + text: string | string[]; + // Value of the option + value: string | string[]; +} + +export const defaultVariableOption = (): VariableOption => ({ + text: "", + value: "", +}); + +// Determine if the variable shows on dashboard +// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing). +export type VariableHide = "dontHide" | "hideLabel" | "hideVariable"; + +export const defaultVariableHide = (): VariableHide => ("dontHide"); + +// Options to config when to refresh a variable +// `never`: Never refresh the variable +// `onDashboardLoad`: Queries the data source every time the dashboard loads. +// `onTimeRangeChanged`: Queries the data source when the dashboard time range changes. +export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged"; + +export const defaultVariableRefresh = (): VariableRefresh => ("never"); + +// Sort variable options +// Accepted values are: +// `disabled`: No sorting +// `alphabeticalAsc`: Alphabetical ASC +// `alphabeticalDesc`: Alphabetical DESC +// `numericalAsc`: Numerical ASC +// `numericalDesc`: Numerical DESC +// `alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC +// `alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC +// `naturalAsc`: Natural ASC +// `naturalDesc`: Natural DESC +// VariableSort enum with default value +export type VariableSort = "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAsc" | "numericalDesc" | "alphabeticalCaseInsensitiveAsc" | "alphabeticalCaseInsensitiveDesc" | "naturalAsc" | "naturalDesc"; + +export const defaultVariableSort = (): VariableSort => ("disabled"); + +// Text variable kind +export interface TextVariableKind { + kind: "TextVariable"; + spec: TextVariableSpec; +} + +export const defaultTextVariableKind = (): TextVariableKind => ({ + kind: "TextVariable", + spec: defaultTextVariableSpec(), +}); + +// Text variable specification +export interface TextVariableSpec { + name: string; + current: VariableOption; + query: string; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultTextVariableSpec = (): TextVariableSpec => ({ + name: "", + current: { text: "", value: "", }, + query: "", + hide: "dontHide", + skipUrlSync: false, +}); + +// Constant variable kind +export interface ConstantVariableKind { + kind: "ConstantVariable"; + spec: ConstantVariableSpec; +} + +export const defaultConstantVariableKind = (): ConstantVariableKind => ({ + kind: "ConstantVariable", + spec: defaultConstantVariableSpec(), +}); + +// Constant variable specification +export interface ConstantVariableSpec { + name: string; + query: string; + current: VariableOption; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultConstantVariableSpec = (): ConstantVariableSpec => ({ + name: "", + query: "", + current: { text: "", value: "", }, + hide: "dontHide", + skipUrlSync: false, +}); + +// Datasource variable kind +export interface DatasourceVariableKind { + kind: "DatasourceVariable"; + spec: DatasourceVariableSpec; +} + +export const defaultDatasourceVariableKind = (): DatasourceVariableKind => ({ + kind: "DatasourceVariable", + spec: defaultDatasourceVariableSpec(), +}); + +// Datasource variable specification +export interface DatasourceVariableSpec { + name: string; + pluginId: string; + refresh: VariableRefresh; + regex: string; + current: VariableOption; + options: VariableOption[]; + multi: boolean; + includeAll: boolean; + allValue?: string; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultDatasourceVariableSpec = (): DatasourceVariableSpec => ({ + name: "", + pluginId: "", + refresh: "never", + regex: "", + current: { text: "", value: "", }, + options: [], + multi: false, + includeAll: false, + hide: "dontHide", + skipUrlSync: false, +}); + +// Interval variable kind +export interface IntervalVariableKind { + kind: "IntervalVariable"; + spec: IntervalVariableSpec; +} + +export const defaultIntervalVariableKind = (): IntervalVariableKind => ({ + kind: "IntervalVariable", + spec: defaultIntervalVariableSpec(), +}); + +// Interval variable specification +export interface IntervalVariableSpec { + name: string; + query: string; + current: VariableOption; + options: VariableOption[]; + auto: boolean; + auto_min: string; + auto_count: number; + refresh: VariableRefresh; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultIntervalVariableSpec = (): IntervalVariableSpec => ({ + name: "", + query: "", + current: { text: "", value: "", }, + options: [], + auto: false, + auto_min: "", + auto_count: 0, + refresh: "never", + hide: "dontHide", + skipUrlSync: false, +}); + +// Custom variable kind +export interface CustomVariableKind { + kind: "CustomVariable"; + spec: CustomVariableSpec; +} + +export const defaultCustomVariableKind = (): CustomVariableKind => ({ + kind: "CustomVariable", + spec: defaultCustomVariableSpec(), +}); + +// Custom variable specification +export interface CustomVariableSpec { + name: string; + query: string; + current: VariableOption; + options: VariableOption[]; + multi: boolean; + includeAll: boolean; + allValue?: string; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultCustomVariableSpec = (): CustomVariableSpec => ({ + name: "", + query: "", + current: defaultVariableOption(), + options: [], + multi: false, + includeAll: false, + hide: "dontHide", + skipUrlSync: false, +}); + +// Group variable kind +export interface GroupByVariableKind { + kind: "GroupByVariable"; + spec: GroupByVariableSpec; +} + +export const defaultGroupByVariableKind = (): GroupByVariableKind => ({ + kind: "GroupByVariable", + spec: defaultGroupByVariableSpec(), +}); + +// GroupBy variable specification +export interface GroupByVariableSpec { + name: string; + datasource?: DataSourceRef; + current: VariableOption; + options: VariableOption[]; + multi: boolean; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({ + name: "", + current: { text: "", value: "", }, + options: [], + multi: false, + hide: "dontHide", + skipUrlSync: false, +}); + +// Adhoc variable kind +export interface AdhocVariableKind { + kind: "AdhocVariable"; + spec: AdhocVariableSpec; +} + +export const defaultAdhocVariableKind = (): AdhocVariableKind => ({ + kind: "AdhocVariable", + spec: defaultAdhocVariableSpec(), +}); + +// Adhoc variable specification +export interface AdhocVariableSpec { + name: string; + datasource?: DataSourceRef; + baseFilters: AdHocFilterWithLabels[]; + filters: AdHocFilterWithLabels[]; + defaultKeys: MetricFindValue[]; + label?: string; + hide: VariableHide; + skipUrlSync: boolean; + description?: string; +} + +export const defaultAdhocVariableSpec = (): AdhocVariableSpec => ({ + name: "", + baseFilters: [], + filters: [], + defaultKeys: [], + hide: "dontHide", + skipUrlSync: false, +}); + +// Define the AdHocFilterWithLabels type +export interface AdHocFilterWithLabels { + key: string; + operator: string; + value: string; + values?: string[]; + keyLabel?: string; + valueLabels?: string[]; + forceEdit?: boolean; + // @deprecated + condition?: string; +} + +export const defaultAdHocFilterWithLabels = (): AdHocFilterWithLabels => ({ + key: "", + operator: "", + value: "", +}); + +// Define the MetricFindValue type +export interface MetricFindValue { + text: string; + value?: string | number; + group?: string; + expandable?: boolean; +} + +export const defaultMetricFindValue = (): MetricFindValue => ({ + text: "", +}); + +// Supported value mapping types +// `value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. +// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. +// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. +// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. +export type MappingType = "value" | "range" | "regex" | "special"; + +export const defaultMappingType = (): MappingType => ("value"); + +// --- Common types --- +export interface Kind { + kind: string; + spec: any; + metadata?: any; +} + +export const defaultKind = (): Kind => ({ + kind: "", + spec: {}, +}); + +// Variable types +export type VariableValue = VariableValueSingle | VariableValueSingle[]; + +export const defaultVariableValue = (): VariableValue => (defaultVariableValueSingle()); + +export type VariableValueSingle = string | boolean | number | CustomVariableValue; + +export const defaultVariableValueSingle = (): VariableValueSingle => (""); + +// Custom variable value +export interface CustomVariableValue { + // The format name or function used in the expression + formatter: string | VariableCustomFormatterFn; +} + +export const defaultCustomVariableValue = (): CustomVariableValue => ({ + formatter: "", +}); + +// Custom formatter function +export interface VariableCustomFormatterFn { + value: any; + legacyVariableModel: { + name: string; + type: VariableType; + multi: boolean; + includeAll: boolean; + }; + legacyDefaultFormatter?: VariableCustomFormatterFn; +} + +export const defaultVariableCustomFormatterFn = (): VariableCustomFormatterFn => ({ + value: {}, + legacyVariableModel: { + name: "", + type: "query", + multi: false, + includeAll: false, +}, +}); + +// Dashboard variable type +// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on. +// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only). +// `constant`: Define a hidden constant. +// `datasource`: Quickly change the data source for an entire dashboard. +// `interval`: Interval variables represent time spans. +// `textbox`: Display a free text input field with an optional default value. +// `custom`: Define the variable options manually using a comma-separated list. +// `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables +export type VariableType = "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" | "snapshot"; + +export const defaultVariableType = (): VariableType => ("query"); + +// Custom formatter variable +export interface CustomFormatterVariable { + name: string; + type: VariableType; + multi: boolean; + includeAll: boolean; +} + +export const defaultCustomFormatterVariable = (): CustomFormatterVariable => ({ + name: "", + type: "query", + multi: false, + includeAll: false, +}); + +// FIXME: should we introduce this? --- Variable value option +export interface VariableValueOption { + label: string; + value: VariableValueSingle; + group?: string; +} + +export const defaultVariableValueOption = (): VariableValueOption => ({ + label: "", + value: defaultVariableValueSingle(), +}); + diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx index 387fcf41dca..fbb8f414e4d 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx @@ -99,6 +99,10 @@ export class DashboardEditPane extends SceneObjectBase { } } + public getSelection(): SceneObject | SceneObject[] | undefined { + return this.state.selection?.getSelection(); + } + public selectObject(obj: SceneObject, id: string, multi?: boolean) { const prevItem = this.state.selection?.getFirstObject(); if (prevItem === obj && !multi) { diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 16645157dfa..b2de2c14ca0 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -75,7 +75,7 @@ import { LayoutOrchestrator } from './layout-manager/LayoutOrchestrator'; import { LayoutRestorer } from './layouts-shared/LayoutRestorer'; import { addNewRowTo, addNewTabTo } from './layouts-shared/addNew'; import { DashboardLayoutManager } from './types/DashboardLayoutManager'; -import { LayoutParent } from './types/LayoutParent'; +import { isLayoutParent, LayoutParent } from './types/LayoutParent'; export const PERSISTED_PROPS = ['title', 'description', 'tags', 'editable', 'graphTooltip', 'links', 'meta', 'preload']; export const PANEL_SEARCH_VAR = 'systemPanelFilterVar'; @@ -500,6 +500,13 @@ export class DashboardScene extends SceneObjectBase impleme this.onEnterEditMode(); } + const selectedObject = this.state.editPane.getSelection(); + if (selectedObject && !Array.isArray(selectedObject) && isLayoutParent(selectedObject)) { + const layout = selectedObject.getLayout(); + layout.addPanel(vizPanel); + return; + } + // Add panel to layout this.state.body.addPanel(vizPanel); } @@ -609,10 +616,22 @@ export class DashboardScene extends SceneObjectBase impleme } public onCreateNewRow() { + const selectedObject = this.state.editPane.getSelection(); + if (selectedObject && !Array.isArray(selectedObject) && isLayoutParent(selectedObject)) { + const layout = selectedObject.getLayout(); + return addNewRowTo(layout); + } + return addNewRowTo(this.state.body); } public onCreateNewTab() { + const selectedObject = this.state.editPane.getSelection(); + if (selectedObject && !Array.isArray(selectedObject) && isLayoutParent(selectedObject)) { + const layout = selectedObject.getLayout(); + return addNewTabTo(layout); + } + return addNewTabTo(this.state.body); } diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index f8cb979f064..3628ea6f026 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -256,6 +256,7 @@ export class DefaultGridLayoutManager sceneGridLayout.setState({ children: [row, ...sceneGridLayout.state.children] }); + this.publishEvent(new NewObjectAddedToCanvasEvent(row), true); return row; } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 7ad15344c45..15e3efbcd31 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -89,6 +89,7 @@ export class RowsLayoutManager extends SceneObjectBase i public addNewRow(): RowItem { const row = new RowItem(); this.setState({ rows: [...this.state.rows, row] }); + this.publishEvent(new NewObjectAddedToCanvasEvent(row), true); return row; } @@ -112,7 +113,7 @@ export class RowsLayoutManager extends SceneObjectBase i }); } - public addRowAbove(row: RowItem) { + public addRowAbove(row: RowItem): RowItem { const index = this.state.rows.indexOf(row); const newRow = new RowItem(); const newRows = [...this.state.rows]; @@ -121,9 +122,11 @@ export class RowsLayoutManager extends SceneObjectBase i this.setState({ rows: newRows }); this.publishEvent(new NewObjectAddedToCanvasEvent(newRow), true); + + return newRow; } - public addRowBelow(row: RowItem) { + public addRowBelow(row: RowItem): RowItem { const rows = this.state.rows; let index = rows.indexOf(row); @@ -139,6 +142,8 @@ export class RowsLayoutManager extends SceneObjectBase i this.setState({ rows: newRows }); this.publishEvent(new NewObjectAddedToCanvasEvent(newRow), true); + + return newRow; } public removeRow(row: RowItem) { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index 6478ff19c54..71af05bfce7 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -110,6 +110,7 @@ export class TabsLayoutManager extends SceneObjectBase i public addNewTab() { const newTab = new TabItem(); this.setState({ tabs: [...this.state.tabs, newTab], currentTabIndex: this.state.tabs.length }); + this.publishEvent(new NewObjectAddedToCanvasEvent(newTab), true); return newTab; } @@ -143,20 +144,24 @@ export class TabsLayoutManager extends SceneObjectBase i this.publishEvent(new ObjectRemovedFromCanvasEvent(tabToRemove), true); } - public addTabBefore(tab: TabItem) { + public addTabBefore(tab: TabItem): TabItem { const newTab = new TabItem(); const tabs = this.state.tabs.slice(); tabs.splice(tabs.indexOf(tab), 0, newTab); this.setState({ tabs, currentTabIndex: this.state.currentTabIndex }); this.publishEvent(new NewObjectAddedToCanvasEvent(newTab), true); + + return newTab; } - public addTabAfter(tab: TabItem) { + public addTabAfter(tab: TabItem): TabItem { const newTab = new TabItem(); const tabs = this.state.tabs.slice(); tabs.splice(tabs.indexOf(tab) + 1, 0, newTab); this.setState({ tabs, currentTabIndex: this.state.currentTabIndex + 1 }); this.publishEvent(new NewObjectAddedToCanvasEvent(newTab), true); + + return newTab; } public moveTabLeft(tab: TabItem) { diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts b/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts index 0a844199114..d7c139d1716 100644 --- a/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts +++ b/public/app/features/dashboard-scene/scene/layouts-shared/addNew.ts @@ -1,5 +1,5 @@ import { config } from '@grafana/runtime'; -import { SceneGridRow } from '@grafana/scenes'; +import { sceneGraph, SceneGridRow } from '@grafana/scenes'; import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared'; import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager'; @@ -11,18 +11,25 @@ import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; import { isLayoutParent } from '../types/LayoutParent'; export function addNewTabTo(layout: DashboardLayoutManager): TabItem { - if (layout instanceof TabsLayoutManager) { - const tab = layout.addNewTab(); - layout.publishEvent(new NewObjectAddedToCanvasEvent(tab), true); - return tab; - } - const layoutParent = layout.parent!; if (!isLayoutParent(layoutParent)) { throw new Error('Parent layout is not a LayoutParent'); } - const tabsLayout = TabsLayoutManager.createFromLayout(layoutParent.getLayout()); + // If layout parent is tab item we add new tab after it rather than create a nested tab + if (layoutParent instanceof TabItem) { + const tabsLayout = sceneGraph.getAncestor(layoutParent, TabsLayoutManager); + return tabsLayout.addTabAfter(layoutParent); + } + + if (layout instanceof TabsLayoutManager) { + return layout.addNewTab(); + } + + // Create new tabs layout and wrap the current layout in the first tab + const tabsLayout = TabsLayoutManager.createEmpty(); + tabsLayout.state.tabs[0].setState({ layout: layout.clone() }); + layoutParent.switchLayout(tabsLayout); const tab = tabsLayout.state.tabs[0]; @@ -37,18 +44,25 @@ export function addNewRowTo(layout: DashboardLayoutManager): RowItem | SceneGrid */ if (!config.featureToggles.dashboardNewLayouts) { if (layout instanceof DefaultGridLayoutManager) { - const row = layout.addNewRow(); - layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true); - return row; + return layout.addNewRow(); } else { throw new Error('New dashboard layouts feature not enabled but new layout found'); } } + const layoutParent = layout.parent!; + if (!isLayoutParent(layoutParent)) { + throw new Error('Parent layout is not a LayoutParent'); + } + + // If adding we are adding a row to a row we add it below the current row + if (layoutParent instanceof RowItem) { + const rowsLayout = sceneGraph.getAncestor(layoutParent, RowsLayoutManager); + return rowsLayout.addRowBelow(layoutParent); + } + if (layout instanceof RowsLayoutManager) { - const row = layout.addNewRow(); - layout.publishEvent(new NewObjectAddedToCanvasEvent(row), true); - return row; + return layout.addNewRow(); } if (layout instanceof TabsLayoutManager) { @@ -59,11 +73,6 @@ export function addNewRowTo(layout: DashboardLayoutManager): RowItem | SceneGrid // If we want to add a row and current layout is custom grid or auto we migrate to rows layout // And wrap current layout in a row - const layoutParent = layout.parent!; - if (!isLayoutParent(layoutParent)) { - throw new Error('Parent layout is not a LayoutParent'); - } - const rowsLayout = RowsLayoutManager.createFromLayout(layoutParent.getLayout()); layoutParent.switchLayout(rowsLayout); diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts index ef3402cc304..c403b5a3d7a 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/RowsLayoutSerializer.ts @@ -16,9 +16,6 @@ export class RowsLayoutSerializer implements LayoutManagerSerializer { spec: { rows: layoutManager.state.rows.map((row) => { const layout = getLayout(row.state.layout); - if (layout.kind === 'RowsLayout') { - throw new Error('Nested RowsLayout is not supported'); - } const rowKind: RowsLayoutRowKind = { kind: 'RowsLayoutRow', spec: { diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts index d5093fb4663..c3f7eedc9db 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/TabsLayoutSerializer.ts @@ -14,9 +14,6 @@ export class TabsLayoutSerializer implements LayoutManagerSerializer { spec: { tabs: layoutManager.state.tabs.map((tab) => { const layout = getLayout(tab.state.layout); - if (layout.kind === 'TabsLayout') { - throw new Error('Nested TabsLayout is not supported'); - } return { kind: 'TabsLayoutTab', spec: {