Alerting: Add method to provisioning API for obtaining a group and its rules (#51398)

* Generate shell for new route

* Propagate path parameters

* Implement route logic

* Add a couple simple tests

* Use NotFound error for not found, avoid returning pointer

* Regenerate
This commit is contained in:
Alexander Weaver 2022-07-05 11:53:50 -05:00 committed by GitHub
parent e64cde8727
commit b9c7eb1380
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 220 additions and 228 deletions

View File

@ -54,6 +54,7 @@ type AlertRuleService interface {
CreateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
UpdateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance alerting_models.Provenance) error
GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (definitions.AlertRuleGroup, error)
UpdateRuleGroup(ctx context.Context, orgID int64, folderUID, rulegroup string, interval int64) error
}
@ -284,7 +285,18 @@ func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *models.ReqContext, UID strin
return response.JSON(http.StatusNoContent, "")
}
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroup, folderUID string, group string) response.Response {
func (srv *ProvisioningSrv) RouteGetAlertRuleGroup(c *models.ReqContext, folder string, group string) response.Response {
g, err := srv.alertRules.GetRuleGroup(c.Req.Context(), c.OrgId, folder, group)
if err != nil {
if errors.Is(err, store.ErrAlertRuleGroupNotFound) {
return ErrResp(http.StatusNotFound, err, "")
}
return ErrResp(http.StatusInternalServerError, err, "")
}
return response.JSON(http.StatusOK, g)
}
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroupMetadata, folderUID string, group string) response.Response {
err := srv.alertRules.UpdateRuleGroup(c.Req.Context(), c.OrgId, folderUID, group, ag.Interval)
if err != nil {
if errors.Is(err, store.ErrOptimisticLock) {

View File

@ -239,6 +239,28 @@ func TestProvisioningApi(t *testing.T) {
require.Equal(t, 404, response.Status())
})
})
t.Run("alert rule groups", func(t *testing.T) {
t.Run("are present, GET returns 200", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
insertRule(t, sut, createTestAlertRule("rule", 1))
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "my-cool-group")
require.Equal(t, 200, response.Status())
})
t.Run("are missing, GET returns 404", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
insertRule(t, sut, createTestAlertRule("rule", 1))
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "does not exist")
require.Equal(t, 404, response.Status())
})
})
}
func createProvisioningSrvSut(t *testing.T) ProvisioningSrv {
@ -382,6 +404,7 @@ func createTestAlertRule(title string, orgID int64) definitions.AlertRule {
},
},
RuleGroup: "my-cool-group",
FolderUID: "folder-uid",
For: time.Second * 60,
NoDataState: models.OK,
ExecErrState: models.OkErrState,

View File

@ -185,7 +185,8 @@ func (api *API) authorize(method, path string) web.Handler {
http.MethodGet + "/api/v1/provisioning/templates/{name}",
http.MethodGet + "/api/v1/provisioning/mute-timings",
http.MethodGet + "/api/v1/provisioning/mute-timings/{name}",
http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}":
http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}",
http.MethodGet + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}":
fallback = middleware.ReqOrgAdmin
eval = ac.EvalPermission(ac.ActionAlertingProvisioningRead) // organization scope

View File

@ -95,6 +95,10 @@ func (f *ForkedProvisioningApi) forkRouteDeleteAlertRule(ctx *models.ReqContext,
return f.svc.RouteDeleteAlertRule(ctx, UID)
}
func (f *ForkedProvisioningApi) forkRoutePutAlertRuleGroup(ctx *models.ReqContext, ag apimodels.AlertRuleGroup, folder, group string) response.Response {
func (f *ForkedProvisioningApi) forkRouteGetAlertRuleGroup(ctx *models.ReqContext, folder, group string) response.Response {
return f.svc.RouteGetAlertRuleGroup(ctx, folder, group)
}
func (f *ForkedProvisioningApi) forkRoutePutAlertRuleGroup(ctx *models.ReqContext, ag apimodels.AlertRuleGroupMetadata, folder, group string) response.Response {
return f.svc.RoutePutAlertRuleGroup(ctx, ag, folder, group)
}

View File

@ -24,6 +24,7 @@ type ProvisioningApiForkingService interface {
RouteDeleteMuteTiming(*models.ReqContext) response.Response
RouteDeleteTemplate(*models.ReqContext) response.Response
RouteGetAlertRule(*models.ReqContext) response.Response
RouteGetAlertRuleGroup(*models.ReqContext) response.Response
RouteGetContactpoints(*models.ReqContext) response.Response
RouteGetMuteTiming(*models.ReqContext) response.Response
RouteGetMuteTimings(*models.ReqContext) response.Response
@ -61,6 +62,11 @@ func (f *ForkedProvisioningApi) RouteGetAlertRule(ctx *models.ReqContext) respon
uIDParam := web.Params(ctx.Req)[":UID"]
return f.forkRouteGetAlertRule(ctx, uIDParam)
}
func (f *ForkedProvisioningApi) RouteGetAlertRuleGroup(ctx *models.ReqContext) response.Response {
folderUIDParam := web.Params(ctx.Req)[":FolderUID"]
groupParam := web.Params(ctx.Req)[":Group"]
return f.forkRouteGetAlertRuleGroup(ctx, folderUIDParam, groupParam)
}
func (f *ForkedProvisioningApi) RouteGetContactpoints(ctx *models.ReqContext) response.Response {
return f.forkRouteGetContactpoints(ctx)
}
@ -113,7 +119,7 @@ func (f *ForkedProvisioningApi) RoutePutAlertRule(ctx *models.ReqContext) respon
func (f *ForkedProvisioningApi) RoutePutAlertRuleGroup(ctx *models.ReqContext) response.Response {
folderUIDParam := web.Params(ctx.Req)[":FolderUID"]
groupParam := web.Params(ctx.Req)[":Group"]
conf := apimodels.AlertRuleGroup{}
conf := apimodels.AlertRuleGroupMetadata{}
if err := web.Bind(ctx.Req, &conf); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
@ -203,6 +209,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingServi
m,
),
)
group.Get(
toMacaronPath("/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
api.authorize(http.MethodGet, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
metrics.Instrument(
http.MethodGet,
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}",
srv.RouteGetAlertRuleGroup,
m,
),
)
group.Get(
toMacaronPath("/api/v1/provisioning/contact-points"),
api.authorize(http.MethodGet, "/api/v1/provisioning/contact-points"),

View File

@ -308,7 +308,7 @@
],
"type": "object"
},
"AlertRuleGroup": {
"AlertRuleGroupMetadata": {
"properties": {
"interval": {
"format": "int64",
@ -470,30 +470,6 @@
},
"type": "array"
},
"DashboardAclUpdateItem": {
"properties": {
"permission": {
"$ref": "#/definitions/PermissionType"
},
"role": {
"enum": [
"Viewer",
"Editor",
"Admin"
],
"type": "string"
},
"teamId": {
"format": "int64",
"type": "integer"
},
"userId": {
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"DateTime": {
"description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.",
"format": "date-time",
@ -1336,48 +1312,6 @@
},
"type": "array"
},
"MetricRequest": {
"properties": {
"debug": {
"type": "boolean"
},
"from": {
"description": "From Start time in epoch timestamps in milliseconds or relative using Grafana time units.",
"example": "now-1h",
"type": "string"
},
"queries": {
"description": "queries.refId Specifies an identifier of the query. Is optional and default to “A”.\nqueries.datasourceId Specifies the data source to be queried. Each query in the request must have an unique datasourceId.\nqueries.maxDataPoints - Species maximum amount of data points that dashboard panel can render. Is optional and default to 100.\nqueries.intervalMs - Specifies the time interval in milliseconds of time series. Is optional and defaults to 1000.",
"example": [
{
"datasource": {
"uid": "PD8C576611E62080A"
},
"format": "table",
"intervalMs": 86400000,
"maxDataPoints": 1092,
"rawSql": "SELECT 1 as valueOne, 2 as valueTwo",
"refId": "A"
}
],
"items": {
"$ref": "#/definitions/Json"
},
"type": "array"
},
"to": {
"description": "To End time in epoch timestamps in milliseconds or relative using Grafana time units.",
"example": "now",
"type": "string"
}
},
"required": [
"from",
"to",
"queries"
],
"type": "object"
},
"MonthRange": {
"properties": {
"Begin": {
@ -1425,34 +1359,6 @@
},
"type": "object"
},
"NavLink": {
"properties": {
"id": {
"type": "string"
},
"target": {
"type": "string"
},
"text": {
"type": "string"
},
"url": {
"type": "string"
}
},
"type": "object"
},
"NavbarPreference": {
"properties": {
"savedItems": {
"items": {
"$ref": "#/definitions/NavLink"
},
"type": "array"
}
},
"type": "object"
},
"NotFound": {
"type": "object"
},
@ -1668,53 +1574,9 @@
},
"type": "object"
},
"PatchPrefsCmd": {
"properties": {
"homeDashboardId": {
"default": 0,
"description": "The numerical :id of a favorited dashboard",
"format": "int64",
"type": "integer"
},
"homeDashboardUID": {
"type": "string"
},
"locale": {
"type": "string"
},
"navbar": {
"$ref": "#/definitions/NavbarPreference"
},
"queryHistory": {
"$ref": "#/definitions/QueryHistoryPreference"
},
"theme": {
"enum": [
"light",
"dark"
],
"type": "string"
},
"timezone": {
"enum": [
"utc",
"browser"
],
"type": "string"
},
"weekStart": {
"type": "string"
}
},
"type": "object"
},
"PermissionDenied": {
"type": "object"
},
"PermissionType": {
"format": "int64",
"type": "integer"
},
"Point": {
"properties": {
"T": {
@ -2036,14 +1898,6 @@
},
"type": "object"
},
"QueryHistoryPreference": {
"properties": {
"homeTab": {
"type": "string"
}
},
"type": "object"
},
"Receiver": {
"properties": {
"email_configs": {
@ -2738,6 +2592,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"
@ -2770,58 +2625,7 @@
"$ref": "#/definitions/Userinfo"
}
},
"title": "URL is a custom URL type that allows validation at configuration load time.",
"type": "object"
},
"UpdateDashboardAclCommand": {
"properties": {
"items": {
"items": {
"$ref": "#/definitions/DashboardAclUpdateItem"
},
"type": "array"
}
},
"type": "object"
},
"UpdatePrefsCmd": {
"properties": {
"homeDashboardId": {
"default": 0,
"description": "The numerical :id of a favorited dashboard",
"format": "int64",
"type": "integer"
},
"homeDashboardUID": {
"type": "string"
},
"locale": {
"type": "string"
},
"navbar": {
"$ref": "#/definitions/NavbarPreference"
},
"queryHistory": {
"$ref": "#/definitions/QueryHistoryPreference"
},
"theme": {
"enum": [
"light",
"dark"
],
"type": "string"
},
"timezone": {
"enum": [
"utc",
"browser"
],
"type": "string"
},
"weekStart": {
"type": "string"
}
},
"title": "A URL represents a parsed URL (technically, a URI reference).",
"type": "object"
},
"Userinfo": {
@ -2991,7 +2795,6 @@
"type": "object"
},
"alertGroup": {
"description": "AlertGroup alert group",
"properties": {
"alerts": {
"description": "alerts",
@ -3015,7 +2818,6 @@
"type": "object"
},
"alertGroups": {
"description": "AlertGroups alert groups",
"items": {
"$ref": "#/definitions/alertGroup"
},
@ -3123,6 +2925,7 @@
"$ref": "#/definitions/Duration"
},
"gettableAlert": {
"description": "GettableAlert gettable alert",
"properties": {
"annotations": {
"$ref": "#/definitions/labelSet"
@ -3184,7 +2987,6 @@
"type": "array"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"properties": {
"comment": {
"description": "comment",
@ -3344,7 +3146,6 @@
"type": "array"
},
"postableSilence": {
"description": "PostableSilence postable silence",
"properties": {
"comment": {
"description": "comment",
@ -3382,6 +3183,7 @@
"type": "object"
},
"receiver": {
"description": "Receiver receiver",
"properties": {
"name": {
"description": "name",
@ -3730,6 +3532,35 @@
}
},
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
"get": {
"operationId": "RouteGetAlertRuleGroup",
"parameters": [
{
"in": "path",
"name": "FolderUID",
"required": true,
"type": "string"
},
{
"in": "path",
"name": "Group",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"$ref": "#/responses/AlertRuleGroup"
},
"404": {
"description": " Not found."
}
},
"summary": "Get a rule group.",
"tags": [
"provisioning"
]
},
"put": {
"consumes": [
"application/json"
@ -3752,15 +3583,15 @@
"in": "body",
"name": "Body",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
"$ref": "#/definitions/AlertRuleGroupMetadata"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroup",
"description": "AlertRuleGroupMetadata",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
"$ref": "#/definitions/AlertRuleGroupMetadata"
}
},
"400": {

View File

@ -135,6 +135,14 @@ func NewAlertRule(rule models.AlertRule, provenance models.Provenance) AlertRule
}
}
// swagger:route GET /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteGetAlertRuleGroup
//
// Get a rule group.
//
// Responses:
// 200: AlertRuleGroup
// 404: description: Not found.
// swagger:route PUT /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RoutePutAlertRuleGroup
//
// Update the interval of a rule group.
@ -143,16 +151,16 @@ func NewAlertRule(rule models.AlertRule, provenance models.Provenance) AlertRule
// - application/json
//
// Responses:
// 200: AlertRuleGroup
// 200: AlertRuleGroupMetadata
// 400: ValidationError
// swagger:parameters RoutePutAlertRuleGroup
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup
type FolderUIDPathParam struct {
// in:path
FolderUID string `json:"FolderUID"`
}
// swagger:parameters RoutePutAlertRuleGroup
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup
type RuleGroupPathParam struct {
// in:path
Group string `json:"Group"`
@ -161,9 +169,16 @@ type RuleGroupPathParam struct {
// swagger:parameters RoutePutAlertRuleGroup
type AlertRuleGroupPayload struct {
// in:body
Body AlertRuleGroup
Body AlertRuleGroupMetadata
}
type AlertRuleGroupMetadata struct {
Interval int64 `json:"interval"`
}
type AlertRuleGroup struct {
Interval int64 `json:"interval"`
Title string `json:"title"`
FolderUID string `json:"folderUid"`
Interval int64 `json:"interval"`
Rules []models.AlertRule `json:"rules"`
}

View File

@ -308,7 +308,7 @@
],
"type": "object"
},
"AlertRuleGroup": {
"AlertRuleGroupMetadata": {
"properties": {
"interval": {
"format": "int64",
@ -2592,6 +2592,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"
@ -2624,7 +2625,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": {
@ -2794,6 +2795,7 @@
"type": "object"
},
"alertGroup": {
"description": "AlertGroup alert group",
"properties": {
"alerts": {
"description": "alerts",
@ -2988,6 +2990,7 @@
"type": "array"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"properties": {
"comment": {
"description": "comment",
@ -3146,6 +3149,7 @@
"type": "array"
},
"postableSilence": {
"description": "PostableSilence postable silence",
"properties": {
"comment": {
"description": "comment",
@ -5157,6 +5161,35 @@
}
},
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
"get": {
"operationId": "RouteGetAlertRuleGroup",
"parameters": [
{
"in": "path",
"name": "FolderUID",
"required": true,
"type": "string"
},
{
"in": "path",
"name": "Group",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"$ref": "#/responses/AlertRuleGroup"
},
"404": {
"description": " Not found."
}
},
"summary": "Get a rule group.",
"tags": [
"provisioning"
]
},
"put": {
"consumes": [
"application/json"
@ -5179,15 +5212,15 @@
"in": "body",
"name": "Body",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
"$ref": "#/definitions/AlertRuleGroupMetadata"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroup",
"description": "AlertRuleGroupMetadata",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
"$ref": "#/definitions/AlertRuleGroupMetadata"
}
},
"400": {

View File

@ -1893,6 +1893,36 @@
}
},
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
"get": {
"tags": [
"provisioning",
"stable"
],
"summary": "Get a rule group.",
"operationId": "RouteGetAlertRuleGroup",
"parameters": [
{
"type": "string",
"name": "FolderUID",
"in": "path",
"required": true
},
{
"type": "string",
"name": "Group",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/AlertRuleGroup"
},
"404": {
"description": " Not found."
}
}
},
"put": {
"consumes": [
"application/json"
@ -1920,15 +1950,15 @@
"name": "Body",
"in": "body",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
"$ref": "#/definitions/AlertRuleGroupMetadata"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroup",
"description": "AlertRuleGroupMetadata",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
"$ref": "#/definitions/AlertRuleGroupMetadata"
}
},
"400": {
@ -2629,7 +2659,7 @@
}
}
},
"AlertRuleGroup": {
"AlertRuleGroupMetadata": {
"type": "object",
"properties": {
"interval": {
@ -4917,8 +4947,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"
@ -5119,6 +5150,7 @@
}
},
"alertGroup": {
"description": "AlertGroup alert group",
"type": "object",
"required": [
"alerts",
@ -5143,7 +5175,6 @@
"$ref": "#/definitions/alertGroup"
},
"alertGroups": {
"description": "AlertGroups alert groups",
"type": "array",
"items": {
"$ref": "#/definitions/alertGroup"
@ -5252,7 +5283,6 @@
"$ref": "#/definitions/Duration"
},
"gettableAlert": {
"description": "GettableAlert gettable alert",
"type": "object",
"required": [
"labels",
@ -5309,7 +5339,6 @@
"$ref": "#/definitions/gettableAlert"
},
"gettableAlerts": {
"description": "GettableAlerts gettable alerts",
"type": "array",
"items": {
"$ref": "#/definitions/gettableAlert"
@ -5515,6 +5544,7 @@
"$ref": "#/definitions/postableSilence"
},
"receiver": {
"description": "Receiver receiver",
"type": "object",
"required": [
"name"

View File

@ -7,6 +7,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/util"
@ -89,6 +90,32 @@ func (service *AlertRuleService) CreateAlertRule(ctx context.Context, rule model
return rule, nil
}
func (service *AlertRuleService) GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (definitions.AlertRuleGroup, error) {
q := models.ListAlertRulesQuery{
OrgID: orgID,
NamespaceUIDs: []string{folder},
RuleGroup: group,
}
if err := service.ruleStore.ListAlertRules(ctx, &q); err != nil {
return definitions.AlertRuleGroup{}, err
}
if len(q.Result) == 0 {
return definitions.AlertRuleGroup{}, store.ErrAlertRuleGroupNotFound
}
res := definitions.AlertRuleGroup{
Title: q.Result[0].RuleGroup,
FolderUID: q.Result[0].NamespaceUID,
Interval: q.Result[0].IntervalSeconds,
Rules: []models.AlertRule{},
}
for _, r := range q.Result {
if r != nil {
res.Rules = append(res.Rules, *r)
}
}
return res, nil
}
// UpdateRuleGroup will update the interval for all rules in the group.
func (service *AlertRuleService) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, interval int64) error {
if err := models.ValidateRuleGroupInterval(interval, service.baseIntervalSeconds); err != nil {