mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Hoist routes for mute timings (#94201)
This commit is contained in:
parent
87fd36aecf
commit
4224d05934
@ -48,7 +48,10 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
|
|||||||
AccessControlAction.AlertingNotificationsExternalWrite,
|
AccessControlAction.AlertingNotificationsExternalWrite,
|
||||||
]),
|
]),
|
||||||
component: importAlertingComponent(
|
component: importAlertingComponent(
|
||||||
() => import(/* webpackChunkName: "MuteTimings" */ 'app/features/alerting/unified/MuteTimings')
|
() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "NewMuteTiming" */ 'app/features/alerting/unified/components/mute-timings/NewMuteTiming'
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -58,7 +61,10 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
|
|||||||
AccessControlAction.AlertingNotificationsExternalWrite,
|
AccessControlAction.AlertingNotificationsExternalWrite,
|
||||||
]),
|
]),
|
||||||
component: importAlertingComponent(
|
component: importAlertingComponent(
|
||||||
() => import(/* webpackChunkName: "MuteTimings" */ 'app/features/alerting/unified/MuteTimings')
|
() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "EditMuteTiming" */ 'app/features/alerting/unified/components/mute-timings/EditMuteTiming'
|
||||||
|
)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { InitialEntry } from 'history';
|
import { InitialEntry } from 'history';
|
||||||
import { last } from 'lodash';
|
import { last } from 'lodash';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
import { Route } from 'react-router';
|
import { Route } from 'react-router';
|
||||||
import { render, within, userEvent, screen } from 'test/test-utils';
|
import { render, screen, userEvent, within } from 'test/test-utils';
|
||||||
import { byRole, byTestId, byText } from 'testing-library-selector';
|
import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||||
|
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
@ -13,27 +14,28 @@ import {
|
|||||||
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
||||||
import { MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID } from 'app/features/alerting/unified/mocks/server/handlers/datasources';
|
import { MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID } from 'app/features/alerting/unified/mocks/server/handlers/datasources';
|
||||||
import {
|
import {
|
||||||
TIME_INTERVAL_NAME_HAPPY_PATH,
|
|
||||||
TIME_INTERVAL_NAME_FILE_PROVISIONED,
|
TIME_INTERVAL_NAME_FILE_PROVISIONED,
|
||||||
|
TIME_INTERVAL_NAME_HAPPY_PATH,
|
||||||
} from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s';
|
} from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s';
|
||||||
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
|
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
|
||||||
import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
|
|
||||||
import MuteTimings from './MuteTimings';
|
import EditMuteTimingPage from './components/mute-timings/EditMuteTiming';
|
||||||
|
import NewMuteTimingPage from './components/mute-timings/NewMuteTiming';
|
||||||
import { grantUserPermissions, mockDataSource } from './mocks';
|
import { grantUserPermissions, mockDataSource } from './mocks';
|
||||||
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||||
|
|
||||||
const indexPageText = 'redirected routes page';
|
const indexPageText = 'redirected routes page';
|
||||||
const renderMuteTimings = (location: InitialEntry = '/alerting/routes/mute-timing/new') => {
|
const renderMuteTimings = (component: ReactNode, location?: InitialEntry) => {
|
||||||
render(
|
render(
|
||||||
<>
|
<>
|
||||||
<Route path="/alerting/routes" exact>
|
<Route path="/alerting/routes" exact>
|
||||||
{indexPageText}
|
{indexPageText}
|
||||||
</Route>
|
</Route>
|
||||||
<MuteTimings />
|
{component}
|
||||||
</>,
|
</>,
|
||||||
{ historyOptions: { initialEntries: [location] } }
|
{ historyOptions: location ? { initialEntries: [location] } : undefined }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -210,9 +212,9 @@ describe('Mute timings', () => {
|
|||||||
|
|
||||||
it('creates a new mute timing, with mute_time_intervals in config', async () => {
|
it('creates a new mute timing, with mute_time_intervals in config', async () => {
|
||||||
const capture = captureRequests();
|
const capture = captureRequests();
|
||||||
renderMuteTimings();
|
renderMuteTimings(<NewMuteTimingPage />);
|
||||||
|
|
||||||
await screen.findByText(/create mute timing/i);
|
await screen.findByText(/add mute timing/i);
|
||||||
|
|
||||||
await fillOutForm({
|
await fillOutForm({
|
||||||
name: 'maintenance period',
|
name: 'maintenance period',
|
||||||
@ -237,8 +239,7 @@ describe('Mute timings', () => {
|
|||||||
it('creates a new mute timing, with time_intervals in config', async () => {
|
it('creates a new mute timing, with time_intervals in config', async () => {
|
||||||
const capture = captureRequests();
|
const capture = captureRequests();
|
||||||
setAlertmanagerConfig(defaultConfigWithNewTimeIntervalsField);
|
setAlertmanagerConfig(defaultConfigWithNewTimeIntervalsField);
|
||||||
renderMuteTimings({
|
renderMuteTimings(<NewMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/new',
|
|
||||||
search: `?alertmanager=${alertmanagerName}`,
|
search: `?alertmanager=${alertmanagerName}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -262,8 +263,7 @@ describe('Mute timings', () => {
|
|||||||
|
|
||||||
it('creates a new mute timing, with time_intervals and mute_time_intervals in config', async () => {
|
it('creates a new mute timing, with time_intervals and mute_time_intervals in config', async () => {
|
||||||
setGrafanaAlertmanagerConfig(defaultConfigWithBothTimeIntervalsField);
|
setGrafanaAlertmanagerConfig(defaultConfigWithBothTimeIntervalsField);
|
||||||
renderMuteTimings({
|
renderMuteTimings(<NewMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/new',
|
|
||||||
search: `?alertmanager=${alertmanagerName}`,
|
search: `?alertmanager=${alertmanagerName}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -284,8 +284,7 @@ describe('Mute timings', () => {
|
|||||||
it('prepopulates the form when editing a mute timing', async () => {
|
it('prepopulates the form when editing a mute timing', async () => {
|
||||||
const capture = captureRequests();
|
const capture = captureRequests();
|
||||||
|
|
||||||
renderMuteTimings({
|
renderMuteTimings(<EditMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/edit',
|
|
||||||
search: `?muteName=${encodeURIComponent(muteTimeInterval.name)}`,
|
search: `?muteName=${encodeURIComponent(muteTimeInterval.name)}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -325,7 +324,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('form is invalid with duplicate mute timing name', async () => {
|
it('form is invalid with duplicate mute timing name', async () => {
|
||||||
renderMuteTimings();
|
renderMuteTimings(<NewMuteTimingPage />);
|
||||||
|
|
||||||
await fillOutForm({ name: muteTimeInterval.name, days: '1' });
|
await fillOutForm({ name: muteTimeInterval.name, days: '1' });
|
||||||
|
|
||||||
@ -335,8 +334,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('replaces mute timings in routes when the mute timing name is changed', async () => {
|
it('replaces mute timings in routes when the mute timing name is changed', async () => {
|
||||||
renderMuteTimings({
|
renderMuteTimings(<EditMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/edit',
|
|
||||||
search: `?muteName=${encodeURIComponent(muteTimeInterval.name)}`,
|
search: `?muteName=${encodeURIComponent(muteTimeInterval.name)}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -351,8 +349,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows error when mute timing does not exist', async () => {
|
it('shows error when mute timing does not exist', async () => {
|
||||||
renderMuteTimings({
|
renderMuteTimings(<EditMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/edit',
|
|
||||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${'does not exist'}`,
|
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${'does not exist'}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -365,9 +362,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('allows creation of new mute timings', async () => {
|
it('allows creation of new mute timings', async () => {
|
||||||
renderMuteTimings({
|
renderMuteTimings(<NewMuteTimingPage />);
|
||||||
pathname: '/alerting/routes/mute-timing/new',
|
|
||||||
});
|
|
||||||
|
|
||||||
await fillOutForm({ name: 'a new mute timing' });
|
await fillOutForm({ name: 'a new mute timing' });
|
||||||
|
|
||||||
@ -376,8 +371,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows error when mute timing does not exist', async () => {
|
it('shows error when mute timing does not exist', async () => {
|
||||||
renderMuteTimings({
|
renderMuteTimings(<EditMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/edit',
|
|
||||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH + '_force_breakage'}`,
|
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH + '_force_breakage'}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -385,8 +379,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads edit form correctly and allows saving', async () => {
|
it('loads edit form correctly and allows saving', async () => {
|
||||||
renderMuteTimings({
|
renderMuteTimings(<EditMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/edit',
|
|
||||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH}`,
|
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -395,8 +388,7 @@ describe('Mute timings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads view form for provisioned interval', async () => {
|
it('loads view form for provisioned interval', async () => {
|
||||||
renderMuteTimings({
|
renderMuteTimings(<EditMuteTimingPage />, {
|
||||||
pathname: '/alerting/routes/mute-timing/edit',
|
|
||||||
search: `?muteName=${TIME_INTERVAL_NAME_FILE_PROVISIONED}`,
|
search: `?muteName=${TIME_INTERVAL_NAME_FILE_PROVISIONED}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
|
||||||
import { Navigate } from 'react-router-dom-v5-compat';
|
|
||||||
|
|
||||||
import { NavModelItem } from '@grafana/data';
|
|
||||||
import { useGetMuteTiming } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
|
|
||||||
import { useURLSearchParams } from 'app/features/alerting/unified/hooks/useURLSearchParams';
|
|
||||||
|
|
||||||
import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper';
|
|
||||||
import MuteTimingForm from './components/mute-timings/MuteTimingForm';
|
|
||||||
import { useAlertmanager } from './state/AlertmanagerContext';
|
|
||||||
|
|
||||||
const EditTimingRoute = () => {
|
|
||||||
const [queryParams] = useURLSearchParams();
|
|
||||||
const { selectedAlertmanager } = useAlertmanager();
|
|
||||||
const name = queryParams.get('muteName')!;
|
|
||||||
const {
|
|
||||||
isLoading,
|
|
||||||
data: timeInterval,
|
|
||||||
isError,
|
|
||||||
} = useGetMuteTiming({
|
|
||||||
alertmanager: selectedAlertmanager!,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return <Navigate replace to="/alerting/routes" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MuteTimingForm
|
|
||||||
editMode
|
|
||||||
loading={isLoading}
|
|
||||||
showError={isError}
|
|
||||||
muteTiming={timeInterval}
|
|
||||||
provisioned={timeInterval?.provisioned}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MuteTimings = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/alerting/routes/mute-timing/new">
|
|
||||||
<MuteTimingForm />
|
|
||||||
</Route>
|
|
||||||
<Route exact path="/alerting/routes/mute-timing/edit">
|
|
||||||
<EditTimingRoute />
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MuteTimingsPage = () => {
|
|
||||||
const pageNav = useMuteTimingNavData();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AlertmanagerPageWrapper navId="am-routes" pageNav={pageNav} accessType="notification">
|
|
||||||
<MuteTimings />
|
|
||||||
</AlertmanagerPageWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useMuteTimingNavData() {
|
|
||||||
const { isExact, path } = useRouteMatch();
|
|
||||||
const [pageNav, setPageNav] = useState<Pick<NavModelItem, 'id' | 'text' | 'icon'> | undefined>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (path === '/alerting/routes/mute-timing/new') {
|
|
||||||
setPageNav({
|
|
||||||
id: 'alert-policy-new',
|
|
||||||
text: 'Add mute timing',
|
|
||||||
});
|
|
||||||
} else if (path === '/alerting/routes/mute-timing/edit') {
|
|
||||||
setPageNav({
|
|
||||||
id: 'alert-policy-edit',
|
|
||||||
text: 'Edit mute timing',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [path, isExact]);
|
|
||||||
|
|
||||||
return pageNav;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MuteTimingsPage;
|
|
@ -0,0 +1,54 @@
|
|||||||
|
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 { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
|
||||||
|
|
||||||
|
import MuteTimingForm from './MuteTimingForm';
|
||||||
|
|
||||||
|
const EditTimingRoute = () => {
|
||||||
|
const [queryParams] = useURLSearchParams();
|
||||||
|
const { selectedAlertmanager } = useAlertmanager();
|
||||||
|
const name = queryParams.get('muteName')!;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
data: timeInterval,
|
||||||
|
isError,
|
||||||
|
} = useGetMuteTiming({
|
||||||
|
alertmanager: selectedAlertmanager!,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return <Navigate replace to="/alerting/routes" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MuteTimingForm
|
||||||
|
editMode
|
||||||
|
loading={isLoading}
|
||||||
|
showError={isError}
|
||||||
|
muteTiming={timeInterval}
|
||||||
|
provisioned={timeInterval?.provisioned}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditMuteTimingPage = () => (
|
||||||
|
<AlertmanagerPageWrapper
|
||||||
|
navId="am-routes"
|
||||||
|
pageNav={{
|
||||||
|
id: 'alert-policy-edit',
|
||||||
|
text: 'Edit mute timing',
|
||||||
|
}}
|
||||||
|
accessType="notification"
|
||||||
|
>
|
||||||
|
<EditTimingRoute />
|
||||||
|
</AlertmanagerPageWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default withErrorBoundary(EditMuteTimingPage, { style: 'page' });
|
@ -100,7 +100,7 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
|
|||||||
{provisioned && <ProvisioningAlert resource={ProvisionedResource.MuteTiming} />}
|
{provisioned && <ProvisioningAlert resource={ProvisionedResource.MuteTiming} />}
|
||||||
<FormProvider {...formApi}>
|
<FormProvider {...formApi}>
|
||||||
<form onSubmit={formApi.handleSubmit(onSubmit)} data-testid="mute-timing-form">
|
<form onSubmit={formApi.handleSubmit(onSubmit)} data-testid="mute-timing-form">
|
||||||
<FieldSet label={'Create mute timing'} disabled={provisioned || updating}>
|
<FieldSet disabled={provisioned || updating}>
|
||||||
<Field
|
<Field
|
||||||
required
|
required
|
||||||
label="Name"
|
label="Name"
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { withErrorBoundary } from '@grafana/ui';
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default withErrorBoundary(NewMuteTimingPage, { style: 'page' });
|
Loading…
Reference in New Issue
Block a user