-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ResponseOps][Window Maintenance] Add window maintenance to alerting …
…plugin public folder and add create form (#153445) Resolves #152272 ## Summary - This PR adds maintenance windows to the alerting plugin and also adds the create form. - The Maintenance Windows feature is hidden by a feature flag by default. - The create form calls the create api, but it's expected to fail because the api hasn't been merged. <img width="1709" alt="Screen Shot 2023-04-03 at 2 56 45 PM" src="https://user-images.githubusercontent.com/109488926/229601948-13a46847-bebc-4674-9446-55163a6a6589.png"> <img width="1706" alt="Screen Shot 2023-04-03 at 2 56 56 PM" src="https://user-images.githubusercontent.com/109488926/229601997-03b232cf-0d50-4047-a92b-79ef0c1e9568.png"> <img width="1706" alt="Screen Shot 2023-04-03 at 2 57 12 PM" src="https://user-images.githubusercontent.com/109488926/229602015-7e0110e6-dd3d-41c4-8601-64943fe01323.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] [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 ### To verify - Enable maintenance windows by adding `ENABLE_MAINTENANCE_WINDOWS = true` to `x-pack/plugins/alerting/common/index.ts` - Go to http://localhost:5601/app/management/insightsAndAlerting/maintenanceWindows or select the Maintenance Windows link on the side nav - Click the `Create a maintenance window` button and verify it takes you to the create from - Verify the bread crumbs and the return link work correctly - Once on the create form, try to submit without entering any values and verify that the form doesn't submit and errors are shown on the `name` and `duration` fields - Submit a maintenance window that is not recurring. You'll see an error popup but verify that the request body is correct and matches what you've selected in the form. - Create a maintenance window that is recurring and try choosing the different options on the recurring form. Verify that the summary at the bottom of the form accurately reflects what you have selected. - Submit a maintenance window that is recurring. Again, you'll see an error popup but verify that the request body is correct and matches what you've selected in the form. --------- Co-authored-by: kibanamachine <[email protected]>
- Loading branch information
1 parent
3b07f96
commit 752147e
Showing
63 changed files
with
3,999 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
x-pack/plugins/alerting/public/application/maintenance_windows.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* 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, { Suspense } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import { Router, Switch } from 'react-router-dom'; | ||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
import { Route } from '@kbn/shared-ux-router'; | ||
import { CoreStart } from '@kbn/core/public'; | ||
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; | ||
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; | ||
import { Storage } from '@kbn/kibana-utils-plugin/public'; | ||
import { ManagementAppMountParams } from '@kbn/management-plugin/public'; | ||
import { EuiLoadingSpinner } from '@elastic/eui'; | ||
import { AlertingPluginStart } from '../plugin'; | ||
import { paths } from '../config'; | ||
|
||
const MaintenanceWindowsLazy: React.FC = React.lazy(() => import('../pages/maintenance_windows')); | ||
const MaintenanceWindowsCreateLazy: React.FC = React.lazy( | ||
() => import('../pages/maintenance_windows/maintenance_window_create_page') | ||
); | ||
|
||
const App = React.memo(() => { | ||
return ( | ||
<> | ||
<Switch> | ||
<Route path="/" exact> | ||
<Suspense fallback={<EuiLoadingSpinner />}> | ||
<MaintenanceWindowsLazy /> | ||
</Suspense> | ||
</Route> | ||
<Route path={paths.alerting.maintenanceWindowsCreate} exact> | ||
<Suspense fallback={<EuiLoadingSpinner />}> | ||
<MaintenanceWindowsCreateLazy /> | ||
</Suspense> | ||
</Route> | ||
</Switch> | ||
</> | ||
); | ||
}); | ||
App.displayName = 'App'; | ||
|
||
export const renderApp = ({ | ||
core, | ||
plugins, | ||
mountParams, | ||
kibanaVersion, | ||
}: { | ||
core: CoreStart; | ||
plugins: AlertingPluginStart; | ||
mountParams: ManagementAppMountParams; | ||
kibanaVersion: string; | ||
}) => { | ||
const { element, history, theme$ } = mountParams; | ||
const i18nCore = core.i18n; | ||
const isDarkMode = core.uiSettings.get('theme:darkMode'); | ||
|
||
const queryClient = new QueryClient(); | ||
|
||
ReactDOM.render( | ||
<KibanaThemeProvider theme$={theme$}> | ||
<KibanaContextProvider | ||
services={{ | ||
...core, | ||
...plugins, | ||
storage: new Storage(localStorage), | ||
kibanaVersion, | ||
}} | ||
> | ||
<Router history={history}> | ||
<EuiThemeProvider darkMode={isDarkMode}> | ||
<i18nCore.Context> | ||
<QueryClientProvider client={queryClient}> | ||
<App /> | ||
</QueryClientProvider> | ||
</i18nCore.Context> | ||
</EuiThemeProvider> | ||
</Router> | ||
</KibanaContextProvider> | ||
</KibanaThemeProvider>, | ||
element | ||
); | ||
return () => { | ||
ReactDOM.unmountComponentAtNode(element); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export { paths, AlertingDeepLinkId, APP_ID, MAINTENANCE_WINDOWS_APP_ID } from './paths'; | ||
|
||
export type { IAlertingDeepLinkId } from './paths'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export const MAINTENANCE_WINDOWS_APP_ID = 'maintenanceWindows'; | ||
export const APP_ID = 'management'; | ||
|
||
export const paths = { | ||
alerting: { | ||
maintenanceWindows: `/${MAINTENANCE_WINDOWS_APP_ID}`, | ||
maintenanceWindowsCreate: '/create', | ||
}, | ||
}; | ||
|
||
export const AlertingDeepLinkId = { | ||
maintenanceWindows: MAINTENANCE_WINDOWS_APP_ID, | ||
maintenanceWindowsCreate: 'create', | ||
}; | ||
|
||
export type IAlertingDeepLinkId = typeof AlertingDeepLinkId[keyof typeof AlertingDeepLinkId]; |
74 changes: 74 additions & 0 deletions
74
x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* 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 { renderHook } from '@testing-library/react-hooks'; | ||
import { useBreadcrumbs } from './use_breadcrumbs'; | ||
import { AlertingDeepLinkId } from '../config'; | ||
import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; | ||
|
||
const mockSetBreadcrumbs = jest.fn(); | ||
const mockSetTitle = jest.fn(); | ||
|
||
jest.mock('../utils/kibana_react', () => { | ||
const originalModule = jest.requireActual('../utils/kibana_react'); | ||
return { | ||
...originalModule, | ||
useKibana: () => { | ||
const { services } = originalModule.useKibana(); | ||
return { | ||
services: { | ||
...services, | ||
chrome: { setBreadcrumbs: mockSetBreadcrumbs, docTitle: { change: mockSetTitle } }, | ||
}, | ||
}; | ||
}, | ||
}; | ||
}); | ||
|
||
jest.mock('./use_navigation', () => { | ||
const originalModule = jest.requireActual('./use_navigation'); | ||
return { | ||
...originalModule, | ||
useNavigation: jest.fn().mockReturnValue({ | ||
getAppUrl: jest.fn((params?: { deepLinkId: string }) => params?.deepLinkId ?? '/test'), | ||
}), | ||
}; | ||
}); | ||
|
||
let appMockRenderer: AppMockRenderer; | ||
|
||
describe('useBreadcrumbs', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
appMockRenderer = createAppMockRenderer(); | ||
}); | ||
|
||
test('set maintenance windows breadcrumbs', () => { | ||
renderHook(() => useBreadcrumbs(AlertingDeepLinkId.maintenanceWindows), { | ||
wrapper: appMockRenderer.AppWrapper, | ||
}); | ||
expect(mockSetBreadcrumbs).toHaveBeenCalledWith([ | ||
{ href: '/test', onClick: expect.any(Function), text: 'Stack Management' }, | ||
{ text: 'Maintenance Windows' }, | ||
]); | ||
}); | ||
|
||
test('set create maintenance windows breadcrumbs', () => { | ||
renderHook(() => useBreadcrumbs(AlertingDeepLinkId.maintenanceWindowsCreate), { | ||
wrapper: appMockRenderer.AppWrapper, | ||
}); | ||
expect(mockSetBreadcrumbs).toHaveBeenCalledWith([ | ||
{ href: '/test', onClick: expect.any(Function), text: 'Stack Management' }, | ||
{ | ||
href: AlertingDeepLinkId.maintenanceWindows, | ||
onClick: expect.any(Function), | ||
text: 'Maintenance Windows', | ||
}, | ||
{ text: 'Create' }, | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* 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'; | ||
import { ChromeBreadcrumb } from '@kbn/core/public'; | ||
import { MouseEvent, useEffect } from 'react'; | ||
import { useKibana } from '../utils/kibana_react'; | ||
import { useNavigation } from './use_navigation'; | ||
import { APP_ID, AlertingDeepLinkId, IAlertingDeepLinkId } from '../config'; | ||
|
||
const breadcrumbTitle: Record<IAlertingDeepLinkId, string> = { | ||
[AlertingDeepLinkId.maintenanceWindows]: i18n.translate( | ||
'xpack.alerting.breadcrumbs.maintenanceWindowsLinkText', | ||
{ | ||
defaultMessage: 'Maintenance Windows', | ||
} | ||
), | ||
[AlertingDeepLinkId.maintenanceWindowsCreate]: i18n.translate( | ||
'xpack.alerting.breadcrumbs.createMaintenanceWindowsLinkText', | ||
{ | ||
defaultMessage: 'Create', | ||
} | ||
), | ||
}; | ||
|
||
const topLevelBreadcrumb: Record<string, IAlertingDeepLinkId> = { | ||
[AlertingDeepLinkId.maintenanceWindowsCreate]: AlertingDeepLinkId.maintenanceWindows, | ||
}; | ||
|
||
function addClickHandlers( | ||
breadcrumbs: ChromeBreadcrumb[], | ||
navigateToHref?: (url: string) => Promise<void> | ||
) { | ||
return breadcrumbs.map((bc) => ({ | ||
...bc, | ||
...(bc.href | ||
? { | ||
onClick: (event: MouseEvent) => { | ||
if (navigateToHref && bc.href) { | ||
event.preventDefault(); | ||
navigateToHref(bc.href); | ||
} | ||
}, | ||
} | ||
: {}), | ||
})); | ||
} | ||
|
||
function getTitleFromBreadCrumbs(breadcrumbs: ChromeBreadcrumb[]) { | ||
return breadcrumbs.map(({ text }) => text?.toString() ?? '').reverse(); | ||
} | ||
|
||
export const useBreadcrumbs = (pageDeepLink: IAlertingDeepLinkId) => { | ||
const { | ||
services: { | ||
chrome: { docTitle, setBreadcrumbs }, | ||
application: { navigateToUrl }, | ||
}, | ||
} = useKibana(); | ||
const setTitle = docTitle.change; | ||
const { getAppUrl } = useNavigation(APP_ID); | ||
|
||
useEffect(() => { | ||
const breadcrumbs = [ | ||
{ | ||
text: i18n.translate('xpack.alerting.breadcrumbs.stackManagementLinkText', { | ||
defaultMessage: 'Stack Management', | ||
}), | ||
href: getAppUrl(), | ||
}, | ||
...(topLevelBreadcrumb[pageDeepLink] | ||
? [ | ||
{ | ||
text: breadcrumbTitle[topLevelBreadcrumb[pageDeepLink]], | ||
href: getAppUrl({ deepLinkId: topLevelBreadcrumb[pageDeepLink] }), | ||
}, | ||
] | ||
: []), | ||
{ | ||
text: breadcrumbTitle[pageDeepLink], | ||
}, | ||
]; | ||
|
||
if (setBreadcrumbs) { | ||
setBreadcrumbs(addClickHandlers(breadcrumbs, navigateToUrl)); | ||
} | ||
if (setTitle) { | ||
setTitle(getTitleFromBreadCrumbs(breadcrumbs)); | ||
} | ||
}, [pageDeepLink, getAppUrl, navigateToUrl, setBreadcrumbs, setTitle]); | ||
}; |
Oops, something went wrong.