Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs

This commit is contained in:
Ryan McKinley
2025-07-10 11:59:23 -07:00
55 changed files with 996 additions and 2383 deletions
+4 -1
View File
@@ -19,6 +19,9 @@ update-app-sdk: ## Update the Grafana App SDK dependency in go.mod
go mod tidy go mod tidy
.PHONY: generate .PHONY: generate
generate: install-app-sdk update-app-sdk generate: do-generate ## Run Grafana App SDK code generation
.PHONY: do-generate
do-generate: install-app-sdk update-app-sdk
## --defencoding=none and noschemasinmanifest are needed to avoid infinite loop while generating recursive models (see routingtree.cue) ## --defencoding=none and noschemasinmanifest are needed to avoid infinite loop while generating recursive models (see routingtree.cue)
@$(APP_SDK_BIN) generate --grouping=group --gogenpath=./pkg/apis --defencoding=none --postprocess --noschemasinmanifest @$(APP_SDK_BIN) generate --grouping=group --gogenpath=./pkg/apis --defencoding=none --postprocess --noschemasinmanifest
+1 -1
View File
@@ -1,4 +1,4 @@
APP_SDK_VERSION := v0.39.2 APP_SDK_VERSION := v0.39.0
APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) APP_SDK_DIR := $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION)
APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk APP_SDK_BIN := $(APP_SDK_DIR)/grafana-app-sdk
@@ -111,8 +111,6 @@ DashboardLink: {
keepTime: bool | *false keepTime: bool | *false
} }
// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec
// This type is widely used in the codebase and changing it will have a big impact
DataSourceRef: { DataSourceRef: {
// The plugin type-id // The plugin type-id
type?: string type?: string
@@ -387,14 +385,15 @@ VizConfigKind: {
} }
AnnotationQuerySpec: { AnnotationQuerySpec: {
query: DataQueryKind datasource?: DataSourceRef
query?: DataQueryKind
enable: bool enable: bool
hide: bool hide: bool
iconColor: string iconColor: string
name: string name: string
builtIn?: bool | *false builtIn?: bool | *false
filter?: AnnotationPanelFilter filter?: AnnotationPanelFilter
legacyOptions?: [string]: _ // Catch-all field for datasource-specific properties. Should not be available in as code tooling. legacyOptions?: [string]: _ //Catch-all field for datasource-specific properties
} }
AnnotationQueryKind: { AnnotationQueryKind: {
@@ -413,19 +412,15 @@ QueryOptionsSpec: {
} }
DataQueryKind: { DataQueryKind: {
kind: "DataQuery" // The kind of a DataQueryKind is the datasource type
group: string kind: string
version: string | *"v0"
// New type for datasource reference
// Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.
datasource?: {
name?: string
}
spec: [string]: _ spec: [string]: _
} }
PanelQuerySpec: { PanelQuerySpec: {
query: DataQueryKind query: DataQueryKind
datasource?: DataSourceRef
refId: string refId: string
hidden: bool hidden: bool
} }
@@ -728,6 +723,7 @@ QueryVariableSpec: {
refresh: VariableRefresh refresh: VariableRefresh
skipUrlSync: bool | *false skipUrlSync: bool | *false
description?: string description?: string
datasource?: DataSourceRef
query: DataQueryKind query: DataQueryKind
regex: string | *"" regex: string | *""
sort: VariableSort sort: VariableSort
@@ -115,8 +115,6 @@ DashboardLink: {
keepTime: bool | *false keepTime: bool | *false
} }
// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec
// This type is widely used in the codebase and changing it will have a big impact
DataSourceRef: { DataSourceRef: {
// The plugin type-id // The plugin type-id
type?: string type?: string
@@ -391,14 +389,15 @@ VizConfigKind: {
} }
AnnotationQuerySpec: { AnnotationQuerySpec: {
query: DataQueryKind datasource?: DataSourceRef
query?: DataQueryKind
enable: bool enable: bool
hide: bool hide: bool
iconColor: string iconColor: string
name: string name: string
builtIn?: bool | *false builtIn?: bool | *false
filter?: AnnotationPanelFilter filter?: AnnotationPanelFilter
legacyOptions?: [string]: _ // Catch-all field for datasource-specific properties. Should not be available in as code tooling. legacyOptions?: [string]: _ //Catch-all field for datasource-specific properties
} }
AnnotationQueryKind: { AnnotationQueryKind: {
@@ -417,19 +416,15 @@ QueryOptionsSpec: {
} }
DataQueryKind: { DataQueryKind: {
kind: "DataQuery" // The kind of a DataQueryKind is the datasource type
group: string kind: string
version: string | *"v0"
// New type for datasource reference
// Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.
datasource?: {
name?: string
}
spec: [string]: _ spec: [string]: _
} }
PanelQuerySpec: { PanelQuerySpec: {
query: DataQueryKind query: DataQueryKind
datasource?: DataSourceRef
refId: string refId: string
hidden: bool hidden: bool
} }
@@ -732,6 +727,7 @@ QueryVariableSpec: {
refresh: VariableRefresh refresh: VariableRefresh
skipUrlSync: bool | *false skipUrlSync: bool | *false
description?: string description?: string
datasource?: DataSourceRef
query: DataQueryKind query: DataQueryKind
regex: string | *"" regex: string | *""
sort: VariableSort sort: VariableSort
@@ -23,42 +23,49 @@ func NewDashboardAnnotationQueryKind() *DashboardAnnotationQueryKind {
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
type DashboardAnnotationQuerySpec struct { type DashboardAnnotationQuerySpec struct {
Query DashboardDataQueryKind `json:"query"` Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
Enable bool `json:"enable"` Query *DashboardDataQueryKind `json:"query,omitempty"`
Hide bool `json:"hide"` Enable bool `json:"enable"`
IconColor string `json:"iconColor"` Hide bool `json:"hide"`
Name string `json:"name"` IconColor string `json:"iconColor"`
BuiltIn *bool `json:"builtIn,omitempty"` Name string `json:"name"`
Filter *DashboardAnnotationPanelFilter `json:"filter,omitempty"` BuiltIn *bool `json:"builtIn,omitempty"`
// Catch-all field for datasource-specific properties. Should not be available in as code tooling. Filter *DashboardAnnotationPanelFilter `json:"filter,omitempty"`
// Catch-all field for datasource-specific properties
LegacyOptions map[string]interface{} `json:"legacyOptions,omitempty"` LegacyOptions map[string]interface{} `json:"legacyOptions,omitempty"`
} }
// NewDashboardAnnotationQuerySpec creates a new DashboardAnnotationQuerySpec object. // NewDashboardAnnotationQuerySpec creates a new DashboardAnnotationQuerySpec object.
func NewDashboardAnnotationQuerySpec() *DashboardAnnotationQuerySpec { func NewDashboardAnnotationQuerySpec() *DashboardAnnotationQuerySpec {
return &DashboardAnnotationQuerySpec{ return &DashboardAnnotationQuerySpec{
Query: *NewDashboardDataQueryKind(),
BuiltIn: (func(input bool) *bool { return &input })(false), BuiltIn: (func(input bool) *bool { return &input })(false),
} }
} }
// +k8s:openapi-gen=true
type DashboardDataSourceRef struct {
// The plugin type-id
Type *string `json:"type,omitempty"`
// Specific datasource instance
Uid *string `json:"uid,omitempty"`
}
// NewDashboardDataSourceRef creates a new DashboardDataSourceRef object.
func NewDashboardDataSourceRef() *DashboardDataSourceRef {
return &DashboardDataSourceRef{}
}
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
type DashboardDataQueryKind struct { type DashboardDataQueryKind struct {
Kind string `json:"kind"` // The kind of a DataQueryKind is the datasource type
Group string `json:"group"` Kind string `json:"kind"`
Version string `json:"version"` Spec map[string]interface{} `json:"spec"`
// New type for datasource reference
// Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.
Datasource *DashboardV2alpha1DataQueryKindDatasource `json:"datasource,omitempty"`
Spec map[string]interface{} `json:"spec"`
} }
// NewDashboardDataQueryKind creates a new DashboardDataQueryKind object. // NewDashboardDataQueryKind creates a new DashboardDataQueryKind object.
func NewDashboardDataQueryKind() *DashboardDataQueryKind { func NewDashboardDataQueryKind() *DashboardDataQueryKind {
return &DashboardDataQueryKind{ return &DashboardDataQueryKind{
Kind: "DataQuery", Spec: map[string]interface{}{},
Version: "v0",
Spec: map[string]interface{}{},
} }
} }
@@ -192,9 +199,10 @@ func NewDashboardPanelQueryKind() *DashboardPanelQueryKind {
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
type DashboardPanelQuerySpec struct { type DashboardPanelQuerySpec struct {
Query DashboardDataQueryKind `json:"query"` Query DashboardDataQueryKind `json:"query"`
RefId string `json:"refId"` Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
Hidden bool `json:"hidden"` RefId string `json:"refId"`
Hidden bool `json:"hidden"`
} }
// NewDashboardPanelQuerySpec creates a new DashboardPanelQuerySpec object. // NewDashboardPanelQuerySpec creates a new DashboardPanelQuerySpec object.
@@ -755,9 +763,7 @@ type DashboardRepeatOptions struct {
// NewDashboardRepeatOptions creates a new DashboardRepeatOptions object. // NewDashboardRepeatOptions creates a new DashboardRepeatOptions object.
func NewDashboardRepeatOptions() *DashboardRepeatOptions { func NewDashboardRepeatOptions() *DashboardRepeatOptions {
return &DashboardRepeatOptions{ return &DashboardRepeatOptions{}
Mode: DashboardRepeatMode,
}
} }
// other repeat modes will be added in the future: label, frame // other repeat modes will be added in the future: label, frame
@@ -932,9 +938,7 @@ type DashboardRowRepeatOptions struct {
// NewDashboardRowRepeatOptions creates a new DashboardRowRepeatOptions object. // NewDashboardRowRepeatOptions creates a new DashboardRowRepeatOptions object.
func NewDashboardRowRepeatOptions() *DashboardRowRepeatOptions { func NewDashboardRowRepeatOptions() *DashboardRowRepeatOptions {
return &DashboardRowRepeatOptions{ return &DashboardRowRepeatOptions{}
Mode: DashboardRepeatMode,
}
} }
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
@@ -1007,9 +1011,7 @@ type DashboardAutoGridRepeatOptions struct {
// NewDashboardAutoGridRepeatOptions creates a new DashboardAutoGridRepeatOptions object. // NewDashboardAutoGridRepeatOptions creates a new DashboardAutoGridRepeatOptions object.
func NewDashboardAutoGridRepeatOptions() *DashboardAutoGridRepeatOptions { func NewDashboardAutoGridRepeatOptions() *DashboardAutoGridRepeatOptions {
return &DashboardAutoGridRepeatOptions{ return &DashboardAutoGridRepeatOptions{}
Mode: DashboardRepeatMode,
}
} }
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
@@ -1075,9 +1077,7 @@ type DashboardTabRepeatOptions struct {
// NewDashboardTabRepeatOptions creates a new DashboardTabRepeatOptions object. // NewDashboardTabRepeatOptions creates a new DashboardTabRepeatOptions object.
func NewDashboardTabRepeatOptions() *DashboardTabRepeatOptions { func NewDashboardTabRepeatOptions() *DashboardTabRepeatOptions {
return &DashboardTabRepeatOptions{ return &DashboardTabRepeatOptions{}
Mode: DashboardRepeatMode,
}
} }
// Links with references to other dashboards or external resources // Links with references to other dashboards or external resources
@@ -1221,6 +1221,7 @@ type DashboardQueryVariableSpec struct {
Refresh DashboardVariableRefresh `json:"refresh"` Refresh DashboardVariableRefresh `json:"refresh"`
SkipUrlSync bool `json:"skipUrlSync"` SkipUrlSync bool `json:"skipUrlSync"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
Query DashboardDataQueryKind `json:"query"` Query DashboardDataQueryKind `json:"query"`
Regex string `json:"regex"` Regex string `json:"regex"`
Sort DashboardVariableSort `json:"sort"` Sort DashboardVariableSort `json:"sort"`
@@ -1626,21 +1627,6 @@ func NewDashboardGroupByVariableSpec() *DashboardGroupByVariableSpec {
} }
} }
// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec
// This type is widely used in the codebase and changing it will have a big impact
// +k8s:openapi-gen=true
type DashboardDataSourceRef struct {
// The plugin type-id
Type *string `json:"type,omitempty"`
// Specific datasource instance
Uid *string `json:"uid,omitempty"`
}
// NewDashboardDataSourceRef creates a new DashboardDataSourceRef object.
func NewDashboardDataSourceRef() *DashboardDataSourceRef {
return &DashboardDataSourceRef{}
}
// Adhoc variable kind // Adhoc variable kind
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
type DashboardAdhocVariableKind struct { type DashboardAdhocVariableKind struct {
@@ -1701,9 +1687,7 @@ type DashboardAdHocFilterWithLabels struct {
// NewDashboardAdHocFilterWithLabels creates a new DashboardAdHocFilterWithLabels object. // NewDashboardAdHocFilterWithLabels creates a new DashboardAdHocFilterWithLabels object.
func NewDashboardAdHocFilterWithLabels() *DashboardAdHocFilterWithLabels { func NewDashboardAdHocFilterWithLabels() *DashboardAdHocFilterWithLabels {
return &DashboardAdHocFilterWithLabels{ return &DashboardAdHocFilterWithLabels{}
Origin: DashboardFilterOrigin,
}
} }
// Determine the origin of the adhoc variable filter // Determine the origin of the adhoc variable filter
@@ -1774,16 +1758,6 @@ func NewDashboardSpec() *DashboardSpec {
} }
} }
// +k8s:openapi-gen=true
type DashboardV2alpha1DataQueryKindDatasource struct {
Name *string `json:"name,omitempty"`
}
// NewDashboardV2alpha1DataQueryKindDatasource creates a new DashboardV2alpha1DataQueryKindDatasource object.
func NewDashboardV2alpha1DataQueryKindDatasource() *DashboardV2alpha1DataQueryKindDatasource {
return &DashboardV2alpha1DataQueryKindDatasource{}
}
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
type DashboardV2alpha1FieldConfigSourceOverrides struct { type DashboardV2alpha1FieldConfigSourceOverrides struct {
Matcher DashboardMatcherConfig `json:"matcher"` Matcher DashboardMatcherConfig `json:"matcher"`
@@ -109,7 +109,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeRangeOption": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeRangeOption(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeRangeOption": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeRangeOption(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeSettingsSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeSettingsSpec(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeSettingsSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeSettingsSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTransformationKind": schema_pkg_apis_dashboard_v2alpha1_DashboardTransformationKind(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTransformationKind": schema_pkg_apis_dashboard_v2alpha1_DashboardTransformationKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1DataQueryKindDatasource": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1DataQueryKindDatasource(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RangeMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RangeMapOptions(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RangeMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RangeMapOptions(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RegexMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RegexMapOptions(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RegexMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RegexMapOptions(ref),
@@ -596,10 +595,14 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationQuerySpec(ref common.
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"object"}, Type: []string{"object"},
Properties: map[string]spec.Schema{ Properties: map[string]spec.Schema{
"datasource": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"),
},
},
"query": { "query": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{}, Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"),
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"),
}, },
}, },
"enable": { "enable": {
@@ -643,7 +646,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationQuerySpec(ref common.
}, },
"legacyOptions": { "legacyOptions": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "Catch-all field for datasource-specific properties. Should not be available in as code tooling.", Description: "Catch-all field for datasource-specific properties",
Type: []string{"object"}, Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{ AdditionalProperties: &spec.SchemaOrBool{
Allows: true, Allows: true,
@@ -657,11 +660,11 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationQuerySpec(ref common.
}, },
}, },
}, },
Required: []string{"query", "enable", "hide", "iconColor", "name"}, Required: []string{"enable", "hide", "iconColor", "name"},
}, },
}, },
Dependencies: []string{ Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAnnotationPanelFilter", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"}, "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAnnotationPanelFilter", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"},
} }
} }
@@ -1502,29 +1505,10 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDataQueryKind(ref common.Refere
Properties: map[string]spec.Schema{ Properties: map[string]spec.Schema{
"kind": { "kind": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Default: "", Description: "The kind of a DataQueryKind is the datasource type",
Type: []string{"string"}, Default: "",
Format: "", Type: []string{"string"},
}, Format: "",
},
"group": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"version": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"datasource": {
SchemaProps: spec.SchemaProps{
Description: "New type for datasource reference Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.",
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1DataQueryKindDatasource"),
}, },
}, },
"spec": { "spec": {
@@ -1542,11 +1526,9 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDataQueryKind(ref common.Refere
}, },
}, },
}, },
Required: []string{"kind", "group", "version", "spec"}, Required: []string{"kind", "spec"},
}, },
}, },
Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1DataQueryKindDatasource"},
} }
} }
@@ -1554,8 +1536,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardDataSourceRef(ref common.Refere
return common.OpenAPIDefinition{ return common.OpenAPIDefinition{
Schema: spec.Schema{ Schema: spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Description: "Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec This type is widely used in the codebase and changing it will have a big impact", Type: []string{"object"},
Type: []string{"object"},
Properties: map[string]spec.Schema{ Properties: map[string]spec.Schema{
"type": { "type": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
@@ -2909,6 +2890,11 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardPanelQuerySpec(ref common.Refer
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"), Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"),
}, },
}, },
"datasource": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"),
},
},
"refId": { "refId": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Default: "", Default: "",
@@ -2928,7 +2914,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardPanelQuerySpec(ref common.Refer
}, },
}, },
Dependencies: []string{ Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind"}, "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"},
} }
} }
@@ -3264,6 +3250,11 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardQueryVariableSpec(ref common.Re
Format: "", Format: "",
}, },
}, },
"datasource": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef"),
},
},
"query": { "query": {
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{}, Default: map[string]interface{}{},
@@ -3341,7 +3332,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardQueryVariableSpec(ref common.Re
}, },
}, },
Dependencies: []string{ Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardVariableOption"}, "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataQueryKind", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDataSourceRef", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardVariableOption"},
} }
} }
@@ -4329,24 +4320,6 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardTransformationKind(ref common.R
} }
} }
func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1DataQueryKindDatasource(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref common.ReferenceCallback) common.OpenAPIDefinition { func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{ return common.OpenAPIDefinition{
Schema: spec.Schema{ Schema: spec.Schema{
+2
View File
@@ -14,6 +14,8 @@ import (
v0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" v0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
) )
var ()
var appManifestData = app.ManifestData{ var appManifestData = app.ManifestData{
AppName: "iam", AppName: "iam",
Group: "iam.grafana.app", Group: "iam.grafana.app",
@@ -1,257 +0,0 @@
{
"kind": "Dashboard",
"apiVersion": "dashboard.grafana.app/v1beta1",
"metadata": {
"name": "test-v1-annotations",
"annotations": {
"hello": "world"
},
"labels": {
"region": "west"
}
},
"spec": {
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": false,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
},
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"enable": true,
"hide": false,
"iconColor": "blue",
"name": "testdata-annos",
"target": {
"lines": 10,
"refId": "Anno",
"scenarioId": "annotations"
}
},
{
"enable": true,
"hide": false,
"iconColor": "blue",
"name": "no-ds-testdata-annos",
"target": {
"lines": 10,
"refId": "Anno",
"scenarioId": "annotations"
}
},
{
"datasource": {
"type": "prometheus",
"uid": "gdev-prometheus"
},
"enable": true,
"hide": false,
"iconColor": "yellow",
"name": "prom-annos",
"target": {
"expr": "{action=\"add_client\"}",
"interval": "",
"lines": 10,
"refId": "Anno",
"scenarioId": "annotations"
}
},
{
"enable": true,
"hide": false,
"iconColor": "yellow",
"name": "no-ds-prom-annos",
"target": {
"expr": "{action=\"add_client\"}",
"interval": "",
"lines": 10,
"refId": "Anno",
"scenarioId": "annotations"
}
},
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "PBBCEC2D313BC06C3"
},
"enable": true,
"hide": false,
"iconColor": "red",
"name": "postgress-annos",
"target": {
"editorMode": "builder",
"format": "table",
"lines": 10,
"rawSql": "",
"refId": "Anno",
"scenarioId": "annotations",
"sql": {
"columns": [
{
"parameters": [],
"type": "function"
}
],
"groupBy": [
{
"property": {
"type": "string"
},
"type": "groupBy"
}
],
"limit": 50
}
}
},
{
"datasource": {
"type": "elasticsearch",
"uid": "gdev-elasticsearch"
},
"enable": true,
"hide": false,
"iconColor": "red",
"name": "elastic - annos",
"tagsField": "asd",
"target": {
"lines": 10,
"query": "test query",
"refId": "Anno",
"scenarioId": "annotations"
},
"textField": "asd",
"timeEndField": "asdas",
"timeField": "asd"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "New panel",
"type": "timeseries"
}
],
"preload": false,
"schemaVersion": 41,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Test: V1 dashboard with annotations",
"version": 8
}
}
+18 -586
View File
@@ -10,44 +10,14 @@
"kind": "AnnotationQuery", "kind": "AnnotationQuery",
"spec": { "spec": {
"builtIn": true, "builtIn": true,
"enable": true, "datasource": {
"filter": { "type": "grafana",
"exclude": false, "uid": "-- Grafana --"
"ids": [1]
}, },
"enable": true,
"hide": true, "hide": true,
"iconColor": "rgba(0, 211, 255, 1)", "iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts", "name": "Annotations \u0026 Alerts"
"query": {
"group": "grafana",
"kind": "DataQuery",
"spec": {},
"version": "v0"
}
}
},
{
"kind": "AnnotationQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "grafana-testdata-datasource",
"version": "v0",
"spec": {
"lines": 10,
"refId": "Anno",
"scenarioId": "annotations"
}
},
"enable": true,
"hide": false,
"iconColor": "red",
"name": "Test data annotations",
"builtIn": false,
"filter": {
"exclude": false,
"ids": [1]
}
} }
} }
], ],
@@ -67,16 +37,8 @@
"spec": { "spec": {
"hidden": false, "hidden": false,
"query": { "query": {
"datasource": { "kind": "grafana-testdata-datasource",
"name": "gdev-testdata" "spec": {}
},
"group": "grafana-testdata-datasource",
"kind": "DataQuery",
"spec": {
"scenarioId": "random_walk",
"seriesCount": 3
},
"version": "v0"
}, },
"refId": "A" "refId": "A"
} }
@@ -89,7 +51,7 @@
"description": "", "description": "",
"id": 1, "id": 1,
"links": [], "links": [],
"title": "Simple timeseries (WITH DS REF)", "title": "Simle timeseries",
"vizConfig": { "vizConfig": {
"kind": "timeseries", "kind": "timeseries",
"spec": { "spec": {
@@ -131,12 +93,12 @@
"mode": "off" "mode": "off"
} }
}, },
"mappings": [],
"thresholds": { "thresholds": {
"mode": "absolute", "mode": "absolute",
"steps": [ "steps": [
{ {
"color": "green", "color": "green"
"value": 0
}, },
{ {
"color": "red", "color": "red",
@@ -160,7 +122,7 @@
"sort": "none" "sort": "none"
} }
}, },
"pluginVersion": "12.1.0-pre" "pluginVersion": "12.0.0-pre"
} }
} }
} }
@@ -177,13 +139,8 @@
"spec": { "spec": {
"hidden": false, "hidden": false,
"query": { "query": {
"group": "grafana-testdata-datasource", "kind": "grafana-testdata-datasource",
"kind": "DataQuery", "spec": {}
"spec": {
"scenarioId": "random_walk",
"seriesCount": 4
},
"version": "v0"
}, },
"refId": "A" "refId": "A"
} }
@@ -196,7 +153,7 @@
"description": "", "description": "",
"id": 2, "id": 2,
"links": [], "links": [],
"title": "Simple stat (NO DS REF)", "title": "Simple stat",
"vizConfig": { "vizConfig": {
"kind": "stat", "kind": "stat",
"spec": { "spec": {
@@ -205,12 +162,12 @@
"color": { "color": {
"mode": "thresholds" "mode": "thresholds"
}, },
"mappings": [],
"thresholds": { "thresholds": {
"mode": "absolute", "mode": "absolute",
"steps": [ "steps": [
{ {
"color": "green", "color": "green"
"value": 0
}, },
{ {
"color": "red", "color": "red",
@@ -236,496 +193,7 @@
"textMode": "auto", "textMode": "auto",
"wideLayout": true "wideLayout": true
}, },
"pluginVersion": "12.1.0-pre" "pluginVersion": "12.0.0-pre"
}
}
}
},
"panel-3": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"group": "prometheus",
"kind": "DataQuery",
"spec": {
"disableTextWrap": false,
"editorMode": "builder",
"expr": "rate(counters_requests[$__rate_interval])",
"fullMetaSearch": false,
"includeNullMetadata": false,
"instant": false,
"legendFormat": "__auto",
"range": true,
"useBackend": false
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 3,
"links": [],
"title": "Panel with NO REF to gdev-prometheus",
"vizConfig": {
"kind": "timeseries",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre"
}
}
}
},
"panel-4": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "gdev-prometheus"
},
"group": "prometheus",
"kind": "DataQuery",
"spec": {
"disableTextWrap": false,
"editorMode": "builder",
"expr": "rate(counters_requests[$__rate_interval])",
"fullMetaSearch": false,
"includeNullMetadata": false,
"instant": false,
"legendFormat": "__auto",
"range": true,
"useBackend": false
},
"version": "v0"
},
"refId": "A"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 4,
"links": [],
"title": "Panel with ref to gdev-prometheus",
"vizConfig": {
"kind": "timeseries",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre"
}
}
}
},
"panel-5": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "gdev-prometheus"
},
"group": "prometheus",
"kind": "DataQuery",
"spec": {
"disableTextWrap": false,
"editorMode": "builder",
"expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])",
"fullMetaSearch": false,
"includeNullMetadata": false,
"legendFormat": "__auto",
"range": true,
"useBackend": false
},
"version": "v0"
},
"refId": "A"
}
},
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"datasource": {
"name": "gdev-testdata"
},
"group": "grafana-testdata-datasource",
"kind": "DataQuery",
"spec": {},
"version": "v0"
},
"refId": "B"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 5,
"links": [],
"title": "Mixed DS WITH REFS",
"vizConfig": {
"kind": "timeseries",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre"
}
}
}
},
"panel-6": {
"kind": "Panel",
"spec": {
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"group": "prometheus",
"kind": "DataQuery",
"spec": {
"disableTextWrap": false,
"editorMode": "builder",
"expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])",
"fullMetaSearch": false,
"includeNullMetadata": false,
"legendFormat": "__auto",
"range": true,
"useBackend": false
},
"version": "v0"
},
"refId": "A"
}
},
{
"kind": "PanelQuery",
"spec": {
"hidden": false,
"query": {
"group": "grafana-testdata-datasource",
"kind": "DataQuery",
"spec": {},
"version": "v0"
},
"refId": "B"
}
}
],
"queryOptions": {},
"transformations": []
}
},
"description": "",
"id": 6,
"links": [],
"title": "Mixed DS WITHOUT REFS",
"vizConfig": {
"kind": "timeseries",
"spec": {
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.1.0-pre"
} }
} }
} }
@@ -736,24 +204,6 @@
"spec": { "spec": {
"columnWidthMode": "standard", "columnWidthMode": "standard",
"items": [ "items": [
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-3"
}
}
},
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-4"
}
}
},
{ {
"kind": "AutoGridLayoutItem", "kind": "AutoGridLayoutItem",
"spec": { "spec": {
@@ -771,24 +221,6 @@
"name": "panel-1" "name": "panel-1"
} }
} }
},
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-5"
}
}
},
{
"kind": "AutoGridLayoutItem",
"spec": {
"element": {
"kind": "ElementReference",
"name": "panel-6"
}
}
} }
], ],
"maxColumnCount": 3, "maxColumnCount": 3,
@@ -803,7 +235,7 @@
"autoRefresh": "", "autoRefresh": "",
"autoRefreshIntervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], "autoRefreshIntervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
"fiscalYearStartMonth": 0, "fiscalYearStartMonth": 0,
"from": "now-5m", "from": "now-6h",
"hideTimepicker": false, "hideTimepicker": false,
"timezone": "browser", "timezone": "browser",
"to": "now" "to": "now"
+41 -26
View File
@@ -2,11 +2,21 @@
"apiVersion": "dashboard.grafana.app/v2alpha1", "apiVersion": "dashboard.grafana.app/v2alpha1",
"kind": "Dashboard", "kind": "Dashboard",
"metadata": { "metadata": {
"name": "fa400625-2a44-4add-a369-e6c972eb4bd6", "name": "admjzp8",
"namespace": "default",
"uid": "hrbekBWXeM7nC7GtouIbcngFyuqt8Xx7KlE4AnTwV7AX",
"resourceVersion": "1",
"generation": 1, "generation": 1,
"creationTimestamp": "2025-05-27T11:40:22Z", "creationTimestamp": "2025-05-16T09:41:56Z",
"labels": {}, "labels": {
"annotations": {} "grafana.app/deprecatedInternalID": "182"
},
"annotations": {
"grafana.app/createdBy": "user:cejvsh18uudxcf",
"grafana.app/updatedBy": "user:cejvsh18uudxcf",
"grafana.app/updatedTimestamp": "2025-05-16T09:41:56Z",
"grafana.app/folder": ""
}
}, },
"spec": { "spec": {
"annotations": [ "annotations": [
@@ -14,20 +24,19 @@
"kind": "AnnotationQuery", "kind": "AnnotationQuery",
"spec": { "spec": {
"builtIn": true, "builtIn": true,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true, "enable": true,
"hide": true, "hide": true,
"iconColor": "rgba(0, 211, 255, 1)", "iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts", "name": "Annotations & Alerts"
"query": {
"group": "grafana",
"kind": "DataQuery",
"spec": {},
"version": "v0"
}
} }
} }
], ],
"cursorSync": "Off", "cursorSync": "Off",
"description": "",
"editable": true, "editable": true,
"elements": { "elements": {
"panel-1": { "panel-1": {
@@ -40,12 +49,14 @@
{ {
"kind": "PanelQuery", "kind": "PanelQuery",
"spec": { "spec": {
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"hidden": false, "hidden": false,
"query": { "query": {
"group": "grafana-testdata-datasource", "kind": "grafana-testdata-datasource",
"kind": "DataQuery", "spec": {}
"spec": {},
"version": "v0"
}, },
"refId": "A" "refId": "A"
} }
@@ -144,12 +155,14 @@
{ {
"kind": "PanelQuery", "kind": "PanelQuery",
"spec": { "spec": {
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"hidden": false, "hidden": false,
"query": { "query": {
"group": "grafana-testdata-datasource", "kind": "grafana-testdata-datasource",
"kind": "DataQuery", "spec": {}
"spec": {},
"version": "v0"
}, },
"refId": "A" "refId": "A"
} }
@@ -248,12 +261,14 @@
{ {
"kind": "PanelQuery", "kind": "PanelQuery",
"spec": { "spec": {
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"hidden": false, "hidden": false,
"query": { "query": {
"group": "grafana-testdata-datasource", "kind": "grafana-testdata-datasource",
"kind": "DataQuery", "spec": {}
"spec": {},
"version": "v0"
}, },
"refId": "A" "refId": "A"
} }
@@ -365,7 +380,7 @@
"spec": { "spec": {
"element": { "element": {
"kind": "ElementReference", "kind": "ElementReference",
"name": "panel-3" "name": "panel-2"
}, },
"height": 8, "height": 8,
"width": 12, "width": 12,
@@ -378,7 +393,7 @@
"spec": { "spec": {
"element": { "element": {
"kind": "ElementReference", "kind": "ElementReference",
"name": "panel-2" "name": "panel-3"
}, },
"height": 8, "height": 8,
"width": 12, "width": 12,
@@ -402,7 +417,7 @@
"timezone": "browser", "timezone": "browser",
"to": "now" "to": "now"
}, },
"title": "Test V2 Dashboard", "title": "New Test V2 Dashboard",
"variables": [] "variables": []
}, },
"status": {} "status": {}
@@ -17,9 +17,5 @@ export const importV2Dashboard = ({ title }: ImportDashboardConfig) => {
if (title) { if (title) {
e2e.components.ImportDashboardForm.name().clear().type(title); e2e.components.ImportDashboardForm.name().clear().type(title);
} }
e2e.components.DataSourcePicker.inputV2().click();
cy.get('div[data-testid="data-source-card"]').first().click();
e2e.components.ImportDashboardForm.submit().click(); e2e.components.ImportDashboardForm.submit().click();
}; };
+1 -1
View File
@@ -97,7 +97,7 @@ require (
github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend
github.com/grafana/grafana-app-sdk v0.39.2 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-app-sdk v0.39.2 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana-app-sdk/logging v0.39.1 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-app-sdk/logging v0.39.1 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana-aws-sdk v1.0.3 // @grafana/aws-datasources github.com/grafana/grafana-aws-sdk v1.0.4 // @grafana/aws-datasources
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // @grafana/partner-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // @grafana/partner-datasources
github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 // @grafana/grafana-operator-experience-squad
github.com/grafana/grafana-google-sdk-go v0.4.1 // @grafana/partner-datasources github.com/grafana/grafana-google-sdk-go v0.4.1 // @grafana/partner-datasources
+2 -2
View File
@@ -1595,8 +1595,8 @@ github.com/grafana/grafana-app-sdk v0.39.2 h1:ymfr+1318t+JC9U2OYrzVpGmNG/aJONUmF
github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY= github.com/grafana/grafana-app-sdk v0.39.2/go.mod h1:t0m6q561lpoHQCixS9LUHFUhUzDClzNtm7BH60gHVSY=
github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU= github.com/grafana/grafana-app-sdk/logging v0.39.1 h1:lI5rbrheuwVPuyIM6LIuEYOCSpgmXahfKtqeMyhbGPU=
github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk=
github.com/grafana/grafana-aws-sdk v1.0.3 h1:FtGiTrdlPXuQmLqeZdyNjjkNKOOdvQ4sxWw8MtJc6ow= github.com/grafana/grafana-aws-sdk v1.0.4 h1:D14UAehsOqpjliHmHzveRQ1p43KCsMzdmb7GovWj+SY=
github.com/grafana/grafana-aws-sdk v1.0.3/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-aws-sdk v1.0.4/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE=
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg=
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo=
github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 h1:S4kHwr//AqhtL9xHBtz1gqVgZQeCRGTxjgsRBAkpjKY= github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 h1:S4kHwr//AqhtL9xHBtz1gqVgZQeCRGTxjgsRBAkpjKY=
+9
View File
@@ -347,6 +347,7 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx+apLkMKt4HC0= github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx+apLkMKt4HC0=
github.com/RoaringBitmap/gocroaring v0.4.0 h1:5nufXUgWpBEUNEJXw7926YAA58ZAQRpWPrQV1xCoSjc= github.com/RoaringBitmap/gocroaring v0.4.0 h1:5nufXUgWpBEUNEJXw7926YAA58ZAQRpWPrQV1xCoSjc=
github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 h1:ZYlhPbqQFU+AHfgtCdHGDTtRW1a8geZyiE8c6Q+Sl1s= github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 h1:ZYlhPbqQFU+AHfgtCdHGDTtRW1a8geZyiE8c6Q+Sl1s=
github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76/go.mod h1:oM0MHmQ3nDsq609SS36p+oYbRi16+oVvU2Bw4Ipv0SE=
github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6 h1:5kUcJJAKWWI82Xnp/CaU0eu5hLlHkmm9acjowSkwCd0= github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6 h1:5kUcJJAKWWI82Xnp/CaU0eu5hLlHkmm9acjowSkwCd0=
github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6/go.mod h1:JwrycNnC8+sZPDyzM3MQ86LvaGzSpfxg885KOOwFRW4= github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6/go.mod h1:JwrycNnC8+sZPDyzM3MQ86LvaGzSpfxg885KOOwFRW4=
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
@@ -570,6 +571,7 @@ github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQ
github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/expr-lang/expr v1.17.0/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/expr-lang/expr v1.17.0/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@@ -679,6 +681,8 @@ github.com/grafana/alerting v0.0.0-20250403153742-418bc7118d05 h1:hMzOzI/S0nkZt0
github.com/grafana/alerting v0.0.0-20250403153742-418bc7118d05/go.mod h1:K3YAJumchx5EEZItGv4D3pCv/Ux796hmoOibP/p/eYk= github.com/grafana/alerting v0.0.0-20250403153742-418bc7118d05/go.mod h1:K3YAJumchx5EEZItGv4D3pCv/Ux796hmoOibP/p/eYk=
github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309 h1:H2p3XKDHnTBGkMXLCgXiqb2dFnHbQ4zPDXOwKK4Ne3Y= github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309 h1:H2p3XKDHnTBGkMXLCgXiqb2dFnHbQ4zPDXOwKK4Ne3Y=
github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA= github.com/grafana/alerting v0.0.0-20250429131604-de176b4a0309/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA=
github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945 h1:3imTbxFpZSVI6IBIB9mn+Xc40lUweWjfMaBSgXR7rLs=
github.com/grafana/alerting v0.0.0-20250701210250-cea2d1683945/go.mod h1:gtR7agmxVfJOmNKV/n2ZULgOYTYNL+PDKYB5N48tQ7Q=
github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8=
github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
@@ -693,6 +697,8 @@ github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+
github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk=
github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0=
github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s=
github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss=
github.com/grafana/grafana-aws-sdk v1.0.2/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE=
github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw= github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw=
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q= github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI= github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI=
@@ -1270,6 +1276,7 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhp
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220818022119-ed83ed61efb9/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -1297,6 +1304,7 @@ golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/plot v0.15.2 h1:Tlfh/jBk2tqjLZ4/P8ZIwGrLEWQSPDLRm/SNWKNXiGI= gonum.org/v1/plot v0.15.2 h1:Tlfh/jBk2tqjLZ4/P8ZIwGrLEWQSPDLRm/SNWKNXiGI=
gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg= gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg=
@@ -1382,6 +1390,7 @@ k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kms v0.33.2/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg= modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg=
modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM=
+1
View File
@@ -355,6 +355,7 @@
"leven": "^4.0.0", "leven": "^4.0.0",
"lodash": "4.17.21", "lodash": "4.17.21",
"logfmt": "^1.3.2", "logfmt": "^1.3.2",
"lossless-json": "^4.1.1",
"lru-cache": "11.1.0", "lru-cache": "11.1.0",
"lru-memoize": "^1.1.0", "lru-memoize": "^1.1.0",
"lucene": "^2.1.1", "lucene": "^2.1.1",
@@ -1,4 +1,4 @@
import { defaultDataQueryKind, Spec } from './v2alpha1/types.spec.gen'; import { Spec } from './v2alpha1/types.spec.gen';
export const handyTestingSchema: Spec = { export const handyTestingSchema: Spec = {
title: 'Default Dashboard', title: 'Default Dashboard',
@@ -37,16 +37,15 @@ export const handyTestingSchema: Spec = {
spec: { spec: {
builtIn: false, builtIn: false,
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'uid',
},
spec: { spec: {
expr: 'test-query', expr: 'test-query',
}, },
}, },
datasource: {
type: 'prometheus',
uid: 'uid',
},
filter: { ids: [1] }, filter: { ids: [1] },
enable: true, enable: true,
hide: false, hide: false,
@@ -58,16 +57,15 @@ export const handyTestingSchema: Spec = {
kind: 'AnnotationQuery', kind: 'AnnotationQuery',
spec: { spec: {
builtIn: false, builtIn: false,
datasource: {
type: 'grafana-testdata-datasource',
uid: 'uid',
},
enable: true, enable: true,
iconColor: 'red', iconColor: 'red',
name: 'Enabled', name: 'Enabled',
query: { query: {
kind: 'DataQuery', kind: 'grafana-testdata-datasource',
version: defaultDataQueryKind().version,
group: 'grafana-testdata-datasource',
datasource: {
name: 'uid',
},
spec: { spec: {
lines: 4, lines: 4,
refId: 'Anno', refId: 'Anno',
@@ -81,16 +79,15 @@ export const handyTestingSchema: Spec = {
kind: 'AnnotationQuery', kind: 'AnnotationQuery',
spec: { spec: {
builtIn: false, builtIn: false,
datasource: {
type: 'grafana-testdata-datasource',
uid: 'uid',
},
enable: false, enable: false,
iconColor: 'yellow', iconColor: 'yellow',
name: 'Disabled', name: 'Disabled',
query: { query: {
kind: 'DataQuery', kind: 'grafana-testdata-datasource',
version: defaultDataQueryKind().version,
group: 'grafana-testdata-datasource',
datasource: {
name: 'uid',
},
spec: { lines: 5, refId: 'Anno', scenarioId: 'annotations' }, spec: { lines: 5, refId: 'Anno', scenarioId: 'annotations' },
}, },
hide: false, hide: false,
@@ -100,17 +97,16 @@ export const handyTestingSchema: Spec = {
kind: 'AnnotationQuery', kind: 'AnnotationQuery',
spec: { spec: {
builtIn: false, builtIn: false,
datasource: {
type: 'grafana-testdata-datasource',
uid: 'uid',
},
enable: true, enable: true,
hide: true, hide: true,
iconColor: 'dark-purple', iconColor: 'dark-purple',
name: 'Hidden', name: 'Hidden',
query: { query: {
kind: 'DataQuery', kind: 'grafana-testdata-datasource',
version: defaultDataQueryKind().version,
group: 'grafana-testdata-datasource',
datasource: {
name: 'uid',
},
spec: { spec: {
lines: 6, lines: 6,
refId: 'Anno', refId: 'Anno',
@@ -132,13 +128,12 @@ export const handyTestingSchema: Spec = {
kind: 'PanelQuery', kind: 'PanelQuery',
spec: { spec: {
refId: 'A', refId: 'A',
datasource: {
type: 'prometheus',
uid: 'datasource1',
},
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'datasource1',
},
spec: { spec: {
expr: 'test-query', expr: 'test-query',
}, },
@@ -270,6 +265,10 @@ export const handyTestingSchema: Spec = {
text: 'text1', text: 'text1',
value: 'value1', value: 'value1',
}, },
datasource: {
type: 'prometheus',
uid: 'datasource1',
},
definition: 'definition1', definition: 'definition1',
description: 'A query variable', description: 'A query variable',
hide: 'dontHide', hide: 'dontHide',
@@ -279,12 +278,7 @@ export const handyTestingSchema: Spec = {
name: 'queryVar', name: 'queryVar',
options: [], options: [],
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'datasource1',
},
spec: { spec: {
expr: 'test-query', expr: 'test-query',
refId: 'A', refId: 'A',
@@ -11,19 +11,19 @@ export const defaultAnnotationQueryKind = (): AnnotationQueryKind => ({
}); });
export interface AnnotationQuerySpec { export interface AnnotationQuerySpec {
query: DataQueryKind; datasource?: DataSourceRef;
query?: DataQueryKind;
enable: boolean; enable: boolean;
hide: boolean; hide: boolean;
iconColor: string; iconColor: string;
name: string; name: string;
builtIn?: boolean; builtIn?: boolean;
filter?: AnnotationPanelFilter; filter?: AnnotationPanelFilter;
// Catch-all field for datasource-specific properties. Should not be available in as code tooling. // Catch-all field for datasource-specific properties
legacyOptions?: Record<string, any>; legacyOptions?: Record<string, any>;
} }
export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({ export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({
query: defaultDataQueryKind(),
enable: false, enable: false,
hide: false, hide: false,
iconColor: "", iconColor: "",
@@ -31,22 +31,24 @@ export const defaultAnnotationQuerySpec = (): AnnotationQuerySpec => ({
builtIn: false, builtIn: false,
}); });
export interface DataSourceRef {
// The plugin type-id
type?: string;
// Specific datasource instance
uid?: string;
}
export const defaultDataSourceRef = (): DataSourceRef => ({
});
export interface DataQueryKind { export interface DataQueryKind {
kind: "DataQuery"; // The kind of a DataQueryKind is the datasource type
group: string; kind: string;
version: string;
// New type for datasource reference
// Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.
datasource?: {
name?: string;
};
spec: Record<string, any>; spec: Record<string, any>;
} }
export const defaultDataQueryKind = (): DataQueryKind => ({ export const defaultDataQueryKind = (): DataQueryKind => ({
kind: "DataQuery", kind: "",
group: "",
version: "v0",
spec: {}, spec: {},
}); });
@@ -149,6 +151,7 @@ export const defaultPanelQueryKind = (): PanelQueryKind => ({
export interface PanelQuerySpec { export interface PanelQuerySpec {
query: DataQueryKind; query: DataQueryKind;
datasource?: DataSourceRef;
refId: string; refId: string;
hidden: boolean; hidden: boolean;
} }
@@ -991,6 +994,7 @@ export interface QueryVariableSpec {
refresh: VariableRefresh; refresh: VariableRefresh;
skipUrlSync: boolean; skipUrlSync: boolean;
description?: string; description?: string;
datasource?: DataSourceRef;
query: DataQueryKind; query: DataQueryKind;
regex: string; regex: string;
sort: VariableSort; sort: VariableSort;
@@ -1279,18 +1283,6 @@ export const defaultGroupByVariableSpec = (): GroupByVariableSpec => ({
skipUrlSync: false, skipUrlSync: false,
}); });
// Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec
// This type is widely used in the codebase and changing it will have a big impact
export interface DataSourceRef {
// The plugin type-id
type?: string;
// Specific datasource instance
uid?: string;
}
export const defaultDataSourceRef = (): DataSourceRef => ({
});
// Adhoc variable kind // Adhoc variable kind
export interface AdhocVariableKind { export interface AdhocVariableKind {
kind: "AdhocVariable"; kind: "AdhocVariable";
+1 -8
View File
@@ -57,7 +57,7 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper)
}, },
Spec: folders.FolderSpec{ Spec: folders.FolderSpec{
Title: v.Title, Title: v.Title,
Description: descr(v.Description), Description: &v.Description,
}, },
} }
@@ -94,10 +94,3 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper)
f.UID = gapiutil.CalculateClusterWideUID(f) f.UID = gapiutil.CalculateClusterWideUID(f)
return f, nil return f, nil
} }
func descr(str string) *string {
if str == "" {
return nil
}
return &str
}
+102 -96
View File
@@ -45,6 +45,10 @@ func TestIntegrationFoldersApp(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
if !db.IsTestDbSQLite() {
t.Skip("test only on sqlite for now")
}
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true, AppModeProduction: true,
EnableFeatureToggles: []string{ EnableFeatureToggles: []string{
@@ -120,71 +124,94 @@ func TestIntegrationFoldersApp(t *testing.T) {
}`, string(v1Disco)) }`, string(v1Disco))
}) })
t.Run("with dual write (unified storage, mode 0)", func(t *testing.T) { // test on all dualwriter modes
doFolderTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ for mode := 0; mode <= 4; mode++ {
AppModeProduction: true, modeDw := grafanarest.DualWriterMode(mode)
DisableAnonymous: true,
APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: {
DualWriterMode: grafanarest.Mode0,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
}))
})
t.Run("with dual write (unified storage, mode 1)", func(t *testing.T) { t.Run(fmt.Sprintf("with dual write (unified storage, mode %v)", modeDw), func(t *testing.T) {
doFolderTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ doFolderTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true, AppModeProduction: true,
DisableAnonymous: true, DisableAnonymous: true,
APIServerStorageType: "unified", APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: { folders.RESOURCEGROUP: {
DualWriterMode: grafanarest.Mode1, DualWriterMode: modeDw,
},
}, },
}, EnableFeatureToggles: []string{
EnableFeatureToggles: []string{ featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagKubernetesClientDashboardsFolders, },
}, }))
})) })
})
t.Run("with dual write (unified storage, mode 1, create nested folders)", func(t *testing.T) { t.Run(fmt.Sprintf("with dual write (unified storage, mode %v, create nested folders)", modeDw), func(t *testing.T) {
doNestedCreateTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ doNestedCreateTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true, AppModeProduction: true,
DisableAnonymous: true, DisableAnonymous: true,
APIServerStorageType: "unified", APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: { folders.RESOURCEGROUP: {
DualWriterMode: grafanarest.Mode1, DualWriterMode: modeDw,
},
}, },
}, EnableFeatureToggles: []string{
EnableFeatureToggles: []string{ featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagNestedFolders,
featuremgmt.FlagNestedFolders, },
}, }))
})) })
})
t.Run("with dual write (unified storage, mode 1, create existing folder)", func(t *testing.T) { t.Run(fmt.Sprintf("with dual write (unified storage, mode %v, create existing folder)", modeDw), func(t *testing.T) {
doCreateDuplicateFolderTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ doCreateDuplicateFolderTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true, AppModeProduction: true,
DisableAnonymous: true, DisableAnonymous: true,
APIServerStorageType: "unified", APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: { folders.RESOURCEGROUP: {
DualWriterMode: grafanarest.Mode1, DualWriterMode: modeDw,
},
}, },
}, EnableFeatureToggles: []string{
EnableFeatureToggles: []string{ featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagKubernetesClientDashboardsFolders, featuremgmt.FlagNestedFolders,
featuremgmt.FlagNestedFolders, },
}, }))
})) })
})
t.Run(fmt.Sprintf("when creating a folder, mode %v, it should trim leading and trailing spaces", modeDw), func(t *testing.T) {
doCreateEnsureTitleIsTrimmedTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: {
DualWriterMode: modeDw,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
})
t.Run(fmt.Sprintf("with dual write (unified storage, mode %v, create circular reference folder)", modeDw), func(t *testing.T) {
doCreateCircularReferenceFolderTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: {
DualWriterMode: modeDw,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
})
}
// This is a general test for the unified storage list operation. We don't have a common test // This is a general test for the unified storage list operation. We don't have a common test
// directory for now, so we (search and storage) keep it here as we own this part of the tests. // directory for now, so we (search and storage) keep it here as we own this part of the tests.
@@ -217,40 +244,6 @@ func TestIntegrationFoldersApp(t *testing.T) {
}) })
} }
}) })
t.Run("when creating a folder it should trim leading and trailing spaces", func(t *testing.T) {
doCreateEnsureTitleIsTrimmedTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: {
DualWriterMode: grafanarest.Mode1,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
})
t.Run("with dual write (unified storage, mode 1, create circular reference folder)", func(t *testing.T) {
doCreateCircularReferenceFolderTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
DisableAnonymous: true,
APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
folders.RESOURCEGROUP: {
DualWriterMode: grafanarest.Mode1,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
})
} }
func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper { func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper {
@@ -276,20 +269,25 @@ func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper
require.NotNil(t, legacyCreate.Result) require.NotNil(t, legacyCreate.Result)
uid := legacyCreate.Result.UID uid := legacyCreate.Result.UID
require.NotEmpty(t, uid) require.NotEmpty(t, uid)
//nolint:staticcheck
id := legacyCreate.Result.ID
require.NotEmpty(t, id)
idStr := fmt.Sprintf("%d", id)
expectedResult := `{ expectedResult := `{
"apiVersion": "folder.grafana.app/v1beta1", "apiVersion": "folder.grafana.app/v1beta1",
"kind": "Folder", "kind": "Folder",
"metadata": { "metadata": {
"creationTimestamp": "${creationTimestamp}", "creationTimestamp": "${creationTimestamp}",
"labels": {"grafana.app/deprecatedInternalID":"1"}, "labels": {"grafana.app/deprecatedInternalID":"` + idStr + `"},
"name": "` + uid + `", "name": "` + uid + `",
"namespace": "default", "namespace": "default",
"resourceVersion": "${resourceVersion}", "resourceVersion": "${resourceVersion}",
"uid": "${uid}" "uid": "${uid}"
}, },
"spec": { "spec": {
"title": "Test" "title": "Test",
"description": ""
}, },
"status": {} "status": {}
}` }`
@@ -592,6 +590,9 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
if !db.IsTestDbSQLite() {
t.Skip("test only on sqlite for now")
}
folderWithoutParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\"}" folderWithoutParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\"}"
folderWithParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\", \"parentUid\": \"parentuid\"}" folderWithParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\", \"parentUid\": \"parentuid\"}"
@@ -716,6 +717,9 @@ func TestIntegrationFolderGetPermissions(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
if !db.IsTestDbSQLite() {
t.Skip("test only on sqlite for now")
}
type testCase struct { type testCase struct {
description string description string
@@ -866,6 +870,9 @@ func TestIntegrationFoldersCreateAPIEndpointK8S(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
if !db.IsTestDbSQLite() {
t.Skip("test only on sqlite for now")
}
folderWithoutParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\"}" folderWithoutParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\"}"
folderWithTitleEmpty := "{ \"title\": \"\"}" folderWithTitleEmpty := "{ \"title\": \"\"}"
@@ -1027,7 +1034,6 @@ func TestIntegrationFoldersGetAPIEndpointK8S(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
if !db.IsTestDbSQLite() { if !db.IsTestDbSQLite() {
t.Skip("test only on sqlite for now") t.Skip("test only on sqlite for now")
} }
@@ -1250,7 +1250,6 @@
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQuerySpec": { "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQuerySpec": {
"type": "object", "type": "object",
"required": [ "required": [
"query",
"enable", "enable",
"hide", "hide",
"iconColor", "iconColor",
@@ -1260,6 +1259,9 @@
"builtIn": { "builtIn": {
"type": "boolean" "type": "boolean"
}, },
"datasource": {
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef"
},
"enable": { "enable": {
"type": "boolean", "type": "boolean",
"default": false "default": false
@@ -1276,7 +1278,7 @@
"default": "" "default": ""
}, },
"legacyOptions": { "legacyOptions": {
"description": "Catch-all field for datasource-specific properties. Should not be available in as code tooling.", "description": "Catch-all field for datasource-specific properties",
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"type": "object" "type": "object"
@@ -1287,12 +1289,7 @@
"default": "" "default": ""
}, },
"query": { "query": {
"default": {}, "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind"
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind"
}
]
} }
} }
}, },
@@ -1868,24 +1865,11 @@
"type": "object", "type": "object",
"required": [ "required": [
"kind", "kind",
"group",
"version",
"spec" "spec"
], ],
"properties": { "properties": {
"datasource": {
"description": "New type for datasource reference Not creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.",
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1DataQueryKindDatasource"
}
]
},
"group": {
"type": "string",
"default": ""
},
"kind": { "kind": {
"description": "The kind of a DataQueryKind is the datasource type",
"type": "string", "type": "string",
"default": "" "default": ""
}, },
@@ -1894,15 +1878,10 @@
"additionalProperties": { "additionalProperties": {
"type": "object" "type": "object"
} }
},
"version": {
"type": "string",
"default": ""
} }
} }
}, },
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef": { "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef": {
"description": "Keeping this for backwards compatibility for GroupByVariableSpec and AdhocVariableSpec This type is widely used in the codebase and changing it will have a big impact",
"type": "object", "type": "object",
"properties": { "properties": {
"type": { "type": {
@@ -2764,6 +2743,9 @@
"hidden" "hidden"
], ],
"properties": { "properties": {
"datasource": {
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef"
},
"hidden": { "hidden": {
"type": "boolean", "type": "boolean",
"default": false "default": false
@@ -3011,6 +2993,9 @@
} }
] ]
}, },
"datasource": {
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef"
},
"definition": { "definition": {
"type": "string" "type": "string"
}, },
@@ -3740,14 +3725,6 @@
} }
} }
}, },
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1DataQueryKindDatasource": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": { "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": {
"type": "object", "type": "object",
"required": [ "required": [
+35 -1
View File
@@ -15,6 +15,17 @@ jest.mock('@grafana/runtime', () => ({
}, },
})); }));
beforeEach(() => {
Object.assign(navigator, {
clipboard: {
write: jest.fn().mockResolvedValue(undefined),
writeText: jest.fn().mockResolvedValue(undefined),
},
});
document.execCommand = jest.fn();
});
describe('createShortLink', () => { describe('createShortLink', () => {
it('creates short link', async () => { it('creates short link', async () => {
const shortUrl = await createShortLink('www.verylonglinkwehavehere.com'); const shortUrl = await createShortLink('www.verylonglinkwehavehere.com');
@@ -23,11 +34,34 @@ describe('createShortLink', () => {
}); });
describe('createAndCopyShortLink', () => { describe('createAndCopyShortLink', () => {
it('copies short link to clipboard', async () => { it('copies short link to clipboard via document.execCommand when navigator.clipboard is undefined', async () => {
Object.assign(navigator, {
clipboard: {
write: undefined,
},
});
document.execCommand = jest.fn(); document.execCommand = jest.fn();
await createAndCopyShortLink('www.verylonglinkwehavehere.com'); await createAndCopyShortLink('www.verylonglinkwehavehere.com');
expect(document.execCommand).toHaveBeenCalledWith('copy'); expect(document.execCommand).toHaveBeenCalledWith('copy');
}); });
it('copies short link to clipboard via navigator.clipboard.writeText when ClipboardItem is undefined', async () => {
window.isSecureContext = true;
await createAndCopyShortLink('www.verylonglinkwehavehere.com');
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('www.short.com');
});
it('copies short link to clipboard via navigator.clipboard.write and ClipboardItem when it is defined', async () => {
global.ClipboardItem = jest.fn().mockImplementation(() => ({
type: 'text/plain',
size: 0,
slice: jest.fn(),
supports: jest.fn().mockReturnValue(true),
// eslint-disable-next-line
})) as any;
await createAndCopyShortLink('www.verylonglinkwehavehere.com');
expect(navigator.clipboard.write).toHaveBeenCalled();
});
}); });
describe('getLogsPermalinkRange', () => { describe('getLogsPermalinkRange', () => {
+21 -4
View File
@@ -35,13 +35,30 @@ export const createShortLink = memoizeOne(async function (path: string) {
} }
}); });
/**
* Creates a ClipboardItem for the shortened link. This is used due to clipboard issues in Safari after making async calls.
* See https://github.com/grafana/grafana/issues/106889
* @param path - The long path to share.
* @returns A ClipboardItem for the shortened link.
*/
const createShortLinkClipboardItem = (path: string) => {
return new ClipboardItem({
'text/plain': createShortLink(path),
});
};
export const createAndCopyShortLink = async (path: string) => { export const createAndCopyShortLink = async (path: string) => {
const shortLink = await createShortLink(path); if (typeof ClipboardItem !== 'undefined' && navigator.clipboard.write) {
if (shortLink) { navigator.clipboard.write([createShortLinkClipboardItem(path)]);
copyStringToClipboard(shortLink);
dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard'))); dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard')));
} else { } else {
dispatch(notifyApp(createErrorNotification('Error generating shortened link'))); const shortLink = await createShortLink(path);
if (shortLink) {
copyStringToClipboard(shortLink);
dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard')));
} else {
dispatch(notifyApp(createErrorNotification('Error generating shortened link')));
}
} }
}; };
@@ -37,39 +37,6 @@ jest.mock('@grafana/runtime', () => {
...original.config.featureToggles, ...original.config.featureToggles,
dashboardNewLayouts: false, // Default value dashboardNewLayouts: false, // Default value
}, },
bootData: {
...original.config.bootData,
settings: {
...original.config.bootData.settings,
datasources: {
'gdev-testdata': {
id: 7,
uid: 'abc',
type: 'grafana-testdata-datasource',
name: 'gdev-testdata',
meta: {
id: 'grafana-testdata-datasource',
type: 'datasource',
name: 'TestData',
aliasIDs: ['testdata'],
},
},
'-- Grafana --': {
id: -1,
uid: 'grafana',
type: 'datasource',
name: '-- Grafana --',
meta: {
id: 'grafana',
type: 'datasource',
name: '-- Grafana --',
},
},
},
defaultDatasource: 'gdev-testdata',
},
},
}, },
}; };
}); });
@@ -1621,11 +1588,9 @@ const v2ProvisionedDashboardResource = {
kind: 'AnnotationQuery', kind: 'AnnotationQuery',
spec: { spec: {
builtIn: true, builtIn: true,
query: { datasource: {
kind: 'DataQuery', type: 'grafana',
group: 'grafana', uid: '-- Grafana --',
spec: {},
version: 'v0',
}, },
enable: true, enable: true,
hide: true, hide: true,
@@ -1648,14 +1613,13 @@ const v2ProvisionedDashboardResource = {
{ {
kind: 'PanelQuery', kind: 'PanelQuery',
spec: { spec: {
datasource: {
type: 'grafana-testdata-datasource',
uid: 'PD8C576611E62080A',
},
hidden: false, hidden: false,
query: { query: {
kind: 'DataQuery', kind: 'grafana-testdata-datasource',
group: 'grafana-testdata-datasource',
version: 'v0',
datasource: {
name: 'PD8C576611E62080A',
},
spec: { spec: {
scenarioId: 'random_walk', scenarioId: 'random_walk',
seriesCount: 2, seriesCount: 2,
@@ -549,12 +549,13 @@ describe('dashboard exporter v2', () => {
{ {
kind: 'PanelQuery', kind: 'PanelQuery',
spec: { spec: {
datasource: {
type: 'prometheus',
uid: '${datasourceVar}',
},
hidden: false, hidden: false,
query: { query: {
datasource: { kind: 'prometheus',
name: '${datasourceVar}',
},
group: 'prometheus',
spec: { spec: {
editorMode: 'builder', editorMode: 'builder',
expr: 'go_goroutines{job="prometheus"}', expr: 'go_goroutines{job="prometheus"}',
@@ -582,7 +583,7 @@ describe('dashboard exporter v2', () => {
it('should replace datasource in a query variable', async () => { it('should replace datasource in a query variable', async () => {
const { dashboard } = await setup(); const { dashboard } = await setup();
const variable = dashboard.variables[0] as QueryVariableKind; const variable = dashboard.variables[0] as QueryVariableKind;
expect(variable.spec.query.datasource?.name).toBeUndefined(); expect(variable.spec.datasource?.uid).toBeUndefined();
}); });
it('do not expose datasource name and id in datasource variable', async () => { it('do not expose datasource name and id in datasource variable', async () => {
@@ -596,7 +597,7 @@ describe('dashboard exporter v2', () => {
const { dashboard } = await setup(); const { dashboard } = await setup();
const annotationQuery = dashboard.annotations[0]; const annotationQuery = dashboard.annotations[0];
expect(annotationQuery.spec.query?.datasource?.name).toBeUndefined(); expect(annotationQuery.spec.datasource?.uid).toBeUndefined();
}); });
it('should remove library panels from layout', async () => { it('should remove library panels from layout', async () => {
@@ -615,8 +616,11 @@ describe('dashboard exporter v2', () => {
if (panel.kind !== 'Panel') { if (panel.kind !== 'Panel') {
throw new Error('Panel should be a Panel'); throw new Error('Panel should be a Panel');
} }
expect(panel.spec.data.spec.queries[0].spec.query.datasource?.name).toBe('${datasourceVar}');
expect(panel.spec.data.spec.queries[0].spec.query.group).toBe('prometheus'); expect(panel.spec.data.spec.queries[0].spec.datasource).toEqual({
type: 'prometheus',
uid: '${datasourceVar}',
});
}); });
}); });
@@ -344,8 +344,7 @@ export async function makeExportableV2(dashboard: DashboardV2Spec) {
const removeDataSourceRefs = ( const removeDataSourceRefs = (
obj: AnnotationQueryKind['spec'] | QueryVariableKind['spec'] | PanelQueryKind['spec'] obj: AnnotationQueryKind['spec'] | QueryVariableKind['spec'] | PanelQueryKind['spec']
) => { ) => {
const datasourceUid = obj.query?.datasource?.name; const datasourceUid = obj.datasource?.uid;
if (datasourceUid?.startsWith('${') && datasourceUid?.endsWith('}')) { if (datasourceUid?.startsWith('${') && datasourceUid?.endsWith('}')) {
const varName = datasourceUid.slice(2, -1); const varName = datasourceUid.slice(2, -1);
// if there's a match we don't want to remove the datasource ref // if there's a match we don't want to remove the datasource ref
@@ -355,7 +354,7 @@ export async function makeExportableV2(dashboard: DashboardV2Spec) {
} }
} }
obj.query && (obj.query.datasource = undefined); obj.datasource = undefined;
}; };
const processPanel = (panel: PanelKind) => { const processPanel = (panel: PanelKind) => {
@@ -15,7 +15,7 @@ const newShareButtonSelector = e2eSelectors.pages.Dashboard.DashNav.newShareButt
export const ShareDashboardButton = ({ dashboard }: ToolbarActionProps) => { export const ShareDashboardButton = ({ dashboard }: ToolbarActionProps) => {
const [_, buildUrl] = useAsyncFn(async () => { const [_, buildUrl] = useAsyncFn(async () => {
DashboardInteractions.toolbarShareClick(); DashboardInteractions.toolbarShareClick();
return await buildShareUrl(dashboard); await buildShareUrl(dashboard);
}, [dashboard]); }, [dashboard]);
return ( return (
@@ -10,13 +10,10 @@ import { Dashboard, VariableModel } from '@grafana/schema';
import { import {
Spec as DashboardV2Spec, Spec as DashboardV2Spec,
defaultSpec as defaultDashboardV2Spec, defaultSpec as defaultDashboardV2Spec,
defaultDataQueryKind,
defaultPanelSpec, defaultPanelSpec,
defaultTimeSettingsSpec, defaultTimeSettingsSpec,
GridLayoutKind, GridLayoutKind,
PanelKind,
PanelSpec, PanelSpec,
QueryVariableKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui';
import { AnnoKeyDashboardSnapshotOriginalUrl } from 'app/features/apiserver/types'; import { AnnoKeyDashboardSnapshotOriginalUrl } from 'app/features/apiserver/types';
@@ -50,13 +47,11 @@ jest.mock('@grafana/runtime', () => ({
name: 'Grafana', name: 'Grafana',
meta: { id: 'grafana' }, meta: { id: 'grafana' },
type: 'datasource', type: 'datasource',
uid: 'grafana',
}, },
prometheus: { prometheus: {
name: 'prometheus', name: 'prometheus',
meta: { id: 'prometheus' }, meta: { id: 'prometheus' },
type: 'datasource', type: 'datasource',
uid: 'prometheus-uid',
}, },
}, },
}, },
@@ -728,14 +723,9 @@ describe('DashboardSceneSerializer', () => {
spec: { spec: {
builtIn: true, builtIn: true,
name: 'Annotations & Alerts', name: 'Annotations & Alerts',
query: { datasource: {
kind: 'DataQuery', uid: '-- Grafana --',
version: defaultDataQueryKind().version, type: 'grafana',
group: 'grafana',
datasource: {
name: '-- Grafana --',
},
spec: {},
}, },
enable: true, enable: true,
hide: true, hide: true,
@@ -894,226 +884,6 @@ describe('DashboardSceneSerializer', () => {
}, },
}); });
}); });
describe('data source references persistence', () => {
it('should not fill data source references for annotations when input did not contain it', () => {
const dashboard = setupV2({
annotations: [
{
kind: 'AnnotationQuery',
spec: {
builtIn: false,
enable: true,
hide: false,
iconColor: 'blue',
name: 'prom-annotations',
query: {
group: 'prometheus',
kind: 'DataQuery',
spec: {
refId: 'Anno',
},
version: 'v0',
},
},
},
],
});
const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);
// referencing index 1 as transformation adds built in annotation query
expect(saveAsModel.annotations[1].spec.query.datasource).toBeUndefined();
});
it('should fill data source references for annotations when input did contain it', () => {
const dashboard = setupV2({
annotations: [
{
kind: 'AnnotationQuery',
spec: {
builtIn: false,
enable: true,
hide: false,
iconColor: 'blue',
name: 'prom-annotations',
query: {
group: 'prometheus',
kind: 'DataQuery',
datasource: {
name: 'prometheus-uid',
},
spec: {
refId: 'Anno',
},
version: 'v0',
},
},
},
],
});
const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);
// referencing index 1 as transformation adds built in annotation query
expect(saveAsModel.annotations[1].spec.query.datasource).toEqual({
name: 'prometheus-uid',
});
});
it('should not fill data source references for panel queries when input did not contain it', () => {
const dashboard = setupV2({
elements: {
'panel-1': {
kind: 'Panel',
spec: {
...defaultPanelSpec(),
data: {
kind: 'QueryGroup',
spec: {
transformations: [],
queryOptions: {},
queries: [
{
kind: 'PanelQuery',
spec: {
refId: 'A',
hidden: false,
query: {
kind: 'DataQuery',
group: 'prometheus',
version: 'v0',
spec: {},
},
},
},
],
},
},
},
},
},
});
const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);
expect(
(saveAsModel.elements['panel-1'] as PanelKind).spec.data.spec.queries[0].spec.query.datasource
).toBeUndefined();
});
it('should fill data source references for panel queries when input did contain it', () => {
const dashboard = setupV2({
elements: {
'panel-1': {
kind: 'Panel',
spec: {
...defaultPanelSpec(),
data: {
kind: 'QueryGroup',
spec: {
transformations: [],
queryOptions: {},
queries: [
{
kind: 'PanelQuery',
spec: {
refId: 'A',
hidden: false,
query: {
kind: 'DataQuery',
group: 'prometheus',
version: 'v0',
datasource: {
name: 'prometheus-uid',
},
spec: {},
},
},
},
],
},
},
},
},
},
});
const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);
expect(
(saveAsModel.elements['panel-1'] as PanelKind).spec.data.spec.queries[0].spec.query.datasource
).toEqual({
name: 'prometheus-uid',
});
});
it('should not fill data source references for query variables when input did contain it', () => {
const queryVariable: QueryVariableKind = {
kind: 'QueryVariable',
spec: {
name: 'app',
current: {
text: 'app1',
value: 'app1',
},
hide: 'dontHide',
includeAll: false,
label: 'Query Variable',
skipUrlSync: false,
regex: '',
definition: '',
options: [],
refresh: 'never',
sort: 'alphabeticalAsc',
multi: false,
allowCustomValue: true,
query: {
kind: 'DataQuery',
group: 'prometheus',
version: 'v0',
spec: {},
},
},
};
const dashboard = setupV2({
variables: [queryVariable],
});
const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);
expect((saveAsModel.variables[0] as QueryVariableKind).spec.query.datasource).toBeUndefined();
});
it('should fill data source references for query variables when input did contain it', () => {
const queryVariable: QueryVariableKind = {
kind: 'QueryVariable',
spec: {
name: 'app',
current: {
text: 'app1',
value: 'app1',
},
hide: 'dontHide',
includeAll: false,
label: 'Query Variable',
skipUrlSync: false,
regex: '',
definition: '',
options: [],
refresh: 'never',
sort: 'alphabeticalAsc',
multi: false,
allowCustomValue: true,
query: {
kind: 'DataQuery',
group: 'prometheus',
version: 'v0',
datasource: {
name: 'prometheus-uid',
},
spec: {},
},
},
};
const dashboard = setupV2({
variables: [queryVariable],
});
const saveAsModel = serializer.getSaveAsModel(dashboard, baseOptions);
expect((saveAsModel.variables[0] as QueryVariableKind).spec.query.datasource).toEqual({
name: 'prometheus-uid',
});
});
});
}); });
describe('panel mapping methods', () => { describe('panel mapping methods', () => {
@@ -1244,7 +1014,7 @@ describe('DashboardSceneSerializer', () => {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
// No datasource defined // No datasource defined
query: { kind: 'DataQuery', version: defaultDataQueryKind().version, group: 'sql', spec: {} }, query: { kind: 'sql', spec: {} },
}, },
}, },
{ {
@@ -1252,15 +1022,8 @@ describe('DashboardSceneSerializer', () => {
spec: { spec: {
refId: 'B', refId: 'B',
hidden: false, hidden: false,
query: { datasource: { uid: 'datasource-1', type: 'prometheus' },
kind: 'DataQuery', query: { kind: 'prometheus', spec: {} },
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'datasource-1',
},
spec: {},
},
}, },
}, },
], ],
@@ -1295,7 +1058,7 @@ describe('DashboardSceneSerializer', () => {
refId: 'C', refId: 'C',
hidden: false, hidden: false,
// No datasource defined // No datasource defined
query: { kind: 'DataQuery', version: defaultDataQueryKind().version, group: 'sql', spec: {} }, query: { kind: 'sql', spec: {} },
}, },
}, },
], ],
@@ -1343,7 +1106,7 @@ describe('DashboardSceneSerializer', () => {
kind: 'AnnotationQuery', kind: 'AnnotationQuery',
spec: { spec: {
name: 'Annotation 1', name: 'Annotation 1',
query: { kind: 'DataQuery', version: defaultDataQueryKind().version, group: 'prometheus', spec: {} }, query: { kind: 'prometheus', spec: {} },
enable: true, enable: true,
hide: false, hide: false,
iconColor: 'red', iconColor: 'red',
@@ -288,7 +288,7 @@ export class V2DashboardSerializer
const panelQueries = elementPanel.spec.data.spec.queries; const panelQueries = elementPanel.spec.data.spec.queries;
for (const query of panelQueries) { for (const query of panelQueries) {
if (!query.spec.query.datasource?.name) { if (!query.spec.datasource) {
const elementId = this.getElementIdForPanel(elementPanel.spec.id); const elementId = this.getElementIdForPanel(elementPanel.spec.id);
if (!this.defaultDsReferencesMap.panels.has(elementId)) { if (!this.defaultDsReferencesMap.panels.has(elementId)) {
this.defaultDsReferencesMap.panels.set(elementId, new Set()); this.defaultDsReferencesMap.panels.set(elementId, new Set());
@@ -306,7 +306,7 @@ export class V2DashboardSerializer
if (saveModel?.variables) { if (saveModel?.variables) {
for (const variable of saveModel.variables) { for (const variable of saveModel.variables) {
// for query variables that dont have a ds defined add them to the list // for query variables that dont have a ds defined add them to the list
if (variable.kind === 'QueryVariable' && !variable.spec.query.datasource?.name) { if (variable.kind === 'QueryVariable' && !variable.spec.datasource) {
this.defaultDsReferencesMap.variables.add(variable.spec.name); this.defaultDsReferencesMap.variables.add(variable.spec.name);
} }
} }
@@ -315,7 +315,7 @@ export class V2DashboardSerializer
// initialize annotations ds references map // initialize annotations ds references map
if (saveModel?.annotations) { if (saveModel?.annotations) {
for (const annotation of saveModel.annotations) { for (const annotation of saveModel.annotations) {
if (!annotation.spec.query?.datasource?.name) { if (!annotation.spec.datasource) {
this.defaultDsReferencesMap.annotations.add(annotation.spec.name); this.defaultDsReferencesMap.annotations.add(annotation.spec.name);
} }
} }
@@ -7,19 +7,28 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
"kind": "AnnotationQuery", "kind": "AnnotationQuery",
"spec": { "spec": {
"builtIn": false, "builtIn": false,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --",
},
"enable": true, "enable": true,
"hide": false, "hide": false,
"iconColor": "red", "iconColor": "red",
"name": "query1", "name": "query1",
"query": { },
"datasource": { },
"name": "-- Grafana --", {
}, "kind": "AnnotationQuery",
"group": "grafana", "spec": {
"kind": "DataQuery", "builtIn": false,
"spec": {}, "datasource": {
"version": "v0", "type": "prometheus",
"uid": "abcdef",
}, },
"enable": true,
"hide": true,
"iconColor": "blue",
"name": "query2",
}, },
}, },
{ {
@@ -28,17 +37,8 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
"builtIn": false, "builtIn": false,
"enable": true, "enable": true,
"hide": true, "hide": true,
"iconColor": "blue", "iconColor": "green",
"name": "query2", "name": "query3",
"query": {
"datasource": {
"name": "abcdef",
},
"group": "prometheus",
"kind": "DataQuery",
"spec": {},
"version": "v0",
},
}, },
}, },
], ],
@@ -152,6 +152,10 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
"text": "text1", "text": "text1",
"value": "value1", "value": "value1",
}, },
"datasource": {
"type": "prometheus",
"uid": "datasource1",
},
"definition": "definition1", "definition": "definition1",
"description": "A query variable", "description": "A query variable",
"hide": "hideLabel", "hide": "hideLabel",
@@ -161,16 +165,11 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
"name": "queryVar", "name": "queryVar",
"options": [], "options": [],
"query": { "query": {
"datasource": { "kind": "prometheus",
"name": "datasource1",
},
"group": "prometheus",
"kind": "DataQuery",
"spec": { "spec": {
"expr": "label_values(node_boot_time_seconds)", "expr": "label_values(node_boot_time_seconds)",
"refId": "A", "refId": "A",
}, },
"version": "v0",
}, },
"refresh": "onDashboardLoad", "refresh": "onDashboardLoad",
"regex": "regex1", "regex": "regex1",
@@ -1,226 +0,0 @@
import { AnnotationQuery } from '@grafana/data';
import { AnnotationQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { transformV1ToV2AnnotationQuery, transformV2ToV1AnnotationQuery } from './annotations';
describe('V1<->V2 annotation convertions', () => {
test('given grafana-built in annotations', () => {
// test case
const annotationDefinition: AnnotationQuery = {
builtIn: 1,
datasource: {
type: 'grafana',
uid: 'grafana',
},
enable: true,
hide: false,
iconColor: 'yellow',
name: 'Annotations \u0026 Alerts',
target: {
// @ts-expect-error
limit: 100,
matchAny: false,
tags: [],
type: 'dashboard',
},
type: 'dashboard',
};
const expectedV2: AnnotationQueryKind = {
kind: 'AnnotationQuery',
spec: {
builtIn: true,
enable: true,
hide: false,
iconColor: 'yellow',
name: 'Annotations \u0026 Alerts',
query: {
kind: 'DataQuery',
group: 'grafana',
version: 'v0',
datasource: {
name: 'grafana',
},
spec: {
limit: 100,
matchAny: false,
tags: [],
type: 'dashboard',
},
},
},
};
const resultV2: AnnotationQueryKind = transformV1ToV2AnnotationQuery(annotationDefinition, 'grafana', 'grafana');
expect(resultV2).toEqual(expectedV2);
const resultV1: AnnotationQuery = transformV2ToV1AnnotationQuery(expectedV2);
expect(resultV1).toEqual(annotationDefinition);
});
test('given annotations with datasource', () => {
const annotationDefinition = {
datasource: {
type: 'grafana-testdata-datasource',
uid: 'uid',
},
enable: true,
hide: false,
iconColor: 'blue',
name: 'testdata-annos',
target: {
lines: 10,
refId: 'Anno',
scenarioId: 'annotations',
},
};
const expectedV2: AnnotationQueryKind = {
kind: 'AnnotationQuery',
spec: {
enable: true,
hide: false,
iconColor: 'blue',
name: 'testdata-annos',
builtIn: false,
query: {
kind: 'DataQuery',
group: 'grafana-testdata-datasource',
version: 'v0',
datasource: {
name: 'uid',
},
spec: {
lines: 10,
refId: 'Anno',
scenarioId: 'annotations',
},
},
},
};
const resultV2: AnnotationQueryKind = transformV1ToV2AnnotationQuery(
annotationDefinition,
'grafana-testdata-datasource',
'uid'
);
expect(resultV2).toEqual(expectedV2);
const resultV1: AnnotationQuery = transformV2ToV1AnnotationQuery(expectedV2);
expect(resultV1).toEqual(annotationDefinition);
});
test('given annotations with target', () => {
const annotationDefinition = {
datasource: {
type: 'prometheus',
uid: 'uid',
},
enable: true,
hide: false,
iconColor: 'yellow',
name: 'prom-annos',
target: {
expr: '{action="add_client"}',
interval: '',
lines: 10,
refId: 'Anno',
scenarioId: 'annotations',
},
};
const expectedV2: AnnotationQueryKind = {
kind: 'AnnotationQuery',
spec: {
enable: true,
hide: false,
iconColor: 'yellow',
name: 'prom-annos',
builtIn: false,
query: {
kind: 'DataQuery',
group: 'prometheus',
version: 'v0',
datasource: {
name: 'uid',
},
spec: {
expr: '{action="add_client"}',
interval: '',
lines: 10,
refId: 'Anno',
scenarioId: 'annotations',
},
},
},
};
const resultV2: AnnotationQueryKind = transformV1ToV2AnnotationQuery(annotationDefinition, 'prometheus', 'uid');
expect(resultV2).toEqual(expectedV2);
const resultV1: AnnotationQuery = transformV2ToV1AnnotationQuery(expectedV2);
expect(resultV1).toEqual(annotationDefinition);
});
test('given annotations with non-schematised options / legacyOptions', () => {
const annotationDefinition = {
datasource: {
type: 'elasticsearch',
uid: 'uid',
},
enable: true,
hide: false,
iconColor: 'red',
name: 'elastic - annos',
tagsField: 'asd',
target: {
lines: 10,
query: 'test query',
refId: 'Anno',
scenarioId: 'annotations',
},
textField: 'asd',
timeEndField: 'asdas',
timeField: 'asd',
};
const expectedV2: AnnotationQueryKind = {
kind: 'AnnotationQuery',
spec: {
enable: true,
hide: false,
iconColor: 'red',
name: 'elastic - annos',
builtIn: false,
query: {
kind: 'DataQuery',
group: 'elasticsearch',
version: 'v0',
datasource: {
name: 'uid',
},
spec: {
lines: 10,
query: 'test query',
refId: 'Anno',
scenarioId: 'annotations',
},
},
legacyOptions: {
tagsField: 'asd',
textField: 'asd',
timeEndField: 'asdas',
timeField: 'asd',
},
},
};
const resultV2: AnnotationQueryKind = transformV1ToV2AnnotationQuery(annotationDefinition, 'elasticsearch', 'uid');
expect(resultV2).toEqual(expectedV2);
const resultV1: AnnotationQuery = transformV2ToV1AnnotationQuery(expectedV2);
expect(resultV1).toEqual(annotationDefinition);
});
});
@@ -1,118 +0,0 @@
import { AnnotationQuery } from '@grafana/data';
import {
AnnotationQueryKind,
defaultDataQueryKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { getRuntimePanelDataSource } from './layoutSerializers/utils';
export function transformV1ToV2AnnotationQuery(
annotation: AnnotationQuery,
dsType: string,
dsUID?: string,
// Overrides are used to provide properties based on scene's annotations data layer object state
override?: Partial<AnnotationQuery>
): AnnotationQueryKind {
const group = annotation.builtIn ? 'grafana' : dsType;
const {
// known properties documented in v1 schema
enable,
hide,
iconColor,
name,
builtIn,
filter,
mappings,
datasource,
target,
snapshotData,
type,
// unknown properties that are still available for configuration through API
...legacyOptions
} = annotation;
const result: AnnotationQueryKind = {
kind: 'AnnotationQuery',
spec: {
builtIn: Boolean(annotation.builtIn),
name: annotation.name,
enable: Boolean(override?.enable) || Boolean(annotation.enable),
hide: Boolean(override?.hide) || Boolean(annotation.hide),
iconColor: annotation.iconColor,
query: {
kind: 'DataQuery',
version: defaultDataQueryKind().version,
group, // Annotation layer has a datasource type provided in runtime.
spec: target || {},
},
},
};
if (dsUID) {
result.spec.query.datasource = {
name: dsUID,
};
}
// if legacy options is not an empty object, add it to the result
if (Object.keys(legacyOptions).length > 0) {
result.spec.legacyOptions = legacyOptions;
}
if (annotation.filter?.ids?.length) {
result.spec.filter = annotation.filter;
}
// TODO: add mappings
return result;
}
export function transformV2ToV1AnnotationQuery(annotation: AnnotationQueryKind): AnnotationQuery {
let { query: dataQuery, ...annotationQuery } = annotation.spec;
// Mapping from AnnotationQueryKind to AnnotationQuery used by scenes.
let annoQuerySpec: AnnotationQuery = {
enable: annotation.spec.enable,
hide: annotation.spec.hide,
iconColor: annotation.spec.iconColor,
name: annotation.spec.name,
// TOOO: mappings
};
if (Object.keys(dataQuery.spec).length > 0) {
// @ts-expect-error DataQueryKind spec should be typed as DataQuery interface
annoQuerySpec.target = {
...dataQuery?.spec,
};
}
if (annotation.spec.builtIn) {
annoQuerySpec.type = 'dashboard';
annoQuerySpec.builtIn = 1;
}
if (annotation.spec.filter) {
annoQuerySpec.filter = annotation.spec.filter;
}
// some annotations will contain in the legacyOptions properties that need to be
// added to the root level AnnotationQuery
if (annotationQuery.legacyOptions) {
annoQuerySpec = {
...annoQuerySpec,
...annotationQuery.legacyOptions,
};
}
// get data source from annotation query
const datasource = getRuntimePanelDataSource(dataQuery);
annoQuerySpec.datasource = datasource;
return annoQuerySpec;
}
@@ -1,7 +1,4 @@
import { import { PanelQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
defaultDataQueryKind,
PanelQueryKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { getRuntimePanelDataSource } from './utils'; import { getRuntimePanelDataSource } from './utils';
@@ -32,12 +29,6 @@ jest.mock('@grafana/runtime', () => ({
meta: { id: 'loki' }, meta: { id: 'loki' },
type: 'datasource', type: 'datasource',
}, },
'-- Grafana --': {
uid: 'grafana',
name: 'Grafana',
meta: { id: 'grafana' },
type: 'datasource',
},
}, },
}, },
}, },
@@ -51,23 +42,22 @@ describe('getRuntimePanelDataSource', () => {
spec: { spec: {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
datasource: {
uid: 'test-ds-uid',
type: 'test-ds-type',
},
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'prometheus-uid',
},
spec: {}, spec: {},
}, },
}, },
}; };
const result = getRuntimePanelDataSource(query.spec.query); const result = getRuntimePanelDataSource(query);
expect(result).toEqual({ expect(result).toEqual({
uid: 'prometheus-uid', uid: 'test-ds-uid',
type: 'prometheus', type: 'test-ds-type',
}); });
}); });
@@ -77,16 +67,15 @@ describe('getRuntimePanelDataSource', () => {
spec: { spec: {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
datasource: undefined,
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
spec: {}, spec: {},
}, },
}, },
}; };
const result = getRuntimePanelDataSource(query.spec.query); const result = getRuntimePanelDataSource(query);
expect(result).toEqual({ expect(result).toEqual({
uid: 'default-prometheus-uid', uid: 'default-prometheus-uid',
@@ -100,16 +89,15 @@ describe('getRuntimePanelDataSource', () => {
spec: { spec: {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
datasource: undefined,
query: { query: {
kind: 'DataQuery', kind: 'loki',
version: defaultDataQueryKind().version,
group: 'loki',
spec: {}, spec: {},
}, },
}, },
}; };
const result = getRuntimePanelDataSource(query.spec.query); const result = getRuntimePanelDataSource(query);
expect(result).toEqual({ expect(result).toEqual({
uid: 'loki-uid', uid: 'loki-uid',
@@ -124,16 +112,15 @@ describe('getRuntimePanelDataSource', () => {
spec: { spec: {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
datasource: undefined,
query: { query: {
kind: 'DataQuery', kind: 'unknown-type',
version: defaultDataQueryKind().version,
group: 'unknown-type',
spec: {}, spec: {},
}, },
}, },
}; };
const result = getRuntimePanelDataSource(query.spec.query); const result = getRuntimePanelDataSource(query);
expect(result).toEqual({ expect(result).toEqual({
uid: 'default-prometheus-uid', uid: 'default-prometheus-uid',
@@ -151,19 +138,18 @@ describe('getRuntimePanelDataSource', () => {
spec: { spec: {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
datasource: {
uid: '',
type: 'test-ds-type',
},
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: '',
},
spec: {}, spec: {},
}, },
}, },
}; };
const result = getRuntimePanelDataSource(query.spec.query); const result = getRuntimePanelDataSource(query);
expect(result).toEqual({ expect(result).toEqual({
uid: 'default-prometheus-uid', uid: 'default-prometheus-uid',
@@ -19,7 +19,6 @@ import {
PanelQueryKind, PanelQueryKind,
QueryVariableKind, QueryVariableKind,
TabsLayoutTabKind, TabsLayoutTabKind,
DataQueryKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
@@ -186,15 +185,12 @@ function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined {
panel.spec.data.spec.queries.forEach((query) => { panel.spec.data.spec.queries.forEach((query) => {
if (!datasource) { if (!datasource) {
if (!query.spec.query.datasource?.name) { if (!query.spec.datasource?.uid) {
datasource = getRuntimePanelDataSource(query.spec.query); datasource = getRuntimePanelDataSource(query);
} else { } else {
datasource = { datasource = query.spec.datasource;
uid: query.spec.query.datasource?.name,
type: query.spec.query.group,
};
} }
} else if (datasource.uid !== query.spec.query.datasource?.name || datasource.type !== query.spec.query.group) { } else if (datasource.uid !== query.spec.datasource?.uid || datasource.type !== query.spec.datasource?.type) {
isMixedDatasource = true; isMixedDatasource = true;
} }
}); });
@@ -203,19 +199,11 @@ function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined {
} }
export function getRuntimeVariableDataSource(variable: QueryVariableKind): DataSourceRef | undefined { export function getRuntimeVariableDataSource(variable: QueryVariableKind): DataSourceRef | undefined {
const ds: DataSourceRef = { return getDataSourceForQuery(variable.spec.datasource, variable.spec.query.kind);
uid: variable.spec.query.datasource?.name,
type: variable.spec.query.group,
};
return getDataSourceForQuery(ds, variable.spec.query.group);
} }
export function getRuntimePanelDataSource(query: DataQueryKind): DataSourceRef { export function getRuntimePanelDataSource(query: PanelQueryKind): DataSourceRef | undefined {
const ds: DataSourceRef = { return getDataSourceForQuery(query.spec.datasource, query.spec.query.kind);
uid: query.datasource?.name,
type: query.group,
};
return getDataSourceForQuery(ds, query.group);
} }
/** /**
@@ -223,7 +211,10 @@ export function getRuntimePanelDataSource(query: DataQueryKind): DataSourceRef {
* @param queryKind - The kind of query being performed * @param queryKind - The kind of query being performed
* @returns The resolved DataSourceRef * @returns The resolved DataSourceRef
*/ */
function getDataSourceForQuery(querySpecDS: DataSourceRef | undefined | null, queryKind: string): DataSourceRef { function getDataSourceForQuery(
querySpecDS: DataSourceRef | undefined | null,
queryKind: string
): DataSourceRef | undefined {
// If datasource is specified and has a uid, use it // If datasource is specified and has a uid, use it
if (querySpecDS?.uid) { if (querySpecDS?.uid) {
return querySpecDS; return querySpecDS;
@@ -262,22 +253,14 @@ function getDataSourceForQuery(querySpecDS: DataSourceRef | undefined | null, qu
}; };
} }
if (dsList && !dsList[defaultDatasource]) { // If we don't find a default datasource, return undefined
throw new Error(`Default datasource ${defaultDatasource} not found in datasource list`); return undefined;
}
// In the datasource list from bootData "id" is the type and the uid could be uid or the name
// in cases like grafana, dashboard or mixed datasource
return {
uid: dsList[defaultDatasource].uid || dsList[defaultDatasource].name,
type: dsList[defaultDatasource].meta.id,
};
} }
function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery { function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery {
return { return {
refId: query.spec.refId, refId: query.spec.refId,
datasource: getRuntimePanelDataSource(query.spec.query), datasource: getRuntimePanelDataSource(query),
hide: query.spec.hidden, hide: query.spec.hidden,
...query.spec.query.spec, ...query.spec.query.spec,
}; };
@@ -45,8 +45,8 @@ const getDataSourceMock = jest.fn();
const fakeDsMock: DataSourceApi = { const fakeDsMock: DataSourceApi = {
name: 'fake-std', name: 'fake-std',
type: 'fake-type', type: 'fake-std',
getRef: () => ({ type: 'fake-type', uid: 'fake-uid' }), getRef: () => ({ type: 'fake-std', uid: 'fake-std' }),
query: () => query: () =>
Promise.resolve({ Promise.resolve({
data: [], data: [],
@@ -74,7 +74,7 @@ const fakeDsMock: DataSourceApi = {
toDataQuery: (q) => ({ ...q, refId: 'FakeDataSource-refId' }), toDataQuery: (q) => ({ ...q, refId: 'FakeDataSource-refId' }),
}, },
id: 1, id: 1,
uid: 'fake-uid', uid: 'fake-std',
}; };
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
@@ -95,7 +95,7 @@ describe('sceneVariablesSetToVariables', () => {
description: 'test-desc', description: 'test-desc',
value: ['selected-value'], value: ['selected-value'],
text: ['selected-value-text'], text: ['selected-value-text'],
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
query: 'query', query: 'query',
includeAll: true, includeAll: true,
allowCustomValue: true, allowCustomValue: true,
@@ -123,8 +123,8 @@ describe('sceneVariablesSetToVariables', () => {
], ],
}, },
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"definition": undefined, "definition": undefined,
"description": "test-desc", "description": "test-desc",
@@ -148,7 +148,7 @@ describe('sceneVariablesSetToVariables', () => {
description: 'test-desc', description: 'test-desc',
value: ['selected-value'], value: ['selected-value'],
text: ['selected-value-text'], text: ['selected-value-text'],
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
query: 'query', query: 'query',
definition: 'query', definition: 'query',
includeAll: true, includeAll: true,
@@ -176,8 +176,8 @@ describe('sceneVariablesSetToVariables', () => {
], ],
}, },
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"definition": "query", "definition": "query",
"description": "test-desc", "description": "test-desc",
@@ -201,7 +201,7 @@ describe('sceneVariablesSetToVariables', () => {
description: 'test-desc', description: 'test-desc',
value: ['selected-value'], value: ['selected-value'],
text: ['selected-value-text'], text: ['selected-value-text'],
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
query: 'query', query: 'query',
options: [ options: [
{ label: 'test', value: 'test' }, { label: 'test', value: 'test' },
@@ -228,7 +228,7 @@ describe('sceneVariablesSetToVariables', () => {
description: 'test-desc', description: 'test-desc',
value: ['test'], value: ['test'],
text: ['test'], text: ['test'],
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
query: 'query', query: 'query',
options: [ options: [
{ label: 'test', value: 'test' }, { label: 'test', value: 'test' },
@@ -499,7 +499,7 @@ describe('sceneVariablesSetToVariables', () => {
allowCustomValue: true, allowCustomValue: true,
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
filters: [ filters: [
{ {
key: 'filterTest', key: 'filterTest',
@@ -533,8 +533,8 @@ describe('sceneVariablesSetToVariables', () => {
}, },
], ],
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"defaultKeys": undefined, "defaultKeys": undefined,
"description": "test-desc", "description": "test-desc",
@@ -662,7 +662,7 @@ describe('sceneVariablesSetToVariables', () => {
allowCustomValue: true, allowCustomValue: true,
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
defaultKeys: [ defaultKeys: [
{ {
text: 'some', text: 'some',
@@ -710,8 +710,8 @@ describe('sceneVariablesSetToVariables', () => {
}, },
], ],
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"defaultKeys": [ "defaultKeys": [
{ {
@@ -757,7 +757,7 @@ describe('sceneVariablesSetToVariables', () => {
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
allowCustomValue: true, allowCustomValue: true,
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
defaultOptions: [ defaultOptions: [
{ {
text: 'Foo', text: 'Foo',
@@ -784,8 +784,8 @@ describe('sceneVariablesSetToVariables', () => {
"value": [], "value": [],
}, },
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"defaultValue": undefined, "defaultValue": undefined,
"description": "test-desc", "description": "test-desc",
@@ -813,7 +813,7 @@ describe('sceneVariablesSetToVariables', () => {
name: 'test', name: 'test',
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
defaultOptions: [ defaultOptions: [
{ {
text: 'Foo', text: 'Foo',
@@ -841,7 +841,7 @@ describe('sceneVariablesSetToVariables', () => {
description: 'test-desc', description: 'test-desc',
value: ['selected-value'], value: ['selected-value'],
text: ['selected-value-text'], text: ['selected-value-text'],
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
query: 'query', query: 'query',
includeAll: true, includeAll: true,
allValue: 'test-all', allValue: 'test-all',
@@ -856,45 +856,44 @@ describe('sceneVariablesSetToVariables', () => {
expect(result).toHaveLength(1); expect(result).toHaveLength(1);
expect(result[0]).toMatchInlineSnapshot(` expect(result[0]).toMatchInlineSnapshot(`
{ {
"kind": "QueryVariable", "kind": "QueryVariable",
"spec": {
"allValue": "test-all",
"allowCustomValue": true,
"current": {
"text": [
"selected-value-text",
],
"value": [
"selected-value",
],
},
"datasource": {
"type": "fake-std",
"uid": "fake-std",
},
"definition": undefined,
"description": "test-desc",
"hide": "dontHide",
"includeAll": true,
"label": "test-label",
"multi": true,
"name": "test",
"options": [],
"query": {
"kind": "fake-std",
"spec": { "spec": {
"allValue": "test-all", "__legacyStringValue": "query",
"allowCustomValue": true,
"current": {
"text": [
"selected-value-text",
],
"value": [
"selected-value",
],
},
"definition": undefined,
"description": "test-desc",
"hide": "dontHide",
"includeAll": true,
"label": "test-label",
"multi": true,
"name": "test",
"options": [],
"query": {
"datasource": {
"name": "fake-uid",
},
"group": "fake-type",
"kind": "DataQuery",
"spec": {
"__legacyStringValue": "query",
},
"version": "v0",
},
"refresh": "onDashboardLoad",
"regex": "",
"skipUrlSync": false,
"sort": "disabled",
}, },
} },
`); "refresh": "onDashboardLoad",
"regex": "",
"skipUrlSync": false,
"sort": "disabled",
},
}
`);
}); });
it('should handle CustomVariable', () => { it('should handle CustomVariable', () => {
@@ -1153,7 +1152,7 @@ describe('sceneVariablesSetToVariables', () => {
name: 'test', name: 'test',
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
filters: [ filters: [
{ {
key: 'filterTest', key: 'filterTest',
@@ -1189,8 +1188,8 @@ describe('sceneVariablesSetToVariables', () => {
}, },
], ],
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"defaultKeys": [], "defaultKeys": [],
"description": "test-desc", "description": "test-desc",
@@ -1215,7 +1214,7 @@ describe('sceneVariablesSetToVariables', () => {
name: 'test', name: 'test',
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
defaultKeys: [ defaultKeys: [
{ {
text: 'some', text: 'some',
@@ -1265,8 +1264,8 @@ describe('sceneVariablesSetToVariables', () => {
}, },
], ],
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"defaultKeys": [ "defaultKeys": [
{ {
@@ -1313,7 +1312,7 @@ describe('sceneVariablesSetToVariables', () => {
name: 'test', name: 'test',
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
defaultOptions: [ defaultOptions: [
{ {
text: 'Foo', text: 'Foo',
@@ -1341,8 +1340,8 @@ describe('sceneVariablesSetToVariables', () => {
"value": [], "value": [],
}, },
"datasource": { "datasource": {
"type": "fake-type", "type": "fake-std",
"uid": "fake-uid", "uid": "fake-std",
}, },
"defaultValue": undefined, "defaultValue": undefined,
"description": "test-desc", "description": "test-desc",
@@ -1373,7 +1372,7 @@ describe('sceneVariablesSetToVariables', () => {
name: 'test', name: 'test',
label: 'test-label', label: 'test-label',
description: 'test-desc', description: 'test-desc',
datasource: { uid: 'fake-uid', type: 'fake-type' }, datasource: { uid: 'fake-std', type: 'fake-std' },
defaultOptions: [ defaultOptions: [
{ {
text: 'Foo', text: 'Foo',
@@ -24,7 +24,6 @@ import {
GroupByVariableKind, GroupByVariableKind,
defaultVariableHide, defaultVariableHide,
VariableOption, VariableOption,
defaultDataQueryKind,
AdHocFilterWithLabels, AdHocFilterWithLabels,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
@@ -295,30 +294,14 @@ export function sceneVariablesSetToSchemaV2Variables(
} }
const query = variable.state.query; const query = variable.state.query;
let dataQuery: DataQueryKind | string; let dataQuery: DataQueryKind | string;
const datasource = getElementDatasource(set, variable, 'variable', undefined, dsReferencesMapping);
if (typeof query !== 'string') { if (typeof query !== 'string') {
dataQuery = { dataQuery = {
kind: 'DataQuery', kind: variable.state.datasource?.type ?? getDataQueryKind(query),
version: defaultDataQueryKind().version,
group: datasource?.type ?? getDataQueryKind(query),
...(datasource?.uid && {
datasource: {
name: datasource.uid,
},
}),
spec: getDataQuerySpec(query), spec: getDataQuerySpec(query),
}; };
} else { } else {
dataQuery = { dataQuery = {
kind: 'DataQuery', kind: variable.state.datasource?.type ?? getDataQueryKind(query),
version: defaultDataQueryKind().version,
group: datasource?.type ?? getDataQueryKind(query),
...(datasource?.uid && {
datasource: {
name: datasource.uid,
},
}),
spec: { spec: {
[LEGACY_STRING_VALUE_KEY]: query, [LEGACY_STRING_VALUE_KEY]: query,
}, },
@@ -332,6 +315,7 @@ export function sceneVariablesSetToSchemaV2Variables(
options, options,
query: dataQuery, query: dataQuery,
definition: variable.state.definition, definition: variable.state.definition,
datasource: getElementDatasource(set, variable, 'variable', undefined, dsReferencesMapping),
sort: transformSortVariableToEnum(variable.state.sort), sort: transformSortVariableToEnum(variable.state.sort),
refresh: transformVariableRefreshToEnum(variable.state.refresh), refresh: transformVariableRefreshToEnum(variable.state.refresh),
regex: variable.state.regex, regex: variable.state.regex,
@@ -22,7 +22,6 @@ import {
CustomVariableKind, CustomVariableKind,
Spec as DashboardV2Spec, Spec as DashboardV2Spec,
DatasourceVariableKind, DatasourceVariableKind,
defaultDataQueryKind,
GridLayoutItemSpec, GridLayoutItemSpec,
GridLayoutSpec, GridLayoutSpec,
GroupByVariableKind, GroupByVariableKind,
@@ -139,7 +138,6 @@ describe('transformSaveModelSchemaV2ToScene', () => {
// Variables // Variables
const variables = scene.state?.$variables; const variables = scene.state?.$variables;
expect(variables?.state.variables).toHaveLength(dash.variables.length); expect(variables?.state.variables).toHaveLength(dash.variables.length);
validateVariable({ validateVariable({
sceneVariable: variables?.state.variables[0], sceneVariable: variables?.state.variables[0],
variableKind: dash.variables[0] as QueryVariableKind, variableKind: dash.variables[0] as QueryVariableKind,
@@ -208,26 +206,36 @@ describe('transformSaveModelSchemaV2ToScene', () => {
// Annotations // Annotations
expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet); expect(scene.state.$data).toBeInstanceOf(DashboardDataLayerSet);
const dataLayers = scene.state.$data as DashboardDataLayerSet; const dataLayers = scene.state.$data as DashboardDataLayerSet;
// we should get two annotations, Grafana built-in and the custom ones
expect(dataLayers.state.annotationLayers).toHaveLength(dash.annotations.length); expect(dataLayers.state.annotationLayers).toHaveLength(dash.annotations.length);
expect(dataLayers.state.annotationLayers[0].state.name).toBe(dash.annotations[0].spec.name); expect(dataLayers.state.annotationLayers).toHaveLength(5);
expect(dataLayers.state.annotationLayers[0].state.isEnabled).toBe(dash.annotations[0].spec.enable);
expect(dataLayers.state.annotationLayers[0].state.isHidden).toBe(dash.annotations[0].spec.hide); // Built-in
const builtInAnnotation = dataLayers.state.annotationLayers[0] as unknown as DashboardAnnotationsDataLayer;
expect(builtInAnnotation.state.name).toBe('Annotations & Alerts');
expect(builtInAnnotation.state.isEnabled).toBe(true);
expect(builtInAnnotation.state.isHidden).toBe(true);
expect(builtInAnnotation.state?.query.builtIn).toBe(1);
// Enabled // Enabled
expect(dataLayers.state.annotationLayers[1].state.name).toBe(dash.annotations[1].spec.name); expect(dataLayers.state.annotationLayers[1].state.name).toBe(dash.annotations[1].spec.name);
expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(dash.annotations[1].spec.enable); expect(dataLayers.state.annotationLayers[1].state.isEnabled).toBe(dash.annotations[1].spec.enable);
expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(dash.annotations[1].spec.hide); expect(dataLayers.state.annotationLayers[1].state.isHidden).toBe(dash.annotations[1].spec.hide);
// Disabled
expect(dataLayers.state.annotationLayers[2].state.name).toBe(dash.annotations[2].spec.name); expect(dataLayers.state.annotationLayers[2].state.name).toBe(dash.annotations[2].spec.name);
expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(dash.annotations[2].spec.enable); expect(dataLayers.state.annotationLayers[2].state.isEnabled).toBe(dash.annotations[2].spec.enable);
expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(dash.annotations[2].spec.hide); expect(dataLayers.state.annotationLayers[2].state.isHidden).toBe(dash.annotations[2].spec.hide);
// Hidden // Disabled
expect(dataLayers.state.annotationLayers[3].state.name).toBe(dash.annotations[3].spec.name); expect(dataLayers.state.annotationLayers[3].state.name).toBe(dash.annotations[3].spec.name);
expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(dash.annotations[3].spec.enable); expect(dataLayers.state.annotationLayers[3].state.isEnabled).toBe(dash.annotations[3].spec.enable);
expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(dash.annotations[3].spec.hide); expect(dataLayers.state.annotationLayers[3].state.isHidden).toBe(dash.annotations[3].spec.hide);
// Hidden
expect(dataLayers.state.annotationLayers[4].state.name).toBe(dash.annotations[4].spec.name);
expect(dataLayers.state.annotationLayers[4].state.isEnabled).toBe(dash.annotations[4].spec.enable);
expect(dataLayers.state.annotationLayers[4].state.isHidden).toBe(dash.annotations[4].spec.hide);
// VizPanel // VizPanel
const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels(); const vizPanels = (scene.state.body as DashboardLayoutManager).getVizPanels();
expect(vizPanels).toHaveLength(2); expect(vizPanels).toHaveLength(2);
@@ -271,14 +279,13 @@ describe('transformSaveModelSchemaV2ToScene', () => {
kind: 'PanelQuery', kind: 'PanelQuery',
spec: { spec: {
refId: 'A', refId: 'A',
datasource: {
type: 'graphite',
uid: 'datasource1',
},
hidden: false, hidden: false,
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'graphite',
datasource: {
name: 'datasource1',
},
spec: { spec: {
expr: 'test-query', expr: 'test-query',
}, },
@@ -300,14 +307,13 @@ describe('transformSaveModelSchemaV2ToScene', () => {
kind: 'PanelQuery', kind: 'PanelQuery',
spec: { spec: {
refId: 'A', refId: 'A',
datasource: {
type: 'prometheus',
uid: 'datasource1',
},
hidden: false, hidden: false,
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'datasource1',
},
spec: { spec: {
expr: 'test-query', expr: 'test-query',
}, },
@@ -334,12 +340,7 @@ describe('transformSaveModelSchemaV2ToScene', () => {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'abc123',
},
spec: { spec: {
expr: 'test-query', expr: 'test-query',
}, },
@@ -721,6 +722,10 @@ describe('transformSaveModelSchemaV2ToScene', () => {
enable: true, enable: true,
hide: false, hide: false,
iconColor: 'purple', iconColor: 'purple',
datasource: {
type: 'prometheus',
uid: 'abc123',
},
legacyOptions: { legacyOptions: {
expr: 'rate(http_requests_total[5m])', expr: 'rate(http_requests_total[5m])',
queryType: 'range', queryType: 'range',
@@ -728,15 +733,6 @@ describe('transformSaveModelSchemaV2ToScene', () => {
useValueAsTime: true, useValueAsTime: true,
step: '1m', step: '1m',
}, },
query: {
kind: 'DataQuery',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'abc123',
},
spec: {},
},
}, },
}, },
], ],
@@ -768,35 +764,23 @@ describe('transformSaveModelSchemaV2ToScene', () => {
// Get the annotation layers // Get the annotation layers
const dataLayerSet = scene.state.$data as DashboardDataLayerSet; const dataLayerSet = scene.state.$data as DashboardDataLayerSet;
expect(dataLayerSet).toBeDefined(); expect(dataLayerSet).toBeDefined();
// it should have two annotation layers, built-in and custom
expect(dataLayerSet.state.annotationLayers.length).toBe(2); expect(dataLayerSet.state.annotationLayers.length).toBe(2);
const defaultAnnotationLayer = dataLayerSet.state.annotationLayers[0] as DashboardAnnotationsDataLayer;
// Verify that the default annotation layer has been correctly initialized
expect(defaultAnnotationLayer.state.query).toEqual({
datasource: {
uid: '-- Grafana --',
type: 'grafana',
},
builtIn: 1,
enable: true,
iconColor: 'rgba(0, 211, 255, 1)',
name: 'Annotations & Alerts',
hide: true,
type: 'dashboard',
});
const annotationLayer = dataLayerSet.state.annotationLayers[1] as DashboardAnnotationsDataLayer; const annotationLayer = dataLayerSet.state.annotationLayers[1] as DashboardAnnotationsDataLayer;
// Verify that the legacyOptions have been merged into the query object // Verify that the legacyOptions have been merged into the query object
expect(annotationLayer.state.query).toMatchObject({ expect(annotationLayer.state.query).toMatchObject({
datasource: {
uid: 'abc123',
type: 'prometheus',
},
name: 'Annotation with legacy options', name: 'Annotation with legacy options',
enable: true, expr: 'rate(http_requests_total[5m])',
hide: false, queryType: 'range',
iconColor: 'purple', legendFormat: '{{method}} {{endpoint}}',
useValueAsTime: true,
step: '1m',
});
// Verify the original legacyOptions object is also preserved
expect(annotationLayer.state.query.legacyOptions).toMatchObject({
expr: 'rate(http_requests_total[5m])', expr: 'rate(http_requests_total[5m])',
queryType: 'range', queryType: 'range',
legendFormat: '{{method}} {{endpoint}}', legendFormat: '{{method}} {{endpoint}}',
@@ -28,7 +28,6 @@ import {
defaultAdhocVariableKind, defaultAdhocVariableKind,
defaultConstantVariableKind, defaultConstantVariableKind,
defaultCustomVariableKind, defaultCustomVariableKind,
defaultDataQueryKind,
defaultDatasourceVariableKind, defaultDatasourceVariableKind,
defaultGroupByVariableKind, defaultGroupByVariableKind,
defaultIntervalVariableKind, defaultIntervalVariableKind,
@@ -63,7 +62,6 @@ import { DashboardScene } from '../scene/DashboardScene';
import { DashboardLayoutManager } from '../scene/types/DashboardLayoutManager'; import { DashboardLayoutManager } from '../scene/types/DashboardLayoutManager';
import { getIntervalsFromQueryString } from '../utils/utils'; import { getIntervalsFromQueryString } from '../utils/utils';
import { transformV2ToV1AnnotationQuery } from './annotations';
import { SnapshotVariable } from './custom-variables/SnapshotVariable'; import { SnapshotVariable } from './custom-variables/SnapshotVariable';
import { layoutDeserializerRegistry } from './layoutSerializers/layoutSerializerRegistry'; import { layoutDeserializerRegistry } from './layoutSerializers/layoutSerializerRegistry';
import { getRuntimeVariableDataSource } from './layoutSerializers/utils'; import { getRuntimeVariableDataSource } from './layoutSerializers/utils';
@@ -92,23 +90,32 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
const { spec: dashboard, metadata, apiVersion } = dto; const { spec: dashboard, metadata, apiVersion } = dto;
// annotations might not come with the builtIn Grafana annotation, we need to add it // annotations might not come with the builtIn Grafana annotation, we need to add it
const grafanaBuiltAnnotation = getGrafanaBuiltInAnnotationDataLayer(dashboard); const grafanaBuiltAnnotation = getGrafanaBuiltInAnnotationDataLayer(dashboard);
if (grafanaBuiltAnnotation) { if (grafanaBuiltAnnotation) {
dashboard.annotations.unshift(grafanaBuiltAnnotation); dashboard.annotations.unshift(grafanaBuiltAnnotation);
} }
const annotationLayers = dashboard.annotations.map((annotation) => { const annotationLayers = dashboard.annotations.map((annotation) => {
const annotationQuerySpec = transformV2ToV1AnnotationQuery(annotation); let annoQuerySpec = annotation.spec;
// some annotations will contain in the legacyOptions properties that need to be
const layerState = { // added to the root level annotation spec
if (annoQuerySpec?.legacyOptions) {
annoQuerySpec = {
...annoQuerySpec,
...annoQuerySpec.legacyOptions,
};
}
return new DashboardAnnotationsDataLayer({
key: uniqueId('annotations-'), key: uniqueId('annotations-'),
query: annotationQuerySpec, query: {
...annoQuerySpec,
builtIn: annotation.spec.builtIn ? 1 : 0,
},
name: annotation.spec.name, name: annotation.spec.name,
isEnabled: Boolean(annotation.spec.enable), isEnabled: Boolean(annotation.spec.enable),
isHidden: Boolean(annotation.spec.hide), isHidden: Boolean(annotation.spec.hide),
}; });
return new DashboardAnnotationsDataLayer(layerState);
}); });
const isDashboardEditable = Boolean(dashboard.editable); const isDashboardEditable = Boolean(dashboard.editable);
@@ -507,15 +514,7 @@ function getGrafanaBuiltInAnnotationDataLayer(dashboard: DashboardV2Spec) {
const grafanaBuiltAnnotation: AnnotationQueryKind = { const grafanaBuiltAnnotation: AnnotationQueryKind = {
kind: 'AnnotationQuery', kind: 'AnnotationQuery',
spec: { spec: {
query: { datasource: { uid: '-- Grafana --', type: 'grafana' },
kind: 'DataQuery',
version: defaultDataQueryKind().version,
group: 'grafana',
datasource: {
name: '-- Grafana --',
},
spec: {},
},
name: 'Annotations & Alerts', name: 'Annotations & Alerts',
iconColor: DEFAULT_ANNOTATION_COLOR, iconColor: DEFAULT_ANNOTATION_COLOR,
enable: true, enable: true,
@@ -57,7 +57,6 @@ import {
validateDashboardSchemaV2, validateDashboardSchemaV2,
getDataQueryKind, getDataQueryKind,
getAutoAssignedDSRef, getAutoAssignedDSRef,
getVizPanelQueries,
} from './transformSceneToSaveModelSchemaV2'; } from './transformSceneToSaveModelSchemaV2';
// Mock dependencies // Mock dependencies
@@ -411,7 +410,9 @@ describe('transformSceneToSaveModelSchemaV2', () => {
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
// Check that the annotation layers are correctly transformed // Check that the annotation layers are correctly transformed
expect(result.annotations).toHaveLength(2); expect(result.annotations).toHaveLength(3);
// Check annotation layer 3 without initial data source isn't updated with runtime default
expect(result.annotations?.[2].spec.datasource?.type).toBe(undefined);
}); });
it('should transform the minimum scene to save model schema v2', () => { it('should transform the minimum scene to save model schema v2', () => {
@@ -561,8 +562,16 @@ describe('transformSceneToSaveModelSchemaV2', () => {
name: 'annotation-with-options', name: 'annotation-with-options',
enable: true, enable: true,
iconColor: 'red', iconColor: 'red',
customProp1: true, legacyOptions: {
customProp2: 'test', expr: 'rate(http_requests_total[5m])',
queryType: 'range',
legendFormat: '{{method}} {{endpoint}}',
useValueAsTime: true,
},
// Some other properties that aren't in the annotation spec
// and should be moved to options
customProp1: 'value1',
customProp2: 'value2',
}, },
name: 'layerWithOptions', name: 'layerWithOptions',
isEnabled: true, isEnabled: true,
@@ -584,11 +593,19 @@ describe('transformSceneToSaveModelSchemaV2', () => {
expect(result.annotations.length).toBe(1); expect(result.annotations.length).toBe(1);
expect(result.annotations[0].spec.legacyOptions).toBeDefined(); expect(result.annotations[0].spec.legacyOptions).toBeDefined();
expect(result.annotations[0].spec.legacyOptions).toEqual({ expect(result.annotations[0].spec.legacyOptions).toEqual({
customProp1: true, expr: 'rate(http_requests_total[5m])',
customProp2: 'test', queryType: 'range',
legendFormat: '{{method}} {{endpoint}}',
useValueAsTime: true,
customProp1: 'value1',
customProp2: 'value2',
}); });
// Ensure these properties are not at the root level // Ensure these properties are not at the root level
expect(result).not.toHaveProperty('annotations[0].spec.expr');
expect(result).not.toHaveProperty('annotations[0].spec.queryType');
expect(result).not.toHaveProperty('annotations[0].spec.legendFormat');
expect(result).not.toHaveProperty('annotations[0].spec.useValueAsTime');
expect(result).not.toHaveProperty('annotations[0].spec.customProp1'); expect(result).not.toHaveProperty('annotations[0].spec.customProp1');
expect(result).not.toHaveProperty('annotations[0].spec.customProp2'); expect(result).not.toHaveProperty('annotations[0].spec.customProp2');
}); });
@@ -817,50 +834,6 @@ describe('getElementDatasource', () => {
}); });
}); });
describe('getVizPanelQueries', () => {
it('should handle panel query datasources correctly', () => {
const queryWithDS: SceneDataQuery = {
refId: 'B',
datasource: { uid: 'prometheus-uid', type: 'prometheus' },
};
const queryWithoutDS: SceneDataQuery = {
refId: 'A',
};
// Mock query runner
const queryRunner = new SceneQueryRunner({
queries: [queryWithoutDS, queryWithDS],
datasource: { uid: 'default-ds', type: 'default' },
});
// Create test elements
const vizPanel = new VizPanel({
key: 'panel-1',
pluginId: 'timeseries',
$data: queryRunner,
});
// Mock dsReferencesMapping
const dsReferencesMapping = {
panels: new Map(new Set([['panel-1', new Set<string>(['A'])]])),
variables: new Set<string>(),
annotations: new Set<string>(),
};
const result = getVizPanelQueries(vizPanel, dsReferencesMapping);
expect(result.length).toBe(2);
expect(result[0].spec.query.kind).toBe('DataQuery');
expect(result[0].spec.query.datasource).toBeUndefined(); // ignore datasource if it wasn't provided
expect(result[0].spec.query.group).toBe('default');
expect(result[0].spec.query.version).toBe('v0');
expect(result[1].spec.query.kind).toBe('DataQuery');
expect(result[1].spec.query.datasource?.name).toBe('prometheus-uid');
expect(result[1].spec.query.group).toBe('prometheus');
expect(result[1].spec.query.version).toBe('v0');
});
});
function getMinimalSceneState(body: DashboardLayoutManager): Partial<DashboardSceneState> { function getMinimalSceneState(body: DashboardLayoutManager): Partial<DashboardSceneState> {
return { return {
id: 1, id: 1,
@@ -1092,6 +1065,18 @@ function createAnnotationLayers() {
isEnabled: true, isEnabled: true,
isHidden: true, isHidden: true,
}), }),
// this could happen if a dahboard was created from code and the datasource was not defined
new DashboardAnnotationsDataLayer({
key: 'layer3',
query: {
name: 'query3',
enable: true,
iconColor: 'green',
},
name: 'layer3',
isEnabled: true,
isHidden: true,
}),
]; ];
} }
@@ -43,7 +43,6 @@ import {
DashboardCursorSync, DashboardCursorSync,
FieldConfig, FieldConfig,
FieldColor, FieldColor,
defaultDataQueryKind,
} from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen'; } from '../../../../../packages/grafana-schema/src/schema/dashboard/v2alpha1/types.spec.gen';
import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet';
import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene';
@@ -52,7 +51,6 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getLibraryPanelBehavior, getPanelIdForVizPanel, getQueryRunnerFor, isLibraryPanel } from '../utils/utils'; import { getLibraryPanelBehavior, getPanelIdForVizPanel, getQueryRunnerFor, isLibraryPanel } from '../utils/utils';
import { DSReferencesMapping } from './DashboardSceneSerializer'; import { DSReferencesMapping } from './DashboardSceneSerializer';
import { transformV1ToV2AnnotationQuery } from './annotations';
import { sceneVariablesSetToSchemaV2Variables } from './sceneVariablesSetToVariables'; import { sceneVariablesSetToSchemaV2Variables } from './sceneVariablesSetToVariables';
import { colorIdEnumToColorIdV2, transformCursorSynctoEnum } from './transformToV2TypesUtils'; import { colorIdEnumToColorIdV2, transformCursorSynctoEnum } from './transformToV2TypesUtils';
@@ -252,7 +250,7 @@ function getPanelLinks(panel: VizPanel): DataLink[] {
return []; return [];
} }
export function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSReferencesMapping): PanelQueryKind[] { function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSReferencesMapping): PanelQueryKind[] {
const queries: PanelQueryKind[] = []; const queries: PanelQueryKind[] = [];
const queryRunner = getQueryRunnerFor(vizPanel); const queryRunner = getQueryRunnerFor(vizPanel);
const vizPanelQueries = queryRunner?.state.queries; const vizPanelQueries = queryRunner?.state.queries;
@@ -260,22 +258,12 @@ export function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSR
if (vizPanelQueries) { if (vizPanelQueries) {
vizPanelQueries.forEach((query) => { vizPanelQueries.forEach((query) => {
const queryDatasource = getElementDatasource(vizPanel, query, 'panel', queryRunner, dsReferencesMapping); const queryDatasource = getElementDatasource(vizPanel, query, 'panel', queryRunner, dsReferencesMapping);
const dataQuery: DataQueryKind = { const dataQuery: DataQueryKind = {
kind: 'DataQuery', kind: getDataQueryKind(query, queryRunner),
version: defaultDataQueryKind().version,
group: getDataQueryKind(query, queryRunner),
datasource: {
name: queryDatasource?.uid,
},
spec: omit(query, 'datasource', 'refId', 'hide'), spec: omit(query, 'datasource', 'refId', 'hide'),
}; };
if (!dataQuery.datasource?.name) {
delete dataQuery.datasource;
}
const querySpec: PanelQuerySpec = { const querySpec: PanelQuerySpec = {
datasource: queryDatasource,
query: dataQuery, query: dataQuery,
refId: query.refId, refId: query.refId,
hidden: Boolean(query.hide), hidden: Boolean(query.hide),
@@ -419,46 +407,78 @@ function getAnnotations(state: DashboardSceneState, dsReferencesMapping?: DSRefe
if (!(layer instanceof dataLayers.AnnotationsDataLayer)) { if (!(layer instanceof dataLayers.AnnotationsDataLayer)) {
continue; continue;
} }
const result: AnnotationQueryKind = {
kind: 'AnnotationQuery',
spec: {
builtIn: Boolean(layer.state.query.builtIn),
name: layer.state.query.name,
datasource: getElementDatasource(layer, layer.state.query, 'annotation', undefined, dsReferencesMapping),
enable: Boolean(layer.state.isEnabled),
hide: Boolean(layer.state.isHidden),
iconColor: layer.state.query.iconColor,
},
};
const datasource = getElementDatasource(layer, layer.state.query, 'annotation', undefined, dsReferencesMapping); // Transform v1 dashboard (using target) to v2 structure
// adds extra condition to prioritize query over target
let layerDs = layer.state.query.datasource; // if query is defined, use it
if (layer.state.query.target && !layer.state.query.query) {
if (!layerDs) { // Handle built-in annotations
// This can happen only if we are transforming a scene that was created if (layer.state.query.builtIn) {
// from a v1 spec. In v1 annotation layer can contain no datasource ref, which is guaranteed result.spec.query = {
// for layers created for v2 schema. See transform transformSaveModelSchemaV2ToScene.ts. kind: 'grafana', // built-in annotations are always of type grafana
// In this case we will resolve default data source spec: {
layerDs = getDefaultDataSourceRef(); ...layer.state.query.target,
console.error( },
'Misconfigured AnnotationsDataLayer: Data source is required for annotations. Resolving default data source', };
layer, } else {
layerDs result.spec.query = {
); kind: getAnnotationQueryKind(layer.state.query),
spec: {
...layer.state.query.target,
},
};
}
} }
// For annotations without query.query defined (e.g., grafana annotations without tags)
else if (layer.state.query.query?.kind) {
result.spec.query = {
kind: layer.state.query.query.kind,
spec: {
...layer.state.query.query.spec,
},
};
}
// Collect datasource-specific properties not in standard annotation spec
let otherProps = omit(
layer.state.query,
'type',
'target',
'builtIn',
'name',
'datasource',
'iconColor',
'enable',
'hide',
'filter',
'query'
);
const result = transformV1ToV2AnnotationQuery(layer.state.query, layerDs.type!, layerDs.uid!, { // Store extra properties in the legacyOptions field instead of directly in the spec
enable: layer.state.isEnabled, if (Object.keys(otherProps).length > 0) {
hide: layer.state.isHidden, // Extract options property and get the rest of the properties
}); const { legacyOptions, ...restProps } = otherProps;
// Merge options with the rest of the properties
const annotationQuery = layer.state.query; result.spec.legacyOptions = { ...legacyOptions, ...restProps };
}
// If filter is an empty array, don't save it // If filter is an empty array, don't save it
if (annotationQuery.filter?.ids?.length) { if (layer.state.query.filter?.ids?.length) {
result.spec.filter = annotationQuery.filter; result.spec.filter = layer.state.query.filter;
}
// Finally, if the datasource references mapping did not containt data source ref,
// this means that the original model that was fetched did not contain it. In such scenario we don't want to save
// the explicit data source reference, so lets remove it from the save model.
if (!datasource) {
delete result.spec.query.datasource;
} }
annotations.push(result); annotations.push(result);
} }
return annotations; return annotations;
} }
@@ -22,8 +22,8 @@ export default function ShareButton({ dashboard, panel }: { dashboard: Dashboard
const [_, buildUrl] = useAsyncFn(async () => { const [_, buildUrl] = useAsyncFn(async () => {
DashboardInteractions.toolbarShareClick(); DashboardInteractions.toolbarShareClick();
return await buildShareUrl(dashboard, panel); await buildShareUrl(dashboard, panel);
}, [dashboard]); }, [dashboard, panel]);
const onMenuClick = useCallback((isOpen: boolean) => { const onMenuClick = useCallback((isOpen: boolean) => {
if (isOpen) { if (isOpen) {
@@ -5,7 +5,6 @@ import { locationService, reportInteraction } from '@grafana/runtime';
import { import {
AnnotationQueryKind, AnnotationQueryKind,
Spec as DashboardV2Spec, Spec as DashboardV2Spec,
defaultDataQueryKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { Form } from 'app/core/components/Form/Form'; import { Form } from 'app/core/components/Form/Form';
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
@@ -17,8 +16,6 @@ import { ImportDashboardFormV2 } from './ImportDashboardFormV2';
const IMPORT_FINISHED_EVENT_NAME = 'dashboard_import_imported'; const IMPORT_FINISHED_EVENT_NAME = 'dashboard_import_imported';
type FormData = SaveDashboardCommand<DashboardV2Spec> & { [key: `datasource-${string}`]: string };
export function ImportDashboardOverviewV2() { export function ImportDashboardOverviewV2() {
const [uidReset, setUidReset] = useState(false); const [uidReset, setUidReset] = useState(false);
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -37,29 +34,24 @@ export function ImportDashboardOverviewV2() {
dispatch(clearLoadedDashboard()); dispatch(clearLoadedDashboard());
} }
async function onSubmit(form: FormData) { async function onSubmit(form: SaveDashboardCommand<DashboardV2Spec>) {
reportInteraction(IMPORT_FINISHED_EVENT_NAME); reportInteraction(IMPORT_FINISHED_EVENT_NAME);
const dashboardWithDataSources: DashboardV2Spec = { const dashboardWithDataSources: DashboardV2Spec = {
...dashboard, ...dashboard,
title: form.dashboard.title, title: form.dashboard.title,
annotations: dashboard.annotations?.map((annotation: AnnotationQueryKind) => { annotations: dashboard.annotations?.map((annotation: AnnotationQueryKind) => {
const dsType = annotation.spec.query?.spec.group; if (annotation.spec.query?.kind) {
if (dsType) { const dsType = annotation.spec.query.kind;
if (form[`datasource-${dsType}` as keyof typeof form]) { if (form[`datasource-${dsType}` as keyof typeof form]) {
const ds = form[`datasource-${dsType}` as keyof typeof form] as { uid: string; type: string }; const ds = form[`datasource-${dsType}` as keyof typeof form] as { uid: string; type: string };
return { return {
...annotation, ...annotation,
spec: { spec: {
...annotation.spec, ...annotation.spec,
query: { datasource: {
kind: 'DataQuery', uid: ds.uid,
group: dsType, type: ds.type,
version: defaultDataQueryKind().version,
datasource: { name: ds.uid },
spec: {
...annotation.spec.query?.spec,
},
}, },
}, },
}; };
@@ -69,23 +61,18 @@ export function ImportDashboardOverviewV2() {
}), }),
variables: dashboard.variables?.map((variable) => { variables: dashboard.variables?.map((variable) => {
if (variable.kind === 'QueryVariable') { if (variable.kind === 'QueryVariable') {
const dsType = variable.spec.query?.spec.group; if (variable.spec.query?.kind) {
if (dsType) { const dsType = variable.spec.query.kind;
if (form[`datasource-${dsType}` as keyof typeof form]) { if (form[`datasource-${dsType}` as keyof typeof form]) {
const ds = form[`datasource-${dsType}` as keyof typeof form] as { uid: string; type: string }; const ds = form[`datasource-${dsType}` as keyof typeof form] as { uid: string; type: string };
return { return {
...variable, ...variable,
spec: { spec: {
...variable.spec, ...variable.spec,
query: { datasource: {
...variable.spec.query, ...variable.spec.datasource,
spec: { uid: ds.uid,
...variable.spec.query.spec, type: ds.type,
group: ds.type,
datasource: {
name: ds.uid,
},
},
}, },
options: [], options: [],
current: { current: {
@@ -170,7 +157,7 @@ export function ImportDashboardOverviewV2() {
return ( return (
<> <>
<Form<FormData> <Form<SaveDashboardCommand<DashboardV2Spec> & { [key: `datasource-${string}`]: string }>
onSubmit={onSubmit} onSubmit={onSubmit}
defaultValues={{ dashboard, k8s: { annotations: { 'grafana.app/folder': folder.uid } } }} defaultValues={{ dashboard, k8s: { annotations: { 'grafana.app/folder': folder.uid } } }}
validateOnMount validateOnMount
@@ -52,8 +52,7 @@ export function validateVariable<
expect(sceneVariable?.state.pluginId).toBe(variableKind.spec.pluginId); expect(sceneVariable?.state.pluginId).toBe(variableKind.spec.pluginId);
} }
if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') { if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') {
expect(sceneVariable?.state.datasource?.type).toBe(variableKind.spec.query?.group); expect(sceneVariable?.state.datasource).toBe(variableKind.spec.datasource);
expect(sceneVariable?.state.datasource?.uid).toBe(variableKind.spec.query?.datasource?.name);
expect(sceneVariable?.state.query).toEqual(variableKind.spec.query.spec); expect(sceneVariable?.state.query).toEqual(variableKind.spec.query.spec);
} }
if (sceneVariable instanceof CustomVariable && variableKind.kind === 'CustomVariable') { if (sceneVariable instanceof CustomVariable && variableKind.kind === 'CustomVariable') {
@@ -2,7 +2,6 @@ import { AnnotationQuery, DataQuery, VariableModel, VariableRefresh, Panel } fro
import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2_examples'; import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2_examples';
import { import {
Spec as DashboardV2Spec, Spec as DashboardV2Spec,
defaultDataQueryKind,
GridLayoutItemKind, GridLayoutItemKind,
GridLayoutKind, GridLayoutKind,
PanelKind, PanelKind,
@@ -467,14 +466,10 @@ describe('ResponseTransformers', () => {
{ {
kind: 'PanelQuery', kind: 'PanelQuery',
spec: { spec: {
datasource: 'datasource1',
hidden: false, hidden: false,
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
datasource: {
name: 'datasource1',
},
spec: { spec: {
expr: 'test-query', expr: 'test-query',
}, },
@@ -978,9 +973,11 @@ describe('ResponseTransformers', () => {
result.forEach((query) => { result.forEach((query) => {
expect(query.kind).toBe('PanelQuery'); expect(query.kind).toBe('PanelQuery');
expect(query.spec.query.group).toEqual('theoretical-ds'); expect(query.spec.datasource).toEqual({
expect(query.spec.query.datasource?.name).toEqual('theoretical-uid'); type: 'theoretical-ds',
expect(query.spec.query.kind).toBe('DataQuery'); uid: 'theoretical-uid',
});
expect(query.spec.query.kind).toBe('theoretical-ds');
}); });
}); });
@@ -1006,9 +1003,11 @@ describe('ResponseTransformers', () => {
result.forEach((query) => { result.forEach((query) => {
expect(query.kind).toBe('PanelQuery'); expect(query.kind).toBe('PanelQuery');
expect(query.spec.query.group).toEqual('theoretical-ds'); expect(query.spec.datasource).toEqual({
expect(query.spec.query.datasource?.name).toEqual('theoretical-uid'); type: 'theoretical-ds',
expect(query.spec.query.kind).toBe('DataQuery'); uid: 'theoretical-uid',
});
expect(query.spec.query.kind).toBe('theoretical-ds');
}); });
}); });
}); });
@@ -1016,14 +1015,14 @@ describe('ResponseTransformers', () => {
function validateAnnotation(v1: AnnotationQuery, v2: DashboardV2Spec['annotations'][0]) { function validateAnnotation(v1: AnnotationQuery, v2: DashboardV2Spec['annotations'][0]) {
const { spec: v2Spec } = v2; const { spec: v2Spec } = v2;
expect(v1.name).toBe(v2Spec.name); expect(v1.name).toBe(v2Spec.name);
expect(v1.datasource?.type).toBe(v2Spec.query.group); expect(v1.datasource).toBe(v2Spec.datasource);
expect(v1.datasource?.uid).toBe(v2Spec.query.datasource?.name);
expect(v1.enable).toBe(v2Spec.enable); expect(v1.enable).toBe(v2Spec.enable);
expect(v1.hide).toBe(v2Spec.hide); expect(v1.hide).toBe(v2Spec.hide);
expect(v1.iconColor).toBe(v2Spec.iconColor); expect(v1.iconColor).toBe(v2Spec.iconColor);
expect(v1.builtIn).toBe(v2Spec.builtIn ? 1 : undefined); expect(v1.builtIn).toBe(v2Spec.builtIn ? 1 : 0);
expect(v1.target).toEqual(v2Spec.query.spec); expect(v1.target).toBe(v2Spec.query?.spec);
expect(v1.filter).toEqual(v2Spec.filter); expect(v1.filter).toEqual(v2Spec.filter);
} }
@@ -1044,10 +1043,7 @@ describe('ResponseTransformers', () => {
return { return {
refId: q.spec.refId, refId: q.spec.refId,
hide: q.spec.hidden, hide: q.spec.hidden,
datasource: { datasource: q.spec.datasource,
type: q.spec.query.spec.group,
uid: q.spec.query.spec.datasource?.uid,
},
...q.spec.query.spec, ...q.spec.query.spec,
}; };
}) })
@@ -1097,24 +1093,18 @@ describe('ResponseTransformers', () => {
}; };
expect(v2Common).toEqual(v1Common); expect(v2Common).toEqual(v1Common);
if (v2.kind === 'QueryVariable') { if (v2.kind === 'QueryVariable') {
expect(v2.spec.query).toMatchObject({ expect(v2.spec.datasource).toEqual(v1.datasource);
kind: 'DataQuery',
version: defaultDataQueryKind().version,
group: (v1.datasource?.type || getDefaultDataSourceRef()?.type) ?? 'grafana',
...(v1.datasource?.uid && {
datasource: {
name: v1.datasource?.uid,
},
}),
});
if (typeof v1.query === 'string') { if (typeof v1.query === 'string') {
expect(v2.spec.query.spec).toEqual({ expect(v2.spec.query.spec[LEGACY_STRING_VALUE_KEY]).toEqual(v1.query);
[LEGACY_STRING_VALUE_KEY]: v1.query,
});
} else { } else {
expect(v2.spec.query.spec).toEqual({ expect(v2.spec.query).toEqual({
...(typeof v1.query === 'object' ? v1.query : {}), kind: v1.datasource?.type,
spec: {
...(typeof v1.query === 'object' ? v1.query : {}),
},
}); });
} }
} }
@@ -38,7 +38,6 @@ import {
LibraryPanelKind, LibraryPanelKind,
PanelKind, PanelKind,
GridLayoutItemKind, GridLayoutItemKind,
defaultDataQueryKind,
RowsLayoutRowKind, RowsLayoutRowKind,
GridLayoutKind, GridLayoutKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
@@ -56,7 +55,6 @@ import {
DeprecatedInternalId, DeprecatedInternalId,
ObjectMeta, ObjectMeta,
} from 'app/features/apiserver/types'; } from 'app/features/apiserver/types';
import { transformV2ToV1AnnotationQuery } from 'app/features/dashboard-scene/serialization/annotations';
import { GRID_ROW_HEIGHT } from 'app/features/dashboard-scene/serialization/const'; import { GRID_ROW_HEIGHT } from 'app/features/dashboard-scene/serialization/const';
import { validateFiltersOrigin } from 'app/features/dashboard-scene/serialization/sceneVariablesSetToVariables'; import { validateFiltersOrigin } from 'app/features/dashboard-scene/serialization/sceneVariablesSetToVariables';
import { TypedVariableModelV2 } from 'app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene'; import { TypedVariableModelV2 } from 'app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene';
@@ -504,13 +502,9 @@ export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourc
spec: { spec: {
refId: t.refId, refId: t.refId,
hidden: t.hide ?? false, hidden: t.hide ?? false,
datasource: t.datasource ? t.datasource : panelDatasource,
query: { query: {
kind: 'DataQuery', kind: t.datasource?.type || panelDatasource.type!,
version: defaultDataQueryKind().version,
group: t.datasource?.type || panelDatasource.type!,
datasource: {
name: t.datasource?.uid || panelDatasource.uid!,
},
spec: { spec: {
...query, ...query,
}, },
@@ -574,12 +568,7 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables']
regex: v.regex || '', regex: v.regex || '',
sort: transformSortVariableToEnum(v.sort), sort: transformSortVariableToEnum(v.sort),
query: { query: {
kind: 'DataQuery', kind: v.datasource?.type || getDefaultDatasourceType(),
version: defaultDataQueryKind().version,
group: v.datasource?.type ?? getDefaultDatasourceType(),
datasource: {
name: v.datasource?.uid,
},
spec: query, spec: query,
}, },
allowCustomValue: v.allowCustomValue ?? true, allowCustomValue: v.allowCustomValue ?? true,
@@ -733,12 +722,7 @@ function getAnnotations(annotations: AnnotationQuery[]): DashboardV2Spec['annota
iconColor: a.iconColor, iconColor: a.iconColor,
builtIn: Boolean(a.builtIn), builtIn: Boolean(a.builtIn),
query: { query: {
kind: 'DataQuery', kind: a.datasource?.type || getDefaultDatasourceType(),
version: defaultDataQueryKind().version,
group: a.datasource?.type || getDefaultDatasourceType(),
datasource: {
name: a.datasource?.uid,
},
spec: { spec: {
...a.target, ...a.target,
}, },
@@ -773,10 +757,7 @@ function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] {
LEGACY_STRING_VALUE_KEY in v.spec.query.spec LEGACY_STRING_VALUE_KEY in v.spec.query.spec
? v.spec.query.spec[LEGACY_STRING_VALUE_KEY] ? v.spec.query.spec[LEGACY_STRING_VALUE_KEY]
: v.spec.query.spec, : v.spec.query.spec,
datasource: { datasource: v.spec.datasource,
type: v.spec.query?.spec.group,
uid: v.spec.query?.spec.datasource?.name,
},
sort: transformSortVariableToEnumV1(v.spec.sort), sort: transformSortVariableToEnumV1(v.spec.sort),
refresh: transformVariableRefreshToEnumV1(v.spec.refresh), refresh: transformVariableRefreshToEnumV1(v.spec.refresh),
regex: v.spec.regex, regex: v.spec.regex,
@@ -893,6 +874,22 @@ function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] {
return variables; return variables;
} }
function getAnnotationsV1(annotations: DashboardV2Spec['annotations']): AnnotationQuery[] {
// @ts-expect-error - target v2 query is not compatible with v1 target
return annotations.map((a) => {
return {
name: a.spec.name,
datasource: a.spec.datasource,
enable: a.spec.enable,
hide: a.spec.hide,
iconColor: a.spec.iconColor,
builtIn: a.spec.builtIn ? 1 : 0,
target: a.spec.query?.spec,
filter: a.spec.filter,
};
});
}
interface LibraryPanelDTO extends Pick<Panel, 'libraryPanel' | 'id' | 'title' | 'gridPos' | 'type'> {} interface LibraryPanelDTO extends Pick<Panel, 'libraryPanel' | 'id' | 'title' | 'gridPos' | 'type'> {}
function getPanelsV1( function getPanelsV1(
@@ -953,10 +950,7 @@ function transformV2PanelToV1Panel(
return { return {
refId: q.spec.refId, refId: q.spec.refId,
hide: q.spec.hidden, hide: q.spec.hidden,
datasource: { datasource: q.spec.datasource,
uid: q.spec.query.spec.datasource?.uid,
type: q.spec.query.spec.group,
},
...q.spec.query.spec, ...q.spec.query.spec,
}; };
}), }),
@@ -1140,8 +1134,7 @@ function transformToV1VariableTypes(variable: TypedVariableModelV2): VariableTyp
} }
export function transformDashboardV2SpecToV1(spec: DashboardV2Spec, metadata: ObjectMeta): DashboardDataDTO { export function transformDashboardV2SpecToV1(spec: DashboardV2Spec, metadata: ObjectMeta): DashboardDataDTO {
const annotations = spec.annotations.map(transformV2ToV1AnnotationQuery); const annotations = getAnnotationsV1(spec.annotations);
const variables = getVariablesV1(spec.variables); const variables = getVariablesV1(spec.variables);
const panels = getPanelsV1(spec.elements, spec.layout); const panels = getPanelsV1(spec.elements, spec.layout);
return { return {
@@ -154,6 +154,7 @@ export const InfiniteScroll = ({
displayedFields={displayedFields} displayedFields={displayedFields}
index={index} index={index}
log={logs[index]} log={logs[index]}
logs={logs}
onClick={onClick} onClick={onClick}
showTime={showTime} showTime={showTime}
style={style} style={style}
@@ -45,6 +45,7 @@ describe.each(fontSizes)('LogLine', (fontSize: LogListFontSize) => {
displayedFields: [], displayedFields: [],
index: 0, index: 0,
log, log,
logs: [log],
onClick: jest.fn(), onClick: jest.fn(),
showTime: true, showTime: true,
style: {}, style: {},
@@ -27,6 +27,7 @@ export interface Props {
displayedFields: string[]; displayedFields: string[];
index: number; index: number;
log: LogListModel; log: LogListModel;
logs: LogListModel[];
showTime: boolean; showTime: boolean;
style: CSSProperties; style: CSSProperties;
styles: LogLineStyles; styles: LogLineStyles;
@@ -41,6 +42,7 @@ export const LogLine = ({
displayedFields, displayedFields,
index, index,
log, log,
logs,
style, style,
styles, styles,
onClick, onClick,
@@ -57,6 +59,7 @@ export const LogLine = ({
height={style.height} height={style.height}
index={index} index={index}
log={log} log={log}
logs={logs}
styles={styles} styles={styles}
onClick={onClick} onClick={onClick}
onOverflow={onOverflow} onOverflow={onOverflow}
@@ -79,6 +82,7 @@ const LogLineComponent = memo(
height, height,
index, index,
log, log,
logs,
styles, styles,
onClick, onClick,
onOverflow, onOverflow,
@@ -237,7 +241,7 @@ const LogLineComponent = memo(
</Button> </Button>
</div> </div>
)} )}
{detailsMode === 'inline' && detailsShown && <InlineLogLineDetails logs={[]} />} {detailsMode === 'inline' && detailsShown && <InlineLogLineDetails logs={logs} />}
</> </>
); );
} }
@@ -13,6 +13,7 @@ import { createLogLineLinks } from '../logParser';
import { LogLineDetailsDisplayedFields } from './LogLineDetailsDisplayedFields'; import { LogLineDetailsDisplayedFields } from './LogLineDetailsDisplayedFields';
import { LabelWithLinks, LogLineDetailsFields, LogLineDetailsLabelFields } from './LogLineDetailsFields'; import { LabelWithLinks, LogLineDetailsFields, LogLineDetailsLabelFields } from './LogLineDetailsFields';
import { LogLineDetailsHeader } from './LogLineDetailsHeader'; import { LogLineDetailsHeader } from './LogLineDetailsHeader';
import { LogLineDetailsLog } from './LogLineDetailsLog';
import { useLogListContext } from './LogListContext'; import { useLogListContext } from './LogListContext';
import { LogListModel } from './processing'; import { LogListModel } from './processing';
@@ -102,7 +103,7 @@ export const LogLineDetailsComponent = ({ log, logs }: LogLineDetailsComponentPr
isOpen={logLineOpen} isOpen={logLineOpen}
onToggle={(isOpen: boolean) => handleToggle('logLineOpen', isOpen)} onToggle={(isOpen: boolean) => handleToggle('logLineOpen', isOpen)}
> >
<div className={styles.logLineWrapper}>{log.raw}</div> <LogLineDetailsLog log={log} />
</ControlledCollapse> </ControlledCollapse>
{displayedFields.length > 0 && setDisplayedFields && ( {displayedFields.length > 0 && setDisplayedFields && (
<ControlledCollapse <ControlledCollapse
@@ -189,8 +190,4 @@ const getStyles = (theme: GrafanaTheme2) => ({
componentWrapper: css({ componentWrapper: css({
padding: theme.spacing(0, 1, 1, 1), padding: theme.spacing(0, 1, 1, 1),
}), }),
logLineWrapper: css({
maxHeight: '50vh',
overflow: 'auto',
}),
}); });
@@ -0,0 +1,44 @@
import { css } from '@emotion/css';
import { memo, useMemo } from 'react';
import { useStyles2 } from '@grafana/ui';
import { getStyles } from './LogLine';
import { useLogListContext } from './LogListContext';
import { LogListModel } from './processing';
interface Props {
log: LogListModel;
}
export const LogLineDetailsLog = memo(({ log: originalLog }: Props) => {
const { syntaxHighlighting } = useLogListContext();
const logStyles = useStyles2(getStyles);
const log = useMemo(() => {
const log = originalLog.clone();
return log;
}, [originalLog]);
return (
<div className={styles.logLineWrapper}>
{!syntaxHighlighting ? (
<div className="field no-highlighting">{log.body}</div>
) : (
<div className={logStyles.logLine}>
<div className={logStyles.wrappedLogLine}>
<div className="field log-syntax-highlight" dangerouslySetInnerHTML={{ __html: log.highlightedBody }} />
</div>
</div>
)}
</div>
);
});
LogLineDetailsLog.displayName = 'LogLineDetailsLog';
const styles = {
logLineWrapper: css({
maxHeight: '50vh',
overflow: 'auto',
}),
};
@@ -140,6 +140,36 @@ describe('preProcessLogs', () => {
); );
expect(logListModel.getDisplayedFieldValue(LOG_LINE_BODY_FIELD_NAME, true)).toBe('log message 1'); expect(logListModel.getDisplayedFieldValue(LOG_LINE_BODY_FIELD_NAME, true)).toBe('log message 1');
}); });
test('Prettifies JSON', () => {
const entry = '{"key": "value", "otherKey": "otherValue"}';
const logListModel = createLogLine(
{ entry },
{
escape: false,
order: LogsSortOrder.Descending,
timeZone: 'browser',
wrapLogMessage: false, // unwrapped
}
);
expect(logListModel.entry).toBe(entry);
expect(logListModel.body).not.toBe(entry);
});
test('Uses lossless parsing', () => {
const entry = '{"number": 90071992547409911}';
const logListModel = createLogLine(
{ entry },
{
escape: false,
order: LogsSortOrder.Descending,
timeZone: 'browser',
wrapLogMessage: false, // unwrapped
}
);
expect(logListModel.entry).toBe(entry);
expect(logListModel.body).toContain('90071992547409911');
});
}); });
test('Orders logs', () => { test('Orders logs', () => {
@@ -243,6 +273,35 @@ describe('preProcessLogs', () => {
expect(entry).toContain(longLog.body); expect(entry).toContain(longLog.body);
}); });
test('Sets the collapsed state based on the new lines count', () => {
const entry = new Array(virtualization.getTruncationLineCount()).fill('test\n').join('');
const multilineLog = createLogLine(
{ entry, labels: { field: 'value' } },
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization, wrapLogMessage: true }
);
expect(multilineLog.collapsed).toBeUndefined();
multilineLog.updateCollapsedState([], container);
expect(multilineLog.collapsed).toBe(true);
expect(entry).toContain(multilineLog.body);
});
test('Correctly counts new lines', () => {
const entry = new Array(virtualization.getTruncationLineCount() - 1).fill('test\n').join('');
const multilineLog = createLogLine(
{ entry, labels: { field: 'value' } },
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization, wrapLogMessage: true }
);
expect(multilineLog.collapsed).toBeUndefined();
multilineLog.updateCollapsedState([], container);
expect(multilineLog.collapsed).toBeUndefined();
});
test('Considers the displayed fields to set the collapsed state', () => { test('Considers the displayed fields to set the collapsed state', () => {
// Make container half of the size // Make container half of the size
jest.spyOn(container, 'clientWidth', 'get').mockReturnValue(100); jest.spyOn(container, 'clientWidth', 'get').mockReturnValue(100);
@@ -255,6 +314,22 @@ describe('preProcessLogs', () => {
expect(longLog.collapsed).toBeUndefined(); expect(longLog.collapsed).toBeUndefined();
}); });
test('Considers new lines in displayed fields to set the collapsed state', () => {
const entry = new Array(virtualization.getTruncationLineCount() - 1).fill('test\n').join('');
const field = 'test\n';
const multilineLog = createLogLine(
{ entry, labels: { field } },
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization, wrapLogMessage: true }
);
expect(multilineLog.collapsed).toBeUndefined();
multilineLog.updateCollapsedState(['field', LOG_LINE_BODY_FIELD_NAME], container);
expect(multilineLog.collapsed).toBe(true);
expect(entry).toContain(multilineLog.body);
});
test('Updates the body based on the collapsed state', () => { test('Updates the body based on the collapsed state', () => {
expect(longLog.collapsed).toBeUndefined(); expect(longLog.collapsed).toBeUndefined();
expect(longLog.body).toBe(entry); expect(longLog.body).toBe(entry);
@@ -1,7 +1,8 @@
import ansicolor from 'ansicolor'; import ansicolor from 'ansicolor';
import { parse, stringify } from 'lossless-json';
import Prism, { Grammar } from 'prismjs'; import Prism, { Grammar } from 'prismjs';
import { DataFrame, dateTimeFormat, Labels, LogLevel, LogRowModel, LogsSortOrder } from '@grafana/data'; import { DataFrame, dateTimeFormat, Labels, LogLevel, LogRowModel, LogsSortOrder, textUtil } from '@grafana/data';
import { GetFieldLinksFn } from 'app/plugins/panel/logs/types'; import { GetFieldLinksFn } from 'app/plugins/panel/logs/types';
import { checkLogsError, checkLogsSampled, escapeUnescapedString, sortLogRows } from '../../utils'; import { checkLogsError, checkLogsSampled, escapeUnescapedString, sortLogRows } from '../../utils';
@@ -97,8 +98,23 @@ export class LogListModel implements LogRowModel {
this.raw = raw; this.raw = raw;
} }
clone() {
const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
// Unless this function is required outside of <LogLineDetailsLog />, we create a wrapped clone, so new lines are not stripped.
clone._wrapLogMessage = true;
clone._body = undefined;
clone._highlightedBody = undefined;
return clone;
}
get body(): string { get body(): string {
if (this._body === undefined) { if (this._body === undefined) {
try {
const parsed = stringify(parse(this.raw), undefined, 2);
if (parsed) {
this.raw = parsed;
}
} catch (error) {}
this._body = this.collapsed this._body = this.collapsed
? this.raw.substring(0, this._virtualization?.getTruncationLength(null) ?? TRUNCATION_DEFAULT_LENGTH) ? this.raw.substring(0, this._virtualization?.getTruncationLength(null) ?? TRUNCATION_DEFAULT_LENGTH)
: this.raw; : this.raw;
@@ -124,7 +140,11 @@ export class LogListModel implements LogRowModel {
if (this._highlightedBody === undefined) { if (this._highlightedBody === undefined) {
this._grammar = this._grammar ?? generateLogGrammar(this); this._grammar = this._grammar ?? generateLogGrammar(this);
const extraGrammar = generateTextMatchGrammar(this.searchWords, this._currentSearch); const extraGrammar = generateTextMatchGrammar(this.searchWords, this._currentSearch);
this._highlightedBody = Prism.highlight(this.body, { ...extraGrammar, ...this._grammar }, 'lokiql'); this._highlightedBody = Prism.highlight(
textUtil.sanitize(this.body),
{ ...extraGrammar, ...this._grammar },
'lokiql'
);
} }
return this._highlightedBody; return this._highlightedBody;
} }
@@ -154,16 +174,27 @@ export class LogListModel implements LogRowModel {
} }
updateCollapsedState(displayedFields: string[], container: HTMLDivElement | null) { updateCollapsedState(displayedFields: string[], container: HTMLDivElement | null) {
const lineLength = const line =
displayedFields.length > 0 displayedFields.length > 0
? displayedFields.map((field) => this.getDisplayedFieldValue(field, true)).join('').length ? displayedFields.map((field) => this.getDisplayedFieldValue(field, true)).join('')
: this.entry.length; : this.body;
const collapsed =
lineLength >= (this._virtualization?.getTruncationLength(container) ?? TRUNCATION_DEFAULT_LENGTH) // Length truncation
let collapsed =
line.length >= (this._virtualization?.getTruncationLength(container) ?? TRUNCATION_DEFAULT_LENGTH)
? true ? true
: undefined; : undefined;
// Newlines truncation
if (!collapsed && this._virtualization) {
const truncationLimit = this._virtualization.getTruncationLineCount();
collapsed = countNewLines(line, truncationLimit) >= truncationLimit ? true : collapsed;
}
if (this.collapsed === undefined || collapsed === undefined) { if (this.collapsed === undefined || collapsed === undefined) {
this.collapsed = collapsed; this.collapsed = collapsed;
this._body = undefined;
this._highlightedBody = undefined;
} }
return this.collapsed; return this.collapsed;
} }
@@ -226,3 +257,23 @@ function logLevelToDisplayLevel(level = '') {
return level; return level;
} }
} }
function countNewLines(log: string, limit = Infinity) {
let count = 0;
for (let i = 0; i < log.length; ++i) {
// No need to iterate further
if (count > Infinity) {
return count;
}
if (log[i] === '\n') {
count += 1;
} else if (log[i] === '\r') {
count += 1;
// skip LF in CRLF
if (log[i] === '\n') {
i += 1;
}
}
}
return count;
}
@@ -55,7 +55,6 @@ export class LogLineVirtualization {
getGridSize = () => this.gridSize; getGridSize = () => this.gridSize;
getPaddingBottom = () => this.paddingBottom; getPaddingBottom = () => this.paddingBottom;
// 2/3 of the viewport height
getTruncationLineCount = () => Math.round(window.innerHeight / this.getLineHeight() / 1.5); getTruncationLineCount = () => Math.round(window.innerHeight / this.getLineHeight() / 1.5);
getTruncationLength = (container: HTMLDivElement | null) => { getTruncationLength = (container: HTMLDivElement | null) => {
@@ -7,7 +7,6 @@ import {
defaultSpec as defaultDashboardV2Spec, defaultSpec as defaultDashboardV2Spec,
defaultPanelSpec, defaultPanelSpec,
defaultQueryVariableSpec, defaultQueryVariableSpec,
defaultDataQueryKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
import { getLibraryPanel } from 'app/features/library-panels/state/api'; import { getLibraryPanel } from 'app/features/library-panels/state/api';
@@ -837,9 +836,7 @@ describe('processV2Datasources', () => {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
query: { query: {
kind: 'DataQuery', kind: 'prometheus',
version: defaultDataQueryKind().version,
group: 'prometheus',
spec: { spec: {
expr: 'access_evaluation_duration_count', expr: 'access_evaluation_duration_count',
range: true, range: true,
@@ -865,9 +862,7 @@ describe('processV2Datasources', () => {
...defaultQueryVariableSpec(), ...defaultQueryVariableSpec(),
name: 'var1', name: 'var1',
query: { query: {
kind: 'DataQuery', kind: 'loki',
version: defaultDataQueryKind().version,
group: 'loki',
spec: { spec: {
expr: 'access_evaluation_duration_count', expr: 'access_evaluation_duration_count',
range: true, range: true,
@@ -885,9 +880,7 @@ describe('processV2Datasources', () => {
hide: false, hide: false,
iconColor: 'red', iconColor: 'red',
query: { query: {
kind: 'DataQuery', kind: 'loki',
version: defaultDataQueryKind().version,
group: 'loki',
spec: { spec: {
expr: 'access_evaluation_duration_count', expr: 'access_evaluation_duration_count',
range: true, range: true,
@@ -989,17 +982,13 @@ describe('processV2DatasourceInput', () => {
...defaultQueryVariableSpec(), ...defaultQueryVariableSpec(),
name: 'var2WithGrafanaDs', name: 'var2WithGrafanaDs',
query: { query: {
kind: 'DataQuery' as const, kind: 'grafana',
version: defaultDataQueryKind().version,
group: 'grafana',
spec: { spec: {
expr: 'access_evaluation_duration_count', panelId: 2,
range: true,
}, },
}, },
}, },
}; };
const result = await processV2DatasourceInput(queryVariable.spec, {}); const result = await processV2DatasourceInput(queryVariable.spec, {});
expect(result).toEqual({}); expect(result).toEqual({});
}); });
@@ -1012,9 +1001,7 @@ describe('processV2DatasourceInput', () => {
refId: 'A', refId: 'A',
hidden: false, hidden: false,
query: { query: {
kind: 'DataQuery' as const, kind: 'datasource',
version: defaultDataQueryKind().version,
group: 'datasource',
spec: { spec: {
panelId: 2, panelId: 2,
}, },
@@ -168,7 +168,6 @@ export function processV2Datasources(dashboard: DashboardV2Spec): ThunkResult<vo
if (element.kind !== 'Panel') { if (element.kind !== 'Panel') {
throw new Error('Only panels are currenlty supported in v2 dashboards'); throw new Error('Only panels are currenlty supported in v2 dashboards');
} }
if (element.spec.data.spec.queries.length > 0) { if (element.spec.data.spec.queries.length > 0) {
for (const query of element.spec.data.spec.queries) { for (const query of element.spec.data.spec.queries) {
inputs = await processV2DatasourceInput(query.spec, inputs); inputs = await processV2DatasourceInput(query.spec, inputs);
@@ -333,43 +332,43 @@ export function getFolderByUid(uid: string): Promise<{ uid: string; title: strin
} }
export async function processV2DatasourceInput( export async function processV2DatasourceInput(
spec: PanelQueryKind['spec'] | QueryVariableKind['spec'] | AnnotationQueryKind['spec'], obj: PanelQueryKind['spec'] | QueryVariableKind['spec'] | AnnotationQueryKind['spec'],
inputs: Record<string, DataSourceInput> = {} inputs: Record<string, DataSourceInput> = {}
) { ) {
const datasourceRef = spec.query.datasource; const datasourceRef = obj?.datasource;
let dataSourceInput: DataSourceInput | undefined; if (!datasourceRef && obj?.query) {
const dsType = spec.query.group; const dsType = obj.query.kind;
if (!datasourceRef) {
// if dsType is grafana, it means we are using a built-in annotation or default grafana datasource, in those // if dsType is grafana, it means we are using a built-in annotation or default grafana datasource, in those
// cases we don't need to map it // cases we don't need to map it
// "datasource" type is what we call "--Dashboard--" datasource <.-.> // "datasource" type is what we call "--Dashboard--" datasource <.-.>
if (dsType === 'grafana' || dsType === 'datasource') { if (dsType === 'grafana' || dsType === 'datasource') {
return inputs; return inputs;
} }
} const datasource = await getDatasourceSrv().get({ type: dsType });
let dataSourceInput: DataSourceInput | undefined;
if (datasource) {
dataSourceInput = {
name: datasource.name,
label: datasource.name,
info: `Select a ${datasource.name} data source`,
value: datasource.uid,
type: InputType.DataSource,
pluginId: datasource.meta?.id,
};
const datasource = await getDatasourceSrv().get({ type: dsType }); inputs[datasource.meta?.id] = dataSourceInput;
if (datasource) { } else {
dataSourceInput = { dataSourceInput = {
name: datasource.name, name: dsType,
label: datasource.name, label: dsType,
info: `Select a ${datasource.name} data source`, info: `No data sources of type ${dsType} found`,
value: datasource.uid, value: '',
type: InputType.DataSource, type: InputType.DataSource,
pluginId: datasource.meta?.id, pluginId: dsType,
}; };
inputs[datasource.meta?.id] = dataSourceInput;
} else { inputs[dsType] = dataSourceInput;
dataSourceInput = { }
name: dsType,
label: dsType,
info: `No data sources of type ${dsType} found`,
value: '',
type: InputType.DataSource,
pluginId: dsType,
};
inputs[dsType] = dataSourceInput;
} }
return inputs; return inputs;
} }
+8
View File
@@ -18355,6 +18355,7 @@ __metadata:
leven: "npm:^4.0.0" leven: "npm:^4.0.0"
lodash: "npm:4.17.21" lodash: "npm:4.17.21"
logfmt: "npm:^1.3.2" logfmt: "npm:^1.3.2"
lossless-json: "npm:^4.1.1"
lru-cache: "npm:11.1.0" lru-cache: "npm:11.1.0"
lru-memoize: "npm:^1.1.0" lru-memoize: "npm:^1.1.0"
lucene: "npm:^2.1.1" lucene: "npm:^2.1.1"
@@ -22160,6 +22161,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lossless-json@npm:^4.1.1":
version: 4.1.1
resolution: "lossless-json@npm:4.1.1"
checksum: 10/1e22e9e4ad8ebf169567a8c24553403bfd7abc33c141f4918f1b9626417f80477b849e6175a315a224e0c68072759c69a0e332a925de80269a7f9720af3a7fbd
languageName: node
linkType: hard
"loupe@npm:^3.1.0, loupe@npm:^3.1.1, loupe@npm:^3.1.2": "loupe@npm:^3.1.0, loupe@npm:^3.1.1, loupe@npm:^3.1.2":
version: 3.1.3 version: 3.1.3
resolution: "loupe@npm:3.1.3" resolution: "loupe@npm:3.1.3"