diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts index e925ee42fb14caa..2170559dace5a36 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts @@ -39,6 +39,11 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ); }; + const setLoadWatchHistoryItemResponse = (response: HttpResponse = {}) => { + const defaultResponse = { watchHistoryItem: {} }; + server.respondWith('GET', `${API_ROOT}/history/:id`, mockResponse(defaultResponse, response)); + }; + const setDeleteWatchResponse = (response?: HttpResponse, error?: any) => { const status = error ? error.status || 400 : 200; const body = error ? JSON.stringify(error.body) : JSON.stringify(response); @@ -90,10 +95,38 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ); }; + const setDeactivateWatchResponse = (response: HttpResponse = {}) => { + const defaultResponse = { watchStatus: {} }; + server.respondWith( + 'PUT', + `${API_ROOT}/watch/:id/deactivate`, + mockResponse(defaultResponse, response) + ); + }; + + const setActivateWatchResponse = (response: HttpResponse = {}) => { + const defaultResponse = { watchStatus: {} }; + server.respondWith( + 'PUT', + `${API_ROOT}/watch/:id/activate`, + mockResponse(defaultResponse, response) + ); + }; + + const setAcknowledgeWatchResponse = (response: HttpResponse = {}) => { + const defaultResponse = { watchStatus: {} }; + server.respondWith( + 'PUT', + `${API_ROOT}/watch/:id/action/:actionId/acknowledge`, + mockResponse(defaultResponse, response) + ); + }; + return { setLoadWatchesResponse, setLoadWatchResponse, setLoadWatchHistoryResponse, + setLoadWatchHistoryItemResponse, setDeleteWatchResponse, setSaveWatchResponse, setLoadExecutionResultResponse, @@ -101,6 +134,9 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { setLoadEsFieldsResponse, setLoadSettingsResponse, setLoadWatchVisualizeResponse, + setDeactivateWatchResponse, + setActivateWatchResponse, + setAcknowledgeWatchResponse, }; }; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts index adf6bf14677701b..ac4e4eab1dcc827 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts @@ -11,6 +11,7 @@ import { findTestSubject, TestBed, TestBedConfig, + nextTick, } from '../../../../../../test_utils'; import { WatchStatus } from '../../../public/sections/watch_status/components/watch_status'; import { ROUTES } from '../../../common/constants'; @@ -28,8 +29,9 @@ const initTestBed = registerTestBed(WatchStatus, testBedConfig); export interface WatchStatusTestBed extends TestBed { actions: { + selectTab: (tab: 'execution history' | 'action statuses') => void; clickToggleActivationButton: () => void; - clickAcknowledgeButton: () => void; + clickAcknowledgeButton: (index: number) => void; clickDeleteWatchButton: () => void; clickWatchExecutionAt: (index: number, tableCellText: string) => void; }; @@ -42,6 +44,15 @@ export const setup = async (): Promise => { * User Actions */ + const selectTab = (tab: 'execution history' | 'action statuses') => { + const tabs = ['execution history', 'action statuses']; + + testBed + .find('tab') + .at(tabs.indexOf(tab)) + .simulate('click'); + }; + const clickToggleActivationButton = async () => { const { component } = testBed; const button = testBed.find('toggleWatchActivationButton'); @@ -53,9 +64,12 @@ export const setup = async (): Promise => { }); }; - const clickAcknowledgeButton = async () => { - const { component } = testBed; - const button = testBed.find('acknowledgeWatchButton'); + const clickAcknowledgeButton = async (index: number) => { + const { component, table } = testBed; + const { rows } = table.getMetaData('watchActionStatusTable'); + const currentRow = rows[index]; + const lastColumn = currentRow.columns[currentRow.columns.length - 1].reactWrapper; + const button = findTestSubject(lastColumn, 'acknowledgeWatchButton'); // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { @@ -79,12 +93,14 @@ export const setup = async (): Promise => { const { component, table } = testBed; const { rows } = table.getMetaData('watchHistoryTable'); const currentRow = rows[index]; - const firstColumn = currentRow.columns[currentRow.columns.length - 1].reactWrapper; - const button = findTestSubject(firstColumn, `watchIdColumn-${tableCellText}`); + const firstColumn = currentRow.columns[0].reactWrapper; + + const button = findTestSubject(firstColumn, `watchStartTimeColumn-${tableCellText}`); // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { button.simulate('click'); + await nextTick(100); component.update(); }); }; @@ -92,6 +108,7 @@ export const setup = async (): Promise => { return { ...testBed, actions: { + selectTab, clickToggleActivationButton, clickAcknowledgeButton, clickDeleteWatchButton, @@ -110,9 +127,14 @@ export type TestSubjects = | 'actionErrorsFlyout.title' | 'deleteWatchButton' | 'pageTitle' + | 'tab' | 'toggleWatchActivationButton' + | 'watchActionStatusTable' + | 'watchActionsTable' + | 'watchDetailSection' | 'watchHistoryDetailFlyout' | 'watchHistoryDetailFlyout.title' + | 'watchHistorySection' | 'watchHistoryErrorDetailFlyout' | 'watchHistoryErrorDetailFlyout.errorMessage' | 'watchHistoryErrorDetailFlyout.title' diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts index d119b7bb21f6a49..a7d5b4d3d470bd8 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts @@ -197,7 +197,7 @@ describe.skip('', () => { ).toContain('Delete watch'); }); - test('should send the correct HTTP request to delete repository', async () => { + test('should send the correct HTTP request to delete watch', async () => { const { component, actions, table } = testBed; const { rows } = table.getMetaData('watchesTable'); diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts index 4e0f3ce31eea26b..eb2d377f39deeb6 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts @@ -8,6 +8,11 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { WatchStatusTestBed } from './helpers/watch_status.helpers'; import { WATCH } from './helpers/constants'; import { getWatchHistory } from '../../test/fixtures'; +import moment from 'moment'; +import { ROUTES } from '../../common/constants'; +import { WATCH_STATES, ACTION_STATES } from '../../common/constants'; + +const { API_ROOT } = ROUTES; jest.mock('ui/chrome', () => ({ breadcrumbs: { set: () => {} }, @@ -18,6 +23,28 @@ jest.mock('ui/time_buckets', () => {}); const { setup } = pageHelpers.watchStatus; +const watchHistory1 = getWatchHistory({ startTime: '2019-06-04T01:11:11.294' }); +const watchHistory2 = getWatchHistory({ startTime: '2019-06-04T01:10:10.987Z' }); + +const watchHistoryItems = { watchHistoryItems: [watchHistory1, watchHistory2] }; + +const ACTION_ID = 'my_logging_action_1'; + +const watch = { + ...WATCH.watch, + watchStatus: { + state: WATCH_STATES.FIRING, + isActive: true, + actionStatuses: [ + { + id: ACTION_ID, + state: ACTION_STATES.FIRING, + isAckable: true, + }, + ], + }, +}; + describe.skip('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchStatusTestBed; @@ -28,12 +55,7 @@ describe.skip('', () => { describe('on component mount', () => { beforeEach(async () => { - const watchHistory1 = getWatchHistory({ startTime: '2019-06-04T01:11:11.294' }); - const watchHistory2 = getWatchHistory({ startTime: '2019-06-04T01:10:10.987Z' }); - - const watchHistoryItems = { watchHistoryItems: [watchHistory1, watchHistory2] }; - - httpRequestsMockHelpers.setLoadWatchResponse(WATCH); + httpRequestsMockHelpers.setLoadWatchResponse({ watch }); httpRequestsMockHelpers.setLoadWatchHistoryResponse(watchHistoryItems); testBed = await setup(); @@ -48,7 +70,216 @@ describe.skip('', () => { test('should set the correct page title', () => { const { find } = testBed; - expect(find('pageTitle').text()).toBe(`Current status for '${WATCH.watch.name}'`); + expect(find('pageTitle').text()).toBe(`Current status for '${watch.name}'`); + }); + + describe('tabs', () => { + test('should have 2 tabs', () => { + const { find } = testBed; + + expect(find('tab').length).toBe(2); + expect(find('tab').map(t => t.text())).toEqual(['Execution history', 'Action statuses']); + }); + + test('should navigate to the "Action statuses" tab', () => { + const { exists, actions } = testBed; + + expect(exists('watchHistorySection')).toBe(true); + expect(exists('watchDetailSection')).toBe(false); + + actions.selectTab('action statuses'); + + expect(exists('watchHistorySection')).toBe(false); + expect(exists('watchDetailSection')).toBe(true); + }); + }); + + describe('execution history', () => { + test('should list history items in the table', () => { + const { table } = testBed; + const { tableCellsValues } = table.getMetaData('watchHistoryTable'); + + const getExpectedValue = (value: any) => (typeof value === 'undefined' ? '' : value); + + tableCellsValues.forEach((row, i) => { + const historyItem = watchHistoryItems.watchHistoryItems[i]; + const { startTime, watchStatus } = historyItem; + + expect(row).toEqual([ + getExpectedValue(moment(startTime).format()), + getExpectedValue(watchStatus.state), + getExpectedValue(watchStatus.comment), + ]); + }); + }); + + test('should show execution history details on click', async () => { + const { actions, exists, find } = testBed; + + const watchHistoryItem = { + ...watchHistory1, + watchId: watch.id, + watchStatus: { + state: WATCH_STATES.FIRING, + actionStatuses: [ + { + id: 'my_logging_action_1', + state: ACTION_STATES.FIRING, + isAckable: true, + }, + ], + }, + }; + + const formattedStartTime = moment(watchHistoryItem.startTime).format(); + + httpRequestsMockHelpers.setLoadWatchHistoryItemResponse({ watchHistoryItem }); + + await actions.clickWatchExecutionAt(0, formattedStartTime); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(latestRequest.method).toBe('GET'); + expect(latestRequest.url).toBe(`${API_ROOT}/history/${watchHistoryItem.id}`); + + expect(exists('watchHistoryDetailFlyout')).toBe(true); + }); + }); + + describe('delete watch', () => { + test('should show a confirmation when clicking the delete button', async () => { + const { actions } = testBed; + + await actions.clickDeleteWatchButton(); + + // We need to read the document "body" as the modal is added there and not inside + // the component DOM tree. + expect( + document.body.querySelector('[data-test-subj="deleteWatchesConfirmation"]') + ).not.toBe(null); + + expect( + document.body.querySelector('[data-test-subj="deleteWatchesConfirmation"]')!.textContent + ).toContain('Delete watch'); + }); + + test('should send the correct HTTP request to delete watch', async () => { + const { component, actions } = testBed; + + await actions.clickDeleteWatchButton(); + + const modal = document.body.querySelector('[data-test-subj="deleteWatchesConfirmation"]'); + const confirmButton: HTMLButtonElement | null = modal!.querySelector( + '[data-test-subj="confirmModalConfirmButton"]' + ); + + httpRequestsMockHelpers.setDeleteWatchResponse({ + results: { + successes: [watch.id], + errors: [], + }, + }); + + // @ts-ignore (remove when react 16.9.0 is released) + await act(async () => { + confirmButton!.click(); + await nextTick(); + component.update(); + }); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(latestRequest.method).toBe('POST'); + expect(latestRequest.url).toBe(`${API_ROOT}/watches/delete`); + }); + }); + + describe('activate & deactive watch', () => { + test('should send the correct HTTP request to deactivate and activate a watch', async () => { + const { actions } = testBed; + + httpRequestsMockHelpers.setDeactivateWatchResponse({ + watchStatus: { + state: WATCH_STATES.DISABLED, + isActive: false, + }, + }); + + await actions.clickToggleActivationButton(); + + const deactivateRequest = server.requests[server.requests.length - 1]; + + expect(deactivateRequest.method).toBe('PUT'); + expect(deactivateRequest.url).toBe(`${API_ROOT}/watch/${watch.id}/deactivate`); + + httpRequestsMockHelpers.setActivateWatchResponse({ + watchStatus: { + state: WATCH_STATES.FIRING, + isActive: true, + }, + }); + + await actions.clickToggleActivationButton(); + + const activateRequest = server.requests[server.requests.length - 1]; + + expect(activateRequest.method).toBe('PUT'); + expect(activateRequest.url).toBe(`${API_ROOT}/watch/${watch.id}/activate`); + }); + }); + + describe('action statuses', () => { + beforeEach(() => { + const { actions } = testBed; + + actions.selectTab('action statuses'); + }); + + test('should list the watch actions in a table', () => { + const { table } = testBed; + const { tableCellsValues } = table.getMetaData('watchActionStatusTable'); + + tableCellsValues.forEach((row, i) => { + const action = watch.watchStatus.actionStatuses[i]; + const { id, state, isAckable } = action; + + expect(row).toEqual([id, state, isAckable ? 'Acknowledge' : '']); + }); + }); + + test('should allow an action to be acknowledged', async () => { + const { actions, table } = testBed; + + httpRequestsMockHelpers.setAcknowledgeWatchResponse({ + watchStatus: { + state: WATCH_STATES.FIRING, + isActive: true, + comment: 'Acked', + actionStatuses: [ + { + id: ACTION_ID, + state: ACTION_STATES.ACKNOWLEDGED, + isAckable: false, + }, + ], + }, + }); + + await actions.clickAcknowledgeButton(0); + + const latestRequest = server.requests[server.requests.length - 1]; + + expect(latestRequest.method).toBe('PUT'); + expect(latestRequest.url).toBe( + `${API_ROOT}/watch/${watch.id}/action/${ACTION_ID}/acknowledge` + ); + + const { tableCellsValues } = table.getMetaData('watchActionStatusTable'); + + tableCellsValues.forEach(row => { + expect(row).toEqual([ACTION_ID, ACTION_STATES.ACKNOWLEDGED, '']); + }); + }); }); }); }); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx index b96002d6875a6af..b095e23d61ea1f7 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx @@ -178,7 +178,7 @@ const WatchDetailUi = () => { : [...baseColumns, actionColumn]; return ( - +
{selectedErrorAction && ( { columns={columns} pagination={PAGINATION} sorting={true} + data-test-subj="watchActionStatusTable" message={ { /> } /> - +
); }; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx index c612509005eaff3..57e0a98ec94803c 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx @@ -124,8 +124,7 @@ const WatchHistoryUi = () => { const formattedDate = startTime.format(); return ( setDetailWatchId(item.id)} > {formattedDate} @@ -253,6 +252,7 @@ const WatchHistoryUi = () => { defaultMessage="No current status to show" /> } + data-test-subj="watchActionsTable" /> @@ -272,7 +272,7 @@ const WatchHistoryUi = () => { } return ( - +
{ } /> {flyout} - +
); }; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx index 1714525c8e74069..409cea68628f943 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx @@ -241,6 +241,7 @@ export const WatchStatus = ({ }} isSelected={tab.id === selectedTab} key={index} + data-test-subj="tab" > {tab.name} diff --git a/x-pack/legacy/plugins/watcher/test/fixtures/watch.ts b/x-pack/legacy/plugins/watcher/test/fixtures/watch.ts index 910ffb989f86a33..d948fddeefd5882 100644 --- a/x-pack/legacy/plugins/watcher/test/fixtures/watch.ts +++ b/x-pack/legacy/plugins/watcher/test/fixtures/watch.ts @@ -27,6 +27,7 @@ interface Watch { lastMetCondition?: Moment; lastChecked?: Moment; isActive: boolean; + actionStatuses?: any[]; }; } diff --git a/x-pack/legacy/plugins/watcher/test/fixtures/watch_history.ts b/x-pack/legacy/plugins/watcher/test/fixtures/watch_history.ts index a62b91286d519ba..70275e6e8869eb1 100644 --- a/x-pack/legacy/plugins/watcher/test/fixtures/watch_history.ts +++ b/x-pack/legacy/plugins/watcher/test/fixtures/watch_history.ts @@ -4,20 +4,35 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getRandomString } from '../../../../../test_utils'; + interface WatchHistory { startTime: string; + id: string; + watchId: string; watchStatus: { state: 'OK' | 'Firing' | 'Error' | 'Config error' | 'Disabled'; comment?: string; + actionStatuses?: Array<{ + id: string; + state: 'OK' | 'Firing' | 'Error' | 'Acked' | 'Throttled' | 'Config error'; + }>; }; + details?: object; } export const getWatchHistory = ({ startTime = '2019-06-03T19:44:11.088Z', + id = getRandomString(), + watchId = getRandomString(), watchStatus = { state: 'OK', }, + details = {}, }: Partial = {}): WatchHistory => ({ startTime, + id, + watchId, watchStatus, + details, });