Compare commits
78 Commits
oscark/poc
...
njvrzm/err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79a61a2b63 | ||
|
|
dc4c106e91 | ||
|
|
33a1c60433 | ||
|
|
521670981a | ||
|
|
79ca4e5aec | ||
|
|
e3bc61e7d2 | ||
|
|
cc6a75d021 | ||
|
|
6d0f7f3567 | ||
|
|
913c0ba3c5 | ||
|
|
552b6aa717 | ||
|
|
2ddb4049c6 | ||
|
|
318a0ebb36 | ||
|
|
bba5c44dc4 | ||
|
|
44e6ea3d8b | ||
|
|
014d4758c6 | ||
|
|
82b4ce0ece | ||
|
|
52698cf0da | ||
|
|
d291dfb35b | ||
|
|
9c6feb8de5 | ||
|
|
e7625186af | ||
|
|
75b2c905cd | ||
|
|
45fc95cfc9 | ||
|
|
9c3cdd4814 | ||
|
|
2dad8b7b5b | ||
|
|
9a831ab4e1 | ||
|
|
759035a465 | ||
|
|
6e155523a3 | ||
|
|
5c0ee2d746 | ||
|
|
0c6b97bee2 | ||
|
|
4c79775b57 | ||
|
|
e088c9aac9 | ||
|
|
7182511bcf | ||
|
|
3023a72175 | ||
|
|
30ad61e0e9 | ||
|
|
0b58cd3900 | ||
|
|
4ba2fe6cce | ||
|
|
a345f78ae0 | ||
|
|
fa1e6cce5e | ||
|
|
e38f007d30 | ||
|
|
c38e515dec | ||
|
|
4f57ebe4ad | ||
|
|
3f5f0f783b | ||
|
|
5e4e6c1172 | ||
|
|
f5218b5eb8 | ||
|
|
a1389bc173 | ||
|
|
0a0f92e85e | ||
|
|
45f665d203 | ||
|
|
47436a3eeb | ||
|
|
359505a8aa | ||
|
|
ac866b0114 | ||
|
|
521cc11994 | ||
|
|
84120fb210 | ||
|
|
096208202e | ||
|
|
dd1edf7f16 | ||
|
|
15b5dcda80 | ||
|
|
5585595c16 | ||
|
|
6daa7ff729 | ||
|
|
8cfac85b48 | ||
|
|
0284d1e669 | ||
|
|
3522efdf32 | ||
|
|
2fbe2f77e3 | ||
|
|
4164239f56 | ||
|
|
14c595f206 | ||
|
|
471d6f5236 | ||
|
|
f91efcfe2c | ||
|
|
49032ae3d7 | ||
|
|
8b316cca25 | ||
|
|
fa73caf6c8 | ||
|
|
0e4b1c7b1e | ||
|
|
aa69d97f1e | ||
|
|
7812f783bb | ||
|
|
b2d6bb7a05 | ||
|
|
56dd1ca867 | ||
|
|
62b2a202de | ||
|
|
133865182e | ||
|
|
338ae95ef5 | ||
|
|
19c9f21cc4 | ||
|
|
9760eef62f |
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -425,6 +425,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
|
||||
/public/locales/enterprise/i18next.config.ts @grafana/grafana-frontend-platform
|
||||
/public/app/core/internationalization/ @grafana/grafana-frontend-platform
|
||||
/e2e/ @grafana/grafana-frontend-platform
|
||||
/e2e-playwright/alerting-suite/ @grafana/alerting-frontend
|
||||
/e2e-playwright/cloud-plugins-suite/ @grafana/partner-datasources
|
||||
/e2e-playwright/dashboard-new-layouts/ @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboard-cujs/ @grafana/dashboards-squad
|
||||
@@ -500,7 +501,6 @@ i18next.config.ts @grafana/grafana-frontend-platform
|
||||
/e2e-playwright/various-suite/filter-annotations.spec.ts @grafana/dashboards-squad
|
||||
/e2e-playwright/various-suite/frontend-sandbox-app.spec.ts @grafana/plugins-platform-frontend
|
||||
/e2e-playwright/various-suite/frontend-sandbox-datasource.spec.ts @grafana/plugins-platform-frontend
|
||||
/e2e-playwright/various-suite/gauge.spec.ts @grafana/dataviz-squad
|
||||
/e2e-playwright/various-suite/grafana-datasource-random-walk.spec.ts @grafana/grafana-frontend-platform
|
||||
/e2e-playwright/various-suite/graph-auto-migrate.spec.ts @grafana/dataviz-squad
|
||||
/e2e-playwright/various-suite/inspect-drawer.spec.ts @grafana/dashboards-squad
|
||||
@@ -519,7 +519,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
|
||||
/e2e-playwright/various-suite/solo-route.spec.ts @grafana/dashboards-squad
|
||||
/e2e-playwright/various-suite/trace-view-scrolling.spec.ts @grafana/observability-traces-and-profiling
|
||||
/e2e-playwright/various-suite/verify-i18n.spec.ts @grafana/grafana-frontend-platform
|
||||
/e2e-playwright/various-suite/visualization-suggestions.spec.ts @grafana/dataviz-squad
|
||||
/e2e-playwright/various-suite/visualization-suggestions*.spec.ts @grafana/dataviz-squad
|
||||
/e2e-playwright/various-suite/perf-test.spec.ts @grafana/grafana-frontend-platform
|
||||
|
||||
# Packages
|
||||
|
||||
1
.github/actions/change-detection/action.yml
vendored
1
.github/actions/change-detection/action.yml
vendored
@@ -99,6 +99,7 @@ runs:
|
||||
- '${{ inputs.self }}'
|
||||
e2e:
|
||||
- 'e2e/**'
|
||||
- 'e2e-playwright/**'
|
||||
- '.github/actions/setup-enterprise/**'
|
||||
- '.github/actions/checkout/**'
|
||||
- 'emails/**'
|
||||
|
||||
@@ -157,7 +157,7 @@ require (
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/google/wire v0.7.0 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // indirect
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
|
||||
|
||||
@@ -619,8 +619,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg=
|
||||
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY=
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4
|
||||
github.com/grafana/grafana-app-sdk v0.48.7
|
||||
github.com/grafana/grafana-app-sdk/logging v0.48.7
|
||||
|
||||
@@ -243,8 +243,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4=
|
||||
github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
|
||||
|
||||
@@ -31,6 +31,10 @@ const (
|
||||
maxLimit = 1000
|
||||
Namespace = "grafana"
|
||||
Subsystem = "alerting"
|
||||
|
||||
// LogQL field path for alert rule UID after JSON parsing.
|
||||
// Loki flattens nested JSON fields with underscores: alert.labels.__alert_rule_uid__ -> alert_labels___alert_rule_uid__
|
||||
lokiAlertRuleUIDField = "alert_labels___alert_rule_uid__"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -111,13 +115,13 @@ func buildQuery(query Query) (string, error) {
|
||||
fmt.Sprintf(`%s=%q`, historian.LabelFrom, historian.LabelFromValue),
|
||||
}
|
||||
|
||||
if query.RuleUID != nil {
|
||||
selectors = append(selectors,
|
||||
fmt.Sprintf(`%s=%q`, historian.LabelRuleUID, *query.RuleUID))
|
||||
}
|
||||
|
||||
logql := fmt.Sprintf(`{%s} | json`, strings.Join(selectors, `,`))
|
||||
|
||||
// Add ruleUID filter as JSON line filter if specified.
|
||||
if query.RuleUID != nil && *query.RuleUID != "" {
|
||||
logql += fmt.Sprintf(` | %s = %q`, lokiAlertRuleUIDField, *query.RuleUID)
|
||||
}
|
||||
|
||||
// Add receiver filter if specified.
|
||||
if query.Receiver != nil && *query.Receiver != "" {
|
||||
logql += fmt.Sprintf(` | receiver = %q`, *query.Receiver)
|
||||
@@ -211,16 +215,13 @@ func parseLokiEntry(s lokiclient.Sample) (Entry, error) {
|
||||
groupLabels = make(map[string]string)
|
||||
}
|
||||
|
||||
alerts := make([]EntryAlert, len(lokiEntry.Alerts))
|
||||
for i, a := range lokiEntry.Alerts {
|
||||
alerts[i] = EntryAlert{
|
||||
Status: a.Status,
|
||||
Labels: a.Labels,
|
||||
Annotations: a.Annotations,
|
||||
StartsAt: a.StartsAt,
|
||||
EndsAt: a.EndsAt,
|
||||
}
|
||||
}
|
||||
alerts := []EntryAlert{{
|
||||
Status: lokiEntry.Alert.Status,
|
||||
Labels: lokiEntry.Alert.Labels,
|
||||
Annotations: lokiEntry.Alert.Annotations,
|
||||
StartsAt: lokiEntry.Alert.StartsAt,
|
||||
EndsAt: lokiEntry.Alert.EndsAt,
|
||||
}}
|
||||
|
||||
return Entry{
|
||||
Timestamp: s.T,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/alerting/models"
|
||||
"github.com/grafana/alerting/notify/historian"
|
||||
"github.com/grafana/alerting/notify/historian/lokiclient"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
@@ -133,9 +134,8 @@ func TestBuildQuery(t *testing.T) {
|
||||
query: Query{
|
||||
RuleUID: stringPtr("test-rule-uid"),
|
||||
},
|
||||
expected: fmt.Sprintf(`{%s=%q,%s=%q} | json`,
|
||||
historian.LabelFrom, historian.LabelFromValue,
|
||||
historian.LabelRuleUID, "test-rule-uid"),
|
||||
expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid"`,
|
||||
historian.LabelFrom, historian.LabelFromValue),
|
||||
},
|
||||
{
|
||||
name: "query with receiver filter",
|
||||
@@ -143,9 +143,8 @@ func TestBuildQuery(t *testing.T) {
|
||||
RuleUID: stringPtr("test-rule-uid"),
|
||||
Receiver: stringPtr("email-receiver"),
|
||||
},
|
||||
expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | receiver = "email-receiver"`,
|
||||
historian.LabelFrom, historian.LabelFromValue,
|
||||
historian.LabelRuleUID, "test-rule-uid"),
|
||||
expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | receiver = "email-receiver"`,
|
||||
historian.LabelFrom, historian.LabelFromValue),
|
||||
},
|
||||
{
|
||||
name: "query with status filter",
|
||||
@@ -153,9 +152,8 @@ func TestBuildQuery(t *testing.T) {
|
||||
RuleUID: stringPtr("test-rule-uid"),
|
||||
Status: createStatusPtr(v0alpha1.CreateNotificationqueryRequestNotificationStatusFiring),
|
||||
},
|
||||
expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | status = "firing"`,
|
||||
historian.LabelFrom, historian.LabelFromValue,
|
||||
historian.LabelRuleUID, "test-rule-uid"),
|
||||
expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | status = "firing"`,
|
||||
historian.LabelFrom, historian.LabelFromValue),
|
||||
},
|
||||
{
|
||||
name: "query with success outcome filter",
|
||||
@@ -163,9 +161,8 @@ func TestBuildQuery(t *testing.T) {
|
||||
RuleUID: stringPtr("test-rule-uid"),
|
||||
Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeSuccess),
|
||||
},
|
||||
expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | error = ""`,
|
||||
historian.LabelFrom, historian.LabelFromValue,
|
||||
historian.LabelRuleUID, "test-rule-uid"),
|
||||
expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | error = ""`,
|
||||
historian.LabelFrom, historian.LabelFromValue),
|
||||
},
|
||||
{
|
||||
name: "query with error outcome filter",
|
||||
@@ -173,9 +170,8 @@ func TestBuildQuery(t *testing.T) {
|
||||
RuleUID: stringPtr("test-rule-uid"),
|
||||
Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeError),
|
||||
},
|
||||
expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | error != ""`,
|
||||
historian.LabelFrom, historian.LabelFromValue,
|
||||
historian.LabelRuleUID, "test-rule-uid"),
|
||||
expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | error != ""`,
|
||||
historian.LabelFrom, historian.LabelFromValue),
|
||||
},
|
||||
{
|
||||
name: "query with many filters",
|
||||
@@ -185,9 +181,8 @@ func TestBuildQuery(t *testing.T) {
|
||||
Status: createStatusPtr(v0alpha1.CreateNotificationqueryRequestNotificationStatusResolved),
|
||||
Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeSuccess),
|
||||
},
|
||||
expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | receiver = "email-receiver" | status = "resolved" | error = ""`,
|
||||
historian.LabelFrom, historian.LabelFromValue,
|
||||
historian.LabelRuleUID, "test-rule-uid"),
|
||||
expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | receiver = "email-receiver" | status = "resolved" | error = ""`,
|
||||
historian.LabelFrom, historian.LabelFromValue),
|
||||
},
|
||||
{
|
||||
name: "query with group label matcher",
|
||||
@@ -277,19 +272,19 @@ func TestParseLokiEntry(t *testing.T) {
|
||||
GroupLabels: map[string]string{
|
||||
"alertname": "test-alert",
|
||||
},
|
||||
Alerts: []historian.NotificationHistoryLokiEntryAlert{
|
||||
{
|
||||
Status: "firing",
|
||||
Labels: map[string]string{
|
||||
"severity": "critical",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": "Test alert",
|
||||
},
|
||||
StartsAt: now,
|
||||
EndsAt: now.Add(1 * time.Hour),
|
||||
Alert: historian.NotificationHistoryLokiEntryAlert{
|
||||
Status: "firing",
|
||||
Labels: map[string]string{
|
||||
"severity": "critical",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": "Test alert",
|
||||
},
|
||||
StartsAt: now,
|
||||
EndsAt: now.Add(1 * time.Hour),
|
||||
},
|
||||
AlertIndex: 0,
|
||||
AlertCount: 1,
|
||||
Retry: false,
|
||||
Duration: 100,
|
||||
PipelineTime: now,
|
||||
@@ -335,7 +330,9 @@ func TestParseLokiEntry(t *testing.T) {
|
||||
Error: "notification failed",
|
||||
GroupKey: "key:thing",
|
||||
GroupLabels: map[string]string{},
|
||||
Alerts: []historian.NotificationHistoryLokiEntryAlert{},
|
||||
Alert: historian.NotificationHistoryLokiEntryAlert{},
|
||||
AlertIndex: 0,
|
||||
AlertCount: 1,
|
||||
PipelineTime: now,
|
||||
}),
|
||||
},
|
||||
@@ -347,7 +344,7 @@ func TestParseLokiEntry(t *testing.T) {
|
||||
Outcome: OutcomeError,
|
||||
GroupKey: "key:thing",
|
||||
GroupLabels: map[string]string{},
|
||||
Alerts: []EntryAlert{},
|
||||
Alerts: []EntryAlert{{}},
|
||||
Error: stringPtr("notification failed"),
|
||||
PipelineTime: now,
|
||||
},
|
||||
@@ -365,7 +362,7 @@ func TestParseLokiEntry(t *testing.T) {
|
||||
Status: Status("firing"),
|
||||
Outcome: OutcomeSuccess,
|
||||
GroupLabels: map[string]string{},
|
||||
Alerts: []EntryAlert{},
|
||||
Alerts: []EntryAlert{{}},
|
||||
PipelineTime: now,
|
||||
},
|
||||
},
|
||||
@@ -448,7 +445,9 @@ func TestLokiReader_RunQuery(t *testing.T) {
|
||||
Receiver: "receiver-1",
|
||||
Status: "firing",
|
||||
GroupLabels: map[string]string{},
|
||||
Alerts: []historian.NotificationHistoryLokiEntryAlert{},
|
||||
Alert: historian.NotificationHistoryLokiEntryAlert{},
|
||||
AlertIndex: 0,
|
||||
AlertCount: 1,
|
||||
PipelineTime: now,
|
||||
}),
|
||||
},
|
||||
@@ -459,7 +458,9 @@ func TestLokiReader_RunQuery(t *testing.T) {
|
||||
Receiver: "receiver-3",
|
||||
Status: "firing",
|
||||
GroupLabels: map[string]string{},
|
||||
Alerts: []historian.NotificationHistoryLokiEntryAlert{},
|
||||
Alert: historian.NotificationHistoryLokiEntryAlert{},
|
||||
AlertIndex: 0,
|
||||
AlertCount: 1,
|
||||
PipelineTime: now,
|
||||
}),
|
||||
},
|
||||
@@ -474,7 +475,9 @@ func TestLokiReader_RunQuery(t *testing.T) {
|
||||
Receiver: "receiver-2",
|
||||
Status: "firing",
|
||||
GroupLabels: map[string]string{},
|
||||
Alerts: []historian.NotificationHistoryLokiEntryAlert{},
|
||||
Alert: historian.NotificationHistoryLokiEntryAlert{},
|
||||
AlertIndex: 0,
|
||||
AlertCount: 1,
|
||||
PipelineTime: now,
|
||||
}),
|
||||
},
|
||||
@@ -546,19 +549,19 @@ func createMockLokiResponse(timestamp time.Time) lokiclient.QueryRes {
|
||||
GroupLabels: map[string]string{
|
||||
"alertname": "test-alert",
|
||||
},
|
||||
Alerts: []historian.NotificationHistoryLokiEntryAlert{
|
||||
{
|
||||
Status: "firing",
|
||||
Labels: map[string]string{
|
||||
"severity": "critical",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": "Test alert",
|
||||
},
|
||||
StartsAt: timestamp,
|
||||
EndsAt: timestamp.Add(1 * time.Hour),
|
||||
Alert: historian.NotificationHistoryLokiEntryAlert{
|
||||
Status: "firing",
|
||||
Labels: map[string]string{
|
||||
"severity": "critical",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": "Test alert",
|
||||
},
|
||||
StartsAt: timestamp,
|
||||
EndsAt: timestamp.Add(1 * time.Hour),
|
||||
},
|
||||
AlertIndex: 0,
|
||||
AlertCount: 1,
|
||||
Retry: false,
|
||||
Duration: 100,
|
||||
PipelineTime: timestamp,
|
||||
@@ -587,10 +590,19 @@ func createLokiEntryJSONWithNilLabels(t *testing.T, timestamp time.Time) string
|
||||
"status": "firing",
|
||||
"error": "",
|
||||
"groupLabels": null,
|
||||
"alerts": [],
|
||||
"alert": {},
|
||||
"alertIndex": 0,
|
||||
"alertCount": 1,
|
||||
"retry": false,
|
||||
"duration": 0,
|
||||
"pipelineTime": "%s"
|
||||
}`, timestamp.Format(time.RFC3339Nano))
|
||||
return jsonStr
|
||||
}
|
||||
|
||||
func TestRuleUIDLabelConstant(t *testing.T) {
|
||||
// Verify that models.RuleUIDLabel has the expected value.
|
||||
// If this changes in the alerting module, our LogQL field path constant will be incorrect
|
||||
// and filtering for a single alert rule by its UID will break.
|
||||
assert.Equal(t, "__alert_rule_uid__", models.RuleUIDLabel)
|
||||
}
|
||||
|
||||
@@ -180,12 +180,15 @@ func countAnnotationsV0V1(spec map[string]interface{}) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
annotationList, ok := annotations["list"].([]interface{})
|
||||
if !ok {
|
||||
return 0
|
||||
// Handle both []interface{} (from JSON unmarshaling) and []map[string]interface{} (from programmatic creation)
|
||||
if annotationList, ok := annotations["list"].([]interface{}); ok {
|
||||
return len(annotationList)
|
||||
}
|
||||
if annotationList, ok := annotations["list"].([]map[string]interface{}); ok {
|
||||
return len(annotationList)
|
||||
}
|
||||
|
||||
return len(annotationList)
|
||||
return 0
|
||||
}
|
||||
|
||||
// countLinksV0V1 counts dashboard links in v0alpha1 or v1beta1 dashboard spec
|
||||
@@ -194,12 +197,15 @@ func countLinksV0V1(spec map[string]interface{}) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
links, ok := spec["links"].([]interface{})
|
||||
if !ok {
|
||||
return 0
|
||||
// Handle both []interface{} (from JSON unmarshaling) and []map[string]interface{} (from programmatic creation)
|
||||
if links, ok := spec["links"].([]interface{}); ok {
|
||||
return len(links)
|
||||
}
|
||||
if links, ok := spec["links"].([]map[string]interface{}); ok {
|
||||
return len(links)
|
||||
}
|
||||
|
||||
return len(links)
|
||||
return 0
|
||||
}
|
||||
|
||||
// countVariablesV0V1 counts template variables in v0alpha1 or v1beta1 dashboard spec
|
||||
@@ -213,12 +219,15 @@ func countVariablesV0V1(spec map[string]interface{}) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
variableList, ok := templating["list"].([]interface{})
|
||||
if !ok {
|
||||
return 0
|
||||
// Handle both []interface{} (from JSON unmarshaling) and []map[string]interface{} (from programmatic creation)
|
||||
if variableList, ok := templating["list"].([]interface{}); ok {
|
||||
return len(variableList)
|
||||
}
|
||||
if variableList, ok := templating["list"].([]map[string]interface{}); ok {
|
||||
return len(variableList)
|
||||
}
|
||||
|
||||
return len(variableList)
|
||||
return 0
|
||||
}
|
||||
|
||||
// collectStatsV0V1 collects statistics from v0alpha1 or v1beta1 dashboard
|
||||
|
||||
@@ -71,12 +71,11 @@
|
||||
"id": 1,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": true,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
@@ -150,12 +149,11 @@
|
||||
"id": 4,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
@@ -229,12 +227,11 @@
|
||||
"id": 3,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
@@ -271,85 +268,6 @@
|
||||
"title": "Center and bar glow",
|
||||
"type": "radialbar"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 12,
|
||||
"y": 1
|
||||
},
|
||||
"id": 5,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"segmentCount": 1,
|
||||
"segmentSpacing": 0.3,
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "1",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"max": 100,
|
||||
"min": 1,
|
||||
"noise": 22,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 22,
|
||||
"startValue": 1
|
||||
}
|
||||
],
|
||||
"title": "Spotlight",
|
||||
"type": "radialbar"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
@@ -391,10 +309,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -470,10 +387,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": false,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -549,10 +465,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": false,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -641,10 +556,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -720,10 +634,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -799,10 +712,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -878,10 +790,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -974,10 +885,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1053,10 +963,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1132,10 +1041,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1211,10 +1119,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1290,10 +1197,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1386,10 +1292,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1469,10 +1374,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1552,10 +1456,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1641,13 +1544,13 @@
|
||||
"options": {
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"barShape": "rounded",
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": true
|
||||
},
|
||||
"endpointMarker": "glow",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1662,8 +1565,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1730,10 +1632,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1748,8 +1649,7 @@
|
||||
"shape": "gauge",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": true,
|
||||
"spotlight": true
|
||||
"sparkline": true
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1830,10 +1730,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1848,8 +1747,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1917,9 +1815,6 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"sparkline": false,
|
||||
"spotlight": true,
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
@@ -1934,10 +1829,10 @@
|
||||
"segmentCount": 12,
|
||||
"segmentSpacing": 0.3,
|
||||
"shape": "circle",
|
||||
"barShape": "rounded",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2004,10 +1899,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2022,8 +1916,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2090,10 +1983,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"rounded": true,
|
||||
"spotlight": true,
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2108,8 +2000,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
|
||||
@@ -955,8 +955,6 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
|
||||
142
apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.bom-in-links.json
vendored
Normal file
142
apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.bom-in-links.json
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"kind": "Dashboard",
|
||||
"apiVersion": "dashboard.grafana.app/v1beta1",
|
||||
"metadata": {
|
||||
"name": "bom-in-links-test",
|
||||
"namespace": "org-1",
|
||||
"labels": {
|
||||
"test": "bom-stripping"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"title": "BOM Stripping Test Dashboard",
|
||||
"description": "Testing that BOM characters are stripped from URLs during conversion",
|
||||
"schemaVersion": 42,
|
||||
"tags": ["test", "bom"],
|
||||
"editable": true,
|
||||
"links": [
|
||||
{
|
||||
"title": "Dashboard link with BOM",
|
||||
"type": "link",
|
||||
"url": "http://example.com?var=${datasource}&other=value",
|
||||
"targetBlank": true,
|
||||
"icon": "external link"
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "table",
|
||||
"title": "Panel with BOM in field config override links",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{"color": "green"},
|
||||
{"color": "red", "value": 80}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "server"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "links",
|
||||
"value": [
|
||||
{
|
||||
"title": "Override link with BOM",
|
||||
"url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}&var-server=${__value.raw}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "Panel data link with BOM",
|
||||
"url": "http://example.com/${__data.fields.cluster}&var=value",
|
||||
"targetBlank": true
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-ds"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "timeseries",
|
||||
"title": "Panel with BOM in options dataLinks",
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"showLegend": true,
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"dataLinks": [
|
||||
{
|
||||
"title": "Options data link with BOM",
|
||||
"url": "http://example.com?series=${__series.name}&time=${__value.time}",
|
||||
"targetBlank": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"links": [
|
||||
{
|
||||
"title": "Field config default link with BOM",
|
||||
"url": "http://example.com?field=${__field.name}&value=${__value.raw}",
|
||||
"targetBlank": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-ds"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
166
apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.groupby.json
vendored
Normal file
166
apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.groupby.json
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
{
|
||||
"kind": "DashboardWithAccessInfo",
|
||||
"apiVersion": "dashboard.grafana.app/v1beta1",
|
||||
"metadata": {
|
||||
"name": "groupby-test"
|
||||
},
|
||||
"spec": {
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations \u0026 Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"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",
|
||||
"showValues": false,
|
||||
"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": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(counters_requests)",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "works with group by var",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
"schemaVersion": 42,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"text": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
],
|
||||
"value": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"name": "Group by",
|
||||
"type": "groupby"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "groupby test",
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@
|
||||
"value": [
|
||||
{
|
||||
"title": "filter",
|
||||
"url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}"
|
||||
"url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
"value": [
|
||||
{
|
||||
"title": "filter",
|
||||
"url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}"
|
||||
"url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -77,13 +77,12 @@
|
||||
"id": 1,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -156,13 +155,12 @@
|
||||
"id": 4,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -235,13 +233,12 @@
|
||||
"id": 3,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -277,85 +274,6 @@
|
||||
"title": "Center and bar glow",
|
||||
"type": "radialbar"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 12,
|
||||
"y": 1
|
||||
},
|
||||
"id": 5,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"segmentCount": 1,
|
||||
"segmentSpacing": 0.3,
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "1",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"max": 100,
|
||||
"min": 1,
|
||||
"noise": 22,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 22,
|
||||
"startValue": 1
|
||||
}
|
||||
],
|
||||
"title": "Spotlight",
|
||||
"type": "radialbar"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
@@ -393,13 +311,12 @@
|
||||
"id": 8,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -472,13 +389,12 @@
|
||||
"id": 22,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -551,13 +467,12 @@
|
||||
"id": 23,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -643,13 +558,12 @@
|
||||
"id": 18,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.1,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -722,13 +636,12 @@
|
||||
"id": 19,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.32,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -801,13 +714,12 @@
|
||||
"id": 20,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.57,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -880,13 +792,12 @@
|
||||
"id": 21,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.8,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -976,13 +887,12 @@
|
||||
"id": 25,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1055,13 +965,12 @@
|
||||
"id": 26,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1134,13 +1043,12 @@
|
||||
"id": 29,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1213,13 +1121,12 @@
|
||||
"id": 30,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1292,13 +1199,12 @@
|
||||
"id": 28,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1388,13 +1294,12 @@
|
||||
"id": 32,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1471,13 +1376,12 @@
|
||||
"id": 34,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1554,13 +1458,12 @@
|
||||
"id": 33,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1645,15 +1548,15 @@
|
||||
"id": 9,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"endpointMarker": "glow",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1668,8 +1571,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1731,14 +1633,13 @@
|
||||
"id": 11,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -1754,8 +1655,7 @@
|
||||
"shape": "gauge",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": true,
|
||||
"spotlight": true
|
||||
"sparkline": true
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1831,14 +1731,13 @@
|
||||
"id": 13,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -1854,8 +1753,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1918,15 +1816,13 @@
|
||||
"id": 14,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -1942,8 +1838,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2005,14 +1900,13 @@
|
||||
"id": 15,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.84,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -2028,8 +1922,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2091,14 +1984,13 @@
|
||||
"id": 16,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.66,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -2114,8 +2006,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
|
||||
@@ -73,13 +73,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -165,14 +164,13 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -188,8 +186,7 @@
|
||||
"shape": "gauge",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": true,
|
||||
"spotlight": true
|
||||
"sparkline": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -262,14 +259,13 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -285,8 +281,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -360,15 +355,13 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -384,8 +377,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -459,14 +451,13 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.84,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -482,8 +473,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -556,14 +546,13 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.66,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -579,8 +568,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -653,13 +641,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.1,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -745,13 +732,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.32,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -837,13 +823,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.57,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -929,13 +914,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.8,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1021,13 +1005,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1113,13 +1096,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1201,13 +1183,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1293,13 +1274,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1385,13 +1365,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1477,13 +1456,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1573,13 +1551,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1661,13 +1638,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1753,13 +1729,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1849,13 +1824,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1945,13 +1919,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2045,105 +2018,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"segmentCount": 1,
|
||||
"segmentSpacing": 0.3,
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"value": 80,
|
||||
"color": "red"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-5": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 5,
|
||||
"title": "Spotlight",
|
||||
"description": "",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "grafana-testdata-datasource",
|
||||
"spec": {
|
||||
"alias": "1",
|
||||
"max": 100,
|
||||
"min": 1,
|
||||
"noise": 22,
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 22,
|
||||
"startValue": 1
|
||||
}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {
|
||||
"maxDataPoints": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "radialbar",
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2229,13 +2109,12 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2321,15 +2200,15 @@
|
||||
"spec": {
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"endpointMarker": "glow",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2344,8 +2223,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -2429,19 +2307,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"width": 4,
|
||||
"height": 6,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-5"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
|
||||
@@ -77,13 +77,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -172,14 +171,13 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -195,8 +193,7 @@
|
||||
"shape": "gauge",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": true,
|
||||
"spotlight": true
|
||||
"sparkline": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -272,14 +269,13 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -295,8 +291,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -373,15 +368,13 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -397,8 +390,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -475,14 +467,13 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.84,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -498,8 +489,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -575,14 +565,13 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.66,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -598,8 +587,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -675,13 +663,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.1,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -770,13 +757,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.32,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -865,13 +851,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.57,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -960,13 +945,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.8,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1055,13 +1039,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1150,13 +1133,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1241,13 +1223,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1336,13 +1317,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1431,13 +1411,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1526,13 +1505,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1625,13 +1603,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1716,13 +1693,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1811,13 +1787,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1910,13 +1885,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2009,13 +1983,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2112,108 +2085,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"segmentCount": 1,
|
||||
"segmentSpacing": 0.3,
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"value": 80,
|
||||
"color": "red"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-5": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 5,
|
||||
"title": "Spotlight",
|
||||
"description": "",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "grafana-testdata-datasource",
|
||||
"version": "v0",
|
||||
"spec": {
|
||||
"alias": "1",
|
||||
"max": 100,
|
||||
"min": 1,
|
||||
"noise": 22,
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 22,
|
||||
"startValue": 1
|
||||
}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {
|
||||
"maxDataPoints": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "radialbar",
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2302,13 +2179,12 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2397,15 +2273,15 @@
|
||||
"version": "13.0.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"endpointMarker": "glow",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2420,8 +2296,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -2505,19 +2380,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"width": 4,
|
||||
"height": 6,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-5"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
|
||||
@@ -961,9 +961,7 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
|
||||
@@ -864,9 +864,7 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
|
||||
@@ -901,9 +901,7 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
|
||||
161
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v0alpha1.json
vendored
Normal file
161
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v0alpha1.json
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
{
|
||||
"kind": "Dashboard",
|
||||
"apiVersion": "dashboard.grafana.app/v0alpha1",
|
||||
"metadata": {
|
||||
"name": "bom-in-links-test",
|
||||
"namespace": "org-1",
|
||||
"labels": {
|
||||
"test": "bom-stripping"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Testing that BOM characters are stripped from URLs during conversion",
|
||||
"editable": true,
|
||||
"links": [
|
||||
{
|
||||
"icon": "external link",
|
||||
"targetBlank": true,
|
||||
"title": "Dashboard link with BOM",
|
||||
"type": "link",
|
||||
"url": "http://example.com?var=${datasource}\u0026other=value"
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "server"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "links",
|
||||
"value": [
|
||||
{
|
||||
"title": "Override link with BOM",
|
||||
"url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Panel data link with BOM",
|
||||
"url": "http://example.com/${__data.fields.cluster}\u0026var=value"
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-ds"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel with BOM in field config override links",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": false,
|
||||
"title": "Field config default link with BOM",
|
||||
"url": "http://example.com?field=${__field.name}\u0026value=${__value.raw}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"dataLinks": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Options data link with BOM",
|
||||
"url": "http://example.com?series=${__series.name}\u0026time=${__value.time}"
|
||||
}
|
||||
],
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-ds"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Panel with BOM in options dataLinks",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 42,
|
||||
"tags": [
|
||||
"test",
|
||||
"bom"
|
||||
],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m"
|
||||
]
|
||||
},
|
||||
"title": "BOM Stripping Test Dashboard"
|
||||
},
|
||||
"status": {
|
||||
"conversion": {
|
||||
"failed": false,
|
||||
"storedVersion": "v1beta1"
|
||||
}
|
||||
}
|
||||
}
|
||||
242
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2alpha1.json
vendored
Normal file
242
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2alpha1.json
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"kind": "Dashboard",
|
||||
"apiVersion": "dashboard.grafana.app/v2alpha1",
|
||||
"metadata": {
|
||||
"name": "bom-in-links-test",
|
||||
"namespace": "org-1",
|
||||
"labels": {
|
||||
"test": "bom-stripping"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"annotations": [],
|
||||
"cursorSync": "Off",
|
||||
"description": "Testing that BOM characters are stripped from URLs during conversion",
|
||||
"editable": true,
|
||||
"elements": {
|
||||
"panel-1": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 1,
|
||||
"title": "Panel with BOM in field config override links",
|
||||
"description": "",
|
||||
"links": [
|
||||
{
|
||||
"title": "Panel data link with BOM",
|
||||
"url": "http://example.com/${__data.fields.cluster}\u0026var=value",
|
||||
"targetBlank": true
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "prometheus",
|
||||
"spec": {}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-ds"
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "table",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": null,
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"value": 80,
|
||||
"color": "red"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "server"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "links",
|
||||
"value": [
|
||||
{
|
||||
"title": "Override link with BOM",
|
||||
"url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-2": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 2,
|
||||
"title": "Panel with BOM in options dataLinks",
|
||||
"description": "",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "prometheus",
|
||||
"spec": {}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-ds"
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "timeseries",
|
||||
"spec": {
|
||||
"pluginVersion": "",
|
||||
"options": {
|
||||
"dataLinks": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Options data link with BOM",
|
||||
"url": "http://example.com?series=${__series.name}\u0026time=${__value.time}"
|
||||
}
|
||||
],
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": false,
|
||||
"title": "Field config default link with BOM",
|
||||
"url": "http://example.com?field=${__field.name}\u0026value=${__value.raw}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"kind": "GridLayout",
|
||||
"spec": {
|
||||
"items": [
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 12,
|
||||
"height": 8,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"width": 12,
|
||||
"height": 8,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "Dashboard link with BOM",
|
||||
"type": "link",
|
||||
"icon": "external link",
|
||||
"tooltip": "",
|
||||
"url": "http://example.com?var=${datasource}\u0026other=value",
|
||||
"tags": [],
|
||||
"asDropdown": false,
|
||||
"targetBlank": true,
|
||||
"includeVars": false,
|
||||
"keepTime": false
|
||||
}
|
||||
],
|
||||
"liveNow": false,
|
||||
"preload": false,
|
||||
"tags": [
|
||||
"test",
|
||||
"bom"
|
||||
],
|
||||
"timeSettings": {
|
||||
"timezone": "browser",
|
||||
"from": "now-6h",
|
||||
"to": "now",
|
||||
"autoRefresh": "",
|
||||
"autoRefreshIntervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m"
|
||||
],
|
||||
"hideTimepicker": false,
|
||||
"fiscalYearStartMonth": 0
|
||||
},
|
||||
"title": "BOM Stripping Test Dashboard",
|
||||
"variables": []
|
||||
},
|
||||
"status": {
|
||||
"conversion": {
|
||||
"failed": false,
|
||||
"storedVersion": "v1beta1"
|
||||
}
|
||||
}
|
||||
}
|
||||
246
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2beta1.json
vendored
Normal file
246
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2beta1.json
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
{
|
||||
"kind": "Dashboard",
|
||||
"apiVersion": "dashboard.grafana.app/v2beta1",
|
||||
"metadata": {
|
||||
"name": "bom-in-links-test",
|
||||
"namespace": "org-1",
|
||||
"labels": {
|
||||
"test": "bom-stripping"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"annotations": [],
|
||||
"cursorSync": "Off",
|
||||
"description": "Testing that BOM characters are stripped from URLs during conversion",
|
||||
"editable": true,
|
||||
"elements": {
|
||||
"panel-1": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 1,
|
||||
"title": "Panel with BOM in field config override links",
|
||||
"description": "",
|
||||
"links": [
|
||||
{
|
||||
"title": "Panel data link with BOM",
|
||||
"url": "http://example.com/${__data.fields.cluster}\u0026var=value",
|
||||
"targetBlank": true
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "prometheus",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "test-ds"
|
||||
},
|
||||
"spec": {}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "table",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": null,
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"value": 80,
|
||||
"color": "red"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "server"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "links",
|
||||
"value": [
|
||||
{
|
||||
"title": "Override link with BOM",
|
||||
"url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-2": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 2,
|
||||
"title": "Panel with BOM in options dataLinks",
|
||||
"description": "",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "prometheus",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "test-ds"
|
||||
},
|
||||
"spec": {}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "timeseries",
|
||||
"version": "",
|
||||
"spec": {
|
||||
"options": {
|
||||
"dataLinks": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Options data link with BOM",
|
||||
"url": "http://example.com?series=${__series.name}\u0026time=${__value.time}"
|
||||
}
|
||||
],
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
}
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": false,
|
||||
"title": "Field config default link with BOM",
|
||||
"url": "http://example.com?field=${__field.name}\u0026value=${__value.raw}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"kind": "GridLayout",
|
||||
"spec": {
|
||||
"items": [
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 12,
|
||||
"height": 8,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"width": 12,
|
||||
"height": 8,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "Dashboard link with BOM",
|
||||
"type": "link",
|
||||
"icon": "external link",
|
||||
"tooltip": "",
|
||||
"url": "http://example.com?var=${datasource}\u0026other=value",
|
||||
"tags": [],
|
||||
"asDropdown": false,
|
||||
"targetBlank": true,
|
||||
"includeVars": false,
|
||||
"keepTime": false
|
||||
}
|
||||
],
|
||||
"liveNow": false,
|
||||
"preload": false,
|
||||
"tags": [
|
||||
"test",
|
||||
"bom"
|
||||
],
|
||||
"timeSettings": {
|
||||
"timezone": "browser",
|
||||
"from": "now-6h",
|
||||
"to": "now",
|
||||
"autoRefresh": "",
|
||||
"autoRefreshIntervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m"
|
||||
],
|
||||
"hideTimepicker": false,
|
||||
"fiscalYearStartMonth": 0
|
||||
},
|
||||
"title": "BOM Stripping Test Dashboard",
|
||||
"variables": []
|
||||
},
|
||||
"status": {
|
||||
"conversion": {
|
||||
"failed": false,
|
||||
"storedVersion": "v1beta1"
|
||||
}
|
||||
}
|
||||
}
|
||||
172
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v0alpha1.json
vendored
Normal file
172
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v0alpha1.json
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"kind": "DashboardWithAccessInfo",
|
||||
"apiVersion": "dashboard.grafana.app/v0alpha1",
|
||||
"metadata": {
|
||||
"name": "groupby-test"
|
||||
},
|
||||
"spec": {
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations \u0026 Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"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",
|
||||
"showValues": false,
|
||||
"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": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(counters_requests)",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "works with group by var",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
"schemaVersion": 42,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"text": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
],
|
||||
"value": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"name": "Group by",
|
||||
"type": "groupby"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "groupby test",
|
||||
"weekStart": ""
|
||||
},
|
||||
"status": {
|
||||
"conversion": {
|
||||
"failed": false,
|
||||
"storedVersion": "v1beta1"
|
||||
}
|
||||
}
|
||||
}
|
||||
229
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2alpha1.json
vendored
Normal file
229
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2alpha1.json
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"kind": "DashboardWithAccessInfo",
|
||||
"apiVersion": "dashboard.grafana.app/v2alpha1",
|
||||
"metadata": {
|
||||
"name": "groupby-test"
|
||||
},
|
||||
"spec": {
|
||||
"annotations": [
|
||||
{
|
||||
"kind": "AnnotationQuery",
|
||||
"spec": {
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"query": {
|
||||
"kind": "grafana",
|
||||
"spec": {}
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations \u0026 Alerts",
|
||||
"builtIn": true,
|
||||
"legacyOptions": {
|
||||
"type": "dashboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"cursorSync": "Off",
|
||||
"editable": true,
|
||||
"elements": {
|
||||
"panel-2": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 2,
|
||||
"title": "works with group by var",
|
||||
"description": "",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "prometheus",
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "sum(counters_requests)",
|
||||
"legendFormat": "__auto",
|
||||
"range": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "timeseries",
|
||||
"spec": {
|
||||
"pluginVersion": "12.4.0-pre",
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"value": 80,
|
||||
"color": "red"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"kind": "GridLayout",
|
||||
"spec": {
|
||||
"items": [
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"width": 12,
|
||||
"height": 8,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"preload": false,
|
||||
"tags": [],
|
||||
"timeSettings": {
|
||||
"timezone": "browser",
|
||||
"from": "now-6h",
|
||||
"to": "now",
|
||||
"autoRefresh": "",
|
||||
"autoRefreshIntervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"hideTimepicker": false,
|
||||
"fiscalYearStartMonth": 0
|
||||
},
|
||||
"title": "groupby test",
|
||||
"variables": [
|
||||
{
|
||||
"kind": "GroupByVariable",
|
||||
"spec": {
|
||||
"name": "Group by",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "test-uid"
|
||||
},
|
||||
"current": {
|
||||
"text": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
],
|
||||
"value": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
]
|
||||
},
|
||||
"options": [],
|
||||
"multi": true,
|
||||
"hide": "dontHide",
|
||||
"skipUrlSync": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"conversion": {
|
||||
"failed": false,
|
||||
"storedVersion": "v1beta1"
|
||||
}
|
||||
}
|
||||
}
|
||||
232
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2beta1.json
vendored
Normal file
232
apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2beta1.json
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
{
|
||||
"kind": "DashboardWithAccessInfo",
|
||||
"apiVersion": "dashboard.grafana.app/v2beta1",
|
||||
"metadata": {
|
||||
"name": "groupby-test"
|
||||
},
|
||||
"spec": {
|
||||
"annotations": [
|
||||
{
|
||||
"kind": "AnnotationQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "grafana",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Grafana --"
|
||||
},
|
||||
"spec": {}
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations \u0026 Alerts",
|
||||
"builtIn": true,
|
||||
"legacyOptions": {
|
||||
"type": "dashboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"cursorSync": "Off",
|
||||
"editable": true,
|
||||
"elements": {
|
||||
"panel-2": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 2,
|
||||
"title": "works with group by var",
|
||||
"description": "",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "prometheus",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "test-uid"
|
||||
},
|
||||
"spec": {
|
||||
"editorMode": "code",
|
||||
"expr": "sum(counters_requests)",
|
||||
"legendFormat": "__auto",
|
||||
"range": true
|
||||
}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "timeseries",
|
||||
"version": "12.4.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"value": 80,
|
||||
"color": "red"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"kind": "GridLayout",
|
||||
"spec": {
|
||||
"items": [
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"width": 12,
|
||||
"height": 8,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-2"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"preload": false,
|
||||
"tags": [],
|
||||
"timeSettings": {
|
||||
"timezone": "browser",
|
||||
"from": "now-6h",
|
||||
"to": "now",
|
||||
"autoRefresh": "",
|
||||
"autoRefreshIntervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"hideTimepicker": false,
|
||||
"fiscalYearStartMonth": 0
|
||||
},
|
||||
"title": "groupby test",
|
||||
"variables": [
|
||||
{
|
||||
"kind": "GroupByVariable",
|
||||
"group": "prometheus",
|
||||
"datasource": {
|
||||
"name": "test-uid"
|
||||
},
|
||||
"spec": {
|
||||
"name": "Group by",
|
||||
"current": {
|
||||
"text": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
],
|
||||
"value": [
|
||||
"a_legacy_label",
|
||||
"app",
|
||||
"exported_instance",
|
||||
"exported_job"
|
||||
]
|
||||
},
|
||||
"options": [],
|
||||
"multi": true,
|
||||
"hide": "dontHide",
|
||||
"skipUrlSync": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"conversion": {
|
||||
"failed": false,
|
||||
"storedVersion": "v1beta1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +229,36 @@ func getBoolField(m map[string]interface{}, key string, defaultValue bool) bool
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// stripBOM removes Byte Order Mark (BOM) characters from a string.
|
||||
// BOMs (U+FEFF) can be introduced through copy/paste from certain editors
|
||||
// and cause CUE validation errors ("illegal byte order mark").
|
||||
func stripBOM(s string) string {
|
||||
return strings.ReplaceAll(s, "\ufeff", "")
|
||||
}
|
||||
|
||||
// stripBOMFromInterface recursively strips BOM characters from all strings
|
||||
// in an interface{} value (map, slice, or string).
|
||||
func stripBOMFromInterface(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return stripBOM(val)
|
||||
case map[string]interface{}:
|
||||
result := make(map[string]interface{}, len(val))
|
||||
for k, v := range val {
|
||||
result[k] = stripBOMFromInterface(v)
|
||||
}
|
||||
return result
|
||||
case []interface{}:
|
||||
result := make([]interface{}, len(val))
|
||||
for i, item := range val {
|
||||
result[i] = stripBOMFromInterface(item)
|
||||
}
|
||||
return result
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func getUnionField[T ~string](m map[string]interface{}, key string) *T {
|
||||
if val, ok := m[key]; ok {
|
||||
if str, ok := val.(string); ok && str != "" {
|
||||
@@ -393,7 +423,8 @@ func transformLinks(dashboard map[string]interface{}) []dashv2alpha1.DashboardDa
|
||||
// Optional field - only set if present
|
||||
if url, exists := linkMap["url"]; exists {
|
||||
if urlStr, ok := url.(string); ok {
|
||||
dashLink.Url = &urlStr
|
||||
cleanUrl := stripBOM(urlStr)
|
||||
dashLink.Url = &cleanUrl
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1734,7 +1765,9 @@ func buildGroupByVariable(ctx context.Context, varMap map[string]interface{}, co
|
||||
Hide: commonProps.Hide,
|
||||
SkipUrlSync: commonProps.SkipUrlSync,
|
||||
Current: buildVariableCurrent(varMap["current"]),
|
||||
Multi: getBoolField(varMap, "multi", false),
|
||||
// We set it to true by default because GroupByVariable
|
||||
// constructor defaults to multi: true
|
||||
Multi: getBoolField(varMap, "multi", true),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2237,7 +2270,7 @@ func transformDataLinks(panelMap map[string]interface{}) []dashv2alpha1.Dashboar
|
||||
if linkMap, ok := link.(map[string]interface{}); ok {
|
||||
dataLink := dashv2alpha1.DashboardDataLink{
|
||||
Title: schemaversion.GetStringValue(linkMap, "title"),
|
||||
Url: schemaversion.GetStringValue(linkMap, "url"),
|
||||
Url: stripBOM(schemaversion.GetStringValue(linkMap, "url")),
|
||||
}
|
||||
if _, exists := linkMap["targetBlank"]; exists {
|
||||
targetBlank := getBoolField(linkMap, "targetBlank", false)
|
||||
@@ -2329,6 +2362,12 @@ func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizCo
|
||||
}
|
||||
}
|
||||
|
||||
// Strip BOMs from options (may contain dataLinks with URLs that have BOMs)
|
||||
cleanedOptions := stripBOMFromInterface(options)
|
||||
if cleanedMap, ok := cleanedOptions.(map[string]interface{}); ok {
|
||||
options = cleanedMap
|
||||
}
|
||||
|
||||
// Build field config by mapping each field individually
|
||||
fieldConfigSource := extractFieldConfigSource(fieldConfig)
|
||||
|
||||
@@ -2472,9 +2511,14 @@ func extractFieldConfigDefaults(defaults map[string]interface{}) dashv2alpha1.Da
|
||||
hasDefaults = true
|
||||
}
|
||||
|
||||
// Extract array field
|
||||
// Extract array field - strip BOMs from link URLs
|
||||
if linksArray, ok := extractArrayField(defaults, "links"); ok {
|
||||
fieldConfigDefaults.Links = linksArray
|
||||
cleanedLinks := stripBOMFromInterface(linksArray)
|
||||
if cleanedArray, ok := cleanedLinks.([]interface{}); ok {
|
||||
fieldConfigDefaults.Links = cleanedArray
|
||||
} else {
|
||||
fieldConfigDefaults.Links = linksArray
|
||||
}
|
||||
hasDefaults = true
|
||||
}
|
||||
|
||||
@@ -2760,9 +2804,11 @@ func extractFieldConfigOverrides(fieldConfig map[string]interface{}) []dashv2alp
|
||||
fieldOverride.Properties = make([]dashv2alpha1.DashboardDynamicConfigValue, 0, len(propertiesArray))
|
||||
for _, property := range propertiesArray {
|
||||
if propertyMap, ok := property.(map[string]interface{}); ok {
|
||||
// Strip BOMs from property values (may contain links with URLs)
|
||||
cleanedValue := stripBOMFromInterface(propertyMap["value"])
|
||||
fieldOverride.Properties = append(fieldOverride.Properties, dashv2alpha1.DashboardDynamicConfigValue{
|
||||
Id: schemaversion.GetStringValue(propertyMap, "id"),
|
||||
Value: propertyMap["value"],
|
||||
Value: cleanedValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +628,20 @@
|
||||
}
|
||||
],
|
||||
"title": "Only nulls and no user set min \u0026 max",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "convertFieldType",
|
||||
"options": {
|
||||
"conversions": [
|
||||
{
|
||||
"destinationType": "number",
|
||||
"targetField": "A-series"
|
||||
}
|
||||
],
|
||||
"fields": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "gauge"
|
||||
},
|
||||
{
|
||||
@@ -1179,4 +1193,4 @@
|
||||
"title": "Panel Tests - Gauge",
|
||||
"uid": "_5rDmaQiz",
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +75,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -154,10 +153,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -233,10 +231,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -305,85 +302,6 @@
|
||||
"x": 12,
|
||||
"y": 1
|
||||
},
|
||||
"id": 5,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"segmentCount": 1,
|
||||
"segmentSpacing": 0.3,
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "1",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"max": 100,
|
||||
"min": 1,
|
||||
"noise": 22,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 22,
|
||||
"startValue": 1
|
||||
}
|
||||
],
|
||||
"title": "Spotlight",
|
||||
"type": "radialbar"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 16,
|
||||
"y": 1
|
||||
},
|
||||
"id": 8,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
@@ -391,10 +309,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -460,8 +377,8 @@
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 7
|
||||
"x": 16,
|
||||
"y": 1
|
||||
},
|
||||
"id": 22,
|
||||
"maxDataPoints": 20,
|
||||
@@ -470,10 +387,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -539,8 +455,8 @@
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 4,
|
||||
"y": 7
|
||||
"x": 20,
|
||||
"y": 1
|
||||
},
|
||||
"id": 23,
|
||||
"maxDataPoints": 20,
|
||||
@@ -549,10 +465,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -593,7 +508,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 13
|
||||
"y": 7
|
||||
},
|
||||
"id": 17,
|
||||
"panels": [],
|
||||
@@ -630,9 +545,9 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 14
|
||||
"y": 8
|
||||
},
|
||||
"id": 18,
|
||||
"maxDataPoints": 20,
|
||||
@@ -641,10 +556,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -709,9 +623,9 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 5,
|
||||
"y": 14
|
||||
"w": 4,
|
||||
"x": 4,
|
||||
"y": 8
|
||||
},
|
||||
"id": 19,
|
||||
"maxDataPoints": 20,
|
||||
@@ -720,10 +634,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -788,9 +701,9 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 10,
|
||||
"y": 14
|
||||
"w": 4,
|
||||
"x": 8,
|
||||
"y": 8
|
||||
},
|
||||
"id": 20,
|
||||
"maxDataPoints": 20,
|
||||
@@ -799,10 +712,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -867,9 +779,9 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 15,
|
||||
"y": 14
|
||||
"w": 4,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
},
|
||||
"id": 21,
|
||||
"maxDataPoints": 20,
|
||||
@@ -878,10 +790,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -922,7 +833,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 20
|
||||
"y": 14
|
||||
},
|
||||
"id": 24,
|
||||
"panels": [],
|
||||
@@ -963,9 +874,9 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 21
|
||||
"y": 15
|
||||
},
|
||||
"id": 25,
|
||||
"maxDataPoints": 20,
|
||||
@@ -974,10 +885,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1042,9 +952,9 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 21
|
||||
"w": 4,
|
||||
"x": 4,
|
||||
"y": 15
|
||||
},
|
||||
"id": 26,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1053,10 +963,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1121,9 +1030,9 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 12,
|
||||
"y": 21
|
||||
"w": 4,
|
||||
"x": 8,
|
||||
"y": 15
|
||||
},
|
||||
"id": 29,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1132,10 +1041,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1199,10 +1107,10 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 27
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 12,
|
||||
"y": 15
|
||||
},
|
||||
"id": 30,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1211,10 +1119,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1278,10 +1185,10 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 27
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 16,
|
||||
"y": 15
|
||||
},
|
||||
"id": 28,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1290,10 +1197,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1330,7 +1236,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 34
|
||||
"y": 21
|
||||
},
|
||||
"id": 31,
|
||||
"panels": [],
|
||||
@@ -1377,7 +1283,7 @@
|
||||
"h": 10,
|
||||
"w": 7,
|
||||
"x": 0,
|
||||
"y": 35
|
||||
"y": 22
|
||||
},
|
||||
"id": 32,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1386,10 +1292,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1460,7 +1365,7 @@
|
||||
"h": 10,
|
||||
"w": 7,
|
||||
"x": 7,
|
||||
"y": 35
|
||||
"y": 22
|
||||
},
|
||||
"id": 34,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1469,10 +1374,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1543,7 +1447,7 @@
|
||||
"h": 10,
|
||||
"w": 6,
|
||||
"x": 14,
|
||||
"y": 35
|
||||
"y": 22
|
||||
},
|
||||
"id": 33,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1552,10 +1456,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -1592,7 +1495,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 45
|
||||
"y": 32
|
||||
},
|
||||
"id": 6,
|
||||
"panels": [],
|
||||
@@ -1633,20 +1536,20 @@
|
||||
"h": 6,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 46
|
||||
"y": 33
|
||||
},
|
||||
"id": 9,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"barShape": "rounded",
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"endpointMarker": "glow",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1661,8 +1564,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1717,7 +1619,7 @@
|
||||
"h": 6,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 52
|
||||
"y": 39
|
||||
},
|
||||
"id": 11,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1727,10 +1629,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1745,8 +1646,7 @@
|
||||
"shape": "gauge",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": true,
|
||||
"spotlight": true
|
||||
"sparkline": true
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1773,7 +1673,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 58
|
||||
"y": 45
|
||||
},
|
||||
"id": 12,
|
||||
"panels": [],
|
||||
@@ -1815,7 +1715,7 @@
|
||||
"h": 7,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 13,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1825,10 +1725,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1843,8 +1742,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1862,6 +1760,22 @@
|
||||
"startValue": 0
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "calculateField",
|
||||
"options": {
|
||||
"mode": "unary",
|
||||
"reduce": {
|
||||
"reducer": "sum"
|
||||
},
|
||||
"replaceFields": true,
|
||||
"unary": {
|
||||
"operator": "round",
|
||||
"fieldName": "A-series"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "Active gateways",
|
||||
"type": "radialbar"
|
||||
},
|
||||
@@ -1900,7 +1814,7 @@
|
||||
"h": 7,
|
||||
"w": 5,
|
||||
"x": 4,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 14,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1910,10 +1824,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1928,8 +1841,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1947,6 +1859,22 @@
|
||||
"startValue": 0
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "calculateField",
|
||||
"options": {
|
||||
"mode": "unary",
|
||||
"reduce": {
|
||||
"reducer": "sum"
|
||||
},
|
||||
"replaceFields": true,
|
||||
"unary": {
|
||||
"operator": "round",
|
||||
"fieldName": "A-series"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "Active pods",
|
||||
"type": "radialbar"
|
||||
},
|
||||
@@ -1984,7 +1912,7 @@
|
||||
"h": 7,
|
||||
"w": 5,
|
||||
"x": 9,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 15,
|
||||
"maxDataPoints": 20,
|
||||
@@ -1994,10 +1922,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2012,8 +1939,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2068,7 +1994,7 @@
|
||||
"h": 7,
|
||||
"w": 6,
|
||||
"x": 14,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 16,
|
||||
"maxDataPoints": 20,
|
||||
@@ -2078,10 +2004,9 @@
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "rounded",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2096,8 +2021,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2124,7 +2048,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 66
|
||||
"y": 53
|
||||
},
|
||||
"id": 35,
|
||||
"panels": [],
|
||||
@@ -2155,10 +2079,10 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"h": 5,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 67
|
||||
"y": 54
|
||||
},
|
||||
"id": 36,
|
||||
"options": {
|
||||
@@ -2166,10 +2090,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -2223,10 +2146,10 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 67
|
||||
"h": 5,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 54
|
||||
},
|
||||
"id": 37,
|
||||
"options": {
|
||||
@@ -2234,10 +2157,9 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"barShape": "flat",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
@@ -2279,4 +2201,4 @@
|
||||
"title": "Panel tests - Gauge (new)",
|
||||
"uid": "panel-tests-gauge-new",
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +485,7 @@
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"displayName": "My gauge",
|
||||
"minVizHeight": 75,
|
||||
"minVizWidth": 75,
|
||||
"orientation": "auto",
|
||||
@@ -955,9 +956,7 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1162,4 +1161,4 @@
|
||||
"title": "Panel tests - Old gauge to new",
|
||||
"uid": "panel-tests-old-gauge-to-new",
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ require (
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
|
||||
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // indirect
|
||||
|
||||
@@ -827,8 +827,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg=
|
||||
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY=
|
||||
|
||||
20
apps/plugins/README.md
Normal file
20
apps/plugins/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Plugins App
|
||||
|
||||
API documentation is available at http://localhost:3000/swagger?api=plugins.grafana.app-v0alpha1
|
||||
|
||||
## Codegen
|
||||
|
||||
- Go: `make generate`
|
||||
- Frontend: Follow instructions in this [README](../..//packages/grafana-api-clients/README.md)
|
||||
|
||||
## Plugin sync
|
||||
|
||||
The plugin sync pushes the plugins loaded from disk to the plugins API.
|
||||
|
||||
To enable, add these feature toggles in your `custom.ini`:
|
||||
|
||||
```ini
|
||||
[feature_toggles]
|
||||
pluginInstallAPISync = true
|
||||
pluginStoreServiceLoading = true
|
||||
```
|
||||
@@ -90,7 +90,7 @@ require (
|
||||
github.com/google/gnostic-models v0.7.1 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
|
||||
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // indirect
|
||||
|
||||
@@ -213,8 +213,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0=
|
||||
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg=
|
||||
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY=
|
||||
|
||||
22
apps/provisioning/pkg/auth/access_checker.go
Normal file
22
apps/provisioning/pkg/auth/access_checker.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
)
|
||||
|
||||
//go:generate mockery --name AccessChecker --structname MockAccessChecker --inpackage --filename access_checker_mock.go --with-expecter
|
||||
|
||||
// AccessChecker provides access control checks with optional role-based fallback.
|
||||
type AccessChecker interface {
|
||||
// Check performs an access check and returns nil if allowed, or an appropriate
|
||||
// API error if denied. If req.Namespace is empty, it will be filled from the
|
||||
// identity's namespace.
|
||||
Check(ctx context.Context, req authlib.CheckRequest, folder string) error
|
||||
|
||||
// WithFallbackRole returns an AccessChecker configured with the specified fallback role.
|
||||
// Whether the fallback is actually applied depends on the implementation.
|
||||
WithFallbackRole(role identity.RoleType) AccessChecker
|
||||
}
|
||||
135
apps/provisioning/pkg/auth/access_checker_mock.go
Normal file
135
apps/provisioning/pkg/auth/access_checker_mock.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
identity "github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
types "github.com/grafana/authlib/types"
|
||||
)
|
||||
|
||||
// MockAccessChecker is an autogenerated mock type for the AccessChecker type
|
||||
type MockAccessChecker struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockAccessChecker_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockAccessChecker) EXPECT() *MockAccessChecker_Expecter {
|
||||
return &MockAccessChecker_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Check provides a mock function with given fields: ctx, req, folder
|
||||
func (_m *MockAccessChecker) Check(ctx context.Context, req types.CheckRequest, folder string) error {
|
||||
ret := _m.Called(ctx, req, folder)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Check")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, types.CheckRequest, string) error); ok {
|
||||
r0 = rf(ctx, req, folder)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockAccessChecker_Check_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Check'
|
||||
type MockAccessChecker_Check_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Check is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - req types.CheckRequest
|
||||
// - folder string
|
||||
func (_e *MockAccessChecker_Expecter) Check(ctx interface{}, req interface{}, folder interface{}) *MockAccessChecker_Check_Call {
|
||||
return &MockAccessChecker_Check_Call{Call: _e.mock.On("Check", ctx, req, folder)}
|
||||
}
|
||||
|
||||
func (_c *MockAccessChecker_Check_Call) Run(run func(ctx context.Context, req types.CheckRequest, folder string)) *MockAccessChecker_Check_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(types.CheckRequest), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockAccessChecker_Check_Call) Return(_a0 error) *MockAccessChecker_Check_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockAccessChecker_Check_Call) RunAndReturn(run func(context.Context, types.CheckRequest, string) error) *MockAccessChecker_Check_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// WithFallbackRole provides a mock function with given fields: role
|
||||
func (_m *MockAccessChecker) WithFallbackRole(role identity.RoleType) AccessChecker {
|
||||
ret := _m.Called(role)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for WithFallbackRole")
|
||||
}
|
||||
|
||||
var r0 AccessChecker
|
||||
if rf, ok := ret.Get(0).(func(identity.RoleType) AccessChecker); ok {
|
||||
r0 = rf(role)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(AccessChecker)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockAccessChecker_WithFallbackRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithFallbackRole'
|
||||
type MockAccessChecker_WithFallbackRole_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// WithFallbackRole is a helper method to define mock.On call
|
||||
// - role identity.RoleType
|
||||
func (_e *MockAccessChecker_Expecter) WithFallbackRole(role interface{}) *MockAccessChecker_WithFallbackRole_Call {
|
||||
return &MockAccessChecker_WithFallbackRole_Call{Call: _e.mock.On("WithFallbackRole", role)}
|
||||
}
|
||||
|
||||
func (_c *MockAccessChecker_WithFallbackRole_Call) Run(run func(role identity.RoleType)) *MockAccessChecker_WithFallbackRole_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(identity.RoleType))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockAccessChecker_WithFallbackRole_Call) Return(_a0 AccessChecker) *MockAccessChecker_WithFallbackRole_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockAccessChecker_WithFallbackRole_Call) RunAndReturn(run func(identity.RoleType) AccessChecker) *MockAccessChecker_WithFallbackRole_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockAccessChecker creates a new instance of MockAccessChecker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockAccessChecker(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockAccessChecker {
|
||||
mock := &MockAccessChecker{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package auth provides authentication utilities for the provisioning API.
|
||||
package auth
|
||||
|
||||
import (
|
||||
@@ -6,7 +7,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
)
|
||||
|
||||
@@ -15,29 +15,61 @@ type tokenExchanger interface {
|
||||
Exchange(ctx context.Context, req authn.TokenExchangeRequest) (*authn.TokenExchangeResponse, error)
|
||||
}
|
||||
|
||||
// RoundTripper injects an exchanged access token for the provisioning API into outgoing requests.
|
||||
type RoundTripper struct {
|
||||
client tokenExchanger
|
||||
transport http.RoundTripper
|
||||
audience string
|
||||
// RoundTripperOption configures optional behavior for the RoundTripper.
|
||||
type RoundTripperOption func(*RoundTripper)
|
||||
|
||||
// ExtraAudience appends an additional audience to the token exchange request.
|
||||
//
|
||||
// This is primarily used by operators connecting to the multitenant aggregator,
|
||||
// where the token must include both the target API server's audience (e.g., dashboards,
|
||||
// folders) and the provisioning group audience. The provisioning group audience is
|
||||
// required so that the token passes the enforceManagerProperties check, which prevents
|
||||
// unauthorized updates to provisioned resources.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// authrt.NewRoundTripper(client, rt, "dashboards.grafana.app", authrt.ExtraAudience("provisioning.grafana.app"))
|
||||
func ExtraAudience(audience string) RoundTripperOption {
|
||||
return func(rt *RoundTripper) {
|
||||
rt.extraAudience = audience
|
||||
}
|
||||
}
|
||||
|
||||
// NewRoundTripper constructs a RoundTripper that exchanges the provided token per request
|
||||
// and forwards the request to the provided base transport.
|
||||
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string) *RoundTripper {
|
||||
return &RoundTripper{
|
||||
// RoundTripper is an http.RoundTripper that performs token exchange before each request.
|
||||
// It exchanges the service's credentials for an access token scoped to the configured
|
||||
// audience(s), then injects that token into the outgoing request's X-Access-Token header.
|
||||
type RoundTripper struct {
|
||||
client tokenExchanger
|
||||
transport http.RoundTripper
|
||||
audience string
|
||||
extraAudience string
|
||||
}
|
||||
|
||||
// NewRoundTripper creates a RoundTripper that exchanges tokens for each outgoing request.
|
||||
//
|
||||
// Parameters:
|
||||
// - tokenExchangeClient: the client used to exchange credentials for access tokens
|
||||
// - base: the underlying transport to delegate requests to after token injection
|
||||
// - audience: the primary audience for the token (typically the target API server's group)
|
||||
// - opts: optional configuration (e.g., ExtraAudience to include additional audiences)
|
||||
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string, opts ...RoundTripperOption) *RoundTripper {
|
||||
rt := &RoundTripper{
|
||||
client: tokenExchangeClient,
|
||||
transport: base,
|
||||
audience: audience,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(rt)
|
||||
}
|
||||
return rt
|
||||
}
|
||||
|
||||
// RoundTrip exchanges credentials for an access token and injects it into the request.
|
||||
// The token is scoped to all configured audiences and the wildcard namespace ("*").
|
||||
func (t *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// when we want to write resources with the provisioning API, the audience needs to include provisioning
|
||||
// so that it passes the check in enforceManagerProperties, which prevents others from updating provisioned resources
|
||||
audiences := []string{t.audience}
|
||||
if t.audience != v0alpha1.GROUP {
|
||||
audiences = append(audiences, v0alpha1.GROUP)
|
||||
if t.extraAudience != "" && t.extraAudience != t.audience {
|
||||
audiences = append(audiences, t.extraAudience)
|
||||
}
|
||||
|
||||
tokenResponse, err := t.client.Exchange(req.Context(), authn.TokenExchangeRequest{
|
||||
|
||||
@@ -71,16 +71,29 @@ func TestRoundTripper_AudiencesAndNamespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
audience string
|
||||
extraAudience string
|
||||
wantAudiences []string
|
||||
}{
|
||||
{
|
||||
name: "adds group when custom audience",
|
||||
name: "uses only provided audience by default",
|
||||
audience: "example-audience",
|
||||
wantAudiences: []string{"example-audience"},
|
||||
},
|
||||
{
|
||||
name: "uses only group audience by default",
|
||||
audience: v0alpha1.GROUP,
|
||||
wantAudiences: []string{v0alpha1.GROUP},
|
||||
},
|
||||
{
|
||||
name: "extra audience adds provisioning group",
|
||||
audience: "example-audience",
|
||||
extraAudience: v0alpha1.GROUP,
|
||||
wantAudiences: []string{"example-audience", v0alpha1.GROUP},
|
||||
},
|
||||
{
|
||||
name: "no duplicate when group audience",
|
||||
name: "extra audience no duplicate when same as primary",
|
||||
audience: v0alpha1.GROUP,
|
||||
extraAudience: v0alpha1.GROUP,
|
||||
wantAudiences: []string{v0alpha1.GROUP},
|
||||
},
|
||||
}
|
||||
@@ -88,11 +101,15 @@ func TestRoundTripper_AudiencesAndNamespace(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fx := &fakeExchanger{resp: &authn.TokenExchangeResponse{Token: "abc123"}}
|
||||
var opts []RoundTripperOption
|
||||
if tt.extraAudience != "" {
|
||||
opts = append(opts, ExtraAudience(tt.extraAudience))
|
||||
}
|
||||
tr := NewRoundTripper(fx, roundTripperFunc(func(_ *http.Request) (*http.Response, error) {
|
||||
rr := httptest.NewRecorder()
|
||||
rr.WriteHeader(http.StatusOK)
|
||||
return rr.Result(), nil
|
||||
}), tt.audience)
|
||||
}), tt.audience, opts...)
|
||||
|
||||
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil)
|
||||
resp, err := tr.RoundTrip(req)
|
||||
|
||||
153
apps/provisioning/pkg/auth/session_access_checker.go
Normal file
153
apps/provisioning/pkg/auth/session_access_checker.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
)
|
||||
|
||||
// sessionAccessChecker implements AccessChecker using Grafana session identity.
|
||||
type sessionAccessChecker struct {
|
||||
inner authlib.AccessChecker
|
||||
fallbackRole identity.RoleType
|
||||
}
|
||||
|
||||
// NewSessionAccessChecker creates an AccessChecker that gets identity from Grafana
|
||||
// sessions via GetRequester(ctx). Supports optional role-based fallback via
|
||||
// WithFallbackRole for backwards compatibility.
|
||||
func NewSessionAccessChecker(inner authlib.AccessChecker) AccessChecker {
|
||||
return &sessionAccessChecker{
|
||||
inner: inner,
|
||||
fallbackRole: "",
|
||||
}
|
||||
}
|
||||
|
||||
// WithFallbackRole returns a new AccessChecker with the specified fallback role.
|
||||
func (c *sessionAccessChecker) WithFallbackRole(role identity.RoleType) AccessChecker {
|
||||
return &sessionAccessChecker{
|
||||
inner: c.inner,
|
||||
fallbackRole: role,
|
||||
}
|
||||
}
|
||||
|
||||
// Check performs an access check with optional role-based fallback.
|
||||
// Returns nil if access is allowed, or an appropriate API error if denied.
|
||||
func (c *sessionAccessChecker) Check(ctx context.Context, req authlib.CheckRequest, folder string) error {
|
||||
logger := logging.FromContext(ctx).With("logger", "sessionAccessChecker")
|
||||
|
||||
// Get identity from Grafana session
|
||||
requester, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
logger.Debug("failed to get requester",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"error", err.Error(),
|
||||
)
|
||||
return apierrors.NewUnauthorized(fmt.Sprintf("failed to get requester: %v", err))
|
||||
}
|
||||
|
||||
logger.Debug("checking access",
|
||||
"identityType", requester.GetIdentityType(),
|
||||
"orgRole", requester.GetOrgRole(),
|
||||
"namespace", requester.GetNamespace(),
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"group", req.Group,
|
||||
"name", req.Name,
|
||||
"folder", folder,
|
||||
"fallbackRole", c.fallbackRole,
|
||||
)
|
||||
|
||||
// Fill in namespace from identity if not provided
|
||||
if req.Namespace == "" {
|
||||
req.Namespace = requester.GetNamespace()
|
||||
}
|
||||
|
||||
// Perform the access check
|
||||
rsp, err := c.inner.Check(ctx, requester, req, folder)
|
||||
|
||||
// Build the GroupResource for error messages
|
||||
gr := schema.GroupResource{Group: req.Group, Resource: req.Resource}
|
||||
|
||||
// No fallback configured, return result directly
|
||||
if c.fallbackRole == "" {
|
||||
if err != nil {
|
||||
logger.Debug("access check error (no fallback)",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"error", err.Error(),
|
||||
)
|
||||
return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s.%s is forbidden: %w", req.Resource, req.Group, err))
|
||||
}
|
||||
if !rsp.Allowed {
|
||||
logger.Debug("access check denied (no fallback)",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"group", req.Group,
|
||||
"allowed", rsp.Allowed,
|
||||
)
|
||||
return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("permission denied"))
|
||||
}
|
||||
logger.Debug("access allowed",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fallback is configured - apply fallback logic
|
||||
if err != nil {
|
||||
if requester.GetOrgRole().Includes(c.fallbackRole) {
|
||||
logger.Debug("access allowed via role fallback (after error)",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"fallbackRole", c.fallbackRole,
|
||||
"orgRole", requester.GetOrgRole(),
|
||||
)
|
||||
return nil // Fallback succeeded
|
||||
}
|
||||
logger.Debug("access check error (fallback failed)",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"error", err.Error(),
|
||||
"fallbackRole", c.fallbackRole,
|
||||
"orgRole", requester.GetOrgRole(),
|
||||
)
|
||||
return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s.%s is forbidden: %w", req.Resource, req.Group, err))
|
||||
}
|
||||
|
||||
if rsp.Allowed {
|
||||
logger.Debug("access allowed",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fall back to role for backwards compatibility
|
||||
if requester.GetOrgRole().Includes(c.fallbackRole) {
|
||||
logger.Debug("access allowed via role fallback",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"fallbackRole", c.fallbackRole,
|
||||
"orgRole", requester.GetOrgRole(),
|
||||
)
|
||||
return nil // Fallback succeeded
|
||||
}
|
||||
|
||||
logger.Debug("access denied (fallback role not met)",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"group", req.Group,
|
||||
"fallbackRole", c.fallbackRole,
|
||||
"orgRole", requester.GetOrgRole(),
|
||||
)
|
||||
return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s role is required", strings.ToLower(string(c.fallbackRole))))
|
||||
}
|
||||
244
apps/provisioning/pkg/auth/session_access_checker_test.go
Normal file
244
apps/provisioning/pkg/auth/session_access_checker_test.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// mockRequester implements identity.Requester for testing.
|
||||
type mockRequester struct {
|
||||
identity.Requester
|
||||
orgRole identity.RoleType
|
||||
identityType authlib.IdentityType
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (m *mockRequester) GetOrgRole() identity.RoleType {
|
||||
return m.orgRole
|
||||
}
|
||||
|
||||
func (m *mockRequester) GetIdentityType() authlib.IdentityType {
|
||||
return m.identityType
|
||||
}
|
||||
|
||||
func (m *mockRequester) GetNamespace() string {
|
||||
return m.namespace
|
||||
}
|
||||
|
||||
func TestSessionAccessChecker_Check(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
req := authlib.CheckRequest{
|
||||
Verb: "get",
|
||||
Group: "provisioning.grafana.app",
|
||||
Resource: "repositories",
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fallbackRole identity.RoleType
|
||||
innerResponse authlib.CheckResponse
|
||||
innerErr error
|
||||
requester *mockRequester
|
||||
expectAllow bool
|
||||
}{
|
||||
{
|
||||
name: "allowed by checker",
|
||||
fallbackRole: identity.RoleAdmin,
|
||||
innerResponse: authlib.CheckResponse{Allowed: true},
|
||||
requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser},
|
||||
expectAllow: true,
|
||||
},
|
||||
{
|
||||
name: "denied by checker, fallback to admin role succeeds",
|
||||
fallbackRole: identity.RoleAdmin,
|
||||
innerResponse: authlib.CheckResponse{Allowed: false},
|
||||
requester: &mockRequester{orgRole: identity.RoleAdmin, identityType: authlib.TypeUser},
|
||||
expectAllow: true,
|
||||
},
|
||||
{
|
||||
name: "denied by checker, fallback to admin role fails for viewer",
|
||||
fallbackRole: identity.RoleAdmin,
|
||||
innerResponse: authlib.CheckResponse{Allowed: false},
|
||||
requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser},
|
||||
expectAllow: false,
|
||||
},
|
||||
{
|
||||
name: "error from checker, fallback to admin role succeeds",
|
||||
fallbackRole: identity.RoleAdmin,
|
||||
innerErr: errors.New("access check failed"),
|
||||
requester: &mockRequester{orgRole: identity.RoleAdmin, identityType: authlib.TypeUser},
|
||||
expectAllow: true,
|
||||
},
|
||||
{
|
||||
name: "error from checker, fallback fails for viewer",
|
||||
fallbackRole: identity.RoleAdmin,
|
||||
innerErr: errors.New("access check failed"),
|
||||
requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser},
|
||||
expectAllow: false,
|
||||
},
|
||||
{
|
||||
name: "denied, editor fallback succeeds for editor",
|
||||
fallbackRole: identity.RoleEditor,
|
||||
innerResponse: authlib.CheckResponse{Allowed: false},
|
||||
requester: &mockRequester{orgRole: identity.RoleEditor, identityType: authlib.TypeUser},
|
||||
expectAllow: true,
|
||||
},
|
||||
{
|
||||
name: "denied, editor fallback fails for viewer",
|
||||
fallbackRole: identity.RoleEditor,
|
||||
innerResponse: authlib.CheckResponse{Allowed: false},
|
||||
requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser},
|
||||
expectAllow: false,
|
||||
},
|
||||
{
|
||||
name: "no fallback configured, denied stays denied",
|
||||
fallbackRole: "", // no fallback
|
||||
innerResponse: authlib.CheckResponse{Allowed: false},
|
||||
requester: &mockRequester{orgRole: identity.RoleAdmin, identityType: authlib.TypeUser},
|
||||
expectAllow: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: tt.innerResponse,
|
||||
err: tt.innerErr,
|
||||
}
|
||||
|
||||
checker := NewSessionAccessChecker(mock)
|
||||
if tt.fallbackRole != "" {
|
||||
checker = checker.WithFallbackRole(tt.fallbackRole)
|
||||
}
|
||||
|
||||
// Add requester to context
|
||||
testCtx := identity.WithRequester(ctx, tt.requester)
|
||||
|
||||
err := checker.Check(testCtx, req, "")
|
||||
|
||||
if tt.expectAllow {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
assert.True(t, apierrors.IsForbidden(err), "expected Forbidden error, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionAccessChecker_NoRequester(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: true},
|
||||
}
|
||||
|
||||
checker := NewSessionAccessChecker(mock)
|
||||
err := checker.Check(context.Background(), authlib.CheckRequest{}, "")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.True(t, apierrors.IsUnauthorized(err), "expected Unauthorized error")
|
||||
}
|
||||
|
||||
func TestSessionAccessChecker_WithFallbackRole_ImmutableOriginal(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: false},
|
||||
}
|
||||
|
||||
original := NewSessionAccessChecker(mock)
|
||||
withAdmin := original.WithFallbackRole(identity.RoleAdmin)
|
||||
withEditor := original.WithFallbackRole(identity.RoleEditor)
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), &mockRequester{
|
||||
orgRole: identity.RoleEditor,
|
||||
identityType: authlib.TypeUser,
|
||||
})
|
||||
|
||||
req := authlib.CheckRequest{}
|
||||
|
||||
// Original should deny (no fallback)
|
||||
err := original.Check(ctx, req, "")
|
||||
require.Error(t, err, "original should deny without fallback")
|
||||
|
||||
// WithAdmin should deny for editor
|
||||
err = withAdmin.Check(ctx, req, "")
|
||||
require.Error(t, err, "admin fallback should deny for editor")
|
||||
|
||||
// WithEditor should allow for editor
|
||||
err = withEditor.Check(ctx, req, "")
|
||||
require.NoError(t, err, "editor fallback should allow for editor")
|
||||
}
|
||||
|
||||
func TestSessionAccessChecker_WithFallbackRole_ChainedCalls(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: false},
|
||||
}
|
||||
|
||||
// Ensure chained WithFallbackRole calls work correctly
|
||||
checker := NewSessionAccessChecker(mock).
|
||||
WithFallbackRole(identity.RoleAdmin).
|
||||
WithFallbackRole(identity.RoleEditor) // This should override admin
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), &mockRequester{
|
||||
orgRole: identity.RoleEditor,
|
||||
identityType: authlib.TypeUser,
|
||||
})
|
||||
|
||||
err := checker.Check(ctx, authlib.CheckRequest{}, "")
|
||||
require.NoError(t, err, "last fallback (editor) should be used")
|
||||
}
|
||||
|
||||
func TestSessionAccessChecker_RealSignedInUser(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: false},
|
||||
}
|
||||
|
||||
checker := NewSessionAccessChecker(mock).WithFallbackRole(identity.RoleAdmin)
|
||||
|
||||
// Use a real SignedInUser
|
||||
signedInUser := &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: 1,
|
||||
OrgRole: identity.RoleAdmin,
|
||||
}
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), signedInUser)
|
||||
|
||||
err := checker.Check(ctx, authlib.CheckRequest{}, "")
|
||||
require.NoError(t, err, "admin user should be allowed via fallback")
|
||||
}
|
||||
|
||||
func TestSessionAccessChecker_FillsNamespace(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: true},
|
||||
}
|
||||
|
||||
checker := NewSessionAccessChecker(mock)
|
||||
|
||||
ctx := identity.WithRequester(context.Background(), &mockRequester{
|
||||
orgRole: identity.RoleAdmin,
|
||||
identityType: authlib.TypeUser,
|
||||
namespace: "org-123",
|
||||
})
|
||||
|
||||
// Request without namespace
|
||||
req := authlib.CheckRequest{
|
||||
Verb: "get",
|
||||
Group: "provisioning.grafana.app",
|
||||
Resource: "repositories",
|
||||
Name: "test-repo",
|
||||
// Namespace intentionally empty
|
||||
}
|
||||
|
||||
err := checker.Check(ctx, req, "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
92
apps/provisioning/pkg/auth/token_access_checker.go
Normal file
92
apps/provisioning/pkg/auth/token_access_checker.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
)
|
||||
|
||||
// tokenAccessChecker implements AccessChecker using access tokens from context.
|
||||
type tokenAccessChecker struct {
|
||||
inner authlib.AccessChecker
|
||||
}
|
||||
|
||||
// NewTokenAccessChecker creates an AccessChecker that gets identity from access tokens
|
||||
// via AuthInfoFrom(ctx). Role-based fallback is not supported.
|
||||
func NewTokenAccessChecker(inner authlib.AccessChecker) AccessChecker {
|
||||
return &tokenAccessChecker{inner: inner}
|
||||
}
|
||||
|
||||
// WithFallbackRole returns the same checker since fallback is not supported.
|
||||
func (c *tokenAccessChecker) WithFallbackRole(_ identity.RoleType) AccessChecker {
|
||||
return c
|
||||
}
|
||||
|
||||
// Check performs an access check using AuthInfo from context.
|
||||
// Returns nil if access is allowed, or an appropriate API error if denied.
|
||||
func (c *tokenAccessChecker) Check(ctx context.Context, req authlib.CheckRequest, folder string) error {
|
||||
logger := logging.FromContext(ctx).With("logger", "tokenAccessChecker")
|
||||
|
||||
// Get identity from access token in context
|
||||
id, ok := authlib.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
logger.Debug("no auth info in context",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"namespace", req.Namespace,
|
||||
)
|
||||
return apierrors.NewUnauthorized("no auth info in context")
|
||||
}
|
||||
|
||||
logger.Debug("checking access",
|
||||
"identityType", id.GetIdentityType(),
|
||||
"namespace", id.GetNamespace(),
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"group", req.Group,
|
||||
"name", req.Name,
|
||||
"folder", folder,
|
||||
)
|
||||
|
||||
// Fill in namespace from identity if not provided
|
||||
if req.Namespace == "" {
|
||||
req.Namespace = id.GetNamespace()
|
||||
}
|
||||
|
||||
// Perform the access check
|
||||
rsp, err := c.inner.Check(ctx, id, req, folder)
|
||||
|
||||
// Build the GroupResource for error messages
|
||||
gr := schema.GroupResource{Group: req.Group, Resource: req.Resource}
|
||||
|
||||
if err != nil {
|
||||
logger.Debug("access check error",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"error", err.Error(),
|
||||
)
|
||||
return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s.%s is forbidden: %w", req.Resource, req.Group, err))
|
||||
}
|
||||
if !rsp.Allowed {
|
||||
logger.Debug("access check denied",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
"group", req.Group,
|
||||
"identityType", id.GetIdentityType(),
|
||||
"allowed", rsp.Allowed,
|
||||
)
|
||||
return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("permission denied"))
|
||||
}
|
||||
|
||||
logger.Debug("access allowed",
|
||||
"resource", req.Resource,
|
||||
"verb", req.Verb,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
137
apps/provisioning/pkg/auth/token_access_checker_test.go
Normal file
137
apps/provisioning/pkg/auth/token_access_checker_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTokenAccessChecker_Check(t *testing.T) {
|
||||
req := authlib.CheckRequest{
|
||||
Verb: "get",
|
||||
Group: "provisioning.grafana.app",
|
||||
Resource: "repositories",
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
innerResponse authlib.CheckResponse
|
||||
innerErr error
|
||||
authInfo *identity.StaticRequester
|
||||
expectAllow bool
|
||||
}{
|
||||
{
|
||||
name: "allowed by checker",
|
||||
innerResponse: authlib.CheckResponse{Allowed: true},
|
||||
authInfo: &identity.StaticRequester{Type: authlib.TypeUser},
|
||||
expectAllow: true,
|
||||
},
|
||||
{
|
||||
name: "denied by checker",
|
||||
innerResponse: authlib.CheckResponse{Allowed: false},
|
||||
authInfo: &identity.StaticRequester{Type: authlib.TypeUser},
|
||||
expectAllow: false,
|
||||
},
|
||||
{
|
||||
name: "error from checker",
|
||||
innerErr: errors.New("access check failed"),
|
||||
authInfo: &identity.StaticRequester{Type: authlib.TypeUser},
|
||||
expectAllow: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: tt.innerResponse,
|
||||
err: tt.innerErr,
|
||||
}
|
||||
|
||||
checker := NewTokenAccessChecker(mock)
|
||||
|
||||
// Add auth info to context
|
||||
testCtx := authlib.WithAuthInfo(context.Background(), tt.authInfo)
|
||||
|
||||
err := checker.Check(testCtx, req, "")
|
||||
|
||||
if tt.expectAllow {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
assert.True(t, apierrors.IsForbidden(err), "expected Forbidden error, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenAccessChecker_NoAuthInfo(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: true},
|
||||
}
|
||||
|
||||
checker := NewTokenAccessChecker(mock)
|
||||
err := checker.Check(context.Background(), authlib.CheckRequest{}, "")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.True(t, apierrors.IsUnauthorized(err), "expected Unauthorized error")
|
||||
}
|
||||
|
||||
func TestTokenAccessChecker_WithFallbackRole_IsNoOp(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: false},
|
||||
}
|
||||
|
||||
checker := NewTokenAccessChecker(mock)
|
||||
checkerWithFallback := checker.WithFallbackRole(identity.RoleAdmin)
|
||||
|
||||
// They should be the same instance
|
||||
assert.Same(t, checker, checkerWithFallback, "WithFallbackRole should return same instance")
|
||||
}
|
||||
|
||||
func TestTokenAccessChecker_FillsNamespace(t *testing.T) {
|
||||
mock := &mockInnerAccessChecker{
|
||||
response: authlib.CheckResponse{Allowed: true},
|
||||
}
|
||||
|
||||
checker := NewTokenAccessChecker(mock)
|
||||
|
||||
ctx := authlib.WithAuthInfo(context.Background(), &identity.StaticRequester{
|
||||
Type: authlib.TypeUser,
|
||||
Namespace: "org-123",
|
||||
})
|
||||
|
||||
// Request without namespace
|
||||
req := authlib.CheckRequest{
|
||||
Verb: "get",
|
||||
Group: "provisioning.grafana.app",
|
||||
Resource: "repositories",
|
||||
Name: "test-repo",
|
||||
// Namespace intentionally empty
|
||||
}
|
||||
|
||||
err := checker.Check(ctx, req, "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// mockInnerAccessChecker implements authlib.AccessChecker for testing.
|
||||
type mockInnerAccessChecker struct {
|
||||
response authlib.CheckResponse
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockInnerAccessChecker) Check(_ context.Context, _ authlib.AuthInfo, _ authlib.CheckRequest, _ string) (authlib.CheckResponse, error) {
|
||||
return m.response, m.err
|
||||
}
|
||||
|
||||
func (m *mockInnerAccessChecker) Compile(_ context.Context, _ authlib.AuthInfo, _ authlib.ListRequest) (authlib.ItemChecker, authlib.Zookie, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -1653,6 +1653,11 @@ loki_basic_auth_password =
|
||||
# Accepts duration formats like: 30s, 1m, 1h.
|
||||
rule_query_offset = 1m
|
||||
|
||||
# Default data source UID to use for query execution when importing Prometheus rules.
|
||||
# This default is used when the X-Grafana-Alerting-Datasource-UID header is not provided.
|
||||
# If not set, the header becomes required.
|
||||
default_datasource_uid =
|
||||
|
||||
[recording_rules]
|
||||
# Enable recording rules.
|
||||
enabled = true
|
||||
|
||||
@@ -1615,6 +1615,11 @@ max_annotations_to_keep =
|
||||
# Accepts duration formats like: 30s, 1m, 1h.
|
||||
rule_query_offset = 1m
|
||||
|
||||
# Default data source UID to use for query execution when importing Prometheus rules.
|
||||
# This default is used when the X-Grafana-Alerting-Datasource-UID header is not provided.
|
||||
# If not set, the header becomes required.
|
||||
default_datasource_uid =
|
||||
|
||||
#################################### Recording Rules #####################
|
||||
[recording_rules]
|
||||
# Enable recording rules.
|
||||
|
||||
@@ -600,6 +600,20 @@
|
||||
"stringInput": "null,null"
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "convertFieldType",
|
||||
"options": {
|
||||
"fields": {},
|
||||
"conversions": [
|
||||
{
|
||||
"targetField": "A-series",
|
||||
"destinationType": "number"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "Only nulls and no user set min & max",
|
||||
"type": "gauge"
|
||||
},
|
||||
|
||||
@@ -71,13 +71,12 @@
|
||||
"id": 1,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -148,13 +147,12 @@
|
||||
"id": 4,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -225,13 +223,12 @@
|
||||
"id": 3,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -299,93 +296,15 @@
|
||||
"x": 12,
|
||||
"y": 1
|
||||
},
|
||||
"id": 5,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["lastNotNull"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"segmentCount": 1,
|
||||
"segmentSpacing": 0.3,
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "1",
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"max": 100,
|
||||
"min": 1,
|
||||
"noise": 22,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"spread": 22,
|
||||
"startValue": 1
|
||||
}
|
||||
],
|
||||
"title": "Spotlight",
|
||||
"type": "radialbar"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "grafana-testdata-datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 16,
|
||||
"y": 1
|
||||
},
|
||||
"id": 8,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -450,19 +369,18 @@
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 7
|
||||
"x": 16,
|
||||
"y": 1
|
||||
},
|
||||
"id": 22,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -527,19 +445,18 @@
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 4,
|
||||
"y": 7
|
||||
"x": 20,
|
||||
"y": 1
|
||||
},
|
||||
"id": 23,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -579,7 +496,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 13
|
||||
"y": 7
|
||||
},
|
||||
"id": 17,
|
||||
"panels": [],
|
||||
@@ -616,20 +533,19 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 14
|
||||
"y": 8
|
||||
},
|
||||
"id": 18,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.1,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -693,20 +609,19 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 5,
|
||||
"y": 14
|
||||
"w": 4,
|
||||
"x": 4,
|
||||
"y": 8
|
||||
},
|
||||
"id": 19,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.32,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -770,20 +685,19 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 10,
|
||||
"y": 14
|
||||
"w": 4,
|
||||
"x": 8,
|
||||
"y": 8
|
||||
},
|
||||
"id": 20,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.57,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -847,20 +761,19 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 15,
|
||||
"y": 14
|
||||
"w": 4,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
},
|
||||
"id": 21,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidthFactor": 0.8,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": false,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -900,7 +813,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 20
|
||||
"y": 14
|
||||
},
|
||||
"id": 24,
|
||||
"panels": [],
|
||||
@@ -941,20 +854,19 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 21
|
||||
"y": 15
|
||||
},
|
||||
"id": 25,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1018,20 +930,19 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 21
|
||||
"w": 4,
|
||||
"x": 4,
|
||||
"y": 15
|
||||
},
|
||||
"id": 26,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1095,20 +1006,19 @@
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 6,
|
||||
"w": 5,
|
||||
"x": 12,
|
||||
"y": 21
|
||||
"w": 4,
|
||||
"x": 8,
|
||||
"y": 15
|
||||
},
|
||||
"id": 29,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1171,21 +1081,20 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 27
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 12,
|
||||
"y": 15
|
||||
},
|
||||
"id": 30,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1248,21 +1157,20 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 27
|
||||
"h": 6,
|
||||
"w": 4,
|
||||
"x": 16,
|
||||
"y": 15
|
||||
},
|
||||
"id": 28,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.72,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": false,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": false
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1298,7 +1206,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 34
|
||||
"y": 21
|
||||
},
|
||||
"id": 31,
|
||||
"panels": [],
|
||||
@@ -1345,18 +1253,17 @@
|
||||
"h": 10,
|
||||
"w": 7,
|
||||
"x": 0,
|
||||
"y": 35
|
||||
"y": 22
|
||||
},
|
||||
"id": 32,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1426,18 +1333,17 @@
|
||||
"h": 10,
|
||||
"w": 7,
|
||||
"x": 7,
|
||||
"y": 35
|
||||
"y": 22
|
||||
},
|
||||
"id": 34,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1507,18 +1413,17 @@
|
||||
"h": 10,
|
||||
"w": 6,
|
||||
"x": 14,
|
||||
"y": 35
|
||||
"y": 22
|
||||
},
|
||||
"id": 33,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.9,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1554,7 +1459,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 45
|
||||
"y": 32
|
||||
},
|
||||
"id": 6,
|
||||
"panels": [],
|
||||
@@ -1595,20 +1500,20 @@
|
||||
"h": 6,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 46
|
||||
"y": 33
|
||||
},
|
||||
"id": 9,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"endpointMarker": "glow",
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -1621,8 +1526,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1677,19 +1581,18 @@
|
||||
"h": 6,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 52
|
||||
"y": 39
|
||||
},
|
||||
"id": 11,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.4,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -1703,8 +1606,7 @@
|
||||
"shape": "gauge",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": true,
|
||||
"spotlight": true
|
||||
"sparkline": true
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1731,7 +1633,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 58
|
||||
"y": 45
|
||||
},
|
||||
"id": 12,
|
||||
"panels": [],
|
||||
@@ -1773,19 +1675,18 @@
|
||||
"h": 7,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 13,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -1799,8 +1700,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1818,6 +1718,22 @@
|
||||
"startValue": 0
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "calculateField",
|
||||
"options": {
|
||||
"mode": "unary",
|
||||
"reduce": {
|
||||
"reducer": "sum"
|
||||
},
|
||||
"replaceFields": true,
|
||||
"unary": {
|
||||
"operator": "round",
|
||||
"fieldName": "A-series"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "Active gateways",
|
||||
"type": "radialbar"
|
||||
},
|
||||
@@ -1856,19 +1772,18 @@
|
||||
"h": 7,
|
||||
"w": 5,
|
||||
"x": 4,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 14,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.49,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -1882,8 +1797,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -1901,6 +1815,22 @@
|
||||
"startValue": 0
|
||||
}
|
||||
],
|
||||
"transformations": [
|
||||
{
|
||||
"id": "calculateField",
|
||||
"options": {
|
||||
"mode": "unary",
|
||||
"reduce": {
|
||||
"reducer": "sum"
|
||||
},
|
||||
"replaceFields": true,
|
||||
"unary": {
|
||||
"operator": "round",
|
||||
"fieldName": "A-series"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "Active pods",
|
||||
"type": "radialbar"
|
||||
},
|
||||
@@ -1938,19 +1868,18 @@
|
||||
"h": 7,
|
||||
"w": 5,
|
||||
"x": 9,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 15,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.84,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -1964,8 +1893,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2020,19 +1948,18 @@
|
||||
"h": 7,
|
||||
"w": 6,
|
||||
"x": 14,
|
||||
"y": 59
|
||||
"y": 46
|
||||
},
|
||||
"id": 16,
|
||||
"maxDataPoints": 20,
|
||||
"options": {
|
||||
"barShape": "rounded",
|
||||
"barWidth": 12,
|
||||
"barWidthFactor": 0.66,
|
||||
"effects": {
|
||||
"barGlow": true,
|
||||
"centerGlow": true,
|
||||
"gradient": true,
|
||||
"rounded": true,
|
||||
"spotlight": true
|
||||
"gradient": true
|
||||
},
|
||||
"glow": "both",
|
||||
"orientation": "auto",
|
||||
@@ -2046,8 +1973,7 @@
|
||||
"shape": "circle",
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": false,
|
||||
"sparkline": false,
|
||||
"spotlight": true
|
||||
"sparkline": false
|
||||
},
|
||||
"pluginVersion": "13.0.0-pre",
|
||||
"targets": [
|
||||
@@ -2074,7 +2000,7 @@
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 66
|
||||
"y": 53
|
||||
},
|
||||
"id": 35,
|
||||
"panels": [],
|
||||
@@ -2105,20 +2031,19 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"h": 5,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 67
|
||||
"y": 54
|
||||
},
|
||||
"id": 36,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.5,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2171,20 +2096,19 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 67
|
||||
"h": 5,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 54
|
||||
},
|
||||
"id": 37,
|
||||
"options": {
|
||||
"barShape": "flat",
|
||||
"barWidthFactor": 0.5,
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"gradient": true,
|
||||
"rounded": false,
|
||||
"spotlight": false
|
||||
"gradient": true
|
||||
},
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
@@ -2224,5 +2148,6 @@
|
||||
"timezone": "browser",
|
||||
"title": "Panel tests - Gauge (new)",
|
||||
"uid": "panel-tests-gauge-new",
|
||||
"version": 9
|
||||
"version": 22,
|
||||
"weekStart": ""
|
||||
}
|
||||
|
||||
@@ -474,6 +474,7 @@
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"displayName": "My gauge",
|
||||
"minVizHeight": 75,
|
||||
"minVizWidth": 75,
|
||||
"orientation": "auto",
|
||||
@@ -956,8 +957,6 @@
|
||||
"effects": {
|
||||
"barGlow": false,
|
||||
"centerGlow": false,
|
||||
"rounded": false,
|
||||
"spotlight": false,
|
||||
"gradient": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ To convert data source-managed alert rules to Grafana managed alerts:
|
||||
|
||||
Pausing stops alert rule evaluation behavior for the newly created Grafana-managed alert rules.
|
||||
|
||||
9. (Optional) In the **Target data source** of the **Recording rules** section, you can select the data source that the imported recording rules will query. By default, it is the data source selected in the **Data source** dropdown.
|
||||
9. (Optional) In the **Target data source** of the **Recording rules** section, you can select the data source to which the imported recording rules will write metrics. By default, it is the data source selected in the **Data source** dropdown.
|
||||
|
||||
10. Click **Import**.
|
||||
|
||||
@@ -242,6 +242,8 @@ Set to `true` to import recording rules in paused state.
|
||||
|
||||
The UID of the data source to use for alert rule queries.
|
||||
|
||||
If not specified in the header, Grafana uses the configured default from `unified_alerting.prometheus_conversion.default_datasource_uid`. If neither the header nor the configuration option is provided, the request fails.
|
||||
|
||||
#### `X-Grafana-Alerting-Target-Datasource-UID`
|
||||
|
||||
The UID of the target data source for recording rules. If not specified, the value from `X-Grafana-Alerting-Datasource-UID` is used.
|
||||
|
||||
@@ -128,35 +128,48 @@ The set up process verifies the path and provides an error message if a problem
|
||||
|
||||
#### Synchronization limitations
|
||||
|
||||
Full instance sync is not available in Grafana Cloud.
|
||||
{{< admonition type="caution" >}}
|
||||
|
||||
In Grafana OSS/Enterprise:
|
||||
Full instance sync is not available in Grafana Cloud and is experimental and unsupported in Grafana OSS/Enterprise.
|
||||
|
||||
{{< /admonition >}}
|
||||
|
||||
To have access to full instance sync you must explicitly enable the option.
|
||||
|
||||
The following applies:
|
||||
|
||||
- If you try to perform a full instance sync with resources that contain alerts or panels, the connection will be blocked.
|
||||
- You won't be able to create new alerts or library panels after setup is completed.
|
||||
- If you opted for full instance sync and want to use alerts and library panels, you'll have to delete the provisioned repository and connect again with folder sync.
|
||||
|
||||
#### Set up synchronization
|
||||
|
||||
Choose to either sync your entire organization resources with external storage, or to sync certain resources to a new Grafana folder (with up to 10 connections).
|
||||
You can sync external resources into a new folder without affecting the rest of your instance.
|
||||
|
||||
- Choose **Sync all resources with external storage** if you want to sync and manage your entire Grafana instance through external storage. With this option, all of your dashboards are synced to that one repository. You can only have one provisioned connection with this selection, and you won't have the option of setting up additional repositories to connect to.
|
||||
To set up synchronization:
|
||||
|
||||
- Choose **Sync external storage to new Grafana folder** to sync external resources into a new folder without affecting the rest of your instance. You can repeat this process for up to 10 connections.
|
||||
1. Select which resources you want to sync.
|
||||
|
||||
Next, enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI.
|
||||
1. Enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI.
|
||||
|
||||
Click **Synchronize** to continue.
|
||||
1. Click **Synchronize** to continue.
|
||||
|
||||
1. You can repeat this process for up to 10 connections.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
|
||||
Optionally, you can export any unmanaged resources into the provisioned folder. See how in [Synchronize with external storage](#synchronize-with-external-storage).
|
||||
|
||||
{{< /admonition >}}
|
||||
|
||||
### Synchronize with external storage
|
||||
|
||||
After this one time step, all future updates are automatically saved to the local file path and provisioned back to the instance.
|
||||
In this step you proceed to synchronize the resources selected in the previous step. Optionally, you can check the **Migrate existing resources** box to migrate your unmanaged dashboards to the provisioned folder.
|
||||
|
||||
During the initial synchronization, your dashboards will be temporarily unavailable. No data or configurations will be lost.
|
||||
Select **Begin synchronization** to start the process. After this one time step, all future updates are automatically saved to the local file path and provisioned back to the instance.
|
||||
|
||||
Note that during the initial synchronization, your dashboards will be temporarily unavailable. No data or configurations will be lost.
|
||||
How long the process takes depends upon the number of resources involved.
|
||||
|
||||
Select **Begin synchronization** to start the process.
|
||||
|
||||
### Choose additional settings
|
||||
|
||||
If you wish, you can make any files synchronized as as **Read only** so no changes can be made to the resources through Grafana.
|
||||
|
||||
@@ -132,17 +132,35 @@ To connect your GitHub repository:
|
||||
|
||||
### Choose what to synchronize
|
||||
|
||||
In this step, you can decide which elements to synchronize. The available options depend on the status of your Grafana instance:
|
||||
|
||||
- If the instance contains resources in an incompatible data format, you'll have to migrate all the data using instance sync. Folder sync won't be supported.
|
||||
- If there's already another connection using folder sync, instance sync won't be offered.
|
||||
You can sync external resources into a new folder without affecting the rest of your instance.
|
||||
|
||||
To set up synchronization:
|
||||
|
||||
- Choose **Sync all resources with external storage** if you want to sync and manage your entire Grafana instance through external storage. With this option, all of your dashboards are synced to that one repository. You can only have one provisioned connection with this selection, and you won't have the option of setting up additional repositories to connect to.
|
||||
- Choose **Sync external storage to new Grafana folder** to sync external resources into a new folder without affecting the rest of your instance. You can repeat this process for up to 10 connections.
|
||||
1. Select which resources you want to sync.
|
||||
|
||||
Next, enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI. Click **Synchronize** to continue.
|
||||
1. Enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI.
|
||||
|
||||
1. Click **Synchronize** to continue.
|
||||
|
||||
1. You can repeat this process for up to 10 connections.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
|
||||
Optionally, you can export any unmanaged resources into the provisioned folder. See how in [Synchronize with external storage](#synchronize-with-external-storage).
|
||||
|
||||
{{< /admonition >}}
|
||||
|
||||
#### Full instance sync
|
||||
|
||||
Full instance sync is not available in Grafana Cloud and is experimental and unsupported in Grafana OSS/Enterprise.
|
||||
|
||||
To have access to this option you must enable experimental instance sync on purpose.
|
||||
|
||||
### Synchronize with external storage
|
||||
|
||||
After this one time step, all future updates are automatically saved to the Git repository and provisioned back to the instance.
|
||||
|
||||
Check the **Migrate existing resources** box to migrate your unmanaged dashboards to the provisioned folder.
|
||||
|
||||
### Choose additional settings
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ Using Git Sync, you can:
|
||||
|
||||
{{< admonition type="caution" >}}
|
||||
|
||||
Git Sync only works with specific folders for the moment. Full-instance sync is not currently supported.
|
||||
Full instance sync is not available in Grafana Cloud and is experimental and unsupported in Grafana OSS/Enterprise.
|
||||
|
||||
{{< /admonition >}}
|
||||
|
||||
@@ -84,7 +84,7 @@ Refer to [Requirements](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/obser
|
||||
|
||||
- You can only sync dashboards and folders. Refer to [Supported resources](#supported-resources) for more information.
|
||||
- If you're using Git Sync in Grafana OSS and Grafana Enterprise, some resources might be in an incompatible data format and won't be synced.
|
||||
- Full-instance sync is not available in Grafana Cloud and has limitations in Grafana OSS and Grafana Enterprise. Refer to [Choose what to synchronize](../git-sync-setup/#choose-what-to-synchronize) for more details.
|
||||
- Full-instance sync is not available in Grafana Cloud and is experimental in Grafana OSS and Grafana Enterprise. Refer to [Choose what to synchronize](../git-sync-setup/#choose-what-to-synchronize) for more details.
|
||||
- When migrating to full instance sync, during the synchronization process your resources will be temporarily unavailable. No one will be able to create, edit, or delete resources during this process.
|
||||
- If you want to manage existing resources with Git Sync, you need to save them as JSON files and commit them to the synced repository. Open a PR to import, copy, move, or save a dashboard.
|
||||
- Restoring resources from the UI is currently not possible. As an alternative, you can restore dashboards directly in your GitHub repository by raising a PR, and they will be updated in Grafana.
|
||||
|
||||
@@ -112,6 +112,12 @@ For example, this video demonstrates the visual Prometheus query builder:
|
||||
|
||||
For general information about querying in Grafana, and common options and user interface elements across all query editors, refer to [Query and transform data](ref:query-transform-data).
|
||||
|
||||
## Build a dashboard from the data source
|
||||
|
||||
After you've configured a data source, you can start creating a dashboard directly from it, by clicking the **Build a dashboard** button.
|
||||
|
||||
For more information, refer to [Begin dashboard creation from data source configuration](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/visualizations/dashboards/build-dashboards/create-dashboard/#begin-dashboard-creation-from-connections).
|
||||
|
||||
## Special data sources
|
||||
|
||||
Grafana includes three special data sources:
|
||||
|
||||
@@ -2052,6 +2052,10 @@ This section applies only to rules imported as Grafana-managed rules. For more i
|
||||
|
||||
Set the query offset to imported Grafana-managed rules when `query_offset` is not defined in the original rule group configuration. The default value is `1m`.
|
||||
|
||||
#### `default_datasource_uid`
|
||||
|
||||
Set the default data source UID to use for query execution when importing Prometheus rules. Grafana uses this default when the `X-Grafana-Alerting-Datasource-UID` header isn't provided during import. If this option isn't set, the header becomes required. The default value is empty.
|
||||
|
||||
<hr>
|
||||
|
||||
### `[annotations]`
|
||||
|
||||
@@ -99,7 +99,7 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
|
||||
- Understand the query language of the target data source.
|
||||
- Ensure that data source for which you are writing a query has been added. For more information about adding a data source, refer to [Add a data source](ref:add-a-data-source) if you need instructions.
|
||||
|
||||
**To create a dashboard**:
|
||||
To create a dashboard, follow these steps:
|
||||
|
||||
{{< shared id="create-dashboard" >}}
|
||||
|
||||
@@ -171,6 +171,28 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
|
||||
|
||||
Now, when you want to make more changes to the saved dashboard, click **Edit** in the top-right corner.
|
||||
|
||||
### Begin dashboard creation from data source configuration
|
||||
|
||||
You can start the process of creating a dashboard directly from a data source rather than from the **Dashboards** page.
|
||||
|
||||
To begin building a dashboard directly from a data source, follow these steps:
|
||||
|
||||
1. Navigate to **Connections > Data sources**.
|
||||
1. On the row of the data source for which you want to build a dashboard, click **Build a dashboard**.
|
||||
|
||||
The empty dashboard page opens.
|
||||
|
||||
1. Do one of the following:
|
||||
- Click **+Add visualization** to configure all the elements of the new dashboard.
|
||||
- Select one of the suggested dashboards by clicking its **Use dashboard** button. This can be helpful when you're not sure how to most effectively visualize your data.
|
||||
The suggested dashboards are specific to your data source type (for example, Prometheus, Loki, or Elasticsearch). If there are more than three dashboard suggestions, you can click **View all** to see the rest of them.
|
||||
|
||||

|
||||
|
||||
{{< docs/public-preview product="Suggested dashboards" >}}
|
||||
|
||||
1. Complete the rest of the dashboard configuration. For more detailed steps, refer to [Create a dashboard](#create-a-dashboard), beginning at step five.
|
||||
|
||||
## Copy a dashboard
|
||||
|
||||
To copy a dashboard, follow these steps:
|
||||
|
||||
@@ -98,7 +98,7 @@ You can share dashboards in the following ways:
|
||||
- [As a report](#schedule-a-report)
|
||||
- [As a snapshot](#share-a-snapshot)
|
||||
- [As a PDF export](#export-a-dashboard-as-pdf)
|
||||
- [As a JSON file export](#export-a-dashboard-as-json)
|
||||
- [As a JSON file export](#export-a-dashboard-as-code)
|
||||
- [As an image export](#export-a-dashboard-as-an-image)
|
||||
|
||||
When you share a dashboard externally as a link or by email, those dashboards are included in a list of your shared dashboards. To view the list and manage these dashboards, navigate to **Dashboards > Shared dashboards**.
|
||||
|
||||
271
e2e-playwright/alerting-suite/saved-searches.spec.ts
Normal file
271
e2e-playwright/alerting-suite/saved-searches.spec.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
/**
|
||||
* UI selectors for Saved Searches e2e tests.
|
||||
* Each selector is a function that takes the page and returns a locator.
|
||||
*/
|
||||
const ui = {
|
||||
// Main elements
|
||||
savedSearchesButton: (page: Page) => page.getByRole('button', { name: /saved searches/i }),
|
||||
dropdown: (page: Page) => page.getByRole('dialog', { name: /saved searches/i }),
|
||||
searchInput: (page: Page) => page.getByTestId('search-query-input'),
|
||||
|
||||
// Save functionality
|
||||
saveButton: (page: Page) => page.getByRole('button', { name: /save current search/i }),
|
||||
saveConfirmButton: (page: Page) => page.getByRole('button', { name: /^save$/i }),
|
||||
saveNameInput: (page: Page) => page.getByPlaceholder(/enter a name/i),
|
||||
|
||||
// Action menu
|
||||
actionsButton: (page: Page) => page.getByRole('button', { name: /actions/i }),
|
||||
renameMenuItem: (page: Page) => page.getByText(/rename/i),
|
||||
deleteMenuItem: (page: Page) => page.getByText(/^delete$/i),
|
||||
setAsDefaultMenuItem: (page: Page) => page.getByText(/set as default/i),
|
||||
deleteConfirmButton: (page: Page) => page.getByRole('button', { name: /^delete$/i }),
|
||||
|
||||
// Indicators
|
||||
emptyState: (page: Page) => page.getByText(/no saved searches/i),
|
||||
defaultIcon: (page: Page) => page.locator('[title="Default search"]'),
|
||||
duplicateError: (page: Page) => page.getByText(/already exists/i),
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to clear saved searches storage.
|
||||
* UserStorage uses localStorage as fallback, so we clear both potential keys.
|
||||
*/
|
||||
async function clearSavedSearches(page: Page) {
|
||||
await page.evaluate(() => {
|
||||
// Clear localStorage keys that might contain saved searches
|
||||
// UserStorage stores under 'grafana.userstorage.alerting' pattern
|
||||
const keysToRemove = Object.keys(localStorage).filter(
|
||||
(key) => key.includes('alerting') && (key.includes('savedSearches') || key.includes('userstorage'))
|
||||
);
|
||||
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
||||
|
||||
// Also clear session storage visited flag
|
||||
const sessionKeysToRemove = Object.keys(sessionStorage).filter((key) => key.includes('alerting'));
|
||||
sessionKeysToRemove.forEach((key) => sessionStorage.removeItem(key));
|
||||
});
|
||||
}
|
||||
|
||||
test.describe(
|
||||
'Alert Rules - Saved Searches',
|
||||
{
|
||||
tag: ['@alerting'],
|
||||
},
|
||||
() => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Clear any saved searches from previous tests before navigating
|
||||
await page.goto('/alerting/list');
|
||||
await clearSavedSearches(page);
|
||||
await page.reload();
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// Clean up saved searches after each test
|
||||
await clearSavedSearches(page);
|
||||
});
|
||||
|
||||
test('should display Saved searches button', async ({ page }) => {
|
||||
await expect(ui.savedSearchesButton(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should open dropdown when clicking Saved searches button', async ({ page }) => {
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await expect(ui.dropdown(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show empty state when no saved searches exist', async ({ page }) => {
|
||||
// Storage is cleared in beforeEach, so we should see empty state
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await expect(ui.emptyState(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should enable Save current search button when search query is entered', async ({ page }) => {
|
||||
// Enter a search query
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
// Open saved searches
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await expect(ui.saveButton(page)).toBeEnabled();
|
||||
});
|
||||
|
||||
test('should disable Save current search button when search query is empty', async ({ page }) => {
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await expect(ui.saveButton(page)).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should save a new search', async ({ page }) => {
|
||||
// Enter a search query
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
// Open saved searches
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
// Click save button
|
||||
await ui.saveButton(page).click();
|
||||
|
||||
// Enter name and save
|
||||
await ui.saveNameInput(page).fill('My Firing Rules');
|
||||
await ui.saveConfirmButton(page).click();
|
||||
|
||||
// Verify the saved search appears in the list
|
||||
await expect(page.getByText('My Firing Rules')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show validation error for duplicate name', async ({ page }) => {
|
||||
// First save a search
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await ui.saveButton(page).click();
|
||||
|
||||
await ui.saveNameInput(page).fill('Duplicate Test');
|
||||
await ui.saveConfirmButton(page).click();
|
||||
|
||||
// Try to save another with the same name
|
||||
await ui.saveButton(page).click();
|
||||
await ui.saveNameInput(page).fill('Duplicate Test');
|
||||
await ui.saveConfirmButton(page).click();
|
||||
|
||||
// Verify validation error
|
||||
await expect(ui.duplicateError(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should apply a saved search', async ({ page }) => {
|
||||
// Create a saved search first
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await ui.saveButton(page).click();
|
||||
|
||||
await ui.saveNameInput(page).fill('Apply Test');
|
||||
await ui.saveConfirmButton(page).click();
|
||||
|
||||
// Clear the search
|
||||
await ui.searchInput(page).clear();
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
// Apply the saved search
|
||||
await ui.savedSearchesButton(page).click();
|
||||
await page.getByRole('button', { name: /apply search.*apply test/i }).click();
|
||||
|
||||
// Verify the search input is updated
|
||||
await expect(ui.searchInput(page)).toHaveValue('state:firing');
|
||||
});
|
||||
|
||||
test('should rename a saved search', async ({ page }) => {
|
||||
// Create a saved search
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await ui.saveButton(page).click();
|
||||
|
||||
await ui.saveNameInput(page).fill('Original Name');
|
||||
await ui.saveConfirmButton(page).click();
|
||||
|
||||
// Open action menu and click rename
|
||||
await ui.actionsButton(page).click();
|
||||
await ui.renameMenuItem(page).click();
|
||||
|
||||
// Enter new name
|
||||
const renameInput = page.getByDisplayValue('Original Name');
|
||||
await renameInput.clear();
|
||||
await renameInput.fill('Renamed Search');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// Verify the name was updated
|
||||
await expect(page.getByText('Renamed Search')).toBeVisible();
|
||||
await expect(page.getByText('Original Name')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should delete a saved search', async ({ page }) => {
|
||||
// Create a saved search
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await ui.saveButton(page).click();
|
||||
|
||||
await ui.saveNameInput(page).fill('To Delete');
|
||||
await ui.saveConfirmButton(page).click();
|
||||
|
||||
// Verify it was saved
|
||||
await expect(page.getByText('To Delete')).toBeVisible();
|
||||
|
||||
// Open action menu and click delete
|
||||
await ui.actionsButton(page).click();
|
||||
await ui.deleteMenuItem(page).click();
|
||||
|
||||
// Confirm delete
|
||||
await ui.deleteConfirmButton(page).click();
|
||||
|
||||
// Verify it was deleted
|
||||
await expect(page.getByText('To Delete')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should set a search as default', async ({ page }) => {
|
||||
// Create a saved search
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await ui.saveButton(page).click();
|
||||
|
||||
await ui.saveNameInput(page).fill('Default Test');
|
||||
await ui.saveConfirmButton(page).click();
|
||||
|
||||
// Set as default
|
||||
await ui.actionsButton(page).click();
|
||||
await ui.setAsDefaultMenuItem(page).click();
|
||||
|
||||
// Verify the star icon appears (indicating default)
|
||||
await expect(ui.defaultIcon(page)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should close dropdown when pressing Escape', async ({ page }) => {
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
await expect(ui.dropdown(page)).toBeVisible();
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await expect(ui.dropdown(page)).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should cancel save mode when pressing Escape', async ({ page }) => {
|
||||
// Enter a search query
|
||||
await ui.searchInput(page).fill('state:firing');
|
||||
await ui.searchInput(page).press('Enter');
|
||||
|
||||
await ui.savedSearchesButton(page).click();
|
||||
|
||||
// Start save mode
|
||||
await ui.saveButton(page).click();
|
||||
|
||||
await expect(ui.saveNameInput(page)).toBeVisible();
|
||||
|
||||
// Press Escape to cancel
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Verify we're back to list mode
|
||||
await expect(ui.saveNameInput(page)).not.toBeVisible();
|
||||
await expect(ui.saveButton(page)).toBeVisible();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -10,7 +10,7 @@ const NUM_NESTED_DASHBOARDS = 60;
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import testDashboard from '../dashboards/TestDashboard.json';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ test.use({
|
||||
scenes: true,
|
||||
sharingDashboardImage: true, // Enable the export image feature
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { test, expect } from '@grafana/plugin-e2e';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { test, expect } from '@grafana/plugin-e2e';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import testDashboard from '../dashboards/DataLinkWithoutSlugTest.json';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import testDashboard from '../dashboards/DashboardLiveTest.json';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { test, expect } from '@grafana/plugin-e2e';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardScene: false, // this test is for the old sharing modal only used when scenes is turned off
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { test, expect } from '@grafana/plugin-e2e';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardScene: false, // this test is for the old sharing modal only used when scenes is turned off
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ test.use({
|
||||
featureToggles: {
|
||||
scenes: true,
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ test.use({
|
||||
featureToggles: {
|
||||
scenes: true,
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ test.use({
|
||||
featureToggles: {
|
||||
scenes: true,
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ test.use({
|
||||
timezoneId: 'Pacific/Easter',
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ const TIMEZONE_DASHBOARD_UID = 'd41dbaa2-a39e-4536-ab2b-caca52f1a9c8';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ test.use({
|
||||
},
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { test, expect } from '@grafana/plugin-e2e';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const PAGE_UNDER_TEST = 'edediimbjhdz4b/a-tall-dashboard';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import testDashboard from '../dashboards/TestDashboard.json';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const PAGE_UNDER_TEST = '-Y-tnEDWk/templating-nested-template-variables';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const DASHBOARD_NAME = 'Test variable output';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ async function assertPreviewValues(
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const DASHBOARD_NAME = 'Test variable output';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ async function assertPreviewValues(
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const DASHBOARD_NAME = 'Templating - Nested Template Variables';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const DASHBOARD_NAME = 'Test variable output';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const PAGE_UNDER_TEST = 'WVpf2jp7z/repeating-a-panel-horizontally';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const PAGE_UNDER_TEST = 'OY8Ghjt7k/repeating-a-panel-vertically';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const PAGE_UNDER_TEST = 'dtpl2Ctnk/repeating-an-empty-row';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const PAGE_UNDER_TEST = '-Y-tnEDWk/templating-nested-template-variables';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const DASHBOARD_UID = 'ZqZnVvFZz';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardScene: false, // this test is for the old sharing modal only used when scenes is turned off
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ const DASHBOARD_UID = 'yBCC3aKGk';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const PAGE_UNDER_TEST = 'AejrN1AMz';
|
||||
test.use({
|
||||
featureToggles: {
|
||||
kubernetesDashboards: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
kubernetesDashboardsV2: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
dashboardNewLayouts: process.env.FORCE_V2_DASHBOARDS_API === 'true',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,18 +2,16 @@ import { Locator } from '@playwright/test';
|
||||
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import { setVisualization } from './vizpicker-utils';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
canvasPanelPanZoom: true,
|
||||
},
|
||||
});
|
||||
test.describe('Canvas Panel - Scene Tests', () => {
|
||||
test.beforeEach(async ({ page, gotoDashboardPage, selectors }) => {
|
||||
test.beforeEach(async ({ page, gotoDashboardPage }) => {
|
||||
const dashboardPage = await gotoDashboardPage({});
|
||||
const panelEditPage = await dashboardPage.addPanel();
|
||||
await setVisualization(panelEditPage, 'Canvas', selectors);
|
||||
await panelEditPage.setVisualization('Canvas');
|
||||
|
||||
// Wait for canvas panel to load
|
||||
await page.waitForSelector('[data-testid="canvas-scene-pan-zoom"]', { timeout: 10000 });
|
||||
|
||||
101
e2e-playwright/panels-suite/gauge.spec.ts
Normal file
101
e2e-playwright/panels-suite/gauge.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
// this test requires a larger viewport so all gauge panels load properly
|
||||
test.use({
|
||||
featureToggles: { newGauge: true },
|
||||
viewport: { width: 1280, height: 3000 },
|
||||
});
|
||||
|
||||
const OLD_GAUGES_DASHBOARD_UID = '_5rDmaQiz';
|
||||
const NEW_GAUGES_DASHBOARD_UID = 'panel-tests-gauge-new';
|
||||
|
||||
test.describe(
|
||||
'Gauge Panel',
|
||||
{
|
||||
tag: ['@panels', '@gauge'],
|
||||
},
|
||||
() => {
|
||||
test('successfully migrates all gauge panels', async ({ gotoDashboardPage, selectors }) => {
|
||||
const dashboardPage = await gotoDashboardPage({ uid: OLD_GAUGES_DASHBOARD_UID });
|
||||
|
||||
// check that gauges are rendered
|
||||
const gaugeElements = dashboardPage.getByGrafanaSelector(
|
||||
selectors.components.Panels.Visualization.Gauge.Container
|
||||
);
|
||||
await expect(gaugeElements).toHaveCount(16);
|
||||
|
||||
// check that no panel errors exist
|
||||
const errorInfo = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerCornerInfo('error'));
|
||||
await expect(errorInfo).toBeHidden();
|
||||
});
|
||||
|
||||
test('renders new gauge panels', async ({ gotoDashboardPage, selectors }) => {
|
||||
// open Panel Tests - Gauge
|
||||
const dashboardPage = await gotoDashboardPage({ uid: NEW_GAUGES_DASHBOARD_UID });
|
||||
|
||||
// check that gauges are rendered
|
||||
const gaugeElements = dashboardPage.getByGrafanaSelector(
|
||||
selectors.components.Panels.Visualization.Gauge.Container
|
||||
);
|
||||
await expect(gaugeElements).toHaveCount(32);
|
||||
|
||||
// check that no panel errors exist
|
||||
const errorInfo = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerCornerInfo('error'));
|
||||
await expect(errorInfo).toBeHidden();
|
||||
});
|
||||
|
||||
test('renders sparklines in gauge panels', async ({ gotoDashboardPage, page }) => {
|
||||
await gotoDashboardPage({
|
||||
uid: NEW_GAUGES_DASHBOARD_UID,
|
||||
queryParams: new URLSearchParams({ editPanel: '11' }),
|
||||
});
|
||||
|
||||
await expect(page.locator('.uplot')).toHaveCount(5);
|
||||
});
|
||||
|
||||
test('"no data"', async ({ gotoDashboardPage, selectors }) => {
|
||||
const dashboardPage = await gotoDashboardPage({
|
||||
uid: NEW_GAUGES_DASHBOARD_UID,
|
||||
queryParams: new URLSearchParams({ editPanel: '36' }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Gauge.Container),
|
||||
'that the gauge does not appear'
|
||||
).toBeHidden();
|
||||
|
||||
await expect(
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage),
|
||||
'that the empty text appears'
|
||||
).toHaveText('No data');
|
||||
|
||||
// update the "No value" option and see if the panel updates
|
||||
const noValueOption = dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Standard options No value'))
|
||||
.locator('input');
|
||||
|
||||
await noValueOption.fill('My empty value');
|
||||
await noValueOption.blur();
|
||||
await expect(
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.Gauge.Container),
|
||||
'that the empty text shows up in an empty gauge'
|
||||
).toHaveText('My empty value');
|
||||
|
||||
// test the "no numeric fields" message on the next panel
|
||||
const dashboardPage2 = await gotoDashboardPage({
|
||||
uid: NEW_GAUGES_DASHBOARD_UID,
|
||||
queryParams: new URLSearchParams({ editPanel: '37' }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
dashboardPage2.getByGrafanaSelector(selectors.components.Panels.Visualization.Gauge.Container),
|
||||
'that the gauge does not appear'
|
||||
).toBeHidden();
|
||||
|
||||
await expect(
|
||||
dashboardPage2.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage),
|
||||
'that the empty text appears'
|
||||
).toHaveText('Data is missing a number field');
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,24 +0,0 @@
|
||||
import { expect, E2ESelectorGroups, PanelEditPage } from '@grafana/plugin-e2e';
|
||||
|
||||
// this replaces the panelEditPage.setVisualization method used previously in tests, since it
|
||||
// does not know how to use the updated 12.4 viz picker UI to set the visualization
|
||||
export const setVisualization = async (panelEditPage: PanelEditPage, vizName: string, selectors: E2ESelectorGroups) => {
|
||||
const vizPicker = panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleVizPicker);
|
||||
await expect(vizPicker, '"Change" button should be visible').toBeVisible();
|
||||
await vizPicker.click();
|
||||
|
||||
const allVizTabBtn = panelEditPage.getByGrafanaSelector(selectors.components.Tab.title('All visualizations'));
|
||||
await expect(allVizTabBtn, '"All visualiations" button should be visible').toBeVisible();
|
||||
await allVizTabBtn.click();
|
||||
|
||||
const vizItem = panelEditPage.getByGrafanaSelector(selectors.components.PluginVisualization.item(vizName));
|
||||
await expect(vizItem, `"${vizName}" item should be visible`).toBeVisible();
|
||||
await vizItem.scrollIntoViewIfNeeded();
|
||||
await vizItem.click();
|
||||
|
||||
await expect(vizPicker, '"Change" button should be visible again').toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.header),
|
||||
'Panel header should have the new viz type name'
|
||||
).toHaveText(vizName);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { setVisualization } from '../../../panels-suite/vizpicker-utils';
|
||||
import { formatExpectError } from '../errors';
|
||||
import { successfulDataQuery } from '../mocks/queries';
|
||||
|
||||
@@ -25,10 +24,10 @@ test.describe(
|
||||
).toContainText(['Field', 'Max', 'Mean', 'Last']);
|
||||
});
|
||||
|
||||
test('table panel data assertions', async ({ panelEditPage, selectors }) => {
|
||||
test('table panel data assertions', async ({ panelEditPage }) => {
|
||||
await panelEditPage.mockQueryDataResponse(successfulDataQuery, 200);
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
await setVisualization(panelEditPage, 'Table', selectors);
|
||||
await panelEditPage.setVisualization('Table');
|
||||
await panelEditPage.refreshPanel();
|
||||
await expect(
|
||||
panelEditPage.panel.locator,
|
||||
@@ -44,10 +43,10 @@ test.describe(
|
||||
).toContainText(['val1', 'val2', 'val3', 'val4']);
|
||||
});
|
||||
|
||||
test('timeseries panel - table view assertions', async ({ panelEditPage, selectors }) => {
|
||||
test('timeseries panel - table view assertions', async ({ panelEditPage }) => {
|
||||
await panelEditPage.mockQueryDataResponse(successfulDataQuery, 200);
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
await setVisualization(panelEditPage, 'Time series', selectors);
|
||||
await panelEditPage.setVisualization('Time series');
|
||||
await panelEditPage.refreshPanel();
|
||||
await panelEditPage.toggleTableView();
|
||||
await expect(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { expect, test } from '@grafana/plugin-e2e';
|
||||
|
||||
import { setVisualization } from '../../../panels-suite/vizpicker-utils';
|
||||
import { formatExpectError } from '../errors';
|
||||
import { successfulDataQuery } from '../mocks/queries';
|
||||
import { scenarios } from '../mocks/resources';
|
||||
@@ -54,10 +53,10 @@ test.describe(
|
||||
).toHaveText(scenarios.map((s) => s.name));
|
||||
});
|
||||
|
||||
test('mocked query data response', async ({ panelEditPage, page, selectors }) => {
|
||||
test('mocked query data response', async ({ panelEditPage, page }) => {
|
||||
await panelEditPage.mockQueryDataResponse(successfulDataQuery, 200);
|
||||
await panelEditPage.datasource.set('gdev-testdata');
|
||||
await setVisualization(panelEditPage, TABLE_VIZ_NAME, selectors);
|
||||
await panelEditPage.setVisualization(TABLE_VIZ_NAME);
|
||||
await panelEditPage.refreshPanel();
|
||||
await expect(
|
||||
panelEditPage.panel.getErrorIcon(),
|
||||
@@ -76,7 +75,7 @@ test.describe(
|
||||
selectors,
|
||||
page,
|
||||
}) => {
|
||||
await setVisualization(panelEditPage, TABLE_VIZ_NAME, selectors);
|
||||
await panelEditPage.setVisualization(TABLE_VIZ_NAME);
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.header),
|
||||
formatExpectError('Expected panel visualization to be set to table')
|
||||
@@ -93,8 +92,8 @@ test.describe(
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('Select time zone in timezone picker', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('Select time zone in timezone picker', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = await panelEditPage.getCustomOptions('Axis');
|
||||
const timeZonePicker = axisOptions.getSelect('Time zone');
|
||||
|
||||
@@ -102,8 +101,8 @@ test.describe(
|
||||
await expect(timeZonePicker).toHaveSelected('Europe/Stockholm');
|
||||
});
|
||||
|
||||
test('select unit in unit picker', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('select unit in unit picker', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const standardOptions = panelEditPage.getStandardOptions();
|
||||
const unitPicker = standardOptions.getUnitPicker('Unit');
|
||||
|
||||
@@ -112,8 +111,8 @@ test.describe(
|
||||
await expect(unitPicker).toHaveSelected('Pixels');
|
||||
});
|
||||
|
||||
test('enter value in number input', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('enter value in number input', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const lineWith = axisOptions.getNumberInput('Soft min');
|
||||
|
||||
@@ -122,8 +121,8 @@ test.describe(
|
||||
await expect(lineWith).toHaveValue('10');
|
||||
});
|
||||
|
||||
test('enter value in slider', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('enter value in slider', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const graphOptions = panelEditPage.getCustomOptions('Graph styles');
|
||||
const lineWidth = graphOptions.getSliderInput('Line width');
|
||||
|
||||
@@ -132,8 +131,8 @@ test.describe(
|
||||
await expect(lineWidth).toHaveValue('10');
|
||||
});
|
||||
|
||||
test('select value in single value select', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('select value in single value select', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const standardOptions = panelEditPage.getStandardOptions();
|
||||
const colorSchemeSelect = standardOptions.getSelect('Color scheme');
|
||||
|
||||
@@ -141,8 +140,8 @@ test.describe(
|
||||
await expect(colorSchemeSelect).toHaveSelected('Classic palette');
|
||||
});
|
||||
|
||||
test('clear input', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('clear input', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const panelOptions = panelEditPage.getPanelOptions();
|
||||
const title = panelOptions.getTextInput('Title');
|
||||
|
||||
@@ -151,8 +150,8 @@ test.describe(
|
||||
await expect(title).toHaveValue('');
|
||||
});
|
||||
|
||||
test('enter value in input', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('enter value in input', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const panelOptions = panelEditPage.getPanelOptions();
|
||||
const description = panelOptions.getTextInput('Description');
|
||||
|
||||
@@ -161,8 +160,8 @@ test.describe(
|
||||
await expect(description).toHaveValue('This is a panel');
|
||||
});
|
||||
|
||||
test('unchecking switch', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('unchecking switch', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const showBorder = axisOptions.getSwitch('Show border');
|
||||
|
||||
@@ -174,8 +173,8 @@ test.describe(
|
||||
await expect(showBorder).toBeChecked({ checked: false });
|
||||
});
|
||||
|
||||
test('checking switch', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('checking switch', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const showBorder = axisOptions.getSwitch('Show border');
|
||||
|
||||
@@ -184,8 +183,8 @@ test.describe(
|
||||
await expect(showBorder).toBeChecked();
|
||||
});
|
||||
|
||||
test('re-selecting value in radio button group', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('re-selecting value in radio button group', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const placement = axisOptions.getRadioGroup('Placement');
|
||||
|
||||
@@ -196,8 +195,8 @@ test.describe(
|
||||
await expect(placement).toHaveChecked('Auto');
|
||||
});
|
||||
|
||||
test('selecting value in radio button group', async ({ panelEditPage, selectors }) => {
|
||||
await setVisualization(panelEditPage, TIME_SERIES_VIZ_NAME, selectors);
|
||||
test('selecting value in radio button group', async ({ panelEditPage }) => {
|
||||
await panelEditPage.setVisualization(TIME_SERIES_VIZ_NAME);
|
||||
const axisOptions = panelEditPage.getCustomOptions('Axis');
|
||||
const placement = axisOptions.getRadioGroup('Placement');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BootData } from '@grafana/data';
|
||||
import { BootData, PanelPluginMeta } from '@grafana/data';
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
test.describe(
|
||||
@@ -22,7 +22,7 @@ test.describe(
|
||||
await dashboardPage.addPanel();
|
||||
|
||||
// Get panel types from window object
|
||||
const panelTypes = await page.evaluate(() => {
|
||||
const panelTypes: PanelPluginMeta[] = await page.evaluate(() => {
|
||||
// @grafana/plugin-e2e doesn't export the full bootdata config
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const win = window as typeof window & { grafanaBootData: BootData };
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
// this test requires a larger viewport so all gauge panels load properly
|
||||
test.use({
|
||||
viewport: { width: 1280, height: 1080 },
|
||||
});
|
||||
|
||||
test.describe(
|
||||
'Gauge Panel',
|
||||
{
|
||||
tag: ['@various'],
|
||||
},
|
||||
() => {
|
||||
test('Gauge rendering e2e tests', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
// open Panel Tests - Gauge
|
||||
const dashboardPage = await gotoDashboardPage({ uid: '_5rDmaQiz' });
|
||||
|
||||
// check that gauges are rendered
|
||||
const gaugeElements = page.locator('.flot-base');
|
||||
await expect(gaugeElements).toHaveCount(16);
|
||||
|
||||
// check that no panel errors exist
|
||||
const errorInfo = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerCornerInfo('error'));
|
||||
await expect(errorInfo).toBeHidden();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,178 @@
|
||||
import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
test.use({
|
||||
featureToggles: {
|
||||
newVizSuggestions: true,
|
||||
externalVizSuggestions: false,
|
||||
},
|
||||
viewport: {
|
||||
width: 800,
|
||||
height: 1500,
|
||||
},
|
||||
});
|
||||
|
||||
test.describe(
|
||||
'Visualization suggestions v2',
|
||||
{
|
||||
tag: ['@various', '@suggestions'],
|
||||
},
|
||||
() => {
|
||||
test('Should be shown and clickable', async ({ selectors, gotoPanelEditPage }) => {
|
||||
// Open dashboard with edit panel
|
||||
const panelEditPage = await gotoPanelEditPage({
|
||||
dashboard: {
|
||||
uid: 'aBXrJ0R7z',
|
||||
},
|
||||
id: '9',
|
||||
});
|
||||
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.Panels.Panel.content).locator('.uplot'),
|
||||
'time series to be rendered inside panel'
|
||||
).toBeVisible();
|
||||
|
||||
// Try visualization suggestions
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleVizPicker).click();
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.Tab.title('Suggestions')).click();
|
||||
|
||||
// Verify we see suggestions
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Line chart')),
|
||||
'line chart suggestion to be rendered'
|
||||
).toBeVisible();
|
||||
|
||||
// TODO: in this part of the test, we will change the query and the transforms and observe suggestions being updated.
|
||||
|
||||
// Select a visualization and verify table header is visible from preview
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Table')).click();
|
||||
await expect(
|
||||
panelEditPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Panel.content)
|
||||
.getByRole('grid')
|
||||
.getByRole('row')
|
||||
.first(),
|
||||
'table to be rendered inside panel'
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.discardChangesButton),
|
||||
'discard changes button disabled since panel has not yet changed'
|
||||
).toBeDisabled();
|
||||
|
||||
// apply the suggestion and verify panel options are visible
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.confirm('Table')).click();
|
||||
await expect(
|
||||
panelEditPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Panel.content)
|
||||
.getByRole('grid')
|
||||
.getByRole('row')
|
||||
.first(),
|
||||
'table to be rendered inside panel'
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.header),
|
||||
'options pane to be rendered'
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.discardChangesButton),
|
||||
'discard changes button enabled now that panel is dirty'
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test('should not apply suggestion if you navigate toggle the viz picker back off', async ({
|
||||
selectors,
|
||||
gotoPanelEditPage,
|
||||
}) => {
|
||||
// Open dashboard with edit panel
|
||||
const panelEditPage = await gotoPanelEditPage({
|
||||
dashboard: {
|
||||
uid: 'aBXrJ0R7z',
|
||||
},
|
||||
id: '9',
|
||||
});
|
||||
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.Panels.Panel.content).locator('.uplot'),
|
||||
'time series to be rendered inside panel;'
|
||||
).toBeVisible();
|
||||
|
||||
// Try visualization suggestions
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleVizPicker).click();
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.Tab.title('Suggestions')).click();
|
||||
|
||||
// Verify we see suggestions
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Line chart')),
|
||||
'line chart suggestion to be rendered'
|
||||
).toBeVisible();
|
||||
|
||||
// Select a visualization
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Table')).click();
|
||||
await expect(
|
||||
panelEditPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Panel.content)
|
||||
.getByRole('grid')
|
||||
.getByRole('row')
|
||||
.first(),
|
||||
'table to be rendered inside panel'
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.discardChangesButton)
|
||||
).toBeDisabled();
|
||||
|
||||
// Verify that toggling the viz picker back cancels the suggestion, restores the line chart, shows panel options
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleVizPicker).click();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.Panels.Panel.content).locator('.uplot'),
|
||||
'time series to be rendered inside panel'
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.header),
|
||||
'options pane to be rendered'
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.discardChangesButton),
|
||||
'discard changes button is still disabled since no changes were applied'
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
test('should not apply suggestion if you navigate back to the dashboard', async ({
|
||||
page,
|
||||
selectors,
|
||||
gotoPanelEditPage,
|
||||
}) => {
|
||||
// Open dashboard with edit panel
|
||||
const panelEditPage = await gotoPanelEditPage({
|
||||
dashboard: {
|
||||
uid: 'aBXrJ0R7z',
|
||||
},
|
||||
id: '9',
|
||||
});
|
||||
|
||||
// Try visualization suggestions
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.PanelEditor.toggleVizPicker).click();
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.Tab.title('Suggestions')).click();
|
||||
|
||||
// Verify we see suggestions
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Line chart')),
|
||||
'line chart suggestion to be rendered'
|
||||
).toBeVisible();
|
||||
|
||||
// Select a visualization
|
||||
await panelEditPage.getByGrafanaSelector(selectors.components.VisualizationPreview.card('Table')).click();
|
||||
await expect(page.getByRole('grid').getByRole('row').first(), 'table row to be rendered').toBeVisible();
|
||||
await expect(
|
||||
panelEditPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.discardChangesButton)
|
||||
).toBeDisabled();
|
||||
|
||||
// Verify that navigating back to the dashboard cancels the suggestion and restores the line chart.
|
||||
await panelEditPage
|
||||
.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.backToDashboardButton)
|
||||
.click();
|
||||
await expect(
|
||||
page.locator('[data-viz-panel-key="panel-9"]').locator('.uplot'),
|
||||
'time series to be rendered inside the panel'
|
||||
).toBeVisible();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -3,7 +3,7 @@ import { test, expect } from '@grafana/plugin-e2e';
|
||||
test.describe(
|
||||
'Visualization suggestions',
|
||||
{
|
||||
tag: ['@various'],
|
||||
tag: ['@various', '@suggestions'],
|
||||
},
|
||||
() => {
|
||||
test('Should be shown and clickable', async ({ page, selectors, gotoPanelEditPage }) => {
|
||||
|
||||
@@ -1911,11 +1911,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/settings/JsonModelEditView.tsx": {
|
||||
"react/no-unescaped-entities": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx": {
|
||||
"react-hooks/rules-of-hooks": {
|
||||
"count": 4
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user