Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ResponseOps][Window Maintenance] Add window maintenance to alerting plugin public folder and add create form #153445

Merged
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
cf9afef
Added maintenance windows to the alerting plugin
doakalexi Mar 1, 2023
fc7146f
Adding maintenance windows page
doakalexi Mar 2, 2023
1e5b244
Adding empty page
doakalexi Mar 9, 2023
63497cd
Adding create form
doakalexi Mar 22, 2023
92d9a88
Merge branch 'main' of github.com:elastic/kibana into alerting/mainte…
doakalexi Mar 22, 2023
22a6c2f
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Mar 22, 2023
2972629
Removing duplicate file
doakalexi Mar 22, 2023
9584a03
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Mar 22, 2023
88fa0ee
Adding route to create maintenance window
doakalexi Mar 22, 2023
0bb494d
Adding route
doakalexi Mar 22, 2023
7fb8f18
Fixing test failures
doakalexi Mar 23, 2023
5c55505
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Mar 23, 2023
d8ea294
Adding more tests
doakalexi Mar 23, 2023
7e8de9e
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Mar 23, 2023
35fbd11
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Mar 23, 2023
3c7071b
Adding more tests
doakalexi Mar 24, 2023
b4c08e5
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Mar 24, 2023
3fd54b7
Adding feature flag
doakalexi Mar 24, 2023
2da332f
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Mar 24, 2023
b37ebc6
Fixing test failure
doakalexi Mar 24, 2023
eb0c787
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Mar 24, 2023
ca83a91
Deleting unused file
doakalexi Mar 24, 2023
e8891c8
Fixing type check failure
doakalexi Mar 24, 2023
9ef329a
Fixing test failures
doakalexi Mar 27, 2023
f1c1949
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Mar 27, 2023
334efa4
Removing date specific test
doakalexi Mar 27, 2023
c242878
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Mar 27, 2023
f7c67fb
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Mar 27, 2023
12a0143
Merge branch 'main' of github.com:elastic/kibana into alerting/mainte…
doakalexi Mar 27, 2023
563b8cb
Using app id
doakalexi Mar 29, 2023
378daaa
Merge branch 'main' of github.com:elastic/kibana into alerting/mainte…
doakalexi Mar 29, 2023
8b257aa
Fixing failure
doakalexi Mar 29, 2023
7eebe54
Addressing pr feedback
doakalexi Mar 30, 2023
28a4050
Removing feature flag from kibana.yml
doakalexi Mar 30, 2023
0da9c84
Set feature flag to false
doakalexi Mar 30, 2023
a83065c
fixing lint
doakalexi Mar 31, 2023
8551ef0
Updating the form layout
doakalexi Mar 31, 2023
7dff402
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Mar 31, 2023
7d64a3b
Updated form
doakalexi Apr 3, 2023
658c716
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Apr 3, 2023
89ffb8b
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Apr 3, 2023
d72b121
Updating text
doakalexi Apr 3, 2023
36accc8
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Apr 3, 2023
3f23f69
Removing duplicate translation
doakalexi Apr 3, 2023
3009aca
Update weekdays to be 3 letter
doakalexi Apr 3, 2023
0bffd64
Fixing failing test
doakalexi Apr 4, 2023
5f4cfb0
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Apr 4, 2023
ebd69fe
Removing css file
doakalexi Apr 4, 2023
39f0a74
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Apr 4, 2023
e408455
Addressing pr comments
doakalexi Apr 5, 2023
30c30c7
Adding lazy
doakalexi Apr 5, 2023
3e14094
Adressing small pr comments
doakalexi Apr 10, 2023
f15b578
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Apr 10, 2023
cd3cbde
Fixing recurring summary
doakalexi Apr 10, 2023
740d17c
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Apr 10, 2023
e5d8d89
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Apr 10, 2023
74d759f
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Apr 10, 2023
5936064
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Apr 11, 2023
3a1e267
Updating rrule convert function
doakalexi Apr 13, 2023
dc4bbb1
Merge branch 'alerting/maintenance_windows_create_form' of github.com…
doakalexi Apr 13, 2023
cbb2919
Merge branch 'main' into alerting/maintenance_windows_create_form
doakalexi Apr 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ export const BASE_ALERTING_API_PATH = '/api/alerting';
export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting';
export const ALERTS_FEATURE_ID = 'alerts';
export const MONITORING_HISTORY_LIMIT = 200;
export const ENABLE_MAINTENANCE_WINDOWS = false;
5 changes: 4 additions & 1 deletion x-pack/plugins/alerting/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
"features",
"kibanaUtils",
"licensing",
"taskManager"
"taskManager",
"kibanaReact",
"management",
"esUiShared",
],
"optionalPlugins": [
"usageCollection",
Expand Down
79 changes: 79 additions & 0 deletions x-pack/plugins/alerting/public/application/maintenance_windows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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 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 { AlertingPluginStart } from '../plugin';
import { routes } from '../routes';

const App = React.memo(() => {
return (
<>
<Switch>
{Object.keys(routes.maintenanceWindows).map((path) => {
const { handler, exact } = routes.maintenanceWindows[path];
const Wrapper = () => handler();
return <Route key={path} path={path} exact={exact} component={Wrapper} />;
doakalexi marked this conversation as resolved.
Show resolved Hide resolved
})}
</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);
};
};
10 changes: 10 additions & 0 deletions x-pack/plugins/alerting/public/config/index.ts
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';
23 changes: 23 additions & 0 deletions x-pack/plugins/alerting/public/config/paths.ts
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 x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx
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' },
]);
});
});
95 changes: 95 additions & 0 deletions x-pack/plugins/alerting/public/hooks/use_breadcrumbs.ts
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]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks/dom';
import { waitFor } from '@testing-library/dom';

import { MaintenanceWindow } from '../pages/maintenance_windows/types';
import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils';
import { useCreateMaintenanceWindow } from './use_create_maintenance_window';

const mockAddDanger = jest.fn();
const mockAddSuccess = jest.fn();

jest.mock('../utils/kibana_react', () => {
const originalModule = jest.requireActual('../utils/kibana_react');
return {
...originalModule,
useKibana: () => {
const { services } = originalModule.useKibana();
return {
services: {
...services,
notifications: { toasts: { addSuccess: mockAddSuccess, addDanger: mockAddDanger } },
},
};
},
};
});
jest.mock('../services/maintenance_windows_api/create', () => ({
createMaintenanceWindow: jest.fn(),
}));

const { createMaintenanceWindow } = jest.requireMock('../services/maintenance_windows_api/create');

const maintenanceWindow: MaintenanceWindow = {
title: 'test',
duration: 1,
rRule: {
dtstart: '2023-03-23T19:16:21.293Z',
tzid: 'America/New_York',
freq: 3,
interval: 1,
byweekday: ['TH'],
},
};

let appMockRenderer: AppMockRenderer;

describe('useCreateMaintenanceWindow', () => {
beforeEach(() => {
jest.clearAllMocks();

appMockRenderer = createAppMockRenderer();
createMaintenanceWindow.mockResolvedValue(maintenanceWindow);
});

it('should call onSuccess if api succeeds', async () => {
const { result } = renderHook(() => useCreateMaintenanceWindow(), {
wrapper: appMockRenderer.AppWrapper,
});

await act(async () => {
await result.current.mutate(maintenanceWindow);
});
await waitFor(() => expect(mockAddSuccess).toBeCalledWith("Created maintenance window 'test'"));
});

it('should call onError if api fails', async () => {
createMaintenanceWindow.mockRejectedValue('');

const { result } = renderHook(() => useCreateMaintenanceWindow(), {
wrapper: appMockRenderer.AppWrapper,
});

await act(async () => {
await result.current.mutate(maintenanceWindow);
});

await waitFor(() =>
expect(mockAddDanger).toBeCalledWith('Failed to create maintenance window.')
);
});
});
Loading