diff --git a/docs/sources/developers/http_api/alerting_provisioning.md b/docs/sources/developers/http_api/alerting_provisioning.md index 7d098624f94..c431e261ee5 100644 --- a/docs/sources/developers/http_api/alerting_provisioning.md +++ b/docs/sources/developers/http_api/alerting_provisioning.md @@ -244,10 +244,11 @@ GET /api/v1/provisioning/alert-rules/{UID}/export #### Parameters -| Name | Source | Type | Go type | Separator | Required | Default | Description | -| -------- | ------- | ------- | -------- | --------- | :------: | ------- | -------------------------------------------------- | -| UID | `path` | string | `string` | | ✓ | | Alert rule UID | -| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. | +| Name | Source | Type | Go type | Separator | Required | Default | Description | +| -------- | ------- | -------- | -------- | --------- | :------: | -------- | --------------------------------------------------------------------------------------------------------------------------------- | +| UID | `path` | string | `string` | | ✓ | | Alert rule UID | +| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. | +| format | `query` | `string` | string | | | `"yaml"` | Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence. | #### All responses @@ -322,11 +323,12 @@ GET /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export #### Parameters -| Name | Source | Type | Go type | Separator | Required | Default | Description | -| --------- | ------- | ------- | -------- | --------- | :------: | ------- | -------------------------------------------------- | -| FolderUID | `path` | string | `string` | | ✓ | | | -| Group | `path` | string | `string` | | ✓ | | | -| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. | +| Name | Source | Type | Go type | Separator | Required | Default | Description | +| --------- | ------- | -------- | -------- | --------- | :------: | -------- | --------------------------------------------------------------------------------------------------------------------------------- | +| FolderUID | `path` | string | `string` | | ✓ | | | +| Group | `path` | string | `string` | | ✓ | | | +| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. | +| format | `query` | `string` | string | | | `"yaml"` | Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence. | #### All responses @@ -381,9 +383,10 @@ GET /api/v1/provisioning/alert-rules/export #### Parameters -| Name | Source | Type | Go type | Separator | Required | Default | Description | -| -------- | ------- | ------- | ------- | --------- | :------: | ------- | -------------------------------------------------- | -| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. | +| Name | Source | Type | Go type | Separator | Required | Default | Description | +| -------- | ------- | -------- | ------- | --------- | :------: | -------- | --------------------------------------------------------------------------------------------------------------------------------- | +| download | `query` | boolean | `bool` | | | | Whether to initiate a download of the file or not. | +| format | `query` | `string` | string | | | `"yaml"` | Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence. | #### All responses diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index 93c3e3696bf..297200cfa9a 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -428,12 +428,22 @@ func determineProvenance(ctx *contextmodel.ReqContext) alerting_models.Provenanc } func exportResponse(c *contextmodel.ReqContext, body any) response.Response { - format := "json" + var format = "yaml" + acceptHeader := c.Req.Header.Get("Accept") - if strings.Contains(acceptHeader, "yaml") && !strings.Contains(acceptHeader, "json") { + if strings.Contains(acceptHeader, "yaml") { format = "yaml" } + if strings.Contains(acceptHeader, "json") { + format = "json" + } + + queryFormat := c.Query("format") + if queryFormat == "yaml" || queryFormat == "json" { + format = queryFormat + } + download := c.QueryBoolWithDefault("download", false) if download { r := response.JSONDownload diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 41e85d71b4f..4b63f046706 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -410,6 +410,34 @@ func TestProvisioningApi(t *testing.T) { require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type")) }) + t.Run("query format contains yaml, GET returns text yaml", func(t *testing.T) { + sut := createProvisioningSrvSut(t) + rc := createTestRequestCtx() + insertRule(t, sut, createTestAlertRule("rule", 1)) + + rc.Context.Req.Form.Set("format", "yaml") + + response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group") + response.WriteTo(&rc) + + require.Equal(t, 200, response.Status()) + require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type")) + }) + + t.Run("query format contains unknown value, GET returns text yaml", func(t *testing.T) { + sut := createProvisioningSrvSut(t) + rc := createTestRequestCtx() + insertRule(t, sut, createTestAlertRule("rule", 1)) + + rc.Context.Req.Form.Set("format", "foo") + + response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group") + response.WriteTo(&rc) + + require.Equal(t, 200, response.Status()) + require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type")) + }) + t.Run("accept header contains json, GET returns json", func(t *testing.T) { sut := createProvisioningSrvSut(t) rc := createTestRequestCtx() @@ -474,12 +502,31 @@ func TestProvisioningApi(t *testing.T) { require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition")) }) + t.Run("yaml body content is the default", func(t *testing.T) { + sut := createProvisioningSrvSut(t) + rc := createTestRequestCtx() + insertRule(t, sut, createTestAlertRule("rule1", 1)) + insertRule(t, sut, createTestAlertRule("rule2", 1)) + + expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder" + + ": Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n" + + " condition: A\n data:\n - refId: A\n datasourceUid" + + ": \"\"\n model:\n conditions:\n - evaluator:\n" + + " params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n isPaused: false\n" + + response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group") + + require.Equal(t, 200, response.Status()) + require.Equal(t, expectedResponse, string(response.Body())) + }) + t.Run("json body content is as expected", func(t *testing.T) { sut := createProvisioningSrvSut(t) rc := createTestRequestCtx() insertRule(t, sut, createTestAlertRule("rule1", 1)) insertRule(t, sut, createTestAlertRule("rule2", 1)) + rc.Context.Req.Header.Add("Accept", "application/json") expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false},{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false}]}]}` response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group") @@ -613,6 +660,7 @@ func TestProvisioningApi(t *testing.T) { expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false}]}]}` + rc.Context.Req.Header.Add("Accept", "application/json") response := sut.RouteGetAlertRuleExport(&rc, "rule1") require.Equal(t, 200, response.Status()) @@ -729,6 +777,7 @@ func TestProvisioningApi(t *testing.T) { insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule2", 1, "folder-uid", "groupb")) insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule3", 1, "folder-uid2", "groupb")) + rc.Context.Req.Header.Add("Accept", "application/json") expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false}]},{"orgId":1,"name":"groupb","folder":"Folder Title2","interval":"1m","rules":[{"uid":"rule3","title":"rule3","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s","isPaused":false}]}]}` response := sut.RouteGetAlertRulesExport(&rc) diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index eac67975b3d..5efd5113f48 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -363,6 +363,10 @@ "external" ], "type": "string" + }, + "numExternalAlertmanagers": { + "format": "int64", + "type": "integer" } }, "type": "object" @@ -3290,6 +3294,7 @@ "type": "object" }, "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "properties": { "ForceQuery": { "type": "boolean" @@ -3325,7 +3330,7 @@ "$ref": "#/definitions/Userinfo" } }, - "title": "URL is a custom URL type that allows validation at configuration load time.", + "title": "A URL represents a parsed URL (technically, a URI reference).", "type": "object" }, "Userinfo": { @@ -3502,7 +3507,6 @@ "type": "object" }, "alertGroup": { - "description": "AlertGroup alert group", "properties": { "alerts": { "description": "alerts", @@ -3526,6 +3530,7 @@ "type": "object" }, "alertGroups": { + "description": "AlertGroups alert groups", "items": { "$ref": "#/definitions/alertGroup" }, @@ -3630,7 +3635,6 @@ "type": "object" }, "gettableAlert": { - "description": "GettableAlert gettable alert", "properties": { "annotations": { "$ref": "#/definitions/labelSet" @@ -3686,6 +3690,7 @@ "type": "object" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert" }, @@ -3740,14 +3745,12 @@ "type": "object" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence" }, "type": "array" }, "integration": { - "description": "Integration integration", "properties": { "lastNotifyAttempt": { "description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time", @@ -4113,6 +4116,13 @@ "in": "query", "name": "download", "type": "boolean" + }, + { + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "in": "query", + "name": "format", + "type": "string" } ], "responses": { @@ -4244,6 +4254,13 @@ "in": "query", "name": "download", "type": "boolean" + }, + { + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "in": "query", + "name": "format", + "type": "string" } ], "produces": [ @@ -4493,6 +4510,13 @@ "in": "query", "name": "download", "type": "boolean" + }, + { + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "in": "query", + "name": "format", + "type": "string" } ], "produces": [ diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go index 1b792e539ab..75ff2bb6619 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go @@ -249,6 +249,12 @@ type ExportQueryParams struct { // required: false // default: false Download bool `json:"download"` + + // Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence. + // in: query + // required: false + // default: yaml + Format string `json:"format"` } // swagger:model diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index e2d625d9e09..5bfb8c385e1 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -363,6 +363,10 @@ "external" ], "type": "string" + }, + "numExternalAlertmanagers": { + "format": "int64", + "type": "integer" } }, "type": "object" @@ -3290,6 +3294,7 @@ "type": "object" }, "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "properties": { "ForceQuery": { "type": "boolean" @@ -3325,7 +3330,7 @@ "$ref": "#/definitions/Userinfo" } }, - "title": "URL is a custom URL type that allows validation at configuration load time.", + "title": "A URL represents a parsed URL (technically, a URI reference).", "type": "object" }, "Userinfo": { @@ -3502,6 +3507,7 @@ "type": "object" }, "alertGroup": { + "description": "AlertGroup alert group", "properties": { "alerts": { "description": "alerts", @@ -3525,7 +3531,6 @@ "type": "object" }, "alertGroups": { - "description": "AlertGroups alert groups", "items": { "$ref": "#/definitions/alertGroup" }, @@ -3630,6 +3635,7 @@ "type": "object" }, "gettableAlert": { + "description": "GettableAlert gettable alert", "properties": { "annotations": { "$ref": "#/definitions/labelSet" @@ -3691,6 +3697,7 @@ "type": "array" }, "gettableSilence": { + "description": "GettableSilence gettable silence", "properties": { "comment": { "description": "comment", @@ -3927,7 +3934,6 @@ "type": "object" }, "receiver": { - "description": "Receiver receiver", "properties": { "active": { "description": "active", @@ -5784,6 +5790,13 @@ "in": "query", "name": "download", "type": "boolean" + }, + { + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "in": "query", + "name": "format", + "type": "string" } ], "responses": { @@ -5915,6 +5928,13 @@ "in": "query", "name": "download", "type": "boolean" + }, + { + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "in": "query", + "name": "format", + "type": "string" } ], "produces": [ @@ -6164,6 +6184,13 @@ "in": "query", "name": "download", "type": "boolean" + }, + { + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "in": "query", + "name": "format", + "type": "string" } ], "produces": [ diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 972006cc4cf..1e3eb9070b4 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -1761,6 +1761,13 @@ "description": "Whether to initiate a download of the file or not.", "name": "download", "in": "query" + }, + { + "type": "string", + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "name": "format", + "in": "query" } ], "responses": { @@ -1901,6 +1908,13 @@ "description": "Whether to initiate a download of the file or not.", "name": "download", "in": "query" + }, + { + "type": "string", + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "name": "format", + "in": "query" } ], "responses": { @@ -2157,6 +2171,13 @@ "description": "Whether to initiate a download of the file or not.", "name": "download", "in": "query" + }, + { + "type": "string", + "default": "yaml", + "description": "Format of the downloaded file, either yaml or json. Accept header can also be used, but the query parameter will take precedence.", + "name": "format", + "in": "query" } ], "responses": { @@ -2975,6 +2996,10 @@ "internal", "external" ] + }, + "numExternalAlertmanagers": { + "type": "integer", + "format": "int64" } } }, @@ -5905,8 +5930,9 @@ } }, "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "type": "object", - "title": "URL is a custom URL type that allows validation at configuration load time.", + "title": "A URL represents a parsed URL (technically, a URI reference).", "properties": { "ForceQuery": { "type": "boolean" @@ -6117,6 +6143,7 @@ } }, "alertGroup": { + "description": "AlertGroup alert group", "type": "object", "required": [ "alerts", @@ -6141,7 +6168,6 @@ "$ref": "#/definitions/alertGroup" }, "alertGroups": { - "description": "AlertGroups alert groups", "type": "array", "items": { "$ref": "#/definitions/alertGroup" @@ -6247,6 +6273,7 @@ } }, "gettableAlert": { + "description": "GettableAlert gettable alert", "type": "object", "required": [ "labels", @@ -6310,6 +6337,7 @@ "$ref": "#/definitions/gettableAlerts" }, "gettableSilence": { + "description": "GettableSilence gettable silence", "type": "object", "required": [ "comment", @@ -6550,7 +6578,6 @@ "$ref": "#/definitions/postableSilence" }, "receiver": { - "description": "Receiver receiver", "type": "object", "required": [ "active", diff --git a/public/app/features/alerting/unified/RuleList.tsx b/public/app/features/alerting/unified/RuleList.tsx index 7dfc10724fc..56437196652 100644 --- a/public/app/features/alerting/unified/RuleList.tsx +++ b/public/app/features/alerting/unified/RuleList.tsx @@ -5,7 +5,7 @@ import { useAsyncFn, useInterval } from 'react-use'; import { GrafanaTheme2, urlUtil } from '@grafana/data'; import { Stack } from '@grafana/experimental'; -import { BackendSrvRequest, getBackendSrv, logInfo } from '@grafana/runtime'; +import { logInfo } from '@grafana/runtime'; import { Button, LinkButton, useStyles2, withErrorBoundary } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useDispatch } from 'app/types'; @@ -33,20 +33,7 @@ const VIEWS = { state: RuleListStateView, }; -const onExport = async () => { - const exportURL = `/api/v1/provisioning/alert-rules/export?download=true`; - const options: BackendSrvRequest = { - url: exportURL, - headers: { Accept: 'yaml' }, - responseType: 'blob', - }; - const blob = await getBackendSrv().get(exportURL, undefined, undefined, options); - const fileUrl = window.URL.createObjectURL(blob); - const downloadLink = document.createElement('a'); - downloadLink.href = fileUrl; - downloadLink.download = 'export.yaml'; - downloadLink.click(); -}; +const onExport = () => window.open('/api/v1/provisioning/alert-rules/export?download=true&format=yaml'); const RuleList = withErrorBoundary( () => {