diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx index 8430a85d0b0..ee8fe2bbb17 100644 --- a/public/app/features/alerting/routes.tsx +++ b/public/app/features/alerting/routes.tsx @@ -3,10 +3,7 @@ import { config } from 'app/core/config'; import { GrafanaRouteComponent, RouteDescriptor } from 'app/core/navigation/types'; import { AccessControlAction } from 'app/types'; -import { - PERMISSIONS_CONTACT_POINTS, - PERMISSIONS_CONTACT_POINTS_MODIFY, -} from './unified/components/contact-points/permissions'; +import { PERMISSIONS_CONTACT_POINTS } from './unified/components/contact-points/permissions'; import { evaluateAccess } from './unified/utils/access-control'; export function getAlertingRoutes(cfg = config): RouteDescriptor[] { @@ -104,7 +101,43 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] { ...PERMISSIONS_CONTACT_POINTS, ]), component: importAlertingComponent( - () => import(/* webpackChunkName: "NotificationsListPage" */ 'app/features/alerting/unified/Receivers') + () => + import( + /* webpackChunkName: "ContactPoints" */ 'app/features/alerting/unified/components/contact-points/ContactPoints' + ) + ), + }, + { + path: '/alerting/notifications/receivers/new', + roles: evaluateAccess([ + AccessControlAction.AlertingNotificationsRead, + AccessControlAction.AlertingNotificationsExternalRead, + ...PERMISSIONS_CONTACT_POINTS, + ]), + component: importAlertingComponent( + () => + import( + /* webpackChunkName: "NewReceiverView" */ 'app/features/alerting/unified/components/receivers/NewReceiverView' + ) + ), + }, + { + path: '/alerting/notifications/receivers/:name/edit', + roles: evaluateAccess([ + AccessControlAction.AlertingNotificationsWrite, + AccessControlAction.AlertingNotificationsExternalWrite, + AccessControlAction.AlertingNotificationsRead, + AccessControlAction.AlertingNotificationsExternalRead, + // We check any contact point permission here because a user without edit permissions + // still has to be able to visit the "edit" page, because we don't have a separate view for edit vs view + // (we just disable the form instead) + ...PERMISSIONS_CONTACT_POINTS, + ]), + component: importAlertingComponent( + () => + import( + /* webpackChunkName: "EditContactPoint" */ 'app/features/alerting/unified/components/contact-points/EditContactPoint' + ) ), }, { @@ -118,60 +151,16 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] { ), }, { - path: '/alerting/notifications/:type/new', - roles: evaluateAccess([ - AccessControlAction.AlertingNotificationsWrite, - AccessControlAction.AlertingNotificationsExternalWrite, - ...PERMISSIONS_CONTACT_POINTS_MODIFY, - ]), - component: importAlertingComponent( - () => import(/* webpackChunkName: "NotificationsListPage" */ 'app/features/alerting/unified/Receivers') - ), - }, - { - path: '/alerting/notifications/receivers/:id/edit', - roles: evaluateAccess([ - AccessControlAction.AlertingNotificationsWrite, - AccessControlAction.AlertingNotificationsExternalWrite, - AccessControlAction.AlertingNotificationsRead, - AccessControlAction.AlertingNotificationsExternalRead, - // We check any contact point permission here because a user without edit permissions - // still has to be able to visit the "edit" page, because we don't have a separate view for edit vs view - // (we just disable the form instead) - ...PERMISSIONS_CONTACT_POINTS, - ]), - component: importAlertingComponent( - () => import(/* webpackChunkName: "NotificationsListPage" */ 'app/features/alerting/unified/Receivers') - ), - }, - { - path: '/alerting/notifications/:type/:id/edit', + path: '/alerting/notifications/global-config', roles: evaluateAccess([ AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite, ]), component: importAlertingComponent( - () => import(/* webpackChunkName: "NotificationsListPage" */ 'app/features/alerting/unified/Receivers') - ), - }, - { - path: '/alerting/notifications/:type/:id/duplicate', - roles: evaluateAccess([ - AccessControlAction.AlertingNotificationsWrite, - AccessControlAction.AlertingNotificationsExternalWrite, - ]), - component: importAlertingComponent( - () => import(/* webpackChunkName: "NotificationsListPage" */ 'app/features/alerting/unified/Receivers') - ), - }, - { - path: '/alerting/notifications/:type', - roles: evaluateAccess([ - AccessControlAction.AlertingNotificationsWrite, - AccessControlAction.AlertingNotificationsExternalWrite, - ]), - component: importAlertingComponent( - () => import(/* webpackChunkName: "NotificationsListPage" */ 'app/features/alerting/unified/Receivers') + () => + import( + /* webpackChunkName: "GlobalConfig" */ 'app/features/alerting/unified/components/contact-points/components/GlobalConfig' + ) ), }, { diff --git a/public/app/features/alerting/unified/Receivers.test.tsx b/public/app/features/alerting/unified/Receivers.test.tsx index c7619e39f18..6a4d4227e6f 100644 --- a/public/app/features/alerting/unified/Receivers.test.tsx +++ b/public/app/features/alerting/unified/Receivers.test.tsx @@ -1,3 +1,4 @@ +import { Route, Routes } from 'react-router-dom-v5-compat'; import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; import { render, screen, waitFor, userEvent } from 'test/test-utils'; @@ -11,9 +12,10 @@ import { grantUserPermissions } from 'app/features/alerting/unified/mocks'; import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; import { AccessControlAction } from 'app/types'; -import ContactPoints from './Receivers'; - import 'core-js/stable/structured-clone'; +import ContactPoints from './components/contact-points/ContactPoints'; +import EditContactPoint from './components/contact-points/EditContactPoint'; +import NewReceiverView from './components/receivers/NewReceiverView'; const server = setupMswServer(); @@ -28,6 +30,21 @@ const saveContactPoint = async () => { return user.click(await screen.findByRole('button', { name: /save contact point/i })); }; +const setup = (location: string) => { + return render( + + } /> + } /> + } /> + , + { + historyOptions: { + initialEntries: [location], + }, + } + ); +}; + beforeEach(() => { grantUserPermissions([ AccessControlAction.AlertingNotificationsRead, @@ -41,18 +58,7 @@ beforeEach(() => { }); it('can save a contact point with a select dropdown', async () => { - const user = userEvent.setup(); - - render(, { - historyOptions: { - initialEntries: [ - { - pathname: `/alerting/notifications/receivers/new`, - search: `?alertmanager=${PROVISIONED_MIMIR_ALERTMANAGER_UID}`, - }, - ], - }, - }); + const { user } = setup(`/alerting/notifications/receivers/new?alertmanager=${PROVISIONED_MIMIR_ALERTMANAGER_UID}`); // Fill out contact point name const contactPointName = await screen.findByPlaceholderText(/name/i); @@ -75,16 +81,7 @@ it('can save a contact point with a select dropdown', async () => { }); it('can save existing Telegram contact point', async () => { - render(, { - historyOptions: { - initialEntries: [ - { - pathname: `/alerting/notifications/receivers/Telegram/edit`, - search: `?alertmanager=${PROVISIONED_MIMIR_ALERTMANAGER_UID}`, - }, - ], - }, - }); + setup(`/alerting/notifications/receivers/Telegram/edit?alertmanager=${PROVISIONED_MIMIR_ALERTMANAGER_UID}`); // Here, we're implicitly testing that our parsing of an existing Telegram integration works correctly // Our mock server will reject a request if we've sent the Chat ID as `0`, diff --git a/public/app/features/alerting/unified/Receivers.tsx b/public/app/features/alerting/unified/Receivers.tsx deleted file mode 100644 index 7b6e3b87052..00000000000 --- a/public/app/features/alerting/unified/Receivers.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Route, Switch } from 'react-router-dom'; - -import { withErrorBoundary } from '@grafana/ui'; -import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; - -import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper'; - -const ContactPointsV2 = SafeDynamicImport(() => import('./components/contact-points/ContactPoints')); -const EditContactPoint = SafeDynamicImport(() => import('./components/contact-points/EditContactPoint')); -const NewReceiverView = SafeDynamicImport(() => import('./components/receivers/NewReceiverView')); -const GlobalConfig = SafeDynamicImport(() => import('./components/contact-points/components/GlobalConfig')); - -const ContactPoints = (): JSX.Element => ( - - - - - - - - -); - -export default withErrorBoundary(ContactPoints, { style: 'page' }); diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx index cf5417fc4ac..76d8b271183 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx @@ -19,7 +19,7 @@ import { setupDataSources } from '../../testSetup/datasources'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { ContactPoint } from './ContactPoint'; -import ContactPointsPageContents from './ContactPoints'; +import { ContactPointsPageContents } from './ContactPoints'; import setupMimirFlavoredServer, { MIMIR_DATASOURCE_UID } from './__mocks__/mimirFlavoredServer'; import setupVanillaAlertmanagerFlavoredServer, { VANILLA_ALERTMANAGER_DATASOURCE_UID, diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx index 17c4f58cd9b..7f89081d217 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx @@ -12,6 +12,7 @@ import { TabContent, TabsBar, Text, + withErrorBoundary, } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { t, Trans } from 'app/core/internationalization'; @@ -24,6 +25,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 { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { GrafanaAlertmanagerDeliveryWarning } from '../GrafanaAlertmanagerDeliveryWarning'; import { ContactPoint } from './ContactPoint'; @@ -179,7 +181,7 @@ const useTabQueryParam = () => { return [param, setParam] as const; }; -const ContactPointsPageContents = () => { +export const ContactPointsPageContents = () => { const { selectedAlertmanager } = useAlertmanager(); const [activeTab, setActiveTab] = useTabQueryParam(); @@ -242,4 +244,12 @@ const ContactPointsList = ({ contactPoints, search, pageSize = DEFAULT_PAGE_SIZE ); }; -export default ContactPointsPageContents; +function ContactPointsPage() { + return ( + + + + ); +} + +export default withErrorBoundary(ContactPointsPage, { style: 'page' }); diff --git a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx index f85c3eeeef0..96a00fc1d33 100644 --- a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx +++ b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx @@ -1,18 +1,18 @@ -import { RouteChildrenProps } from 'react-router-dom'; +import { useParams } from 'react-router-dom-v5-compat'; -import { Alert, LoadingPlaceholder } from '@grafana/ui'; +import { Alert, LoadingPlaceholder, withErrorBoundary } 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 { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { EditReceiverView } from '../receivers/EditReceiverView'; -type Props = RouteChildrenProps<{ name: string }>; - -const EditContactPoint = ({ match }: Props) => { +const EditContactPoint = () => { const { selectedAlertmanager } = useAlertmanager(); + const { name = '' } = useParams(); - const contactPointName = decodeURIComponent(match?.params.name!); + const contactPointName = decodeURIComponent(name); const { isLoading, error, @@ -42,4 +42,12 @@ const EditContactPoint = ({ match }: Props) => { return ; }; -export default EditContactPoint; +function EditContactPointPage() { + return ( + + + + ); +} + +export default withErrorBoundary(EditContactPointPage, { style: 'page' }); diff --git a/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx b/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx index da56a3f5148..e5514b01d66 100644 --- a/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx +++ b/public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx @@ -1,7 +1,8 @@ -import { Alert } from '@grafana/ui'; +import { Alert, withErrorBoundary } from '@grafana/ui'; import { useAlertmanagerConfig } from '../../../hooks/useAlertmanagerConfig'; import { useAlertmanager } from '../../../state/AlertmanagerContext'; +import { AlertmanagerPageWrapper } from '../../AlertingPageWrapper'; import { GlobalConfigForm } from '../../receivers/GlobalConfigForm'; const NewMessageTemplate = () => { @@ -27,4 +28,12 @@ const NewMessageTemplate = () => { return ; }; -export default NewMessageTemplate; +function NewMessageTemplatePage() { + return ( + + + + ); +} + +export default withErrorBoundary(NewMessageTemplatePage, { style: 'page' }); diff --git a/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx b/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx index c84a9492d26..4f2a3322da4 100644 --- a/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx +++ b/public/app/features/alerting/unified/components/receivers/NewReceiverView.tsx @@ -1,6 +1,8 @@ +import { withErrorBoundary } from '@grafana/ui'; import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; +import { AlertmanagerPageWrapper } from '../AlertingPageWrapper'; import { CloudReceiverForm } from './form/CloudReceiverForm'; import { GrafanaReceiverForm } from './form/GrafanaReceiverForm'; @@ -14,4 +16,12 @@ const NewReceiverView = () => { } }; -export default NewReceiverView; +function NewReceiverViewPage() { + return ( + + + + ); +} + +export default withErrorBoundary(NewReceiverViewPage, { style: 'page' });