diff --git a/.betterer.results b/.betterer.results index c0ff4acd23b..0f1715e5605 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1803,25 +1803,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "12"], [0, 0, 0, "No untranslated strings. Wrap text with ", "13"] ], - "public/app/features/alerting/unified/components/notification-policies/Policy.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "11"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "12"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "13"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "14"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "15"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "16"] - ], "public/app/features/alerting/unified/components/notification-policies/PromDurationDocs.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], diff --git a/public/app/features/alerting/unified/NotificationPolicies.test.tsx b/public/app/features/alerting/unified/NotificationPolicies.test.tsx index 919a19d2d78..466e034cd4c 100644 --- a/public/app/features/alerting/unified/NotificationPolicies.test.tsx +++ b/public/app/features/alerting/unified/NotificationPolicies.test.tsx @@ -644,7 +644,7 @@ describe('NotificationPolicies', () => { renderNotificationPolicies(dataSources.promAlertManager.name); const rootRouteContainer = await ui.rootRouteContainer.find(); await waitFor(() => - expect(within(rootRouteContainer).getByTestId('matching-instances')).toHaveTextContent('0instances') + expect(within(rootRouteContainer).getByTestId('matching-instances')).toHaveTextContent('0instance') ); expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument(); diff --git a/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx b/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx index bbd6e3acca0..6d13a6037a8 100644 --- a/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx @@ -10,7 +10,7 @@ import { } from 'app/features/alerting/unified/openapi/timeIntervalsApi.gen'; import { deleteMuteTimingAction, updateAlertManagerConfigAction } from 'app/features/alerting/unified/state/actions'; import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks'; -import { renameMuteTimings } from 'app/features/alerting/unified/utils/alertmanager'; +import { renameTimeInterval } from 'app/features/alerting/unified/utils/alertmanager'; import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; import { PROVENANCE_ANNOTATION, PROVENANCE_NONE } from 'app/features/alerting/unified/utils/k8s/constants'; import { getK8sNamespace, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils'; @@ -261,7 +261,7 @@ export const useUpdateMuteTiming = ({ alertmanager }: BaseAlertmanagerArgs) => { } if (nameHasChanged && draft.alertmanager_config.route) { - draft.alertmanager_config.route = renameMuteTimings( + draft.alertmanager_config.route = renameTimeInterval( timeInterval.name, originalName, draft.alertmanager_config.route diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx index 739b10fac0e..77d54ae2a5c 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx @@ -100,8 +100,9 @@ describe('Policy', () => { // for grouping expect(within(defaultPolicy).getByTestId('grouping')).toHaveTextContent('grafana_folder, alertname'); - // no mute timings + // no timings expect(within(defaultPolicy).queryByTestId('mute-timings')).not.toBeInTheDocument(); + expect(within(defaultPolicy).queryByTestId('active-timings')).not.toBeInTheDocument(); // for timing options expect(within(defaultPolicy).getByTestId('timing-options')).toHaveTextContent( @@ -132,6 +133,7 @@ describe('Policy', () => { // expect(within(firstPolicy).getByTestId('matching-instances')).toHaveTextContent('0instances'); expect(within(firstPolicy).getByTestId('contact-point')).toHaveTextContent('provisioned-contact-point'); expect(within(firstPolicy).getByTestId('mute-timings')).toHaveTextContent('Muted whenmt-1'); + expect(within(firstPolicy).getByTestId('active-timings')).toHaveTextContent('Active whenmt-2'); expect(within(firstPolicy).getByTestId('inherited-properties')).toHaveTextContent('Inherited2 properties'); // second custom policy should be correct @@ -139,6 +141,7 @@ describe('Policy', () => { expect(within(secondPolicy).getByTestId('label-matchers')).toHaveTextContent(/^region \= EMEA$/); expect(within(secondPolicy).queryByTestId('continue-matching')).not.toBeInTheDocument(); expect(within(secondPolicy).queryByTestId('mute-timings')).not.toBeInTheDocument(); + expect(within(secondPolicy).queryByTestId('active-timings')).not.toBeInTheDocument(); expect(within(secondPolicy).getByTestId('inherited-properties')).toHaveTextContent('Inherited3 properties'); // third custom policy should be correct @@ -360,6 +363,7 @@ const mockRoutes: RouteWithID = { receiver: 'provisioned-contact-point', object_matchers: [['team', eq, 'operations']], mute_time_intervals: ['mt-1'], + active_time_intervals: ['mt-2'], continue: true, routes: [ { diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx index b4239207f8f..0560d6bb20b 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx @@ -163,6 +163,7 @@ const Policy = (props: PolicyComponentProps) => { const groupBy = currentRoute.group_by; const muteTimings = currentRoute.mute_time_intervals ?? []; + const activeTimings = currentRoute.active_time_intervals ?? []; const timingOptions: TimingOptions = { group_wait: currentRoute.group_wait, @@ -245,7 +246,9 @@ const Policy = (props: PolicyComponentProps) => { ) : hasMatchers ? ( ) : ( - No matchers + + No matchers + )} {/* TODO maybe we should move errors to the gutter instead? */} @@ -264,7 +267,7 @@ const Policy = (props: PolicyComponentProps) => { type="button" onClick={() => onAddPolicy(currentRoute, 'child')} > - New child policy + New child policy ) : ( { icon="angle-down" type="button" > - Add new policy + Add new policy )} @@ -326,6 +329,7 @@ const Policy = (props: PolicyComponentProps) => { contactPoint={contactPoint ?? undefined} groupBy={groupBy} muteTimings={muteTimings} + activeTimings={activeTimings} timingOptions={timingOptions} inheritedProperties={inheritedProperties} alertManagerSourceName={alertManagerSourceName} @@ -379,7 +383,9 @@ const Policy = (props: PolicyComponentProps) => { className={styles.moreButtons} onClick={() => setVisibleChildPolicies(visibleChildPolicies + POLICIES_PER_PAGE)} > - {moreCount} additional {pluralize('policy', moreCount)} + + {{ count: moreCount }} additional policies + )} @@ -397,6 +403,7 @@ interface MetadataRowProps { contactPoint?: string; groupBy?: string[]; muteTimings?: string[]; + activeTimings?: string[]; timingOptions?: TimingOptions; inheritedProperties?: Partial; alertManagerSourceName: string; @@ -417,6 +424,7 @@ function MetadataRow({ timingOptions, groupBy, muteTimings = [], + activeTimings = [], matchingInstancesPreview, inheritedProperties, matchingAlertGroups, @@ -436,6 +444,7 @@ function MetadataRow({ const singleGroup = isDefaultPolicy && isArray(groupBy) && groupBy.length === 0; const hasMuteTimings = Boolean(muteTimings.length); + const hasActiveTimings = Boolean(activeTimings.length); return (
@@ -450,12 +459,18 @@ function MetadataRow({ data-testid="matching-instances" > {numberOfAlertInstances ?? '-'} - {pluralize('instance', numberOfAlertInstances)} + + + instance + + )} {contactPoint && ( - Delivered to + + Delivered to + {customGrouping && ( - Grouped by + + Grouped by + {groupBy.join(', ')} )} {singleGroup && ( - Single group + + Single group + )} {noGrouping && ( - Not grouping + + Not grouping + )} )} {hasMuteTimings && ( - Muted when - + + Muted when + + + + )} + {hasActiveTimings && ( + + + Active when + + )} {timingOptions && ( @@ -498,7 +529,9 @@ function MetadataRow({ {hasInheritedProperties && ( <> - Inherited + + Inherited + @@ -514,7 +547,7 @@ export const useCreateDropdownMenuActions = ( provisioned: boolean, onEditPolicy: (route: RouteWithID, isDefault?: boolean, readOnly?: boolean) => void, currentRoute: RouteWithID, - toggleShowExportDrawer: (nextValue?: any) => void, + toggleShowExportDrawer: () => void, onDeletePolicy: (route: RouteWithID) => void ) => { const [ @@ -644,10 +677,12 @@ function DefaultPolicyIndicator() { return ( <> - Default policy + Default policy - All alert instances will be handled by the default policy if no other matching policies are found. + + All alert instances will be handled by the default policy if no other matching policies are found. + ); @@ -656,7 +691,7 @@ function DefaultPolicyIndicator() { function AutogeneratedRootIndicator() { return ( - Auto-generated policies + Auto-generated policies ); } @@ -683,7 +718,7 @@ const InheritedProperties: FC<{ properties: InheritableProperties }> = ({ proper ); -const MuteTimings: FC<{ timings: string[]; alertManagerSourceName: string }> = ({ +const TimeIntervals: FC<{ timings: string[]; alertManagerSourceName: string }> = ({ timings, alertManagerSourceName, }) => { @@ -852,7 +887,9 @@ const ContactPointsHoverDetails: FC = ({ placement="top" header={ -
Contact Point
+
+ Contact Point +
{contactPoint}
} @@ -930,7 +967,7 @@ const routePropertyToValue = ( if (isNotGrouping) { return ( - Not grouping + Not grouping ); } @@ -938,7 +975,7 @@ const routePropertyToValue = ( if (isSingleGroup) { return ( - Single group + Single group ); } diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index a1c3ad5afa6..018a3ff68ff 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -52,7 +52,7 @@ import { discoverFeatures } from '../api/buildInfo'; import { FetchPromRulesFilter, fetchRules } from '../api/prometheus'; import { FetchRulerRulesFilter, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from '../api/ruler'; import { RuleFormValues } from '../types/rule-form'; -import { addDefaultsToAlertmanagerConfig, removeMuteTimingFromRoute } from '../utils/alertmanager'; +import { addDefaultsToAlertmanagerConfig, removeTimeIntervalFromRoute } from '../utils/alertmanager'; import { GRAFANA_RULES_SOURCE_NAME, getAllRulesSourceNames, @@ -590,7 +590,7 @@ export const deleteMuteTimingAction = (alertManagerSourceName: string, muteTimin alertmanager_config: { ...configWithoutMuteTimings, route: config.alertmanager_config.route - ? removeMuteTimingFromRoute(muteTimingName, config.alertmanager_config?.route) + ? removeTimeIntervalFromRoute(muteTimingName, config.alertmanager_config?.route) : undefined, ...time_intervals_without_mute_to_save, }, diff --git a/public/app/features/alerting/unified/utils/alertmanager.test.ts b/public/app/features/alerting/unified/utils/alertmanager.test.ts index 3c0293aef94..1607a72ac6b 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.test.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.test.ts @@ -1,7 +1,7 @@ import { Matcher, MatcherOperator, Route } from 'app/plugins/datasource/alertmanager/types'; import { Labels } from 'app/types/unified-alerting-dto'; -import { labelsMatchMatchers, removeMuteTimingFromRoute, matchersToString } from './alertmanager'; +import { labelsMatchMatchers, removeTimeIntervalFromRoute, matchersToString } from './alertmanager'; import { parseMatcher, parsePromQLStyleMatcherLooseSafe } from './matchers'; describe('Alertmanager utils', () => { @@ -104,6 +104,7 @@ describe('Alertmanager utils', () => { receiver: 'slack', object_matchers: [['env', MatcherOperator.equal, 'prod']], mute_time_intervals: ['test2'], + active_time_intervals: ['test1'], }, { receiver: 'pagerduty', @@ -113,13 +114,15 @@ describe('Alertmanager utils', () => { ], }; - it('should remove mute timings from routes', () => { - expect(removeMuteTimingFromRoute('test1', route)).toEqual({ + it('should remove time interval from routes', () => { + expect(removeTimeIntervalFromRoute('test1', route)).toEqual({ mute_time_intervals: ['test2'], + active_time_intervals: [], object_matchers: [['foo', '=', 'bar']], receiver: 'gmail', routes: [ { + active_time_intervals: [], mute_time_intervals: ['test2'], object_matchers: [['env', '=', 'prod']], receiver: 'slack', @@ -127,6 +130,7 @@ describe('Alertmanager utils', () => { }, { mute_time_intervals: [], + active_time_intervals: [], object_matchers: [['env', '=', 'eu']], receiver: 'pagerduty', routes: undefined, diff --git a/public/app/features/alerting/unified/utils/alertmanager.ts b/public/app/features/alerting/unified/utils/alertmanager.ts index 8a73dd2685c..c7e05d82dd0 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.ts @@ -35,22 +35,22 @@ export function addDefaultsToAlertmanagerConfig(config: AlertManagerCortexConfig return config; } -export function removeMuteTimingFromRoute(muteTiming: string, route: Route): Route { +export function removeTimeIntervalFromRoute(muteTiming: string, route: Route): Route { const newRoute: Route = { ...route, mute_time_intervals: route.mute_time_intervals?.filter((muteName) => muteName !== muteTiming) ?? [], - routes: route.routes?.map((subRoute) => removeMuteTimingFromRoute(muteTiming, subRoute)), + active_time_intervals: route.active_time_intervals?.filter((muteName) => muteName !== muteTiming) ?? [], + routes: route.routes?.map((subRoute) => removeTimeIntervalFromRoute(muteTiming, subRoute)), }; return newRoute; } -export function renameMuteTimings(newMuteTimingName: string, oldMuteTimingName: string, route: Route): Route { +export function renameTimeInterval(newName: string, oldName: string, route: Route): Route { return { ...route, - mute_time_intervals: route.mute_time_intervals?.map((name) => - name === oldMuteTimingName ? newMuteTimingName : name - ), - routes: route.routes?.map((subRoute) => renameMuteTimings(newMuteTimingName, oldMuteTimingName, subRoute)), + mute_time_intervals: route.mute_time_intervals?.map((name) => (name === oldName ? newName : name)), + active_time_intervals: route.active_time_intervals?.map((name) => (name === oldName ? newName : name)), + routes: route.routes?.map((subRoute) => renameTimeInterval(newName, oldName, subRoute)), }; } diff --git a/public/app/plugins/datasource/alertmanager/types.ts b/public/app/plugins/datasource/alertmanager/types.ts index c5bcacd9457..c27d4e53122 100644 --- a/public/app/plugins/datasource/alertmanager/types.ts +++ b/public/app/plugins/datasource/alertmanager/types.ts @@ -119,7 +119,10 @@ export type Route = { group_interval?: string; repeat_interval?: string; routes?: Route[]; + /** Times when the route should be muted. */ mute_time_intervals?: string[]; + /** Times when the route should be active. This is the opposite of `mute_time_intervals` */ + active_time_intervals?: string[]; /** only the root policy might have a provenance field defined */ provenance?: string; }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b2112d30d29..f5a96871f45 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -106,6 +106,7 @@ "export-all": "Export all", "view": "View" }, + "contact-point": "Contact Point", "contact-points": { "delivery-duration": "Last delivery took <1>", "last-delivery-attempt": "Last delivery attempt", @@ -136,7 +137,23 @@ "saving": "Saving mute timing" }, "policies": { + "default-policy": { + "description": "All alert instances will be handled by the default policy if no other matching policies are found.", + "title": "Default policy" + }, + "generated-policies": "Auto-generated policies", "metadata": { + "active-time": "Active when", + "delivered-to": "Delivered to", + "grouped-by": "Grouped by", + "grouping": { + "none": "Not grouping", + "single-group": "Single group" + }, + "inherited": "Inherited", + "mute-time": "Muted when", + "n-instances_one": "instance", + "n-instances_other": "instances", "timingOptions": { "groupInterval": { "description": "How long to wait before sending a notification about new alerts that are added to a group of alerts for which an initial notification has already been sent.", @@ -151,7 +168,12 @@ "label": "Repeated every <1>" } } - } + }, + "n-more-policies_one": "{{count}} additional policy", + "n-more-policies_other": "{{count}} additional policies", + "new-child": "New child policy", + "new-policy": "Add new policy", + "no-matchers": "No matchers" }, "provisioning": { "badge-tooltip-provenance": "This resource has been provisioned via {{provenance}} and cannot be edited through the UI", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 244caed14ca..729d80a2982 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -106,6 +106,7 @@ "export-all": "Ēχpőřŧ äľľ", "view": "Vįęŵ" }, + "contact-point": "Cőʼnŧäčŧ Pőįʼnŧ", "contact-points": { "delivery-duration": "Ŀäşŧ đęľįvęřy ŧőőĸ <1>", "last-delivery-attempt": "Ŀäşŧ đęľįvęřy äŧŧęmpŧ", @@ -136,7 +137,23 @@ "saving": "Ŝävįʼnģ mūŧę ŧįmįʼnģ" }, "policies": { + "default-policy": { + "description": "Åľľ äľęřŧ įʼnşŧäʼnčęş ŵįľľ þę ĥäʼnđľęđ þy ŧĥę đęƒäūľŧ pőľįčy įƒ ʼnő őŧĥęř mäŧčĥįʼnģ pőľįčįęş äřę ƒőūʼnđ.", + "title": "Đęƒäūľŧ pőľįčy" + }, + "generated-policies": "Åūŧő-ģęʼnęřäŧęđ pőľįčįęş", "metadata": { + "active-time": "Åčŧįvę ŵĥęʼn", + "delivered-to": "Đęľįvęřęđ ŧő", + "grouped-by": "Ğřőūpęđ þy", + "grouping": { + "none": "Ńőŧ ģřőūpįʼnģ", + "single-group": "Ŝįʼnģľę ģřőūp" + }, + "inherited": "Ĩʼnĥęřįŧęđ", + "mute-time": "Mūŧęđ ŵĥęʼn", + "n-instances_one": "įʼnşŧäʼnčę", + "n-instances_other": "įʼnşŧäʼnčęş", "timingOptions": { "groupInterval": { "description": "Ħőŵ ľőʼnģ ŧő ŵäįŧ þęƒőřę şęʼnđįʼnģ ä ʼnőŧįƒįčäŧįőʼn äþőūŧ ʼnęŵ äľęřŧş ŧĥäŧ äřę äđđęđ ŧő ä ģřőūp őƒ äľęřŧş ƒőř ŵĥįčĥ äʼn įʼnįŧįäľ ʼnőŧįƒįčäŧįőʼn ĥäş äľřęäđy þęęʼn şęʼnŧ.", @@ -151,7 +168,12 @@ "label": "Ŗępęäŧęđ ęvęřy <1>" } } - } + }, + "n-more-policies_one": "{{count}} äđđįŧįőʼnäľ pőľįčy", + "n-more-policies_other": "{{count}} äđđįŧįőʼnäľ pőľįčįęş", + "new-child": "Ńęŵ čĥįľđ pőľįčy", + "new-policy": "Åđđ ʼnęŵ pőľįčy", + "no-matchers": "Ńő mäŧčĥęřş" }, "provisioning": { "badge-tooltip-provenance": "Ŧĥįş řęşőūřčę ĥäş þęęʼn přővįşįőʼnęđ vįä {{provenance}} äʼnđ čäʼnʼnőŧ þę ęđįŧęđ ŧĥřőūģĥ ŧĥę ŮĨ",