Compare commits
1 Commits
sriram/SQL
...
leventebal
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
007010e8cd |
@@ -127,6 +127,8 @@ DashboardLink: {
|
||||
keepTime: bool | *false
|
||||
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
|
||||
placement?: DashboardLinkPlacement
|
||||
// The source that registered the link (if any)
|
||||
source?: ControlSourceRef
|
||||
}
|
||||
|
||||
// Dashboard Link placement. Defines where the link should be displayed.
|
||||
@@ -792,6 +794,13 @@ VariableOption: {
|
||||
value: string | [...string]
|
||||
}
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
ControlSourceRef: {
|
||||
uid: string
|
||||
sourceId: string // E.g. "prometheus"
|
||||
sourceType: string // E.g. "datasource"
|
||||
}
|
||||
|
||||
// Query variable specification
|
||||
QueryVariableSpec: {
|
||||
name: string | *""
|
||||
|
||||
@@ -127,6 +127,8 @@ DashboardLink: {
|
||||
keepTime: bool | *false
|
||||
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
|
||||
placement?: DashboardLinkPlacement
|
||||
// The source that registered the link (if any)
|
||||
source?: ControlSourceRef
|
||||
}
|
||||
|
||||
// Dashboard Link placement. Defines where the link should be displayed.
|
||||
@@ -796,6 +798,14 @@ VariableOption: {
|
||||
value: string | [...string]
|
||||
}
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
ControlSourceRef: {
|
||||
uid: string
|
||||
sourceId: string // E.g. "prometheus"
|
||||
sourceType: string // E.g. "datasource"
|
||||
}
|
||||
|
||||
|
||||
// Query variable specification
|
||||
QueryVariableSpec: {
|
||||
name: string | *""
|
||||
|
||||
@@ -277,6 +277,14 @@ lineage: schemas: [{
|
||||
uid?: string
|
||||
} @cuetsy(kind="interface") @grafana(TSVeneer="type")
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
#ControlSourceRef: {
|
||||
uid: string
|
||||
sourceId: string // E.g. "prometheus"
|
||||
sourceType: string // E.g. "datasource"
|
||||
}
|
||||
|
||||
|
||||
// Links with references to other dashboards or external resources
|
||||
#DashboardLink: {
|
||||
// Title to display with the link
|
||||
@@ -301,6 +309,8 @@ lineage: schemas: [{
|
||||
includeVars: bool | *false
|
||||
// If true, includes current time range in the link as query params
|
||||
keepTime: bool | *false
|
||||
// The source that registered the link (if any)
|
||||
source?: #ControlSourceRef
|
||||
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
|
||||
@@ -277,6 +277,14 @@ lineage: schemas: [{
|
||||
uid?: string
|
||||
} @cuetsy(kind="interface") @grafana(TSVeneer="type")
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
#ControlSourceRef: {
|
||||
uid: string
|
||||
sourceId: string // E.g. "prometheus"
|
||||
sourceType: string // E.g. "datasource"
|
||||
}
|
||||
|
||||
|
||||
// Links with references to other dashboards or external resources
|
||||
#DashboardLink: {
|
||||
// Title to display with the link
|
||||
@@ -301,6 +309,8 @@ lineage: schemas: [{
|
||||
includeVars: bool | *false
|
||||
// If true, includes current time range in the link as query params
|
||||
keepTime: bool | *false
|
||||
// The source that registered the link (if any)
|
||||
source?: #ControlSourceRef
|
||||
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ DashboardLink: {
|
||||
keepTime: bool | *false
|
||||
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
|
||||
placement?: DashboardLinkPlacement
|
||||
// The source that registered the link (if any)
|
||||
source?: ControlSourceRef
|
||||
}
|
||||
|
||||
// Dashboard Link placement. Defines where the link should be displayed.
|
||||
@@ -796,6 +798,13 @@ VariableOption: {
|
||||
value: string | [...string]
|
||||
}
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
ControlSourceRef: {
|
||||
uid: string
|
||||
sourceId: string // E.g. "prometheus"
|
||||
sourceType: string // E.g. "datasource"
|
||||
}
|
||||
|
||||
// Query variable specification
|
||||
QueryVariableSpec: {
|
||||
name: string | *""
|
||||
|
||||
@@ -1238,6 +1238,8 @@ type DashboardDashboardLink struct {
|
||||
KeepTime bool `json:"keepTime"`
|
||||
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
|
||||
Placement *string `json:"placement,omitempty"`
|
||||
// The source that registered the link (if any)
|
||||
Source *DashboardControlSourceRef `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
// NewDashboardDashboardLink creates a new DashboardDashboardLink object.
|
||||
@@ -1266,6 +1268,21 @@ const (
|
||||
// +k8s:openapi-gen=true
|
||||
const DashboardDashboardLinkPlacement = "inControlsMenu"
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardControlSourceRef struct {
|
||||
Uid string `json:"uid"`
|
||||
// E.g. "prometheus"
|
||||
SourceId string `json:"sourceId"`
|
||||
// E.g. "datasource"
|
||||
SourceType string `json:"sourceType"`
|
||||
}
|
||||
|
||||
// NewDashboardControlSourceRef creates a new DashboardControlSourceRef object.
|
||||
func NewDashboardControlSourceRef() *DashboardControlSourceRef {
|
||||
return &DashboardControlSourceRef{}
|
||||
}
|
||||
|
||||
// Time configuration
|
||||
// It defines the default time config for the time picker, the refresh picker for the specific dashboard.
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -44,6 +44,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConditionalRenderingVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConditionalRenderingVariableSpec(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConstantVariableKind": schema_pkg_apis_dashboard_v2alpha1_DashboardConstantVariableKind(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConstantVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardConstantVariableSpec(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardControlSourceRef": schema_pkg_apis_dashboard_v2alpha1_DashboardControlSourceRef(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardConversionStatus": schema_pkg_apis_dashboard_v2alpha1_DashboardConversionStatus(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardCustomVariableKind": schema_pkg_apis_dashboard_v2alpha1_DashboardCustomVariableKind(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardCustomVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardCustomVariableSpec(ref),
|
||||
@@ -1383,6 +1384,43 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardConstantVariableSpec(ref common
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v2alpha1_DashboardControlSourceRef(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Source information for controls (e.g. variables or links)",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"uid": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sourceId": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "E.g. \"prometheus\"",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sourceType": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "E.g. \"datasource\"",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"uid", "sourceId", "sourceType"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v2alpha1_DashboardConversionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@@ -1657,10 +1695,18 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDashboardLink(ref common.Refere
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"source": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The source that registered the link (if any)",
|
||||
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardControlSourceRef"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"title", "type", "icon", "tooltip", "tags", "asDropdown", "targetBlank", "includeVars", "keepTime"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardControlSourceRef"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ DashboardLink: {
|
||||
keepTime: bool | *false
|
||||
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
|
||||
placement?: DashboardLinkPlacement
|
||||
// The source that registered the link (if any)
|
||||
source?: ControlSourceRef
|
||||
}
|
||||
|
||||
// Dashboard Link placement. Defines where the link should be displayed.
|
||||
@@ -800,6 +802,14 @@ VariableOption: {
|
||||
value: string | [...string]
|
||||
}
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
ControlSourceRef: {
|
||||
uid: string
|
||||
sourceId: string // E.g. "prometheus"
|
||||
sourceType: string // E.g. "datasource"
|
||||
}
|
||||
|
||||
|
||||
// Query variable specification
|
||||
QueryVariableSpec: {
|
||||
name: string | *""
|
||||
|
||||
@@ -1242,6 +1242,8 @@ type DashboardDashboardLink struct {
|
||||
KeepTime bool `json:"keepTime"`
|
||||
// Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
|
||||
Placement *string `json:"placement,omitempty"`
|
||||
// The source that registered the link (if any)
|
||||
Source *DashboardControlSourceRef `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
// NewDashboardDashboardLink creates a new DashboardDashboardLink object.
|
||||
@@ -1270,6 +1272,21 @@ const (
|
||||
// +k8s:openapi-gen=true
|
||||
const DashboardDashboardLinkPlacement = "inControlsMenu"
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardControlSourceRef struct {
|
||||
Uid string `json:"uid"`
|
||||
// E.g. "prometheus"
|
||||
SourceId string `json:"sourceId"`
|
||||
// E.g. "datasource"
|
||||
SourceType string `json:"sourceType"`
|
||||
}
|
||||
|
||||
// NewDashboardControlSourceRef creates a new DashboardControlSourceRef object.
|
||||
func NewDashboardControlSourceRef() *DashboardControlSourceRef {
|
||||
return &DashboardControlSourceRef{}
|
||||
}
|
||||
|
||||
// Time configuration
|
||||
// It defines the default time config for the time picker, the refresh picker for the specific dashboard.
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
@@ -44,6 +44,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardConditionalRenderingVariableSpec": schema_pkg_apis_dashboard_v2beta1_DashboardConditionalRenderingVariableSpec(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardConstantVariableKind": schema_pkg_apis_dashboard_v2beta1_DashboardConstantVariableKind(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardConstantVariableSpec": schema_pkg_apis_dashboard_v2beta1_DashboardConstantVariableSpec(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardControlSourceRef": schema_pkg_apis_dashboard_v2beta1_DashboardControlSourceRef(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardConversionStatus": schema_pkg_apis_dashboard_v2beta1_DashboardConversionStatus(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardCustomVariableKind": schema_pkg_apis_dashboard_v2beta1_DashboardCustomVariableKind(ref),
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardCustomVariableSpec": schema_pkg_apis_dashboard_v2beta1_DashboardCustomVariableSpec(ref),
|
||||
@@ -1395,6 +1396,43 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardConstantVariableSpec(ref common.
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v2beta1_DashboardControlSourceRef(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Source information for controls (e.g. variables or links)",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"uid": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sourceId": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "E.g. \"prometheus\"",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sourceType": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "E.g. \"datasource\"",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"uid", "sourceId", "sourceType"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v2beta1_DashboardConversionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@@ -1669,10 +1707,18 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardDashboardLink(ref common.Referen
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"source": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The source that registered the link (if any)",
|
||||
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardControlSourceRef"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"title", "type", "icon", "tooltip", "tags", "asDropdown", "targetBlank", "includeVars", "keepTime"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardControlSourceRef"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
apps/dashboard/pkg/apis/dashboard_manifest.go
generated
4
apps/dashboard/pkg/apis/dashboard_manifest.go
generated
File diff suppressed because one or more lines are too long
@@ -273,6 +273,14 @@ lineage: schemas: [{
|
||||
uid?: string
|
||||
} @cuetsy(kind="interface") @grafana(TSVeneer="type")
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
#ControlSourceRef: {
|
||||
uid: string
|
||||
sourceId: string // E.g. "prometheus"
|
||||
sourceType: string // E.g. "datasource"
|
||||
}
|
||||
|
||||
|
||||
// Links with references to other dashboards or external resources
|
||||
#DashboardLink: {
|
||||
// Title to display with the link
|
||||
@@ -297,6 +305,8 @@ lineage: schemas: [{
|
||||
includeVars: bool | *false
|
||||
// If true, includes current time range in the link as query params
|
||||
keepTime: bool | *false
|
||||
// The source that registered the link (if any)
|
||||
source?: #ControlSourceRef
|
||||
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
|
||||
@@ -295,8 +295,8 @@
|
||||
"@grafana/plugin-ui": "^0.11.1",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "6.52.0",
|
||||
"@grafana/scenes-react": "6.52.0",
|
||||
"@grafana/scenes": "6.53.0--canary.1315.20365173522.0",
|
||||
"@grafana/scenes-react": "6.53.0--canary.1315.20365173522.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { DashboardLink, DataSourceRef } from '@grafana/schema';
|
||||
|
||||
import { deprecationWarning } from '../utils/deprecationWarning';
|
||||
import { makeClassES5Compatible } from '../utils/makeClassES5Compatible';
|
||||
@@ -17,7 +17,7 @@ import { PanelData } from './panel';
|
||||
import { GrafanaPlugin, PluginMeta } from './plugin';
|
||||
import { DataQuery } from './query';
|
||||
import { Scope } from './scopes';
|
||||
import { AdHocVariableFilter } from './templateVars';
|
||||
import { AdHocVariableFilter, TypedVariableModel } from './templateVars';
|
||||
import { RawTimeRange, TimeRange } from './time';
|
||||
import { UserStorage } from './userStorage';
|
||||
import { CustomVariableSupport, DataSourceVariableSupport, StandardVariableSupport } from './variables';
|
||||
@@ -330,6 +330,16 @@ abstract class DataSourceApi<
|
||||
*/
|
||||
getTagValues?(options: DataSourceGetTagValuesOptions<TQuery>): Promise<GetTagResponse> | Promise<MetricFindValue[]>;
|
||||
|
||||
/**
|
||||
* Get default variables that will be added to the dashboard
|
||||
*/
|
||||
getDefaultVariables?(): TypedVariableModel[];
|
||||
|
||||
/**
|
||||
* Get default dashboard links that should be added when this datasource is used.
|
||||
*/
|
||||
getDefaultLinks?(): DashboardLink[];
|
||||
|
||||
/**
|
||||
* Set after constructor call, as the data source instance is the most common thing to pass around
|
||||
* we attach the components to this instance for easy access
|
||||
|
||||
@@ -195,6 +195,13 @@ export interface BaseVariableModel {
|
||||
error: any | null;
|
||||
description: string | null;
|
||||
usedInRepeat?: boolean;
|
||||
source?: ControlSourceRef;
|
||||
}
|
||||
|
||||
export interface ControlSourceRef {
|
||||
uid: string;
|
||||
sourceId: string;
|
||||
sourceType: string;
|
||||
}
|
||||
|
||||
export interface SnapshotVariableModel extends VariableWithOptions {
|
||||
|
||||
@@ -332,6 +332,14 @@ export interface DashboardLink {
|
||||
* Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.
|
||||
*/
|
||||
placement?: DashboardLinkPlacement;
|
||||
/**
|
||||
* The source that registered the link (if any)
|
||||
*/
|
||||
source?: {
|
||||
uid: string;
|
||||
sourceId: string; // E.g. "prometheus"
|
||||
sourceType: string; // E.g. "datasource"
|
||||
};
|
||||
/**
|
||||
* List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards
|
||||
*/
|
||||
|
||||
16
pkg/kinds/dashboard/dashboard_spec_gen.go
generated
16
pkg/kinds/dashboard/dashboard_spec_gen.go
generated
@@ -287,6 +287,8 @@ type DashboardLink struct {
|
||||
IncludeVars bool `json:"includeVars"`
|
||||
// If true, includes current time range in the link as query params
|
||||
KeepTime bool `json:"keepTime"`
|
||||
// The source that registered the link (if any)
|
||||
Source *ControlSourceRef `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
// NewDashboardLink creates a new DashboardLink object.
|
||||
@@ -313,6 +315,20 @@ const (
|
||||
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
|
||||
const DashboardLinkPlacement = "inControlsMenu"
|
||||
|
||||
// Source information for controls (e.g. variables or links)
|
||||
type ControlSourceRef struct {
|
||||
Uid string `json:"uid"`
|
||||
// E.g. "prometheus"
|
||||
SourceId string `json:"sourceId"`
|
||||
// E.g. "datasource"
|
||||
SourceType string `json:"sourceType"`
|
||||
}
|
||||
|
||||
// NewControlSourceRef creates a new ControlSourceRef object.
|
||||
func NewControlSourceRef() *ControlSourceRef {
|
||||
return &ControlSourceRef{}
|
||||
}
|
||||
|
||||
// Transformations allow to manipulate data returned by a query before the system applies a visualization.
|
||||
// Using transformations you can: rename fields, join time series data, perform mathematical operations across queries,
|
||||
// use the output of one transformation as the input to another transformation, etc.
|
||||
|
||||
@@ -1683,6 +1683,29 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardControlSourceRef": {
|
||||
"description": "Source information for controls (e.g. variables or links)",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uid",
|
||||
"sourceId",
|
||||
"sourceType"
|
||||
],
|
||||
"properties": {
|
||||
"sourceId": {
|
||||
"description": "E.g. \"prometheus\"",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceType": {
|
||||
"description": "E.g. \"datasource\"",
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConversionStatus": {
|
||||
"description": "ConversionStatus is the status of the conversion of the dashboard.",
|
||||
"type": "object",
|
||||
@@ -1836,6 +1859,9 @@
|
||||
"placement": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLinkPlacement"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardControlSourceRef"
|
||||
},
|
||||
"tags": {
|
||||
"description": "List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards",
|
||||
"type": "array",
|
||||
@@ -2274,7 +2300,7 @@
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThresholdsConfig"
|
||||
},
|
||||
"unit": {
|
||||
"description": "Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n`suffix:<suffix>` for custom unit that should go after value.\n`prefix:<prefix>` for custom unit that should go before value.\n`time:<format>` For custom date time formats type for example `time:YYYY-MM-DD`.\n`si:<base scale><unit characters>` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n`count:<unit>` for a custom count unit.\n`currency:<unit>` for custom a currency unit.",
|
||||
"description": "Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n`suffix:\u003csuffix\u003e` for custom unit that should go after value.\n`prefix:\u003cprefix\u003e` for custom unit that should go before value.\n`time:\u003cformat\u003e` For custom date time formats type for example `time:YYYY-MM-DD`.\n`si:\u003cbase scale\u003e\u003cunit characters\u003e` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n`count:\u003cunit\u003e` for a custom count unit.\n`currency:\u003cunit\u003e` for custom a currency unit.",
|
||||
"type": "string"
|
||||
},
|
||||
"writeable": {
|
||||
@@ -3790,7 +3816,7 @@
|
||||
],
|
||||
"properties": {
|
||||
"options": {
|
||||
"description": "Map with <value_to_match>: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }",
|
||||
"description": "Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult"
|
||||
@@ -4214,7 +4240,7 @@
|
||||
}
|
||||
},
|
||||
"io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": {
|
||||
"description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:<name>', where <name> is the name of a field in a struct, or key in a map 'v:<value>', where <value> is the exact json formatted value of a list item 'i:<index>', where <index> is position of a item in a list 'k:<keys>', where <keys> is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff",
|
||||
"description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:\u003cname\u003e', where \u003cname\u003e is the name of a field in a struct, or key in a map 'v:\u003cvalue\u003e', where \u003cvalue\u003e is the exact json formatted value of a list item 'i:\u003cindex\u003e', where \u003cindex\u003e is position of a item in a list 'k:\u003ckeys\u003e', where \u003ckeys\u003e is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff",
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": {
|
||||
@@ -4568,4 +4594,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1698,6 +1698,29 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardControlSourceRef": {
|
||||
"description": "Source information for controls (e.g. variables or links)",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uid",
|
||||
"sourceId",
|
||||
"sourceType"
|
||||
],
|
||||
"properties": {
|
||||
"sourceId": {
|
||||
"description": "E.g. \"prometheus\"",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceType": {
|
||||
"description": "E.g. \"datasource\"",
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConversionStatus": {
|
||||
"description": "ConversionStatus is the status of the conversion of the dashboard.",
|
||||
"type": "object",
|
||||
@@ -1851,6 +1874,9 @@
|
||||
"placement": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardLinkPlacement"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardControlSourceRef"
|
||||
},
|
||||
"tags": {
|
||||
"description": "List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards",
|
||||
"type": "array",
|
||||
@@ -2293,7 +2319,7 @@
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardThresholdsConfig"
|
||||
},
|
||||
"unit": {
|
||||
"description": "Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n`suffix:<suffix>` for custom unit that should go after value.\n`prefix:<prefix>` for custom unit that should go before value.\n`time:<format>` For custom date time formats type for example `time:YYYY-MM-DD`.\n`si:<base scale><unit characters>` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n`count:<unit>` for a custom count unit.\n`currency:<unit>` for custom a currency unit.",
|
||||
"description": "Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n`suffix:\u003csuffix\u003e` for custom unit that should go after value.\n`prefix:\u003cprefix\u003e` for custom unit that should go before value.\n`time:\u003cformat\u003e` For custom date time formats type for example `time:YYYY-MM-DD`.\n`si:\u003cbase scale\u003e\u003cunit characters\u003e` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n`count:\u003cunit\u003e` for a custom count unit.\n`currency:\u003cunit\u003e` for custom a currency unit.",
|
||||
"type": "string"
|
||||
},
|
||||
"writeable": {
|
||||
@@ -3816,7 +3842,7 @@
|
||||
],
|
||||
"properties": {
|
||||
"options": {
|
||||
"description": "Map with <value_to_match>: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }",
|
||||
"description": "Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMappingResult"
|
||||
@@ -4245,7 +4271,7 @@
|
||||
}
|
||||
},
|
||||
"io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": {
|
||||
"description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:<name>', where <name> is the name of a field in a struct, or key in a map 'v:<value>', where <value> is the exact json formatted value of a list item 'i:<index>', where <index> is position of a item in a list 'k:<keys>', where <keys> is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff",
|
||||
"description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:\u003cname\u003e', where \u003cname\u003e is the name of a field in a struct, or key in a map 'v:\u003cvalue\u003e', where \u003cvalue\u003e is the exact json formatted value of a list item 'i:\u003cindex\u003e', where \u003cindex\u003e is position of a item in a list 'k:\u003ckeys\u003e', where \u003ckeys\u003e is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff",
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": {
|
||||
@@ -4599,4 +4625,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,14 @@ jest.mock('app/features/playlist/PlaylistSrv', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../utils/dashboardControls', () => ({
|
||||
...jest.requireActual('../utils/dashboardControls'),
|
||||
loadDefaultControlsFromDatasources: jest.fn().mockResolvedValue({
|
||||
defaultVariables: [],
|
||||
defaultLinks: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createTestStore = () =>
|
||||
configureStore({
|
||||
reducer: {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { locationUtil, UrlQueryMap } from '@grafana/data';
|
||||
import { locationUtil, TypedVariableModel, UrlQueryMap } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { config, getBackendSrv, getDataSourceSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||
import { sceneGraph } from '@grafana/scenes';
|
||||
import { DashboardLink } from '@grafana/schema';
|
||||
import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { GetRepositoryFilesWithPathApiResponse, provisioningAPIv0alpha1 } from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { StateManagerBase } from 'app/core/services/StateManagerBase';
|
||||
@@ -41,6 +42,11 @@ import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { buildNewDashboardSaveModel, buildNewDashboardSaveModelV2 } from '../serialization/buildNewDashboardSaveModel';
|
||||
import { transformSaveModelSchemaV2ToScene } from '../serialization/transformSaveModelSchemaV2ToScene';
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import {
|
||||
getDsRefsFromV1Dashboard,
|
||||
getDsRefsFromV2Dashboard,
|
||||
loadDefaultControlsFromDatasources,
|
||||
} from '../utils/dashboardControls';
|
||||
import { restoreDashboardStateFromLocalStorage } from '../utils/dashboardSessionState';
|
||||
|
||||
import { processQueryParamsForDashboardLoad, updateNavModel } from './utils';
|
||||
@@ -85,6 +91,8 @@ export interface LoadDashboardOptions {
|
||||
slug?: string;
|
||||
type?: string;
|
||||
urlFolderUid?: string;
|
||||
defaultVariables?: TypedVariableModel[];
|
||||
defaultLinks?: DashboardLink[];
|
||||
}
|
||||
|
||||
export type HomeDashboardDTO = DashboardDTO & {
|
||||
@@ -114,6 +122,9 @@ abstract class DashboardScenePageStateManagerBase<T>
|
||||
abstract reloadDashboard(queryParams: UrlQueryMap): Promise<void>;
|
||||
abstract transformResponseToScene(rsp: T | null, options: LoadDashboardOptions): DashboardScene | null;
|
||||
abstract loadSnapshotScene(slug: string): Promise<DashboardScene>;
|
||||
abstract getDefaultControls(
|
||||
rsp: T
|
||||
): Promise<{ defaultVariables: TypedVariableModel[]; defaultLinks: DashboardLink[] }>;
|
||||
|
||||
protected cache: Record<string, DashboardScene> = {};
|
||||
|
||||
@@ -370,6 +381,10 @@ abstract class DashboardScenePageStateManagerBase<T>
|
||||
return null;
|
||||
}
|
||||
|
||||
const { defaultVariables, defaultLinks } = await this.getDefaultControls(rsp);
|
||||
options.defaultVariables = defaultVariables;
|
||||
options.defaultLinks = defaultLinks;
|
||||
|
||||
return this.transformResponseToScene(rsp, options);
|
||||
}
|
||||
|
||||
@@ -610,6 +625,14 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
return this.buildDashboardDTOFromInterpolated(interpolatedDashboard);
|
||||
}
|
||||
|
||||
public getDefaultControls(
|
||||
rsp: DashboardDTO
|
||||
): Promise<{ defaultVariables: TypedVariableModel[]; defaultLinks: DashboardLink[] }> {
|
||||
const datasourceRefs = getDsRefsFromV1Dashboard(rsp);
|
||||
|
||||
return loadDefaultControlsFromDatasources(datasourceRefs);
|
||||
}
|
||||
|
||||
public async fetchDashboard({
|
||||
type,
|
||||
slug,
|
||||
@@ -816,7 +839,7 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
}
|
||||
|
||||
if (rsp) {
|
||||
const scene = transformSaveModelSchemaV2ToScene(rsp);
|
||||
const scene = transformSaveModelSchemaV2ToScene(rsp, options);
|
||||
|
||||
// Cache scene only if not coming from Explore, we don't want to cache temporary dashboard
|
||||
if (options.uid) {
|
||||
@@ -829,6 +852,14 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
throw new Error('Dashboard not found');
|
||||
}
|
||||
|
||||
public async getDefaultControls(
|
||||
rsp: DashboardWithAccessInfo<DashboardV2Spec>
|
||||
): Promise<{ defaultVariables: TypedVariableModel[]; defaultLinks: DashboardLink[] }> {
|
||||
const datasourceRefs = getDsRefsFromV2Dashboard(rsp);
|
||||
|
||||
return loadDefaultControlsFromDatasources(datasourceRefs);
|
||||
}
|
||||
|
||||
public async fetchDashboard({
|
||||
type,
|
||||
slug,
|
||||
@@ -1101,6 +1132,15 @@ export class UnifiedDashboardScenePageStateManager extends DashboardScenePageSta
|
||||
public resetActiveManager() {
|
||||
this.activeManager = shouldForceV2API() ? this.v2Manager : this.v1Manager;
|
||||
}
|
||||
|
||||
public async getDefaultControls(
|
||||
rsp: DashboardDTO | DashboardWithAccessInfo<DashboardV2Spec>
|
||||
): Promise<{ defaultVariables: TypedVariableModel[]; defaultLinks: DashboardLink[] }> {
|
||||
if (isDashboardV2Resource(rsp)) {
|
||||
return this.v2Manager.getDefaultControls(rsp);
|
||||
}
|
||||
return this.v1Manager.getDefaultControls(rsp);
|
||||
}
|
||||
}
|
||||
|
||||
const managers: {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SceneDataLayerProvider, SceneVariable } from '@grafana/scenes';
|
||||
import { DashboardLink } from '@grafana/schema';
|
||||
import { Box, Menu, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { sortDefaultLinksFirst, sortDefaultVarsFirst } from '../../utils/dashboardControls';
|
||||
import { DashboardLinkRenderer } from '../DashboardLinkRenderer';
|
||||
import { DataLayerControl } from '../DataLayerControl';
|
||||
import { VariableValueSelectWrapper } from '../VariableControls';
|
||||
@@ -36,7 +37,7 @@ export function DashboardControlsMenu({ variables, links, annotations, dashboard
|
||||
}}
|
||||
>
|
||||
{/* Variables */}
|
||||
{variables.map((variable, index) => (
|
||||
{sortDefaultVarsFirst(variables).map((variable, index) => (
|
||||
<div key={variable.state.key}>
|
||||
<VariableValueSelectWrapper variable={variable} inMenu />
|
||||
</div>
|
||||
@@ -54,7 +55,7 @@ export function DashboardControlsMenu({ variables, links, annotations, dashboard
|
||||
{links.length > 0 && dashboardUID && (
|
||||
<>
|
||||
{(variables.length > 0 || annotations.length > 0) && <MenuDivider />}
|
||||
{links.map((link, index) => (
|
||||
{sortDefaultLinksFirst(links).map((link, index) => (
|
||||
<div key={`${link.title}-${index}`}>
|
||||
<DashboardLinkRenderer link={link} dashboardUID={dashboardUID} inMenu />
|
||||
</div>
|
||||
|
||||
@@ -714,6 +714,396 @@ exports[`transformSceneToSaveModel Given a scene with rows Should transform back
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`transformSceneToSaveModel Given a simple scene with custom settings Should not transform back links that are registered by a datasource 1`] = `
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana",
|
||||
},
|
||||
"enable": true,
|
||||
"hide": false,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard",
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "gdev-testdata",
|
||||
},
|
||||
"enable": true,
|
||||
"hide": false,
|
||||
"iconColor": "red",
|
||||
"name": "Enabled",
|
||||
"target": {
|
||||
"lines": 4,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations",
|
||||
},
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "gdev-testdata",
|
||||
},
|
||||
"enable": false,
|
||||
"hide": false,
|
||||
"iconColor": "yellow",
|
||||
"name": "Disabled",
|
||||
"target": {
|
||||
"lines": 5,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations",
|
||||
},
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "gdev-testdata",
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "dark-purple",
|
||||
"name": "Hidden",
|
||||
"target": {
|
||||
"lines": 6,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"description": "My custom description",
|
||||
"editable": false,
|
||||
"fiscalYearStartMonth": 1,
|
||||
"graphTooltip": 1,
|
||||
"id": 1351,
|
||||
"links": [
|
||||
{
|
||||
"asDropdown": false,
|
||||
"icon": "external link",
|
||||
"includeVars": false,
|
||||
"keepTime": false,
|
||||
"tags": [],
|
||||
"targetBlank": false,
|
||||
"title": "Link 1",
|
||||
"tooltip": "",
|
||||
"type": "dashboards",
|
||||
"url": "",
|
||||
},
|
||||
{
|
||||
"asDropdown": false,
|
||||
"icon": "external link",
|
||||
"includeVars": false,
|
||||
"keepTime": false,
|
||||
"source": {
|
||||
"sourceId": "prometheus",
|
||||
"sourceType": "datasource",
|
||||
"uid": "123456",
|
||||
},
|
||||
"tags": [],
|
||||
"targetBlank": false,
|
||||
"title": "Link 2",
|
||||
"tooltip": "",
|
||||
"type": "dashboards",
|
||||
"url": "",
|
||||
},
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"description": "This is a simple time series graph",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic",
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"lineWidth": 2,
|
||||
},
|
||||
},
|
||||
"overrides": [],
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
},
|
||||
"id": 28,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"alias": "series",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 1,
|
||||
},
|
||||
],
|
||||
"title": "Simple time series graph ",
|
||||
"type": "timeseries",
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
},
|
||||
"id": 5,
|
||||
"panels": [],
|
||||
"title": "Row title",
|
||||
"type": "row",
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [],
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 9,
|
||||
},
|
||||
"id": 29,
|
||||
"options": {},
|
||||
"targets": [
|
||||
{
|
||||
"alias": "series",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 1,
|
||||
},
|
||||
],
|
||||
"title": "panel inside row",
|
||||
"type": "timeseries",
|
||||
},
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [],
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 11,
|
||||
"x": 12,
|
||||
"y": 9,
|
||||
},
|
||||
"id": 25,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false,
|
||||
},
|
||||
"content": "content",
|
||||
"mode": "markdown",
|
||||
},
|
||||
"pluginVersion": "10.2.0-pre",
|
||||
"title": "Transparent text panel",
|
||||
"transparent": true,
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"preload": false,
|
||||
"refresh": "5m",
|
||||
"schemaVersion": 42,
|
||||
"tags": [
|
||||
"tag1",
|
||||
"tag2",
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"baseFilters": [],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "wc2AL7L7k",
|
||||
},
|
||||
"filters": [],
|
||||
"name": "Filters",
|
||||
"type": "adhoc",
|
||||
},
|
||||
{
|
||||
"auto": true,
|
||||
"auto_count": 30,
|
||||
"auto_min": "10s",
|
||||
"current": {
|
||||
"text": "1m",
|
||||
"value": "1m",
|
||||
},
|
||||
"name": "intervalVar",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "1m",
|
||||
"value": "1m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "10m",
|
||||
"value": "10m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "30m",
|
||||
"value": "30m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "1h",
|
||||
"value": "1h",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "6h",
|
||||
"value": "6h",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "12h",
|
||||
"value": "12h",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "1d",
|
||||
"value": "1d",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "7d",
|
||||
"value": "7d",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "14d",
|
||||
"value": "14d",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "30d",
|
||||
"value": "30d",
|
||||
},
|
||||
],
|
||||
"query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
|
||||
"refresh": 2,
|
||||
"type": "interval",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": [
|
||||
"a",
|
||||
],
|
||||
"value": [
|
||||
"a",
|
||||
],
|
||||
},
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "customVar",
|
||||
"options": [],
|
||||
"query": "a, b, c",
|
||||
"type": "custom",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "gdev-testdata",
|
||||
"value": "PD8C576611E62080A",
|
||||
},
|
||||
"includeAll": false,
|
||||
"name": "dsVar",
|
||||
"options": [],
|
||||
"query": "grafana-testdata-datasource",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"type": "datasource",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "A",
|
||||
"value": "A",
|
||||
},
|
||||
"definition": "*",
|
||||
"includeAll": false,
|
||||
"name": "query0",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "*",
|
||||
"refId": "StandardVariableQuery",
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"type": "query",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "test",
|
||||
"value": "test",
|
||||
},
|
||||
"hide": 2,
|
||||
"name": "constant",
|
||||
"query": "test",
|
||||
"skipUrlSync": true,
|
||||
"type": "constant",
|
||||
},
|
||||
],
|
||||
},
|
||||
"time": {
|
||||
"from": "now-5m",
|
||||
"to": "now",
|
||||
},
|
||||
"timepicker": {
|
||||
"hidden": true,
|
||||
"refresh_intervals": [
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
],
|
||||
},
|
||||
"timezone": "America/New_York",
|
||||
"title": "My custom title",
|
||||
"uid": "nP8rcffGkasd",
|
||||
"version": 2,
|
||||
"weekStart": "monday",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`transformSceneToSaveModel Given a simple scene with custom settings Should transform back to persisted model 1`] = `
|
||||
{
|
||||
"annotations": {
|
||||
@@ -1087,6 +1477,370 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`transformSceneToSaveModel Given a simple scene with variables Should not transform back variables registered by a datasource 1`] = `
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana",
|
||||
},
|
||||
"enable": true,
|
||||
"hide": false,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard",
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "gdev-testdata",
|
||||
},
|
||||
"enable": true,
|
||||
"hide": false,
|
||||
"iconColor": "red",
|
||||
"name": "Enabled",
|
||||
"target": {
|
||||
"lines": 4,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations",
|
||||
},
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "gdev-testdata",
|
||||
},
|
||||
"enable": false,
|
||||
"hide": false,
|
||||
"iconColor": "yellow",
|
||||
"name": "Disabled",
|
||||
"target": {
|
||||
"lines": 5,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations",
|
||||
},
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "gdev-testdata",
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "dark-purple",
|
||||
"name": "Hidden",
|
||||
"target": {
|
||||
"lines": 6,
|
||||
"refId": "Anno",
|
||||
"scenarioId": "annotations",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 1,
|
||||
"graphTooltip": 1,
|
||||
"id": 1351,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"description": "This is a simple time series graph",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic",
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"lineWidth": 2,
|
||||
},
|
||||
},
|
||||
"overrides": [],
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
},
|
||||
"id": 28,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none",
|
||||
},
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"alias": "series",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 1,
|
||||
},
|
||||
],
|
||||
"title": "Simple time series graph ",
|
||||
"type": "timeseries",
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
},
|
||||
"id": 5,
|
||||
"panels": [],
|
||||
"title": "Row title",
|
||||
"type": "row",
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [],
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 9,
|
||||
},
|
||||
"id": 29,
|
||||
"options": {},
|
||||
"targets": [
|
||||
{
|
||||
"alias": "series",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A",
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 1,
|
||||
},
|
||||
],
|
||||
"title": "panel inside row",
|
||||
"type": "timeseries",
|
||||
},
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [],
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 11,
|
||||
"x": 12,
|
||||
"y": 9,
|
||||
},
|
||||
"id": 25,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false,
|
||||
},
|
||||
"content": "content",
|
||||
"mode": "markdown",
|
||||
},
|
||||
"pluginVersion": "10.2.0-pre",
|
||||
"title": "Transparent text panel",
|
||||
"transparent": true,
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"preload": false,
|
||||
"refresh": "",
|
||||
"schemaVersion": 42,
|
||||
"tags": [
|
||||
"gdev",
|
||||
"graph-ng",
|
||||
"demo",
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"baseFilters": [],
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "wc2AL7L7k",
|
||||
},
|
||||
"filters": [],
|
||||
"name": "Filters",
|
||||
"type": "adhoc",
|
||||
},
|
||||
{
|
||||
"auto": true,
|
||||
"auto_count": 30,
|
||||
"auto_min": "10s",
|
||||
"current": {
|
||||
"text": "1m",
|
||||
"value": "1m",
|
||||
},
|
||||
"name": "intervalVar",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "1m",
|
||||
"value": "1m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "10m",
|
||||
"value": "10m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "30m",
|
||||
"value": "30m",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "1h",
|
||||
"value": "1h",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "6h",
|
||||
"value": "6h",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "12h",
|
||||
"value": "12h",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "1d",
|
||||
"value": "1d",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "7d",
|
||||
"value": "7d",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "14d",
|
||||
"value": "14d",
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "30d",
|
||||
"value": "30d",
|
||||
},
|
||||
],
|
||||
"query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
|
||||
"refresh": 2,
|
||||
"type": "interval",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": [
|
||||
"a",
|
||||
],
|
||||
"value": [
|
||||
"a",
|
||||
],
|
||||
},
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "customVar",
|
||||
"options": [],
|
||||
"query": "a, b, c",
|
||||
"type": "custom",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "gdev-testdata",
|
||||
"value": "PD8C576611E62080A",
|
||||
},
|
||||
"includeAll": false,
|
||||
"name": "dsVar",
|
||||
"options": [],
|
||||
"query": "grafana-testdata-datasource",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"type": "datasource",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "A",
|
||||
"value": "A",
|
||||
},
|
||||
"definition": "*",
|
||||
"includeAll": false,
|
||||
"name": "query0",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "*",
|
||||
"refId": "StandardVariableQuery",
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"type": "query",
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "test",
|
||||
"value": "test",
|
||||
},
|
||||
"hide": 2,
|
||||
"name": "constant",
|
||||
"query": "test",
|
||||
"skipUrlSync": true,
|
||||
"type": "constant",
|
||||
},
|
||||
],
|
||||
},
|
||||
"time": {
|
||||
"from": "now-5m",
|
||||
"to": "now",
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d",
|
||||
],
|
||||
},
|
||||
"timezone": "America/New_York",
|
||||
"title": "Dashboard to load1",
|
||||
"uid": "nP8rcffGkasd",
|
||||
"version": 2,
|
||||
"weekStart": "saturday",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`transformSceneToSaveModel Given a simple scene with variables Should transform back to persisted model 1`] = `
|
||||
{
|
||||
"annotations": {
|
||||
|
||||
@@ -55,6 +55,12 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
const variables: VariableModel[] = [];
|
||||
|
||||
for (const variable of set.state.variables) {
|
||||
// Skipping default variables
|
||||
// (Default variables don't get persisted to the JSON schema.)
|
||||
if (variable.state.source !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const commonProperties = {
|
||||
name: variable.state.name,
|
||||
label: variable.state.label,
|
||||
@@ -312,6 +318,12 @@ export function sceneVariablesSetToSchemaV2Variables(
|
||||
> = [];
|
||||
|
||||
for (const variable of set.state.variables) {
|
||||
// Skipping default variables
|
||||
// (Default variables don't get persisted to the JSON schema.)
|
||||
if (variable.state.source !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const commonProperties = {
|
||||
name: variable.state.name,
|
||||
label: variable.state.label,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
import { TypedVariableModel } from '@grafana/data';
|
||||
import { config, getDataSourceSrv } from '@grafana/runtime';
|
||||
import {
|
||||
AdHocFiltersVariable,
|
||||
@@ -65,6 +66,7 @@ import { DashboardMeta } from 'app/types/dashboard';
|
||||
|
||||
import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior';
|
||||
import { dashboardAnalyticsInitializer } from '../behaviors/DashboardAnalyticsInitializerBehavior';
|
||||
import { LoadDashboardOptions } from '../pages/DashboardScenePageStateManager';
|
||||
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||
import { DashboardControls } from '../scene/DashboardControls';
|
||||
@@ -74,6 +76,7 @@ import { DashboardReloadBehavior } from '../scene/DashboardReloadBehavior';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { DashboardLayoutManager } from '../scene/types/DashboardLayoutManager';
|
||||
import { getIntervalsFromQueryString } from '../utils/utils';
|
||||
import { createSceneVariableFromVariableModel as createSceneVariableFromVariableModelV1 } from '../utils/variables';
|
||||
|
||||
import { transformV2ToV1AnnotationQuery } from './annotations';
|
||||
import { SnapshotVariable } from './custom-variables/SnapshotVariable';
|
||||
@@ -101,7 +104,10 @@ export type TypedVariableModelV2 =
|
||||
| AdhocVariableKind
|
||||
| SwitchVariableKind;
|
||||
|
||||
export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<DashboardV2Spec>): DashboardScene {
|
||||
export function transformSaveModelSchemaV2ToScene(
|
||||
dto: DashboardWithAccessInfo<DashboardV2Spec>,
|
||||
options?: LoadDashboardOptions
|
||||
): DashboardScene {
|
||||
const { spec: dashboard, metadata, apiVersion } = dto;
|
||||
|
||||
const found = dashboard.annotations.some((item) => item.spec.builtIn);
|
||||
@@ -179,8 +185,6 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
.get(dashboard.layout.kind)
|
||||
.deserialize(dashboard.layout, dashboard.elements, dashboard.preload);
|
||||
|
||||
//createLayoutManager(dashboard);
|
||||
|
||||
// Create profiler once and reuse to avoid duplicate metadata setting
|
||||
const dashboardProfiler = getDashboardSceneProfilerWithMetadata(metadata.name, dashboard.title);
|
||||
|
||||
@@ -208,7 +212,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
preload: dashboard.preload,
|
||||
id: dashboardId,
|
||||
isDirty: false,
|
||||
links: dashboard.links,
|
||||
links: [...dashboard.links, ...(options?.defaultLinks ?? [])],
|
||||
meta,
|
||||
tags: dashboard.tags,
|
||||
title: dashboard.title,
|
||||
@@ -224,7 +228,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
weekStart: dashboard.timeSettings.weekStart,
|
||||
UNSAFE_nowDelay: dashboard.timeSettings.nowDelay,
|
||||
}),
|
||||
$variables: getVariables(dashboard, meta.isSnapshot ?? false),
|
||||
$variables: getVariables(dashboard, meta.isSnapshot ?? false, options?.defaultVariables),
|
||||
$behaviors: [
|
||||
new behaviors.CursorSync({
|
||||
sync: transformCursorSyncV2ToV1(dashboard.cursorSync),
|
||||
@@ -269,19 +273,24 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
|
||||
return dashboardScene;
|
||||
}
|
||||
|
||||
function getVariables(dashboard: DashboardV2Spec, isSnapshot: boolean): SceneVariableSet | undefined {
|
||||
function getVariables(
|
||||
dashboard: DashboardV2Spec,
|
||||
isSnapshot: boolean,
|
||||
defaultVariables?: TypedVariableModel[]
|
||||
): SceneVariableSet | undefined {
|
||||
let variables: SceneVariableSet | undefined;
|
||||
|
||||
if (isSnapshot) {
|
||||
variables = createVariablesForSnapshot(dashboard);
|
||||
} else {
|
||||
variables = createVariablesForDashboard(dashboard);
|
||||
variables = createVariablesForDashboard(dashboard, defaultVariables);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
function createVariablesForDashboard(dashboard: DashboardV2Spec) {
|
||||
function createVariablesForDashboard(dashboard: DashboardV2Spec, defaultVariables: TypedVariableModel[] = []) {
|
||||
const isDefined = (v: SceneVariable | null): v is SceneVariable => Boolean(v);
|
||||
const variableObjects = dashboard.variables
|
||||
.map((v) => {
|
||||
try {
|
||||
@@ -293,7 +302,21 @@ function createVariablesForDashboard(dashboard: DashboardV2Spec) {
|
||||
})
|
||||
// TODO: Remove filter
|
||||
// Added temporarily to allow skipping non-compatible variables
|
||||
.filter((v): v is SceneVariable => Boolean(v));
|
||||
.filter(isDefined);
|
||||
|
||||
// Default variables are defined using the `TypedVariableModel` type, so we are still using the V1 transformer to create a scene variable from them.
|
||||
const defaultVariableObjects = defaultVariables
|
||||
? defaultVariables
|
||||
.map((v) => {
|
||||
try {
|
||||
return createSceneVariableFromVariableModelV1(v);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(isDefined)
|
||||
: [];
|
||||
|
||||
// Explicitly disable scopes for public dashboards
|
||||
if (config.featureToggles.scopeFilters && !config.publicDashboardAccessToken) {
|
||||
@@ -301,7 +324,7 @@ function createVariablesForDashboard(dashboard: DashboardV2Spec) {
|
||||
}
|
||||
|
||||
return new SceneVariableSet({
|
||||
variables: variableObjects,
|
||||
variables: [...variableObjects, ...defaultVariableObjects],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ export function createDashboardSceneFromDashboardModel(
|
||||
if (oldModel.meta.isSnapshot) {
|
||||
variables = createVariablesForSnapshot(oldModel);
|
||||
} else {
|
||||
variables = createVariablesForDashboard(oldModel);
|
||||
variables = createVariablesForDashboard(oldModel, options?.defaultVariables);
|
||||
}
|
||||
|
||||
if (oldModel.annotations?.list?.length && !oldModel.isSnapshot()) {
|
||||
@@ -373,7 +373,7 @@ export function createDashboardSceneFromDashboardModel(
|
||||
preload: dto.preload ?? false,
|
||||
id: oldModel.id,
|
||||
isDirty: false,
|
||||
links: oldModel.links || [],
|
||||
links: [...(oldModel.links ?? []), ...(options?.defaultLinks ?? [])],
|
||||
meta: oldModel.meta,
|
||||
tags: oldModel.tags || [],
|
||||
title: oldModel.title,
|
||||
|
||||
@@ -167,29 +167,52 @@ jest.mock('@grafana/scenes', () => ({
|
||||
|
||||
describe('transformSceneToSaveModel', () => {
|
||||
describe('Given a simple scene with custom settings', () => {
|
||||
const dashboardWithCustomSettings = {
|
||||
...dashboard_to_load1,
|
||||
title: 'My custom title',
|
||||
description: 'My custom description',
|
||||
tags: ['tag1', 'tag2'],
|
||||
timezone: 'America/New_York',
|
||||
weekStart: 'monday',
|
||||
graphTooltip: 1,
|
||||
editable: false,
|
||||
refresh: '5m',
|
||||
timepicker: {
|
||||
...dashboard_to_load1.timepicker,
|
||||
refresh_intervals: ['5m', '15m', '30m', '1h'],
|
||||
hidden: true,
|
||||
},
|
||||
links: [{ ...NEW_LINK, title: 'Link 1' }],
|
||||
};
|
||||
|
||||
it('Should transform back to persisted model', () => {
|
||||
const dashboardWithCustomSettings = {
|
||||
...dashboard_to_load1,
|
||||
title: 'My custom title',
|
||||
description: 'My custom description',
|
||||
tags: ['tag1', 'tag2'],
|
||||
timezone: 'America/New_York',
|
||||
weekStart: 'monday',
|
||||
graphTooltip: 1,
|
||||
editable: false,
|
||||
refresh: '5m',
|
||||
timepicker: {
|
||||
...dashboard_to_load1.timepicker,
|
||||
refresh_intervals: ['5m', '15m', '30m', '1h'],
|
||||
hidden: true,
|
||||
},
|
||||
links: [{ ...NEW_LINK, title: 'Link 1' }],
|
||||
};
|
||||
const scene = transformSaveModelToScene({ dashboard: dashboardWithCustomSettings as DashboardDataDTO, meta: {} });
|
||||
const saveModel = transformSceneToSaveModel(scene);
|
||||
|
||||
expect(saveModel).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Should not transform back links that are registered by a datasource', () => {
|
||||
const scene = transformSaveModelToScene({
|
||||
dashboard: {
|
||||
...dashboardWithCustomSettings,
|
||||
links: [
|
||||
{ ...NEW_LINK, title: 'Link 1' },
|
||||
|
||||
// This link should not be part of the JSON model, as it was registered by (and managed by) a datasource plugin
|
||||
{
|
||||
...NEW_LINK,
|
||||
title: 'Link 2',
|
||||
source: { uid: '123456', sourceId: 'prometheus', sourceType: 'datasource' },
|
||||
},
|
||||
],
|
||||
} as DashboardDataDTO,
|
||||
meta: {},
|
||||
});
|
||||
const saveModel = transformSceneToSaveModel(scene);
|
||||
|
||||
expect(saveModel).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Given a simple scene with variables', () => {
|
||||
@@ -197,6 +220,65 @@ describe('transformSceneToSaveModel', () => {
|
||||
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as DashboardDataDTO, meta: {} });
|
||||
const saveModel = transformSceneToSaveModel(scene);
|
||||
|
||||
expect(saveModel).toMatchSnapshot();
|
||||
});
|
||||
it('Should not transform back variables registered by a datasource', () => {
|
||||
const scene = transformSaveModelToScene({
|
||||
dashboard: {
|
||||
...dashboard_to_load1,
|
||||
templating: {
|
||||
list: [
|
||||
...dashboard_to_load1.templating.list,
|
||||
{
|
||||
current: {
|
||||
selected: true,
|
||||
text: ['a'],
|
||||
value: ['a'],
|
||||
},
|
||||
hide: 0,
|
||||
includeAll: true,
|
||||
multi: true,
|
||||
name: 'customVar',
|
||||
options: [
|
||||
{
|
||||
selected: false,
|
||||
text: 'All',
|
||||
value: '$__all',
|
||||
},
|
||||
{
|
||||
selected: true,
|
||||
text: 'a',
|
||||
value: 'a',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
text: 'b',
|
||||
value: 'b',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
text: 'c',
|
||||
value: 'c',
|
||||
},
|
||||
],
|
||||
query: 'a, b, c',
|
||||
skipUrlSync: false,
|
||||
type: 'custom',
|
||||
|
||||
// This marks that the variable was registered by a datasource
|
||||
source: {
|
||||
uid: '123456',
|
||||
sourceId: 'prometheus',
|
||||
sourceType: 'datasource',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as DashboardDataDTO,
|
||||
meta: {},
|
||||
});
|
||||
const saveModel = transformSceneToSaveModel(scene);
|
||||
|
||||
expect(saveModel).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -204,6 +204,24 @@ describe('transformSceneToSaveModelSchemaV2', () => {
|
||||
tooltip: '',
|
||||
type: 'link',
|
||||
},
|
||||
// This link is was added by a datasource, we wouldn't like it to end up in the JSON schema
|
||||
{
|
||||
title: 'Default link',
|
||||
url: 'http://test.com',
|
||||
asDropdown: false,
|
||||
icon: '',
|
||||
includeVars: false,
|
||||
keepTime: false,
|
||||
tags: [],
|
||||
targetBlank: false,
|
||||
tooltip: '',
|
||||
type: 'link',
|
||||
source: {
|
||||
uid: '123456',
|
||||
sourceId: 'prometheus',
|
||||
sourceType: 'datasource',
|
||||
},
|
||||
},
|
||||
],
|
||||
body: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
|
||||
@@ -91,19 +91,23 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps
|
||||
liveNow: getLiveNow(sceneDash),
|
||||
preload: sceneDash.preload ?? defaultDashboardV2Spec().preload,
|
||||
editable: sceneDash.editable ?? defaultDashboardV2Spec().editable,
|
||||
links: (sceneDash.links || []).map((link) => ({
|
||||
title: link.title ?? defaultDashboardLink().title,
|
||||
url: link.url ?? defaultDashboardLink().url,
|
||||
type: link.type ?? defaultDashboardLinkType(),
|
||||
icon: link.icon ?? defaultDashboardLink().icon,
|
||||
tooltip: link.tooltip ?? defaultDashboardLink().tooltip,
|
||||
tags: link.tags ?? defaultDashboardLink().tags,
|
||||
asDropdown: link.asDropdown ?? defaultDashboardLink().asDropdown,
|
||||
keepTime: link.keepTime ?? defaultDashboardLink().keepTime,
|
||||
includeVars: link.includeVars ?? defaultDashboardLink().includeVars,
|
||||
targetBlank: link.targetBlank ?? defaultDashboardLink().targetBlank,
|
||||
...(link.placement !== undefined && { placement: link.placement }),
|
||||
})),
|
||||
links: (sceneDash.links || [])
|
||||
// Links with a `source` property didn't come from the persisted JSON schema, so we also skip them
|
||||
// from generating the JSON model from the scenes object.
|
||||
.filter((link) => link.source === undefined)
|
||||
.map((link) => ({
|
||||
title: link.title ?? defaultDashboardLink().title,
|
||||
url: link.url ?? defaultDashboardLink().url,
|
||||
type: link.type ?? defaultDashboardLinkType(),
|
||||
icon: link.icon ?? defaultDashboardLink().icon,
|
||||
tooltip: link.tooltip ?? defaultDashboardLink().tooltip,
|
||||
tags: link.tags ?? defaultDashboardLink().tags,
|
||||
asDropdown: link.asDropdown ?? defaultDashboardLink().asDropdown,
|
||||
keepTime: link.keepTime ?? defaultDashboardLink().keepTime,
|
||||
includeVars: link.includeVars ?? defaultDashboardLink().includeVars,
|
||||
targetBlank: link.targetBlank ?? defaultDashboardLink().targetBlank,
|
||||
...(link.placement !== undefined && { placement: link.placement }),
|
||||
})),
|
||||
tags: sceneDash.tags ?? defaultDashboardV2Spec().tags,
|
||||
// EOF dashboard settings
|
||||
|
||||
|
||||
1191
public/app/features/dashboard-scene/utils/dashboardControls.test.ts
Normal file
1191
public/app/features/dashboard-scene/utils/dashboardControls.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
171
public/app/features/dashboard-scene/utils/dashboardControls.ts
Normal file
171
public/app/features/dashboard-scene/utils/dashboardControls.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { TypedVariableModel } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { SceneVariable } from '@grafana/scenes';
|
||||
import { DashboardLink, DataSourceRef, VariableHide } from '@grafana/schema';
|
||||
import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { DashboardDTO } from 'app/types/dashboard';
|
||||
|
||||
import { getRuntimePanelDataSource } from '../serialization/layoutSerializers/utils';
|
||||
|
||||
export const loadDatasources = (refs: DataSourceRef[]) => {
|
||||
return Promise.all(refs.map((ref) => getDataSourceSrv().get(ref)));
|
||||
};
|
||||
|
||||
// Deduplicates datasource refs by type, keeping only one ref per datasource plugin type
|
||||
export const deduplicateDatasourceRefsByType = (refs: Array<DataSourceRef | null | undefined>): DataSourceRef[] => {
|
||||
const dsByType: Record<string, DataSourceRef> = {};
|
||||
|
||||
for (const ref of refs) {
|
||||
if (ref && ref.type && !dsByType[ref.type]) {
|
||||
dsByType[ref.type] = ref;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.values(dsByType);
|
||||
};
|
||||
|
||||
export const loadDefaultControlsFromDatasources = async (refs: DataSourceRef[]) => {
|
||||
const datasources = await loadDatasources(refs);
|
||||
const defaultVariables: TypedVariableModel[] = [];
|
||||
const defaultLinks: DashboardLink[] = [];
|
||||
|
||||
// Default variables
|
||||
for (const ds of datasources) {
|
||||
if (ds.getDefaultVariables) {
|
||||
const dsVariables = ds.getDefaultVariables();
|
||||
if (dsVariables && dsVariables.length) {
|
||||
defaultVariables.push(
|
||||
...dsVariables.map((v) => ({
|
||||
...v,
|
||||
// Putting under the dashbaord controls menu by default
|
||||
hide: VariableHide.inControlsMenu,
|
||||
source: {
|
||||
uid: ds.uid,
|
||||
sourceId: ds.type,
|
||||
sourceType: 'datasource',
|
||||
},
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Default links
|
||||
if (ds.getDefaultLinks) {
|
||||
const dsLinks = ds.getDefaultLinks();
|
||||
if (dsLinks && dsLinks.length) {
|
||||
defaultLinks.push(
|
||||
...dsLinks.map((l) => ({
|
||||
...l,
|
||||
isDefault: true,
|
||||
parentDatasourceRef: ds.getRef(),
|
||||
// Putting under the dashboard-controls menu by default
|
||||
placement: 'inControlsMenu' as const,
|
||||
source: {
|
||||
uid: ds.uid,
|
||||
sourceId: ds.type,
|
||||
sourceType: 'datasource',
|
||||
},
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { defaultVariables, defaultLinks };
|
||||
};
|
||||
|
||||
export const getDsRefsFromV1Dashboard = (rsp: DashboardDTO) => {
|
||||
const dashboardModel = new DashboardModel(rsp.dashboard, rsp.meta);
|
||||
|
||||
// Datasources from panels
|
||||
const datasourceRefs = dashboardModel.panels
|
||||
.filter((panel) => panel.type !== 'row')
|
||||
.map((panel): DataSourceRef | null | undefined =>
|
||||
panel.datasource
|
||||
? panel.datasource
|
||||
: panel.targets?.find((t) => t.datasource !== null && t.datasource !== undefined)?.datasource
|
||||
)
|
||||
.filter((ref) => ref !== null && ref !== undefined);
|
||||
|
||||
// Datasources from variables
|
||||
if (dashboardModel.templating?.list) {
|
||||
for (const variable of dashboardModel.templating.list) {
|
||||
if (variable.type === 'query' && variable.datasource) {
|
||||
datasourceRefs.push(variable.datasource);
|
||||
} else if (variable.type === 'datasource' && variable.query) {
|
||||
datasourceRefs.push({ type: variable.query });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deduplicateDatasourceRefsByType(datasourceRefs);
|
||||
};
|
||||
|
||||
export const getDsRefsFromV2Dashboard = (rsp: DashboardWithAccessInfo<DashboardV2Spec>) => {
|
||||
const datasourceRefs: Array<DataSourceRef | null | undefined> = [];
|
||||
|
||||
//Datasources from panels
|
||||
if (rsp.spec.elements) {
|
||||
for (const element of Object.values(rsp.spec.elements)) {
|
||||
if (element.kind === 'Panel') {
|
||||
const panel = element;
|
||||
if (panel.spec.data?.spec?.queries) {
|
||||
for (const query of panel.spec.data.spec.queries) {
|
||||
const queryDs = query.spec.query.datasource?.name
|
||||
? { uid: query.spec.query.datasource.name, type: query.spec.query.group }
|
||||
: getRuntimePanelDataSource(query.spec.query);
|
||||
|
||||
if (queryDs) {
|
||||
datasourceRefs.push(queryDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Datasources from variables
|
||||
if (rsp.spec.variables) {
|
||||
for (const variable of rsp.spec.variables) {
|
||||
if (variable.kind === 'QueryVariable') {
|
||||
const queryVar = variable;
|
||||
if (queryVar.spec.query?.datasource?.name) {
|
||||
datasourceRefs.push({
|
||||
uid: queryVar.spec.query.datasource.name,
|
||||
type: queryVar.spec.query.group,
|
||||
});
|
||||
} else if (queryVar.spec.query?.group) {
|
||||
datasourceRefs.push({ type: queryVar.spec.query.group });
|
||||
}
|
||||
} else if (variable.kind === 'DatasourceVariable') {
|
||||
if (variable.spec.pluginId) {
|
||||
datasourceRefs.push({ type: variable.spec.pluginId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deduplicateDatasourceRefsByType(datasourceRefs);
|
||||
};
|
||||
|
||||
const sortByProp = <T>(items: T[], propGetter: (item: T) => Object | undefined) => {
|
||||
return items.sort((a, b) => {
|
||||
const aProp = propGetter(a) ?? false;
|
||||
const bProp = propGetter(b) ?? false;
|
||||
|
||||
if (aProp && !bProp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!aProp && bProp) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
export const sortDefaultVarsFirst = (items: SceneVariable[]) => sortByProp(items, (item) => item.state.source);
|
||||
export const sortDefaultLinksFirst = (items: DashboardLink[]) => sortByProp(items, (item) => item.source);
|
||||
@@ -22,8 +22,8 @@ import { getCurrentValueForOldIntervalModel, getIntervalsFromQueryString } from
|
||||
|
||||
const DEFAULT_DATASOURCE = 'default';
|
||||
|
||||
export function createVariablesForDashboard(oldModel: DashboardModel) {
|
||||
const variableObjects = oldModel.templating.list
|
||||
export function createVariablesForDashboard(oldModel: DashboardModel, defaultVariables: TypedVariableModel[] = []) {
|
||||
const variableObjects = [...oldModel.templating.list, ...defaultVariables]
|
||||
.map((v) => {
|
||||
try {
|
||||
return createSceneVariableFromVariableModel(v);
|
||||
@@ -42,7 +42,7 @@ export function createVariablesForDashboard(oldModel: DashboardModel) {
|
||||
}
|
||||
|
||||
return new SceneVariableSet({
|
||||
variables: variableObjects,
|
||||
variables: [...variableObjects],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
|
||||
name: variable.name,
|
||||
label: variable.label,
|
||||
description: variable.description,
|
||||
source: variable.source,
|
||||
};
|
||||
if (variable.type === 'adhoc') {
|
||||
const originFilters: AdHocVariableFilter[] = [];
|
||||
|
||||
@@ -21,7 +21,7 @@ afterEach(() => {
|
||||
|
||||
type DeepPartial<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
[P in keyof T]?: Partial<T[P]>;
|
||||
}
|
||||
: T;
|
||||
|
||||
|
||||
22
yarn.lock
22
yarn.lock
@@ -3604,11 +3604,11 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes-react@npm:6.52.0":
|
||||
version: 6.52.0
|
||||
resolution: "@grafana/scenes-react@npm:6.52.0"
|
||||
"@grafana/scenes-react@npm:6.53.0--canary.1315.20365173522.0":
|
||||
version: 6.53.0--canary.1315.20365173522.0
|
||||
resolution: "@grafana/scenes-react@npm:6.53.0--canary.1315.20365173522.0"
|
||||
dependencies:
|
||||
"@grafana/scenes": "npm:6.52.0"
|
||||
"@grafana/scenes": "npm:6.53.0--canary.1315.20365173522.0"
|
||||
lru-cache: "npm:^10.2.2"
|
||||
react-use: "npm:^17.4.0"
|
||||
peerDependencies:
|
||||
@@ -3620,7 +3620,7 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/7f121bcc4fd50f525c7c3457666ad3a32b04783d322d6715aedb6119538f911d0ec265c9c5b49a80478c1deb99286d36d003399a8831f76b6c483f4458b4ce8b
|
||||
checksum: 10/10ef8b3d22c96bb9acd383f4f1fac7eb677ab5f1c517d5320a286013b77361acaac783c3216b0cfe73c514591a6c5a29e2b785e55aa19a02ee2259580dcec3ec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3650,9 +3650,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/scenes@npm:6.52.0":
|
||||
version: 6.52.0
|
||||
resolution: "@grafana/scenes@npm:6.52.0"
|
||||
"@grafana/scenes@npm:6.53.0--canary.1315.20365173522.0":
|
||||
version: 6.53.0--canary.1315.20365173522.0
|
||||
resolution: "@grafana/scenes@npm:6.53.0--canary.1315.20365173522.0"
|
||||
dependencies:
|
||||
"@floating-ui/react": "npm:^0.26.16"
|
||||
"@leeoniya/ufuzzy": "npm:^1.0.16"
|
||||
@@ -3672,7 +3672,7 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/e52e0fb83396776c6cb79f8ac6a8aad0799eb2ccce9d0139f5734a49c3add7a1e3b97f14e0142c95b2bceee3ed8fa97b675b9b94c02382ecd683f470d06ef145
|
||||
checksum: 10/5f651027143a5fd1e9875ecf4b596aac4f43ea0553dd656c04a60698b70cc7c74eed43c6d405eab2e3f9f8fa2854e8eced1f2f0cf0ddf5f69d706fa4321dad72
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -19508,8 +19508,8 @@ __metadata:
|
||||
"@grafana/plugin-ui": "npm:^0.11.1"
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/scenes": "npm:6.52.0"
|
||||
"@grafana/scenes-react": "npm:6.52.0"
|
||||
"@grafana/scenes": "npm:6.53.0--canary.1315.20365173522.0"
|
||||
"@grafana/scenes-react": "npm:6.53.0--canary.1315.20365173522.0"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/test-utils": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user