From a9b1a964b0b9a6b64ed535cb3f1ed2836cc8f837 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Fri, 4 Mar 2022 10:16:13 +0100 Subject: [PATCH] Alerting: adds support for federated rules (#46037) --- .../api/tooling/definitions/cortex-ruler.go | 7 +- .../tooling/definitions/cortex-ruler_test.go | 31 ++++++ pkg/services/ngalert/api/tooling/post.json | 29 +++-- pkg/services/ngalert/api/tooling/spec.json | 26 ++++- public/api-merged.json | 100 +++++++++++------- public/api-spec.json | 63 ++++++----- .../features/alerting/unified/RuleViewer.tsx | 22 +++- .../rules/RuleDetailsActionButtons.tsx | 7 +- .../rules/RuleDetailsFederatedSources.tsx | 23 ++++ .../unified/components/rules/RulesGroup.tsx | 31 +++--- .../hooks/useCombinedRuleNamespaces.ts | 1 + .../features/alerting/unified/utils/rules.ts | 11 ++ public/app/types/unified-alerting-dto.ts | 1 + public/app/types/unified-alerting.ts | 1 + 14 files changed, 253 insertions(+), 100 deletions(-) create mode 100644 public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go index 451c5456fee..f8404905e9d 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go @@ -208,9 +208,10 @@ func (c *PostableRuleGroupConfig) validate() error { // swagger:model type GettableRuleGroupConfig struct { - Name string `yaml:"name" json:"name"` - Interval model.Duration `yaml:"interval,omitempty" json:"interval,omitempty"` - Rules []GettableExtendedRuleNode `yaml:"rules" json:"rules"` + Name string `yaml:"name" json:"name"` + Interval model.Duration `yaml:"interval,omitempty" json:"interval,omitempty"` + SourceTenants []string `yaml:"source_tenants,omitempty" json:"source_tenants,omitempty"` + Rules []GettableExtendedRuleNode `yaml:"rules" json:"rules"` } func (c *GettableRuleGroupConfig) UnmarshalJSON(b []byte) error { diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go index 6957e83a053..364e36a5997 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler_test.go @@ -98,6 +98,21 @@ func Test_Rule_Group_Marshaling(t *testing.T) { }, }, }, + { + desc: "success federated lotex", + input: PostableRuleGroupConfig{ + Name: "foo", + Interval: 0, + Rules: []PostableExtendedRuleNode{ + { + ApiRuleNode: &ApiRuleNode{}, + }, + { + ApiRuleNode: &ApiRuleNode{}, + }, + }, + }, + }, { desc: "success grafana", input: PostableRuleGroupConfig{ @@ -189,6 +204,22 @@ func Test_Rule_Group_Type(t *testing.T) { }, expected: LoTexRulerBackend, }, + { + desc: "success federated lotex", + input: PostableRuleGroupConfig{ + Name: "foo", + Interval: 0, + Rules: []PostableExtendedRuleNode{ + { + ApiRuleNode: &ApiRuleNode{}, + }, + { + ApiRuleNode: &ApiRuleNode{}, + }, + }, + }, + expected: LoTexRulerBackend, + }, { desc: "success grafana", input: PostableRuleGroupConfig{ diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 9a875b709ea..e4a43aad5c3 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -980,6 +980,13 @@ }, "type": "array", "x-go-name": "Rules" + }, + "source_tenants": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "SourceTenants" } }, "type": "object", @@ -2264,6 +2271,13 @@ }, "type": "array", "x-go-name": "Rules" + }, + "source_tenants": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "SourceTenants" } }, "type": "object", @@ -3233,12 +3247,11 @@ "type": "object" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert" }, - "type": "array", - "x-go-name": "GettableAlerts", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + "type": "array" }, "gettableSilence": { "description": "GettableSilence gettable silence", @@ -3430,7 +3443,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "postableSilence": { - "description": "PostableSilence postable silence", "properties": { "comment": { "description": "comment", @@ -3470,10 +3482,11 @@ "matchers", "startsAt" ], - "type": "object" + "type": "object", + "x-go-name": "PostableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { - "description": "Receiver receiver", "properties": { "name": { "description": "name", @@ -3484,7 +3497,9 @@ "required": [ "name" ], - "type": "object" + "type": "object", + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "silence": { "description": "Silence silence", diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index b99f7224990..1f5bd9ca9b4 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -2732,6 +2732,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -4017,6 +4024,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -4799,11 +4813,12 @@ "$ref": "#/definitions/alertGroup" }, "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", "$ref": "#/definitions/alertGroups" }, "alertStatus": { @@ -4987,12 +5002,11 @@ "$ref": "#/definitions/gettableAlert" }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "type": "array", "items": { "$ref": "#/definitions/gettableAlert" }, - "x-go-name": "GettableAlerts", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableAlerts" }, "gettableSilence": { @@ -5187,7 +5201,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "postableSilence": { - "description": "PostableSilence postable silence", "type": "object", "required": [ "comment", @@ -5228,10 +5241,11 @@ "x-go-name": "StartsAt" } }, + "x-go-name": "PostableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/postableSilence" }, "receiver": { - "description": "Receiver receiver", "type": "object", "required": [ "name" @@ -5243,6 +5257,8 @@ "x-go-name": "Name" } }, + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/receiver" }, "silence": { diff --git a/public/api-merged.json b/public/api-merged.json index c136ca4ffb1..9a6603027a1 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -4494,15 +4494,15 @@ "parameters": [ { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true }, { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true } @@ -7846,14 +7846,6 @@ "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", @@ -7862,6 +7854,14 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true } ], "responses": { @@ -7895,16 +7895,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true } @@ -10143,7 +10143,7 @@ }, "x-go-package": "github.com/prometheus/common/config" }, - "BrandingOptions": { + "BrandingOptionsDTO": { "type": "object", "properties": { "emailFooterLink": { @@ -10280,7 +10280,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "orgId": { "type": "integer", @@ -10296,10 +10296,11 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { - "$ref": "#/definitions/State" + "type": "string", + "x-go-name": "State" }, "templateVars": { "type": "object", @@ -10480,7 +10481,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "recipients": { "type": "string", @@ -10491,7 +10492,7 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { "type": "string", @@ -10762,6 +10763,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -11723,6 +11728,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -12068,8 +12077,8 @@ }, "exec_err_state": { "type": "string", - "enum": ["Alerting", "Error"], - "x-go-enum-desc": "Alerting AlertingErrState\nError ErrorErrState", + "enum": ["OK", "Alerting", "Error"], + "x-go-enum-desc": "OK OkErrState\nAlerting AlertingErrState\nError ErrorErrState", "x-go-name": "ExecErrState" }, "id": { @@ -12162,6 +12171,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -13906,8 +13922,8 @@ }, "exec_err_state": { "type": "string", - "enum": ["Alerting", "Error"], - "x-go-enum-desc": "Alerting AlertingErrState\nError ErrorErrState", + "enum": ["OK", "Alerting", "Error"], + "x-go-enum-desc": "OK OkErrState\nAlerting AlertingErrState\nError ErrorErrState", "x-go-name": "ExecErrState" }, "no_data_state": { @@ -14266,7 +14282,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" }, - "ReportOptions": { + "ReportOptionsDTO": { "type": "object", "properties": { "layout": { @@ -14539,6 +14555,13 @@ "$ref": "#/definitions/GettableExtendedRuleNode" }, "x-go-name": "Rules" + }, + "source_tenants": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SourceTenants" } }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -14673,7 +14696,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/models" }, - "Schedule": { + "ScheduleDTO": { "type": "object", "properties": { "day": { @@ -14824,7 +14847,7 @@ "type": "object", "properties": { "branding": { - "$ref": "#/definitions/BrandingOptions" + "$ref": "#/definitions/BrandingOptionsDTO" }, "id": { "type": "integer", @@ -15054,10 +15077,6 @@ "SmtpNotEnabled": { "$ref": "#/definitions/ResponseDetails" }, - "State": { - "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" - }, "Status": { "type": "object", "properties": { @@ -16772,12 +16791,11 @@ } }, "gettableAlerts": { + "description": "GettableAlerts gettable alerts", "type": "array", "items": { "$ref": "#/definitions/gettableAlert" - }, - "x-go-name": "GettableAlerts", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + } }, "gettableSilence": { "description": "GettableSilence gettable silence", @@ -16951,7 +16969,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "postableSilence": { - "description": "PostableSilence postable silence", "type": "object", "required": ["comment", "createdBy", "endsAt", "matchers", "startsAt"], "properties": { @@ -16985,10 +17002,11 @@ "format": "date-time", "x-go-name": "StartsAt" } - } + }, + "x-go-name": "PostableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { - "description": "Receiver receiver", "type": "object", "required": ["name"], "properties": { @@ -16997,7 +17015,9 @@ "type": "string", "x-go-name": "Name" } - } + }, + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "silence": { "description": "Silence silence", diff --git a/public/api-spec.json b/public/api-spec.json index 955bbce2527..5b8469bfb29 100644 --- a/public/api-spec.json +++ b/public/api-spec.json @@ -3544,15 +3544,15 @@ "parameters": [ { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true }, { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true } @@ -6424,14 +6424,6 @@ "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", @@ -6440,6 +6432,14 @@ "schema": { "$ref": "#/definitions/TeamGroupMapping" } + }, + { + "type": "integer", + "format": "int64", + "x-go-name": "TeamID", + "name": "teamId", + "in": "path", + "required": true } ], "responses": { @@ -6473,16 +6473,16 @@ { "type": "integer", "format": "int64", - "x-go-name": "TeamID", - "name": "teamId", + "x-go-name": "GroupID", + "name": "groupId", "in": "path", "required": true }, { "type": "integer", "format": "int64", - "x-go-name": "GroupID", - "name": "groupId", + "x-go-name": "TeamID", + "name": "teamId", "in": "path", "required": true } @@ -8333,7 +8333,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/models" }, - "BrandingOptions": { + "BrandingOptionsDTO": { "type": "object", "properties": { "emailFooterLink": { @@ -8436,7 +8436,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "orgId": { "type": "integer", @@ -8452,10 +8452,11 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { - "$ref": "#/definitions/State" + "type": "string", + "x-go-name": "State" }, "templateVars": { "type": "object", @@ -8636,7 +8637,7 @@ "x-go-name": "Name" }, "options": { - "$ref": "#/definitions/ReportOptions" + "$ref": "#/definitions/ReportOptionsDTO" }, "recipients": { "type": "string", @@ -8647,7 +8648,7 @@ "x-go-name": "ReplyTo" }, "schedule": { - "$ref": "#/definitions/Schedule" + "$ref": "#/definitions/ScheduleDTO" }, "state": { "type": "string", @@ -8918,6 +8919,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -9695,6 +9700,10 @@ "type": "boolean", "x-go-name": "CanAdmin" }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, "canEdit": { "type": "boolean", "x-go-name": "CanEdit" @@ -10839,7 +10848,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" }, - "ReportOptions": { + "ReportOptionsDTO": { "type": "object", "properties": { "layout": { @@ -10971,7 +10980,7 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/models" }, - "Schedule": { + "ScheduleDTO": { "type": "object", "properties": { "day": { @@ -11113,7 +11122,7 @@ "type": "object", "properties": { "branding": { - "$ref": "#/definitions/BrandingOptions" + "$ref": "#/definitions/BrandingOptionsDTO" }, "id": { "type": "integer", @@ -11133,10 +11142,6 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" }, - "State": { - "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" - }, "Status": { "type": "object", "properties": { diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index a0701875b20..1b904541fdc 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -9,6 +9,8 @@ import { LoadingPlaceholder, PanelChromeLoadingIndicator, Icon, + Button, + VerticalGroup, } from '@grafana/ui'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { AlertingQueryRunner } from './state/AlertingQueryRunner'; @@ -28,6 +30,8 @@ import { RuleDetailsExpression } from './components/rules/RuleDetailsExpression' import { RuleDetailsAnnotations } from './components/rules/RuleDetailsAnnotations'; import * as ruleId from './utils/rule-id'; import { AlertQuery } from '../../../types/unified-alerting-dto'; +import { RuleDetailsFederatedSources } from './components/rules/RuleDetailsFederatedSources'; +import { isFederatedRuleGroup } from './utils/rules'; type RuleViewerProps = GrafanaRouteComponentProps<{ id?: string; sourceName?: string }>; @@ -115,9 +119,24 @@ export function RuleViewer({ match }: RuleViewerProps) { ); } + const annotations = Object.entries(rule.annotations).filter(([_, value]) => !!value.trim()); + const isFederatedRule = isFederatedRuleGroup(rule.group); + return ( + {isFederatedRule && ( + + + Federated rule groups are currently an experimental feature. + + + + )}

@@ -143,6 +162,7 @@ export function RuleViewer({ match }: RuleViewerProps) {

+ {isFederatedRule && } {`${rule.namespace.name} / ${rule.group.name}`}
@@ -150,7 +170,7 @@ export function RuleViewer({ match }: RuleViewerProps) {
- {data && Object.keys(data).length > 0 && ( + {!isFederatedRule && data && Object.keys(data).length > 0 && ( <>
Query results runner.cancel()} /> diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx index ab7b69e781e..1b36cccf071 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx @@ -17,6 +17,7 @@ import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; import { getAlertmanagerByUid } from '../../utils/alertmanager'; import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; import { RulerGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; +import { isFederatedRuleGroup } from '../../utils/rules'; interface Props { rule: CombinedRule; @@ -40,6 +41,7 @@ export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { const leftButtons: JSX.Element[] = []; const rightButtons: JSX.Element[] = []; + const isFederated = isFederatedRuleGroup(group); const { isEditable } = useIsRuleEditable(getRulesSourceName(rulesSource), rulerRule); const returnTo = location.pathname + location.search; const isViewMode = inViewMode(location.pathname); @@ -68,7 +70,8 @@ export const RuleDetailsActionButtons: FC = ({ rule, rulesSource }) => { }; // explore does not support grafana rule queries atm - if (isCloudRulesSource(rulesSource) && contextSrv.isEditor) { + // neither do "federated rules" + if (isCloudRulesSource(rulesSource) && contextSrv.isEditor && !isFederated) { leftButtons.push( = ({ rule, rulesSource }) => { ); } - if (isEditable && rulerRule) { + if (isEditable && rulerRule && !isFederated) { const sourceName = getRulesSourceName(rulesSource); const identifier = ruleId.fromRulerRule(sourceName, namespace.name, group.name, rulerRule); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx new file mode 100644 index 00000000000..a7ca0178276 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsFederatedSources.tsx @@ -0,0 +1,23 @@ +import { CombinedRuleGroup } from 'app/types/unified-alerting'; +import React, { FC } from 'react'; +import { DetailsField } from '../DetailsField'; + +interface Props { + group: CombinedRuleGroup; +} + +const RuleDetailsFederatedSources: FC = ({ group }) => { + const sourceTenants = group.source_tenants ?? []; + + return ( + + <> + {sourceTenants.map((tenant) => ( +
{tenant}
+ ))} + +
+ ); +}; + +export { RuleDetailsFederatedSources }; diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index 04c61de0f57..b534a3e8592 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -import { ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui'; +import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui'; import kbn from 'app/core/utils/kbn'; import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; import pluralize from 'pluralize'; @@ -10,7 +10,7 @@ import { useFolder } from '../../hooks/useFolder'; import { useHasRuler } from '../../hooks/useHasRuler'; import { deleteRulesGroupAction } from '../../state/actions'; import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource'; -import { isGrafanaRulerRule } from '../../utils/rules'; +import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; import { CollapseToggle } from '../CollapseToggle'; import { ActionIcon } from './ActionIcon'; import { EditCloudGroupModal } from './EditCloudGroupModal'; @@ -43,6 +43,7 @@ export const RulesGroup: FC = React.memo(({ group, namespace, expandAll } // group "is deleting" if rules source has ruler, but this group has no rules that are in ruler const isDeleting = hasRuler(rulesSource) && !group.rules.find((rule) => !!rule.rulerRule); + const isFederated = isFederatedRuleGroup(group); const deleteGroup = () => { dispatch(deleteRulesGroupAction(namespace, group)); @@ -88,16 +89,18 @@ export const RulesGroup: FC = React.memo(({ group, namespace, expandAll } } } } else if (hasRuler(rulesSource)) { - actionIcons.push( - setIsEditingGroup(true)} - /> - ); + if (!isFederated) { + actionIcons.push( + setIsEditingGroup(true)} + /> + ); + } actionIcons.push( = React.memo(({ group, namespace, expandAll } /> )} -
{groupName}
+
+ {isFederated && } {groupName} +
diff --git a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts index 13abd147ef9..657d33a9113 100644 --- a/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts +++ b/public/app/features/alerting/unified/hooks/useCombinedRuleNamespaces.ts @@ -105,6 +105,7 @@ function addRulerGroupsToCombinedNamespace(namespace: CombinedRuleNamespace, gro const combinedGroup: CombinedRuleGroup = { name: group.name, interval: group.interval, + source_tenants: group.source_tenants, rules: [], }; combinedGroup.rules = group.rules.map((rule) => rulerRuleToCombinedRule(rule, namespace, combinedGroup)); diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 8896e91d561..1e4b31a31ed 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -12,6 +12,7 @@ import { Alert, AlertingRule, CloudRuleIdentifier, + CombinedRuleGroup, GrafanaRuleIdentifier, PrometheusRuleIdentifier, PromRuleWithLocation, @@ -116,3 +117,13 @@ export function getFirstActiveAt(promRule: AlertingRule) { return prev; }, null as Date | null); } + +/** + * A rule group is "federated" when it has at least one "source_tenants" entry, federated rule groups will evaluate rules in multiple tenants + * Non-federated rules do not have this property + * + * see https://grafana.com/docs/metrics-enterprise/latest/tenant-management/tenant-federation/#cross-tenant-alerting-and-recording-rule-federation + */ +export function isFederatedRuleGroup(group: CombinedRuleGroup) { + return Array.isArray(group.source_tenants); +} diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 155ff4f9790..fe2d905f0de 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -151,6 +151,7 @@ export type PostableRuleDTO = RulerAlertingRuleDTO | RulerRecordingRuleDTO | Pos export type RulerRuleGroupDTO = { name: string; interval?: string; + source_tenants?: string[]; rules: R[]; }; diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts index 50c4c5f4c51..ff16b43e9cf 100644 --- a/public/app/types/unified-alerting.ts +++ b/public/app/types/unified-alerting.ts @@ -86,6 +86,7 @@ export interface CombinedRule { export interface CombinedRuleGroup { name: string; interval?: string; + source_tenants?: string[]; rules: CombinedRule[]; }