From 7f7803ba596099aa83e40c6e3f1b5295365c31bf Mon Sep 17 00:00:00 2001 From: Will Browne Date: Tue, 3 May 2022 20:58:02 +0200 Subject: [PATCH] [v8.5.x] Backport 48550 to v8.5.x (#48656) * resolve conflicts * update swagger docs --- docs/sources/http_api/data_source.md | 10 + .../src/types/featureToggles.gen.ts | 1 + pkg/api/docs/definitions/ds.go | 1 + pkg/api/metrics.go | 13 +- pkg/api/metrics_test.go | 50 +++ pkg/api/plugins_test.go | 12 +- pkg/services/featuremgmt/registry.go | 5 + pkg/services/featuremgmt/toggles_gen.go | 4 + public/api-merged.json | 383 ++++++++++++++---- public/api-spec.json | 166 +++++--- .../datasource/cloudwatch/datasource.ts | 6 + 11 files changed, 512 insertions(+), 139 deletions(-) diff --git a/docs/sources/http_api/data_source.md b/docs/sources/http_api/data_source.md index a46ef3b2774..fe05742716a 100644 --- a/docs/sources/http_api/data_source.md +++ b/docs/sources/http_api/data_source.md @@ -691,6 +691,16 @@ In addition, specific properties of each data source should be added in a reques } ``` +#### Status codes + +| Code | Description | +| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 200 | All data source queries returned a successful response. | +| 400 | Bad request due to invalid JSON, missing content type, missing or invalid fields, etc. Or one or more data source queries were unsuccessful. Refer to the body for more details. | +| 403 | Access denied. | +| 404 | Either the data source or plugin required to fulfil the request could not be found. | +| 500 | Unexpected error. Refer to the body and/or server logs for more details. | + ## Deprecated resources The following resources have been deprecated. They will be removed in a future release. diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 992709d7fe6..1f94224eb98 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -55,4 +55,5 @@ export interface FeatureToggles { explore2Dashboard?: boolean; tracing?: boolean; persistNotifications?: boolean; + datasourceQueryMultiStatus?: boolean; } diff --git a/pkg/api/docs/definitions/ds.go b/pkg/api/docs/definitions/ds.go index a77449ba073..e37d465e453 100644 --- a/pkg/api/docs/definitions/ds.go +++ b/pkg/api/docs/definitions/ds.go @@ -14,6 +14,7 @@ import ( // // Responses: // 200: queryDataResponse +// 207: queryDataResponse // 401: unauthorisedError // 400: badRequestError // 403: forbiddenError diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index c37f371ce00..f9baf5a1373 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -31,7 +31,7 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext) response.Response { if err != nil { return hs.handleQueryMetricsError(err) } - return toJsonStreamingResponse(resp) + return hs.toJsonStreamingResponse(resp) } func (hs *HTTPServer) handleQueryMetricsError(err error) *response.NormalResponse { @@ -147,7 +147,7 @@ func (hs *HTTPServer) QueryMetricsFromDashboard(c *models.ReqContext) response.R if err != nil { return hs.handleQueryMetricsError(err) } - return toJsonStreamingResponse(resp) + return hs.toJsonStreamingResponse(resp) } // QueryMetrics returns query metrics @@ -198,11 +198,16 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext) response.Response { return response.JSON(statusCode, &legacyResp) } -func toJsonStreamingResponse(qdr *backend.QueryDataResponse) response.Response { +func (hs *HTTPServer) toJsonStreamingResponse(qdr *backend.QueryDataResponse) response.Response { + statusWhenError := http.StatusBadRequest + if hs.Features.IsEnabled(featuremgmt.FlagDatasourceQueryMultiStatus) { + statusWhenError = http.StatusMultiStatus + } + statusCode := http.StatusOK for _, res := range qdr.Responses { if res.Error != nil { - statusCode = http.StatusBadRequest + statusCode = statusWhenError } } diff --git a/pkg/api/metrics_test.go b/pkg/api/metrics_test.go index 5a32d5cfbba..eba878ed143 100644 --- a/pkg/api/metrics_test.go +++ b/pkg/api/metrics_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" + "github.com/grafana/grafana/pkg/web/webtest" "golang.org/x/oauth2" @@ -21,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/secrets/fakes" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -492,3 +494,51 @@ func TestAPIEndpoint_Metrics_ParseDashboardQueryParams(t *testing.T) { }) } } + +// `/ds/query` endpoint test +func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { + qds := query.ProvideService( + nil, + nil, + nil, + &fakePluginRequestValidator{}, + fakes.NewFakeSecretsService(), + &fakePluginClient{ + QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + resp := backend.Responses{ + "A": backend.DataResponse{ + Error: fmt.Errorf("query failed"), + }, + } + return &backend.QueryDataResponse{Responses: resp}, nil + }, + }, + &fakeOAuthTokenService{}, + ) + serverFeatureEnabled := SetupAPITestServer(t, func(hs *HTTPServer) { + hs.queryDataService = qds + hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, true) + }) + serverFeatureDisabled := SetupAPITestServer(t, func(hs *HTTPServer) { + hs.queryDataService = qds + hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, false) + }) + + t.Run("Status code is 400 when data source response has an error and feature toggle is disabled", func(t *testing.T) { + req := serverFeatureDisabled.NewPostRequest("/api/ds/query", strings.NewReader(queryDatasourceInput)) + webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_VIEWER}) + resp, err := serverFeatureDisabled.SendJSON(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("Status code is 207 when data source response has an error and feature toggle is enabled", func(t *testing.T) { + req := serverFeatureEnabled.NewPostRequest("/api/ds/query", strings.NewReader(queryDatasourceInput)) + webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_VIEWER}) + resp, err := serverFeatureEnabled.SendJSON(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + require.Equal(t, http.StatusMultiStatus, resp.StatusCode) + }) +} diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 3504d51e4cf..a28ec025750 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -249,9 +249,11 @@ type fakePluginClient struct { plugins.Client req *backend.CallResourceRequest + + backend.QueryDataHandlerFunc } -func (c *fakePluginClient) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { +func (c *fakePluginClient) CallResource(_ context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { c.req = req bytes, err := json.Marshal(map[string]interface{}{ "message": "hello", @@ -266,3 +268,11 @@ func (c *fakePluginClient) CallResource(ctx context.Context, req *backend.CallRe Body: bytes, }) } + +func (c *fakePluginClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + if c.QueryDataHandlerFunc != nil { + return c.QueryDataHandlerFunc.QueryData(ctx, req) + } + + return backend.NewQueryDataResponse(), nil +} diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index e82868faeeb..73a3acd09c0 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -219,5 +219,10 @@ var ( State: FeatureStateAlpha, FrontendOnly: true, }, + { + Name: "datasourceQueryMultiStatus", + Description: "Introduce HTTP 207 Multi Status for api/ds/query", + State: FeatureStateAlpha, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index c431e6e5231..c5c7128da66 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -162,4 +162,8 @@ const ( // FlagPersistNotifications // PoC Notifications page FlagPersistNotifications = "persistNotifications" + + // FlagDatasourceQueryMultiStatus + // Introduce HTTP 207 Multi Status for api/ds/query + FlagDatasourceQueryMultiStatus = "datasourceQueryMultiStatus" ) diff --git a/public/api-merged.json b/public/api-merged.json index ff5e20db5e9..6b93374de7c 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -1025,7 +1025,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Delete global User.", "operationId": "deleteUser", @@ -1065,7 +1065,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Return a list of all auth tokens (devices) that the user currently have logged in from.", "operationId": "getAuthTokens", @@ -1102,7 +1102,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Disable user.", "operationId": "disableUser", @@ -1142,7 +1142,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Enable user.", "operationId": "enableUser", @@ -1182,7 +1182,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Logout user revokes all auth tokens (devices) for the user. User of issued auth tokens (devices) will no longer be logged in and will be required to authenticate again upon next activity.", "operationId": "logoutUser", @@ -1225,7 +1225,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set password for user.", "operationId": "setPassword", @@ -1269,7 +1269,7 @@ }, "/admin/users/{user_id}/permissions": { "put": { - "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global:users:*`.", + "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set permissions for user.", "operationId": "setPermissions", @@ -1318,7 +1318,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Fetch user quota.", "operationId": "getUserQuota", @@ -1358,7 +1358,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Update user quota.", "operationId": "updateUserQuota", @@ -1414,7 +1414,7 @@ "basic": [] } ], - "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global:users:*`.", + "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Revoke auth token for user.", "operationId": "revokeAuthToken", @@ -3150,7 +3150,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/DeleteAnnotationsCmd" + "$ref": "#/definitions/MassDeleteAnnotationsCmd" } } ], @@ -4273,7 +4273,7 @@ }, "/datasources/{datasource_id}": { "get": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Get a single data source by Id.", "operationId": "getDatasourceByID", @@ -4308,7 +4308,7 @@ } }, "put": { - "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Update an existing data source.", "operationId": "updateDatasource", @@ -4345,7 +4345,7 @@ } }, "delete": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Delete an existing data source by id.", "operationId": "deleteDatasourceByID", @@ -4547,6 +4547,9 @@ "200": { "$ref": "#/responses/queryDataResponse" }, + "207": { + "$ref": "#/responses/queryDataResponse" + }, "400": { "$ref": "#/responses/badRequestError" }, @@ -6469,6 +6472,178 @@ } } }, + "/provisioning/contact-points": { + "get": { + "tags": ["provisioning"], + "summary": "Get all the contact points.", + "operationId": "RouteGetContactpoints", + "responses": { + "200": { + "description": "Route", + "schema": { + "$ref": "#/definitions/Route" + } + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + }, + "put": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Update an existing contact point.", + "operationId": "RoutePutContactpoints", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/EmbeddedContactPoint" + } + } + ], + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + }, + "post": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Create a contact point.", + "operationId": "RoutePostContactpoints", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/EmbeddedContactPoint" + } + } + ], + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + }, + "/provisioning/contact-points/{ID}": { + "delete": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Delete a contact point.", + "operationId": "RouteDeleteContactpoints", + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + }, + "/provisioning/policies": { + "get": { + "tags": ["provisioning"], + "summary": "Get the notification policy tree.", + "operationId": "RouteGetPolicyTree", + "responses": { + "200": { + "description": "Route", + "schema": { + "$ref": "#/definitions/Route" + } + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + }, + "post": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Sets the notification policy tree.", + "operationId": "RoutePostPolicyTree", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Route" + } + } + ], + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + }, + "/provisioning/templates": { + "get": { + "tags": ["provisioning"], + "summary": "Get all message templates.", + "operationId": "RouteGetTemplates", + "responses": { + "200": { + "$ref": "#/responses/MessageTemplate" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + }, + "/provisioning/templates/{ID}": { + "get": { + "tags": ["provisioning"], + "summary": "Get a message template.", + "operationId": "RouteGetTemplate", + "responses": { + "200": { + "$ref": "#/responses/MessageTemplate" + }, + "404": { + "$ref": "#/responses/NotFound" + } + } + } + }, "/recording-rules": { "get": { "tags": ["recording_rules", "enterprise"], @@ -7888,6 +8063,14 @@ "summary": "Add External Group.", "operationId": "addTeamGroupApi", "parameters": [ + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true + }, { "x-go-name": "Body", "name": "body", @@ -7896,14 +8079,6 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } - }, - { - "type": "integer", - "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", - "in": "path", - "required": true } ], "responses": { @@ -7937,16 +8112,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true } @@ -10122,6 +10297,36 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "AnnotationActions": { + "type": "object", + "properties": { + "canAdd": { + "type": "boolean", + "x-go-name": "CanAdd" + }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, + "canEdit": { + "type": "boolean", + "x-go-name": "CanEdit" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, + "AnnotationPermission": { + "type": "object", + "properties": { + "dashboard": { + "$ref": "#/definitions/AnnotationActions" + }, + "organization": { + "$ref": "#/definitions/AnnotationActions" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "ApiKeyDTO": { "type": "object", "properties": { @@ -10240,7 +10445,7 @@ "x-go-name": "ReportLogo" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CalculateDiffTarget": { "type": "object", @@ -10390,7 +10595,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateAlertNotificationCommand": { "type": "object", @@ -10576,7 +10781,7 @@ "x-go-name": "TemplateVars" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateOrgCommand": { "type": "object", @@ -10836,6 +11041,9 @@ "DashboardMeta": { "type": "object", "properties": { + "annotationsPermissions": { + "$ref": "#/definitions/AnnotationPermission" + }, "canAdmin": { "type": "boolean", "x-go-name": "CanAdmin" @@ -11499,32 +11707,6 @@ }, "x-go-package": "github.com/prometheus/alertmanager/timeinterval" }, - "DeleteAnnotationsCmd": { - "type": "object", - "properties": { - "alertId": { - "type": "integer", - "format": "int64", - "x-go-name": "AlertId" - }, - "annotationId": { - "type": "integer", - "format": "int64", - "x-go-name": "AnnotationId" - }, - "dashboardId": { - "type": "integer", - "format": "int64", - "x-go-name": "DashboardId" - }, - "panelId": { - "type": "integer", - "format": "int64", - "x-go-name": "PanelId" - } - }, - "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" - }, "DeleteTokenCommand": { "type": "object", "properties": { @@ -11631,6 +11813,36 @@ }, "x-go-package": "github.com/prometheus/alertmanager/config" }, + "EmbeddedContactPoint": { + "description": "EmbeddedContactPoint is the contact point type that is used\nby grafanas embedded alertmanager implementation.", + "type": "object", + "properties": { + "disableResolveMessage": { + "type": "boolean", + "x-go-name": "DisableResolveMessage" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "provanance": { + "type": "string", + "x-go-name": "Provenance" + }, + "settings": { + "$ref": "#/definitions/Json" + }, + "type": { + "type": "string", + "x-go-name": "Type" + }, + "uid": { + "type": "string", + "x-go-name": "UID" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + }, "ErrorResponseBody": { "type": "object", "required": ["message"], @@ -12479,18 +12691,18 @@ "x-go-name": "URL" } }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitList": { "type": "array", "items": { "$ref": "#/definitions/Hit" }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitType": { "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HostPort": { "type": "object", @@ -13033,6 +13245,27 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/libraryelements" }, + "MassDeleteAnnotationsCmd": { + "type": "object", + "properties": { + "annotationId": { + "type": "integer", + "format": "int64", + "x-go-name": "AnnotationId" + }, + "dashboardId": { + "type": "integer", + "format": "int64", + "x-go-name": "DashboardId" + }, + "panelId": { + "type": "integer", + "format": "int64", + "x-go-name": "PanelId" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "MatchRegexps": { "type": "object", "title": "MatchRegexps represents a map of Regexp.", @@ -14427,7 +14660,7 @@ "x-go-name": "UseEmailsFromReport" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "ReportOptionsDTO": { "type": "object", @@ -14444,7 +14677,7 @@ "$ref": "#/definitions/TimeRangeDTO" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "ResponseDetails": { "type": "object", @@ -14900,7 +15133,7 @@ "x-go-name": "WorkdaysOnly" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "SearchTeamQueryResult": { "type": "object", @@ -15020,7 +15253,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "SigV4Config": { "description": "SigV4Config is the configuration for signing remote write requests with\nAWS's SigV4 verification process. Empty values will be retrieved using the\nAWS default credentials chain.", @@ -15713,7 +15946,7 @@ "x-go-name": "To" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Token": { "type": "object", @@ -15814,6 +16047,15 @@ "format": "int64", "x-go-name": "TokenExpiresWarnDays" }, + "trial": { + "type": "boolean", + "x-go-name": "Trial" + }, + "trial_exp": { + "type": "integer", + "format": "int64", + "x-go-name": "TrialExpires" + }, "update_days": { "type": "integer", "format": "int64", @@ -16792,12 +17034,11 @@ } }, "alertGroups": { + "description": "AlertGroups alert groups", "type": "array", "items": { "$ref": "#/definitions/alertGroup" - }, - "x-go-name": "AlertGroups", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + } }, "alertStatus": { "description": "AlertStatus alert status", @@ -16900,7 +17141,6 @@ "$ref": "#/definitions/Duration" }, "gettableAlert": { - "description": "GettableAlert gettable alert", "type": "object", "required": ["labels", "annotations", "endsAt", "fingerprint", "receivers", "startsAt", "status", "updatedAt"], "properties": { @@ -16950,7 +17190,9 @@ "format": "date-time", "x-go-name": "UpdatedAt" } - } + }, + "x-go-name": "GettableAlert", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "gettableAlerts": { "description": "GettableAlerts gettable alerts", @@ -17006,12 +17248,11 @@ } }, "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" + } }, "labelSet": { "description": "LabelSet label set", diff --git a/public/api-spec.json b/public/api-spec.json index 2d5f6cbbf2b..ca093fc588c 100644 --- a/public/api-spec.json +++ b/public/api-spec.json @@ -1025,7 +1025,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Delete global User.", "operationId": "deleteUser", @@ -1065,7 +1065,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Return a list of all auth tokens (devices) that the user currently have logged in from.", "operationId": "getAuthTokens", @@ -1102,7 +1102,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Disable user.", "operationId": "disableUser", @@ -1142,7 +1142,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Enable user.", "operationId": "enableUser", @@ -1182,7 +1182,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Logout user revokes all auth tokens (devices) for the user. User of issued auth tokens (devices) will no longer be logged in and will be required to authenticate again upon next activity.", "operationId": "logoutUser", @@ -1225,7 +1225,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set password for user.", "operationId": "setPassword", @@ -1269,7 +1269,7 @@ }, "/admin/users/{user_id}/permissions": { "put": { - "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global:users:*`.", + "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set permissions for user.", "operationId": "setPermissions", @@ -1318,7 +1318,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Fetch user quota.", "operationId": "getUserQuota", @@ -1358,7 +1358,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Update user quota.", "operationId": "updateUserQuota", @@ -1414,7 +1414,7 @@ "basic": [] } ], - "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global:users:*`.", + "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Revoke auth token for user.", "operationId": "revokeAuthToken", @@ -2200,7 +2200,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/DeleteAnnotationsCmd" + "$ref": "#/definitions/MassDeleteAnnotationsCmd" } } ], @@ -3323,7 +3323,7 @@ }, "/datasources/{datasource_id}": { "get": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Get a single data source by Id.", "operationId": "getDatasourceByID", @@ -3358,7 +3358,7 @@ } }, "put": { - "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Update an existing data source.", "operationId": "updateDatasource", @@ -3395,7 +3395,7 @@ } }, "delete": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Delete an existing data source by id.", "operationId": "deleteDatasourceByID", @@ -3597,6 +3597,9 @@ "200": { "$ref": "#/responses/queryDataResponse" }, + "207": { + "$ref": "#/responses/queryDataResponse" + }, "400": { "$ref": "#/responses/badRequestError" }, @@ -6457,6 +6460,14 @@ "summary": "Add External Group.", "operationId": "addTeamGroupApi", "parameters": [ + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true + }, { "x-go-name": "Body", "name": "body", @@ -6465,14 +6476,6 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } - }, - { - "type": "integer", - "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", - "in": "path", - "required": true } ], "responses": { @@ -6506,16 +6509,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true } @@ -8374,6 +8377,36 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" }, + "AnnotationActions": { + "type": "object", + "properties": { + "canAdd": { + "type": "boolean", + "x-go-name": "CanAdd" + }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, + "canEdit": { + "type": "boolean", + "x-go-name": "CanEdit" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, + "AnnotationPermission": { + "type": "object", + "properties": { + "dashboard": { + "$ref": "#/definitions/AnnotationActions" + }, + "organization": { + "$ref": "#/definitions/AnnotationActions" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "ApiKeyDTO": { "type": "object", "properties": { @@ -8421,7 +8454,7 @@ "x-go-name": "ReportLogo" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CalculateDiffTarget": { "type": "object", @@ -8537,7 +8570,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateAlertNotificationCommand": { "type": "object", @@ -8723,7 +8756,7 @@ "x-go-name": "TemplateVars" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateOrgCommand": { "type": "object", @@ -8983,6 +9016,9 @@ "DashboardMeta": { "type": "object", "properties": { + "annotationsPermissions": { + "$ref": "#/definitions/AnnotationPermission" + }, "canAdmin": { "type": "boolean", "x-go-name": "CanAdmin" @@ -9625,32 +9661,6 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/tsdb/legacydata" }, - "DeleteAnnotationsCmd": { - "type": "object", - "properties": { - "alertId": { - "type": "integer", - "format": "int64", - "x-go-name": "AlertId" - }, - "annotationId": { - "type": "integer", - "format": "int64", - "x-go-name": "AnnotationId" - }, - "dashboardId": { - "type": "integer", - "format": "int64", - "x-go-name": "DashboardId" - }, - "panelId": { - "type": "integer", - "format": "int64", - "x-go-name": "PanelId" - } - }, - "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" - }, "DeleteTokenCommand": { "type": "object", "properties": { @@ -9952,18 +9962,18 @@ "x-go-name": "URL" } }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitList": { "type": "array", "items": { "$ref": "#/definitions/Hit" }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitType": { "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "ImportDashboardInput": { "type": "object", @@ -10395,6 +10405,27 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/libraryelements" }, + "MassDeleteAnnotationsCmd": { + "type": "object", + "properties": { + "annotationId": { + "type": "integer", + "format": "int64", + "x-go-name": "AnnotationId" + }, + "dashboardId": { + "type": "integer", + "format": "int64", + "x-go-name": "DashboardId" + }, + "panelId": { + "type": "integer", + "format": "int64", + "x-go-name": "PanelId" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "Metadata": { "description": "Metadata contains user accesses for a given resource\nEx: map[string]bool{\"create\":true, \"delete\": true}", "type": "object", @@ -10984,7 +11015,7 @@ "x-go-name": "UseEmailsFromReport" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "ReportOptionsDTO": { "type": "object", @@ -11001,7 +11032,7 @@ "$ref": "#/definitions/TimeRangeDTO" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Responses": { "description": "The QueryData method the QueryDataHandler method will set the RefId\nproperty on the DataRespones' frames based on these RefIDs.", @@ -11175,7 +11206,7 @@ "x-go-name": "WorkdaysOnly" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "SearchTeamQueryResult": { "type": "object", @@ -11286,7 +11317,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Status": { "type": "object", @@ -11562,7 +11593,7 @@ "x-go-name": "To" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Token": { "type": "object", @@ -11663,6 +11694,15 @@ "format": "int64", "x-go-name": "TokenExpiresWarnDays" }, + "trial": { + "type": "boolean", + "x-go-name": "Trial" + }, + "trial_exp": { + "type": "integer", + "format": "int64", + "x-go-name": "TrialExpires" + }, "update_days": { "type": "integer", "format": "int64", diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 379ea6e4d45..c4161b4e912 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -31,6 +31,8 @@ import { VariableWithMultiSupport } from 'app/features/variables/types'; import { store } from 'app/store/store'; import { AppNotificationTimeout } from 'app/types'; +import config from '../../../core/config'; + import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider'; import { ThrottlingErrorMessage } from './components/ThrottlingErrorMessage'; import { CloudWatchLanguageProvider } from './language_provider'; @@ -623,6 +625,10 @@ export class CloudWatchDatasource return this.awsRequest(DS_QUERY_ENDPOINT, requestParams, headers).pipe( map((response) => resultsToDataFrames({ data: response })), catchError((err: FetchError) => { + if (config.featureToggles.datasourceQueryMultiStatus && err.status === 207) { + throw err; + } + if (err.status === 400) { throw err; }