Alerting: Split out components for notification policies (#97191)

This commit is contained in:
Tom Ratcliffe 2024-12-18 12:16:46 +00:00 committed by GitHub
parent 89774f3c8d
commit 6df7d1fbc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 256 additions and 257 deletions

View File

@ -1406,12 +1406,9 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "13"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "14"]
],
"public/app/features/alerting/unified/NotificationPolicies.tsx:5381": [
"public/app/features/alerting/unified/NotificationPoliciesPage.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
],
"public/app/features/alerting/unified/PanelAlertTabContent.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
@ -1711,10 +1708,8 @@ exports[`better eslint`] = {
],
"public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"]
],
"public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
@ -1760,18 +1755,18 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "18"]
],
"public/app/features/alerting/unified/components/notification-policies/Filters.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
],
"public/app/features/alerting/unified/components/notification-policies/Modals.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"]
],
"public/app/features/alerting/unified/components/notification-policies/NotificationPoliciesList.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
],
"public/app/features/alerting/unified/components/notification-policies/Policy.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
@ -2579,9 +2574,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"]
],
"public/app/features/alerting/unified/components/silences/SilencesTable.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],

View File

@ -47,7 +47,10 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
...PERMISSIONS_TIME_INTERVALS_MODIFY,
]),
component: importAlertingComponent(
() => import(/* webpackChunkName: "AlertAmRoutes" */ 'app/features/alerting/unified/NotificationPolicies')
() =>
import(
/* webpackChunkName: "NotificationPoliciesPage" */ 'app/features/alerting/unified/NotificationPoliciesPage'
)
),
},
{

View File

@ -32,7 +32,8 @@ import {
} from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types';
import NotificationPolicies, { findRoutesMatchingFilters } from './NotificationPolicies';
import NotificationPolicies from './NotificationPoliciesPage';
import { findRoutesMatchingFilters } from './components/notification-policies/NotificationPoliciesList';
import {
grantUserPermissions,
mockDataSource,

View File

@ -0,0 +1,113 @@
import { css } from '@emotion/css';
import { useState } from 'react';
import { GrafanaTheme2, UrlQueryMap } from '@grafana/data';
import { Tab, TabContent, TabsBar, useStyles2, withErrorBoundary } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
import { NotificationPoliciesList } from 'app/features/alerting/unified/components/notification-policies/NotificationPoliciesList';
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
import { useAlertmanager } from './state/AlertmanagerContext';
enum ActiveTab {
NotificationPolicies = 'notification_policies',
MuteTimings = 'mute_timings',
}
const NotificationPoliciesTabs = () => {
const styles = useStyles2(getStyles);
// Alertmanager logic and data hooks
const { selectedAlertmanager = '' } = useAlertmanager();
const [policiesSupported, canSeePoliciesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationPolicyTree);
const [timingsSupported, canSeeTimingsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewMuteTiming);
const availableTabs = [
canSeePoliciesTab && ActiveTab.NotificationPolicies,
canSeeTimingsTab && ActiveTab.MuteTimings,
].filter((tab) => !!tab);
const { data: muteTimings = [] } = useMuteTimings({
alertmanager: selectedAlertmanager,
skip: !canSeeTimingsTab,
});
// Tab state management
const [queryParams, setQueryParams] = useQueryParams();
const { tab } = getActiveTabFromUrl(queryParams, availableTabs[0]);
const [activeTab, setActiveTab] = useState<ActiveTab>(tab);
const muteTimingsTabActive = activeTab === ActiveTab.MuteTimings;
const policyTreeTabActive = activeTab === ActiveTab.NotificationPolicies;
const numberOfMuteTimings = muteTimings.length;
return (
<>
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={selectedAlertmanager} />
<TabsBar>
{policiesSupported && canSeePoliciesTab && (
<Tab
label={'Notification Policies'}
active={policyTreeTabActive}
onChangeTab={() => {
setActiveTab(ActiveTab.NotificationPolicies);
setQueryParams({ tab: ActiveTab.NotificationPolicies });
}}
/>
)}
{timingsSupported && canSeeTimingsTab && (
<Tab
label={'Mute Timings'}
active={muteTimingsTabActive}
counter={numberOfMuteTimings}
onChangeTab={() => {
setActiveTab(ActiveTab.MuteTimings);
setQueryParams({ tab: ActiveTab.MuteTimings });
}}
/>
)}
</TabsBar>
<TabContent className={styles.tabContent}>
{policyTreeTabActive && <NotificationPoliciesList />}
{muteTimingsTabActive && <MuteTimingsTable />}
</TabContent>
</>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
tabContent: css({
marginTop: theme.spacing(2),
}),
});
interface QueryParamValues {
tab: ActiveTab;
}
function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): QueryParamValues {
let tab = defaultTab;
if (queryParams.tab === ActiveTab.NotificationPolicies) {
tab = ActiveTab.NotificationPolicies;
}
if (queryParams.tab === ActiveTab.MuteTimings) {
tab = ActiveTab.MuteTimings;
}
return {
tab,
};
}
const NotificationPoliciesPage = () => (
<AlertmanagerPageWrapper navId="am-routes" accessType="notification">
<NotificationPoliciesTabs />
</AlertmanagerPageWrapper>
);
export default withErrorBoundary(NotificationPoliciesPage, { style: 'page' });

View File

@ -19,7 +19,7 @@ import { MuteTimingsTable } from './MuteTimingsTable';
const renderWithProvider = (alertManagerSource?: string) => {
return render(
<AlertmanagerProvider accessType={'notification'} alertmanagerSourceName={alertManagerSource}>
<MuteTimingsTable alertManagerSourceName={alertManagerSource ?? GRAFANA_RULES_SOURCE_NAME} />
<MuteTimingsTable />
</AlertmanagerProvider>
);
};

View File

@ -9,6 +9,7 @@ import {
ALL_MUTE_TIMINGS,
useExportMuteTimingsDrawer,
} from 'app/features/alerting/unified/components/mute-timings/useExportMuteTimingsDrawer';
import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext';
import { PROVENANCE_ANNOTATION } from 'app/features/alerting/unified/utils/k8s/constants';
import { Authorize } from '../../components/Authorize';
@ -22,21 +23,18 @@ import { Spacer } from '../Spacer';
import { MuteTiming, useMuteTimings } from './useMuteTimings';
import { renderTimeIntervals } from './util';
interface MuteTimingsTableProps {
alertManagerSourceName: string;
hideActions?: boolean;
}
type TableItem = {
id: string;
data: MuteTiming;
};
export const MuteTimingsTable = ({ alertManagerSourceName, hideActions }: MuteTimingsTableProps) => {
export const MuteTimingsTable = () => {
const { selectedAlertmanager: alertManagerSourceName = '', hasConfigurationAPI } = useAlertmanager();
const hideActions = !hasConfigurationAPI;
const styles = useStyles2(getStyles);
const [ExportAllDrawer, showExportAllDrawer] = useExportMuteTimingsDrawer();
const { data, isLoading, error } = useMuteTimings({ alertmanager: alertManagerSourceName });
const { data, isLoading, error } = useMuteTimings({ alertmanager: alertManagerSourceName ?? '' });
const items = useMemo((): TableItem[] => {
const muteTimings = data || [];
@ -73,10 +71,10 @@ export const MuteTimingsTable = ({ alertManagerSourceName, hideActions }: MuteTi
return (
<div className={styles.container}>
<Stack direction="row" alignItems="center">
<span>
<Trans i18nKey="alerting.mute-timings.description">
Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of
time.
</span>
</Trans>
<Spacer />
{!hideActions && items.length > 0 && (
<Authorize actions={[AlertmanagerAction.CreateMuteTiming]}>
@ -86,7 +84,7 @@ export const MuteTimingsTable = ({ alertManagerSourceName, hideActions }: MuteTi
variant="primary"
href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
>
Add mute timing
<Trans i18nKey="alerting.mute-timings.add-mute-timing">Add mute timing</Trans>
</LinkButton>
</Authorize>
)}

View File

@ -3,7 +3,9 @@ import { debounce, isEqual } from 'lodash';
import { useCallback, useEffect, useRef } from 'react';
import { Button, Field, Icon, Input, Label, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector';
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
import { ObjectMatcher, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
@ -25,6 +27,7 @@ const NotificationPoliciesFilter = ({
onChangeMatchers,
matchingCount,
}: NotificationPoliciesFilterProps) => {
const [contactPointsSupported, canSeeContactPoints] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
const [searchParams, setSearchParams] = useURLSearchParams();
const searchInputRef = useRef<HTMLInputElement | null>(null);
const { queryString, contactPoint } = getNotificationPoliciesFilters(searchParams);
@ -68,13 +71,13 @@ const NotificationPoliciesFilter = ({
label={
<Label>
<Stack gap={0.5}>
<span>Search by matchers</span>
<Trans i18nKey="alerting.common.search-by-matchers">Search by matchers</Trans>
<Tooltip
content={
<div>
<Trans i18nKey="alerting.policies.filter-description">
Filter notification policies by using a comma separated list of matchers, e.g.:
<pre>severity=critical, region=EMEA</pre>
</div>
</Trans>
}
>
<Icon name="info-circle" size="sm" />
@ -97,24 +100,26 @@ const NotificationPoliciesFilter = ({
defaultValue={queryString}
/>
</Field>
<Field label="Search by contact point" style={{ marginBottom: 0 }}>
<ContactPointSelector
selectProps={{
id: 'receiver',
'aria-label': 'Search by contact point',
onChange: (option) => {
setSearchParams({ contactPoint: option?.value?.name });
},
width: 28,
isClearable: true,
}}
selectedContactPointName={searchParams.get('contactPoint') ?? undefined}
/>
</Field>
{contactPointsSupported && canSeeContactPoints && (
<Field label="Search by contact point" style={{ marginBottom: 0 }}>
<ContactPointSelector
selectProps={{
id: 'receiver',
'aria-label': 'Search by contact point',
onChange: (option) => {
setSearchParams({ contactPoint: option?.value?.name });
},
width: 28,
isClearable: true,
}}
selectedContactPointName={searchParams.get('contactPoint') ?? undefined}
/>
</Field>
)}
{hasFilters && (
<Stack alignItems="center">
<Button variant="secondary" icon="times" onClick={clearFilters}>
Clear filters
<Trans i18nKey="alerting.common.clear-filters">Clear filters</Trans>
</Button>
<Text variant="bodySmall" color="secondary">
{matchingCount === 0 && 'No policies matching filters.'}

View File

@ -1,102 +1,50 @@
import { css } from '@emotion/css';
import { useEffect, useMemo, useState } from 'react';
import { useAsyncFn } from 'react-use';
import { GrafanaTheme2, UrlQueryMap } from '@grafana/data';
import {
Alert,
Button,
LoadingPlaceholder,
Stack,
Tab,
TabContent,
TabsBar,
useStyles2,
withErrorBoundary,
} from '@grafana/ui';
import { Alert, Button, Stack } from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { Trans } from 'app/core/internationalization';
import { useContactPointsWithStatus } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
import { FormAmRoute } from 'app/features/alerting/unified/types/amroutes';
import { addUniqueIdentifierToRoute } from 'app/features/alerting/unified/utils/amroutes';
import { ERROR_NEWER_CONFIGURATION } from 'app/features/alerting/unified/utils/k8s/errors';
import { isErrorMatchingCode, stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
import { computeInheritedTree } from 'app/features/alerting/unified/utils/notification-policies';
import { ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
import { alertmanagerApi } from './api/alertmanagerApi';
import { useGetContactPointsState } from './api/receiversApi';
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
import {
NotificationPoliciesFilter,
findRoutesByMatchers,
findRoutesMatchingPredicate,
} from './components/notification-policies/Filters';
import {
useAddPolicyModal,
useAlertGroupsModal,
useDeletePolicyModal,
useEditPolicyModal,
} from './components/notification-policies/Modals';
import { Policy } from './components/notification-policies/Policy';
import {
useNotificationPolicyRoute,
useUpdateNotificationPolicyRoute,
} from './components/notification-policies/useNotificationPolicyRoute';
import { isLoading as isPending, useAsync } from './hooks/useAsync';
import { useAlertmanager } from './state/AlertmanagerContext';
import { FormAmRoute } from './types/amroutes';
import { useRouteGroupsMatcher } from './useRouteGroupsMatcher';
import { addUniqueIdentifierToRoute } from './utils/amroutes';
import { ERROR_NEWER_CONFIGURATION } from './utils/k8s/errors';
import { isErrorMatchingCode, stringifyErrorLike } from './utils/misc';
import { computeInheritedTree } from './utils/notification-policies';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { alertmanagerApi } from './../../api/alertmanagerApi';
import { useGetContactPointsState } from './../../api/receiversApi';
import { isLoading as isPending, useAsync } from './../../hooks/useAsync';
import { useRouteGroupsMatcher } from './../../useRouteGroupsMatcher';
import {
InsertPosition,
addRouteToReferenceRoute,
cleanRouteIDs,
mergePartialAmRouteWithRouteTree,
omitRouteFromRouteTree,
} from './utils/routeTree';
} from './../../utils/routeTree';
import { NotificationPoliciesFilter, findRoutesByMatchers, findRoutesMatchingPredicate } from './Filters';
import { useAddPolicyModal, useAlertGroupsModal, useDeletePolicyModal, useEditPolicyModal } from './Modals';
import { Policy } from './Policy';
import { useNotificationPolicyRoute, useUpdateNotificationPolicyRoute } from './useNotificationPolicyRoute';
enum ActiveTab {
NotificationPolicies = 'notification_policies',
MuteTimings = 'mute_timings',
}
const AmRoutes = () => {
const styles = useStyles2(getStyles);
export const NotificationPoliciesList = () => {
const appNotification = useAppNotification();
const [policiesSupported, canSeePoliciesTab] = useAlertmanagerAbility(AlertmanagerAction.ViewNotificationPolicyTree);
const [timingsSupported, canSeeTimingsTab] = useAlertmanagerAbility(AlertmanagerAction.ViewMuteTiming);
const [contactPointsSupported, canSeeContactPointsStatus] = useAlertmanagerAbility(
AlertmanagerAction.ViewContactPoint
);
const availableTabs = [
canSeePoliciesTab && ActiveTab.NotificationPolicies,
canSeeTimingsTab && ActiveTab.MuteTimings,
].filter((tab) => !!tab);
const [contactPointsSupported, canSeeContactPoints] = useAlertmanagerAbility(AlertmanagerAction.ViewContactPoint);
const [_, canSeeAlertGroups] = useAlertmanagerAbility(AlertmanagerAction.ViewAlertGroups);
const { useGetAlertmanagerAlertGroupsQuery } = alertmanagerApi;
const [queryParams, setQueryParams] = useQueryParams();
const { tab } = getActiveTabFromUrl(queryParams, availableTabs[0]);
const [activeTab, setActiveTab] = useState<ActiveTab>(tab);
const [contactPointFilter, setContactPointFilter] = useState<string | undefined>();
const [labelMatchersFilter, setLabelMatchersFilter] = useState<ObjectMatcher[]>([]);
const { selectedAlertmanager, hasConfigurationAPI, isGrafanaAlertmanager } = useAlertmanager();
const { getRouteGroupsMap } = useRouteGroupsMatcher();
const { data: muteTimings = [] } = useMuteTimings({
alertmanager: selectedAlertmanager ?? '',
skip: !canSeeTimingsTab,
});
const shouldFetchContactPoints =
policiesSupported && canSeePoliciesTab && contactPointsSupported && canSeeContactPointsStatus;
const shouldFetchContactPoints = contactPointsSupported && canSeeContactPoints;
const contactPointsState = useGetContactPointsState(
// Workaround to not try and call this API when we don't have access to the policies tab
shouldFetchContactPoints ? (selectedAlertmanager ?? '') : ''
@ -107,7 +55,7 @@ const AmRoutes = () => {
isLoading,
error: resultError,
refetch: refetchNotificationPolicyRoute,
} = useNotificationPolicyRoute({ alertmanager: selectedAlertmanager ?? '' }, { skip: !canSeePoliciesTab });
} = useNotificationPolicyRoute({ alertmanager: selectedAlertmanager ?? '' });
// We make the assumption that the first policy is the default one
// At the time of writing, this will be always the case for the AM config response, and the K8S API
@ -118,14 +66,14 @@ const AmRoutes = () => {
const { currentData: alertGroups, refetch: refetchAlertGroups } = useGetAlertmanagerAlertGroupsQuery(
{ amSourceName: selectedAlertmanager ?? '' },
{ skip: !canSeePoliciesTab || !canSeeAlertGroups || !selectedAlertmanager }
{ skip: !canSeeAlertGroups || !selectedAlertmanager }
);
const { contactPoints: receivers } = useContactPointsWithStatus({
alertmanager: selectedAlertmanager ?? '',
fetchPolicies: false,
fetchStatuses: canSeeContactPointsStatus,
skip: !canSeePoliciesTab,
fetchStatuses: true,
skip: !shouldFetchContactPoints,
});
const rootRoute = useMemo(() => {
@ -244,104 +192,65 @@ const AmRoutes = () => {
return null;
}
const numberOfMuteTimings = muteTimings.length;
const hasPoliciesData = rootRoute && !resultError && !isLoading;
const hasPoliciesError = !!resultError && !isLoading;
const muteTimingsTabActive = activeTab === ActiveTab.MuteTimings;
const policyTreeTabActive = activeTab === ActiveTab.NotificationPolicies;
return (
<>
<GrafanaAlertmanagerDeliveryWarning currentAlertmanager={selectedAlertmanager} />
<TabsBar>
{policiesSupported && canSeePoliciesTab && (
<Tab
label={'Notification Policies'}
active={policyTreeTabActive}
onChangeTab={() => {
setActiveTab(ActiveTab.NotificationPolicies);
setQueryParams({ tab: ActiveTab.NotificationPolicies });
}}
/>
)}
{timingsSupported && canSeeTimingsTab && (
<Tab
label={'Mute Timings'}
active={muteTimingsTabActive}
counter={numberOfMuteTimings}
onChangeTab={() => {
setActiveTab(ActiveTab.MuteTimings);
setQueryParams({ tab: ActiveTab.MuteTimings });
}}
/>
)}
</TabsBar>
<TabContent className={styles.tabContent}>
{isLoading && <LoadingPlaceholder text="Loading notification policies..." />}
{policyTreeTabActive && (
<>
{hasPoliciesError && (
<Alert severity="error" title="Error loading Alertmanager config">
{stringifyErrorLike(resultError) || 'Unknown error.'}
</Alert>
)}
{/* show when there is an update error */}
{isErrorMatchingCode(updateError, ERROR_NEWER_CONFIGURATION) && (
<Alert severity="info" title="Notification policies have changed">
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Trans i18nKey="alerting.policies.update-errors.conflict">
The notification policy tree has been updated by another user.
</Trans>
<Button onClick={refetchPolicies}>
<Trans i18nKey="alerting.policies.reload-policies">Reload policies</Trans>
</Button>
</Stack>
</Alert>
)}
{hasPoliciesData && (
<Stack direction="column" gap={1}>
{rootRoute && (
<NotificationPoliciesFilter
onChangeMatchers={setLabelMatchersFilter}
onChangeReceiver={setContactPointFilter}
matchingCount={routesMatchingFilters.matchedRoutesWithPath.size}
/>
)}
{rootRoute && (
<Policy
receivers={receivers}
currentRoute={rootRoute}
contactPointsState={contactPointsState.receivers}
readOnly={!hasConfigurationAPI}
provisioned={rootRoute._metadata?.provisioned}
alertManagerSourceName={selectedAlertmanager}
onAddPolicy={openAddModal}
onEditPolicy={openEditModal}
onDeletePolicy={openDeleteModal}
onShowAlertInstances={showAlertGroupsModal}
routesMatchingFilters={routesMatchingFilters}
matchingInstancesPreview={{
groupsMap: routeAlertGroupsMap,
enabled: Boolean(canSeeAlertGroups && !instancesPreviewError),
}}
isAutoGenerated={false}
isDefaultPolicy
/>
)}
</Stack>
)}
{addModal}
{editModal}
{deleteModal}
{alertInstancesModal}
</>
)}
{muteTimingsTabActive && (
<MuteTimingsTable alertManagerSourceName={selectedAlertmanager} hideActions={!hasConfigurationAPI} />
)}
</TabContent>
{hasPoliciesError && (
<Alert severity="error" title="Error loading Alertmanager config">
{stringifyErrorLike(resultError) || 'Unknown error.'}
</Alert>
)}
{/* show when there is an update error */}
{isErrorMatchingCode(updateError, ERROR_NEWER_CONFIGURATION) && (
<Alert severity="info" title="Notification policies have changed">
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Trans i18nKey="alerting.policies.update-errors.conflict">
The notification policy tree has been updated by another user.
</Trans>
<Button onClick={refetchPolicies}>
<Trans i18nKey="alerting.policies.reload-policies">Reload policies</Trans>
</Button>
</Stack>
</Alert>
)}
{hasPoliciesData && (
<Stack direction="column" gap={1}>
{rootRoute && (
<NotificationPoliciesFilter
onChangeMatchers={setLabelMatchersFilter}
onChangeReceiver={setContactPointFilter}
matchingCount={routesMatchingFilters.matchedRoutesWithPath.size}
/>
)}
{rootRoute && (
<Policy
receivers={receivers}
currentRoute={rootRoute}
contactPointsState={contactPointsState.receivers}
readOnly={!hasConfigurationAPI}
provisioned={rootRoute._metadata?.provisioned}
alertManagerSourceName={selectedAlertmanager}
onAddPolicy={openAddModal}
onEditPolicy={openEditModal}
onDeletePolicy={openDeleteModal}
onShowAlertInstances={showAlertGroupsModal}
routesMatchingFilters={routesMatchingFilters}
matchingInstancesPreview={{
groupsMap: routeAlertGroupsMap,
enabled: Boolean(canSeeAlertGroups && !instancesPreviewError),
}}
isAutoGenerated={false}
isDefaultPolicy
/>
)}
</Stack>
)}
{addModal}
{editModal}
{deleteModal}
{alertInstancesModal}
</>
);
};
@ -429,37 +338,3 @@ function findMapIntersection(...matchingRoutes: FilterResult[]): FilterResult {
return result;
}
const getStyles = (theme: GrafanaTheme2) => ({
tabContent: css({
marginTop: theme.spacing(2),
}),
});
interface QueryParamValues {
tab: ActiveTab;
}
function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): QueryParamValues {
let tab = defaultTab;
if (queryParams.tab === ActiveTab.NotificationPolicies) {
tab = ActiveTab.NotificationPolicies;
}
if (queryParams.tab === ActiveTab.MuteTimings) {
tab = ActiveTab.MuteTimings;
}
return {
tab,
};
}
const NotificationPoliciesPage = () => (
<AlertmanagerPageWrapper navId="am-routes" accessType="notification">
<AmRoutes />
</AlertmanagerPageWrapper>
);
export default withErrorBoundary(NotificationPoliciesPage, { style: 'page' });

View File

@ -35,7 +35,6 @@ import {
} from 'app/plugins/datasource/alertmanager/types';
import { ReceiversState } from 'app/types';
import { RoutesMatchingFilters } from '../../NotificationPolicies';
import { AlertmanagerAction, useAlertmanagerAbilities, useAlertmanagerAbility } from '../../hooks/useAbilities';
import { getAmMatcherFormatter } from '../../utils/alertmanager';
import { MatcherFormatter, normalizeMatchers } from '../../utils/matchers';
@ -51,6 +50,7 @@ import { Spacer } from '../Spacer';
import { GrafanaPoliciesExporter } from '../export/GrafanaPoliciesExporter';
import { Matchers } from './Matchers';
import { RoutesMatchingFilters } from './NotificationPoliciesList';
import { TIMING_OPTIONS_DEFAULTS, TimingOptions } from './timingOptions';
interface PolicyComponentProps {
@ -742,7 +742,7 @@ const TimeIntervals: FC<{ timings: string[]; alertManagerSourceName: string }> =
href={createMuteTimingLink(timing, alertManagerSourceName)}
color={canSeeMuteTimings ? 'primary' : 'secondary'}
variant="bodySmall"
inline={false}
inline={canSeeMuteTimings ? false : undefined}
>
{timing}
</Wrapper>

View File

@ -5,6 +5,7 @@ import { FormEvent, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Field, Icon, Input, Label, Stack, Tooltip, useStyles2 } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { Trans } from 'app/core/internationalization';
import { parsePromQLStyleMatcherLoose } from '../../utils/matchers';
import { getSilenceFiltersFromUrlParams } from '../../utils/misc';
@ -48,7 +49,7 @@ export const SilencesFilter = () => {
label={
<Label>
<Stack gap={0.5}>
<span>Search by matchers</span>
<Trans i18nKey="alerting.common.search-by-matchers">Search by matchers</Trans>
<Tooltip
content={
<div>
@ -79,7 +80,7 @@ export const SilencesFilter = () => {
{queryString && (
<div className={styles.rowChild}>
<Button variant="secondary" icon="times" onClick={clearFilters}>
Clear filters
<Trans i18nKey="alerting.common.clear-filters">Clear filters</Trans>
</Button>
</div>
)}

View File

@ -241,11 +241,13 @@
},
"common": {
"cancel": "Cancel",
"clear-filters": "Clear filters",
"delete": "Delete",
"edit": "Edit",
"export": "Export",
"export-all": "Export all",
"loading": "Loading...",
"search-by-matchers": "Search by matchers",
"view": "View"
},
"contact-points": {
@ -327,6 +329,8 @@
}
},
"mute-timings": {
"add-mute-timing": "Add mute timing",
"description": "Enter specific time intervals when not to send notifications or freeze notifications for recurring periods of time.",
"save": "Save mute timing",
"saving": "Saving mute timing"
},
@ -349,6 +353,7 @@
"warning-1": "Deleting this notification policy will permanently remove it.",
"warning-2": "Are you sure you want to delete this policy?"
},
"filter-description": "Filter notification policies by using a comma separated list of matchers, e.g.:<1>severity=critical, region=EMEA</1>",
"generated-policies": "Auto-generated policies",
"matchers": "Matchers",
"metadata": {

View File

@ -241,11 +241,13 @@
},
"common": {
"cancel": "Cäʼnčęľ",
"clear-filters": "Cľęäř ƒįľŧęřş",
"delete": "Đęľęŧę",
"edit": "Ēđįŧ",
"export": "Ēχpőřŧ",
"export-all": "Ēχpőřŧ äľľ",
"loading": "Ŀőäđįʼnģ...",
"search-by-matchers": "Ŝęäřčĥ þy mäŧčĥęřş",
"view": "Vįęŵ"
},
"contact-points": {
@ -327,6 +329,8 @@
}
},
"mute-timings": {
"add-mute-timing": "Åđđ mūŧę ŧįmįʼnģ",
"description": "Ēʼnŧęř şpęčįƒįč ŧįmę įʼnŧęřväľş ŵĥęʼn ʼnőŧ ŧő şęʼnđ ʼnőŧįƒįčäŧįőʼnş őř ƒřęęžę ʼnőŧįƒįčäŧįőʼnş ƒőř řęčūřřįʼnģ pęřįőđş őƒ ŧįmę.",
"save": "Ŝävę mūŧę ŧįmįʼnģ",
"saving": "Ŝävįʼnģ mūŧę ŧįmįʼnģ"
},
@ -349,6 +353,7 @@
"warning-1": "Đęľęŧįʼnģ ŧĥįş ʼnőŧįƒįčäŧįőʼn pőľįčy ŵįľľ pęřmäʼnęʼnŧľy řęmővę įŧ.",
"warning-2": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő đęľęŧę ŧĥįş pőľįčy?"
},
"filter-description": "Fįľŧęř ʼnőŧįƒįčäŧįőʼn pőľįčįęş þy ūşįʼnģ ä čőmmä şępäřäŧęđ ľįşŧ őƒ mäŧčĥęřş, ę.ģ.:<1>şęvęřįŧy=čřįŧįčäľ, řęģįőʼn=ĒMĒÅ</1>",
"generated-policies": "Åūŧő-ģęʼnęřäŧęđ pőľįčįęş",
"matchers": "Mäŧčĥęřş",
"metadata": {