From a34dd8c2608e7770629d2f5194a78371a6d2c268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Tue, 25 Apr 2023 01:12:32 +0200 Subject: [PATCH 01/47] [serverless-docker] Allow using SERVERLESS env var (#155670) --- .../docker_generator/resources/base/bin/kibana-docker | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index b08d26b8c657d9..3123aa7f391dfb 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -414,6 +414,7 @@ kibana_vars=( xpack.task_manager.event_loop_delay.monitor xpack.task_manager.event_loop_delay.warn_threshold xpack.uptime.index + serverless ) longopts='' From 675ed0eee2eb582321bfb9b379783c973a490bd2 Mon Sep 17 00:00:00 2001 From: Nikita Indik Date: Tue, 25 Apr 2023 01:22:56 +0200 Subject: [PATCH 02/47] [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. Screenshot 2023-04-21 at 13 24 11 ### 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 --- x-pack/plugins/alerting/common/index.ts | 6 + .../alerting/common/maintenance_window.ts | 5 + .../active_maintenance_windows.ts | 7 +- .../archive_maintenance_window.ts | 7 +- .../create_maintenance_window.ts | 14 +- .../delete_maintenance_window.ts | 7 +- .../find_maintenance_windows.ts | 7 +- .../finish_maintenance_window.ts | 7 +- .../get_maintenance_window.ts | 7 +- .../update_maintenance_window.ts | 7 +- .../detection_rules/maintenance_window.cy.ts | 58 ++++++++ .../security_solution/cypress/tsconfig.json | 3 +- .../maintenance_window_callout/api.ts | 18 +++ .../maintenance_window_callout.test.tsx | 136 ++++++++++++++++++ .../maintenance_window_callout.tsx | 27 ++++ .../translations.ts | 36 +++++ .../use_fetch_active_maintenance_windows.ts | 27 ++++ .../pages/rule_management/index.tsx | 3 + 18 files changed, 359 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 8b4c04d15cfc43..1fa0806effdef7 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -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; diff --git a/x-pack/plugins/alerting/common/maintenance_window.ts b/x-pack/plugins/alerting/common/maintenance_window.ts index 0392d0cdb3667c..e41140f8fc9187 100644 --- a/x-pack/plugins/alerting/common/maintenance_window.ts +++ b/x-pack/plugins/alerting/common/maintenance_window.ts @@ -46,6 +46,11 @@ export type MaintenanceWindow = MaintenanceWindowSOAttributes & { id: string; }; +export type MaintenanceWindowCreateBody = Omit< + MaintenanceWindowSOProperties, + 'events' | 'expirationDate' | 'enabled' | 'archived' +>; + export interface MaintenanceWindowClientContext { getModificationMetadata: () => Promise; savedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts index 706630edfbd4ad..8bd3f7d3e0b492 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/active_maintenance_windows.ts @@ -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}`], diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts index 123f374f79b057..e46bc07463e2f6 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/archive_maintenance_window.ts @@ -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, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts index a74147d15890ce..d26f8494e10611 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/create_maintenance_window.ts @@ -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 = ({ 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, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts index 2415dbe74b53dc..c9ea00ef170de8 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/delete_maintenance_window.ts @@ -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, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts index b581a011630a98..e9262b3e510791 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/find_maintenance_windows.ts @@ -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}`], diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts index 2cd5ff9ba09948..0cb663043d57ac 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/finish_maintenance_window.ts @@ -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, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts index dc01beeef148ab..b92281373817d1 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/get_maintenance_window.ts @@ -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, }, diff --git a/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts b/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts index 7778b4d6213592..5e636245871525 100644 --- a/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts +++ b/x-pack/plugins/alerting/server/routes/maintenance_window/update_maintenance_window.ts @@ -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, diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.ts new file mode 100644 index 00000000000000..af534d325ebca9 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/maintenance_window.cy.ts @@ -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 = { + 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'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/tsconfig.json b/x-pack/plugins/security_solution/cypress/tsconfig.json index 4af63c6d1b4062..4f7ce1b811f702 100644 --- a/x-pack/plugins/security_solution/cypress/tsconfig.json +++ b/x-pack/plugins/security_solution/cypress/tsconfig.json @@ -28,6 +28,7 @@ "force": true }, "@kbn/rison", - "@kbn/datemath" + "@kbn/datemath", + "@kbn/alerting-plugin" ] } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.ts new file mode 100644 index 00000000000000..9d3c28429df5b4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/api.ts @@ -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 => + KibanaServices.get().http.fetch(INTERNAL_ALERTING_API_GET_ACTIVE_MAINTENANCE_WINDOWS_PATH, { + method: 'GET', + signal, + }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx new file mode 100644 index 00000000000000..20a8e0d1f2f94a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.test.tsx @@ -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 = { + 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 = { + 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 = { + 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>; + + 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(, { 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(, { 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(, { 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(, { 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 }) => ( + {children} + ); + return wrapper; + }; + + const mockError = new Error('Network error'); + (fetchActiveMaintenanceWindows as jest.Mock).mockRejectedValue(mockError); + + render(, { 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.", + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx new file mode 100644 index 00000000000000..878347dc37c98e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/maintenance_window_callout.tsx @@ -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 ( + + {i18n.MAINTENANCE_WINDOW_RUNNING_DESCRIPTION} + + ); + } + + return null; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts new file mode 100644 index 00000000000000..21071aee618a16 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/translations.ts @@ -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.", + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts new file mode 100644 index 00000000000000..3603cafbda935a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/maintenance_window_callout/use_fetch_active_maintenance_windows.ts @@ -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 }); + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx index 95e0857874423b..03231f2030eb2b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx @@ -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') && ( )} + From 402085882773e450582e238bb04ede62807a5404 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Mon, 24 Apr 2023 18:54:41 -0700 Subject: [PATCH 03/47] [Shared UX] Clean up toolbar styles (#155667) ## Summary Closes #155668. This PR fixes a few minor visual buttons with the `Toolbar` component styles. #### Before Screenshot 2023-04-24 at 2 41 16 PM #### After Screenshot 2023-04-24 at 2 39 09 PM Changes: - apply same 8px gap between all toolbar buttons - remove shadow from icon button group buttons causing the double border between buttons - fix border radius on button group to match other toolbar buttons ### Checklist Delete any items that are not applicable to this PR. - [ ] 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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] 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)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] 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)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] 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) --- .../__snapshots__/icon_button_group.test.tsx.snap | 4 ++-- .../buttons/icon_button_group/icon_button_group.styles.ts | 8 ++++++++ .../src/buttons/icon_button_group/icon_button_group.tsx | 1 + .../src/toolbar/__snapshots__/toolbar.test.tsx.snap | 2 +- packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap b/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap index 3780fa1bcddd6e..c472f58ec3e2f7 100644 --- a/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap +++ b/packages/shared-ux/button_toolbar/src/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap @@ -2,7 +2,7 @@ exports[` is rendered 1`] = `
is rendered 1`] = `