Alerting: Use alerting-specific error boundary for page components (#99980)

Use alerting-specific error boundary for page components
This commit is contained in:
Konrad Lalik 2025-02-13 09:20:45 +01:00 committed by GitHub
parent df64dd0762
commit fbf96916aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 238 additions and 180 deletions

View File

@ -21,6 +21,8 @@ interface Props {
onError?: (error: Error) => void;
/** Callback error state is cleared due to recover props change */
onRecover?: () => void;
/** Default error logger - Faro by default */
errorLogger?: (error: Error) => void;
}
interface State {
@ -35,7 +37,12 @@ export class ErrorBoundary extends PureComponent<Props, State> {
};
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
faro?.api?.pushError(error);
const logger = this.props.errorLogger ?? faro?.api?.pushError;
if (logger) {
logger(error);
}
this.setState({ error, errorInfo });
if (this.props.onError) {
@ -89,6 +96,8 @@ export interface ErrorBoundaryAlertProps {
/** Will re-render children after error if recover values changes */
dependencies?: unknown[];
/** Default error logger - Faro by default */
errorLogger?: (error: Error) => void;
}
export class ErrorBoundaryAlert extends PureComponent<ErrorBoundaryAlertProps> {
@ -98,10 +107,10 @@ export class ErrorBoundaryAlert extends PureComponent<ErrorBoundaryAlertProps> {
};
render() {
const { title, children, style, dependencies } = this.props;
const { title, children, style, dependencies, errorLogger } = this.props;
return (
<ErrorBoundary dependencies={dependencies}>
<ErrorBoundary dependencies={dependencies} errorLogger={errorLogger}>
{({ error, errorInfo }) => {
if (!errorInfo) {
return children;

View File

@ -19,6 +19,7 @@ import { NOTIFICATIONS_POLL_INTERVAL_MS } from './utils/constants';
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
import { getFiltersFromUrlParams } from './utils/misc';
import { initialAsyncRequestState } from './utils/redux';
import { withPageErrorBoundary } from './withPageErrorBoundary';
const AlertGroups = () => {
const { selectedAlertmanager } = useAlertmanager();
@ -89,10 +90,12 @@ const AlertGroups = () => {
);
};
const AlertGroupsPage = () => (
<AlertmanagerPageWrapper navId="groups" accessType="instance">
<AlertGroups />
</AlertmanagerPageWrapper>
);
function AlertGroupsPage() {
return (
<AlertmanagerPageWrapper navId="groups" accessType="instance">
<AlertGroups />
</AlertmanagerPageWrapper>
);
}
export default AlertGroupsPage;
export default withPageErrorBoundary(AlertGroupsPage);

View File

@ -1,7 +1,9 @@
import { NavModel } from '@grafana/data';
import { Page } from 'app/core/components/Page/Page';
export default function FeatureTogglePage() {
import { withPageErrorBoundary } from './withPageErrorBoundary';
function FeatureTogglePage() {
const navModel: NavModel = {
node: {
text: 'Alerting is not enabled',
@ -25,3 +27,5 @@ enabled = true
</Page>
);
}
export default withPageErrorBoundary(FeatureTogglePage);

View File

@ -1,6 +1,5 @@
import { useLocation } from 'react-router-dom-v5-compat';
import { withErrorBoundary } from '@grafana/ui';
import {
defaultsFromQuery,
getDefaultSilenceFormValues,
@ -12,6 +11,7 @@ import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
import { SilencesEditor } from './components/silences/SilencesEditor';
import { useAlertmanager } from './state/AlertmanagerContext';
import { withPageErrorBoundary } from './withPageErrorBoundary';
const SilencesEditorComponent = () => {
const location = useLocation();
@ -48,4 +48,5 @@ function NewSilencePage() {
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(NewSilencePage, { style: 'page' });
export default withPageErrorBoundary(NewSilencePage);

View File

@ -2,7 +2,7 @@ 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 { Tab, TabContent, TabsBar, useStyles2 } 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';
@ -12,6 +12,7 @@ import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
import { GrafanaAlertmanagerDeliveryWarning } from './components/GrafanaAlertmanagerDeliveryWarning';
import { MuteTimingsTable } from './components/mute-timings/MuteTimingsTable';
import { useAlertmanager } from './state/AlertmanagerContext';
import { withPageErrorBoundary } from './withPageErrorBoundary';
enum ActiveTab {
NotificationPolicies = 'notification_policies',
@ -104,10 +105,12 @@ function getActiveTabFromUrl(queryParams: UrlQueryMap, defaultTab: ActiveTab): Q
};
}
const NotificationPoliciesPage = () => (
<AlertmanagerPageWrapper navId="am-routes" accessType="notification">
<NotificationPoliciesTabs />
</AlertmanagerPageWrapper>
);
function NotificationPoliciesPage() {
return (
<AlertmanagerPageWrapper navId="am-routes" accessType="notification">
<NotificationPoliciesTabs />
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(NotificationPoliciesPage, { style: 'page' });
export default withPageErrorBoundary(NotificationPoliciesPage);

View File

@ -5,7 +5,7 @@ import { useLocation } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { config, isFetchError } from '@grafana/runtime';
import { Alert, Card, Icon, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui';
import { Alert, Card, Icon, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { AlertLabels } from './components/AlertLabels';
import { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout';
@ -13,6 +13,7 @@ import { useCloudCombinedRulesMatching } from './hooks/useCombinedRule';
import { getRulesSourceByName } from './utils/datasource';
import { createViewLink } from './utils/misc';
import { unescapePathSeparators } from './utils/rule-id';
import { withPageErrorBoundary } from './withPageErrorBoundary';
const pageTitle = 'Find rule';
const subUrl = config.appSubUrl;
@ -153,4 +154,4 @@ function getStyles(theme: GrafanaTheme2) {
};
}
export default withErrorBoundary(RedirectToRuleViewer, { style: 'page' });
export default withPageErrorBoundary(RedirectToRuleViewer);

View File

@ -3,6 +3,7 @@ import { Suspense, lazy } from 'react';
import { config } from '@grafana/runtime';
import RuleListV1 from './rule-list/RuleList.v1';
import { withPageErrorBoundary } from './withPageErrorBoundary';
const RuleListV2 = lazy(() => import('./rule-list/RuleList.v2'));
const RuleList = () => {
@ -11,4 +12,4 @@ const RuleList = () => {
return <Suspense>{newView ? <RuleListV2 /> : <RuleListV1 />}</Suspense>;
};
export default RuleList;
export default withPageErrorBoundary(RuleList);

View File

@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom-v5-compat';
import { NavModelItem } from '@grafana/data';
import { isFetchError } from '@grafana/runtime';
import { Alert, withErrorBoundary } from '@grafana/ui';
import { Alert } from '@grafana/ui';
import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound';
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
@ -12,6 +12,7 @@ import DetailView, { ActiveTab, useActiveTab } from './components/rule-viewer/Ru
import { useCombinedRule } from './hooks/useCombinedRule';
import { stringifyErrorLike } from './utils/misc';
import { getRuleIdFromPathname, parse as parseRuleId } from './utils/rule-id';
import { withPageErrorBoundary } from './withPageErrorBoundary';
const RuleViewer = (): JSX.Element => {
const params = useParams();
@ -86,4 +87,4 @@ function ErrorMessage({ error }: ErrorMessageProps) {
return <Alert title={'Something went wrong loading the rule'}>{stringifyErrorLike(error)}</Alert>;
}
export default withErrorBoundary(RuleViewer, { style: 'page' });
export default withPageErrorBoundary(RuleViewer);

View File

@ -6,8 +6,9 @@ import { useEditConfigurationDrawer } from './components/settings/ConfigurationD
import { ExternalAlertmanagers } from './components/settings/ExternalAlertmanagers';
import InternalAlertmanager from './components/settings/InternalAlertmanager';
import { SettingsProvider, useSettings } from './components/settings/SettingsContext';
import { withPageErrorBoundary } from './withPageErrorBoundary';
export default function SettingsPage() {
function SettingsPage() {
return (
<SettingsProvider>
<SettingsContent />
@ -47,3 +48,5 @@ function SettingsContent() {
</AlertingPageWrapper>
);
}
export default withPageErrorBoundary(SettingsPage);

View File

@ -1,28 +1,29 @@
import { Route, Routes } from 'react-router-dom-v5-compat';
import { withErrorBoundary } from '@grafana/ui';
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
import DuplicateMessageTemplate from './components/contact-points/DuplicateMessageTemplate';
import EditMessageTemplate from './components/contact-points/EditMessageTemplate';
import NewMessageTemplate from './components/contact-points/NewMessageTemplate';
import { withPageErrorBoundary } from './withPageErrorBoundary';
const NotificationTemplates = (): JSX.Element => (
<AlertmanagerPageWrapper
navId="receivers"
accessType="notification"
pageNav={{
id: 'templates',
text: 'Notification templates',
subTitle: 'Create and edit a group of notification templates',
}}
>
<Routes>
<Route path=":name/edit" element={<EditMessageTemplate />} />
<Route path="new" element={<NewMessageTemplate />} />
<Route path=":name/duplicate" element={<DuplicateMessageTemplate />} />
</Routes>
</AlertmanagerPageWrapper>
);
function NotificationTemplates() {
return (
<AlertmanagerPageWrapper
navId="receivers"
accessType="notification"
pageNav={{
id: 'templates',
text: 'Notification templates',
subTitle: 'Create and edit a group of notification templates',
}}
>
<Routes>
<Route path=":name/edit" element={<EditMessageTemplate />} />
<Route path="new" element={<NewMessageTemplate />} />
<Route path=":name/duplicate" element={<DuplicateMessageTemplate />} />
</Routes>
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(NotificationTemplates, { style: 'page' });
export default withPageErrorBoundary(NotificationTemplates);

View File

@ -12,7 +12,6 @@ import {
TabContent,
TabsBar,
Text,
withErrorBoundary,
} from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { Trans, t } from 'app/core/internationalization';
@ -25,6 +24,7 @@ import { usePagination } from '../../hooks/usePagination';
import { useURLSearchParams } from '../../hooks/useURLSearchParams';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning';
@ -270,4 +270,4 @@ function ContactPointsPage() {
);
}
export default withErrorBoundary(ContactPointsPage, { style: 'page' });
export default withPageErrorBoundary(ContactPointsPage);

View File

@ -8,13 +8,15 @@ import { useAlertmanager } from '../../state/AlertmanagerContext';
import { generateCopiedName } from '../../utils/duplicate';
import { stringifyErrorLike } from '../../utils/misc';
import { updateDefinesWithUniqueValue } from '../../utils/templates';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { TemplateForm } from '../receivers/TemplateForm';
import { useGetNotificationTemplate, useNotificationTemplates } from './useNotificationTemplates';
const notFoundComponent = <EntityNotFound entity="Notification template" />;
const DuplicateMessageTemplate = () => {
const DuplicateMessageTemplateComponent = () => {
const { selectedAlertmanager } = useAlertmanager();
const { name } = useParams<{ name: string }>();
const templateUid = name ? decodeURIComponent(name) : undefined;
@ -63,4 +65,12 @@ const DuplicateMessageTemplate = () => {
);
};
export default DuplicateMessageTemplate;
function DuplicateMessageTemplate() {
return (
<AlertmanagerPageWrapper navId="receivers" accessType="notification">
<DuplicateMessageTemplateComponent />
</AlertmanagerPageWrapper>
);
}
export default withPageErrorBoundary(DuplicateMessageTemplate);

View File

@ -1,10 +1,11 @@
import { useParams } from 'react-router-dom-v5-compat';
import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui';
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import { useGetContactPoint } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
import { stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { EditReceiverView } from '../receivers/EditReceiverView';
@ -50,4 +51,4 @@ function EditContactPointPage() {
);
}
export default withErrorBoundary(EditContactPointPage, { style: 'page' });
export default withPageErrorBoundary(EditContactPointPage);

View File

@ -6,13 +6,15 @@ import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'
import { isNotFoundError } from '../../api/util';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { stringifyErrorLike } from '../../utils/misc';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { TemplateForm } from '../receivers/TemplateForm';
import { useGetNotificationTemplate } from './useNotificationTemplates';
const notFoundComponent = <EntityNotFound entity="Notification template" />;
const EditMessageTemplate = () => {
const EditMessageTemplateComponent = () => {
const { name } = useParams<{ name: string }>();
const templateUid = name ? decodeURIComponent(name) : undefined;
@ -47,4 +49,12 @@ const EditMessageTemplate = () => {
return <TemplateForm alertmanager={selectedAlertmanager ?? ''} originalTemplate={currentData} />;
};
export default EditMessageTemplate;
function EditMessageTemplate() {
return (
<AlertmanagerPageWrapper navId="receivers" accessType="notification">
<EditMessageTemplateComponent />
</AlertmanagerPageWrapper>
);
}
export default withPageErrorBoundary(EditMessageTemplate);

View File

@ -1,16 +1,16 @@
import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { TemplateForm } from '../receivers/TemplateForm';
const NewMessageTemplate = () => {
function NewMessageTemplate() {
const { selectedAlertmanager } = useAlertmanager();
if (!selectedAlertmanager) {
return <EntityNotFound entity="Alertmanager" />;
}
return (
<AlertmanagerPageWrapper navId="receivers" accessType="notification">
<TemplateForm alertmanager={selectedAlertmanager ?? ''} />
</AlertmanagerPageWrapper>
);
}
return <TemplateForm alertmanager={selectedAlertmanager} />;
};
export default NewMessageTemplate;
export default withPageErrorBoundary(NewMessageTemplate);

View File

@ -1,11 +1,12 @@
import { Alert, withErrorBoundary } from '@grafana/ui';
import { Alert } from '@grafana/ui';
import { useAlertmanagerConfig } from '../../../hooks/useAlertmanagerConfig';
import { useAlertmanager } from '../../../state/AlertmanagerContext';
import { withPageErrorBoundary } from '../../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../../AlertingPageWrapper';
import { GlobalConfigForm } from '../../receivers/GlobalConfigForm';
const NewMessageTemplate = () => {
const GlobalConfig = () => {
const { selectedAlertmanager } = useAlertmanager();
const { data, isLoading, error } = useAlertmanagerConfig(selectedAlertmanager);
@ -28,12 +29,12 @@ const NewMessageTemplate = () => {
return <GlobalConfigForm config={data} alertManagerSourceName={selectedAlertmanager!} />;
};
function NewMessageTemplatePage() {
function GlobalConfigPage() {
return (
<AlertmanagerPageWrapper navId="receivers" accessType="notification">
<NewMessageTemplate />
<GlobalConfig />
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(NewMessageTemplatePage, { style: 'page' });
export default withPageErrorBoundary(GlobalConfigPage);

View File

@ -1,21 +1,8 @@
import * as React from 'react';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertingPageWrapper } from '../AlertingPageWrapper';
import { ModifyExportRuleForm } from '../rule-editor/alert-rule-form/ModifyExportRuleForm';
export default function ExportNewGrafanaRule() {
return (
<ExportNewGrafanaRuleWrapper>
<ModifyExportRuleForm />
</ExportNewGrafanaRuleWrapper>
);
}
interface ExportNewGrafanaRuleWrapperProps {
children: React.ReactNode;
}
function ExportNewGrafanaRuleWrapper({ children }: ExportNewGrafanaRuleWrapperProps) {
function ExportNewGrafanaRulePage() {
return (
<AlertingPageWrapper
navId="alert-list"
@ -24,7 +11,9 @@ function ExportNewGrafanaRuleWrapper({ children }: ExportNewGrafanaRuleWrapperPr
subTitle: 'Export a new rule definition in Terraform(HCL) format. Any changes you make will not be saved.',
}}
>
{children}
<ModifyExportRuleForm />
</AlertingPageWrapper>
);
}
export default withPageErrorBoundary(ExportNewGrafanaRulePage);

View File

@ -1,4 +1,3 @@
import * as React from 'react';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom-v5-compat';
@ -12,10 +11,11 @@ import { stringifyErrorLike } from '../../utils/misc';
import * as ruleId from '../../utils/rule-id';
import { isGrafanaRulerRule } from '../../utils/rules';
import { createRelativeUrl } from '../../utils/url';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertingPageWrapper } from '../AlertingPageWrapper';
import { ModifyExportRuleForm } from '../rule-editor/alert-rule-form/ModifyExportRuleForm';
export default function GrafanaModifyExport() {
function GrafanaModifyExport() {
const { id } = useParams();
const ruleIdentifier = useMemo<RuleIdentifier | undefined>(() => {
return ruleId.tryParse(id, true);
@ -23,38 +23,13 @@ export default function GrafanaModifyExport() {
if (!ruleIdentifier) {
return (
<ModifyExportWrapper>
<Alert title="Invalid rule ID" severity="error">
The rule UID in the page URL is invalid. Please check the URL and try again.
</Alert>
</ModifyExportWrapper>
<Alert title="Invalid rule ID" severity="error">
The rule UID in the page URL is invalid. Please check the URL and try again.
</Alert>
);
}
return (
<ModifyExportWrapper>
<RuleModifyExport ruleIdentifier={ruleIdentifier} />
</ModifyExportWrapper>
);
}
interface ModifyExportWrapperProps {
children: React.ReactNode;
}
function ModifyExportWrapper({ children }: ModifyExportWrapperProps) {
return (
<AlertingPageWrapper
navId="alert-list"
pageNav={{
text: 'Modify export',
subTitle:
'Modify the current alert rule and export the rule definition in the format of your choice. Any changes you make will not be saved.',
}}
>
{children}
</AlertingPageWrapper>
);
return <RuleModifyExport ruleIdentifier={ruleIdentifier} />;
}
function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }) {
@ -105,3 +80,20 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }
return <Alert title="Unknown error" />;
}
function GrafanaModifyExportPage() {
return (
<AlertingPageWrapper
navId="alert-list"
pageNav={{
text: 'Modify export',
subTitle:
'Modify the current alert rule and export the rule definition in the format of your choice. Any changes you make will not be saved.',
}}
>
<GrafanaModifyExport />
</AlertingPageWrapper>
);
}
export default withPageErrorBoundary(GrafanaModifyExportPage);

View File

@ -1,10 +1,10 @@
import { Navigate } from 'react-router-dom-v5-compat';
import { withErrorBoundary } from '@grafana/ui';
import { useGetMuteTiming } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
import { useURLSearchParams } from 'app/features/alerting/unified/hooks/useURLSearchParams';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import MuteTimingForm from './MuteTimingForm';
@ -38,17 +38,16 @@ const EditTimingRoute = () => {
);
};
const EditMuteTimingPage = () => (
<AlertmanagerPageWrapper
navId="am-routes"
pageNav={{
id: 'alert-policy-edit',
text: 'Edit mute timing',
}}
accessType="notification"
>
<EditTimingRoute />
</AlertmanagerPageWrapper>
);
function EditMuteTimingPage() {
return (
<AlertmanagerPageWrapper
navId="am-routes"
pageNav={{ id: 'alert-policy-edit', text: 'Edit mute timing' }}
accessType="notification"
>
<EditTimingRoute />
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(EditMuteTimingPage, { style: 'page' });
export default withPageErrorBoundary(EditMuteTimingPage);

View File

@ -1,20 +1,18 @@
import { withErrorBoundary } from '@grafana/ui';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import MuteTimingForm from './MuteTimingForm';
const NewMuteTimingPage = () => (
<AlertmanagerPageWrapper
navId="am-routes"
pageNav={{
id: 'alert-policy-new',
text: 'Add mute timing',
}}
accessType="notification"
>
<MuteTimingForm />
</AlertmanagerPageWrapper>
);
function NewMuteTimingPage() {
return (
<AlertmanagerPageWrapper
navId="am-routes"
pageNav={{ id: 'alert-policy-new', text: 'Add mute timing' }}
accessType="notification"
>
<MuteTimingForm />
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(NewMuteTimingPage, { style: 'page' });
export default withPageErrorBoundary(NewMuteTimingPage);

View File

@ -1,7 +1,7 @@
import { withErrorBoundary } from '@grafana/ui';
import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { CloudReceiverForm } from './form/CloudReceiverForm';
@ -24,4 +24,4 @@ function NewReceiverViewPage() {
);
}
export default withErrorBoundary(NewReceiverViewPage, { style: 'page' });
export default withPageErrorBoundary(NewReceiverViewPage);

View File

@ -1,14 +1,14 @@
import { withErrorBoundary } from '@grafana/ui';
import { withPageErrorBoundary } from '../../../withPageErrorBoundary';
import { AlertingPageWrapper } from '../../AlertingPageWrapper';
import { CentralAlertHistoryScene } from './CentralAlertHistoryScene';
const HistoryPage = () => {
function HistoryPage() {
return (
<AlertingPageWrapper navId="alerts-history" isLoading={false}>
<CentralAlertHistoryScene />
</AlertingPageWrapper>
);
};
export default withErrorBoundary(HistoryPage, { style: 'page' });
}
export default withPageErrorBoundary(HistoryPage);

View File

@ -25,7 +25,6 @@ import {
Stack,
TextArea,
useStyles2,
withErrorBoundary,
} from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { SilenceCreatedResponse, alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi';
@ -38,6 +37,7 @@ import { useAlertmanager } from '../../state/AlertmanagerContext';
import { SilenceFormFields } from '../../types/silence-form';
import { matcherFieldToMatcher } from '../../utils/alertmanager';
import { makeAMLink } from '../../utils/misc';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning';
@ -296,4 +296,5 @@ function ExistingSilenceEditorPage() {
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(ExistingSilenceEditorPage, { style: 'page' });
export default withPageErrorBoundary(ExistingSilenceEditorPage);

View File

@ -12,7 +12,6 @@ import {
LoadingPlaceholder,
Stack,
useStyles2,
withErrorBoundary,
} from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { Trans } from 'app/core/internationalization';
@ -27,6 +26,7 @@ import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbili
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { parsePromQLStyleMatcherLooseSafe } from '../../utils/matchers';
import { getSilenceFiltersFromUrlParams, makeAMLink, stringifyErrorLike } from '../../utils/misc';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { Authorize } from '../Authorize';
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
@ -393,4 +393,5 @@ function SilencesTablePage() {
</AlertmanagerPageWrapper>
);
}
export default withErrorBoundary(SilencesTablePage, { style: 'page' });
export default withPageErrorBoundary(SilencesTablePage);

View File

@ -5,12 +5,13 @@ import { Box, Stack, Tab, TabContent, TabsBar } from '@grafana/ui';
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
import { isLocalDevEnv } from '../utils/misc';
import { withPageErrorBoundary } from '../withPageErrorBoundary';
import GettingStarted, { WelcomeHeader } from './GettingStarted';
import { getInsightsScenes, insightsIsAvailable } from './Insights';
import { PluginIntegrations } from './PluginIntegrations';
export default function Home() {
function Home() {
const insightsEnabled = (insightsIsAvailable() || isLocalDevEnv()) && Boolean(config.featureToggles.alertingInsights);
const [activeTab, setActiveTab] = useState<'insights' | 'overview'>(insightsEnabled ? 'insights' : 'overview');
@ -51,3 +52,5 @@ export default function Home() {
</AlertingPageWrapper>
);
}
export default withPageErrorBoundary(Home);

View File

@ -2,7 +2,6 @@ import { useCallback } from 'react';
import { useParams } from 'react-router-dom-v5-compat';
import { NavModelItem } from '@grafana/data';
import { withErrorBoundary } from '@grafana/ui';
import { RuleIdentifier } from 'app/types/unified-alerting';
import { AlertWarning } from '../AlertWarning';
@ -11,6 +10,7 @@ import { AlertRuleForm } from '../components/rule-editor/alert-rule-form/AlertRu
import { useURLSearchParams } from '../hooks/useURLSearchParams';
import { useRulesAccess } from '../utils/accessControlHooks';
import * as ruleId from '../utils/rule-id';
import { withPageErrorBoundary } from '../withPageErrorBoundary';
import { CloneRuleEditor } from './CloneRuleEditor';
import { ExistingRuleEditor } from './ExistingRuleEditor';
@ -78,7 +78,9 @@ const RuleEditor = () => {
);
};
export default withErrorBoundary(RuleEditor, { style: 'page' });
// The pageNav property makes it difficult to only rely on AlertingPageWrapper
// to catch errors.
export default withPageErrorBoundary(RuleEditor);
function useRuleEditorPathParams() {
const params = useParams<RuleEditorPathParams>();

View File

@ -4,7 +4,7 @@ import { useAsyncFn, useInterval } from 'react-use';
import { urlUtil } from '@grafana/data';
import { logInfo } from '@grafana/runtime';
import { Button, LinkButton, Stack, withErrorBoundary } from '@grafana/ui';
import { Button, LinkButton, Stack } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { Trans } from 'app/core/internationalization';
import { useDispatch } from 'app/types';
@ -155,7 +155,7 @@ const RuleListV1 = () => {
);
};
export default withErrorBoundary(RuleListV1, { style: 'page' });
export default RuleListV1;
export function CreateAlertButton() {
const [createRuleSupported, createRuleAllowed] = useAlertingAbility(AlertingAction.CreateAlertRule);

View File

@ -1,5 +1,3 @@
import { withErrorBoundary } from '@grafana/ui';
import { AlertingPageWrapper } from '../components/AlertingPageWrapper';
import RulesFilter from '../components/rules/Filter/RulesFilter';
import { SupportedView } from '../components/rules/Filter/RulesViewModeSelector';
@ -9,24 +7,25 @@ import { useURLSearchParams } from '../hooks/useURLSearchParams';
import { FilterView } from './FilterView';
import { GroupedView } from './GroupedView';
const RuleList = withErrorBoundary(
() => {
const [queryParams] = useURLSearchParams();
const { filterState, hasActiveFilters } = useRulesFilter();
function RuleList() {
const [queryParams] = useURLSearchParams();
const { filterState, hasActiveFilters } = useRulesFilter();
const view: SupportedView = queryParams.get('view') === 'list' ? 'list' : 'grouped';
const showListView = hasActiveFilters || view === 'list';
const view: SupportedView = queryParams.get('view') === 'list' ? 'list' : 'grouped';
const showListView = hasActiveFilters || view === 'list';
return (
// We don't want to show the Loading... indicator for the whole page.
// We show separate indicators for Grafana-managed and Cloud rules
<AlertingPageWrapper navId="alert-list" isLoading={false} actions={null}>
<RulesFilter onClear={() => {}} />
{showListView ? <FilterView filterState={filterState} /> : <GroupedView />}
</AlertingPageWrapper>
);
},
{ style: 'page' }
);
return (
<>
<RulesFilter onClear={() => {}} />
{showListView ? <FilterView filterState={filterState} /> : <GroupedView />}
</>
);
}
export default RuleList;
export default function RuleListPage() {
return (
<AlertingPageWrapper navId="alert-list" isLoading={false} actions={null}>
<RuleList />
</AlertingPageWrapper>
);
}

View File

@ -0,0 +1,25 @@
import { ComponentType } from 'react';
import { ErrorBoundaryAlertProps, withErrorBoundary } from '@grafana/ui';
import { logError } from './Analytics';
/**
* HOC for wrapping alerting page in an error boundary.
* It provides alerting-specific error handling.
*
* @param Component - the react component to wrap in error boundary
* @param errorBoundaryProps - error boundary options
*
* @public
*/
export function withPageErrorBoundary<P extends {} = {}>(
Component: ComponentType<P>,
errorBoundaryProps: Omit<ErrorBoundaryAlertProps, 'children' | 'errorLogger' | 'style'> = {}
): ComponentType<P> {
return withErrorBoundary(Component, {
...errorBoundaryProps,
style: 'page',
errorLogger: logError,
});
}