Skip to content

Commit

Permalink
[ResponseOps][Window Maintenance] Add the stop and archive actions to…
Browse files Browse the repository at this point in the history
… the maintenance window table (#155201)

Resolves #154814

## Summary

This pr adds the cancel/archive actions to the Maintenance Windows
table, and adds an archive callout to the create form.
@lcawl I think I need your help with some of the text in the modals 🙂 

**Create Form:**
Callout
<img width="1370" alt="Screen Shot 2023-04-18 at 9 49 58 PM"
src="https://user-images.githubusercontent.com/109488926/232945833-77f01988-e5c4-4d5f-a2f9-b361085964e2.png">

Modal
<img width="1335" alt="Screen Shot 2023-04-18 at 9 50 05 PM"
src="https://user-images.githubusercontent.com/109488926/232945810-dacb3a26-fa59-4e5e-995c-14b7f0977c10.png">

**Cancel Modal:**
<img width="628" alt="Screen Shot 2023-04-18 at 9 52 18 PM"
src="https://user-images.githubusercontent.com/109488926/232946092-c4001ccc-d2f9-475a-bc67-3b2ab6fdf31e.png">

**Cancel and Archive Modal:**
<img width="621" alt="Screen Shot 2023-04-18 at 9 52 56 PM"
src="https://user-images.githubusercontent.com/109488926/232946171-154f8601-d5aa-40f1-824a-bff25b4d3d91.png">

**Archive Modal:**
<img width="611" alt="Screen Shot 2023-04-18 at 9 53 25 PM"
src="https://user-images.githubusercontent.com/109488926/232946240-ee25bcd0-80d6-403c-9fb3-9aec89d11ebd.png">

**Unarchive Modal:**
<img width="620" alt="Screen Shot 2023-04-18 at 9 54 16 PM"
src="https://user-images.githubusercontent.com/109488926/232946350-814d0c05-5511-4fad-8ac4-0c5824d94b8e.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

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Lisa Cawley <[email protected]>
  • Loading branch information
3 people authored Apr 21, 2023
1 parent da5a4b0 commit dbb8e2e
Show file tree
Hide file tree
Showing 22 changed files with 1,186 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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 { useArchiveMaintenanceWindow } from './use_archive_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/archive', () => ({
archiveMaintenanceWindow: jest.fn(),
}));

const { archiveMaintenanceWindow } = jest.requireMock(
'../services/maintenance_windows_api/archive'
);

const maintenanceWindow: MaintenanceWindow = {
title: 'archive',
duration: 1,
rRule: {
dtstart: '2023-03-23T19:16:21.293Z',
tzid: 'America/New_York',
},
};

let appMockRenderer: AppMockRenderer;

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

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

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

await act(async () => {
await result.current.mutate({ maintenanceWindowId: '123', archive: true });
});
await waitFor(() =>
expect(mockAddSuccess).toBeCalledWith("Archived maintenance window 'archive'")
);
});

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

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

await act(async () => {
await result.current.mutate({ maintenanceWindowId: '123', archive: true });
});

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

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

await act(async () => {
await result.current.mutate({ maintenanceWindowId: '123', archive: false });
});
await waitFor(() =>
expect(mockAddSuccess).toBeCalledWith("Unarchived maintenance window 'archive'")
);
});

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

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

await act(async () => {
await result.current.mutate({ maintenanceWindowId: '123', archive: false });
});

await waitFor(() =>
expect(mockAddDanger).toBeCalledWith('Failed to unarchive maintenance window.')
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { useMutation } from '@tanstack/react-query';

import { useKibana } from '../utils/kibana_react';
import { archiveMaintenanceWindow } from '../services/maintenance_windows_api/archive';

export function useArchiveMaintenanceWindow() {
const {
http,
notifications: { toasts },
} = useKibana().services;

const mutationFn = ({
maintenanceWindowId,
archive,
}: {
maintenanceWindowId: string;
archive: boolean;
}) => {
return archiveMaintenanceWindow({ http, maintenanceWindowId, archive });
};

return useMutation(mutationFn, {
onSuccess: (data, { archive }) => {
const archiveToast = i18n.translate('xpack.alerting.maintenanceWindowsArchiveSuccess', {
defaultMessage: "Archived maintenance window '{title}'",
values: {
title: data.title,
},
});
const unarchiveToast = i18n.translate('xpack.alerting.maintenanceWindowsUnarchiveSuccess', {
defaultMessage: "Unarchived maintenance window '{title}'",
values: {
title: data.title,
},
});
toasts.addSuccess(archive ? archiveToast : unarchiveToast);
},
onError: (error, { archive }) => {
const archiveToast = i18n.translate('xpack.alerting.maintenanceWindowsArchiveFailure', {
defaultMessage: 'Failed to archive maintenance window.',
});
const unarchiveToast = i18n.translate('xpack.alerting.maintenanceWindowsUnarchiveFailure', {
defaultMessage: 'Failed to unarchive maintenance window.',
});
toasts.addDanger(archive ? archiveToast : unarchiveToast);
},
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export function useCreateMaintenanceWindow() {
};

return useMutation(mutationFn, {
onSuccess: (variables: MaintenanceWindow) => {
onSuccess: (data) => {
toasts.addSuccess(
i18n.translate('xpack.alerting.maintenanceWindowsCreateSuccess', {
defaultMessage: "Created maintenance window '{title}'",
values: {
title: variables.title,
title: data.title,
},
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export const useFindMaintenanceWindows = () => {
}
};

const { isLoading, data = [] } = useQuery({
const {
isLoading,
data = [],
refetch,
} = useQuery({
queryKey: ['findMaintenanceWindows'],
queryFn,
onError: onErrorFn,
Expand All @@ -42,5 +46,6 @@ export const useFindMaintenanceWindows = () => {
return {
maintenanceWindows: data,
isLoading,
refetch,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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 { useFinishAndArchiveMaintenanceWindow } from './use_finish_and_archive_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/finish', () => ({
finishMaintenanceWindow: jest.fn(),
}));
jest.mock('../services/maintenance_windows_api/archive', () => ({
archiveMaintenanceWindow: jest.fn(),
}));

const { finishMaintenanceWindow } = jest.requireMock('../services/maintenance_windows_api/finish');
const { archiveMaintenanceWindow } = jest.requireMock(
'../services/maintenance_windows_api/archive'
);

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

let appMockRenderer: AppMockRenderer;

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

appMockRenderer = createAppMockRenderer();
finishMaintenanceWindow.mockResolvedValue(maintenanceWindow);
archiveMaintenanceWindow.mockResolvedValue(maintenanceWindow);
});

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

await act(async () => {
await result.current.mutate('123');
});
await waitFor(() =>
expect(mockAddSuccess).toBeCalledWith(
"Cancelled and archived running maintenance window 'test'"
)
);
});

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

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

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

await waitFor(() =>
expect(mockAddDanger).toBeCalledWith('Failed to cancel and archive maintenance window.')
);
});

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

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

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

await waitFor(() =>
expect(mockAddDanger).toBeCalledWith('Failed to cancel and archive maintenance window.')
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 { useMutation } from '@tanstack/react-query';

import { useKibana } from '../utils/kibana_react';
import { finishMaintenanceWindow } from '../services/maintenance_windows_api/finish';
import { archiveMaintenanceWindow } from '../services/maintenance_windows_api/archive';

export function useFinishAndArchiveMaintenanceWindow() {
const {
http,
notifications: { toasts },
} = useKibana().services;

const mutationFn = async (maintenanceWindowId: string) => {
await finishMaintenanceWindow({ http, maintenanceWindowId });
return archiveMaintenanceWindow({ http, maintenanceWindowId, archive: true });
};

return useMutation(mutationFn, {
onSuccess: (data) => {
toasts.addSuccess(
i18n.translate('xpack.alerting.maintenanceWindowsFinishedAndArchiveSuccess', {
defaultMessage: "Cancelled and archived running maintenance window '{title}'",
values: {
title: data.title,
},
})
);
},
onError: () => {
toasts.addDanger(
i18n.translate('xpack.alerting.maintenanceWindowsFinishedAndArchiveFailure', {
defaultMessage: 'Failed to cancel and archive maintenance window.',
})
);
},
});
}
Loading

0 comments on commit dbb8e2e

Please sign in to comment.