mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Add active maintenance window callout to the Rules Management page (#155386)
**Addresses:** https://github.com/elastic/kibana/issues/155099 **Documentation issue:** https://github.com/elastic/security-docs/issues/3181 ## Summary Adds a Maintenance Window callout to the Rules Management page. This callout is only displayed when a maintenance window is running. <img width="1260" alt="Screenshot 2023-04-21 at 13 24 11" src="https://user-images.githubusercontent.com/15949146/233624339-9c9b6e3e-9e5e-424d-9d19-9cd7d4e92259.png"> ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) issue created: https://github.com/elastic/security-docs/issues/3181 - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Georgii Gorbachev <banderror@gmail.com>
This commit is contained in:
parent
a34dd8c260
commit
675ed0eee2
18 changed files with 359 additions and 23 deletions
|
@ -58,6 +58,12 @@ export const LEGACY_BASE_ALERT_API_PATH = '/api/alerts';
|
|||
export const BASE_ALERTING_API_PATH = '/api/alerting';
|
||||
export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting';
|
||||
export const INTERNAL_ALERTING_API_FIND_RULES_PATH = `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_find`;
|
||||
|
||||
export const INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH =
|
||||
`${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window` as const;
|
||||
export const INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH =
|
||||
`${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/_active` as const;
|
||||
|
||||
export const ALERTS_FEATURE_ID = 'alerts';
|
||||
export const MONITORING_HISTORY_LIMIT = 200;
|
||||
export const ENABLE_MAINTENANCE_WINDOWS = false;
|
||||
|
|
|
@ -46,6 +46,11 @@ export type MaintenanceWindow = MaintenanceWindowSOAttributes & {
|
|||
id: string;
|
||||
};
|
||||
|
||||
export type MaintenanceWindowCreateBody = Omit<
|
||||
MaintenanceWindowSOProperties,
|
||||
'events' | 'expirationDate' | 'enabled' | 'archived'
|
||||
>;
|
||||
|
||||
export interface MaintenanceWindowClientContext {
|
||||
getModificationMetadata: () => Promise<MaintenanceWindowModificationMetadata>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
import { IRouter } from '@kbn/core/server';
|
||||
import { ILicenseState } from '../../lib';
|
||||
import { verifyAccessAndContext, rewriteMaintenanceWindowRes } from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH,
|
||||
} from '../../types';
|
||||
import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
export const activeMaintenanceWindowsRoute = (
|
||||
|
@ -17,7 +20,7 @@ export const activeMaintenanceWindowsRoute = (
|
|||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/_active`,
|
||||
path: INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH,
|
||||
validate: {},
|
||||
options: {
|
||||
tags: [`access:${MAINTENANCE_WINDOW_API_PRIVILEGES.READ_MAINTENANCE_WINDOW}`],
|
||||
|
|
|
@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState } from '../../lib';
|
||||
import { verifyAccessAndContext, rewritePartialMaintenanceBodyRes } from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
} from '../../types';
|
||||
import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
|
@ -26,7 +29,7 @@ export const archiveMaintenanceWindowRoute = (
|
|||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}/_archive`,
|
||||
path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}/_archive`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
body: bodySchema,
|
||||
|
|
|
@ -14,8 +14,11 @@ import {
|
|||
RewriteRequestCase,
|
||||
rewriteMaintenanceWindowRes,
|
||||
} from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import { MaintenanceWindowSOProperties, MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
} from '../../types';
|
||||
import { MaintenanceWindowCreateBody, MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
const bodySchema = schema.object({
|
||||
title: schema.string(),
|
||||
|
@ -23,11 +26,6 @@ const bodySchema = schema.object({
|
|||
r_rule: rRuleSchema,
|
||||
});
|
||||
|
||||
type MaintenanceWindowCreateBody = Omit<
|
||||
MaintenanceWindowSOProperties,
|
||||
'events' | 'expirationDate' | 'enabled' | 'archived'
|
||||
>;
|
||||
|
||||
export const rewriteQueryReq: RewriteRequestCase<MaintenanceWindowCreateBody> = ({
|
||||
r_rule: rRule,
|
||||
...rest
|
||||
|
@ -42,7 +40,7 @@ export const createMaintenanceWindowRoute = (
|
|||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window`,
|
||||
path: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
validate: {
|
||||
body: bodySchema,
|
||||
},
|
||||
|
|
|
@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState } from '../../lib';
|
||||
import { verifyAccessAndContext } from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
} from '../../types';
|
||||
import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
|
@ -22,7 +25,7 @@ export const deleteMaintenanceWindowRoute = (
|
|||
) => {
|
||||
router.delete(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}`,
|
||||
path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
import { IRouter } from '@kbn/core/server';
|
||||
import { ILicenseState } from '../../lib';
|
||||
import { verifyAccessAndContext, rewriteMaintenanceWindowRes } from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
} from '../../types';
|
||||
import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
export const findMaintenanceWindowsRoute = (
|
||||
|
@ -17,7 +20,7 @@ export const findMaintenanceWindowsRoute = (
|
|||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/_find`,
|
||||
path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/_find`,
|
||||
validate: {},
|
||||
options: {
|
||||
tags: [`access:${MAINTENANCE_WINDOW_API_PRIVILEGES.READ_MAINTENANCE_WINDOW}`],
|
||||
|
|
|
@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState } from '../../lib';
|
||||
import { verifyAccessAndContext, rewritePartialMaintenanceBodyRes } from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
} from '../../types';
|
||||
import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
|
@ -22,7 +25,7 @@ export const finishMaintenanceWindowRoute = (
|
|||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}/_finish`,
|
||||
path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}/_finish`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
|
|
|
@ -9,7 +9,10 @@ import { IRouter } from '@kbn/core/server';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { ILicenseState } from '../../lib';
|
||||
import { verifyAccessAndContext, rewriteMaintenanceWindowRes } from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
} from '../../types';
|
||||
import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
|
@ -22,7 +25,7 @@ export const getMaintenanceWindowRoute = (
|
|||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}`,
|
||||
path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}`,
|
||||
validate: {
|
||||
params: paramSchema,
|
||||
},
|
||||
|
|
|
@ -14,7 +14,10 @@ import {
|
|||
RewriteRequestCase,
|
||||
rewritePartialMaintenanceBodyRes,
|
||||
} from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import {
|
||||
AlertingRequestHandlerContext,
|
||||
INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
} from '../../types';
|
||||
import { MaintenanceWindowSOProperties, MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
|
@ -49,7 +52,7 @@ export const updateMaintenanceWindowRoute = (
|
|||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/{id}`,
|
||||
path: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/{id}`,
|
||||
validate: {
|
||||
body: bodySchema,
|
||||
params: paramSchema,
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH } from '@kbn/alerting-plugin/common';
|
||||
import type { MaintenanceWindowCreateBody } from '@kbn/alerting-plugin/common';
|
||||
import type { AsApiContract } from '@kbn/alerting-plugin/server/routes/lib';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import { login, visit } from '../../tasks/login';
|
||||
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Maintenance window callout on Rule Management page', () => {
|
||||
let maintenanceWindowId = '';
|
||||
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
|
||||
const body: AsApiContract<MaintenanceWindowCreateBody> = {
|
||||
title: 'My maintenance window',
|
||||
duration: 60000, // 1 minute
|
||||
r_rule: {
|
||||
dtstart: new Date().toISOString(),
|
||||
tzid: 'Europe/Amsterdam',
|
||||
freq: 0,
|
||||
count: 1,
|
||||
},
|
||||
};
|
||||
|
||||
// Create a test maintenance window
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
body,
|
||||
}).then((response) => {
|
||||
maintenanceWindowId = response.body.id;
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// Delete a test maintenance window
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `${INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH}/${maintenanceWindowId}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
});
|
||||
});
|
||||
|
||||
it('Displays the callout when there are running maintenance windows', () => {
|
||||
visit(DETECTIONS_RULE_MANAGEMENT_URL);
|
||||
|
||||
cy.contains('A maintenance window is currently running');
|
||||
});
|
||||
});
|
|
@ -28,6 +28,7 @@
|
|||
"force": true
|
||||
},
|
||||
"@kbn/rison",
|
||||
"@kbn/datemath"
|
||||
"@kbn/datemath",
|
||||
"@kbn/alerting-plugin"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { MaintenanceWindow } from '@kbn/alerting-plugin/common/maintenance_window';
|
||||
import { INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH } from '@kbn/alerting-plugin/common';
|
||||
import { KibanaServices } from '../../../../common/lib/kibana';
|
||||
|
||||
export const fetchActiveMaintenanceWindows = async (
|
||||
signal?: AbortSignal
|
||||
): Promise<MaintenanceWindow[]> =>
|
||||
KibanaServices.get().http.fetch(INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH, {
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, waitFor, cleanup } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { MaintenanceWindowStatus } from '@kbn/alerting-plugin/common';
|
||||
import type { MaintenanceWindow } from '@kbn/alerting-plugin/common';
|
||||
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
||||
import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock';
|
||||
import { MaintenanceWindowCallout } from './maintenance_window_callout';
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
import { fetchActiveMaintenanceWindows } from './api';
|
||||
|
||||
jest.mock('../../../../common/hooks/use_app_toasts');
|
||||
|
||||
jest.mock('./api', () => ({
|
||||
fetchActiveMaintenanceWindows: jest.fn(() => Promise.resolve([])),
|
||||
}));
|
||||
|
||||
const RUNNING_MAINTENANCE_WINDOW_1: Partial<MaintenanceWindow> = {
|
||||
title: 'Maintenance window 1',
|
||||
id: '63057284-ac31-42ba-fe22-adfe9732e5ae',
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
events: [{ gte: '2023-04-20T16:27:30.753Z', lte: '2023-04-20T16:57:30.753Z' }],
|
||||
};
|
||||
|
||||
const RUNNING_MAINTENANCE_WINDOW_2: Partial<MaintenanceWindow> = {
|
||||
title: 'Maintenance window 2',
|
||||
id: '45894340-df98-11ed-ac81-bfcb4982b4fd',
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
events: [{ gte: '2023-04-20T16:47:42.871Z', lte: '2023-04-20T17:11:32.192Z' }],
|
||||
};
|
||||
|
||||
const UPCOMING_MAINTENANCE_WINDOW: Partial<MaintenanceWindow> = {
|
||||
title: 'Upcoming maintenance window',
|
||||
id: '5eafe070-e030-11ed-ac81-bfcb4982b4fd',
|
||||
status: MaintenanceWindowStatus.Upcoming,
|
||||
events: [
|
||||
{ gte: '2023-04-21T10:36:14.028Z', lte: '2023-04-21T10:37:00.000Z' },
|
||||
{ gte: '2023-04-28T10:36:14.028Z', lte: '2023-04-28T10:37:00.000Z' },
|
||||
],
|
||||
};
|
||||
|
||||
describe('MaintenanceWindowCallout', () => {
|
||||
let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
appToastsMock = useAppToastsMock.create();
|
||||
(useAppToasts as jest.Mock).mockReturnValue(appToastsMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should be visible if currently there is at least one "running" maintenance window', async () => {
|
||||
(fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([RUNNING_MAINTENANCE_WINDOW_1]);
|
||||
|
||||
const { findByText } = render(<MaintenanceWindowCallout />, { wrapper: TestProviders });
|
||||
|
||||
expect(await findByText('A maintenance window is currently running')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should be visible if currently there are multiple "running" maintenance windows', async () => {
|
||||
(fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([
|
||||
RUNNING_MAINTENANCE_WINDOW_1,
|
||||
RUNNING_MAINTENANCE_WINDOW_2,
|
||||
]);
|
||||
|
||||
const { findAllByText } = render(<MaintenanceWindowCallout />, { wrapper: TestProviders });
|
||||
|
||||
expect(await findAllByText('A maintenance window is currently running')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should NOT be visible if currently there are no active (running or upcoming) maintenance windows', async () => {
|
||||
(fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([]);
|
||||
|
||||
const { container } = render(<MaintenanceWindowCallout />, { wrapper: TestProviders });
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should NOT be visible if currently there are no "running" maintenance windows', async () => {
|
||||
(fetchActiveMaintenanceWindows as jest.Mock).mockResolvedValue([UPCOMING_MAINTENANCE_WINDOW]);
|
||||
|
||||
const { container } = render(<MaintenanceWindowCallout />, { wrapper: TestProviders });
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should see an error toast if there was an error while fetching maintenance windows', async () => {
|
||||
const createReactQueryWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
// Turn retries off, otherwise we won't be able to test errors
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
logger: {
|
||||
// Turn network error logging off, so we don't log the failed request to the console
|
||||
error: () => {},
|
||||
// eslint-disable-next-line no-console
|
||||
log: console.log,
|
||||
// eslint-disable-next-line no-console
|
||||
warn: console.warn,
|
||||
},
|
||||
});
|
||||
const wrapper: React.FC = ({ children }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
const mockError = new Error('Network error');
|
||||
(fetchActiveMaintenanceWindows as jest.Mock).mockRejectedValue(mockError);
|
||||
|
||||
render(<MaintenanceWindowCallout />, { wrapper: createReactQueryWrapper() });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appToastsMock.addError).toHaveBeenCalledTimes(1);
|
||||
expect(appToastsMock.addError).toHaveBeenCalledWith(mockError, {
|
||||
title: 'Failed to check if any maintenance window is currently running',
|
||||
toastMessage: "Notification actions won't run while a maintenance window is running.",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { MaintenanceWindowStatus } from '@kbn/alerting-plugin/common';
|
||||
import { useFetchActiveMaintenanceWindows } from './use_fetch_active_maintenance_windows';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export function MaintenanceWindowCallout(): JSX.Element | null {
|
||||
const { data } = useFetchActiveMaintenanceWindows();
|
||||
const activeMaintenanceWindows = data || [];
|
||||
|
||||
if (activeMaintenanceWindows.some(({ status }) => status === MaintenanceWindowStatus.Running)) {
|
||||
return (
|
||||
<EuiCallOut title={i18n.MAINTENANCE_WINDOW_RUNNING} color="warning" iconType="iInCircle">
|
||||
{i18n.MAINTENANCE_WINDOW_RUNNING_DESCRIPTION}
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const MAINTENANCE_WINDOW_RUNNING = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.maintenanceWindowActive',
|
||||
{
|
||||
defaultMessage: 'A maintenance window is currently running',
|
||||
}
|
||||
);
|
||||
|
||||
export const MAINTENANCE_WINDOW_RUNNING_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.maintenanceWindowActiveDescription',
|
||||
{
|
||||
defaultMessage: "Notification actions won't run while a maintenance window is running.",
|
||||
}
|
||||
);
|
||||
|
||||
export const FETCH_ERROR = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.fetchError',
|
||||
{
|
||||
defaultMessage: 'Failed to check if any maintenance window is currently running',
|
||||
}
|
||||
);
|
||||
|
||||
export const FETCH_ERROR_DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleManagementUi.maintenanceWindowCallout.fetchErrorDescription',
|
||||
{
|
||||
defaultMessage: "Notification actions won't run while a maintenance window is running.",
|
||||
}
|
||||
);
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH } from '@kbn/alerting-plugin/common';
|
||||
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
||||
import * as i18n from './translations';
|
||||
import { fetchActiveMaintenanceWindows } from './api';
|
||||
|
||||
export const useFetchActiveMaintenanceWindows = () => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
return useQuery(
|
||||
['GET', INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH],
|
||||
({ signal }) => fetchActiveMaintenanceWindows(signal),
|
||||
{
|
||||
refetchInterval: 60000,
|
||||
onError: (error) => {
|
||||
addError(error, { title: i18n.FETCH_ERROR, toastMessage: i18n.FETCH_ERROR_DESCRIPTION });
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
|
@ -42,6 +42,8 @@ import { RulesTableContextProvider } from '../../components/rules_table/rules_ta
|
|||
import * as i18n from '../../../../detections/pages/detection_engine/rules/translations';
|
||||
import { useInvalidateFetchRuleManagementFiltersQuery } from '../../../rule_management/api/hooks/use_fetch_rule_management_filters_query';
|
||||
|
||||
import { MaintenanceWindowCallout } from '../../components/maintenance_window_callout/maintenance_window_callout';
|
||||
|
||||
const RulesPageComponent: React.FC = () => {
|
||||
const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState();
|
||||
const [isValueListFlyoutVisible, showValueListFlyout, hideValueListFlyout] = useBoolState();
|
||||
|
@ -158,6 +160,7 @@ const RulesPageComponent: React.FC = () => {
|
|||
prePackagedTimelineStatus === 'timelineNeedUpdate') && (
|
||||
<UpdatePrePackagedRulesCallOut data-test-subj="update-callout-button" />
|
||||
)}
|
||||
<MaintenanceWindowCallout />
|
||||
<AllRules data-test-subj="all-rules" />
|
||||
</SecuritySolutionPageWrapper>
|
||||
</RulesTableContextProvider>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue