418 lines
14 KiB
TypeScript
418 lines
14 KiB
TypeScript
import { set } from 'lodash';
|
|
|
|
import { RelativeTimeRange } from '@grafana/data';
|
|
import { t } from 'app/core/internationalization';
|
|
import { Matcher } from 'app/plugins/datasource/alertmanager/types';
|
|
import {
|
|
GrafanaRuleGroupIdentifier,
|
|
RuleIdentifier,
|
|
RuleNamespace,
|
|
RulerDataSourceConfig,
|
|
} from 'app/types/unified-alerting';
|
|
import {
|
|
AlertQuery,
|
|
Annotations,
|
|
GrafanaAlertStateDecision,
|
|
Labels,
|
|
PostableRulerRuleGroupDTO,
|
|
PromRulesResponse,
|
|
RulerGrafanaRuleDTO,
|
|
RulerRuleGroupDTO,
|
|
RulerRulesConfigDTO,
|
|
} from 'app/types/unified-alerting-dto';
|
|
|
|
import { ExportFormats } from '../components/export/providers';
|
|
import { Folder } from '../types/rule-form';
|
|
import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid, isGrafanaRulesSource } from '../utils/datasource';
|
|
import { arrayKeyValuesToObject } from '../utils/labels';
|
|
import { isCloudRuleIdentifier, isGrafanaRulerRule, isPrometheusRuleIdentifier } from '../utils/rules';
|
|
|
|
import { WithNotificationOptions, alertingApi } from './alertingApi';
|
|
import { GRAFANA_RULER_CONFIG } from './featureDiscoveryApi';
|
|
import {
|
|
FetchPromRulesFilter,
|
|
getRulesFilterSearchParams,
|
|
groupRulesByFileName,
|
|
paramsWithMatcherAndState,
|
|
} from './prometheus';
|
|
import { FetchRulerRulesFilter, rulerUrlBuilder } from './ruler';
|
|
|
|
export type ResponseLabels = {
|
|
labels: AlertInstances[];
|
|
};
|
|
|
|
export type PreviewResponse = ResponseLabels[];
|
|
|
|
export interface Datasource {
|
|
type: string;
|
|
uid: string;
|
|
}
|
|
|
|
export const PREVIEW_URL = '/api/v1/rule/test/grafana';
|
|
export const PROM_RULES_URL = 'api/prometheus/grafana/api/v1/rules';
|
|
|
|
export enum PrometheusAPIFilters {
|
|
RuleGroup = 'rule_group',
|
|
Namespace = 'file',
|
|
FolderUID = 'folder_uid',
|
|
LimitAlerts = 'limit_alerts',
|
|
}
|
|
|
|
export interface Data {
|
|
refId: string;
|
|
relativeTimeRange: RelativeTimeRange;
|
|
queryType: string;
|
|
datasourceUid: string;
|
|
model: AlertQuery;
|
|
}
|
|
|
|
export interface GrafanaAlert {
|
|
data?: Data;
|
|
condition: string;
|
|
no_data_state: GrafanaAlertStateDecision;
|
|
title: string;
|
|
}
|
|
|
|
export interface Rule {
|
|
grafana_alert: GrafanaAlert;
|
|
for: string;
|
|
labels: Labels;
|
|
annotations: Annotations;
|
|
}
|
|
|
|
export type AlertInstances = Record<string, string>;
|
|
|
|
interface ExportRulesParams {
|
|
format: ExportFormats;
|
|
folderUid?: string;
|
|
group?: string;
|
|
ruleUid?: string;
|
|
}
|
|
|
|
export interface AlertGroupUpdated {
|
|
message: string;
|
|
/**
|
|
* UIDs of rules updated from this request
|
|
*/
|
|
updated: string[];
|
|
}
|
|
|
|
export const alertRuleApi = alertingApi.injectEndpoints({
|
|
endpoints: (build) => ({
|
|
preview: build.mutation<
|
|
PreviewResponse,
|
|
{
|
|
alertQueries: AlertQuery[];
|
|
condition: string;
|
|
folder: Folder;
|
|
customLabels: Array<{
|
|
key: string;
|
|
value: string;
|
|
}>;
|
|
alertName?: string;
|
|
alertUid?: string;
|
|
}
|
|
>({
|
|
query: ({ alertQueries, condition, customLabels, folder, alertName, alertUid }) => ({
|
|
url: PREVIEW_URL,
|
|
data: {
|
|
rule: {
|
|
grafana_alert: {
|
|
data: alertQueries,
|
|
condition: condition,
|
|
no_data_state: 'Alerting',
|
|
title: alertName,
|
|
uid: alertUid ?? 'N/A',
|
|
},
|
|
for: '0s',
|
|
labels: arrayKeyValuesToObject(customLabels),
|
|
annotations: {},
|
|
},
|
|
folderUid: folder.uid,
|
|
folderTitle: folder.title,
|
|
},
|
|
method: 'POST',
|
|
}),
|
|
}),
|
|
|
|
prometheusRulesByNamespace: build.query<
|
|
RuleNamespace[],
|
|
{
|
|
limitAlerts?: number;
|
|
identifier?: RuleIdentifier;
|
|
filter?: FetchPromRulesFilter;
|
|
state?: string[];
|
|
matcher?: Matcher[];
|
|
}
|
|
>({
|
|
query: ({ limitAlerts, identifier, filter, state, matcher }) => {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
// if we're fetching for Grafana managed rules, we should add a limit to the number of alert instances
|
|
// we do this because the response is large otherwise and we don't show all of them in the UI anyway.
|
|
if (limitAlerts) {
|
|
searchParams.set(PrometheusAPIFilters.LimitAlerts, String(limitAlerts));
|
|
}
|
|
|
|
if (identifier && (isPrometheusRuleIdentifier(identifier) || isCloudRuleIdentifier(identifier))) {
|
|
searchParams.set(PrometheusAPIFilters.Namespace, identifier.namespace);
|
|
searchParams.set(PrometheusAPIFilters.RuleGroup, identifier.groupName);
|
|
}
|
|
|
|
const filterParams = getRulesFilterSearchParams(filter);
|
|
const params = { ...filterParams, ...Object.fromEntries(searchParams) };
|
|
|
|
return { url: PROM_RULES_URL, params: paramsWithMatcherAndState(params, state, matcher) };
|
|
},
|
|
transformResponse: (response: PromRulesResponse): RuleNamespace[] => {
|
|
return groupRulesByFileName(response.data.groups, GRAFANA_RULES_SOURCE_NAME);
|
|
},
|
|
}),
|
|
|
|
prometheusRuleNamespaces: build.query<
|
|
RuleNamespace[],
|
|
{
|
|
ruleSourceName: string;
|
|
namespace?: string;
|
|
groupName?: string;
|
|
ruleName?: string;
|
|
dashboardUid?: string;
|
|
panelId?: number;
|
|
limitAlerts?: number;
|
|
maxGroups?: number;
|
|
excludeAlerts?: boolean;
|
|
}
|
|
>({
|
|
query: ({
|
|
ruleSourceName,
|
|
namespace,
|
|
groupName,
|
|
ruleName,
|
|
dashboardUid,
|
|
panelId,
|
|
limitAlerts,
|
|
maxGroups,
|
|
excludeAlerts,
|
|
}) => {
|
|
const queryParams: Record<string, string | undefined> = {
|
|
rule_group: groupName,
|
|
rule_name: ruleName,
|
|
dashboard_uid: dashboardUid, // Supported only by Grafana managed rules
|
|
panel_id: panelId?.toString(), // Supported only by Grafana managed rules
|
|
};
|
|
|
|
if (namespace) {
|
|
if (isGrafanaRulesSource(ruleSourceName)) {
|
|
set(queryParams, PrometheusAPIFilters.FolderUID, namespace);
|
|
} else {
|
|
set(queryParams, PrometheusAPIFilters.Namespace, namespace);
|
|
}
|
|
}
|
|
|
|
if (limitAlerts !== undefined) {
|
|
set(queryParams, PrometheusAPIFilters.LimitAlerts, String(limitAlerts));
|
|
}
|
|
|
|
if (maxGroups) {
|
|
set(queryParams, 'max_groups', maxGroups);
|
|
}
|
|
|
|
if (excludeAlerts) {
|
|
set(queryParams, 'exclude_alerts', 'true');
|
|
}
|
|
|
|
return {
|
|
url: `api/prometheus/${getDatasourceAPIUid(ruleSourceName)}/api/v1/rules`,
|
|
params: queryParams,
|
|
};
|
|
},
|
|
transformResponse: (response: PromRulesResponse, _, args): RuleNamespace[] => {
|
|
return groupRulesByFileName(response.data.groups, args.ruleSourceName);
|
|
},
|
|
providesTags: ['CombinedAlertRule'],
|
|
}),
|
|
|
|
rulerRules: build.query<
|
|
RulerRulesConfigDTO,
|
|
{ rulerConfig: RulerDataSourceConfig; filter?: FetchRulerRulesFilter }
|
|
>({
|
|
query: ({ rulerConfig, filter }) => {
|
|
const { path, params } = rulerUrlBuilder(rulerConfig).rules(filter);
|
|
return { url: path, params };
|
|
},
|
|
providesTags: ['CombinedAlertRule'],
|
|
}),
|
|
|
|
rulerNamespace: build.query<RulerRulesConfigDTO, { rulerConfig: RulerDataSourceConfig; namespace: string }>({
|
|
query: ({ rulerConfig, namespace }) => {
|
|
const { path, params } = rulerUrlBuilder(rulerConfig).namespace(namespace);
|
|
return { url: path, params };
|
|
},
|
|
providesTags: (_result, _error, { namespace }) => [{ type: 'RuleNamespace', id: namespace }],
|
|
}),
|
|
|
|
// TODO This should be probably a separate ruler API file
|
|
getRuleGroupForNamespace: build.query<
|
|
RulerRuleGroupDTO,
|
|
WithNotificationOptions<{ rulerConfig: RulerDataSourceConfig; namespace: string; group: string }>
|
|
>({
|
|
query: ({ rulerConfig, namespace, group, notificationOptions }) => {
|
|
const { path, params } = rulerUrlBuilder(rulerConfig).namespaceGroup(namespace, group);
|
|
return {
|
|
url: path,
|
|
params,
|
|
notificationOptions,
|
|
};
|
|
},
|
|
providesTags: (_result, _error, { namespace, group, rulerConfig }) => [
|
|
{ type: 'RuleGroup', id: `${rulerConfig.dataSourceUid}/${namespace}/${group}` },
|
|
{ type: 'RuleNamespace', id: `${rulerConfig.dataSourceUid}/${namespace}` },
|
|
],
|
|
}),
|
|
|
|
getGrafanaRulerGroup: build.query<RulerRuleGroupDTO<RulerGrafanaRuleDTO>, GrafanaRuleGroupIdentifier>({
|
|
query: ({ namespace, groupName }) => {
|
|
const { path, params } = rulerUrlBuilder(GRAFANA_RULER_CONFIG).namespaceGroup(namespace.uid, groupName);
|
|
return { url: path, params };
|
|
},
|
|
providesTags: (_result, _error, { namespace, groupName }) => [
|
|
{ type: 'RuleGroup', id: `grafana/${namespace.uid}/${groupName}` },
|
|
{ type: 'RuleNamespace', id: `grafana/${namespace.uid}` },
|
|
],
|
|
}),
|
|
|
|
deleteRuleGroupFromNamespace: build.mutation<
|
|
RulerRuleGroupDTO,
|
|
WithNotificationOptions<{ rulerConfig: RulerDataSourceConfig; namespace: string; group: string }>
|
|
>({
|
|
query: ({ rulerConfig, namespace, group, notificationOptions }) => {
|
|
const successMessage = t('alerting.rule-groups.delete.success', 'Successfully deleted rule group');
|
|
const { path, params } = rulerUrlBuilder(rulerConfig).namespaceGroup(namespace, group);
|
|
|
|
return {
|
|
url: path,
|
|
params,
|
|
method: 'DELETE',
|
|
notificationOptions: {
|
|
successMessage,
|
|
...notificationOptions,
|
|
},
|
|
};
|
|
},
|
|
invalidatesTags: (_result, _error, { namespace, group, rulerConfig }) => [
|
|
{ type: 'RuleGroup', id: `${rulerConfig.dataSourceUid}/${namespace}/${group}` },
|
|
{ type: 'RuleNamespace', id: `${rulerConfig.dataSourceUid}/${namespace}` },
|
|
],
|
|
}),
|
|
|
|
upsertRuleGroupForNamespace: build.mutation<
|
|
AlertGroupUpdated,
|
|
WithNotificationOptions<{
|
|
rulerConfig: RulerDataSourceConfig;
|
|
namespace: string;
|
|
payload: PostableRulerRuleGroupDTO;
|
|
}>
|
|
>({
|
|
query: ({ payload, namespace, rulerConfig, notificationOptions }) => {
|
|
const { path, params } = rulerUrlBuilder(rulerConfig).namespace(namespace);
|
|
|
|
const successMessage = t('alerting.rule-groups.update.success', 'Successfully updated rule group');
|
|
|
|
return {
|
|
url: path,
|
|
params,
|
|
data: payload,
|
|
method: 'POST',
|
|
notificationOptions: {
|
|
successMessage,
|
|
...notificationOptions,
|
|
},
|
|
};
|
|
},
|
|
invalidatesTags: (result, _error, { namespace, payload, rulerConfig }) => {
|
|
const grafanaRulerRules = payload.rules.filter((rule) => isGrafanaRulerRule(rule));
|
|
|
|
return [
|
|
{ type: 'RuleNamespace', id: `${rulerConfig.dataSourceUid}/${namespace}` },
|
|
{ type: 'RuleGroup', id: `${rulerConfig.dataSourceUid}/${namespace}/${payload.name}` },
|
|
...grafanaRulerRules.flatMap((rule) => [
|
|
{ type: 'GrafanaRulerRule', id: rule.grafana_alert.uid } as const,
|
|
{ type: 'GrafanaRulerRuleVersion', id: rule.grafana_alert.uid } as const,
|
|
]),
|
|
];
|
|
},
|
|
}),
|
|
getAlertRule: build.query<RulerGrafanaRuleDTO, { uid: string }>({
|
|
// TODO: In future, if supported in other rulers, parametrize ruler source name
|
|
// For now, to make the consumption of this hook clearer, only support Grafana ruler
|
|
query: ({ uid }) => ({ url: `/api/ruler/${GRAFANA_RULES_SOURCE_NAME}/api/v1/rule/${uid}` }),
|
|
providesTags: (_result, _error, { uid }) => [{ type: 'GrafanaRulerRule', id: uid }],
|
|
}),
|
|
getAlertVersionHistory: build.query<RulerGrafanaRuleDTO[], { uid: string }>({
|
|
query: ({ uid }) => ({ url: `/api/ruler/${GRAFANA_RULES_SOURCE_NAME}/api/v1/rule/${uid}/versions` }),
|
|
providesTags: (_result, _error, { uid }) => [{ type: 'GrafanaRulerRuleVersion', id: uid }],
|
|
}),
|
|
|
|
exportRules: build.query<string, ExportRulesParams>({
|
|
query: ({ format, folderUid, group, ruleUid }) => ({
|
|
url: `/api/ruler/grafana/api/v1/export/rules`,
|
|
params: { format: format, folderUid: folderUid, group: group, ruleUid: ruleUid },
|
|
responseType: 'text',
|
|
}),
|
|
keepUnusedDataFor: 0,
|
|
}),
|
|
exportReceiver: build.query<string, { receiverName: string; decrypt: boolean; format: ExportFormats }>({
|
|
query: ({ receiverName, decrypt, format }) => ({
|
|
url: `/api/v1/provisioning/contact-points/export/`,
|
|
params: { format: format, decrypt: decrypt, name: receiverName },
|
|
responseType: 'text',
|
|
}),
|
|
keepUnusedDataFor: 0,
|
|
}),
|
|
exportReceivers: build.query<string, { decrypt: boolean; format: ExportFormats }>({
|
|
query: ({ decrypt, format }) => ({
|
|
url: `/api/v1/provisioning/contact-points/export/`,
|
|
params: { format: format, decrypt: decrypt },
|
|
responseType: 'text',
|
|
}),
|
|
keepUnusedDataFor: 0,
|
|
}),
|
|
exportPolicies: build.query<string, { format: ExportFormats }>({
|
|
query: ({ format }) => ({
|
|
url: `/api/v1/provisioning/policies/export/`,
|
|
params: { format: format },
|
|
responseType: 'text',
|
|
}),
|
|
keepUnusedDataFor: 0,
|
|
}),
|
|
exportModifiedRuleGroup: build.mutation<
|
|
string,
|
|
{ payload: PostableRulerRuleGroupDTO; format: ExportFormats; nameSpaceUID: string }
|
|
>({
|
|
query: ({ payload, format, nameSpaceUID }) => ({
|
|
url: `/api/ruler/grafana/api/v1/rules/${nameSpaceUID}/export/`,
|
|
params: { format: format },
|
|
responseType: 'text',
|
|
data: payload,
|
|
method: 'POST',
|
|
}),
|
|
}),
|
|
exportMuteTimings: build.query<string, { format: ExportFormats }>({
|
|
query: ({ format }) => ({
|
|
url: `/api/v1/provisioning/mute-timings/export/`,
|
|
params: { format: format },
|
|
responseType: 'text',
|
|
}),
|
|
keepUnusedDataFor: 0,
|
|
}),
|
|
exportMuteTiming: build.query<string, { format: ExportFormats; muteTiming: string }>({
|
|
query: ({ format, muteTiming }) => ({
|
|
url: `/api/v1/provisioning/mute-timings/${muteTiming}/export/`,
|
|
params: { format: format },
|
|
responseType: 'text',
|
|
}),
|
|
keepUnusedDataFor: 0,
|
|
}),
|
|
}),
|
|
});
|