From 3abb35536461e0972dfcab03c226d807c2f030a6 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 21 Oct 2021 14:38:28 +0200 Subject: [PATCH 01/22] Chore: Update codeowners (#40756) --- .github/CODEOWNERS | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 04fcddc4b15..ad210108d6c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,13 +14,15 @@ # Documentation owner: Jita Chatterjee /docs/ @grafana/docs-squad @pkolyvas /contribute/ @marcusolsson @grafana/docs-squad @pkolyvas -/docs/sources/developers/plugins/ @marcusolsson @grafana/docs-squad +/docs/sources/developers/plugins/ @marcusolsson @grafana/docs-squad @grafana/plugins-platform +/docs/sources/developers/plugins/backend @marcusolsson @grafana/docs-squad @grafana/plugins-platform-backend /docs/sources/enterprise/ @osg-grafana @grafana/docs-squad # Backend code *.go @grafana/backend-platform go.mod @grafana/backend-platform go.sum @grafana/backend-platform +/.bingo @grafana/backend-platform # Continuous Integration .drone.yml @grafana/grafana-release-eng @@ -56,25 +58,31 @@ go.sum @grafana/backend-platform /pkg/services/libraryelements @grafana/user-essentials /pkg/services/librarypanels @grafana/user-essentials +# Plugins +/pkg/api/pluginproxy @grafana/plugins-platform-backend +/pkg/plugins @grafana/plugins-platform-backend +/pkg/services/datasourceproxy @grafana/plugins-platform-backend +/pkg/services/datasources @grafana/plugins-platform-backend + # Backend code docs /contribute/style-guides/backend.md @grafana/backend-platform /contribute/architecture/backend @grafana/backend-platform /contribute/engineering/backend @grafana/backend-platform /e2e @grafana/user-essentials -/packages @grafana/user-essentials @grafana/plugins-platform @grafana/grafana-bi-squad +/packages @grafana/user-essentials @grafana/plugins-platform-frontend @grafana/grafana-bi-squad /packages/grafana-e2e-selectors @grafana/user-essentials /packages/grafana-e2e @grafana/user-essentials -/packages/grafana-toolkit @grafana/plugins-platform -/packages/grafana-ui/.storybook @grafana/plugins-platform +/packages/grafana-toolkit @grafana/plugins-platform-frontend +/packages/grafana-ui/.storybook @grafana/plugins-platform-frontend /packages/grafana-ui/src/components/DateTimePickers @grafana/grafana-bi-squad /packages/grafana-ui/src/components/GraphNG @grafana/grafana-bi-squad /packages/grafana-ui/src/components/Table @grafana/grafana-bi-squad /packages/grafana-ui/src/components/TimeSeries @grafana/grafana-bi-squad /packages/grafana-ui/src/components/uPlot @grafana/grafana-bi-squad -/packages/grafana-ui/src/utils/storybook @grafana/plugins-platform +/packages/grafana-ui/src/utils/storybook @grafana/plugins-platform-frontend /packages/jaeger-ui-components/ @grafana/observability-squad -/plugins-bundled @grafana/plugins-platform +/plugins-bundled @grafana/plugins-platform-frontend /public @grafana/user-essentials /public/app/core/components/TimePicker @grafana/grafana-bi-squad /public/app/features/canvas/ @grafana/grafana-edge-squad @@ -93,11 +101,11 @@ go.sum @grafana/backend-platform /public/app/plugins/panel/geomap @grafana/grafana-edge-squad /public/app/plugins/panel/canvas @grafana/grafana-edge-squad /public/app/plugins/panel/icon @grafana/grafana-edge-squad -/scripts/build/release-packages.sh @grafana/plugins-platform -/scripts/circle-release-next-packages.sh @grafana/plugins-platform -/scripts/ci-frontend-metrics.sh @grafana/user-essentials @grafana/plugins-platform @grafana/grafana-bi-squad -/scripts/ci-reference-docs-build.sh @grafana/plugins-platform -/scripts/ci-reference-docs-lint.sh @grafana/plugins-platform +/scripts/build/release-packages.sh @grafana/plugins-platform-frontend +/scripts/circle-release-next-packages.sh @grafana/plugins-platform-frontend +/scripts/ci-frontend-metrics.sh @grafana/user-essentials @grafana/plugins-platform-frontend @grafana/grafana-bi-squad +/scripts/ci-reference-docs-build.sh @grafana/plugins-platform-frontend +/scripts/ci-reference-docs-lint.sh @grafana/plugins-platform-frontend /scripts/grunt @grafana/frontend-ops /scripts/webpack @grafana/frontend-ops /scripts/generate-a11y-report.sh @grafana/user-essentials @@ -111,7 +119,7 @@ lerna.json @grafana/frontend-ops .pa11yci-pr.conf.js @grafana/user-essentials # @grafana/ui component documentation -*.mdx @marcusolsson @jessover9000 @grafana/plugins-platform +*.mdx @marcusolsson @jessover9000 @grafana/plugins-platform-frontend # Core datasources /public/app/plugins/datasource/cloudwatch @grafana/cloud-datasources @grafana/observability-squad From 967721068e13af2cbae0b7b94f6ff981867e6f46 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Thu, 21 Oct 2021 13:47:06 +0100 Subject: [PATCH 02/22] Alerting: Support custom annotations and labels when testing contact points Support custom annotations and labels when testing contact points --- pkg/services/ngalert/api/api_alertmanager.go | 8 +- .../api/tooling/definitions/alertmanager.go | 12 +- pkg/services/ngalert/api/tooling/post.json | 58 +- pkg/services/ngalert/api/tooling/spec.json | 50 +- pkg/services/ngalert/notifier/receivers.go | 60 +- .../alerting/api_notification_channel_test.go | 533 +++++++++++++++--- 6 files changed, 591 insertions(+), 130 deletions(-) diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index c5eeba6c855..852a20980a2 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -444,8 +444,12 @@ func contextWithTimeoutFromRequest(ctx context.Context, r *http.Request, default func newTestReceiversResult(r *notifier.TestReceiversResult) apimodels.TestReceiversResult { v := apimodels.TestReceiversResult{ - Receivers: make([]apimodels.TestReceiverResult, len(r.Receivers)), - NotifedAt: r.NotifedAt, + Alert: apimodels.TestReceiversConfigAlertParams{ + Annotations: r.Alert.Annotations, + Labels: r.Alert.Labels, + }, + Receivers: make([]apimodels.TestReceiverResult, len(r.Receivers)), + NotifiedAt: r.NotifedAt, } for ix, next := range r.Receivers { configs := make([]apimodels.TestReceiverConfigResult, len(next.Configs)) diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 1b539438951..4c256e917e3 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -137,6 +137,8 @@ type MultiStatus struct{} // swagger:parameters RoutePostTestReceivers type TestReceiversConfigParams struct { + // in:body + Alert *TestReceiversConfigAlertParams `yaml:"alert,omitempty" json:"alert,omitempty"` // in:body Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"` } @@ -145,10 +147,16 @@ func (c *TestReceiversConfigParams) ProcessConfig(encrypt EncryptFn) error { return processReceiverConfigs(c.Receivers, encrypt) } +type TestReceiversConfigAlertParams struct { + Annotations model.LabelSet `yaml:"annotations,omitempty" json:"annotations,omitempty"` + Labels model.LabelSet `yaml:"labels,omitempty" json:"labels,omitempty"` +} + // swagger:model type TestReceiversResult struct { - Receivers []TestReceiverResult `json:"receivers"` - NotifedAt time.Time `json:"notified_at"` + Alert TestReceiversConfigAlertParams `json:"alert"` + Receivers []TestReceiverResult `json:"receivers"` + NotifiedAt time.Time `json:"notified_at"` } // swagger:model diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 699af43a8cc..5c1f9159ecc 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -1065,6 +1065,19 @@ "type": "array", "x-go-package": "github.com/prometheus/common/model" }, + "LabelSet": { + "additionalProperties": { + "$ref": "#/definitions/LabelValue" + }, + "description": "A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet\nmay be fully-qualified down to the point where it may resolve to a single\nMetric in the data store or not. All operations that occur within the realm\nof a LabelSet can emit a vector of Metric entities to which the LabelSet may\nmatch.", + "type": "object", + "x-go-package": "github.com/prometheus/common/model" + }, + "LabelValue": { + "title": "A LabelValue is an associated value for a LabelName.", + "type": "string", + "x-go-package": "github.com/prometheus/common/model" + }, "Labels": { "description": "Labels is a sorted set of labels. Order has to be guaranteed upon\ninstantiation.", "items": { @@ -2403,12 +2416,27 @@ "type": "object", "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "TestReceiversConfigAlertParams": { + "properties": { + "annotations": { + "$ref": "#/definitions/LabelSet" + }, + "labels": { + "$ref": "#/definitions/LabelSet" + } + }, + "type": "object", + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + }, "TestReceiversResult": { "properties": { + "alert": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + }, "notified_at": { "format": "date-time", "type": "string", - "x-go-name": "NotifedAt" + "x-go-name": "NotifiedAt" }, "receivers": { "items": { @@ -2649,6 +2677,7 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroup": { + "description": "AlertGroup alert group", "properties": { "alerts": { "description": "alerts", @@ -2670,9 +2699,7 @@ "labels", "receiver" ], - "type": "object", - "x-go-name": "AlertGroup", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + "type": "object" }, "alertGroups": { "items": { @@ -2869,7 +2896,6 @@ "type": "array" }, "gettableSilence": { - "description": "GettableSilence gettable silence", "properties": { "comment": { "description": "comment", @@ -2921,14 +2947,17 @@ "status", "updatedAt" ], - "type": "object" + "type": "object", + "x-go-name": "GettableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence" }, - "type": "array" + "type": "array", + "x-go-name": "GettableSilences", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "labelSet": { "additionalProperties": { @@ -3101,7 +3130,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { - "description": "Receiver receiver", "properties": { "name": { "description": "name", @@ -3112,7 +3140,9 @@ "required": [ "name" ], - "type": "object" + "type": "object", + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "silence": { "description": "Silence silence", @@ -3699,6 +3729,14 @@ "post": { "operationId": "RoutePostTestReceivers", "parameters": [ + { + "in": "body", + "name": "alert", + "schema": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + }, + "x-go-name": "Alert" + }, { "in": "body", "name": "receivers", diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 263e3cce322..7914844df1d 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -493,6 +493,14 @@ "summary": "Test Grafana managed receivers without saving them.", "operationId": "RoutePostTestReceivers", "parameters": [ + { + "x-go-name": "Alert", + "name": "alert", + "in": "body", + "schema": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + } + }, { "x-go-name": "Receivers", "name": "receivers", @@ -2090,6 +2098,19 @@ }, "x-go-package": "github.com/prometheus/common/model" }, + "LabelSet": { + "description": "A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet\nmay be fully-qualified down to the point where it may resolve to a single\nMetric in the data store or not. All operations that occur within the realm\nof a LabelSet can emit a vector of Metric entities to which the LabelSet may\nmatch.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/LabelValue" + }, + "x-go-package": "github.com/prometheus/common/model" + }, + "LabelValue": { + "type": "string", + "title": "A LabelValue is an associated value for a LabelName.", + "x-go-package": "github.com/prometheus/common/model" + }, "Labels": { "description": "Labels is a sorted set of labels. Order has to be guaranteed upon\ninstantiation.", "type": "array", @@ -3429,13 +3450,28 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "TestReceiversConfigAlertParams": { + "type": "object", + "properties": { + "annotations": { + "$ref": "#/definitions/LabelSet" + }, + "labels": { + "$ref": "#/definitions/LabelSet" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + }, "TestReceiversResult": { "type": "object", "properties": { + "alert": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + }, "notified_at": { "type": "string", "format": "date-time", - "x-go-name": "NotifedAt" + "x-go-name": "NotifiedAt" }, "receivers": { "type": "array", @@ -3675,6 +3711,7 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroup": { + "description": "AlertGroup alert group", "type": "object", "required": [ "alerts", @@ -3697,8 +3734,6 @@ "$ref": "#/definitions/receiver" } }, - "x-go-name": "AlertGroup", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/alertGroup" }, "alertGroups": { @@ -3899,7 +3934,6 @@ "$ref": "#/definitions/gettableAlerts" }, "gettableSilence": { - "description": "GettableSilence gettable silence", "type": "object", "required": [ "comment", @@ -3952,14 +3986,17 @@ "x-go-name": "UpdatedAt" } }, + "x-go-name": "GettableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableSilence" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "type": "array", "items": { "$ref": "#/definitions/gettableSilence" }, + "x-go-name": "GettableSilences", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableSilences" }, "labelSet": { @@ -4134,7 +4171,6 @@ "$ref": "#/definitions/postableSilence" }, "receiver": { - "description": "Receiver receiver", "type": "object", "required": [ "name" @@ -4146,6 +4182,8 @@ "x-go-name": "Name" } }, + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/receiver" }, "silence": { diff --git a/pkg/services/ngalert/notifier/receivers.go b/pkg/services/ngalert/notifier/receivers.go index 4985183d102..6fbcd9f6875 100644 --- a/pkg/services/ngalert/notifier/receivers.go +++ b/pkg/services/ngalert/notifier/receivers.go @@ -24,6 +24,7 @@ var ( ) type TestReceiversResult struct { + Alert types.Alert Receivers []TestReceiverResult NotifedAt time.Time } @@ -61,19 +62,7 @@ func (e ReceiverTimeoutError) Error() string { func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigParams) (*TestReceiversResult, error) { // now represents the start time of the test now := time.Now() - testAlert := &types.Alert{ - Alert: model.Alert{ - Labels: model.LabelSet{ - model.LabelName("alertname"): "TestAlert", - model.LabelName("instance"): "Grafana", - }, - Annotations: model.LabelSet{ - model.LabelName("summary"): "Notification test", - }, - StartsAt: now, - }, - UpdatedAt: now, - } + testAlert := newTestAlert(c, now, now) // we must set a group key that is unique per test as some receivers use this key to deduplicate alerts ctx = notify.WithGroupKey(ctx, testAlert.Labels.String()+now.String()) @@ -97,7 +86,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei Error error } - newTestReceiversResult := func(results []result, notifiedAt time.Time) *TestReceiversResult { + newTestReceiversResult := func(alert types.Alert, results []result, notifiedAt time.Time) *TestReceiversResult { m := make(map[string]TestReceiverResult) for _, receiver := range c.Receivers { // set up the result for this receiver @@ -122,6 +111,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei m[next.ReceiverName] = tmp } v := new(TestReceiversResult) + v.Alert = alert v.Receivers = make([]TestReceiverResult, 0, len(c.Receivers)) v.NotifedAt = notifiedAt for _, next := range m { @@ -165,7 +155,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei } if len(jobs) == 0 { - return newTestReceiversResult(invalid, now), nil + return newTestReceiversResult(testAlert, invalid, now), nil } numWorkers := maxTestReceiversWorkers @@ -188,7 +178,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei Config: next.Config, ReceiverName: next.ReceiverName, } - if _, err := next.Notifier.Notify(ctx, testAlert); err != nil { + if _, err := next.Notifier.Notify(ctx, &testAlert); err != nil { v.Error = err } resultCh <- v @@ -204,7 +194,43 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei results = append(results, next) } - return newTestReceiversResult(append(invalid, results...), now), nil + return newTestReceiversResult(testAlert, append(invalid, results...), now), nil +} + +func newTestAlert(c apimodels.TestReceiversConfigParams, startsAt, updatedAt time.Time) types.Alert { + var ( + defaultAnnotations = model.LabelSet{ + "summary": "Notification test", + } + defaultLabels = model.LabelSet{ + "alertname": "TestAlert", + "instance": "Grafana", + } + ) + + alert := types.Alert{ + Alert: model.Alert{ + Labels: defaultLabels, + Annotations: defaultAnnotations, + StartsAt: startsAt, + }, + UpdatedAt: updatedAt, + } + + if c.Alert != nil { + if c.Alert.Annotations != nil { + for k, v := range c.Alert.Annotations { + alert.Annotations[k] = v + } + } + if c.Alert.Labels != nil { + for k, v := range c.Alert.Labels { + alert.Labels[k] = v + } + } + } + + return alert } func processNotifierError(config *apimodels.PostableGrafanaReceiver, err error) error { diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index db844620d63..41e9dc90aa5 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -92,14 +92,14 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{ + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": { "addresses":"example@email.com" }, - "secureFields":{} + "secureFields": {} } ] }] @@ -109,22 +109,39 @@ func TestTestReceivers(t *testing.T) { require.NoError(t, err) }) - var result apimodels.TestReceiversResult - require.NoError(t, json.NewDecoder(resp.Body).Decode(&result)) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 1) require.Len(t, result.Receivers[0].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "ok", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) require.Len(t, mockEmails.emails, 1) require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) @@ -160,38 +177,54 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{}, - "secureFields":{} + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": {}, + "secureFields": {} } ] }] }`, http.StatusBadRequest) - b, err := ioutil.ReadAll(resp.Body) - require.NoError(t, err) t.Cleanup(func() { require.NoError(t, resp.Body.Close()) }) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 1) require.Len(t, result.Receivers[0].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "failed", - Error: "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "failed", + "error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) }) t.Run("assert timed out receiver returns 408 Request Timeout", func(t *testing.T) { @@ -225,14 +258,14 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{ + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": { "addresses":"example@email.com" }, - "secureFields":{} + "secureFields": {} } ] }] @@ -248,23 +281,40 @@ func TestTestReceivers(t *testing.T) { }) require.Equal(t, http.StatusRequestTimeout, resp.StatusCode) - var result apimodels.TestReceiversResult - require.NoError(t, json.NewDecoder(resp.Body).Decode(&result)) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 1) require.Len(t, result.Receivers[0].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "failed", - Error: "the receiver timed out: context deadline exceeded", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "failed", + "error": "the receiver timed out: context deadline exceeded" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) }) t.Run("assert multiple different errors returns 207 Multi Status", func(t *testing.T) { @@ -298,26 +348,26 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{}, - "secureFields":{} + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": {}, + "secureFields": {} } ] }, { "name":"receiver-2", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-2", - "type":"email", - "disableResolveMessage":false, - "settings":{ + "uid": "", + "name": "receiver-2", + "type": "email", + "disableResolveMessage": false, + "settings": { "addresses":"example@email.com" }, - "secureFields":{} + "secureFields": {} } ] }] @@ -333,32 +383,329 @@ func TestTestReceivers(t *testing.T) { }) require.Equal(t, http.StatusMultiStatus, resp.StatusCode) - var result apimodels.TestReceiversResult - require.NoError(t, json.NewDecoder(resp.Body).Decode(&result)) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 2) require.Len(t, result.Receivers[0].Configs, 1) require.Len(t, result.Receivers[1].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "failed", - Error: "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings", - }}, - }, { - Name: "receiver-2", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-2", - UID: result.Receivers[1].Configs[0].UID, - Status: "failed", - Error: "the receiver timed out: context deadline exceeded", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "failed", + "error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings" + } + ] + }, { + "name":"receiver-2", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-2", + "uid": "%s", + "status": "failed", + "error": "the receiver timed out: context deadline exceeded" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.Receivers[1].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + }) +} + +func TestTestReceiversAlertCustomization(t *testing.T) { + t.Run("assert custom annotations and labels are sent", func(t *testing.T) { + // Setup Grafana and its Database + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + }) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + store.Bus = bus.GetBus() + + createUser(t, store, models.CreateUserCommand{ + DefaultOrgRole: string(models.ROLE_EDITOR), + Login: "grafana", + Password: "password", + }) + + oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync") + mockEmails := &mockEmailHandler{} + bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync) + t.Cleanup(func() { + bus.AddHandlerCtx("", oldEmailBus) + }) + + testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr) + // nolint + resp := postRequest(t, testReceiversURL, `{ + "alert": { + "annotations": { + "annotation1": "value1" + }, + "labels": { + "label1": "value1" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "uid":"", + "name":"receiver-1", + "type":"email", + "disableResolveMessage":false, + "settings":{ + "addresses":"example@email.com" + }, + "secureFields":{} + } + ] + }] +}`, http.StatusOK) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) + require.Len(t, result.Receivers, 1) + require.Len(t, result.Receivers[0].Configs, 1) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "annotation1": "value1", + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana", + "label1": "value1" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + + require.Len(t, mockEmails.emails, 1) + require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) + }) + + t.Run("assert custom annotations can replace default annotations", func(t *testing.T) { + // Setup Grafana and its Database + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + }) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + store.Bus = bus.GetBus() + + createUser(t, store, models.CreateUserCommand{ + DefaultOrgRole: string(models.ROLE_EDITOR), + Login: "grafana", + Password: "password", + }) + + oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync") + mockEmails := &mockEmailHandler{} + bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync) + t.Cleanup(func() { + bus.AddHandlerCtx("", oldEmailBus) + }) + + testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr) + // nolint + resp := postRequest(t, testReceiversURL, `{ + "alert": { + "annotations": { + "summary": "This is a custom annotation" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "uid":"", + "name":"receiver-1", + "type":"email", + "disableResolveMessage":false, + "settings":{ + "addresses":"example@email.com" + }, + "secureFields":{} + } + ] + }] +}`, http.StatusOK) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) + require.Len(t, result.Receivers, 1) + require.Len(t, result.Receivers[0].Configs, 1) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "This is a custom annotation" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + + require.Len(t, mockEmails.emails, 1) + require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) + }) + + t.Run("assert custom labels can replace default label", func(t *testing.T) { + // Setup Grafana and its Database + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + }) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + store.Bus = bus.GetBus() + + createUser(t, store, models.CreateUserCommand{ + DefaultOrgRole: string(models.ROLE_EDITOR), + Login: "grafana", + Password: "password", + }) + + oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync") + mockEmails := &mockEmailHandler{} + bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync) + t.Cleanup(func() { + bus.AddHandlerCtx("", oldEmailBus) + }) + + testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr) + // nolint + resp := postRequest(t, testReceiversURL, `{ + "alert": { + "labels": { + "alertname": "This is a custom label" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "uid":"", + "name":"receiver-1", + "type":"email", + "disableResolveMessage":false, + "settings":{ + "addresses":"example@email.com" + }, + "secureFields":{} + } + ] + }] +}`, http.StatusOK) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) + require.Len(t, result.Receivers, 1) + require.Len(t, result.Receivers[0].Configs, 1) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "This is a custom label", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + + require.Len(t, mockEmails.emails, 1) + require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) }) } From ba90b57b66bfac3cd2c76f81143af8a9f98401f4 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Thu, 21 Oct 2021 15:01:14 +0200 Subject: [PATCH 03/22] Prometheus: Transform NaN values from matrix response to null on backend (#40757) * avoid duplicate allocations * set labels * Replace NaN in matrix with null * Refactor and add test * Update test * Append response only if no error Co-authored-by: Ryan McKinley --- pkg/tsdb/prometheus/prometheus.go | 29 +++++++++++++++++--------- pkg/tsdb/prometheus/prometheus_test.go | 22 +++++++++++++++++++ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 32f4fc890d2..6043861773e 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -181,9 +181,9 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) if err != nil { plog.Error("Exemplar query", query.Expr, "failed with", err) result.Responses[query.RefId] = backend.DataResponse{Error: err} - continue + } else { + response[ExemplarQueryType] = exemplarResponse } - response[ExemplarQueryType] = exemplarResponse } frames, err := parseResponse(response, query) @@ -403,19 +403,28 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery) data.Frames for _, v := range matrix { tags := make(map[string]string, len(v.Metric)) - timeVector := make([]time.Time, 0, len(v.Values)) - values := make([]float64, 0, len(v.Values)) for k, v := range v.Metric { tags[string(k)] = string(v) } - for _, k := range v.Values { - timeVector = append(timeVector, time.Unix(k.Timestamp.Unix(), 0).UTC()) - values = append(values, float64(k.Value)) + + timeField := data.NewFieldFromFieldType(data.FieldTypeTime, len(v.Values)) + valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(v.Values)) + + for i, k := range v.Values { + timeField.Set(i, time.Unix(k.Timestamp.Unix(), 0).UTC()) + value := float64(k.Value) + if !math.IsNaN(value) { + valueField.Set(i, &value) + } } + name := formatLegend(v.Metric, query) - frame := data.NewFrame(name, - data.NewField("Time", nil, timeVector), - data.NewField("Value", tags, values).SetConfig(&data.FieldConfig{DisplayNameFromDS: name})) + timeField.Name = data.TimeSeriesTimeFieldName + valueField.Name = data.TimeSeriesValueFieldName + valueField.Config = &data.FieldConfig{DisplayNameFromDS: name} + valueField.Labels = tags + + frame := data.NewFrame(name, timeField, valueField) frame.Meta = &data.FrameMeta{ Custom: map[string]string{ "resultType": "matrix", diff --git a/pkg/tsdb/prometheus/prometheus_test.go b/pkg/tsdb/prometheus/prometheus_test.go index 0d91b89b1d2..e3625618c2a 100644 --- a/pkg/tsdb/prometheus/prometheus_test.go +++ b/pkg/tsdb/prometheus/prometheus_test.go @@ -1,6 +1,7 @@ package prometheus import ( + "math" "testing" "time" @@ -440,6 +441,27 @@ func TestPrometheus_parseResponse(t *testing.T) { require.Equal(t, "UTC", testValue.(time.Time).Location().String()) }) + t.Run("matrix response with NaN value should be changed to null", func(t *testing.T) { + value := make(map[PrometheusQueryType]interface{}) + value[RangeQueryType] = p.Matrix{ + &p.SampleStream{ + Metric: p.Metric{"app": "Application"}, + Values: []p.SamplePair{ + {Value: p.SampleValue(math.NaN()), Timestamp: 1000}, + }, + }, + } + query := &PrometheusQuery{ + LegendFormat: "", + } + res, err := parseResponse(value, query) + require.NoError(t, err) + + var nilPointer *float64 + require.Equal(t, res[0].Fields[1].Name, "Value") + require.Equal(t, res[0].Fields[1].At(0), nilPointer) + }) + t.Run("vector response should be parsed normally", func(t *testing.T) { value := make(map[PrometheusQueryType]interface{}) value[RangeQueryType] = p.Vector{ From 740444680cd2432da40aeab4b79e16cebe8a3544 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Thu, 21 Oct 2021 15:41:40 +0200 Subject: [PATCH 04/22] Access Control: Add fgac to datasource query endpoints (#40294) * Protect datasource tsdb and proxy endpoints with access control * Add datasource query permissions to fixed admin role Co-authored-by: Gabriel MABILLE Co-authored-by: Mitch Seaman --- pkg/api/api.go | 14 +++++++------- pkg/api/roles.go | 20 +++++++++++++++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index e69a1f2dab3..359d2078fb6 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -303,11 +303,11 @@ func (hs *HTTPServer) registerRoutes() { }, reqOrgAdmin) apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings) - apiRoute.Any("/datasources/proxy/:id/*", reqSignedIn, hs.ProxyDataSourceRequest) - apiRoute.Any("/datasources/proxy/:id", reqSignedIn, hs.ProxyDataSourceRequest) - apiRoute.Any("/datasources/:id/resources", hs.CallDatasourceResource) - apiRoute.Any("/datasources/:id/resources/*", hs.CallDatasourceResource) - apiRoute.Any("/datasources/:id/health", routing.Wrap(hs.CheckDatasourceHealth)) + apiRoute.Any("/datasources/proxy/:id/*", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.ProxyDataSourceRequest) + apiRoute.Any("/datasources/proxy/:id", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.ProxyDataSourceRequest) + apiRoute.Any("/datasources/:id/resources", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.CallDatasourceResource) + apiRoute.Any("/datasources/:id/resources/*", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.CallDatasourceResource) + apiRoute.Any("/datasources/:id/health", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), routing.Wrap(hs.CheckDatasourceHealth)) // Folders apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) { @@ -373,10 +373,10 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Get("/search/", routing.Wrap(Search)) // metrics - apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics)) + apiRoute.Post("/tsdb/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics)) // DataSource w/ expressions - apiRoute.Post("/ds/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2)) + apiRoute.Post("/ds/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2)) apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) { alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), routing.Wrap(hs.AlertTest)) diff --git a/pkg/api/roles.go b/pkg/api/roles.go index fd7ad7c7261..c220948a2f0 100644 --- a/pkg/api/roles.go +++ b/pkg/api/roles.go @@ -10,6 +10,7 @@ const ( ActionProvisioningReload = "provisioning:reload" ActionDatasourcesRead = "datasources:read" + ActionDatasourcesQuery = "datasources:query" ActionDatasourcesCreate = "datasources:create" ActionDatasourcesWrite = "datasources:write" ActionDatasourcesDelete = "datasources:delete" @@ -63,11 +64,17 @@ func (hs *HTTPServer) declareFixedRoles() error { Action: ActionDatasourcesWrite, Scope: ScopeDatasourcesAll, }, - {Action: ActionDatasourcesCreate}, + { + Action: ActionDatasourcesCreate, + }, { Action: ActionDatasourcesDelete, Scope: ScopeDatasourcesAll, }, + { + Action: ActionDatasourcesQuery, + Scope: ScopeDatasourcesAll, + }, }, }, Grants: []string{string(models.ROLE_ADMIN)}, @@ -86,6 +93,17 @@ func (hs *HTTPServer) declareFixedRoles() error { }, Grants: []string{string(models.ROLE_VIEWER)}, }, + { + Role: accesscontrol.RoleDTO{ + Version: 1, + Name: "fixed:datasources:compatibility:querier", + Description: "Query data sources when data source permissions are not in use", + Permissions: []accesscontrol.Permission{ + {Action: ActionDatasourcesQuery}, + }, + }, + Grants: []string{string(models.ROLE_VIEWER)}, + }, } return hs.AccessControl.DeclareFixedRoles(registrations...) From 447a088750b1513a97424ddd619080575a62ae02 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 21 Oct 2021 14:55:02 +0100 Subject: [PATCH 05/22] Annotations: Allow addition of custom values whilst tags are loading (#40702) --- packages/grafana-ui/src/components/Select/SelectBase.tsx | 2 ++ packages/grafana-ui/src/components/Select/types.ts | 1 + public/app/core/components/TagFilter/TagFilter.tsx | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 5fcdf98833e..218ebf9f989 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -88,6 +88,7 @@ const CustomControl = (props: any) => { export function SelectBase({ allowCustomValue = false, + allowCreateWhileLoading = false, 'aria-label': ariaLabel, autoFocus = false, backspaceRemovesValue = true, @@ -227,6 +228,7 @@ export function SelectBase({ if (allowCustomValue) { ReactSelectComponent = Creatable as any; + creatableProps.allowCreateWhileLoading = allowCreateWhileLoading; creatableProps.formatCreateLabel = formatCreateLabel ?? ((input: string) => `Create: ${input}`); creatableProps.onCreateOption = onCreateOption; creatableProps.isValidNewOption = isValidNewOption; diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts index 6da83a58e63..094d755f96a 100644 --- a/packages/grafana-ui/src/components/Select/types.ts +++ b/packages/grafana-ui/src/components/Select/types.ts @@ -9,6 +9,7 @@ export type InputActionMeta = { export interface SelectCommonProps { /** Aria label applied to the input field */ ['aria-label']?: string; + allowCreateWhileLoading?: boolean; allowCustomValue?: boolean; /** Focus is set to the Select when rendered*/ autoFocus?: boolean; diff --git a/public/app/core/components/TagFilter/TagFilter.tsx b/public/app/core/components/TagFilter/TagFilter.tsx index 3780b8da72c..e0761c722ff 100644 --- a/public/app/core/components/TagFilter/TagFilter.tsx +++ b/public/app/core/components/TagFilter/TagFilter.tsx @@ -68,6 +68,7 @@ export const TagFilter: FC = ({ const value = tags.map((tag) => ({ value: tag, label: tag, count: 0 })); const selectOptions = { + allowCreateWhileLoading: true, allowCustomValue, formatCreateLabel, defaultOptions: true, From f86fa876f2598794dfa1fe74155b0e7941daef2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Thu, 21 Oct 2021 15:56:31 +0200 Subject: [PATCH 06/22] Logs: Add a feature toggle to run logs volume query automatically (#40579) * Add a feature toggle to run logs volume query automatically * Add missing default value * Move feature toggle after moving auto-load button * Remove unused import --- packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + public/app/features/explore/LogsContainer.tsx | 7 ++++++- .../features/explore/SecondaryActions.test.tsx | 17 +++++++++++++++++ public/app/features/explore/state/query.test.ts | 15 ++++++++++++--- public/app/features/explore/state/query.ts | 3 +++ 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 5ec011acb69..c69f4e4dbd9 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -53,6 +53,7 @@ export interface FeatureToggles { prometheusMonaco: boolean; newNavigation: boolean; fullRangeLogsVolume: boolean; + autoLoadFullRangeLogsVolume: boolean; } /** diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index c2816f44d34..1249ab6fe25 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -69,6 +69,7 @@ export class GrafanaBootConfig implements GrafanaConfig { prometheusMonaco: false, newNavigation: false, fullRangeLogsVolume: false, + autoLoadFullRangeLogsVolume: false, }; licenseInfo: LicenseInfo = {} as LicenseInfo; rendererAvailable = false; diff --git a/public/app/features/explore/LogsContainer.tsx b/public/app/features/explore/LogsContainer.tsx index d9e44cbaa47..9deb905f8a0 100644 --- a/public/app/features/explore/LogsContainer.tsx +++ b/public/app/features/explore/LogsContainer.tsx @@ -22,6 +22,7 @@ import { Logs } from './Logs'; import { LogsCrossFadeTransition } from './utils/LogsCrossFadeTransition'; import { LiveTailControls } from './useLiveTailControls'; import { getFieldLinksForExplore } from './utils/links'; +import { config } from 'app/core/config'; interface LogsContainerProps extends PropsFromRedux { width: number; @@ -150,7 +151,11 @@ export class LogsContainer extends PureComponent { getFieldLinks={this.getFieldLinks} addResultsToCache={() => addResultsToCache(exploreId)} clearCache={() => clearCache(exploreId)} - loadingLogsVolumeAvailable={hasLogsVolumeSupport(datasourceInstance) && !!logsVolumeDataProvider} + loadingLogsVolumeAvailable={ + hasLogsVolumeSupport(datasourceInstance) && + !!logsVolumeDataProvider && + !config.featureToggles.autoLoadFullRangeLogsVolume + } onClickLoadLogsVolume={() => loadLogsVolumeData(exploreId)} /> diff --git a/public/app/features/explore/SecondaryActions.test.tsx b/public/app/features/explore/SecondaryActions.test.tsx index 6b55113f6d8..1b49db65815 100644 --- a/public/app/features/explore/SecondaryActions.test.tsx +++ b/public/app/features/explore/SecondaryActions.test.tsx @@ -3,6 +3,19 @@ import { noop } from 'lodash'; import { shallow } from 'enzyme'; import { SecondaryActions } from './SecondaryActions'; +import { config } from '@grafana/runtime'; + +jest.mock('@grafana/runtime', () => ({ + ...((jest.requireActual('@grafana/runtime') as unknown) as object), + config: { + ...((jest.requireActual('@grafana/runtime') as unknown) as any).config, + featureToggles: { + fullRangeLogsVolume: true, + autoLoadFullRangeLogsVolume: false, + }, + }, +})); + const addQueryRowButtonSelector = '[aria-label="Add row button"]'; const richHistoryButtonSelector = '[aria-label="Rich history button"]'; const queryInspectorButtonSelector = '[aria-label="Query inspector button"]'; @@ -66,4 +79,8 @@ describe('SecondaryActions', () => { wrapper.find(queryInspectorButtonSelector).simulate('click'); expect(onClickQueryInspector).toBeCalled(); }); + + it('does not render load logs volume button when auto loading is enabled', () => { + config.featureToggles.autoLoadFullRangeLogsVolume = true; + }); }); diff --git a/public/app/features/explore/state/query.test.ts b/public/app/features/explore/state/query.test.ts index 8d501fffc1c..c578e599dcb 100644 --- a/public/app/features/explore/state/query.test.ts +++ b/public/app/features/explore/state/query.test.ts @@ -33,6 +33,7 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { configureStore } from '../../../store/configureStore'; import { setTimeSrv } from '../../dashboard/services/TimeSrv'; import Mock = jest.Mock; +import { config } from '@grafana/runtime'; jest.mock('@grafana/runtime', () => ({ ...((jest.requireActual('@grafana/runtime') as unknown) as object), @@ -40,6 +41,7 @@ jest.mock('@grafana/runtime', () => ({ ...((jest.requireActual('@grafana/runtime') as unknown) as any).config, featureToggles: { fullRangeLogsVolume: true, + autoLoadFullRangeLogsVolume: false, }, }, })); @@ -322,6 +324,7 @@ describe('reducer', () => { describe('logs volume', () => { let dispatch: ThunkDispatch, getState: () => StoreState, + unsubscribes: Function[], mockLogsVolumeDataProvider: () => Observable; beforeEach(() => { @@ -352,11 +355,9 @@ describe('reducer', () => { dispatch = store.dispatch; getState = store.getState; - }); - it('should cancel any unfinished logs volume queries', async () => { setupQueryResponse(getState()); - let unsubscribes: Function[] = []; + unsubscribes = []; mockLogsVolumeDataProvider = () => { return ({ @@ -369,7 +370,9 @@ describe('reducer', () => { }, } as unknown) as Observable; }; + }); + it('should cancel any unfinished logs volume queries', async () => { await dispatch(runQueries(ExploreId.left)); // no subscriptions created yet expect(unsubscribes).toHaveLength(0); @@ -391,5 +394,11 @@ describe('reducer', () => { expect(unsubscribes[0]).toBeCalled(); expect(unsubscribes[1]).not.toBeCalled(); }); + + it('should load logs volume after running the query', async () => { + config.featureToggles.autoLoadFullRangeLogsVolume = true; + await dispatch(runQueries(ExploreId.left)); + expect(unsubscribes).toHaveLength(1); + }); }); }); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 306d3d446cf..d6028ece374 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -485,6 +485,9 @@ export const runQueries = ( const { logsVolumeData, absoluteRange } = getState().explore[exploreId]!; if (!canReuseLogsVolumeData(logsVolumeData, queries, absoluteRange)) { dispatch(cleanLogsVolumeAction({ exploreId })); + if (config.featureToggles.autoLoadFullRangeLogsVolume) { + dispatch(loadLogsVolumeData(exploreId)); + } } } else { dispatch( From c8f03139f80711f390bb50f13c212387c13bfb71 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 21 Oct 2021 15:10:57 +0100 Subject: [PATCH 07/22] Update latest.json (#40767) --- latest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latest.json b/latest.json index c32a916455f..32de21fec7a 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "8.2.1", - "testing": "8.2.1" + "stable": "8.2.2", + "testing": "8.2.2" } From 8d06bddeda4bd6bd1588b2f1ca651f318775f1f3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 16:52:26 +0200 Subject: [PATCH 08/22] Update dependency @types/node to v14 (#40405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dependency @types/node to v14 * Remove global var Co-authored-by: Renovate Bot Co-authored-by: Zoltán Bedi --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-e2e/package.json | 2 +- packages/grafana-toolkit/package.json | 2 +- packages/grafana-ui/package.json | 2 +- public/test/jest-shim.ts | 2 - yarn.lock | 41 +++++---------------- 8 files changed, 16 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 8e53525d566..ac64f90fa16 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "@types/logfmt": "^1.2.1", "@types/lru-cache": "^5.1.0", "@types/mousetrap": "1.6.3", - "@types/node": "13.7.0", + "@types/node": "14.17.27", "@types/papaparse": "5.2.0", "@types/pluralize": "^0.0.29", "@types/prismjs": "1.16.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index fff69c9c6e0..b66359958f9 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -42,7 +42,7 @@ "@types/jquery": "3.3.38", "@types/lodash": "4.14.123", "@types/marked": "1.1.0", - "@types/node": "10.14.1", + "@types/node": "14.17.27", "@types/papaparse": "5.2.0", "@types/react": "17.0.30", "@types/rollup-plugin-visualizer": "4.2.1", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 55a0992670b..2240d60ba10 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -26,7 +26,7 @@ "devDependencies": { "@rollup/plugin-commonjs": "16.0.0", "@rollup/plugin-node-resolve": "10.0.0", - "@types/node": "13.7.7", + "@types/node": "14.17.27", "@types/rollup-plugin-visualizer": "4.2.1", "@types/systemjs": "^0.20.6", "rollup": "2.33.3", diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json index ac8b2e3ce1b..180dfad5559 100644 --- a/packages/grafana-e2e/package.json +++ b/packages/grafana-e2e/package.json @@ -32,7 +32,7 @@ "devDependencies": { "@rollup/plugin-commonjs": "16.0.0", "@rollup/plugin-node-resolve": "10.0.0", - "@types/node": "13.7.7", + "@types/node": "14.17.27", "@types/rollup-plugin-visualizer": "4.2.1", "date-fns": "2.21.3", "rollup": "2.33.3", diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index 4dc0cf4f823..c262da91434 100644 --- a/packages/grafana-toolkit/package.json +++ b/packages/grafana-toolkit/package.json @@ -37,7 +37,7 @@ "@types/fs-extra": "^9.0.13", "@types/inquirer": "^6.0.3", "@types/jest": "26.0.15", - "@types/node": "^12.0.4", + "@types/node": "^14.0.0", "@types/prettier": "^2.4.0", "@types/react-dev-utils": "^9.0.4", "@types/rimraf": "^3.0.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 2d3025fec34..25b1c2ed97d 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -100,7 +100,7 @@ "@types/jquery": "3.3.38", "@types/lodash": "4.14.123", "@types/mock-raf": "1.0.2", - "@types/node": "10.14.1", + "@types/node": "14.17.27", "@types/papaparse": "5.2.0", "@types/react": "17.0.30", "@types/react-beautiful-dnd": "13.1.1", diff --git a/public/test/jest-shim.ts b/public/test/jest-shim.ts index 1343887f8b6..5b01becaaae 100644 --- a/public/test/jest-shim.ts +++ b/public/test/jest-shim.ts @@ -1,5 +1,3 @@ -declare var global: NodeJS.Global; - (global as any).requestAnimationFrame = (callback: any) => { setTimeout(callback, 0); }; diff --git a/yarn.lock b/yarn.lock index 8b24e22fe10..b1458067172 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2383,7 +2383,7 @@ __metadata: "@types/jquery": 3.3.38 "@types/lodash": 4.14.123 "@types/marked": 1.1.0 - "@types/node": 10.14.1 + "@types/node": 14.17.27 "@types/papaparse": 5.2.0 "@types/react": 17.0.30 "@types/rollup-plugin-visualizer": 4.2.1 @@ -2413,7 +2413,7 @@ __metadata: "@grafana/tsconfig": ^1.0.0-rc1 "@rollup/plugin-commonjs": 16.0.0 "@rollup/plugin-node-resolve": 10.0.0 - "@types/node": 13.7.7 + "@types/node": 14.17.27 "@types/rollup-plugin-visualizer": 4.2.1 "@types/systemjs": ^0.20.6 commander: 5.0.0 @@ -2440,7 +2440,7 @@ __metadata: "@mochajs/json-file-reporter": ^1.2.0 "@rollup/plugin-commonjs": 16.0.0 "@rollup/plugin-node-resolve": 10.0.0 - "@types/node": 13.7.7 + "@types/node": 14.17.27 "@types/rollup-plugin-visualizer": 4.2.1 blink-diff: 1.0.13 commander: 5.0.0 @@ -2573,7 +2573,7 @@ __metadata: "@types/fs-extra": ^9.0.13 "@types/inquirer": ^6.0.3 "@types/jest": 26.0.15 - "@types/node": ^12.0.4 + "@types/node": ^14.0.0 "@types/prettier": ^2.4.0 "@types/react-dev-utils": ^9.0.4 "@types/rimraf": ^3.0.0 @@ -2684,7 +2684,7 @@ __metadata: "@types/jquery": 3.3.38 "@types/lodash": 4.14.123 "@types/mock-raf": 1.0.2 - "@types/node": 10.14.1 + "@types/node": 14.17.27 "@types/papaparse": 5.2.0 "@types/react": 17.0.30 "@types/react-beautiful-dnd": 13.1.1 @@ -7767,13 +7767,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:10.14.1": - version: 10.14.1 - resolution: "@types/node@npm:10.14.1" - checksum: be0881716c81f6355035fe64fa16d6830e3f27f1e657738c46dd5605d6c4248681ceb64f8df738bf3e2107df608c4a057668dd618fa32e5697be3959fa056c4c - languageName: node - linkType: hard - "@types/node@npm:12.20.24": version: 12.20.24 resolution: "@types/node@npm:12.20.24" @@ -7781,24 +7774,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:13.7.0": - version: 13.7.0 - resolution: "@types/node@npm:13.7.0" - checksum: ccc4eb30fd225dfdbf15307704b4acdb34fe57b13ff0942b1122723fe27b218d8d30915d7ff61f63e58a8213304ec3bd58bbb9cb0bcf13e687e57f5b25af6ec6 - languageName: node - linkType: hard - -"@types/node@npm:13.7.7": - version: 13.7.7 - resolution: "@types/node@npm:13.7.7" - checksum: 834f382e965ddf6eb8aed11a949e48254cdcd7b29fa41e226c1b3d54f820e0e09cebb78db7ad20e6c18d688b7337b4376c6e08b598f49ac985ec6b07c524931a - languageName: node - linkType: hard - -"@types/node@npm:^12.0.4": - version: 12.20.28 - resolution: "@types/node@npm:12.20.28" - checksum: 09811db5eabb02062cc04ab887cf20dcae87e904ccbe4d1767ebd8c88ab31eeea9c62f6c94b16f52230a4e2f1ab0d8a411e3b3cb744c7d0e6fbc4fa5f0f25ba8 +"@types/node@npm:14.17.27, @types/node@npm:^14.0.0": + version: 14.17.27 + resolution: "@types/node@npm:14.17.27" + checksum: 4f7eeaa329f4b2b9ff4bb0bb4582ad92e328a4e66f62d5b96ac4e3b51210ed092bb56726c30643beb860f6df633a29e08ed94adad2d573c85b4794d0bf0bbc06 languageName: node linkType: hard @@ -17673,7 +17652,7 @@ fsevents@~2.1.2: "@types/logfmt": ^1.2.1 "@types/lru-cache": ^5.1.0 "@types/mousetrap": 1.6.3 - "@types/node": 13.7.0 + "@types/node": 14.17.27 "@types/ol": ^6.5.1 "@types/papaparse": 5.2.0 "@types/pluralize": ^0.0.29 From 76e30c5e97ccbc8d18fadbfc6ffa3c7c574fa07e Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Thu, 21 Oct 2021 17:04:43 +0200 Subject: [PATCH 09/22] Chore: Refactor alerting notifier tests to remove goconvey (#40758) --- .../alerting/notifiers/alertmanager_test.go | 83 +- pkg/services/alerting/notifiers/base_test.go | 53 +- .../alerting/notifiers/dingding_test.go | 71 +- .../alerting/notifiers/discord_test.go | 61 +- pkg/services/alerting/notifiers/email_test.go | 93 +- .../alerting/notifiers/googlechat_test.go | 57 +- .../alerting/notifiers/hipchat_test.go | 95 +- pkg/services/alerting/notifiers/kafka_test.go | 59 +- pkg/services/alerting/notifiers/line_test.go | 55 +- .../alerting/notifiers/opsgenie_test.go | 345 +++---- .../alerting/notifiers/pagerduty_test.go | 849 +++++++++--------- .../alerting/notifiers/pushover_test.go | 111 ++- pkg/services/alerting/notifiers/sensu_test.go | 61 +- pkg/services/alerting/notifiers/teams_test.go | 87 +- .../alerting/notifiers/telegram_test.go | 169 ++-- .../alerting/notifiers/threema_test.go | 147 ++- .../alerting/notifiers/victorops_test.go | 209 +++-- 17 files changed, 1293 insertions(+), 1312 deletions(-) diff --git a/pkg/services/alerting/notifiers/alertmanager_test.go b/pkg/services/alerting/notifiers/alertmanager_test.go index e764d3e6fde..d9084879013 100644 --- a/pkg/services/alerting/notifiers/alertmanager_test.go +++ b/pkg/services/alerting/notifiers/alertmanager_test.go @@ -10,8 +10,9 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestReplaceIllegalCharswithUnderscore(t *testing.T) { @@ -80,57 +81,55 @@ func TestWhenAlertManagerShouldNotify(t *testing.T) { //nolint:goconst func TestAlertmanagerNotifier(t *testing.T) { - Convey("Alertmanager notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "alertmanager", - Type: "alertmanager", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "alertmanager", + Type: "alertmanager", + Settings: settingsJSON, + } - _, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := `{ "url": "http://127.0.0.1:9093/", "basicAuthUser": "user", "basicAuthPassword": "password" }` + t.Run("from settings", func(t *testing.T) { + json := `{ "url": "http://127.0.0.1:9093/", "basicAuthUser": "user", "basicAuthPassword": "password" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "alertmanager", - Type: "alertmanager", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "alertmanager", + Type: "alertmanager", + Settings: settingsJSON, + } - not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - alertmanagerNotifier := not.(*AlertmanagerNotifier) + not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + alertmanagerNotifier := not.(*AlertmanagerNotifier) - So(err, ShouldBeNil) - So(alertmanagerNotifier.BasicAuthUser, ShouldEqual, "user") - So(alertmanagerNotifier.BasicAuthPassword, ShouldEqual, "password") - So(alertmanagerNotifier.URL, ShouldResemble, []string{"http://127.0.0.1:9093/"}) - }) + require.NoError(t, err) + require.Equal(t, alertmanagerNotifier.BasicAuthUser, "user") + require.Equal(t, alertmanagerNotifier.BasicAuthPassword, "password") + require.Equal(t, alertmanagerNotifier.URL, []string{"http://127.0.0.1:9093/"}) + }) - Convey("from settings with multiple alertmanager", func() { - json := `{ "url": "http://alertmanager1:9093,http://alertmanager2:9093" }` + t.Run("from settings with multiple alertmanager", func(t *testing.T) { + json := `{ "url": "http://alertmanager1:9093,http://alertmanager2:9093" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "alertmanager", - Type: "alertmanager", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "alertmanager", + Type: "alertmanager", + Settings: settingsJSON, + } - not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - alertmanagerNotifier := not.(*AlertmanagerNotifier) + not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + alertmanagerNotifier := not.(*AlertmanagerNotifier) - So(err, ShouldBeNil) - So(alertmanagerNotifier.URL, ShouldResemble, []string{"http://alertmanager1:9093", "http://alertmanager2:9093"}) - }) + require.NoError(t, err) + require.Equal(t, alertmanagerNotifier.URL, []string{"http://alertmanager1:9093", "http://alertmanager2:9093"}) }) }) } diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index 24e08b39f23..32d68376b84 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -9,8 +9,9 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestShouldSendAlertNotification(t *testing.T) { @@ -184,38 +185,36 @@ func TestShouldSendAlertNotification(t *testing.T) { } func TestBaseNotifier(t *testing.T) { - Convey("default constructor for notifiers", t, func() { - bJSON := simplejson.New() + bJSON := simplejson.New() - model := &models.AlertNotification{ - Uid: "1", - Name: "name", - Type: "email", - Settings: bJSON, - } + model := &models.AlertNotification{ + Uid: "1", + Name: "name", + Type: "email", + Settings: bJSON, + } - Convey("can parse false value", func() { - bJSON.Set("uploadImage", false) + t.Run("can parse false value", func(t *testing.T) { + bJSON.Set("uploadImage", false) - base := NewNotifierBase(model) - So(base.UploadImage, ShouldBeFalse) - }) + base := NewNotifierBase(model) + require.False(t, base.UploadImage) + }) - Convey("can parse true value", func() { - bJSON.Set("uploadImage", true) + t.Run("can parse true value", func(t *testing.T) { + bJSON.Set("uploadImage", true) - base := NewNotifierBase(model) - So(base.UploadImage, ShouldBeTrue) - }) + base := NewNotifierBase(model) + require.True(t, base.UploadImage) + }) - Convey("default value should be true for backwards compatibility", func() { - base := NewNotifierBase(model) - So(base.UploadImage, ShouldBeTrue) - }) + t.Run("default value should be true for backwards compatibility", func(t *testing.T) { + base := NewNotifierBase(model) + require.True(t, base.UploadImage) + }) - Convey("default value should be false for backwards compatibility", func() { - base := NewNotifierBase(model) - So(base.DisableResolveMessage, ShouldBeFalse) - }) + t.Run("default value should be false for backwards compatibility", func(t *testing.T) { + base := NewNotifierBase(model) + require.False(t, base.DisableResolveMessage) }) } diff --git a/pkg/services/alerting/notifiers/dingding_test.go b/pkg/services/alerting/notifiers/dingding_test.go index 6f193cfeda5..63143fb2631 100644 --- a/pkg/services/alerting/notifiers/dingding_test.go +++ b/pkg/services/alerting/notifiers/dingding_test.go @@ -9,51 +9,50 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestDingDingNotifier(t *testing.T) { - Convey("Dingding notifier tests", t, func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "dingding_testing", - Type: "dingding", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "dingding_testing", + Type: "dingding", + Settings: settingsJSON, + } - _, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) - Convey("settings should trigger incident", func() { - json := `{ "url": "https://www.google.com" }` + _, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) + t.Run("settings should trigger incident", func(t *testing.T) { + json := `{ "url": "https://www.google.com" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "dingding_testing", - Type: "dingding", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "dingding_testing", + Type: "dingding", + Settings: settingsJSON, + } - not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - notifier := not.(*DingDingNotifier) + not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + notifier := not.(*DingDingNotifier) - So(err, ShouldBeNil) - So(notifier.Name, ShouldEqual, "dingding_testing") - So(notifier.Type, ShouldEqual, "dingding") - So(notifier.URL, ShouldEqual, "https://www.google.com") + require.Nil(t, err) + require.Equal(t, "dingding_testing", notifier.Name) + require.Equal(t, "dingding", notifier.Type) + require.Equal(t, "https://www.google.com", notifier.URL) - Convey("genBody should not panic", func() { - evalContext := alerting.NewEvalContext(context.Background(), - &alerting.Rule{ - State: models.AlertStateAlerting, - Message: `{host="localhost"}`, - }, &validations.OSSPluginRequestValidator{}) - _, err = notifier.genBody(evalContext, "") - So(err, ShouldBeNil) - }) + t.Run("genBody should not panic", func(t *testing.T) { + evalContext := alerting.NewEvalContext(context.Background(), + &alerting.Rule{ + State: models.AlertStateAlerting, + Message: `{host="localhost"}`, + }, &validations.OSSPluginRequestValidator{}) + _, err = notifier.genBody(evalContext, "") + require.Nil(t, err) }) }) } diff --git a/pkg/services/alerting/notifiers/discord_test.go b/pkg/services/alerting/notifiers/discord_test.go index a50209ea6ab..809d03df105 100644 --- a/pkg/services/alerting/notifiers/discord_test.go +++ b/pkg/services/alerting/notifiers/discord_test.go @@ -6,51 +6,50 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestDiscordNotifier(t *testing.T) { - Convey("Discord notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "discord_testing", - Type: "discord", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "discord_testing", + Type: "discord", + Settings: settingsJSON, + } - _, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("settings should trigger incident", func() { - json := ` + t.Run("settings should trigger incident", func(t *testing.T) { + json := ` { "avatar_url": "https://grafana.com/img/fav32.png", "content": "@everyone Please check this notification", "url": "https://web.hook/" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "discord_testing", - Type: "discord", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "discord_testing", + Type: "discord", + Settings: settingsJSON, + } - not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - discordNotifier := not.(*DiscordNotifier) + not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + discordNotifier := not.(*DiscordNotifier) - So(err, ShouldBeNil) - So(discordNotifier.Name, ShouldEqual, "discord_testing") - So(discordNotifier.Type, ShouldEqual, "discord") - So(discordNotifier.AvatarURL, ShouldEqual, "https://grafana.com/img/fav32.png") - So(discordNotifier.Content, ShouldEqual, "@everyone Please check this notification") - So(discordNotifier.WebhookURL, ShouldEqual, "https://web.hook/") - }) + require.Nil(t, err) + require.Equal(t, "discord_testing", discordNotifier.Name) + require.Equal(t, "discord", discordNotifier.Type) + require.Equal(t, "https://grafana.com/img/fav32.png", discordNotifier.AvatarURL) + require.Equal(t, "@everyone Please check this notification", discordNotifier.Content) + require.Equal(t, "https://web.hook/", discordNotifier.WebhookURL) }) }) } diff --git a/pkg/services/alerting/notifiers/email_test.go b/pkg/services/alerting/notifiers/email_test.go index 6f01b020e65..2017e4da6c5 100644 --- a/pkg/services/alerting/notifiers/email_test.go +++ b/pkg/services/alerting/notifiers/email_test.go @@ -6,74 +6,73 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestEmailNotifier(t *testing.T) { - Convey("Email notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "email", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "email", + Settings: settingsJSON, + } - _, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := ` + t.Run("from settings", func(t *testing.T) { + json := ` { "addresses": "ops@grafana.org" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "email", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "email", + Settings: settingsJSON, + } - not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - emailNotifier := not.(*EmailNotifier) + not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + emailNotifier := not.(*EmailNotifier) - So(err, ShouldBeNil) - So(emailNotifier.Name, ShouldEqual, "ops") - So(emailNotifier.Type, ShouldEqual, "email") - So(emailNotifier.Addresses[0], ShouldEqual, "ops@grafana.org") - }) + require.Nil(t, err) + require.Equal(t, "ops", emailNotifier.Name) + require.Equal(t, "email", emailNotifier.Type) + require.Equal(t, "ops@grafana.org", emailNotifier.Addresses[0]) + }) - Convey("from settings with two emails", func() { - json := ` + t.Run("from settings with two emails", func(t *testing.T) { + json := ` { "addresses": "ops@grafana.org;dev@grafana.org" }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "ops", - Type: "email", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "ops", + Type: "email", + Settings: settingsJSON, + } - not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - emailNotifier := not.(*EmailNotifier) + not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + emailNotifier := not.(*EmailNotifier) - So(err, ShouldBeNil) - So(emailNotifier.Name, ShouldEqual, "ops") - So(emailNotifier.Type, ShouldEqual, "email") - So(len(emailNotifier.Addresses), ShouldEqual, 2) + require.Nil(t, err) + require.Equal(t, "ops", emailNotifier.Name) + require.Equal(t, "email", emailNotifier.Type) + require.Equal(t, 2, len(emailNotifier.Addresses)) - So(emailNotifier.Addresses[0], ShouldEqual, "ops@grafana.org") - So(emailNotifier.Addresses[1], ShouldEqual, "dev@grafana.org") - }) + require.Equal(t, "ops@grafana.org", emailNotifier.Addresses[0]) + require.Equal(t, "dev@grafana.org", emailNotifier.Addresses[1]) }) }) } diff --git a/pkg/services/alerting/notifiers/googlechat_test.go b/pkg/services/alerting/notifiers/googlechat_test.go index 52b33a2c018..8cde1cd3547 100644 --- a/pkg/services/alerting/notifiers/googlechat_test.go +++ b/pkg/services/alerting/notifiers/googlechat_test.go @@ -6,47 +6,46 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestGoogleChatNotifier(t *testing.T) { - Convey("Google Hangouts Chat notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "googlechat", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "googlechat", + Settings: settingsJSON, + } - _, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := ` + t.Run("from settings", func(t *testing.T) { + json := ` { "url": "http://google.com" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "googlechat", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "googlechat", + Settings: settingsJSON, + } - not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - webhookNotifier := not.(*GoogleChatNotifier) + not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + webhookNotifier := not.(*GoogleChatNotifier) - So(err, ShouldBeNil) - So(webhookNotifier.Name, ShouldEqual, "ops") - So(webhookNotifier.Type, ShouldEqual, "googlechat") - So(webhookNotifier.URL, ShouldEqual, "http://google.com") - }) + require.Nil(t, err) + require.Equal(t, "ops", webhookNotifier.Name) + require.Equal(t, "googlechat", webhookNotifier.Type) + require.Equal(t, "http://google.com", webhookNotifier.URL) }) }) } diff --git a/pkg/services/alerting/notifiers/hipchat_test.go b/pkg/services/alerting/notifiers/hipchat_test.go index cd3edc04cdc..b27e941d50f 100644 --- a/pkg/services/alerting/notifiers/hipchat_test.go +++ b/pkg/services/alerting/notifiers/hipchat_test.go @@ -6,75 +6,74 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) //nolint:goconst func TestHipChatNotifier(t *testing.T) { - Convey("HipChat notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "hipchat", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "hipchat", + Settings: settingsJSON, + } - _, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := ` + t.Run("from settings", func(t *testing.T) { + json := ` { "url": "http://google.com" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "hipchat", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "hipchat", + Settings: settingsJSON, + } - not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - hipchatNotifier := not.(*HipChatNotifier) + not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + hipchatNotifier := not.(*HipChatNotifier) - So(err, ShouldBeNil) - So(hipchatNotifier.Name, ShouldEqual, "ops") - So(hipchatNotifier.Type, ShouldEqual, "hipchat") - So(hipchatNotifier.URL, ShouldEqual, "http://google.com") - So(hipchatNotifier.APIKey, ShouldEqual, "") - So(hipchatNotifier.RoomID, ShouldEqual, "") - }) + require.Nil(t, err) + require.Equal(t, "ops", hipchatNotifier.Name) + require.Equal(t, "hipchat", hipchatNotifier.Type) + require.Equal(t, "http://google.com", hipchatNotifier.URL) + require.Equal(t, "", hipchatNotifier.APIKey) + require.Equal(t, "", hipchatNotifier.RoomID) + }) - Convey("from settings with Recipient and Mention", func() { - json := ` + t.Run("from settings with Recipient and Mention", func(t *testing.T) { + json := ` { "url": "http://www.hipchat.com", "apikey": "1234", "roomid": "1234" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "hipchat", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "hipchat", + Settings: settingsJSON, + } - not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - hipchatNotifier := not.(*HipChatNotifier) + not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + hipchatNotifier := not.(*HipChatNotifier) - So(err, ShouldBeNil) - So(hipchatNotifier.Name, ShouldEqual, "ops") - So(hipchatNotifier.Type, ShouldEqual, "hipchat") - So(hipchatNotifier.URL, ShouldEqual, "http://www.hipchat.com") - So(hipchatNotifier.APIKey, ShouldEqual, "1234") - So(hipchatNotifier.RoomID, ShouldEqual, "1234") - }) + require.Nil(t, err) + require.Equal(t, "ops", hipchatNotifier.Name) + require.Equal(t, "hipchat", hipchatNotifier.Type) + require.Equal(t, "http://www.hipchat.com", hipchatNotifier.URL) + require.Equal(t, "1234", hipchatNotifier.APIKey) + require.Equal(t, "1234", hipchatNotifier.RoomID) }) }) } diff --git a/pkg/services/alerting/notifiers/kafka_test.go b/pkg/services/alerting/notifiers/kafka_test.go index 1b83e863748..86e0be76b28 100644 --- a/pkg/services/alerting/notifiers/kafka_test.go +++ b/pkg/services/alerting/notifiers/kafka_test.go @@ -6,49 +6,48 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestKafkaNotifier(t *testing.T) { - Convey("Kafka notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "kafka_testing", - Type: "kafka", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "kafka_testing", + Type: "kafka", + Settings: settingsJSON, + } - _, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("settings should send an event to kafka", func() { - json := ` + t.Run("settings should send an event to kafka", func(t *testing.T) { + json := ` { "kafkaRestProxy": "http://localhost:8082", "kafkaTopic": "topic1" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "kafka_testing", - Type: "kafka", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "kafka_testing", + Type: "kafka", + Settings: settingsJSON, + } - not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - kafkaNotifier := not.(*KafkaNotifier) + not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + kafkaNotifier := not.(*KafkaNotifier) - So(err, ShouldBeNil) - So(kafkaNotifier.Name, ShouldEqual, "kafka_testing") - So(kafkaNotifier.Type, ShouldEqual, "kafka") - So(kafkaNotifier.Endpoint, ShouldEqual, "http://localhost:8082") - So(kafkaNotifier.Topic, ShouldEqual, "topic1") - }) + require.Nil(t, err) + require.Equal(t, "kafka_testing", kafkaNotifier.Name) + require.Equal(t, "kafka", kafkaNotifier.Type) + require.Equal(t, "http://localhost:8082", kafkaNotifier.Endpoint) + require.Equal(t, "topic1", kafkaNotifier.Topic) }) }) } diff --git a/pkg/services/alerting/notifiers/line_test.go b/pkg/services/alerting/notifiers/line_test.go index 1fc3b0a59fc..be7559a10b2 100644 --- a/pkg/services/alerting/notifiers/line_test.go +++ b/pkg/services/alerting/notifiers/line_test.go @@ -6,43 +6,42 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestLineNotifier(t *testing.T) { - Convey("Line notifier tests", t, func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "line_testing", - Type: "line", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "line_testing", + Type: "line", + Settings: settingsJSON, + } - _, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) - Convey("settings should trigger incident", func() { - json := ` + _, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) + t.Run("settings should trigger incident", func(t *testing.T) { + json := ` { "token": "abcdefgh0123456789" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "line_testing", - Type: "line", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "line_testing", + Type: "line", + Settings: settingsJSON, + } - not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue) - lineNotifier := not.(*LineNotifier) + not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue) + lineNotifier := not.(*LineNotifier) - So(err, ShouldBeNil) - So(lineNotifier.Name, ShouldEqual, "line_testing") - So(lineNotifier.Type, ShouldEqual, "line") - So(lineNotifier.Token, ShouldEqual, "abcdefgh0123456789") - }) + require.Nil(t, err) + require.Equal(t, "line_testing", lineNotifier.Name) + require.Equal(t, "line", lineNotifier.Type) + require.Equal(t, "abcdefgh0123456789", lineNotifier.Token) }) } diff --git a/pkg/services/alerting/notifiers/opsgenie_test.go b/pkg/services/alerting/notifiers/opsgenie_test.go index d220eed8984..6e339071028 100644 --- a/pkg/services/alerting/notifiers/opsgenie_test.go +++ b/pkg/services/alerting/notifiers/opsgenie_test.go @@ -2,6 +2,8 @@ package notifiers import ( "context" + "reflect" + "strings" "testing" "github.com/grafana/grafana/pkg/bus" @@ -10,217 +12,216 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestOpsGenieNotifier(t *testing.T) { - Convey("OpsGenie notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "opsgenie_testing", - Type: "opsgenie", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "opsgenie_testing", + Type: "opsgenie", + Settings: settingsJSON, + } - _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("settings should trigger incident", func() { - json := ` + t.Run("settings should trigger incident", func(t *testing.T) { + json := ` { "apiKey": "abcdefgh0123456789" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "opsgenie_testing", - Type: "opsgenie", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "opsgenie_testing", + Type: "opsgenie", + Settings: settingsJSON, + } - not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - opsgenieNotifier := not.(*OpsGenieNotifier) + not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + opsgenieNotifier := not.(*OpsGenieNotifier) - So(err, ShouldBeNil) - So(opsgenieNotifier.Name, ShouldEqual, "opsgenie_testing") - So(opsgenieNotifier.Type, ShouldEqual, "opsgenie") - So(opsgenieNotifier.APIKey, ShouldEqual, "abcdefgh0123456789") - }) + require.Nil(t, err) + require.Equal(t, "opsgenie_testing", opsgenieNotifier.Name) + require.Equal(t, "opsgenie", opsgenieNotifier.Type) + require.Equal(t, "abcdefgh0123456789", opsgenieNotifier.APIKey) }) + }) - Convey("Handling notification tags", func() { - Convey("invalid sendTagsAs value should return error", func() { - json := `{ + t.Run("Handling notification tags", func(t *testing.T) { + t.Run("invalid sendTagsAs value should return error", func(t *testing.T) { + json := `{ "apiKey": "abcdefgh0123456789", "sendTagsAs": "not_a_valid_value" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "opsgenie_testing", - Type: "opsgenie", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "opsgenie_testing", + Type: "opsgenie", + Settings: settingsJSON, + } - _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - So(err, ShouldHaveSameTypeAs, alerting.ValidationError{}) - So(err.Error(), ShouldEndWith, "Invalid value for sendTagsAs: \"not_a_valid_value\"") - }) + _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + require.Equal(t, reflect.TypeOf(err), reflect.TypeOf(alerting.ValidationError{})) + require.True(t, strings.HasSuffix(err.Error(), "Invalid value for sendTagsAs: \"not_a_valid_value\"")) + }) - Convey("alert payload should include tag pairs only as an array in the tags key when sendAsTags is not set", func() { - json := `{ + t.Run("alert payload should include tag pairs only as an array in the tags key when sendAsTags is not set", func(t *testing.T) { + json := `{ "apiKey": "abcdefgh0123456789" }` - tagPairs := []*models.Tag{ - {Key: "keyOnly"}, - {Key: "aKey", Value: "aValue"}, + tagPairs := []*models.Tag{ + {Key: "keyOnly"}, + {Key: "aKey", Value: "aValue"}, + } + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "opsgenie_testing", + Type: "opsgenie", + Settings: settingsJSON, + } + + notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error + + opsgenieNotifier := notifier.(*OpsGenieNotifier) + + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + AlertRuleTags: tagPairs, + }, &validations.OSSPluginRequestValidator{}) + evalContext.IsTestRun = true + + tags := make([]string, 0) + details := make(map[string]interface{}) + bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error { + bodyJSON, err := simplejson.NewJson([]byte(cmd.Body)) + if err == nil { + tags = bodyJSON.Get("tags").MustStringArray([]string{}) + details = bodyJSON.Get("details").MustMap(map[string]interface{}{}) } - - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "opsgenie_testing", - Type: "opsgenie", - Settings: settingsJSON, - } - - notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error - - opsgenieNotifier := notifier.(*OpsGenieNotifier) - - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - AlertRuleTags: tagPairs, - }, &validations.OSSPluginRequestValidator{}) - evalContext.IsTestRun = true - - tags := make([]string, 0) - details := make(map[string]interface{}) - bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error { - bodyJSON, err := simplejson.NewJson([]byte(cmd.Body)) - if err == nil { - tags = bodyJSON.Get("tags").MustStringArray([]string{}) - details = bodyJSON.Get("details").MustMap(map[string]interface{}{}) - } - return err - }) - - alertErr := opsgenieNotifier.createAlert(evalContext) - - So(notifierErr, ShouldBeNil) - So(alertErr, ShouldBeNil) - So(tags, ShouldResemble, []string{"keyOnly", "aKey:aValue"}) - So(details, ShouldResemble, map[string]interface{}{"url": ""}) + return err }) - Convey("alert payload should include tag pairs only as a map in the details key when sendAsTags=details", func() { - json := `{ + alertErr := opsgenieNotifier.createAlert(evalContext) + + require.Nil(t, notifierErr) + require.Nil(t, alertErr) + require.Equal(t, tags, []string{"keyOnly", "aKey:aValue"}) + require.Equal(t, details, map[string]interface{}{"url": ""}) + }) + + t.Run("alert payload should include tag pairs only as a map in the details key when sendAsTags=details", func(t *testing.T) { + json := `{ "apiKey": "abcdefgh0123456789", "sendTagsAs": "details" }` - tagPairs := []*models.Tag{ - {Key: "keyOnly"}, - {Key: "aKey", Value: "aValue"}, + tagPairs := []*models.Tag{ + {Key: "keyOnly"}, + {Key: "aKey", Value: "aValue"}, + } + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "opsgenie_testing", + Type: "opsgenie", + Settings: settingsJSON, + } + + notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error + + opsgenieNotifier := notifier.(*OpsGenieNotifier) + + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + AlertRuleTags: tagPairs, + }, nil) + evalContext.IsTestRun = true + + tags := make([]string, 0) + details := make(map[string]interface{}) + bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error { + bodyJSON, err := simplejson.NewJson([]byte(cmd.Body)) + if err == nil { + tags = bodyJSON.Get("tags").MustStringArray([]string{}) + details = bodyJSON.Get("details").MustMap(map[string]interface{}{}) } - - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "opsgenie_testing", - Type: "opsgenie", - Settings: settingsJSON, - } - - notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error - - opsgenieNotifier := notifier.(*OpsGenieNotifier) - - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - AlertRuleTags: tagPairs, - }, nil) - evalContext.IsTestRun = true - - tags := make([]string, 0) - details := make(map[string]interface{}) - bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error { - bodyJSON, err := simplejson.NewJson([]byte(cmd.Body)) - if err == nil { - tags = bodyJSON.Get("tags").MustStringArray([]string{}) - details = bodyJSON.Get("details").MustMap(map[string]interface{}{}) - } - return err - }) - - alertErr := opsgenieNotifier.createAlert(evalContext) - - So(notifierErr, ShouldBeNil) - So(alertErr, ShouldBeNil) - So(tags, ShouldResemble, []string{}) - So(details, ShouldResemble, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""}) + return err }) - Convey("alert payload should include tag pairs as both a map in the details key and an array in the tags key when sendAsTags=both", func() { - json := `{ + alertErr := opsgenieNotifier.createAlert(evalContext) + + require.Nil(t, notifierErr) + require.Nil(t, alertErr) + require.Equal(t, tags, []string{}) + require.Equal(t, details, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""}) + }) + + t.Run("alert payload should include tag pairs as both a map in the details key and an array in the tags key when sendAsTags=both", func(t *testing.T) { + json := `{ "apiKey": "abcdefgh0123456789", "sendTagsAs": "both" }` - tagPairs := []*models.Tag{ - {Key: "keyOnly"}, - {Key: "aKey", Value: "aValue"}, + tagPairs := []*models.Tag{ + {Key: "keyOnly"}, + {Key: "aKey", Value: "aValue"}, + } + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "opsgenie_testing", + Type: "opsgenie", + Settings: settingsJSON, + } + + notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error + + opsgenieNotifier := notifier.(*OpsGenieNotifier) + + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + AlertRuleTags: tagPairs, + }, nil) + evalContext.IsTestRun = true + + tags := make([]string, 0) + details := make(map[string]interface{}) + bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error { + bodyJSON, err := simplejson.NewJson([]byte(cmd.Body)) + if err == nil { + tags = bodyJSON.Get("tags").MustStringArray([]string{}) + details = bodyJSON.Get("details").MustMap(map[string]interface{}{}) } - - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "opsgenie_testing", - Type: "opsgenie", - Settings: settingsJSON, - } - - notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error - - opsgenieNotifier := notifier.(*OpsGenieNotifier) - - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - AlertRuleTags: tagPairs, - }, nil) - evalContext.IsTestRun = true - - tags := make([]string, 0) - details := make(map[string]interface{}) - bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error { - bodyJSON, err := simplejson.NewJson([]byte(cmd.Body)) - if err == nil { - tags = bodyJSON.Get("tags").MustStringArray([]string{}) - details = bodyJSON.Get("details").MustMap(map[string]interface{}{}) - } - return err - }) - - alertErr := opsgenieNotifier.createAlert(evalContext) - - So(notifierErr, ShouldBeNil) - So(alertErr, ShouldBeNil) - So(tags, ShouldResemble, []string{"keyOnly", "aKey:aValue"}) - So(details, ShouldResemble, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""}) + return err }) + + alertErr := opsgenieNotifier.createAlert(evalContext) + + require.Nil(t, notifierErr) + require.Nil(t, alertErr) + require.Equal(t, tags, []string{"keyOnly", "aKey:aValue"}) + require.Equal(t, details, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""}) }) }) } diff --git a/pkg/services/alerting/notifiers/pagerduty_test.go b/pkg/services/alerting/notifiers/pagerduty_test.go index fac067a6717..fc6bcedc0af 100644 --- a/pkg/services/alerting/notifiers/pagerduty_test.go +++ b/pkg/services/alerting/notifiers/pagerduty_test.go @@ -12,7 +12,8 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func presenceComparer(a, b string) bool { @@ -26,516 +27,512 @@ func presenceComparer(a, b string) bool { } func TestPagerdutyNotifier(t *testing.T) { - Convey("Pagerduty notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "pageduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pageduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - _, err = NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err = NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("severity should override default", func() { - json := `{ "integrationKey": "abcdefgh0123456789", "severity": "info", "tags": ["foo"]}` + t.Run("severity should override default", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789", "severity": "info", "tags": ["foo"]}` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - pagerdutyNotifier := not.(*PagerdutyNotifier) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + pagerdutyNotifier := not.(*PagerdutyNotifier) - So(err, ShouldBeNil) - So(pagerdutyNotifier.Name, ShouldEqual, "pagerduty_testing") - So(pagerdutyNotifier.Type, ShouldEqual, "pagerduty") - So(pagerdutyNotifier.Key, ShouldEqual, "abcdefgh0123456789") - So(pagerdutyNotifier.Severity, ShouldEqual, "info") - So(pagerdutyNotifier.AutoResolve, ShouldBeFalse) - }) + require.Nil(t, err) + require.Equal(t, "pagerduty_testing", pagerdutyNotifier.Name) + require.Equal(t, "pagerduty", pagerdutyNotifier.Type) + require.Equal(t, "abcdefgh0123456789", pagerdutyNotifier.Key) + require.Equal(t, "info", pagerdutyNotifier.Severity) + require.False(t, pagerdutyNotifier.AutoResolve) + }) - Convey("auto resolve and severity should have expected defaults", func() { - json := `{ "integrationKey": "abcdefgh0123456789" }` + t.Run("auto resolve and severity should have expected defaults", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789" }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - pagerdutyNotifier := not.(*PagerdutyNotifier) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + pagerdutyNotifier := not.(*PagerdutyNotifier) - So(err, ShouldBeNil) - So(pagerdutyNotifier.Name, ShouldEqual, "pagerduty_testing") - So(pagerdutyNotifier.Type, ShouldEqual, "pagerduty") - So(pagerdutyNotifier.Key, ShouldEqual, "abcdefgh0123456789") - So(pagerdutyNotifier.Severity, ShouldEqual, "critical") - So(pagerdutyNotifier.AutoResolve, ShouldBeFalse) - }) + require.Nil(t, err) + require.Equal(t, "pagerduty_testing", pagerdutyNotifier.Name) + require.Equal(t, "pagerduty", pagerdutyNotifier.Type) + require.Equal(t, "abcdefgh0123456789", pagerdutyNotifier.Key) + require.Equal(t, "critical", pagerdutyNotifier.Severity) + require.False(t, pagerdutyNotifier.AutoResolve) + }) - Convey("settings should trigger incident", func() { - json := ` + t.Run("settings should trigger incident", func(t *testing.T) { + json := ` { "integrationKey": "abcdefgh0123456789", "autoResolve": false }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - pagerdutyNotifier := not.(*PagerdutyNotifier) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + pagerdutyNotifier := not.(*PagerdutyNotifier) - So(err, ShouldBeNil) - So(pagerdutyNotifier.Name, ShouldEqual, "pagerduty_testing") - So(pagerdutyNotifier.Type, ShouldEqual, "pagerduty") - So(pagerdutyNotifier.Key, ShouldEqual, "abcdefgh0123456789") - So(pagerdutyNotifier.AutoResolve, ShouldBeFalse) - }) + require.Nil(t, err) + require.Equal(t, "pagerduty_testing", pagerdutyNotifier.Name) + require.Equal(t, "pagerduty", pagerdutyNotifier.Type) + require.Equal(t, "abcdefgh0123456789", pagerdutyNotifier.Key) + require.False(t, pagerdutyNotifier.AutoResolve) + }) - Convey("should return properly formatted default v2 event payload", func() { - json := `{ + t.Run("should return properly formatted default v2 event payload", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789", "autoResolve": false }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, err) - pagerdutyNotifier := not.(*PagerdutyNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}) - evalContext.IsTestRun = true + pagerdutyNotifier := not.(*PagerdutyNotifier) + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + }, &validations.OSSPluginRequestValidator{}) + evalContext.IsTestRun = true - payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) - payload, err := simplejson.NewJson(payloadJSON) - So(err, ShouldBeNil) + payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) + require.Nil(t, err) + payload, err := simplejson.NewJson(payloadJSON) + require.Nil(t, err) - diff := cmp.Diff(map[string]interface{}{ - "client": "Grafana", - "client_url": "", - "dedup_key": "alertId-0", - "event_action": "trigger", - "links": []interface{}{ - map[string]interface{}{ - "href": "", - }, - }, - "payload": map[string]interface{}{ - "component": "Grafana", - "source": "<>", - "custom_details": map[string]interface{}{ - "state": "alerting", - }, - "severity": "critical", - "summary": "someRule - someMessage", - "timestamp": "<>", - }, - "routing_key": "abcdefgh0123456789", - }, payload.Interface(), cmp.Comparer(presenceComparer)) - So(diff, ShouldBeEmpty) - }) + diff := cmp.Diff(map[string]interface{}{ + "client": "Grafana", + "client_url": "", + "dedup_key": "alertId-0", + "event_action": "trigger", + "links": []interface{}{ + map[string]interface{}{ + "href": "", + }, + }, + "payload": map[string]interface{}{ + "component": "Grafana", + "source": "<>", + "custom_details": map[string]interface{}{ + "state": "alerting", + }, + "severity": "critical", + "summary": "someRule - someMessage", + "timestamp": "<>", + }, + "routing_key": "abcdefgh0123456789", + }, payload.Interface(), cmp.Comparer(presenceComparer)) + require.Empty(t, diff) + }) - Convey("should return properly formatted default v2 event payload with empty message", func() { - json := `{ + t.Run("should return properly formatted default v2 event payload with empty message", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789", "autoResolve": false }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, err) - pagerdutyNotifier := not.(*PagerdutyNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}) - evalContext.IsTestRun = true + pagerdutyNotifier := not.(*PagerdutyNotifier) + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + State: models.AlertStateAlerting, + }, &validations.OSSPluginRequestValidator{}) + evalContext.IsTestRun = true - payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) - payload, err := simplejson.NewJson(payloadJSON) - So(err, ShouldBeNil) + payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) + require.Nil(t, err) + payload, err := simplejson.NewJson(payloadJSON) + require.Nil(t, err) - diff := cmp.Diff(map[string]interface{}{ - "client": "Grafana", - "client_url": "", - "dedup_key": "alertId-0", - "event_action": "trigger", - "links": []interface{}{ - map[string]interface{}{ - "href": "", - }, - }, - "payload": map[string]interface{}{ - "component": "Grafana", - "source": "<>", - "custom_details": map[string]interface{}{ - "state": "alerting", - }, - "severity": "critical", - "summary": "someRule", - "timestamp": "<>", - }, - "routing_key": "abcdefgh0123456789", - }, payload.Interface(), cmp.Comparer(presenceComparer)) - So(diff, ShouldBeEmpty) - }) + diff := cmp.Diff(map[string]interface{}{ + "client": "Grafana", + "client_url": "", + "dedup_key": "alertId-0", + "event_action": "trigger", + "links": []interface{}{ + map[string]interface{}{ + "href": "", + }, + }, + "payload": map[string]interface{}{ + "component": "Grafana", + "source": "<>", + "custom_details": map[string]interface{}{ + "state": "alerting", + }, + "severity": "critical", + "summary": "someRule", + "timestamp": "<>", + }, + "routing_key": "abcdefgh0123456789", + }, payload.Interface(), cmp.Comparer(presenceComparer)) + require.Empty(t, diff) + }) - Convey("should return properly formatted payload with message moved to details", func() { - json := `{ + t.Run("should return properly formatted payload with message moved to details", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789", "autoResolve": false, "messageInDetails": true }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, err) - pagerdutyNotifier := not.(*PagerdutyNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}) - evalContext.IsTestRun = true - evalContext.EvalMatches = []*alerting.EvalMatch{ - { - // nil is a terrible value to test with, but the cmp.Diff doesn't - // like comparing actual floats. So this is roughly the equivalent - // of <> - Value: null.FloatFromPtr(nil), - Metric: "someMetric", + pagerdutyNotifier := not.(*PagerdutyNotifier) + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + }, &validations.OSSPluginRequestValidator{}) + evalContext.IsTestRun = true + evalContext.EvalMatches = []*alerting.EvalMatch{ + { + // nil is a terrible value to test with, but the cmp.Diff doesn't + // like comparing actual floats. So this is roughly the equivalent + // of <> + Value: null.FloatFromPtr(nil), + Metric: "someMetric", + }, + } + + payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) + require.NoError(t, err) + payload, err := simplejson.NewJson(payloadJSON) + require.NoError(t, err) + + diff := cmp.Diff(map[string]interface{}{ + "client": "Grafana", + "client_url": "", + "dedup_key": "alertId-0", + "event_action": "trigger", + "links": []interface{}{ + map[string]interface{}{ + "href": "", + }, + }, + "payload": map[string]interface{}{ + "component": "Grafana", + "source": "<>", + "custom_details": map[string]interface{}{ + "message": "someMessage", + "queries": map[string]interface{}{ + "someMetric": nil, }, - } + "state": "alerting", + }, + "severity": "critical", + "summary": "someRule", + "timestamp": "<>", + }, + "routing_key": "abcdefgh0123456789", + }, payload.Interface(), cmp.Comparer(presenceComparer)) + require.Empty(t, diff) + }) - payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) - payload, err := simplejson.NewJson(payloadJSON) - So(err, ShouldBeNil) - - diff := cmp.Diff(map[string]interface{}{ - "client": "Grafana", - "client_url": "", - "dedup_key": "alertId-0", - "event_action": "trigger", - "links": []interface{}{ - map[string]interface{}{ - "href": "", - }, - }, - "payload": map[string]interface{}{ - "component": "Grafana", - "source": "<>", - "custom_details": map[string]interface{}{ - "message": "someMessage", - "queries": map[string]interface{}{ - "someMetric": nil, - }, - "state": "alerting", - }, - "severity": "critical", - "summary": "someRule", - "timestamp": "<>", - }, - "routing_key": "abcdefgh0123456789", - }, payload.Interface(), cmp.Comparer(presenceComparer)) - So(diff, ShouldBeEmpty) - }) - - Convey("should return properly formatted v2 event payload when using override tags", func() { - json := `{ + t.Run("should return properly formatted v2 event payload when using override tags", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789", "autoResolve": false }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.NoError(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.NoError(t, err) - pagerdutyNotifier := not.(*PagerdutyNotifier) + pagerdutyNotifier := not.(*PagerdutyNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - AlertRuleTags: []*models.Tag{ - {Key: "keyOnly"}, - {Key: "group", Value: "aGroup"}, - {Key: "class", Value: "aClass"}, - {Key: "component", Value: "aComponent"}, - {Key: "severity", Value: "warning"}, - {Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)}, - }, - }, &validations.OSSPluginRequestValidator{}) - evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" - evalContext.IsTestRun = true + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + AlertRuleTags: []*models.Tag{ + {Key: "keyOnly"}, + {Key: "group", Value: "aGroup"}, + {Key: "class", Value: "aClass"}, + {Key: "component", Value: "aComponent"}, + {Key: "severity", Value: "warning"}, + {Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)}, + }, + }, &validations.OSSPluginRequestValidator{}) + evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" + evalContext.IsTestRun = true - payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) - payload, err := simplejson.NewJson(payloadJSON) - So(err, ShouldBeNil) + payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) + require.NoError(t, err) + payload, err := simplejson.NewJson(payloadJSON) + require.NoError(t, err) - diff := cmp.Diff(map[string]interface{}{ - "client": "Grafana", - "client_url": "", - "dedup_key": "key-" + strings.Repeat("x", 250), - "event_action": "trigger", - "links": []interface{}{ - map[string]interface{}{ - "href": "", - }, - }, - "payload": map[string]interface{}{ - "source": "<>", - "component": "aComponent", - "custom_details": map[string]interface{}{ - "group": "aGroup", - "class": "aClass", - "component": "aComponent", - "severity": "warning", - "dedup_key": "key-" + strings.Repeat("x", 250), - "keyOnly": "", - "state": "alerting", - }, - "severity": "warning", - "summary": "someRule - someMessage", - "timestamp": "<>", - "class": "aClass", - "group": "aGroup", - }, - "images": []interface{}{ - map[string]interface{}{ - "src": "http://somewhere.com/omg_dont_panic.png", - }, - }, - "routing_key": "abcdefgh0123456789", - }, payload.Interface(), cmp.Comparer(presenceComparer)) - So(diff, ShouldBeEmpty) - }) + diff := cmp.Diff(map[string]interface{}{ + "client": "Grafana", + "client_url": "", + "dedup_key": "key-" + strings.Repeat("x", 250), + "event_action": "trigger", + "links": []interface{}{ + map[string]interface{}{ + "href": "", + }, + }, + "payload": map[string]interface{}{ + "source": "<>", + "component": "aComponent", + "custom_details": map[string]interface{}{ + "group": "aGroup", + "class": "aClass", + "component": "aComponent", + "severity": "warning", + "dedup_key": "key-" + strings.Repeat("x", 250), + "keyOnly": "", + "state": "alerting", + }, + "severity": "warning", + "summary": "someRule - someMessage", + "timestamp": "<>", + "class": "aClass", + "group": "aGroup", + }, + "images": []interface{}{ + map[string]interface{}{ + "src": "http://somewhere.com/omg_dont_panic.png", + }, + }, + "routing_key": "abcdefgh0123456789", + }, payload.Interface(), cmp.Comparer(presenceComparer)) + require.Empty(t, diff) + }) - Convey("should support multiple levels of severity", func() { - json := `{ + t.Run("should support multiple levels of severity", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789", "autoResolve": false }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.NoError(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.NoError(t, err) - pagerdutyNotifier := not.(*PagerdutyNotifier) + pagerdutyNotifier := not.(*PagerdutyNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - AlertRuleTags: []*models.Tag{ - {Key: "keyOnly"}, - {Key: "group", Value: "aGroup"}, - {Key: "class", Value: "aClass"}, - {Key: "component", Value: "aComponent"}, - {Key: "severity", Value: "info"}, - }, - }, &validations.OSSPluginRequestValidator{}) - evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" - evalContext.IsTestRun = true + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + AlertRuleTags: []*models.Tag{ + {Key: "keyOnly"}, + {Key: "group", Value: "aGroup"}, + {Key: "class", Value: "aClass"}, + {Key: "component", Value: "aComponent"}, + {Key: "severity", Value: "info"}, + }, + }, &validations.OSSPluginRequestValidator{}) + evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" + evalContext.IsTestRun = true - payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) - payload, err := simplejson.NewJson(payloadJSON) - So(err, ShouldBeNil) + payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) + require.NoError(t, err) + payload, err := simplejson.NewJson(payloadJSON) + require.NoError(t, err) - diff := cmp.Diff(map[string]interface{}{ - "client": "Grafana", - "client_url": "", - "dedup_key": "alertId-0", - "event_action": "trigger", - "links": []interface{}{ - map[string]interface{}{ - "href": "", - }, - }, - "payload": map[string]interface{}{ - "source": "<>", - "component": "aComponent", - "custom_details": map[string]interface{}{ - "group": "aGroup", - "class": "aClass", - "component": "aComponent", - "severity": "info", - "keyOnly": "", - "state": "alerting", - }, - "severity": "info", - "summary": "someRule - someMessage", - "timestamp": "<>", - "class": "aClass", - "group": "aGroup", - }, - "images": []interface{}{ - map[string]interface{}{ - "src": "http://somewhere.com/omg_dont_panic.png", - }, - }, - "routing_key": "abcdefgh0123456789", - }, payload.Interface(), cmp.Comparer(presenceComparer)) - So(diff, ShouldBeEmpty) - }) + diff := cmp.Diff(map[string]interface{}{ + "client": "Grafana", + "client_url": "", + "dedup_key": "alertId-0", + "event_action": "trigger", + "links": []interface{}{ + map[string]interface{}{ + "href": "", + }, + }, + "payload": map[string]interface{}{ + "source": "<>", + "component": "aComponent", + "custom_details": map[string]interface{}{ + "group": "aGroup", + "class": "aClass", + "component": "aComponent", + "severity": "info", + "keyOnly": "", + "state": "alerting", + }, + "severity": "info", + "summary": "someRule - someMessage", + "timestamp": "<>", + "class": "aClass", + "group": "aGroup", + }, + "images": []interface{}{ + map[string]interface{}{ + "src": "http://somewhere.com/omg_dont_panic.png", + }, + }, + "routing_key": "abcdefgh0123456789", + }, payload.Interface(), cmp.Comparer(presenceComparer)) + require.Empty(t, diff) + }) - Convey("should ignore invalid severity for PD but keep the tag", func() { - json := `{ + t.Run("should ignore invalid severity for PD but keep the tag", func(t *testing.T) { + json := `{ "integrationKey": "abcdefgh0123456789", "autoResolve": false, "severity": "critical" }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.NoError(t, err) - model := &models.AlertNotification{ - Name: "pagerduty_testing", - Type: "pagerduty", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "pagerduty_testing", + Type: "pagerduty", + Settings: settingsJSON, + } - not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.NoError(t, err) - pagerdutyNotifier := not.(*PagerdutyNotifier) + pagerdutyNotifier := not.(*PagerdutyNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - AlertRuleTags: []*models.Tag{ - {Key: "keyOnly"}, - {Key: "group", Value: "aGroup"}, - {Key: "class", Value: "aClass"}, - {Key: "component", Value: "aComponent"}, - {Key: "severity", Value: "llama"}, - }, - }, &validations.OSSPluginRequestValidator{}) - evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" - evalContext.IsTestRun = true + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + AlertRuleTags: []*models.Tag{ + {Key: "keyOnly"}, + {Key: "group", Value: "aGroup"}, + {Key: "class", Value: "aClass"}, + {Key: "component", Value: "aComponent"}, + {Key: "severity", Value: "llama"}, + }, + }, &validations.OSSPluginRequestValidator{}) + evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" + evalContext.IsTestRun = true - payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) - payload, err := simplejson.NewJson(payloadJSON) - So(err, ShouldBeNil) + payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) + require.NoError(t, err) + payload, err := simplejson.NewJson(payloadJSON) + require.NoError(t, err) - diff := cmp.Diff(map[string]interface{}{ - "client": "Grafana", - "client_url": "", - "dedup_key": "alertId-0", - "event_action": "trigger", - "links": []interface{}{ - map[string]interface{}{ - "href": "", - }, - }, - "payload": map[string]interface{}{ - "source": "<>", - "component": "aComponent", - "custom_details": map[string]interface{}{ - "group": "aGroup", - "class": "aClass", - "component": "aComponent", - "severity": "llama", - "keyOnly": "", - "state": "alerting", - }, - "severity": "critical", - "summary": "someRule - someMessage", - "timestamp": "<>", - "class": "aClass", - "group": "aGroup", - }, - "images": []interface{}{ - map[string]interface{}{ - "src": "http://somewhere.com/omg_dont_panic.png", - }, - }, - "routing_key": "abcdefgh0123456789", - }, payload.Interface(), cmp.Comparer(presenceComparer)) - So(diff, ShouldBeEmpty) - }) - }) + diff := cmp.Diff(map[string]interface{}{ + "client": "Grafana", + "client_url": "", + "dedup_key": "alertId-0", + "event_action": "trigger", + "links": []interface{}{ + map[string]interface{}{ + "href": "", + }, + }, + "payload": map[string]interface{}{ + "source": "<>", + "component": "aComponent", + "custom_details": map[string]interface{}{ + "group": "aGroup", + "class": "aClass", + "component": "aComponent", + "severity": "llama", + "keyOnly": "", + "state": "alerting", + }, + "severity": "critical", + "summary": "someRule - someMessage", + "timestamp": "<>", + "class": "aClass", + "group": "aGroup", + }, + "images": []interface{}{ + map[string]interface{}{ + "src": "http://somewhere.com/omg_dont_panic.png", + }, + }, + "routing_key": "abcdefgh0123456789", + }, payload.Interface(), cmp.Comparer(presenceComparer)) + require.Empty(t, diff) }) } diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go index cef325a87c3..fe27b424b0f 100644 --- a/pkg/services/alerting/notifiers/pushover_test.go +++ b/pkg/services/alerting/notifiers/pushover_test.go @@ -10,28 +10,28 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestPushoverNotifier(t *testing.T) { - Convey("Pushover notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "Pushover", - Type: "pushover", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "Pushover", + Type: "pushover", + Settings: settingsJSON, + } - _, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := ` + t.Run("from settings", func(t *testing.T) { + json := ` { "apiToken": "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve", "userKey": "tzNZYf36y0ohWwXo4XoUrB61rz1A4o", @@ -41,58 +41,55 @@ func TestPushoverNotifier(t *testing.T) { "okSound": "magic" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "Pushover", - Type: "pushover", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "Pushover", + Type: "pushover", + Settings: settingsJSON, + } - not, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - pushoverNotifier := not.(*PushoverNotifier) + not, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + pushoverNotifier := not.(*PushoverNotifier) - So(err, ShouldBeNil) - So(pushoverNotifier.Name, ShouldEqual, "Pushover") - So(pushoverNotifier.Type, ShouldEqual, "pushover") - So(pushoverNotifier.APIToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve") - So(pushoverNotifier.UserKey, ShouldEqual, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o") - So(pushoverNotifier.AlertingPriority, ShouldEqual, 1) - So(pushoverNotifier.OKPriority, ShouldEqual, 2) - So(pushoverNotifier.AlertingSound, ShouldEqual, "pushover") - So(pushoverNotifier.OKSound, ShouldEqual, "magic") - }) + require.Nil(t, err) + require.Equal(t, "Pushover", pushoverNotifier.Name) + require.Equal(t, "pushover", pushoverNotifier.Type) + require.Equal(t, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve", pushoverNotifier.APIToken) + require.Equal(t, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o", pushoverNotifier.UserKey) + require.Equal(t, 1, pushoverNotifier.AlertingPriority) + require.Equal(t, 2, pushoverNotifier.OKPriority) + require.Equal(t, "pushover", pushoverNotifier.AlertingSound) + require.Equal(t, "magic", pushoverNotifier.OKSound) }) }) } func TestGenPushoverBody(t *testing.T) { - Convey("Pushover body generation tests", t, func() { - Convey("Given common sounds", func() { - sirenSound := "siren_sound_tst" - successSound := "success_sound_tst" - notifier := &PushoverNotifier{AlertingSound: sirenSound, OKSound: successSound} + t.Run("Given common sounds", func(t *testing.T) { + sirenSound := "siren_sound_tst" + successSound := "success_sound_tst" + notifier := &PushoverNotifier{AlertingSound: sirenSound, OKSound: successSound} - Convey("When alert is firing - should use siren sound", func() { - evalContext := alerting.NewEvalContext(context.Background(), - &alerting.Rule{ - State: models.AlertStateAlerting, - }, &validations.OSSPluginRequestValidator{}) - _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") + t.Run("When alert is firing - should use siren sound", func(t *testing.T) { + evalContext := alerting.NewEvalContext(context.Background(), + &alerting.Rule{ + State: models.AlertStateAlerting, + }, &validations.OSSPluginRequestValidator{}) + _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") - So(err, ShouldBeNil) - So(strings.Contains(pushoverBody.String(), sirenSound), ShouldBeTrue) - }) + require.Nil(t, err) + require.True(t, strings.Contains(pushoverBody.String(), sirenSound)) + }) - Convey("When alert is ok - should use success sound", func() { - evalContext := alerting.NewEvalContext(context.Background(), - &alerting.Rule{ - State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}) - _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") + t.Run("When alert is ok - should use success sound", func(t *testing.T) { + evalContext := alerting.NewEvalContext(context.Background(), + &alerting.Rule{ + State: models.AlertStateOK, + }, &validations.OSSPluginRequestValidator{}) + _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") - So(err, ShouldBeNil) - So(strings.Contains(pushoverBody.String(), successSound), ShouldBeTrue) - }) + require.Nil(t, err) + require.True(t, strings.Contains(pushoverBody.String(), successSound)) }) }) } diff --git a/pkg/services/alerting/notifiers/sensu_test.go b/pkg/services/alerting/notifiers/sensu_test.go index 11684e57e16..2be6f71d325 100644 --- a/pkg/services/alerting/notifiers/sensu_test.go +++ b/pkg/services/alerting/notifiers/sensu_test.go @@ -6,51 +6,50 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestSensuNotifier(t *testing.T) { - Convey("Sensu notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "sensu", - Type: "sensu", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "sensu", + Type: "sensu", + Settings: settingsJSON, + } - _, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := ` + t.Run("from settings", func(t *testing.T) { + json := ` { "url": "http://sensu-api.example.com:4567/results", "source": "grafana_instance_01", "handler": "myhandler" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "sensu", - Type: "sensu", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "sensu", + Type: "sensu", + Settings: settingsJSON, + } - not, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - sensuNotifier := not.(*SensuNotifier) + not, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + sensuNotifier := not.(*SensuNotifier) - So(err, ShouldBeNil) - So(sensuNotifier.Name, ShouldEqual, "sensu") - So(sensuNotifier.Type, ShouldEqual, "sensu") - So(sensuNotifier.URL, ShouldEqual, "http://sensu-api.example.com:4567/results") - So(sensuNotifier.Source, ShouldEqual, "grafana_instance_01") - So(sensuNotifier.Handler, ShouldEqual, "myhandler") - }) + require.Nil(t, err) + require.Equal(t, "sensu", sensuNotifier.Name) + require.Equal(t, "sensu", sensuNotifier.Type) + require.Equal(t, "http://sensu-api.example.com:4567/results", sensuNotifier.URL) + require.Equal(t, "grafana_instance_01", sensuNotifier.Source) + require.Equal(t, "myhandler", sensuNotifier.Handler) }) }) } diff --git a/pkg/services/alerting/notifiers/teams_test.go b/pkg/services/alerting/notifiers/teams_test.go index f1a267a66c2..3c43931ee8e 100644 --- a/pkg/services/alerting/notifiers/teams_test.go +++ b/pkg/services/alerting/notifiers/teams_test.go @@ -6,69 +6,68 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestTeamsNotifier(t *testing.T) { - Convey("Teams notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "teams", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "teams", + Settings: settingsJSON, + } - _, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := ` + t.Run("from settings", func(t *testing.T) { + json := ` { "url": "http://google.com" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "teams", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "teams", + Settings: settingsJSON, + } - not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - teamsNotifier := not.(*TeamsNotifier) + not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + teamsNotifier := not.(*TeamsNotifier) - So(err, ShouldBeNil) - So(teamsNotifier.Name, ShouldEqual, "ops") - So(teamsNotifier.Type, ShouldEqual, "teams") - So(teamsNotifier.URL, ShouldEqual, "http://google.com") - }) + require.Nil(t, err) + require.Equal(t, "ops", teamsNotifier.Name) + require.Equal(t, "teams", teamsNotifier.Type) + require.Equal(t, "http://google.com", teamsNotifier.URL) + }) - Convey("from settings with Recipient and Mention", func() { - json := ` + t.Run("from settings with Recipient and Mention", func(t *testing.T) { + json := ` { "url": "http://google.com" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "ops", - Type: "teams", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "ops", + Type: "teams", + Settings: settingsJSON, + } - not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - teamsNotifier := not.(*TeamsNotifier) + not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + teamsNotifier := not.(*TeamsNotifier) - So(err, ShouldBeNil) - So(teamsNotifier.Name, ShouldEqual, "ops") - So(teamsNotifier.Type, ShouldEqual, "teams") - So(teamsNotifier.URL, ShouldEqual, "http://google.com") - }) + require.Nil(t, err) + require.Equal(t, "ops", teamsNotifier.Name) + require.Equal(t, "teams", teamsNotifier.Type) + require.Equal(t, "http://google.com", teamsNotifier.URL) }) }) } diff --git a/pkg/services/alerting/notifiers/telegram_test.go b/pkg/services/alerting/notifiers/telegram_test.go index 7d2424b1324..b0b1af92de8 100644 --- a/pkg/services/alerting/notifiers/telegram_test.go +++ b/pkg/services/alerting/notifiers/telegram_test.go @@ -9,51 +9,67 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestTelegramNotifier(t *testing.T) { - Convey("Telegram notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "telegram_testing", - Type: "telegram", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "telegram_testing", + Type: "telegram", + Settings: settingsJSON, + } - _, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("settings should trigger incident", func() { - json := ` + t.Run("settings should trigger incident", func(t *testing.T) { + json := ` { "bottoken": "abcdefgh0123456789", "chatid": "-1234567890" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "telegram_testing", - Type: "telegram", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "telegram_testing", + Type: "telegram", + Settings: settingsJSON, + } - not, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - telegramNotifier := not.(*TelegramNotifier) + not, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + telegramNotifier := not.(*TelegramNotifier) - So(err, ShouldBeNil) - So(telegramNotifier.Name, ShouldEqual, "telegram_testing") - So(telegramNotifier.Type, ShouldEqual, "telegram") - So(telegramNotifier.BotToken, ShouldEqual, "abcdefgh0123456789") - So(telegramNotifier.ChatID, ShouldEqual, "-1234567890") - }) + require.Nil(t, err) + require.Equal(t, "telegram_testing", telegramNotifier.Name) + require.Equal(t, "telegram", telegramNotifier.Type) + require.Equal(t, "abcdefgh0123456789", telegramNotifier.BotToken) + require.Equal(t, "-1234567890", telegramNotifier.ChatID) + }) - Convey("generateCaption should generate a message with all pertinent details", func() { + t.Run("generateCaption should generate a message with all pertinent details", func(t *testing.T) { + evalContext := alerting.NewEvalContext(context.Background(), + &alerting.Rule{ + Name: "This is an alarm", + Message: "Some kind of message.", + State: models.AlertStateOK, + }, &validations.OSSPluginRequestValidator{}) + + caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "") + require.LessOrEqual(t, len(caption), 1024) + require.Contains(t, caption, "Some kind of message.") + require.Contains(t, caption, "[OK] This is an alarm") + require.Contains(t, caption, "http://grafa.url/abcdef") + }) + + t.Run("When generating a message", func(t *testing.T) { + t.Run("URL should be skipped if it's too long", func(t *testing.T) { evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ Name: "This is an alarm", @@ -61,65 +77,48 @@ func TestTelegramNotifier(t *testing.T) { State: models.AlertStateOK, }, &validations.OSSPluginRequestValidator{}) - caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "") - So(len(caption), ShouldBeLessThanOrEqualTo, 1024) - So(caption, ShouldContainSubstring, "Some kind of message.") - So(caption, ShouldContainSubstring, "[OK] This is an alarm") - So(caption, ShouldContainSubstring, "http://grafa.url/abcdef") + caption := generateImageCaption(evalContext, + "http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "foo bar") + require.LessOrEqual(t, len(caption), 1024) + require.Contains(t, caption, "Some kind of message.") + require.Contains(t, caption, "[OK] This is an alarm") + require.Contains(t, caption, "foo bar") + require.NotContains(t, caption, "http") }) - Convey("When generating a message", func() { - Convey("URL should be skipped if it's too long", func() { - evalContext := alerting.NewEvalContext(context.Background(), - &alerting.Rule{ - Name: "This is an alarm", - Message: "Some kind of message.", - State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}) + t.Run("Message should be trimmed if it's too long", func(t *testing.T) { + evalContext := alerting.NewEvalContext(context.Background(), + &alerting.Rule{ + Name: "This is an alarm", + Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.", + State: models.AlertStateOK, + }, &validations.OSSPluginRequestValidator{}) - caption := generateImageCaption(evalContext, - "http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "foo bar") - So(len(caption), ShouldBeLessThanOrEqualTo, 1024) - So(caption, ShouldContainSubstring, "Some kind of message.") - So(caption, ShouldContainSubstring, "[OK] This is an alarm") - So(caption, ShouldContainSubstring, "foo bar") - So(caption, ShouldNotContainSubstring, "http") - }) + caption := generateImageCaption(evalContext, + "http://grafa.url/foo", + "") + require.LessOrEqual(t, len(caption), 1024) + require.Contains(t, caption, "[OK] This is an alarm") + require.NotContains(t, caption, "http") + require.Contains(t, caption, "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri") + }) - Convey("Message should be trimmed if it's too long", func() { - evalContext := alerting.NewEvalContext(context.Background(), - &alerting.Rule{ - Name: "This is an alarm", - Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.", - State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}) + t.Run("Metrics should be skipped if they don't fit", func(t *testing.T) { + evalContext := alerting.NewEvalContext(context.Background(), + &alerting.Rule{ + Name: "This is an alarm", + Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri", + State: models.AlertStateOK, + }, &validations.OSSPluginRequestValidator{}) - caption := generateImageCaption(evalContext, - "http://grafa.url/foo", - "") - So(len(caption), ShouldBeLessThanOrEqualTo, 1024) - So(caption, ShouldContainSubstring, "[OK] This is an alarm") - So(caption, ShouldNotContainSubstring, "http") - So(caption, ShouldContainSubstring, "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri") - }) - - Convey("Metrics should be skipped if they don't fit", func() { - evalContext := alerting.NewEvalContext(context.Background(), - &alerting.Rule{ - Name: "This is an alarm", - Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri", - State: models.AlertStateOK, - }, &validations.OSSPluginRequestValidator{}) - - caption := generateImageCaption(evalContext, - "http://grafa.url/foo", - "foo bar long song") - So(len(caption), ShouldBeLessThanOrEqualTo, 1024) - So(caption, ShouldContainSubstring, "[OK] This is an alarm") - So(caption, ShouldNotContainSubstring, "http") - So(caption, ShouldNotContainSubstring, "foo bar") - }) + caption := generateImageCaption(evalContext, + "http://grafa.url/foo", + "foo bar long song") + require.LessOrEqual(t, len(caption), 1024) + require.Contains(t, caption, "[OK] This is an alarm") + require.NotContains(t, caption, "http") + require.NotContains(t, caption, "foo bar") }) }) }) diff --git a/pkg/services/alerting/notifiers/threema_test.go b/pkg/services/alerting/notifiers/threema_test.go index 6ccfcee7f75..4800afbe53e 100644 --- a/pkg/services/alerting/notifiers/threema_test.go +++ b/pkg/services/alerting/notifiers/threema_test.go @@ -8,118 +8,117 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func TestThreemaNotifier(t *testing.T) { - Convey("Threema notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "threema_testing", - Type: "threema", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "threema_testing", + Type: "threema", + Settings: settingsJSON, + } - _, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("valid settings should be parsed successfully", func() { - json := ` + t.Run("valid settings should be parsed successfully", func(t *testing.T) { + json := ` { "gateway_id": "*3MAGWID", "recipient_id": "ECHOECHO", "api_secret": "1234" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "threema_testing", - Type: "threema", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "threema_testing", + Type: "threema", + Settings: settingsJSON, + } - not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) - threemaNotifier := not.(*ThreemaNotifier) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, err) + threemaNotifier := not.(*ThreemaNotifier) - So(err, ShouldBeNil) - So(threemaNotifier.Name, ShouldEqual, "threema_testing") - So(threemaNotifier.Type, ShouldEqual, "threema") - So(threemaNotifier.GatewayID, ShouldEqual, "*3MAGWID") - So(threemaNotifier.RecipientID, ShouldEqual, "ECHOECHO") - So(threemaNotifier.APISecret, ShouldEqual, "1234") - }) + require.Nil(t, err) + require.Equal(t, "threema_testing", threemaNotifier.Name) + require.Equal(t, "threema", threemaNotifier.Type) + require.Equal(t, "*3MAGWID", threemaNotifier.GatewayID) + require.Equal(t, "ECHOECHO", threemaNotifier.RecipientID) + require.Equal(t, "1234", threemaNotifier.APISecret) + }) - Convey("invalid Threema Gateway IDs should be rejected (prefix)", func() { - json := ` + t.Run("invalid Threema Gateway IDs should be rejected (prefix)", func(t *testing.T) { + json := ` { "gateway_id": "ECHOECHO", "recipient_id": "ECHOECHO", "api_secret": "1234" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "threema_testing", - Type: "threema", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "threema_testing", + Type: "threema", + Settings: settingsJSON, + } - not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(not, ShouldBeNil) - var valErr alerting.ValidationError - So(errors.As(err, &valErr), ShouldBeTrue) - So(valErr.Reason, ShouldEqual, "Invalid Threema Gateway ID: Must start with a *") - }) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, not) + var valErr alerting.ValidationError + require.True(t, errors.As(err, &valErr)) + require.Equal(t, "Invalid Threema Gateway ID: Must start with a *", valErr.Reason) + }) - Convey("invalid Threema Gateway IDs should be rejected (length)", func() { - json := ` + t.Run("invalid Threema Gateway IDs should be rejected (length)", func(t *testing.T) { + json := ` { "gateway_id": "*ECHOECHO", "recipient_id": "ECHOECHO", "api_secret": "1234" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "threema_testing", - Type: "threema", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "threema_testing", + Type: "threema", + Settings: settingsJSON, + } - not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(not, ShouldBeNil) - var valErr alerting.ValidationError - So(errors.As(err, &valErr), ShouldBeTrue) - So(valErr.Reason, ShouldEqual, "Invalid Threema Gateway ID: Must be 8 characters long") - }) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, not) + var valErr alerting.ValidationError + require.True(t, errors.As(err, &valErr)) + require.Equal(t, "Invalid Threema Gateway ID: Must be 8 characters long", valErr.Reason) + }) - Convey("invalid Threema Recipient IDs should be rejected (length)", func() { - json := ` + t.Run("invalid Threema Recipient IDs should be rejected (length)", func(t *testing.T) { + json := ` { "gateway_id": "*3MAGWID", "recipient_id": "ECHOECH", "api_secret": "1234" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "threema_testing", - Type: "threema", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "threema_testing", + Type: "threema", + Settings: settingsJSON, + } - not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(not, ShouldBeNil) - var valErr alerting.ValidationError - So(errors.As(err, &valErr), ShouldBeTrue) - So(valErr.Reason, ShouldEqual, "Invalid Threema Recipient ID: Must be 8 characters long") - }) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, not) + var valErr alerting.ValidationError + require.True(t, errors.As(err, &valErr)) + require.Equal(t, "Invalid Threema Recipient ID: Must be 8 characters long", valErr.Reason) }) }) } diff --git a/pkg/services/alerting/notifiers/victorops_test.go b/pkg/services/alerting/notifiers/victorops_test.go index 264a85b0907..8678e0da1ac 100644 --- a/pkg/services/alerting/notifiers/victorops_test.go +++ b/pkg/services/alerting/notifiers/victorops_test.go @@ -10,7 +10,8 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/validations" - . "github.com/smartystreets/goconvey/convey" + + "github.com/stretchr/testify/require" ) func presenceComparerInt(a, b int64) bool { @@ -23,140 +24,138 @@ func presenceComparerInt(a, b int64) bool { return a == b } func TestVictoropsNotifier(t *testing.T) { - Convey("Victorops notifier tests", t, func() { - Convey("Parsing alert notification from settings", func() { - Convey("empty settings should return error", func() { - json := `{ }` + t.Run("Parsing alert notification from settings", func(t *testing.T) { + t.Run("empty settings should return error", func(t *testing.T) { + json := `{ }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "victorops_testing", - Type: "victorops", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "victorops_testing", + Type: "victorops", + Settings: settingsJSON, + } - _, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldNotBeNil) - }) + _, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Error(t, err) + }) - Convey("from settings", func() { - json := ` + t.Run("from settings", func(t *testing.T) { + json := ` { "url": "http://google.com" }` - settingsJSON, _ := simplejson.NewJson([]byte(json)) - model := &models.AlertNotification{ - Name: "victorops_testing", - Type: "victorops", - Settings: settingsJSON, - } + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &models.AlertNotification{ + Name: "victorops_testing", + Type: "victorops", + Settings: settingsJSON, + } - not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - victoropsNotifier := not.(*VictoropsNotifier) + not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + victoropsNotifier := not.(*VictoropsNotifier) - So(err, ShouldBeNil) - So(victoropsNotifier.Name, ShouldEqual, "victorops_testing") - So(victoropsNotifier.Type, ShouldEqual, "victorops") - So(victoropsNotifier.URL, ShouldEqual, "http://google.com") - }) + require.Nil(t, err) + require.Equal(t, "victorops_testing", victoropsNotifier.Name) + require.Equal(t, "victorops", victoropsNotifier.Type) + require.Equal(t, "http://google.com", victoropsNotifier.URL) + }) - Convey("should return properly formatted event payload when using severity override tag", func() { - json := ` + t.Run("should return properly formatted event payload when using severity override tag", func(t *testing.T) { + json := ` { "url": "http://google.com" }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "victorops_testing", - Type: "victorops", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "victorops_testing", + Type: "victorops", + Settings: settingsJSON, + } - not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, err) - victoropsNotifier := not.(*VictoropsNotifier) + victoropsNotifier := not.(*VictoropsNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateAlerting, - AlertRuleTags: []*models.Tag{ - {Key: "keyOnly"}, - {Key: "severity", Value: "warning"}, - }, - }, &validations.OSSPluginRequestValidator{}) - evalContext.IsTestRun = true + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateAlerting, + AlertRuleTags: []*models.Tag{ + {Key: "keyOnly"}, + {Key: "severity", Value: "warning"}, + }, + }, &validations.OSSPluginRequestValidator{}) + evalContext.IsTestRun = true - payload, err := victoropsNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) + payload, err := victoropsNotifier.buildEventPayload(evalContext) + require.Nil(t, err) - diff := cmp.Diff(map[string]interface{}{ - "alert_url": "", - "entity_display_name": "[Alerting] someRule", - "entity_id": "someRule", - "message_type": "WARNING", - "metrics": map[string]interface{}{}, - "monitoring_tool": "Grafana v", - "state_message": "someMessage", - "state_start_time": int64(-1), - "timestamp": int64(-1), - }, payload.Interface(), cmp.Comparer(presenceComparerInt)) - So(diff, ShouldBeEmpty) - }) - Convey("resolving with severity works properly", func() { - json := ` + diff := cmp.Diff(map[string]interface{}{ + "alert_url": "", + "entity_display_name": "[Alerting] someRule", + "entity_id": "someRule", + "message_type": "WARNING", + "metrics": map[string]interface{}{}, + "monitoring_tool": "Grafana v", + "state_message": "someMessage", + "state_start_time": int64(-1), + "timestamp": int64(-1), + }, payload.Interface(), cmp.Comparer(presenceComparerInt)) + require.Empty(t, diff) + }) + t.Run("resolving with severity works properly", func(t *testing.T) { + json := ` { "url": "http://google.com" }` - settingsJSON, err := simplejson.NewJson([]byte(json)) - So(err, ShouldBeNil) + settingsJSON, err := simplejson.NewJson([]byte(json)) + require.Nil(t, err) - model := &models.AlertNotification{ - Name: "victorops_testing", - Type: "victorops", - Settings: settingsJSON, - } + model := &models.AlertNotification{ + Name: "victorops_testing", + Type: "victorops", + Settings: settingsJSON, + } - not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) - So(err, ShouldBeNil) + not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) + require.Nil(t, err) - victoropsNotifier := not.(*VictoropsNotifier) + victoropsNotifier := not.(*VictoropsNotifier) - evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ - ID: 0, - Name: "someRule", - Message: "someMessage", - State: models.AlertStateOK, - AlertRuleTags: []*models.Tag{ - {Key: "keyOnly"}, - {Key: "severity", Value: "warning"}, - }, - }, &validations.OSSPluginRequestValidator{}) - evalContext.IsTestRun = true + evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ + ID: 0, + Name: "someRule", + Message: "someMessage", + State: models.AlertStateOK, + AlertRuleTags: []*models.Tag{ + {Key: "keyOnly"}, + {Key: "severity", Value: "warning"}, + }, + }, &validations.OSSPluginRequestValidator{}) + evalContext.IsTestRun = true - payload, err := victoropsNotifier.buildEventPayload(evalContext) - So(err, ShouldBeNil) + payload, err := victoropsNotifier.buildEventPayload(evalContext) + require.Nil(t, err) - diff := cmp.Diff(map[string]interface{}{ - "alert_url": "", - "entity_display_name": "[OK] someRule", - "entity_id": "someRule", - "message_type": "RECOVERY", - "metrics": map[string]interface{}{}, - "monitoring_tool": "Grafana v", - "state_message": "someMessage", - "state_start_time": int64(-1), - "timestamp": int64(-1), - }, payload.Interface(), cmp.Comparer(presenceComparerInt)) - So(diff, ShouldBeEmpty) - }) + diff := cmp.Diff(map[string]interface{}{ + "alert_url": "", + "entity_display_name": "[OK] someRule", + "entity_id": "someRule", + "message_type": "RECOVERY", + "metrics": map[string]interface{}{}, + "monitoring_tool": "Grafana v", + "state_message": "someMessage", + "state_start_time": int64(-1), + "timestamp": int64(-1), + }, payload.Interface(), cmp.Comparer(presenceComparerInt)) + require.Empty(t, diff) }) }) } From 6ce87a96553bac1d013aa1bd97be14897ebaf91a Mon Sep 17 00:00:00 2001 From: Sam Jewell <2903904+samjewell@users.noreply.github.com> Date: Thu, 21 Oct 2021 19:19:38 +0100 Subject: [PATCH 10/22] Correct price-plans list for this feature (#40283) * Correct price-plans list for this feature The [Pricing Page][0] states that Enhanced LDAP is only for the Advanced plan See https://github.com/grafana/website/pull/4117/commits/b59520986c516045e4dadc3feda0c5a9c8e7a610 [0]: https://grafana.com/products/cloud/pricing/ * Also update price plans on Enhanced LDAP page --- docs/sources/auth/ldap.md | 2 +- docs/sources/enterprise/enhanced_ldap.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/auth/ldap.md b/docs/sources/auth/ldap.md index ec161a2353e..15b3f29cbd3 100644 --- a/docs/sources/auth/ldap.md +++ b/docs/sources/auth/ldap.md @@ -11,7 +11,7 @@ weight = 300 The LDAP integration in Grafana allows your Grafana users to login with their LDAP credentials. You can also specify mappings between LDAP group memberships and Grafana Organization user roles. -> [Enhanced LDAP authentication]({{< relref "../enterprise/enhanced_ldap.md" >}}) is available in [Grafana Cloud Pro and Advanced](https://grafana.com/docs/grafana-cloud/) and in [Grafana Enterprise]({{< relref "../enterprise" >}}). +> [Enhanced LDAP authentication]({{< relref "../enterprise/enhanced_ldap.md" >}}) is available in [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/) and in [Grafana Enterprise]({{< relref "../enterprise" >}}). > Refer to [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) in Grafana Enterprise to understand how you can control access with fine-grained permissions. diff --git a/docs/sources/enterprise/enhanced_ldap.md b/docs/sources/enterprise/enhanced_ldap.md index 8ad81b76d68..f66ae04c00c 100644 --- a/docs/sources/enterprise/enhanced_ldap.md +++ b/docs/sources/enterprise/enhanced_ldap.md @@ -9,7 +9,7 @@ weight = 600 The enhanced LDAP integration adds additional functionality on top of the [LDAP integration]({{< relref "../auth/ldap.md" >}}) available in the open source edition of Grafana. -> Enhanced LDAP integration is only available in Grafana Enterprise. +> Enhanced LDAP integration is only available in [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/) and in [Grafana Enterprise]({{< relref "../enterprise" >}}). > Refer to [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) in Grafana Enterprise to understand how you can control access with fine-grained permissions. From 8b6a07a3137bc2d7c016fd6bc48026c387e89a2a Mon Sep 17 00:00:00 2001 From: An Date: Thu, 21 Oct 2021 16:09:41 -0400 Subject: [PATCH 11/22] Panel Options: link to an open category section (#40734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * scroll to view with panel query * scroll to view with param * no export const * mock useLocation * Updates to make update function from useQueryParams work * show options * flip logic Co-authored-by: Ryan McKinley Co-authored-by: Torkel Ödegaard --- .../PanelEditor/OptionsPaneCategory.tsx | 37 +++++++++++++++---- .../PanelEditor/OptionsPaneOptions.test.tsx | 6 +++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx index 14f4dc1602e..387ac9e0961 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx @@ -1,10 +1,11 @@ -import React, { FC, ReactNode, useCallback, useEffect, useState } from 'react'; +import React, { FC, ReactNode, useCallback, useEffect, useState, useRef } from 'react'; import { css, cx } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { Counter, Icon, useStyles2 } from '@grafana/ui'; import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers'; import { useLocalStorage } from 'react-use'; import { selectors } from '@grafana/e2e-selectors'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; export interface OptionsPaneCategoryProps { id: string; @@ -18,28 +19,47 @@ export interface OptionsPaneCategoryProps { children: ReactNode; } +const CATEGORY_PARAM_NAME = 'showCategory'; + export const OptionsPaneCategory: FC = React.memo( ({ id, title, children, forceOpen, isOpenDefault, renderTitle, className, itemsCount, isNested = false }) => { const initialIsExpanded = isOpenDefault !== false; - const [savedState, setSavedState] = useLocalStorage(getOptionGroupStorageKey(id), { isExpanded: initialIsExpanded, }); - // `savedState` can be undefined by typescript, so we have to handle that case - const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? initialIsExpanded); const styles = useStyles2(getStyles); + const [queryParams, updateQueryParams] = useQueryParams(); + const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? initialIsExpanded); + const manualClickTime = useRef(0); + const ref = useRef(null); + const isOpenFromUrl = queryParams[CATEGORY_PARAM_NAME] === id; useEffect(() => { - if (!isExpanded && forceOpen && forceOpen > 0) { - setIsExpanded(true); + if (manualClickTime.current) { + // ignore changes since the click handled the expected behavior + if (Date.now() - manualClickTime.current < 200) { + return; + } } - }, [forceOpen, isExpanded]); + if (isOpenFromUrl || forceOpen) { + if (!isExpanded) { + setIsExpanded(true); + } + if (isOpenFromUrl) { + ref.current?.scrollIntoView(); + } + } + }, [forceOpen, isExpanded, isOpenFromUrl]); const onToggle = useCallback(() => { + manualClickTime.current = Date.now(); + updateQueryParams({ + [CATEGORY_PARAM_NAME]: isExpanded ? undefined : id, + }); setSavedState({ isExpanded: !isExpanded }); setIsExpanded(!isExpanded); - }, [setSavedState, setIsExpanded, isExpanded]); + }, [setSavedState, setIsExpanded, updateQueryParams, isExpanded, id]); if (!renderTitle) { renderTitle = function defaultTitle(isExpanded: boolean) { @@ -78,6 +98,7 @@ export const OptionsPaneCategory: FC = React.memo( className={boxStyles} data-testid="options-category" aria-label={selectors.components.OptionsGroup.group(id)} + ref={ref} >
diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx index 67a5e4a2190..6287fcc8e64 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx @@ -21,6 +21,12 @@ standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs); const mockStore = configureMockStore(); const OptionsPaneSelector = selectors.components.PanelEditor.OptionsPane; +jest.mock('react-router-dom', () => ({ + ...(jest.requireActual('react-router-dom') as any), + useLocation: () => ({ + pathname: 'localhost:3000/example/path', + }), +})); class OptionsPaneOptionsTestScenario { onFieldConfigsChange = jest.fn(); From 624d7631e67fe508569e555ff96f51d0e00e8031 Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Thu, 21 Oct 2021 23:28:57 +0300 Subject: [PATCH 12/22] Build/CI: Make `integration-tests` depend on `initialize` (#40782) * Make integration tests depend on initialize * Reorder steps for better visualisation --- .drone.yml | 518 +++++++++++++-------------- Makefile | 2 +- scripts/drone/pipelines/main.star | 4 +- scripts/drone/pipelines/pr.star | 4 +- scripts/drone/pipelines/release.star | 4 +- scripts/drone/steps/lib.star | 8 +- 6 files changed, 250 insertions(+), 290 deletions(-) diff --git a/.drone.yml b/.drone.yml index d026d45679c..b583de99033 100644 --- a/.drone.yml +++ b/.drone.yml @@ -96,6 +96,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --variants linux-x64,linux-x64-musl,osx64,win64,armv6 --no-pull-enterprise @@ -225,38 +255,6 @@ steps: dry_run: true edition: oss ubuntu: false -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests trigger: event: - pull_request @@ -370,6 +368,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise @@ -540,38 +568,6 @@ steps: ubuntu: true username: from_secret: docker_user -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests - commands: - ./scripts/circle-release-canary-packages.sh depends_on: @@ -585,8 +581,6 @@ steps: - ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads depends_on: - end-to-end-tests - - mysql-integration-tests - - postgres-integration-tests environment: GCP_GRAFANA_UPLOAD_KEY: from_secret: gcp_key @@ -827,6 +821,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG} @@ -949,38 +973,6 @@ steps: ubuntu: true username: from_secret: docker_user -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests - commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -1003,8 +995,6 @@ steps: - ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads depends_on: - end-to-end-tests - - mysql-integration-tests - - postgres-integration-tests environment: GCP_GRAFANA_UPLOAD_KEY: from_secret: gcp_key @@ -1209,6 +1199,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG} @@ -1367,38 +1387,6 @@ steps: ubuntu: true username: from_secret: docker_user -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests - commands: - dockerize -wait tcp://redis:6379/0 -timeout 120s - ./bin/grabpl integration-tests @@ -1432,8 +1420,6 @@ steps: - ./bin/grabpl upload-packages --edition enterprise --packages-bucket grafana-downloads depends_on: - end-to-end-tests - - mysql-integration-tests - - postgres-integration-tests - redis-integration-tests - memcached-integration-tests environment: @@ -1496,8 +1482,6 @@ steps: - ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket grafana-downloads-enterprise2 depends_on: - end-to-end-tests-enterprise2 - - mysql-integration-tests - - postgres-integration-tests - redis-integration-tests - memcached-integration-tests environment: @@ -1771,6 +1755,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test @@ -1885,38 +1899,6 @@ steps: dry_run: true edition: oss ubuntu: true -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests - commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -1939,8 +1921,6 @@ steps: - ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads-test depends_on: - end-to-end-tests - - mysql-integration-tests - - postgres-integration-tests environment: GCP_GRAFANA_UPLOAD_KEY: from_secret: gcp_key @@ -2142,6 +2122,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test @@ -2292,38 +2302,6 @@ steps: dry_run: true edition: enterprise ubuntu: true -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests - commands: - dockerize -wait tcp://redis:6379/0 -timeout 120s - ./bin/grabpl integration-tests @@ -2357,8 +2335,6 @@ steps: - ./bin/grabpl upload-packages --edition enterprise --packages-bucket grafana-downloads-test depends_on: - end-to-end-tests - - mysql-integration-tests - - postgres-integration-tests - redis-integration-tests - memcached-integration-tests environment: @@ -2421,8 +2397,6 @@ steps: - ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket grafana-downloads-test depends_on: - end-to-end-tests-enterprise2 - - mysql-integration-tests - - postgres-integration-tests - redis-integration-tests - memcached-integration-tests environment: @@ -2701,6 +2675,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise @@ -2813,38 +2817,6 @@ steps: dry_run: true edition: oss ubuntu: true -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests - commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -2867,8 +2839,6 @@ steps: - ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads depends_on: - end-to-end-tests - - mysql-integration-tests - - postgres-integration-tests environment: GCP_GRAFANA_UPLOAD_KEY: from_secret: gcp_key @@ -3044,6 +3014,36 @@ steps: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.4.3 name: test-frontend +- commands: + - apt-get update + - apt-get install -yq postgresql-client + - dockerize -wait tcp://postgres:5432 -timeout 120s + - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql + - go clean -testcache + - ./bin/grabpl integration-tests --database postgres + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: postgres + PGPASSWORD: grafanatest + POSTGRES_HOST: postgres + image: grafana/build-container:1.4.3 + name: postgres-integration-tests +- commands: + - apt-get update + - apt-get install -yq default-mysql-client + - dockerize -wait tcp://mysql:3306 -timeout 120s + - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root + -prootpass + - go clean -testcache + - ./bin/grabpl integration-tests --database mysql + depends_on: + - initialize + environment: + GRAFANA_TEST_DB: mysql + MYSQL_HOST: mysql + image: grafana/build-container:1.4.3 + name: mysql-integration-tests - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise @@ -3190,38 +3190,6 @@ steps: dry_run: true edition: enterprise ubuntu: true -- commands: - - apt-get update - - apt-get install -yq postgresql-client - - dockerize -wait tcp://postgres:5432 -timeout 120s - - psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql - - go clean -testcache - - ./bin/grabpl integration-tests --database postgres - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: postgres - PGPASSWORD: grafanatest - POSTGRES_HOST: postgres - image: grafana/build-container:1.4.3 - name: postgres-integration-tests -- commands: - - apt-get update - - apt-get install -yq default-mysql-client - - dockerize -wait tcp://mysql:3306 -timeout 120s - - cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root - -prootpass - - go clean -testcache - - ./bin/grabpl integration-tests --database mysql - depends_on: - - test-backend - - test-frontend - environment: - GRAFANA_TEST_DB: mysql - MYSQL_HOST: mysql - image: grafana/build-container:1.4.3 - name: mysql-integration-tests - commands: - yarn storybook:build - ./bin/grabpl verify-storybook @@ -3264,8 +3232,6 @@ steps: - ./bin/grabpl upload-packages --edition enterprise --packages-bucket grafana-downloads depends_on: - end-to-end-tests - - mysql-integration-tests - - postgres-integration-tests - redis-integration-tests - memcached-integration-tests environment: @@ -3328,8 +3294,6 @@ steps: - ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket grafana-downloads-enterprise2 depends_on: - end-to-end-tests-enterprise2 - - mysql-integration-tests - - postgres-integration-tests - redis-integration-tests - memcached-integration-tests environment: @@ -3485,6 +3449,6 @@ kind: secret name: drone_token --- kind: signature -hmac: 0d26eda19dc6e530d02c734704c8a9d01beb5082c7e14b2609577b8695ed06f5 +hmac: 103fe906b04e8a336e3113007921d62a57107bc7a86a14442b438fa54406c084 ... diff --git a/Makefile b/Makefile index 8daf01d7f24..2a8fad66bd2 100644 --- a/Makefile +++ b/Makefile @@ -153,7 +153,7 @@ clean: ## Clean up intermediate build artifacts. # This repository's configuration is protected (https://readme.drone.io/signature/). # Use this make target to regenerate the configuration YAML files when # you modify starlark files. -drone: +drone: $(DRONE) @if [ "$(DRONE_VERSION)" != "1.4.0" ]; then\ echo "WARN: You are using drone-cli ${DRONE_VERSION}. Please update your LOCAL version to 1.4.0. Using latest bingo version...";\ fi diff --git a/scripts/drone/pipelines/main.star b/scripts/drone/pipelines/main.star index 50c651d8f01..e741a767832 100644 --- a/scripts/drone/pipelines/main.star +++ b/scripts/drone/pipelines/main.star @@ -61,6 +61,8 @@ def get_steps(edition, is_downstream=False): test_backend_step(edition=edition), test_backend_integration_step(edition=edition), test_frontend_step(), + postgres_integration_tests_step(), + mysql_integration_tests_step(), build_backend_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), build_frontend_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), build_plugins_step(edition=edition, sign=True), @@ -90,8 +92,6 @@ def get_steps(edition, is_downstream=False): copy_packages_for_docker_step(), build_docker_images_step(edition=edition, ver_mode=ver_mode, publish=publish), build_docker_images_step(edition=edition, ver_mode=ver_mode, ubuntu=True, publish=publish), - postgres_integration_tests_step(), - mysql_integration_tests_step(), ]) if include_enterprise2: diff --git a/scripts/drone/pipelines/pr.star b/scripts/drone/pipelines/pr.star index fdaa852afac..f5519773509 100644 --- a/scripts/drone/pipelines/pr.star +++ b/scripts/drone/pipelines/pr.star @@ -53,6 +53,8 @@ def pr_pipelines(edition): test_backend_step(edition=edition), test_backend_integration_step(edition=edition), test_frontend_step(), + postgres_integration_tests_step(), + mysql_integration_tests_step(), build_backend_step(edition=edition, ver_mode=ver_mode, variants=variants), build_frontend_step(edition=edition, ver_mode=ver_mode), build_plugins_step(edition=edition), @@ -82,8 +84,6 @@ def pr_pipelines(edition): build_docs_website_step(), copy_packages_for_docker_step(), build_docker_images_step(edition=edition, ver_mode=ver_mode, archs=['amd64',]), - postgres_integration_tests_step(), - mysql_integration_tests_step(), ]) if include_enterprise2: diff --git a/scripts/drone/pipelines/release.star b/scripts/drone/pipelines/release.star index 40590d9f0f0..72739465133 100644 --- a/scripts/drone/pipelines/release.star +++ b/scripts/drone/pipelines/release.star @@ -89,6 +89,8 @@ def get_steps(edition, ver_mode): test_backend_step(edition=edition), test_backend_integration_step(edition=edition), test_frontend_step(), + postgres_integration_tests_step(), + mysql_integration_tests_step(), build_backend_step(edition=edition, ver_mode=ver_mode), build_frontend_step(edition=edition, ver_mode=ver_mode), build_plugins_step(edition=edition, sign=True), @@ -113,8 +115,6 @@ def get_steps(edition, ver_mode): copy_packages_for_docker_step(), build_docker_images_step(edition=edition, ver_mode=ver_mode, publish=should_publish), build_docker_images_step(edition=edition, ver_mode=ver_mode, ubuntu=True, publish=should_publish), - postgres_integration_tests_step(), - mysql_integration_tests_step(), ]) build_storybook = build_storybook_step(edition=edition, ver_mode=ver_mode) diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index e76344c7345..2887f4a77a9 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -681,8 +681,7 @@ def postgres_integration_tests_step(): 'name': 'postgres-integration-tests', 'image': build_image, 'depends_on': [ - 'test-backend', - 'test-frontend', + 'initialize', ], 'environment': { 'PGPASSWORD': 'grafanatest', @@ -706,8 +705,7 @@ def mysql_integration_tests_step(): 'name': 'mysql-integration-tests', 'image': build_image, 'depends_on': [ - 'test-backend', - 'test-frontend', + 'initialize', ], 'environment': { 'GRAFANA_TEST_DB': 'mysql', @@ -795,8 +793,6 @@ def upload_packages_step(edition, ver_mode, is_downstream=False): dependencies = [ 'end-to-end-tests' + enterprise2_suffix(edition), - 'mysql-integration-tests', - 'postgres-integration-tests', ] if edition in ('enterprise', 'enterprise2'): From 97df4a57f483c09b61bf7859bffa8d3f10760870 Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Thu, 21 Oct 2021 16:29:56 -0400 Subject: [PATCH 13/22] GoogleCloudMonitoring: use grafana-google-sdk-go for auth (#40490) --- go.mod | 1 + go.sum | 2 + pkg/api/pluginproxy/ds_auth_provider.go | 3 +- pkg/api/pluginproxy/token_provider_gce.go | 38 +++----- pkg/api/pluginproxy/token_provider_jwt.go | 105 ++++++--------------- pkg/api/pluginproxy/token_provider_test.go | 94 ------------------ 6 files changed, 45 insertions(+), 198 deletions(-) diff --git a/go.mod b/go.mod index 2bf2fdb90f6..f198c5df6a5 100644 --- a/go.mod +++ b/go.mod @@ -186,6 +186,7 @@ require ( github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f // indirect + github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect diff --git a/go.sum b/go.sum index 1d9f5ec86b5..3313e034611 100644 --- a/go.sum +++ b/go.sum @@ -1204,6 +1204,8 @@ github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5J github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/grafana/grafana-aws-sdk v0.7.0 h1:D+Lhxi3P/7vpyDHUK/fdX9bL2mRz8hLG04ucNf1E02o= github.com/grafana/grafana-aws-sdk v0.7.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U= +github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5 h1:o7w/t0nLNfkERMdj09U0h3Fl63z8ws1CxwiImeUKLIk= +github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE= github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60= github.com/grafana/grafana-plugin-sdk-go v0.114.0 h1:9I55IXw7mOT71tZ/pdqCaWGz8vxfz31CXjaDtBV9ZBo= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= diff --git a/pkg/api/pluginproxy/ds_auth_provider.go b/pkg/api/pluginproxy/ds_auth_provider.go index cd41a815c0b..ff1dfce2ade 100644 --- a/pkg/api/pluginproxy/ds_auth_provider.go +++ b/pkg/api/pluginproxy/ds_auth_provider.go @@ -105,8 +105,7 @@ func getTokenProvider(ctx context.Context, cfg *setting.Cfg, ds DSInfo, pluginRo if jwtTokenAuth == nil { return nil, fmt.Errorf("'jwtTokenAuth' not configured for authentication type '%s'", authType) } - provider := newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth) - return provider, nil + return newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth), nil case "jwt": if jwtTokenAuth == nil { diff --git a/pkg/api/pluginproxy/token_provider_gce.go b/pkg/api/pluginproxy/token_provider_gce.go index b1f7909c5da..2444b53634e 100644 --- a/pkg/api/pluginproxy/token_provider_gce.go +++ b/pkg/api/pluginproxy/token_provider_gce.go @@ -2,43 +2,31 @@ package pluginproxy import ( "context" - "time" + googletokenprovider "github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider" "github.com/grafana/grafana/pkg/plugins" - "golang.org/x/oauth2/google" ) type gceAccessTokenProvider struct { - datasourceId int64 - datasourceUpdated time.Time - ctx context.Context - route *plugins.AppPluginRoute - authParams *plugins.JwtTokenAuth + source googletokenprovider.TokenProvider + ctx context.Context } func newGceAccessTokenProvider(ctx context.Context, ds DSInfo, pluginRoute *plugins.AppPluginRoute, authParams *plugins.JwtTokenAuth) *gceAccessTokenProvider { + cfg := googletokenprovider.Config{ + RoutePath: pluginRoute.Path, + RouteMethod: pluginRoute.Method, + DataSourceID: ds.ID, + DataSourceUpdated: ds.Updated, + Scopes: authParams.Scopes, + } return &gceAccessTokenProvider{ - datasourceId: ds.ID, - datasourceUpdated: ds.Updated, - ctx: ctx, - route: pluginRoute, - authParams: authParams, + source: googletokenprovider.NewGceAccessTokenProvider(cfg), + ctx: ctx, } } func (provider *gceAccessTokenProvider) GetAccessToken() (string, error) { - tokenSrc, err := google.DefaultTokenSource(provider.ctx, provider.authParams.Scopes...) - if err != nil { - logger.Error("Failed to get default token from meta data server", "error", err) - return "", err - } else { - token, err := tokenSrc.Token() - if err != nil { - logger.Error("Failed to get default access token from meta data server", "error", err) - return "", err - } else { - return token.AccessToken, nil - } - } + return provider.source.GetAccessToken(provider.ctx) } diff --git a/pkg/api/pluginproxy/token_provider_jwt.go b/pkg/api/pluginproxy/token_provider_jwt.go index 27ee7e97af0..97a7ca0b55e 100644 --- a/pkg/api/pluginproxy/token_provider_jwt.go +++ b/pkg/api/pluginproxy/token_provider_jwt.go @@ -2,95 +2,46 @@ package pluginproxy import ( "context" - "fmt" - "sync" - "time" + googletokenprovider "github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider" "github.com/grafana/grafana/pkg/plugins" - "golang.org/x/oauth2" - "golang.org/x/oauth2/jwt" ) -var ( - oauthJwtTokenCache = oauthJwtTokenCacheType{ - cache: map[string]*oauth2.Token{}, - } -) - -type oauthJwtTokenCacheType struct { - cache map[string]*oauth2.Token - sync.Mutex -} - type jwtAccessTokenProvider struct { - datasourceId int64 - datasourceUpdated time.Time - ctx context.Context - route *plugins.AppPluginRoute - authParams *plugins.JwtTokenAuth + source googletokenprovider.TokenProvider + ctx context.Context } func newJwtAccessTokenProvider(ctx context.Context, ds DSInfo, pluginRoute *plugins.AppPluginRoute, authParams *plugins.JwtTokenAuth) *jwtAccessTokenProvider { + jwtConf := &googletokenprovider.JwtTokenConfig{} + if val, ok := authParams.Params["client_email"]; ok { + jwtConf.Email = val + } + + if val, ok := authParams.Params["private_key"]; ok { + jwtConf.PrivateKey = []byte(val) + } + + if val, ok := authParams.Params["token_uri"]; ok { + jwtConf.URI = val + } + + cfg := googletokenprovider.Config{ + RoutePath: pluginRoute.Path, + RouteMethod: pluginRoute.Method, + DataSourceID: ds.ID, + DataSourceUpdated: ds.Updated, + Scopes: authParams.Scopes, + JwtTokenConfig: jwtConf, + } + return &jwtAccessTokenProvider{ - datasourceId: ds.ID, - datasourceUpdated: ds.Updated, - ctx: ctx, - route: pluginRoute, - authParams: authParams, + source: googletokenprovider.NewJwtAccessTokenProvider(cfg), + ctx: ctx, } } func (provider *jwtAccessTokenProvider) GetAccessToken() (string, error) { - oauthJwtTokenCache.Lock() - defer oauthJwtTokenCache.Unlock() - if cachedToken, found := oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()]; found { - if cachedToken.Expiry.After(timeNow().Add(time.Second * 10)) { - logger.Debug("Using token from cache") - return cachedToken.AccessToken, nil - } - } - - conf := &jwt.Config{} - - if val, ok := provider.authParams.Params["client_email"]; ok { - conf.Email = val - } - - if val, ok := provider.authParams.Params["private_key"]; ok { - conf.PrivateKey = []byte(val) - } - - if val, ok := provider.authParams.Params["token_uri"]; ok { - conf.TokenURL = val - } - - conf.Scopes = provider.authParams.Scopes - - token, err := getTokenSource(conf, provider.ctx) - if err != nil { - return "", err - } - - oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()] = token - - logger.Info("Got new access token", "ExpiresOn", token.Expiry) - - return token.AccessToken, nil -} - -// getTokenSource gets a token source. -// Stubbable by tests. -var getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) { - tokenSrc := conf.TokenSource(ctx) - token, err := tokenSrc.Token() - if err != nil { - return nil, err - } - - return token, nil -} - -func (provider *jwtAccessTokenProvider) getAccessTokenCacheKey() string { - return fmt.Sprintf("%v_%v_%v_%v", provider.datasourceId, provider.datasourceUpdated.Unix(), provider.route.Path, provider.route.Method) + return provider.source.GetAccessToken(provider.ctx) } diff --git a/pkg/api/pluginproxy/token_provider_test.go b/pkg/api/pluginproxy/token_provider_test.go index d2a89a89ace..baf3425bfe4 100644 --- a/pkg/api/pluginproxy/token_provider_test.go +++ b/pkg/api/pluginproxy/token_provider_test.go @@ -1,7 +1,6 @@ package pluginproxy import ( - "context" "encoding/json" "net/http" "net/http/httptest" @@ -13,105 +12,12 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/plugins" - "golang.org/x/oauth2" - "golang.org/x/oauth2/jwt" ) var ( token map[string]interface{} ) -func TestAccessToken_pluginWithJWTTokenAuthRoute(t *testing.T) { - pluginRoute := &plugins.AppPluginRoute{ - Path: "pathwithjwttoken1", - URL: "https://api.jwt.io/some/path", - Method: "GET", - JwtTokenAuth: &plugins.JwtTokenAuth{ - Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token", - Scopes: []string{ - "https://www.testapi.com/auth/monitoring.read", - "https://www.testapi.com/auth/cloudplatformprojects.readonly", - }, - Params: map[string]string{ - "token_uri": "{{.JsonData.tokenUri}}", - "client_email": "{{.JsonData.clientEmail}}", - "private_key": "{{.SecureJsonData.privateKey}}", - }, - }, - } - - authParams := &plugins.JwtTokenAuth{ - Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token", - Scopes: []string{ - "https://www.testapi.com/auth/monitoring.read", - "https://www.testapi.com/auth/cloudplatformprojects.readonly", - }, - Params: map[string]string{ - "token_uri": "login.url.com/token", - "client_email": "test@test.com", - "private_key": "testkey", - }, - } - - setUp := func(t *testing.T, fn func(*jwt.Config, context.Context) (*oauth2.Token, error)) { - origFn := getTokenSource - t.Cleanup(func() { - getTokenSource = origFn - }) - - getTokenSource = fn - } - - ds := DSInfo{ID: 1, Updated: time.Now()} - - t.Run("should fetch token using JWT private key", func(t *testing.T) { - setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) { - return &oauth2.Token{AccessToken: "abc"}, nil - }) - provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams) - token, err := provider.GetAccessToken() - require.NoError(t, err) - - assert.Equal(t, "abc", token) - }) - - t.Run("should set JWT config values", func(t *testing.T) { - setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) { - assert.Equal(t, "test@test.com", conf.Email) - assert.Equal(t, []byte("testkey"), conf.PrivateKey) - assert.Equal(t, 2, len(conf.Scopes)) - assert.Equal(t, "https://www.testapi.com/auth/monitoring.read", conf.Scopes[0]) - assert.Equal(t, "https://www.testapi.com/auth/cloudplatformprojects.readonly", conf.Scopes[1]) - assert.Equal(t, "login.url.com/token", conf.TokenURL) - - return &oauth2.Token{AccessToken: "abc"}, nil - }) - - provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams) - _, err := provider.GetAccessToken() - require.NoError(t, err) - }) - - t.Run("should use cached token on second call", func(t *testing.T) { - setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) { - return &oauth2.Token{ - AccessToken: "abc", - Expiry: time.Now().Add(1 * time.Minute)}, nil - }) - provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams) - token1, err := provider.GetAccessToken() - require.NoError(t, err) - assert.Equal(t, "abc", token1) - - getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) { - return &oauth2.Token{AccessToken: "error: cache not used"}, nil - } - token2, err := provider.GetAccessToken() - require.NoError(t, err) - assert.Equal(t, "abc", token2) - }) -} - func TestAccessToken_pluginWithTokenAuthRoute(t *testing.T) { apiHandler := http.NewServeMux() server := httptest.NewServer(apiHandler) From dfeb69dc1781be0d1e2dcb6a1312e621d682b091 Mon Sep 17 00:00:00 2001 From: nikki-kiga <42276368+nikki-kiga@users.noreply.github.com> Date: Thu, 21 Oct 2021 15:56:41 -0700 Subject: [PATCH 14/22] Geomap: Add style rules UI for geoJSON map layer (#40735) * Geomap: Show multiple layers in ui Co-authored-by: Ryan McKinley * Geomap: Add geojson style rules ui * add style rule editor component * update and change to two row styles Co-authored-by: Ryan McKinley --- .../geomap/editor/GeomapStyleRulesEditor.tsx | 65 +++++++ .../panel/geomap/editor/StyleRuleEditor.tsx | 163 ++++++++++++++++++ .../panel/geomap/layers/data/geojsonMapper.ts | 54 ++++-- public/app/plugins/panel/geomap/module.tsx | 27 ++- 4 files changed, 283 insertions(+), 26 deletions(-) create mode 100644 public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx create mode 100644 public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx diff --git a/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx b/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx new file mode 100644 index 00000000000..4b2c8ffe6af --- /dev/null +++ b/public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx @@ -0,0 +1,65 @@ +import React, { FC, useCallback } from 'react'; +import { StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data'; +import { ComparisonOperation, FeatureStyleConfig } from '../types'; +import { Button } from '@grafana/ui'; +import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonMapper'; +import { StyleRuleEditor, StyleRuleEditorSettings } from './StyleRuleEditor'; + +export const GeomapStyleRulesEditor: FC> = (props) => { + const { value, onChange, context } = props; + + const OPTIONS = getComparisonOperatorOptions(); + + const onAddRule = useCallback(() => { + onChange([...value, DEFAULT_STYLE_RULE]); + }, [onChange, value]); + + const onRuleChange = useCallback( + (idx) => (style: FeatureStyleConfig | undefined) => { + const copyStyles = [...value]; + if (style) { + copyStyles[idx] = style; + } else { + //assume undefined is only returned on delete + copyStyles.splice(idx, 1); + } + onChange(copyStyles); + }, + [onChange, value] + ); + + const styleOptions = + value && + value.map((style, idx: number) => { + const itemSettings: StandardEditorsRegistryItem = { + settings: { options: OPTIONS }, + } as any; + + return ( + + ); + }); + + return ( + <> + {styleOptions} + + + ); +}; + +const getComparisonOperatorOptions = () => { + const options = []; + for (const value of Object.values(ComparisonOperation)) { + options.push({ value: value, label: value }); + } + return options; +}; diff --git a/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx b/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx new file mode 100644 index 00000000000..87b1f315dd7 --- /dev/null +++ b/public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx @@ -0,0 +1,163 @@ +import React, { ChangeEvent, FC, useCallback } from 'react'; +import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data'; +import { ComparisonOperation, FeatureStyleConfig } from '../types'; +import { Button, ColorPicker, InlineField, InlineFieldRow, Input, Select, useStyles2 } from '@grafana/ui'; +import { css } from '@emotion/css'; +import { NumberInput } from 'app/features/dimensions/editors/NumberInput'; + +export interface StyleRuleEditorSettings { + options: SelectableValue[]; +} + +export const StyleRuleEditor: FC> = ( + props +) => { + const { value, onChange, item } = props; + const settings: StyleRuleEditorSettings = item.settings; + + const styles = useStyles2(getStyles); + + const LABEL_WIDTH = 10; + + const onChangeComparisonProperty = useCallback( + (e: ChangeEvent) => { + onChange({ + ...value, + rule: { + ...value.rule, + property: e.currentTarget.value, + operation: value.rule?.operation ?? ComparisonOperation.EQ, + value: value.rule?.value ?? '', + }, + }); + }, + [onChange, value] + ); + + const onChangeComparison = useCallback( + (selection: SelectableValue) => { + onChange({ + ...value, + rule: { + ...value.rule, + operation: selection.value ?? ComparisonOperation.EQ, + property: value.rule?.property ?? '', + value: value.rule?.value ?? '', + }, + }); + }, + [onChange, value] + ); + + const onChangeComparisonValue = useCallback( + (e: ChangeEvent) => { + onChange({ + ...value, + rule: { + ...value.rule, + value: e.currentTarget.value, + operation: value.rule?.operation ?? ComparisonOperation.EQ, + property: value.rule?.property ?? '', + }, + }); + }, + [onChange, value] + ); + + const onChangeColor = useCallback( + (c: string) => { + onChange({ ...value, fillColor: c }); + }, + [onChange, value] + ); + + const onChangeStrokeWidth = useCallback( + (num: number | undefined) => { + onChange({ ...value, strokeWidth: num ?? value.strokeWidth ?? 1 }); + }, + [onChange, value] + ); + + const onDelete = useCallback(() => { + onChange(undefined); + }, [onChange]); + + return ( +
+ + + + + + + + + + + + + + + + + +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + rule: css` + margin-bottom: ${theme.spacing(1)}; + `, + row: css` + display: flex; + margin-bottom: 4px; + `, + inline: css` + margin-bottom: 0; + margin-left: 4px; + `, + color: css` + align-items: center; + margin-bottom: 0; + margin-right: 4px; + `, + button: css` + margin-left: 4px; + `, +}); diff --git a/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts b/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts index 309086d590c..f5507acf912 100644 --- a/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts +++ b/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts @@ -7,15 +7,16 @@ import { Feature } from 'ol'; import { Geometry } from 'ol/geom'; import { getGeoMapStyle } from '../../utils/getGeoMapStyle'; import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule'; -import { FeatureStyleConfig } from '../../types'; +import { ComparisonOperation, FeatureStyleConfig } from '../../types'; import { Stroke, Style } from 'ol/style'; import { FeatureLike } from 'ol/Feature'; +import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor'; export interface GeoJSONMapperConfig { // URL for a geojson file src?: string; // Styles that can be applied - styles?: FeatureStyleConfig[]; + styles: FeatureStyleConfig[]; } const defaultOptions: GeoJSONMapperConfig = { @@ -23,6 +24,16 @@ const defaultOptions: GeoJSONMapperConfig = { styles: [], }; +export const DEFAULT_STYLE_RULE: FeatureStyleConfig = { + fillColor: '#1F60C4', + strokeWidth: 1, + rule: { + property: '', + operation: ComparisonOperation.EQ, + value: '', + }, +}; + export const geojsonMapper: MapLayerRegistryItem = { id: 'geojson-value-mapper', name: 'Map values to GeoJSON file', @@ -44,8 +55,8 @@ export const geojsonMapper: MapLayerRegistryItem = { const defaultStyle = new Style({ stroke: new Stroke({ - color: '#1F60C4', - width: 1, + color: DEFAULT_STYLE_RULE.fillColor, + width: DEFAULT_STYLE_RULE.strokeWidth, }), }); @@ -79,20 +90,27 @@ export const geojsonMapper: MapLayerRegistryItem = { // Geojson source url registerOptionsUI: (builder) => { - builder.addSelect({ - path: 'config.src', - name: 'GeoJSON URL', - settings: { - options: [ - { label: 'public/maps/countries.geojson', value: 'public/maps/countries.geojson' }, - { label: 'public/maps/usa-states.geojson', value: 'public/maps/usa-states.geojson' }, - ], - allowCustomValue: true, - }, - defaultValue: defaultOptions.src, - }); + builder + .addSelect({ + path: 'config.src', + name: 'GeoJSON URL', + settings: { + options: [ + { label: 'public/maps/countries.geojson', value: 'public/maps/countries.geojson' }, + { label: 'public/maps/usa-states.geojson', value: 'public/maps/usa-states.geojson' }, + ], + allowCustomValue: true, + }, + defaultValue: defaultOptions.src, + }) + .addCustomEditor({ + id: 'config.styles', + path: 'config.styles', + name: 'Style Rules', + editor: GeomapStyleRulesEditor, + settings: {}, + defaultValue: [], + }); }, - - // fill in the default values defaultOptions, }; diff --git a/public/app/plugins/panel/geomap/module.tsx b/public/app/plugins/panel/geomap/module.tsx index 73d7507086d..90ee0aefd55 100644 --- a/public/app/plugins/panel/geomap/module.tsx +++ b/public/app/plugins/panel/geomap/module.tsx @@ -53,14 +53,25 @@ export const plugin = new PanelPlugin(GeomapPanel) ); } - builder.addNestedOptions( - getLayerEditor({ - category: ['Data layer'], - path: 'layers[0]', // only one for now - basemaps: false, - current: context.options?.layers?.[0], - }) - ); + let layerCount = context.options?.layers?.length; + if (layerCount == null || layerCount < 1) { + layerCount = 1; + } + + for (let i = 0; i < layerCount; i++) { + let name = 'Data layer'; + if (i > 0) { + name += ` (${i + 1})`; + } + builder.addNestedOptions( + getLayerEditor({ + category: [name], + path: `layers[${i}]`, // only one for now + basemaps: false, + current: context.options?.layers?.[i], + }) + ); + } // The controls section category = ['Map controls']; From 80b4ef820a59543293600757526cce5b8f21e39b Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 21 Oct 2021 20:08:26 -0500 Subject: [PATCH 15/22] Stat: Fixes an issue that could lead to browser crash with specific values (#40777) --- .../grafana-ui/src/components/Sparkline/Sparkline.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx index d089d949227..90f7aa89935 100755 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx @@ -92,6 +92,15 @@ export class Sparkline extends PureComponent { getYRange(field: Field) { let { min, max } = this.state.alignedDataFrame.fields[1].state?.range!; + if (min === max) { + if (min === 0) { + max = 100; + } else { + min = 0; + max! *= 2; + } + } + return [ Math.max(min!, field.config.min ?? -Infinity), Math.min(max!, field.config.max ?? Infinity), From a5501b7845770213088394036fd775eb26c5e73d Mon Sep 17 00:00:00 2001 From: Yuriy Tseretyan Date: Fri, 22 Oct 2021 02:58:01 -0400 Subject: [PATCH 16/22] escape reserved word (#40788) --- pkg/infra/kvstore/sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/infra/kvstore/sql.go b/pkg/infra/kvstore/sql.go index a601608b244..829cabeaa83 100644 --- a/pkg/infra/kvstore/sql.go +++ b/pkg/infra/kvstore/sql.go @@ -99,7 +99,7 @@ func (kv *kvStoreSQL) Del(ctx context.Context, orgId int64, namespace string, ke func (kv *kvStoreSQL) Keys(ctx context.Context, orgId int64, namespace string, keyPrefix string) ([]Key, error) { var keys []Key err := kv.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { - query := dbSession.Where("namespace = ?", namespace).And("key LIKE ?", keyPrefix+"%") + query := dbSession.Where("namespace = ?", namespace).And("\"key\" LIKE ?", keyPrefix+"%") if orgId != AllOrganizations { query.And("org_id = ?", orgId) } From b1b0f713129afca4b8d22d0533a12a719621f503 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 22 Oct 2021 09:12:37 +0200 Subject: [PATCH 17/22] Alerting: Layout fixes for email template (#40676) * changes in email footer and ngalert template * more adjustments to labels list --- emails/assets/css/style.css | 3 ++- emails/templates/ng_alert_notification.html | 27 ++++++++++++++++----- public/emails/alert_notification.html | 2 +- public/emails/invited_to_org.html | 2 +- public/emails/new_user_invite.html | 2 +- public/emails/ng_alert_notification.html | 19 ++++++++------- public/emails/reset_password.html | 2 +- public/emails/signup_started.html | 2 +- public/emails/welcome_on_signup.html | 2 +- 9 files changed, 39 insertions(+), 22 deletions(-) diff --git a/emails/assets/css/style.css b/emails/assets/css/style.css index 83f8d2c67a6..065421a173d 100644 --- a/emails/assets/css/style.css +++ b/emails/assets/css/style.css @@ -91,7 +91,8 @@ margin-bottom: 25px; .footer { background-color: #2e2e2e; color: #999999; - margin-top: 20px; + margin: 0 auto; + width: 100%; } @media only screen and (max-width: 600px) { diff --git a/emails/templates/ng_alert_notification.html b/emails/templates/ng_alert_notification.html index d68c13e9b02..ec700434b71 100644 --- a/emails/templates/ng_alert_notification.html +++ b/emails/templates/ng_alert_notification.html @@ -5,21 +5,21 @@ [[ range .Annotations.SortedPairs ]] -

[[ .Name ]]: [[ .Value ]]

+

[[ .Name ]]: [[ .Value ]]

[[ end ]] [[ end ]] - Labels: - + + Labels:
    [[ range .Labels.SortedPairs ]]
  • [[ .Name ]]: [[ .Value ]]
  • [[ end ]]
- + [[ if .SilenceURL ]] - + -[[ end ]] \ No newline at end of file +[[ end ]] diff --git a/public/emails/alert_notification.html b/public/emails/alert_notification.html index 1ffbd182400..8d7917c92d4 100644 --- a/public/emails/alert_notification.html +++ b/public/emails/alert_notification.html @@ -344,7 +344,7 @@ text-decoration: underline; - + - +