From 786f41631a47938748754d70df527671c08f5068 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid Date: Mon, 10 Jan 2022 10:49:00 +0100 Subject: [PATCH] [Uptime][Monitor Management UI] Add Enabled column in monitor management monitors list table. (#121682) (elastic/uptime/issues/415) * Add enabled column in monitor management monitors list table. --- .../monitor_list/actions.tsx | 2 +- .../monitor_list/monitor_enabled.test.tsx | 79 ++++++++++++ .../monitor_list/monitor_enabled.tsx | 113 ++++++++++++++++++ .../monitor_list/monitor_list.tsx | 28 +++-- .../monitor_management/monitor_management.tsx | 2 +- .../public/state/api/monitor_management.ts | 4 +- 6 files changed, 215 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.test.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.tsx diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx index cf3606270f0bd..b0567b1723d9e 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/actions.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiButtonIcon, EuiFlexItem, EuiFlexGroup, EuiLoadingSpinner } from '@elastic/eui'; import { UptimeSettingsContext } from '../../../contexts'; import { useFetcher, FETCH_STATUS } from '../../../../../observability/public'; -import { deleteMonitor } from '../../../state/api/monitor_management'; +import { deleteMonitor } from '../../../state/api'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.test.tsx new file mode 100644 index 0000000000000..5130a8bfb5132 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.test.tsx @@ -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 { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ConfigKey, DataStream, SyntheticsMonitor } from '../../../../common/runtime_types'; +import { render } from '../../../lib/helper/rtl_helpers'; +import { FETCH_STATUS } from '../../../../../observability/public'; +import { spyOnUseFetcher } from '../../../lib/helper/spy_use_fetcher'; +import { MonitorEnabled } from './monitor_enabled'; + +describe('', () => { + const setRefresh = jest.fn(); + const testMonitor = { + [ConfigKey.MONITOR_TYPE]: DataStream.HTTP, + [ConfigKey.ENABLED]: true, + } as unknown as SyntheticsMonitor; + + const assertMonitorEnabled = (button: HTMLButtonElement) => + expect(button).toHaveAttribute('aria-checked', 'true'); + const assertMonitorDisabled = (button: HTMLButtonElement) => + expect(button).toHaveAttribute('aria-checked', 'false'); + + let useFetcher: jest.SpyInstance; + + beforeEach(() => { + useFetcher?.mockClear(); + useFetcher = spyOnUseFetcher({}); + }); + + it('correctly renders "enabled" state', () => { + render(); + + const switchButton = screen.getByRole('switch') as HTMLButtonElement; + assertMonitorEnabled(switchButton); + }); + + it('correctly renders "disabled" state', () => { + render( + + ); + + const switchButton = screen.getByRole('switch') as HTMLButtonElement; + assertMonitorDisabled(switchButton); + }); + + it('toggles on click', () => { + render(); + + const switchButton = screen.getByRole('switch') as HTMLButtonElement; + userEvent.click(switchButton); + assertMonitorDisabled(switchButton); + userEvent.click(switchButton); + assertMonitorEnabled(switchButton); + }); + + it('is disabled while request is in progress', () => { + useFetcher.mockReturnValue({ + data: {}, + status: FETCH_STATUS.LOADING, + refetch: () => {}, + }); + + render(); + const switchButton = screen.getByRole('switch') as HTMLButtonElement; + userEvent.click(switchButton); + + expect(switchButton).toHaveAttribute('disabled'); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.tsx new file mode 100644 index 0000000000000..4b3aba5d6a3ab --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_enabled.tsx @@ -0,0 +1,113 @@ +/* + * 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 { EuiSwitch, EuiProgress, EuiSwitchEvent } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect, useState } from 'react'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { FETCH_STATUS, useFetcher } from '../../../../../observability/public'; +import { ConfigKey, SyntheticsMonitor } from '../../../../common/runtime_types'; +import { setMonitor } from '../../../state/api'; + +interface Props { + id: string; + monitor: SyntheticsMonitor; + setRefresh: React.Dispatch>; +} + +export const MonitorEnabled = ({ id, monitor, setRefresh }: Props) => { + const [isEnabled, setIsEnabled] = useState(null); + + const { notifications } = useKibana(); + + const { status } = useFetcher(() => { + if (isEnabled !== null) { + return setMonitor({ id, monitor: { ...monitor, [ConfigKey.ENABLED]: isEnabled } }); + } + }, [isEnabled]); + + useEffect(() => { + if (status === FETCH_STATUS.FAILURE) { + notifications.toasts.danger({ + title: ( +

+ {getMonitorEnabledUpdateFailureMessage(monitor[ConfigKey.NAME])} +

+ ), + toastLifeTimeMs: 3000, + }); + setIsEnabled(null); + } else if (status === FETCH_STATUS.SUCCESS) { + notifications.toasts.success({ + title: ( +

+ {isEnabled + ? getMonitorEnabledSuccessLabel(monitor[ConfigKey.NAME]) + : getMonitorDisabledSuccessLabel(monitor[ConfigKey.NAME])} +

+ ), + toastLifeTimeMs: 3000, + }); + setRefresh(true); + } + }, [status]); // eslint-disable-line react-hooks/exhaustive-deps + + const enabled = isEnabled ?? monitor[ConfigKey.ENABLED]; + const isLoading = status === FETCH_STATUS.LOADING; + + const handleEnabledChange = (event: EuiSwitchEvent) => { + const checked = event.target.checked; + setIsEnabled(checked); + }; + + return ( +
+ + {isLoading ? ( + + ) : null} +
+ ); +}; + +const ENABLE_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.enableMonitorLabel', { + defaultMessage: 'Enable monitor', +}); + +const DISABLE_MONITOR_LABEL = i18n.translate('xpack.uptime.monitorManagement.disableMonitorLabel', { + defaultMessage: 'Disable monitor', +}); + +const getMonitorEnabledSuccessLabel = (name: string) => + i18n.translate('xpack.uptime.monitorManagement.monitorEnabledSuccessMessage', { + defaultMessage: 'Monitor {name} enabled successfully.', + values: { name }, + }); + +const getMonitorDisabledSuccessLabel = (name: string) => + i18n.translate('xpack.uptime.monitorManagement.monitorDisabledSuccessMessage', { + defaultMessage: 'Monitor {name} disabled successfully.', + values: { name }, + }); + +const getMonitorEnabledUpdateFailureMessage = (name: string) => + i18n.translate('xpack.uptime.monitorManagement.monitorEnabledUpdateFailureMessage', { + defaultMessage: 'Unable to update monitor {name}.', + values: { name }, + }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.tsx index 813511b31761a..75c94c2d07d1e 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_list/monitor_list.tsx @@ -7,12 +7,14 @@ import React, { useContext, useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiPanel, EuiSpacer, EuiLink } from '@elastic/eui'; +import { SyntheticsMonitorSavedObject } from '../../../../common/types'; import { MonitorManagementList as MonitorManagementListState } from '../../../state/reducers/monitor_management'; -import { MonitorFields } from '../../../../common/runtime_types'; +import { MonitorFields, SyntheticsMonitor } from '../../../../common/runtime_types'; import { UptimeSettingsContext } from '../../../contexts'; import { Actions } from './actions'; import { MonitorLocations } from './monitor_locations'; import { MonitorTags } from './tags'; +import { MonitorEnabled } from './monitor_enabled'; import * as labels from '../../overview/monitor_list/translations'; interface Props { @@ -32,7 +34,8 @@ export const MonitorManagementList = ({ setPageSize, setPageIndex, }: Props) => { - const { monitors, total, perPage, page: pageIndex } = list as MonitorManagementListState['list']; + const { total, perPage, page: pageIndex } = list as MonitorManagementListState['list']; + const monitors = list.monitors as SyntheticsMonitorSavedObject[]; const { basePath } = useContext(UptimeSettingsContext); const pagination = useMemo( @@ -84,7 +87,7 @@ export const MonitorManagementList = ({ name: i18n.translate('xpack.uptime.monitorManagement.monitorList.monitorType', { defaultMessage: 'Monitor type', }), - render: ({ type }: Partial) => type, + render: ({ type }: SyntheticsMonitor) => type, }, { align: 'left' as const, @@ -92,7 +95,7 @@ export const MonitorManagementList = ({ name: i18n.translate('xpack.uptime.monitorManagement.monitorList.tags', { defaultMessage: 'Tags', }), - render: ({ tags }: Partial) => (tags ? : null), + render: ({ tags }: SyntheticsMonitor) => (tags ? : null), }, { align: 'left' as const, @@ -100,7 +103,7 @@ export const MonitorManagementList = ({ name: i18n.translate('xpack.uptime.monitorManagement.monitorList.locations', { defaultMessage: 'Locations', }), - render: ({ locations }: Partial) => + render: ({ locations }: SyntheticsMonitor) => locations ? : null, }, { @@ -109,8 +112,7 @@ export const MonitorManagementList = ({ name: i18n.translate('xpack.uptime.monitorManagement.monitorList.schedule', { defaultMessage: 'Schedule', }), - render: ({ schedule }: Partial) => - `@every ${schedule?.number}${schedule?.unit}`, + render: ({ schedule }: SyntheticsMonitor) => `@every ${schedule?.number}${schedule?.unit}`, }, { align: 'left' as const, @@ -118,9 +120,19 @@ export const MonitorManagementList = ({ name: i18n.translate('xpack.uptime.monitorManagement.monitorList.URL', { defaultMessage: 'URL', }), - render: (attributes: Partial) => attributes.urls || attributes.hosts, + render: (attributes: MonitorFields) => attributes.urls || attributes.hosts, truncateText: true, }, + { + align: 'left' as const, + field: 'attributes', + name: i18n.translate('xpack.uptime.monitorManagement.monitorList.enabled', { + defaultMessage: 'Enabled', + }), + render: (attributes: SyntheticsMonitor, record: SyntheticsMonitorSavedObject) => ( + + ), + }, { align: 'left' as const, field: 'id', diff --git a/x-pack/plugins/uptime/public/pages/monitor_management/monitor_management.tsx b/x-pack/plugins/uptime/public/pages/monitor_management/monitor_management.tsx index a272583a2e7b7..0619f4d4bed1c 100644 --- a/x-pack/plugins/uptime/public/pages/monitor_management/monitor_management.tsx +++ b/x-pack/plugins/uptime/public/pages/monitor_management/monitor_management.tsx @@ -24,7 +24,7 @@ export const MonitorManagementPage: React.FC = () => { useEffect(() => { if (refresh) { dispatch(getMonitors({ page: pageIndex, perPage: pageSize })); - setRefresh(false); + setRefresh(false); // TODO: avoid extra re-rendering when `refresh` turn to false (pass down the handler instead) } }, [dispatch, refresh, pageIndex, pageSize]); diff --git a/x-pack/plugins/uptime/public/state/api/monitor_management.ts b/x-pack/plugins/uptime/public/state/api/monitor_management.ts index 33c04c060588d..5f18869257386 100644 --- a/x-pack/plugins/uptime/public/state/api/monitor_management.ts +++ b/x-pack/plugins/uptime/public/state/api/monitor_management.ts @@ -17,14 +17,13 @@ import { import { SyntheticsMonitorSavedObject } from '../../../common/types'; import { apiService } from './utils'; -// TODO: Type the return type from runtime types export const setMonitor = async ({ monitor, id, }: { monitor: SyntheticsMonitor; id?: string; -}): Promise => { +}): Promise => { if (id) { return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor); } else { @@ -32,7 +31,6 @@ export const setMonitor = async ({ } }; -// TODO, change to monitor runtime type export const getMonitor = async ({ id }: { id: string }): Promise => { return await apiService.get(`${API_URLS.SYNTHETICS_MONITORS}/${id}`); };