Alerting: adds support for federated rules (#46037)

This commit is contained in:
Gilles De Mey 2022-03-04 10:16:13 +01:00 committed by GitHub
parent 52629fbc4e
commit a9b1a964b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 253 additions and 100 deletions

View File

@ -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 {

View File

@ -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{

View File

@ -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",

View File

@ -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": {

View File

@ -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",

View File

@ -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": {

View File

@ -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) {
</RuleViewerLayout>
);
}
const annotations = Object.entries(rule.annotations).filter(([_, value]) => !!value.trim());
const isFederatedRule = isFederatedRuleGroup(rule.group);
return (
<RuleViewerLayout wrapInContent={false} title={pageTitle}>
{isFederatedRule && (
<Alert severity="info" title="This rule is part of a federated rule group.">
<VerticalGroup>
Federated rule groups are currently an experimental feature.
<Button fill="text" icon="book">
<a href="https://grafana.com/docs/metrics-enterprise/latest/tenant-management/tenant-federation/#cross-tenant-alerting-and-recording-rule-federation">
Read documentation
</a>
</Button>
</VerticalGroup>
</Alert>
)}
<RuleViewerLayoutContent>
<div>
<h4>
@ -143,6 +162,7 @@ export function RuleViewer({ match }: RuleViewerProps) {
</div>
<div className={styles.rightSide}>
<RuleDetailsDataSources rule={rule} rulesSource={rulesSource} />
{isFederatedRule && <RuleDetailsFederatedSources group={rule.group} />}
<DetailsField label="Namespace / Group">{`${rule.namespace.name} / ${rule.group.name}`}</DetailsField>
</div>
</div>
@ -150,7 +170,7 @@ export function RuleViewer({ match }: RuleViewerProps) {
<RuleDetailsMatchingInstances promRule={rule.promRule} />
</div>
</RuleViewerLayoutContent>
{data && Object.keys(data).length > 0 && (
{!isFederatedRule && data && Object.keys(data).length > 0 && (
<>
<div className={styles.queriesTitle}>
Query results <PanelChromeLoadingIndicator loading={isLoading(data)} onCancel={() => runner.cancel()} />

View File

@ -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<Props> = ({ 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<Props> = ({ 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(
<LinkButton
className={style.button}
@ -174,7 +177,7 @@ export const RuleDetailsActionButtons: FC<Props> = ({ rule, rulesSource }) => {
);
}
if (isEditable && rulerRule) {
if (isEditable && rulerRule && !isFederated) {
const sourceName = getRulesSourceName(rulesSource);
const identifier = ruleId.fromRulerRule(sourceName, namespace.name, group.name, rulerRule);

View File

@ -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<Props> = ({ group }) => {
const sourceTenants = group.source_tenants ?? [];
return (
<DetailsField label="Tenant sources">
<>
{sourceTenants.map((tenant) => (
<div key={tenant}>{tenant}</div>
))}
</>
</DetailsField>
);
};
export { RuleDetailsFederatedSources };

View File

@ -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<Props> = 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<Props> = React.memo(({ group, namespace, expandAll }
}
}
} else if (hasRuler(rulesSource)) {
actionIcons.push(
<ActionIcon
aria-label="edit rule group"
data-testid="edit-group"
key="edit"
icon="pen"
tooltip="edit rule group"
onClick={() => setIsEditingGroup(true)}
/>
);
if (!isFederated) {
actionIcons.push(
<ActionIcon
aria-label="edit rule group"
data-testid="edit-group"
key="edit"
icon="pen"
tooltip="edit rule group"
onClick={() => setIsEditingGroup(true)}
/>
);
}
actionIcons.push(
<ActionIcon
@ -132,7 +135,9 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
/>
</Tooltip>
)}
<h6 className={styles.heading}>{groupName}</h6>
<h6 className={styles.heading}>
{isFederated && <Badge color="purple" text="Federated" />} {groupName}
</h6>
<div className={styles.spacer} />
<div className={styles.headerStats}>
<RuleStats showInactive={false} group={group} />

View File

@ -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));

View File

@ -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);
}

View File

@ -151,6 +151,7 @@ export type PostableRuleDTO = RulerAlertingRuleDTO | RulerRecordingRuleDTO | Pos
export type RulerRuleGroupDTO<R = RulerRuleDTO> = {
name: string;
interval?: string;
source_tenants?: string[];
rules: R[];
};

View File

@ -86,6 +86,7 @@ export interface CombinedRule {
export interface CombinedRuleGroup {
name: string;
interval?: string;
source_tenants?: string[];
rules: CombinedRule[];
}