From f258d357be4173bcc636220feb269776330d2c24 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 4 Mar 2019 10:40:30 -0500 Subject: [PATCH 01/20] replacing Angular watch list UI with React one --- .../common/constants/{index.js => index.ts} | 0 .../{watch_states.js => watch_states.ts} | 14 +- .../components/delete_watches_modal.tsx | 88 ++++++ x-pack/plugins/watcher/public/lib/api.ts | 34 +++ .../public/lib/manage_angular_lifecycle.js | 23 ++ .../watch_list/components/watch_list.tsx | 267 ++++++++++++++++++ .../components/watch_list/_index.scss | 1 - .../components/watch_list/_watch_list.scss | 6 - .../watch_list/components/watch_list/index.js | 7 - .../components/watch_list/watch_list.html | 121 -------- .../components/watch_list/watch_list.js | 210 -------------- .../components/watch_table/index.js | 7 - .../components/watch_table/watch_table.html | 174 ------------ .../components/watch_table/watch_table.js | 89 ------ .../sections/watch_list/watch_list_route.html | 5 +- .../sections/watch_list/watch_list_route.js | 63 +++-- .../api/watches/register_delete_route.js | 15 +- 17 files changed, 467 insertions(+), 657 deletions(-) rename x-pack/plugins/watcher/common/constants/{index.js => index.ts} (100%) rename x-pack/plugins/watcher/common/constants/{watch_states.js => watch_states.ts} (77%) create mode 100644 x-pack/plugins/watcher/public/components/delete_watches_modal.tsx create mode 100644 x-pack/plugins/watcher/public/lib/api.ts create mode 100644 x-pack/plugins/watcher/public/lib/manage_angular_lifecycle.js create mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_index.scss delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_watch_list.scss delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/index.js delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.html delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.js delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/index.js delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.html delete mode 100644 x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.js diff --git a/x-pack/plugins/watcher/common/constants/index.js b/x-pack/plugins/watcher/common/constants/index.ts similarity index 100% rename from x-pack/plugins/watcher/common/constants/index.js rename to x-pack/plugins/watcher/common/constants/index.ts diff --git a/x-pack/plugins/watcher/common/constants/watch_states.js b/x-pack/plugins/watcher/common/constants/watch_states.ts similarity index 77% rename from x-pack/plugins/watcher/common/constants/watch_states.js rename to x-pack/plugins/watcher/common/constants/watch_states.ts index 1c546139b688c..4247982dd01e0 100644 --- a/x-pack/plugins/watcher/common/constants/watch_states.js +++ b/x-pack/plugins/watcher/common/constants/watch_states.ts @@ -6,26 +6,24 @@ import { i18n } from '@kbn/i18n'; -export const WATCH_STATES = { - +export const WATCH_STATES: { [key: string]: string } = { DISABLED: i18n.translate('xpack.watcher.constants.watchStates.disabledStateText', { - defaultMessage: 'Disabled' + defaultMessage: 'Disabled', }), OK: i18n.translate('xpack.watcher.constants.watchStates.okStateText', { - defaultMessage: 'OK' + defaultMessage: 'OK', }), FIRING: i18n.translate('xpack.watcher.constants.watchStates.firingStateText', { - defaultMessage: 'Firing' + defaultMessage: 'Firing', }), ERROR: i18n.translate('xpack.watcher.constants.watchStates.errorStateText', { - defaultMessage: 'Error!' + defaultMessage: 'Error!', }), CONFIG_ERROR: i18n.translate('xpack.watcher.constants.watchStates.configErrorStateText', { - defaultMessage: 'Config error' + defaultMessage: 'Config error', }), - }; diff --git a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx new file mode 100644 index 0000000000000..e4e0c77e03d46 --- /dev/null +++ b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { toastNotifications } from 'ui/notify'; +import { deleteWatches } from '../lib/api'; + +export const DeleteWatchesModal = ({ + watchesToDelete, + callback, +}: { + watchesToDelete: string[]; + callback: (deleted?: string[]) => void; +}) => { + const numWatchesToDelete = watchesToDelete.length; + if (!numWatchesToDelete) { + return null; + } + const confirmModalText = i18n.translate( + 'xpack.watcher.deleteSelectedWatchesConfirmModal.descriptionText', + { + defaultMessage: + 'This will permanently delete {numWatchesToDelete, plural, one {a watch} other {# watches}}. Are you sure?', + values: { numWatchesToDelete }, + } + ); + const confirmButtonText = i18n.translate( + 'xpack.watcher.deleteSelectedWatchesConfirmModal.deleteButtonLabel', + { + defaultMessage: 'Delete {numWatchesToDelete, plural, one {watch} other {# watches}} ', + values: { numWatchesToDelete }, + } + ); + const cancelButtonText = i18n.translate( + 'xpack.watcher.deleteSelectedWatchesConfirmModal.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + ); + return ( + + { + const numTotal = watchesToDelete.length; + const { successes, errors } = await deleteWatches(watchesToDelete); + const numSuccesses = successes.length; + const numErrors = errors.length; + callback(successes); + if (numSuccesses > 0) { + toastNotifications.addSuccess( + i18n.translate( + 'xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText', + { + defaultMessage: + 'Deleted {numSuccesses} {numTotal, plural, one {watch} other {out of # selected watches}} ', + values: { numSuccesses, numTotal, numWatchesToDelete }, + } + ) + ); + } + + if (numErrors > 0) { + toastNotifications.addError( + i18n.translate( + 'xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText', + { + defaultMessage: + "Couldn't delete {numTotal, plural, one {watch} other {{numErrors} out of # selected watches}}", + values: { numErrors, numTotal, numWatchesToDelete }, + } + ) + ); + } + }} + cancelButtonText={cancelButtonText} + confirmButtonText={confirmButtonText} + > + {confirmModalText} + + + ); +}; diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts new file mode 100644 index 0000000000000..1196483d5cd0e --- /dev/null +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Watch } from 'plugins/watcher/models/watch'; +import chrome from 'ui/chrome'; +import { ROUTES } from '../../common/constants'; +let httpClient; +export const setHttpClient = anHttpClient => { + httpClient = anHttpClient; +}; +export const getHttpClient = () => { + return httpClient; +}; +const basePath = chrome.addBasePath(ROUTES.API_ROOT); +export const fetchWatches = async () => { + const { + data: { watches }, + } = await getHttpClient().get(`${basePath}/watches`); + return watches.map(watch => { + return Watch.fromUpstreamJson(watch); + }); +}; +export const deleteWatches = async (watchIds: string[]) => { + const body = { + watchIds, + }; + const { + data: { results }, + } = await getHttpClient().post(`${basePath}/watches/delete`, body); + return results; +}; diff --git a/x-pack/plugins/watcher/public/lib/manage_angular_lifecycle.js b/x-pack/plugins/watcher/public/lib/manage_angular_lifecycle.js new file mode 100644 index 0000000000000..3813e632a0a73 --- /dev/null +++ b/x-pack/plugins/watcher/public/lib/manage_angular_lifecycle.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { unmountComponentAtNode } from 'react-dom'; + +export const manageAngularLifecycle = ($scope, $route, elem) => { + const lastRoute = $route.current; + + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + if (lastRoute.$$route.template === currentRoute.$$route.template) { + $route.current = lastRoute; + } + }); + + $scope.$on('$destroy', () => { + deregister && deregister(); + elem && unmountComponentAtNode(elem); + }); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx new file mode 100644 index 0000000000000..870469c1bf9c1 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useEffect, useState } from 'react'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiInMemoryTable, + EuiLink, + EuiPageContent, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { Moment } from 'moment'; +import { REFRESH_INTERVALS, WATCH_STATES } from '../../../../common/constants'; +import { DeleteWatchesModal } from '../../../components/delete_watches_modal'; +import { fetchWatches } from '../../../lib/api'; + +const stateToIcon: { [key: string]: JSX.Element } = { + [WATCH_STATES.OK]: , + [WATCH_STATES.DISABLED]: , + [WATCH_STATES.FIRING]: , + [WATCH_STATES.ERROR]: , + [WATCH_STATES.CONFIG_ERROR]: , +}; + +const WatchListUi = ({ intl, urlService }: { intl: InjectedIntl; urlService: object }) => { + // hooks + const [watchesLoading, setWatchesLoading] = useState(true); + const [watchesToDelete, setWatchesToDelete] = useState([]); + const [watches, setWatches] = useState([]); + const [selection, setSelection] = useState([]); + const loadWatches = async () => { + const loadedWatches = await fetchWatches(); + setWatches(loadedWatches); + setWatchesLoading(false); + }; + useEffect(() => { + loadWatches(); + const loadInterval = setInterval(loadWatches, REFRESH_INTERVALS.WATCH_LIST); + return () => { + clearInterval(loadInterval); + }; + }, []); + const columns = [ + { + field: 'id', + name: i18n.translate('xpack.watcher.sections.watchList.watchTable.idHeader', { + defaultMessage: 'ID', + }), + sortable: true, + truncateText: true, + render: (id: string) => { + return ( + { + urlService.change(`/management/elasticsearch/watcher/watches/watch/${id}/status`); + }} + > + {id} + + ); + }, + }, + { + field: 'name', + name: i18n.translate('xpack.watcher.sections.watchList.watchTable.nameHeader', { + defaultMessage: 'Name', + }), + sortable: true, + truncateText: true, + }, + { + field: 'watchStatus.state', + name: i18n.translate('xpack.watcher.sections.watchList.watchTable.stateHeader', { + defaultMessage: 'State', + }), + sortable: true, + render: (state: string) => { + return ( + + {stateToIcon[state]} + {` ${state}`} + + ); + }, + }, + { + field: 'watchStatus.comment', + name: i18n.translate('xpack.watcher.sections.watchList.watchTable.commentHeader', { + defaultMessage: 'Comment', + }), + sortable: true, + truncateText: true, + }, + { + field: 'watchStatus.lastMetCondition', + name: i18n.translate('xpack.watcher.sections.watchList.watchTable.lastFiredHeader', { + defaultMessage: 'Last fired', + }), + sortable: true, + truncateText: true, + render: (lastMetCondition: Moment) => { + return lastMetCondition ? lastMetCondition.fromNow() : lastMetCondition; + }, + }, + { + field: 'watchStatus.lastChecked', + name: i18n.translate('xpack.watcher.sections.watchList.watchTable.lastTriggeredHeader', { + defaultMessage: 'Last triggered', + }), + sortable: true, + truncateText: true, + render: (lastChecked: Moment) => { + return lastChecked ? lastChecked.fromNow() : lastChecked; + }, + }, + { + actions: [ + { + render: watch => { + const disabled = watch.isSystemWatch || watch.isBeingDeleted; + return ( + { + urlService.change( + `/management/elasticsearch/watcher/watches/watch/${watch.id}/edit` + ); + }} + > + + + ); + }, + }, + ], + }, + ]; + const selectionConfig = { + onSelectionChange: setSelection, + }; + const pagination = { + initialPageSize: 10, + pageSizeOptions: [10, 50, 100], + }; + const searchConfig = { + box: { + incremental: true, + }, + toolsRight: ( + { + setWatchesToDelete(selection.map(selected => selected.id)); + }} + color="danger" + disabled={!selection.length} + > + + + ), + }; + return ( + + { + if (deleted) { + setWatches( + watches.filter(watch => { + return !deleted.includes(watch.id); + }) + ); + } + setWatchesToDelete([]); + }} + watchesToDelete={watchesToDelete} + /> + + + +

+ +

+
+ + +

+ +

+
+
+
+ + { + urlService.change('/management/elasticsearch/watcher/watches/new-watch/threshold'); + }} + > + + {' '} + { + urlService.change('/management/elasticsearch/watcher/watches/new-watch/json'); + }} + > + + + + + } + /> +
+ ); +}; +export const WatchList = injectI18n(WatchListUi); diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_index.scss b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_index.scss deleted file mode 100644 index 068a77f2e54b2..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './watch_list'; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_watch_list.scss b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_watch_list.scss deleted file mode 100644 index c26daaee9a08c..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/_watch_list.scss +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 1. Watch list width collapses without this. - */ -.watcherWatchList { - width: 100%; /* 1 */ -} diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/index.js b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/index.js deleted file mode 100644 index 1097bb146ff05..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import './watch_list'; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.html b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.html deleted file mode 100644 index e0fc117fa0c5c..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.html +++ /dev/null @@ -1,121 +0,0 @@ - -
- - {{ 'xpack.watcher.sections.watchList.noPermissionToManageWatchesText' | i18n: { defaultMessage: 'You do not have permission to manage watches.' } }} - - -
-

- Watcher -

-
-

-
-
-
- - - -
- -
- -
-
-
- -
- -
- -
- -
- - -
-
- - - - - - {{ 'xpack.watcher.sections.watchList.watchesNotFoundText' | i18n: { defaultMessage: 'No watches found.' } }} - - -
-
- -
- -
- - -
-
-
-
-
-
diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.js b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.js deleted file mode 100644 index 548581ea6be7a..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list/watch_list.js +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uiModules } from 'ui/modules'; -import { InitAfterBindingsWorkaround } from 'ui/compat'; -import { toastNotifications } from 'ui/notify'; -import template from './watch_list.html'; -import '../watch_table'; -import { PAGINATION, REFRESH_INTERVALS, WATCH_TYPES } from 'plugins/watcher/../common/constants'; -import 'ui/pager_control'; -import 'ui/pager'; -import 'ui/react_components'; -import 'ui/table_info'; -import 'plugins/watcher/components/tool_bar_selected_count'; -import 'plugins/watcher/components/forbidden_message'; -import 'plugins/watcher/services/watches'; -import 'plugins/watcher/services/license'; - -const app = uiModules.get('xpack/watcher'); - -app.directive('watchList', function ($injector, i18n) { - const pagerFactory = $injector.get('pagerFactory'); - const watchesService = $injector.get('xpackWatcherWatchesService'); - const licenseService = $injector.get('xpackWatcherLicenseService'); - const confirmModal = $injector.get('confirmModal'); - const $interval = $injector.get('$interval'); - const kbnUrl = $injector.get('kbnUrl'); - - const $filter = $injector.get('$filter'); - const filter = $filter('filter'); - const orderBy = $filter('orderBy'); - const limitTo = $filter('limitTo'); - - return { - restrict: 'E', - template: template, - scope: { - watches: '=' - }, - bindToController: true, - controllerAs: 'watchList', - controller: class WatchListController extends InitAfterBindingsWorkaround { - initAfterBindings($scope) { - this.forbidden = false; - - //The initial load from watch_list_route will return null on a 403 error - if (this.watches === null) { - this.watches = []; - this.forbidden = true; - } - - this.selectedWatches = []; - this.pageOfWatches = []; - this.sortField = 'id'; - this.sortReverse = false; - - this.pager = pagerFactory.create(this.watches.length, PAGINATION.PAGE_SIZE, 1); - - // Reload watches periodically - const refreshInterval = $interval(() => this.loadWatches(), REFRESH_INTERVALS.WATCH_LIST); - $scope.$on('$destroy', () => $interval.cancel(refreshInterval)); - - // react to watch and ui changes - $scope.$watchMulti([ - 'watchList.watches', - 'watchList.sortField', - 'watchList.sortReverse', - 'watchList.query', - 'watchList.pager.currentPage' - ], this.applyFilters); - } - - get hasPageOfWatches() { - return this.pageOfWatches.length > 0; - } - - get hasSelectedWatches() { - return this.selectedWatches.length > 0; - } - - loadWatches = () => { - watchesService.getWatchList() - .then(watches => { - this.watches = watches; - this.forbidden = false; - }) - .catch(err => { - return licenseService.checkValidity() - .then(() => { - if (err.status === 403) { - this.forbidden = true; - } else { - toastNotifications.addDanger(err.data.message); - } - }); - }); - } - - onQueryChange = (query) => { - this.query = query; - }; - - onPageNext = () => { - this.pager.nextPage(); - }; - - onPagePrevious = () => { - this.pager.previousPage(); - }; - - onSortChange = (field, reverse) => { - this.sortField = field; - this.sortReverse = reverse; - }; - - onSelectedChange = (selectedWatches) => { - this.selectedWatches = selectedWatches; - }; - - onClickCreateThresholdAlert = () => { - this.goToWatchWizardForType(WATCH_TYPES.THRESHOLD); - }; - - onClickCreateAdvancedWatch = () => { - this.goToWatchWizardForType(WATCH_TYPES.JSON); - }; - - goToWatchWizardForType = (watchType) => { - const url = `management/elasticsearch/watcher/watches/new-watch/${watchType}`; - kbnUrl.change(url, {}); - }; - - onSelectedWatchesDelete = () => { - const watchesBeingDeleted = this.selectedWatches; - const numWatchesToDelete = watchesBeingDeleted.length; - - const confirmModalText = i18n('xpack.watcher.sections.watchList.deleteSelectedWatchesConfirmModal.descriptionText', { - defaultMessage: 'This will permanently delete {numWatchesToDelete, plural, one {# Watch} other {# Watches}}. Are you sure?', - values: { numWatchesToDelete } - }); - const confirmButtonText = i18n('xpack.watcher.sections.watchList.deleteSelectedWatchesConfirmModal.deleteButtonLabel', { - defaultMessage: 'Delete {numWatchesToDelete, plural, one {# Watch} other {# Watches}} ', - values: { numWatchesToDelete } - }); - - const confirmModalOptions = { - confirmButtonText, - onConfirm: () => this.deleteSelectedWatches(watchesBeingDeleted) - }; - - return confirmModal(confirmModalText, confirmModalOptions); - }; - - deleteSelectedWatches = (watchesBeingDeleted) => { - this.watchesBeingDeleted = watchesBeingDeleted; - - const numWatchesToDelete = this.watchesBeingDeleted.length; - - const watchIds = this.watchesBeingDeleted.map(watch => watch.id); - return watchesService.deleteWatches(watchIds) - .then(results => { - const numSuccesses = results.numSuccesses; - const numErrors = results.numErrors; - const numTotal = numWatchesToDelete; - - if (numSuccesses > 0) { - toastNotifications.addSuccess( - i18n('xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText', { - defaultMessage: - 'Deleted {numSuccesses} out of {numTotal} selected {numWatchesToDelete, plural, one {# watch} other {# watches}}', - values: { numSuccesses, numTotal, numWatchesToDelete } - }) - ); - } - - if (numErrors > 0) { - toastNotifications.addError( - i18n('xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText', { - defaultMessage: - 'Couldn\'t delete {numErrors} out of {numTotal} selected {numWatchesToDelete, plural, one {# watch} other {# watches}}', - values: { numErrors, numTotal, numWatchesToDelete } - }) - ); - } - - this.loadWatches(); - }) - .catch(err => { - return licenseService.checkValidity() - .then(() => toastNotifications.addDanger(err.data.message)); - }); - } - - applyFilters = () => { - let filteredWatches = this.watches; - let pageOfWatches = []; - - filteredWatches = filter(filteredWatches, { searchValue: this.query }); - filteredWatches = orderBy(filteredWatches, this.sortField, this.sortReverse); - pageOfWatches = limitTo(filteredWatches, this.pager.pageSize, this.pager.startIndex); - - this.pageOfWatches = pageOfWatches; - this.pager.setTotalItems(filteredWatches.length); - }; - } - }; -}); diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/index.js b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/index.js deleted file mode 100644 index cb748ea048961..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import './watch_table'; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.html b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.html deleted file mode 100644 index b4c0adb60435d..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - {{ 'xpack.watcher.sections.watchList.watchTable.idColumnLabel' | i18n: { defaultMessage: 'ID' } }} - - - - {{ 'xpack.watcher.sections.watchList.watchTable.nameColumnLabel' | i18n: { defaultMessage: 'Name' } }} - - - - {{ 'xpack.watcher.sections.watchList.watchTable.stateColumnLabel' | i18n: { defaultMessage: 'State' } }} - - - - {{ 'xpack.watcher.sections.watchList.watchTable.commentColumnLabel' | i18n: { defaultMessage: 'Comment' } }} - - - - {{ 'xpack.watcher.sections.watchList.watchTable.lastFiredColumnLabel' | i18n: { defaultMessage: 'Last Fired' } }} - - - - {{ 'xpack.watcher.sections.watchList.watchTable.lastTriggeredColumnLabel' | i18n: { defaultMessage: 'Last Triggered' } }} - -
-
- -
-
-
- - - {{item.watch.id}} - - - {{item.watch.id}} - - -
-
-
- - {{item.watch.name}} - -
-
-
- - - {{ item.watch.watchStatus.state }} - -
-
-
- - {{item.watch.watchStatus.comment}} - -
-
-
- - - {{item.watch.watchStatus.lastFiredHumanized}} - - -
-
-
- - - {{item.watch.watchStatus.lastCheckedHumanized}} - - -
-
-
- -
-
diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.js b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.js deleted file mode 100644 index f442b5f8447f4..0000000000000 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_table/watch_table.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import { uiModules } from 'ui/modules'; -import 'ui/check_box'; -import 'ui/sortable_column'; -import template from './watch_table.html'; -import 'plugins/watcher/components/watch_state_icon'; - -const app = uiModules.get('xpack/watcher'); - -app.directive('watchTable', function () { - return { - restrict: 'E', - replace: true, - template: template, - scope: { - watches: '=', - watchesBeingDeleted: '=', - sortField: '=', - sortReverse: '=', - onSortChange: '=', - onSelectChange: '=' - }, - controllerAs: 'watchTable', - bindToController: true, - controller: class WatchTableController { - constructor($scope) { - this.allSelected = false; - - $scope.$watch('watchTable.watches', (watches) => { - const previousItems = this.items; - - this.items = _.map(watches, (watch) => { - const matchedItem = _.find(previousItems, previousItem => previousItem.watch.id === watch.id); - const selected = Boolean(_.get(matchedItem, 'selected')); - return { watch: watch, selected: selected }; - }); - this.editableItems = this.items.filter(item => this.isEditable(item)); - this.updateSelectedWatches(); - }); - - $scope.$watch('watchTable.watchesBeingDeleted', watches => { - this.items.forEach(item => { - const matchedItem = _.find(watches, watch => watch.id === item.watch.id); - item.selected = false; - item.isBeingDeleted = Boolean(matchedItem); - }); - this.editableItems = this.items.filter(item => this.isEditable(item)); - this.updateSelectedWatches(); - }); - } - - onAllSelectedChange = (itemId, allSelected) => { - _.forEach(this.editableItems, item => { - item.selected = allSelected; - }); - this.updateSelectedWatches(); - }; - - onWatchSelectedChange = (watchId, selected) => { - _.find(this.items, item => item.watch.id === watchId).selected = selected; - this.updateSelectedWatches(); - }; - - updateSelectedWatches = () => { - const selectedItems = _.filter(this.items, item => item.selected); - const selectedWatches = _.map(selectedItems, item => item.watch); - - const areAllEditableItemsSelected = selectedWatches.length === this.editableItems.length; - this.allSelected = areAllEditableItemsSelected && this.editableItems.length > 0; - - this.onSelectChange(selectedWatches); - }; - - isEditable = (item) => { - return !item.watch.isSystemWatch && !item.isBeingDeleted; - } - - areAnyEditable = () => { - return this.editableItems.length !== 0; - } - } - }; -}); diff --git a/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.html b/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.html index ce7b6a2e892dd..13203e912b370 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.html +++ b/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.html @@ -1 +1,4 @@ - + +
+
+ diff --git a/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js b/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js index 822ac71eaebfc..3228acf6a27bd 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js +++ b/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js @@ -3,15 +3,27 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; import routes from 'ui/routes'; import { management } from 'ui/management'; -import { toastNotifications } from 'ui/notify'; import template from './watch_list_route.html'; -import './components/watch_list'; import 'plugins/watcher/services/license'; import { getWatchListBreadcrumbs } from '../../lib/breadcrumbs'; +import { WatchList } from './components/watch_list'; +import { setHttpClient } from '../../lib/api'; +import { I18nContext } from 'ui/i18n'; +import { manageAngularLifecycle } from '../../lib/manage_angular_lifecycle'; +let elem; +const renderReact = async (elem, urlService) => { + render( + + + , + elem + ); +}; routes .when('/management/elasticsearch/watcher/', { redirectTo: '/management/elasticsearch/watcher/watches/' @@ -21,38 +33,31 @@ routes .when('/management/elasticsearch/watcher/watches/', { template: template, controller: class WatchListRouteController { - constructor($injector) { + constructor($injector, $scope, $http, $rootScope, kbnUrl) { const $route = $injector.get('$route'); this.watches = $route.current.locals.watches; + // clean up previously rendered React app if one exists + // this happens because of React Router redirects + elem && unmountComponentAtNode(elem); + // NOTE: We depend upon Angular's $http service because it's decorated with interceptors, + // e.g. to check license status per request. + setHttpClient($http); + const urlService = { + change(url) { + kbnUrl.change(url); + $rootScope.$digest(); + } + }; + $scope.$$postDigest(() => { + elem = document.getElementById('watchListReactRoot'); + renderReact(elem, urlService); + manageAngularLifecycle($scope, $route, elem); + }); } + }, controllerAs: 'watchListRoute', k7Breadcrumbs: getWatchListBreadcrumbs, - resolve: { - watches: ($injector) => { - const watchesService = $injector.get('xpackWatcherWatchesService'); - const licenseService = $injector.get('xpackWatcherLicenseService'); - const kbnUrl = $injector.get('kbnUrl'); - - return watchesService.getWatchList() - .catch(err => { - return licenseService.checkValidity() - .then(() => { - if (err.status === 403) { - return null; - } - - toastNotifications.addDanger(err.data.message); - kbnUrl.redirect('/management'); - return Promise.reject(); - }); - }); - }, - checkLicense: ($injector) => { - const licenseService = $injector.get('xpackWatcherLicenseService'); - return licenseService.checkValidity(); - } - } }); routes.defaults(/\/management/, { diff --git a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js index 29ee6f60676bc..f7b3fe8f06b17 100644 --- a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js +++ b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js @@ -19,12 +19,19 @@ function deleteWatches(callWithRequest, watchIds) { return Promise.all(deletePromises) .then(results => { - const successes = results.filter(result => Boolean(result.success)); - const errors = results.filter(result => Boolean(result.error)); + const errors = []; + const successes = []; + results.forEach(({ success, error }) => { + if (success) { + successes.push(success._id); + } else if(error) { + errors.push(error._id); + } + }); return { - numSuccesses: successes.length, - numErrors: errors.length + successes, + errors }; }); } From 7612d094981557a7d5519d2ae6f7e81d99f752f0 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 4 Mar 2019 14:50:20 -0500 Subject: [PATCH 02/20] fixing TS issues --- .../{action_modes.js => action_modes.ts} | 6 +-- .../{action_states.js => action_states.ts} | 16 +++---- .../{action_types.js => action_types.ts} | 6 +-- .../constants/{agg_types.js => agg_types.ts} | 6 +-- .../{comparators.js => comparators.ts} | 6 +-- .../{error_codes.js => error_codes.ts} | 3 +- ...roll_settings.js => es_scroll_settings.ts} | 8 ++-- .../{index_names.js => index_names.ts} | 2 +- .../common/constants/{lists.js => lists.ts} | 4 +- .../{pagination.js => pagination.ts} | 4 +- .../constants/{watch_history.js => plugin.ts} | 4 +- ...resh_intervals.js => refresh_intervals.ts} | 4 +- .../common/constants/{routes.js => routes.ts} | 2 +- .../{sort_orders.js => sort_orders.ts} | 4 +- .../{time_units.js => time_units.ts} | 4 +- .../constants/{plugin.js => watch_history.ts} | 4 +- .../common/constants/watch_state_comments.js | 33 ------------- .../common/constants/watch_state_comments.ts | 46 +++++++++++++++++++ .../{watch_types.js => watch_types.ts} | 6 +-- .../watch_list/components/watch_list.tsx | 8 +++- 20 files changed, 92 insertions(+), 84 deletions(-) rename x-pack/plugins/watcher/common/constants/{action_modes.js => action_modes.ts} (94%) rename x-pack/plugins/watcher/common/constants/{action_states.js => action_states.ts} (82%) rename x-pack/plugins/watcher/common/constants/{action_types.js => action_types.ts} (82%) rename x-pack/plugins/watcher/common/constants/{agg_types.js => agg_types.ts} (82%) rename x-pack/plugins/watcher/common/constants/{comparators.js => comparators.ts} (78%) rename x-pack/plugins/watcher/common/constants/{error_codes.js => error_codes.ts} (85%) rename x-pack/plugins/watcher/common/constants/{es_scroll_settings.js => es_scroll_settings.ts} (77%) rename x-pack/plugins/watcher/common/constants/{index_names.js => index_names.ts} (84%) rename x-pack/plugins/watcher/common/constants/{lists.js => lists.ts} (74%) rename x-pack/plugins/watcher/common/constants/{pagination.js => pagination.ts} (77%) rename x-pack/plugins/watcher/common/constants/{watch_history.js => plugin.ts} (78%) rename x-pack/plugins/watcher/common/constants/{refresh_intervals.js => refresh_intervals.ts} (78%) rename x-pack/plugins/watcher/common/constants/{routes.js => routes.ts} (84%) rename x-pack/plugins/watcher/common/constants/{sort_orders.js => sort_orders.ts} (77%) rename x-pack/plugins/watcher/common/constants/{time_units.js => time_units.ts} (81%) rename x-pack/plugins/watcher/common/constants/{plugin.js => watch_history.ts} (74%) delete mode 100644 x-pack/plugins/watcher/common/constants/watch_state_comments.js create mode 100644 x-pack/plugins/watcher/common/constants/watch_state_comments.ts rename x-pack/plugins/watcher/common/constants/{watch_types.js => watch_types.ts} (77%) diff --git a/x-pack/plugins/watcher/common/constants/action_modes.js b/x-pack/plugins/watcher/common/constants/action_modes.ts similarity index 94% rename from x-pack/plugins/watcher/common/constants/action_modes.js rename to x-pack/plugins/watcher/common/constants/action_modes.ts index 245f237f16efa..5f703f14b3933 100644 --- a/x-pack/plugins/watcher/common/constants/action_modes.js +++ b/x-pack/plugins/watcher/common/constants/action_modes.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const ACTION_MODES = { - +export const ACTION_MODES: { [key: string]: string } = { // The action execution will be simulated. For example, The email action will create the email that would have been sent but will not actually send it. In this mode, the action may be throttled if the current state of the watch indicates it should be. SIMULATE: 'simulate', @@ -19,6 +18,5 @@ export const ACTION_MODES = { FORCE_EXECUTE: 'force_execute', // The action will be skipped and won’t be executed nor simulated. Effectively forcing the action to be throttled. - SKIP: 'skip' - + SKIP: 'skip', }; diff --git a/x-pack/plugins/watcher/common/constants/action_states.js b/x-pack/plugins/watcher/common/constants/action_states.ts similarity index 82% rename from x-pack/plugins/watcher/common/constants/action_states.js rename to x-pack/plugins/watcher/common/constants/action_states.ts index e34a5fd37ff3a..e9501fc2a60c4 100644 --- a/x-pack/plugins/watcher/common/constants/action_states.js +++ b/x-pack/plugins/watcher/common/constants/action_states.ts @@ -6,36 +6,34 @@ import { i18n } from '@kbn/i18n'; -export const ACTION_STATES = { - +export const ACTION_STATES: { [key: string]: string } = { // Action is not being executed because conditions haven't been met OK: i18n.translate('xpack.watcher.constants.actionStates.okStateText', { - defaultMessage: 'OK' + defaultMessage: 'OK', }), // Action has been acknowledged by user ACKNOWLEDGED: i18n.translate('xpack.watcher.constants.actionStates.acknowledgedStateText', { - defaultMessage: 'Acked' + defaultMessage: 'Acked', }), // Action has been throttled (time-based) by the system THROTTLED: i18n.translate('xpack.watcher.constants.actionStates.throttledStateText', { - defaultMessage: 'Throttled' + defaultMessage: 'Throttled', }), // Action has been completed FIRING: i18n.translate('xpack.watcher.constants.actionStates.firingStateText', { - defaultMessage: 'Firing' + defaultMessage: 'Firing', }), // Action has failed ERROR: i18n.translate('xpack.watcher.constants.actionStates.errorStateText', { - defaultMessage: 'Error' + defaultMessage: 'Error', }), // Action has a configuration error CONFIG_ERROR: i18n.translate('xpack.watcher.constants.actionStates.configErrorStateText', { - defaultMessage: 'Config error' + defaultMessage: 'Config error', }), - }; diff --git a/x-pack/plugins/watcher/common/constants/action_types.js b/x-pack/plugins/watcher/common/constants/action_types.ts similarity index 82% rename from x-pack/plugins/watcher/common/constants/action_types.js rename to x-pack/plugins/watcher/common/constants/action_types.ts index 7b408f0d3a0cc..3bb0d5748347c 100644 --- a/x-pack/plugins/watcher/common/constants/action_types.js +++ b/x-pack/plugins/watcher/common/constants/action_types.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const ACTION_TYPES = { - +export const ACTION_TYPES: { [key: string]: string } = { EMAIL: 'email', WEBHOOK: 'webhook', @@ -22,6 +21,5 @@ export const ACTION_TYPES = { PAGERDUTY: 'pagerduty', - UNKNOWN: 'unknown/invalid' - + UNKNOWN: 'unknown/invalid', }; diff --git a/x-pack/plugins/watcher/common/constants/agg_types.js b/x-pack/plugins/watcher/common/constants/agg_types.ts similarity index 82% rename from x-pack/plugins/watcher/common/constants/agg_types.js rename to x-pack/plugins/watcher/common/constants/agg_types.ts index 3f53a36e89127..1a0f78c33111c 100644 --- a/x-pack/plugins/watcher/common/constants/agg_types.js +++ b/x-pack/plugins/watcher/common/constants/agg_types.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const AGG_TYPES = { - +export const AGG_TYPES: { [key: string]: string } = { COUNT: 'count', AVERAGE: 'avg', @@ -14,6 +13,5 @@ export const AGG_TYPES = { MIN: 'min', - MAX: 'max' - + MAX: 'max', }; diff --git a/x-pack/plugins/watcher/common/constants/comparators.js b/x-pack/plugins/watcher/common/constants/comparators.ts similarity index 78% rename from x-pack/plugins/watcher/common/constants/comparators.js rename to x-pack/plugins/watcher/common/constants/comparators.ts index 69009b2f604b6..4a27b7e275b65 100644 --- a/x-pack/plugins/watcher/common/constants/comparators.js +++ b/x-pack/plugins/watcher/common/constants/comparators.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export const COMPARATORS = { - +export const COMPARATORS: { [key: string]: string } = { GREATER_THAN: '>', - LESS_THAN: '<' - + LESS_THAN: '<', }; diff --git a/x-pack/plugins/watcher/common/constants/error_codes.js b/x-pack/plugins/watcher/common/constants/error_codes.ts similarity index 85% rename from x-pack/plugins/watcher/common/constants/error_codes.js rename to x-pack/plugins/watcher/common/constants/error_codes.ts index 2fa875549358f..f5433ebb576e6 100644 --- a/x-pack/plugins/watcher/common/constants/error_codes.js +++ b/x-pack/plugins/watcher/common/constants/error_codes.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const ERROR_CODES = { - +export const ERROR_CODES: { [key: string]: string } = { // Property missing on object ERR_PROP_MISSING: 'ERR_PROP_MISSING', }; diff --git a/x-pack/plugins/watcher/common/constants/es_scroll_settings.js b/x-pack/plugins/watcher/common/constants/es_scroll_settings.ts similarity index 77% rename from x-pack/plugins/watcher/common/constants/es_scroll_settings.js rename to x-pack/plugins/watcher/common/constants/es_scroll_settings.ts index 25bdcca565ee9..a51a7b117eea6 100644 --- a/x-pack/plugins/watcher/common/constants/es_scroll_settings.js +++ b/x-pack/plugins/watcher/common/constants/es_scroll_settings.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -export const ES_SCROLL_SETTINGS = { - +export const ES_SCROLL_SETTINGS: { + KEEPALIVE: string; + PAGE_SIZE: number; +} = { // How long to keep a scroll alive KEEPALIVE: '30s', // How many results to return per scroll response - PAGE_SIZE: 100 + PAGE_SIZE: 100, }; diff --git a/x-pack/plugins/watcher/common/constants/index_names.js b/x-pack/plugins/watcher/common/constants/index_names.ts similarity index 84% rename from x-pack/plugins/watcher/common/constants/index_names.js rename to x-pack/plugins/watcher/common/constants/index_names.ts index 663d932fbe133..b9ca85963a33d 100644 --- a/x-pack/plugins/watcher/common/constants/index_names.js +++ b/x-pack/plugins/watcher/common/constants/index_names.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const INDEX_NAMES = { +export const INDEX_NAMES: { [key: string]: string } = { WATCHES: '.watches', WATCHER_HISTORY: '.watcher-history-*', }; diff --git a/x-pack/plugins/watcher/common/constants/lists.js b/x-pack/plugins/watcher/common/constants/lists.ts similarity index 74% rename from x-pack/plugins/watcher/common/constants/lists.js rename to x-pack/plugins/watcher/common/constants/lists.ts index da89b99f828ba..1224b279f4816 100644 --- a/x-pack/plugins/watcher/common/constants/lists.js +++ b/x-pack/plugins/watcher/common/constants/lists.ts @@ -5,6 +5,6 @@ */ // Durations are in ms -export const LISTS = { - NEW_ITEMS_HIGHLIGHT_DURATION: 1 * 1000 +export const LISTS: { [key: string]: number } = { + NEW_ITEMS_HIGHLIGHT_DURATION: 1 * 1000, }; diff --git a/x-pack/plugins/watcher/common/constants/pagination.js b/x-pack/plugins/watcher/common/constants/pagination.ts similarity index 77% rename from x-pack/plugins/watcher/common/constants/pagination.js rename to x-pack/plugins/watcher/common/constants/pagination.ts index fd44ffdecfc3b..8ae9d769831c5 100644 --- a/x-pack/plugins/watcher/common/constants/pagination.js +++ b/x-pack/plugins/watcher/common/constants/pagination.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PAGINATION = { - PAGE_SIZE: 20 +export const PAGINATION: { [key: string]: number } = { + PAGE_SIZE: 20, }; diff --git a/x-pack/plugins/watcher/common/constants/watch_history.js b/x-pack/plugins/watcher/common/constants/plugin.ts similarity index 78% rename from x-pack/plugins/watcher/common/constants/watch_history.js rename to x-pack/plugins/watcher/common/constants/plugin.ts index 3fad569ab7878..118e642467938 100644 --- a/x-pack/plugins/watcher/common/constants/watch_history.js +++ b/x-pack/plugins/watcher/common/constants/plugin.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const WATCH_HISTORY = { - INITIAL_RANGE: 'now-1h' +export const PLUGIN: { [key: string]: string } = { + ID: 'watcher', }; diff --git a/x-pack/plugins/watcher/common/constants/refresh_intervals.js b/x-pack/plugins/watcher/common/constants/refresh_intervals.ts similarity index 78% rename from x-pack/plugins/watcher/common/constants/refresh_intervals.js rename to x-pack/plugins/watcher/common/constants/refresh_intervals.ts index 1aff358bcd636..db1daa865c323 100644 --- a/x-pack/plugins/watcher/common/constants/refresh_intervals.js +++ b/x-pack/plugins/watcher/common/constants/refresh_intervals.ts @@ -7,8 +7,8 @@ // In milliseconds const SIXTY_SECONDS = 60 * 1000; -export const REFRESH_INTERVALS = { +export const REFRESH_INTERVALS: { [key: string]: number } = { WATCH_LIST: SIXTY_SECONDS, WATCH_HISTORY: SIXTY_SECONDS, - WATCH_VISUALIZATION: SIXTY_SECONDS + WATCH_VISUALIZATION: SIXTY_SECONDS, }; diff --git a/x-pack/plugins/watcher/common/constants/routes.js b/x-pack/plugins/watcher/common/constants/routes.ts similarity index 84% rename from x-pack/plugins/watcher/common/constants/routes.js rename to x-pack/plugins/watcher/common/constants/routes.ts index 97ae0ee6de181..73a1824c507da 100644 --- a/x-pack/plugins/watcher/common/constants/routes.js +++ b/x-pack/plugins/watcher/common/constants/routes.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const ROUTES = { +export const ROUTES: { [key: string]: string } = { API_ROOT: '/api/watcher', }; diff --git a/x-pack/plugins/watcher/common/constants/sort_orders.js b/x-pack/plugins/watcher/common/constants/sort_orders.ts similarity index 77% rename from x-pack/plugins/watcher/common/constants/sort_orders.js rename to x-pack/plugins/watcher/common/constants/sort_orders.ts index 062941929ddc6..c73d458a3018f 100644 --- a/x-pack/plugins/watcher/common/constants/sort_orders.js +++ b/x-pack/plugins/watcher/common/constants/sort_orders.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const SORT_ORDERS = { +export const SORT_ORDERS: { [key: string]: string } = { ASCENDING: 'asc', - DESCENDING: 'desc' + DESCENDING: 'desc', }; diff --git a/x-pack/plugins/watcher/common/constants/time_units.js b/x-pack/plugins/watcher/common/constants/time_units.ts similarity index 81% rename from x-pack/plugins/watcher/common/constants/time_units.js rename to x-pack/plugins/watcher/common/constants/time_units.ts index ea63c21e22213..c861d47416a80 100644 --- a/x-pack/plugins/watcher/common/constants/time_units.js +++ b/x-pack/plugins/watcher/common/constants/time_units.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export const TIME_UNITS = { +export const TIME_UNITS: { [key: string]: string } = { SECOND: 's', MINUTE: 'm', HOUR: 'h', - DAY: 'd' + DAY: 'd', }; diff --git a/x-pack/plugins/watcher/common/constants/plugin.js b/x-pack/plugins/watcher/common/constants/watch_history.ts similarity index 74% rename from x-pack/plugins/watcher/common/constants/plugin.js rename to x-pack/plugins/watcher/common/constants/watch_history.ts index bcff927863019..520fe1019c8c3 100644 --- a/x-pack/plugins/watcher/common/constants/plugin.js +++ b/x-pack/plugins/watcher/common/constants/watch_history.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN = { - ID: 'watcher' +export const WATCH_HISTORY: { [key: string]: string } = { + INITIAL_RANGE: 'now-1h', }; diff --git a/x-pack/plugins/watcher/common/constants/watch_state_comments.js b/x-pack/plugins/watcher/common/constants/watch_state_comments.js deleted file mode 100644 index cfa4d93d8a140..0000000000000 --- a/x-pack/plugins/watcher/common/constants/watch_state_comments.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const WATCH_STATE_COMMENTS = { - - OK: '', - - PARTIALLY_THROTTLED: i18n.translate('xpack.watcher.constants.watchStateComments.partiallyThrottledStateCommentText', { - defaultMessage: 'Partially Throttled' - }), - - THROTTLED: i18n.translate('xpack.watcher.constants.watchStateComments.throttledStateCommentText', { - defaultMessage: 'Throttled' - }), - - PARTIALLY_ACKNOWLEDGED: i18n.translate('xpack.watcher.constants.watchStateComments.partiallyAcknowledgedStateCommentText', { - defaultMessage: 'Partially Acked' - }), - - ACKNOWLEDGED: i18n.translate('xpack.watcher.constants.watchStateComments.acknowledgedStateCommentText', { - defaultMessage: 'Acked' - }), - - FAILING: i18n.translate('xpack.watcher.constants.watchStateComments.executionFailingStateCommentText', { - defaultMessage: 'Execution Failing' - }), - -}; diff --git a/x-pack/plugins/watcher/common/constants/watch_state_comments.ts b/x-pack/plugins/watcher/common/constants/watch_state_comments.ts new file mode 100644 index 0000000000000..c88425c7c1616 --- /dev/null +++ b/x-pack/plugins/watcher/common/constants/watch_state_comments.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const WATCH_STATE_COMMENTS: { [key: string]: string } = { + OK: '', + + PARTIALLY_THROTTLED: i18n.translate( + 'xpack.watcher.constants.watchStateComments.partiallyThrottledStateCommentText', + { + defaultMessage: 'Partially throttled', + } + ), + + THROTTLED: i18n.translate( + 'xpack.watcher.constants.watchStateComments.throttledStateCommentText', + { + defaultMessage: 'Throttled', + } + ), + + PARTIALLY_ACKNOWLEDGED: i18n.translate( + 'xpack.watcher.constants.watchStateComments.partiallyAcknowledgedStateCommentText', + { + defaultMessage: 'Partially acked', + } + ), + + ACKNOWLEDGED: i18n.translate( + 'xpack.watcher.constants.watchStateComments.acknowledgedStateCommentText', + { + defaultMessage: 'Acked', + } + ), + + FAILING: i18n.translate( + 'xpack.watcher.constants.watchStateComments.executionFailingStateCommentText', + { + defaultMessage: 'Execution failing', + } + ), +}; diff --git a/x-pack/plugins/watcher/common/constants/watch_types.js b/x-pack/plugins/watcher/common/constants/watch_types.ts similarity index 77% rename from x-pack/plugins/watcher/common/constants/watch_types.js rename to x-pack/plugins/watcher/common/constants/watch_types.ts index 51e89a19b6ca0..f0ebd3efc4078 100644 --- a/x-pack/plugins/watcher/common/constants/watch_types.js +++ b/x-pack/plugins/watcher/common/constants/watch_types.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export const WATCH_TYPES = { - +export const WATCH_TYPES: { [key: string]: string } = { JSON: 'json', THRESHOLD: 'threshold', - MONITORING: 'monitoring' - + MONITORING: 'monitoring', }; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index 870469c1bf9c1..4fc6586234153 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -34,7 +34,13 @@ const stateToIcon: { [key: string]: JSX.Element } = { [WATCH_STATES.CONFIG_ERROR]: , }; -const WatchListUi = ({ intl, urlService }: { intl: InjectedIntl; urlService: object }) => { +const WatchListUi = ({ + intl, + urlService, +}: { + intl: InjectedIntl; + urlService: { change: () => void }; +}) => { // hooks const [watchesLoading, setWatchesLoading] = useState(true); const [watchesToDelete, setWatchesToDelete] = useState([]); From 3e2e1cef387d6d32f77c0d880f87a751819fc89c Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 4 Mar 2019 15:55:33 -0500 Subject: [PATCH 03/20] addressing PR feedback --- .../components/delete_watches_modal.tsx | 11 ++- x-pack/plugins/watcher/public/index.scss | 2 +- .../watch_list/components/watch_list.tsx | 81 +++++++++---------- .../public/sections/watch_list/index.scss | 6 ++ .../sections/watch_list/watch_list_route.js | 69 +++++++--------- 5 files changed, 79 insertions(+), 90 deletions(-) create mode 100644 x-pack/plugins/watcher/public/sections/watch_list/index.scss diff --git a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx index e4e0c77e03d46..217413801a4f2 100644 --- a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx +++ b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx @@ -45,9 +45,8 @@ export const DeleteWatchesModal = ({ callback()} onConfirm={async () => { - const numTotal = watchesToDelete.length; const { successes, errors } = await deleteWatches(watchesToDelete); const numSuccesses = successes.length; const numErrors = errors.length; @@ -58,8 +57,8 @@ export const DeleteWatchesModal = ({ 'xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText', { defaultMessage: - 'Deleted {numSuccesses} {numTotal, plural, one {watch} other {out of # selected watches}} ', - values: { numSuccesses, numTotal, numWatchesToDelete }, + 'Deleted{numWatchesToDelete, plural, one {watch} other {{numSuccesses} out of # selected watches}} ', + values: { numSuccesses, numWatchesToDelete }, } ) ); @@ -71,8 +70,8 @@ export const DeleteWatchesModal = ({ 'xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText', { defaultMessage: - "Couldn't delete {numTotal, plural, one {watch} other {{numErrors} out of # selected watches}}", - values: { numErrors, numTotal, numWatchesToDelete }, + "Couldn't delete {numWatchesToDelete, plural, one {watch} other {{numErrors} out of # selected watches}}", + values: { numErrors, numWatchesToDelete }, } ) ); diff --git a/x-pack/plugins/watcher/public/index.scss b/x-pack/plugins/watcher/public/index.scss index b66733cc06c7e..ebf042c7cbd1c 100644 --- a/x-pack/plugins/watcher/public/index.scss +++ b/x-pack/plugins/watcher/public/index.scss @@ -26,4 +26,4 @@ @import 'sections/watch_edit/components/watch_edit_detail/index'; @import 'sections/watch_edit/components/watch_edit_execute_detail/index'; @import 'sections/watch_edit/components/watch_edit_title_panel/index'; -@import 'sections/watch_list/components/watch_list/index'; +@import 'sections/watch_list/index'; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index 4fc6586234153..59071d2e66714 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -34,22 +34,16 @@ const stateToIcon: { [key: string]: JSX.Element } = { [WATCH_STATES.CONFIG_ERROR]: , }; -const WatchListUi = ({ - intl, - urlService, -}: { - intl: InjectedIntl; - urlService: { change: () => void }; -}) => { +const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { // hooks - const [watchesLoading, setWatchesLoading] = useState(true); + const [isWatchesLoading, setIsWatchesLoading] = useState(true); const [watchesToDelete, setWatchesToDelete] = useState([]); const [watches, setWatches] = useState([]); const [selection, setSelection] = useState([]); const loadWatches = async () => { const loadedWatches = await fetchWatches(); setWatches(loadedWatches); - setWatchesLoading(false); + setIsWatchesLoading(false); }; useEffect(() => { loadWatches(); @@ -71,9 +65,7 @@ const WatchListUi = ({ { - urlService.change(`/management/elasticsearch/watcher/watches/watch/${id}/status`); - }} + href={`#/management/elasticsearch/watcher/watches/watch/${id}/status`} > {id} @@ -96,10 +88,12 @@ const WatchListUi = ({ sortable: true, render: (state: string) => { return ( - - {stateToIcon[state]} - {` ${state}`} - + + {stateToIcon[state]} + + {state} + + ); }, }, @@ -146,11 +140,7 @@ const WatchListUi = ({ id: 'xpack.watcher.sections.watchList.watchTable.menuEditButtonDescription', defaultMessage: 'Edit watch', })} - onClick={() => { - urlService.change( - `/management/elasticsearch/watcher/watches/watch/${watch.id}/edit` - ); - }} + href={`#/management/elasticsearch/watcher/watches/watch/${watch.id}/edit`} > - { - urlService.change('/management/elasticsearch/watcher/watches/new-watch/threshold'); - }} - > - - {' '} - { - urlService.change('/management/elasticsearch/watcher/watches/new-watch/json'); - }} - > - - + + + + + + + + + + + + + { +const renderReact = async elem => { render( - + , elem ); }; -routes - .when('/management/elasticsearch/watcher/', { - redirectTo: '/management/elasticsearch/watcher/watches/' - }); - -routes - .when('/management/elasticsearch/watcher/watches/', { - template: template, - controller: class WatchListRouteController { - constructor($injector, $scope, $http, $rootScope, kbnUrl) { - const $route = $injector.get('$route'); - this.watches = $route.current.locals.watches; - // clean up previously rendered React app if one exists - // this happens because of React Router redirects - elem && unmountComponentAtNode(elem); - // NOTE: We depend upon Angular's $http service because it's decorated with interceptors, - // e.g. to check license status per request. - setHttpClient($http); - const urlService = { - change(url) { - kbnUrl.change(url); - $rootScope.$digest(); - } - }; - $scope.$$postDigest(() => { - elem = document.getElementById('watchListReactRoot'); - renderReact(elem, urlService); - manageAngularLifecycle($scope, $route, elem); - }); - } +routes.when('/management/elasticsearch/watcher/', { + redirectTo: '/management/elasticsearch/watcher/watches/', +}); - }, - controllerAs: 'watchListRoute', - k7Breadcrumbs: getWatchListBreadcrumbs, - }); +routes.when('/management/elasticsearch/watcher/watches/', { + template: template, + controller: class WatchListRouteController { + constructor($injector, $scope, $http) { + const $route = $injector.get('$route'); + this.watches = $route.current.locals.watches; + // clean up previously rendered React app if one exists + // this happens because of React Router redirects + elem && unmountComponentAtNode(elem); + // NOTE: We depend upon Angular's $http service because it's decorated with interceptors, + // e.g. to check license status per request. + setHttpClient($http); + $scope.$$postDigest(() => { + elem = document.getElementById('watchListReactRoot'); + renderReact(elem); + manageAngularLifecycle($scope, $route, elem); + }); + } + }, + controllerAs: 'watchListRoute', + k7Breadcrumbs: getWatchListBreadcrumbs, +}); routes.defaults(/\/management/, { resolve: { - watcherManagementSection: ($injector) => { + watcherManagementSection: $injector => { const licenseService = $injector.get('xpackWatcherLicenseService'); const watchesSection = management.getSection('elasticsearch/watcher'); @@ -79,6 +70,6 @@ routes.defaults(/\/management/, { watchesSection.disable(); watchesSection.tooltip = licenseService.message; } - } - } + }, + }, }); From 00c501924b843658a38640076923f7d2a7b00ab0 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 4 Mar 2019 16:28:24 -0500 Subject: [PATCH 04/20] fixing TS issues --- .../watcher/public/components/delete_watches_modal.tsx | 2 +- x-pack/plugins/watcher/public/lib/api.ts | 4 ++-- .../public/sections/watch_list/components/watch_list.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx index 217413801a4f2..37242e91fb972 100644 --- a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx +++ b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx @@ -65,7 +65,7 @@ export const DeleteWatchesModal = ({ } if (numErrors > 0) { - toastNotifications.addError( + toastNotifications.addDanger( i18n.translate( 'xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText', { diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index 1196483d5cd0e..b342f995b7382 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -7,8 +7,8 @@ import { Watch } from 'plugins/watcher/models/watch'; import chrome from 'ui/chrome'; import { ROUTES } from '../../common/constants'; -let httpClient; -export const setHttpClient = anHttpClient => { +let httpClient: ng.IHttpService; +export const setHttpClient = (anHttpClient: ng.IHttpService) => { httpClient = anHttpClient; }; export const getHttpClient = () => { diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index 59071d2e66714..c3ebe09f2c09f 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiButton, From 54930a34d303866e3de7f2dc85cc29d5994e290a Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 4 Mar 2019 17:04:21 -0500 Subject: [PATCH 05/20] more TS fixes --- x-pack/plugins/watcher/public/lib/api.ts | 2 +- .../public/sections/watch_list/components/watch_list.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index b342f995b7382..789febed3ac38 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Watch } from 'plugins/watcher/models/watch'; import chrome from 'ui/chrome'; import { ROUTES } from '../../common/constants'; +import { Watch } from '../models/watch'; let httpClient: ng.IHttpService; export const setHttpClient = (anHttpClient: ng.IHttpService) => { httpClient = anHttpClient; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index c3ebe09f2c09f..57c67d6ef82ae 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -130,8 +130,8 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { { actions: [ { - render: watch => { - const disabled = watch.isSystemWatch || watch.isBeingDeleted; + render: (watch: any) => { + const disabled = watch.isSystemWatch; return ( Date: Mon, 4 Mar 2019 17:20:49 -0500 Subject: [PATCH 06/20] TS fix --- x-pack/plugins/watcher/public/lib/api.ts | 3 +-- x-pack/plugins/watcher/public/models/index.d.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/watcher/public/models/index.d.ts diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index 789febed3ac38..47055654513bc 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -3,10 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { Watch } from 'plugins/watcher/models/watch'; import chrome from 'ui/chrome'; import { ROUTES } from '../../common/constants'; -import { Watch } from '../models/watch'; let httpClient: ng.IHttpService; export const setHttpClient = (anHttpClient: ng.IHttpService) => { httpClient = anHttpClient; diff --git a/x-pack/plugins/watcher/public/models/index.d.ts b/x-pack/plugins/watcher/public/models/index.d.ts new file mode 100644 index 0000000000000..81787a375d44e --- /dev/null +++ b/x-pack/plugins/watcher/public/models/index.d.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +declare module 'plugins/watcher/models/watch' { + export const Watch: any; +} From e6cd294308a85df1196428bf3b915ba34d5469a8 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 4 Mar 2019 17:27:10 -0500 Subject: [PATCH 07/20] addressing more PR feedback --- .../watcher/public/components/delete_watches_modal.tsx | 2 +- .../public/sections/watch_list/components/watch_list.tsx | 4 ++-- x-pack/plugins/watcher/public/sections/watch_list/index.scss | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx index 37242e91fb972..b68c36eae14ca 100644 --- a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx +++ b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx @@ -57,7 +57,7 @@ export const DeleteWatchesModal = ({ 'xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText', { defaultMessage: - 'Deleted{numWatchesToDelete, plural, one {watch} other {{numSuccesses} out of # selected watches}} ', + 'Deleted {numWatchesToDelete, plural, one {watch} other {{numSuccesses} out of # selected watches}} ', values: { numSuccesses, numWatchesToDelete }, } ) diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index 57c67d6ef82ae..dd238fd0eb4cd 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -47,9 +47,9 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { }; useEffect(() => { loadWatches(); - const loadInterval = setInterval(loadWatches, REFRESH_INTERVALS.WATCH_LIST); + const refreshIntervalId = setInterval(loadWatches, REFRESH_INTERVALS.WATCH_LIST); return () => { - clearInterval(loadInterval); + clearInterval(refreshIntervalId); }; }, []); const columns = [ diff --git a/x-pack/plugins/watcher/public/sections/watch_list/index.scss b/x-pack/plugins/watcher/public/sections/watch_list/index.scss index cbe13c63d6a42..9b1371175acdf 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/index.scss +++ b/x-pack/plugins/watcher/public/sections/watch_list/index.scss @@ -1,6 +1,6 @@ /** * 1. Prevent inherited flexbox layout from compressing this element on IE. */ - .watchState__message { +.watchState__message { flex-basis: auto !important; /* 1 */ } \ No newline at end of file From 91f8ce7649b8c58c5ed29ec4d1af1674798e2bea Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Mon, 4 Mar 2019 17:52:45 -0500 Subject: [PATCH 08/20] TS fixes --- x-pack/plugins/watcher/public/lib/api.ts | 2 +- .../public/sections/watch_list/components/watch_list.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index 47055654513bc..e93adf6f71b2a 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -18,7 +18,7 @@ export const fetchWatches = async () => { const { data: { watches }, } = await getHttpClient().get(`${basePath}/watches`); - return watches.map(watch => { + return watches.map((watch: any) => { return Watch.fromUpstreamJson(watch); }); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index dd238fd0eb4cd..66b5f9b7ec2b7 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -168,7 +168,7 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { { - setWatchesToDelete(selection.map(selected => selected.id)); + setWatchesToDelete(selection.map((selected: any) => selected.id)); }} color="danger" disabled={!selection.length} @@ -186,7 +186,7 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { callback={(deleted?: string[]) => { if (deleted) { setWatches( - watches.filter(watch => { + watches.filter((watch: any) => { return !deleted.includes(watch.id); }) ); From 1876c2fcec423abae3183b3cd6a62d4e9d3f3bcf Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 09:23:17 -0500 Subject: [PATCH 09/20] removing unused/incompatible translations --- .../translations/translations/zh-CN.json | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b7575863f4fbf..a60024b76931c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8129,31 +8129,14 @@ "xpack.watcher.sections.watchHistoryItem.executionOutputLabel": "执行输出:", "xpack.watcher.sections.watchHistoryItem.watchSummary.executionStatusLabel": "执行状态:", "xpack.watcher.sections.watchList.createAdvancedWatchButtonLabel": "创建高级监视", - "xpack.watcher.sections.watchList.createAdvancedWatchTooltip": "以原始 JSON 格式设置定制监视", "xpack.watcher.sections.watchList.createThresholdAlertButtonLabel": "创建阈值告警", - "xpack.watcher.sections.watchList.createThresholdAlertButtonTooltip": "在特定条件下发送告警", - "xpack.watcher.sections.watchList.deleteSelectedWatchesConfirmModal.deleteButtonLabel": "删除 {numWatchesToDelete, plural, one {# 个监视} other {# 个监视}} ", - "xpack.watcher.sections.watchList.deleteSelectedWatchesConfirmModal.descriptionText": "这将永久删除 {numWatchesToDelete, plural, one {# 个监视} other {# 个监视}}。是否确定?", - "xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText": "已选的 {numTotal}{numWatchesToDelete, plural, one {# 个监视} other {# 个监视}}有 {numErrors} 个无法删除", - "xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText": "已选的 {numTotal}{numWatchesToDelete, plural, one {# 个监视} other {# 个监视}}有 {numSuccesses} 个已删除", "xpack.watcher.sections.watchList.deleteWatchButtonLabel": "删除", "xpack.watcher.sections.watchList.managementSection.editDisplayName": "编辑", "xpack.watcher.sections.watchList.managementSection.newWatchDisplayName": "新建监视", "xpack.watcher.sections.watchList.managementSection.statusDisplayName": "状态", "xpack.watcher.sections.watchList.managementSection.watcherDisplayName": "Watcher", "xpack.watcher.sections.watchList.managementSection.watchesDisplayName": "监视", - "xpack.watcher.sections.watchList.noPermissionToManageWatchesText": "您无权管理监视。", - "xpack.watcher.sections.watchList.selectedMultipleWatchText": "监视", - "xpack.watcher.sections.watchList.selectedSingleWatchText": "监视", - "xpack.watcher.sections.watchList.watchesNotFoundText": "未找到任何监视。", - "xpack.watcher.sections.watchList.watchTable.commentColumnLabel": "注释", - "xpack.watcher.sections.watchList.watchTable.idColumnLabel": "ID", - "xpack.watcher.sections.watchList.watchTable.lastFiredColumnLabel": "最后发送时间", - "xpack.watcher.sections.watchList.watchTable.lastTriggeredColumnLabel": "最后触发时间", - "xpack.watcher.sections.watchList.watchTable.menuEditButtonLabel": "编辑", "xpack.watcher.sections.watchList.watchTable.menuEditButtonTitle": "编辑", - "xpack.watcher.sections.watchList.watchTable.nameColumnLabel": "名称", - "xpack.watcher.sections.watchList.watchTable.stateColumnLabel": "状态", "xpack.watcher.server.checkLicense.licenseExpiredTextMessage": "您不能使用 {watcher},因为您的 {licenseType} 许可证已过期", "xpack.watcher.thresholdPreviewChart.dataDoesNotExistTextMessage": "您的索引和条件未返回任何数据", "xpack.watcher.thresholdWatchExpression.aggField.itemDescription": "/", From acdf987534ad99e07c012b525964f91c4f4d2136 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 10:47:24 -0500 Subject: [PATCH 10/20] fixing functional test --- .../sections/watch_list/components/watch_list.tsx | 9 ++++++++- x-pack/test/functional/page_objects/watcher_page.js | 11 ++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index 66b5f9b7ec2b7..a7eb47830e198 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -64,7 +64,7 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { return ( {id} @@ -77,6 +77,13 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { name: i18n.translate('xpack.watcher.sections.watchList.watchTable.nameHeader', { defaultMessage: 'Name', }), + render: (name: string, item: any) => { + return ( + + {name} + + ) + } sortable: true, truncateText: true, }, diff --git a/x-pack/test/functional/page_objects/watcher_page.js b/x-pack/test/functional/page_objects/watcher_page.js index b34750b2e6d95..cc14f80d2bc99 100644 --- a/x-pack/test/functional/page_objects/watcher_page.js +++ b/x-pack/test/functional/page_objects/watcher_page.js @@ -31,12 +31,13 @@ export function WatcherPageProvider({ getPageObjects, getService }) { } async getWatch(watchID) { - const watchRow = await testSubjects.find(`watchRow-${watchID}`); - const text = await watchRow.getVisibleText(); - const columns = text.split('\n'); + const watchIdColumn = await testSubjects.find(`watchIDColumn-${watchID}`); + const watchNameColumn = await testSubjects.find(`watchNameColumn-${watchID}`); + const id = await watchIdColumn.getVisibleText(); + const name = await watchNameColumn.getVisibleText(); return { - id: columns[0], - name: columns[1] + id, + name }; } From 633889b7bf3e73c0048a8d4e80adef5d000cacdd Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 11:35:05 -0500 Subject: [PATCH 11/20] prettier fix --- .../public/sections/watch_list/components/watch_list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index a7eb47830e198..d5d428993fdfc 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -73,7 +73,7 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { }, }, { - field: 'name', + field: 'name',˝ name: i18n.translate('xpack.watcher.sections.watchList.watchTable.nameHeader', { defaultMessage: 'Name', }), From 322f0da83968cedaddb3ee7d53feaa5b9063501d Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 11:44:05 -0500 Subject: [PATCH 12/20] fixing typp --- .../public/sections/watch_list/components/watch_list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index d5d428993fdfc..a7eb47830e198 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -73,7 +73,7 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { }, }, { - field: 'name',˝ + field: 'name', name: i18n.translate('xpack.watcher.sections.watchList.watchTable.nameHeader', { defaultMessage: 'Name', }), From 38c85d131bc4559a938f7551ddd8e195046dd58c Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 11:44:59 -0500 Subject: [PATCH 13/20] fixing missing comma --- .../public/sections/watch_list/components/watch_list.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index a7eb47830e198..be3cf1540632f 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -78,12 +78,8 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { defaultMessage: 'Name', }), render: (name: string, item: any) => { - return ( - - {name} - - ) - } + return {name}; + }, sortable: true, truncateText: true, }, From ba86b1ff6aaaed0c76d8fe51ffb2b53cf6c44762 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 13:15:02 -0500 Subject: [PATCH 14/20] fixing data-test-subject typo --- x-pack/test/functional/page_objects/watcher_page.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/test/functional/page_objects/watcher_page.js b/x-pack/test/functional/page_objects/watcher_page.js index cc14f80d2bc99..415133b492046 100644 --- a/x-pack/test/functional/page_objects/watcher_page.js +++ b/x-pack/test/functional/page_objects/watcher_page.js @@ -31,13 +31,13 @@ export function WatcherPageProvider({ getPageObjects, getService }) { } async getWatch(watchID) { - const watchIdColumn = await testSubjects.find(`watchIDColumn-${watchID}`); + const watchIdColumn = await testSubjects.find(`watchIdColumn-${watchID}`); const watchNameColumn = await testSubjects.find(`watchNameColumn-${watchID}`); - const id = await watchIdColumn.getVisibleText(); - const name = await watchNameColumn.getVisibleText(); + const id = await watchIdColumn.getVisibleText(); + const name = await watchNameColumn.getVisibleText(); return { id, - name + name, }; } @@ -57,7 +57,7 @@ export function WatcherPageProvider({ getPageObjects, getService }) { return { checkBox: (await checkBox.getProperty('innerHTML')).includes('input'), id: await id.getVisibleText(), - name: (await name.getVisibleText()).split(',').map(role => role.trim()) + name: (await name.getVisibleText()).split(',').map(role => role.trim()), }; }); } From 1f5d4adc1dfc1e05711a6fcb37a850783d74cbed Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 13:58:42 -0500 Subject: [PATCH 15/20] fixing functional test --- x-pack/test/functional/apps/watcher/watcher_test.js | 10 ++++------ x-pack/test/functional/page_objects/watcher_page.js | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/x-pack/test/functional/apps/watcher/watcher_test.js b/x-pack/test/functional/apps/watcher/watcher_test.js index 1af7da85036bf..56db873c9454c 100644 --- a/x-pack/test/functional/apps/watcher/watcher_test.js +++ b/x-pack/test/functional/apps/watcher/watcher_test.js @@ -34,8 +34,10 @@ export default function ({ getService, getPageObjects }) { it('should prompt user to check to see if you can override a watch with a sameID', async () => { await PageObjects.watcher.createWatch(watchID, updatedName); const modal = await testSubjects.find('confirmModalBodyText'); - const modalText = await modal.getVisibleText(); - expect(modalText).to.be(`Watch with ID "${watchID}" (name: "${watchName}") already exists. Do you want to overwrite it?`); + const modalText = await modal.getVisibleText(); + expect(modalText).to.be( + `Watch with ID "${watchID}" (name: "${watchName}") already exists. Do you want to overwrite it?` + ); await testSubjects.click('confirmModalConfirmButton'); const watch = await PageObjects.watcher.getWatch(watchID); expect(watch.id).to.be(watchID); @@ -48,15 +50,11 @@ export default function ({ getService, getPageObjects }) { log.debug(watchList); expect(watchList.watchID.name).to.eql([updatedName]); await PageObjects.watcher.deleteWatch(watchID); - const modal = await testSubjects.find('confirmModalBodyText'); - const modalText = await modal.getVisibleText(); - expect(modalText).to.be('This will permanently delete 1 Watch. Are you sure?'); await testSubjects.click('confirmModalConfirmButton'); await PageObjects.header.waitUntilLoadingHasFinished(); const watchList1 = indexBy(await PageObjects.watcher.getWatches(), 'id'); log.debug(watchList1); expect(watchList1).to.not.have.key(watchID); }); - }); } diff --git a/x-pack/test/functional/page_objects/watcher_page.js b/x-pack/test/functional/page_objects/watcher_page.js index 415133b492046..e9e16ef804271 100644 --- a/x-pack/test/functional/page_objects/watcher_page.js +++ b/x-pack/test/functional/page_objects/watcher_page.js @@ -48,7 +48,7 @@ export function WatcherPageProvider({ getPageObjects, getService }) { //get all the watches in the list async getWatches() { - const watches = await find.allByCssSelector('.kuiTableRow'); + const watches = await find.allByCssSelector('.euiTableRow'); return mapAsync(watches, async watch => { const checkBox = await watch.findByCssSelector('td:nth-child(1)'); const id = await watch.findByCssSelector('td:nth-child(2)'); From 167b76277bd48eaa8f114a5df1443c1ab2f1b4c2 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Tue, 5 Mar 2019 16:58:45 -0500 Subject: [PATCH 16/20] fixing functional tests --- x-pack/test/functional/page_objects/watcher_page.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/page_objects/watcher_page.js b/x-pack/test/functional/page_objects/watcher_page.js index e9e16ef804271..f0758313af32b 100644 --- a/x-pack/test/functional/page_objects/watcher_page.js +++ b/x-pack/test/functional/page_objects/watcher_page.js @@ -13,9 +13,9 @@ export function WatcherPageProvider({ getPageObjects, getService }) { class WatcherPage { async clearAllWatches() { - const checkBoxExists = await testSubjects.exists('selectAllWatchesCheckBox'); + const checkBoxExists = await testSubjects.exists('checkboxSelectAll'); if (checkBoxExists) { - await testSubjects.click('selectAllWatchesCheckBox'); + await testSubjects.click('checkboxSelectAll'); await testSubjects.click('btnDeleteWatches'); await testSubjects.click('confirmModalConfirmButton'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -42,7 +42,7 @@ export function WatcherPageProvider({ getPageObjects, getService }) { } async deleteWatch() { - await testSubjects.click('selectAllWatchesCheckBox'); + await testSubjects.click('checkboxSelectAll'); await testSubjects.click('btnDeleteWatches'); } From 47efebfece51152dc5e679effe06406cf06e8b62 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Wed, 6 Mar 2019 14:21:03 -0500 Subject: [PATCH 17/20] fixing delete functional tests --- x-pack/test/functional/apps/watcher/watcher_test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/watcher/watcher_test.js b/x-pack/test/functional/apps/watcher/watcher_test.js index 56db873c9454c..d82d9f0049655 100644 --- a/x-pack/test/functional/apps/watcher/watcher_test.js +++ b/x-pack/test/functional/apps/watcher/watcher_test.js @@ -12,6 +12,8 @@ const watchName = 'watch Name'; const updatedName = 'updatedName'; export default function ({ getService, getPageObjects }) { const browser = getService('browser'); + const find = getService('find'); + const retry = getService('retry'); const testSubjects = getService('testSubjects'); const log = getService('log'); const PageObjects = getPageObjects(['security', 'common', 'header', 'settings', 'watcher']); @@ -52,9 +54,11 @@ export default function ({ getService, getPageObjects }) { await PageObjects.watcher.deleteWatch(watchID); await testSubjects.click('confirmModalConfirmButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - const watchList1 = indexBy(await PageObjects.watcher.getWatches(), 'id'); - log.debug(watchList1); - expect(watchList1).to.not.have.key(watchID); + retry.try(async () => { + const row = await find.byCssSelector('.euiTableRow'); + const cell = await row.findByCssSelector('td:nth-child(1)'); + expect(cell.getVisibleText()).to.equal('No watches to show'); + }); }); }); } From 52ee52d8e5d4e289d26400bfd5439bed30296445 Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Thu, 7 Mar 2019 10:26:58 -0500 Subject: [PATCH 18/20] addressing PR feedback --- .../watcher/common/constants/watch_states.ts | 2 +- .../components/delete_watches_modal.tsx | 1 + .../watch_list/components/watch_list.tsx | 8 ++-- .../public/sections/watch_list/index.scss | 2 +- .../sections/watch_list/watch_list_route.js | 2 +- .../api/watches/register_delete_route.js | 42 +++++++++---------- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/watcher/common/constants/watch_states.ts b/x-pack/plugins/watcher/common/constants/watch_states.ts index 4247982dd01e0..6215b492975cf 100644 --- a/x-pack/plugins/watcher/common/constants/watch_states.ts +++ b/x-pack/plugins/watcher/common/constants/watch_states.ts @@ -20,7 +20,7 @@ export const WATCH_STATES: { [key: string]: string } = { }), ERROR: i18n.translate('xpack.watcher.constants.watchStates.errorStateText', { - defaultMessage: 'Error!', + defaultMessage: 'Error', }), CONFIG_ERROR: i18n.translate('xpack.watcher.constants.watchStates.configErrorStateText', { diff --git a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx index b68c36eae14ca..2ac41c159e354 100644 --- a/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx +++ b/x-pack/plugins/watcher/public/components/delete_watches_modal.tsx @@ -44,6 +44,7 @@ export const DeleteWatchesModal = ({ return ( callback()} onConfirm={async () => { diff --git a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx index be3cf1540632f..45437a5485c9c 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_list/components/watch_list.tsx @@ -28,10 +28,10 @@ import { fetchWatches } from '../../../lib/api'; const stateToIcon: { [key: string]: JSX.Element } = { [WATCH_STATES.OK]: , - [WATCH_STATES.DISABLED]: , - [WATCH_STATES.FIRING]: , - [WATCH_STATES.ERROR]: , - [WATCH_STATES.CONFIG_ERROR]: , + [WATCH_STATES.DISABLED]: , + [WATCH_STATES.FIRING]: , + [WATCH_STATES.ERROR]: , + [WATCH_STATES.CONFIG_ERROR]: , }; const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { diff --git a/x-pack/plugins/watcher/public/sections/watch_list/index.scss b/x-pack/plugins/watcher/public/sections/watch_list/index.scss index 9b1371175acdf..d46f170c06b49 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/index.scss +++ b/x-pack/plugins/watcher/public/sections/watch_list/index.scss @@ -3,4 +3,4 @@ */ .watchState__message { flex-basis: auto !important; /* 1 */ -} \ No newline at end of file +} diff --git a/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js b/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js index 4a5f868c6ecb1..e171e0bf33644 100644 --- a/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js +++ b/x-pack/plugins/watcher/public/sections/watch_list/watch_list_route.js @@ -29,7 +29,7 @@ routes.when('/management/elasticsearch/watcher/', { }); routes.when('/management/elasticsearch/watcher/watches/', { - template: template, + template, controller: class WatchListRouteController { constructor($injector, $scope, $http) { const $route = $injector.get('$route'); diff --git a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js index f7b3fe8f06b17..a0bbfb954b755 100644 --- a/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js +++ b/x-pack/plugins/watcher/server/routes/api/watches/register_delete_route.js @@ -6,44 +6,42 @@ import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; import { wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; function deleteWatches(callWithRequest, watchIds) { const deletePromises = watchIds.map(watchId => { return callWithRequest('watcher.deleteWatch', { - id: watchId + id: watchId, }) .then(success => ({ success })) .catch(error => ({ error })); }); - return Promise.all(deletePromises) - .then(results => { - const errors = []; - const successes = []; - results.forEach(({ success, error }) => { - if (success) { - successes.push(success._id); - } else if(error) { - errors.push(error._id); - } - }); - - return { - successes, - errors - }; + return Promise.all(deletePromises).then(results => { + const errors = []; + const successes = []; + results.forEach(({ success, error }) => { + if (success) { + successes.push(success._id); + } else if (error) { + errors.push(error._id); + } }); + + return { + successes, + errors, + }; + }); } export function registerDeleteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); server.route({ path: '/api/watcher/watches/delete', method: 'POST', - handler: async (request) => { + handler: async request => { const callWithRequest = callWithRequestFactory(server, request); try { @@ -54,7 +52,7 @@ export function registerDeleteRoute(server) { } }, config: { - pre: [ licensePreRouting ] - } + pre: [licensePreRouting], + }, }); } From c3700b3faf8fc8647778ea2ea9761c528ad7c9aa Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Wed, 20 Mar 2019 13:48:34 -0400 Subject: [PATCH 19/20] progress on watch edit --- .../watcher/public/components/form_errors.tsx | 38 ++ x-pack/plugins/watcher/public/lib/api.ts | 32 + .../plugins/watcher/public/models/index.d.ts | 3 + .../public/models/watch/threshold_watch.js | 40 +- .../public/sections/watch_edit/agg_types.ts | 46 ++ .../public/sections/watch_edit/comparators.ts | 28 + .../threshold_watch_edit_component.tsx | 617 ++++++++++++++++++ .../watch_edit/components/watch_context.ts | 8 + .../watch_edit/components/watch_edit.tsx | 55 ++ .../sections/watch_edit/group_by_types.ts | 31 + .../public/sections/watch_edit/time_units.ts | 47 ++ .../sections/watch_edit/watch_edit_route.html | 3 + .../sections/watch_edit/watch_edit_route.js | 130 ++-- 13 files changed, 1013 insertions(+), 65 deletions(-) create mode 100644 x-pack/plugins/watcher/public/components/form_errors.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/agg_types.ts create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/comparators.ts create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/watch_context.ts create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/group_by_types.ts create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts diff --git a/x-pack/plugins/watcher/public/components/form_errors.tsx b/x-pack/plugins/watcher/public/components/form_errors.tsx new file mode 100644 index 0000000000000..4bedc582f23f5 --- /dev/null +++ b/x-pack/plugins/watcher/public/components/form_errors.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFormRow } from '@elastic/eui'; +import React, { Children, cloneElement, Fragment, ReactElement } from 'react'; + +export const ErrableFormRow = ({ + errorKey, + isShowingErrors, + errors, + children, + ...rest +}: { + errorKey: string; + isShowingErrors: boolean; + errors: { [key: string]: string[] }; + children: ReactElement; + [key: string]: any; +}) => { + return ( + 0} + error={errors[errorKey]} + {...rest} + > + + {Children.map(children, child => + cloneElement(child, { + isInvalid: isShowingErrors && errors[errorKey].length > 0, + }) + )} + + + ); +}; diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index e93adf6f71b2a..4b58d9c9c27c6 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { Watch } from 'plugins/watcher/models/watch'; +import { __await } from 'tslib'; import chrome from 'ui/chrome'; import { ROUTES } from '../../common/constants'; let httpClient: ng.IHttpService; @@ -31,3 +32,34 @@ export const deleteWatches = async (watchIds: string[]) => { } = await getHttpClient().post(`${basePath}/watches/delete`, body); return results; }; +export const fetchWatch = async (watchId: string) => { + const body = { + watchId, + }; + const { + data: { results }, + } = await getHttpClient().post(`${basePath}/watches/`, body); + return results; +}; +export const loadWatch = async (id: string) => { + const { data: watch } = await getHttpClient().get(`${basePath}/watch/${id}`); + return Watch.fromUpstreamJson(watch.watch); +}; +export const getMatchingIndices = async (pattern: string) => { + if (!pattern.startsWith('*')) { + pattern = `*${pattern}`; + } + if (!pattern.endsWith('*')) { + pattern = `${pattern}*`; + } + const { + data: { indices }, + } = await getHttpClient().post(`${basePath}/indices`, { pattern }); + return indices; +}; +export const fetchFields = async (indexes: string[]) => { + const { + data: { fields }, + } = await getHttpClient().post(`${basePath}/fields`, { indexes }); + return fields; +}; diff --git a/x-pack/plugins/watcher/public/models/index.d.ts b/x-pack/plugins/watcher/public/models/index.d.ts index 81787a375d44e..94668b900d358 100644 --- a/x-pack/plugins/watcher/public/models/index.d.ts +++ b/x-pack/plugins/watcher/public/models/index.d.ts @@ -6,3 +6,6 @@ declare module 'plugins/watcher/models/watch' { export const Watch: any; } +declare module 'plugins/watcher/models/watch/threshold_watch' { + export const ThresholdWatch: any; +} diff --git a/x-pack/plugins/watcher/public/models/watch/threshold_watch.js b/x-pack/plugins/watcher/public/models/watch/threshold_watch.js index 0643a74ea35ed..d32177844f90b 100644 --- a/x-pack/plugins/watcher/public/models/watch/threshold_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/threshold_watch.js @@ -18,7 +18,8 @@ const DEFAULT_VALUES = { TIME_WINDOW_UNIT: 'm', TRIGGER_INTERVAL_SIZE: 1, TRIGGER_INTERVAL_UNIT: 'm', - THRESHOLD: 1000 + THRESHOLD: 1000, + GROUP_BY: 'all', }; /** @@ -30,7 +31,7 @@ export class ThresholdWatch extends BaseWatch { props.type = WATCH_TYPES.THRESHOLD; super(props); - this.index = props.index; + this.index = props.index || []; this.timeField = props.timeField; this.triggerIntervalSize = props.triggerIntervalSize || DEFAULT_VALUES.TRIGGER_INTERVAL_SIZE; this.triggerIntervalUnit = props.triggerIntervalUnit || DEFAULT_VALUES.TRIGGER_INTERVAL_UNIT; @@ -41,6 +42,7 @@ export class ThresholdWatch extends BaseWatch { this.thresholdComparator = props.thresholdComparator || DEFAULT_VALUES.THRESHOLD_COMPARATOR; this.timeWindowSize = props.timeWindowSize || DEFAULT_VALUES.TIME_WINDOW_SIZE; this.timeWindowUnit = props.timeWindowUnit || DEFAULT_VALUES.TIME_WINDOW_UNIT; + this.groupBy = props.groupBy || DEFAULT_VALUES.GROUP_BY; //NOTE: The threshold must be of the appropriate type, i.e.,number/date. //Conversion from string must occur by consumer when assigning a @@ -53,25 +55,33 @@ export class ThresholdWatch extends BaseWatch { } get termOrder() { - return this.thresholdComparator === COMPARATORS.GREATER_THAN ? SORT_ORDERS.DESCENDING : SORT_ORDERS.ASCENDING; + return this.thresholdComparator === COMPARATORS.GREATER_THAN + ? SORT_ORDERS.DESCENDING + : SORT_ORDERS.ASCENDING; } get titleDescription() { - const staticPart = i18n.translate('xpack.watcher.models.thresholdWatch.sendAlertOnSpecificConditionTitleDescription', { - defaultMessage: 'Send an alert when a specific condition is met.' - }); + const staticPart = i18n.translate( + 'xpack.watcher.models.thresholdWatch.sendAlertOnSpecificConditionTitleDescription', + { + defaultMessage: 'Send an alert when a specific condition is met.', + } + ); if (isNaN(this.triggerIntervalSize)) { return staticPart; } const timeUnitLabel = getTimeUnitsLabel(this.triggerIntervalUnit, this.triggerIntervalSize); - const dynamicPartText = i18n.translate('xpack.watcher.models.thresholdWatch.thresholdWatchIntervalTitleDescription', { - defaultMessage: 'This will run every {triggerIntervalSize} {timeUnitLabel}.', - values: { - triggerIntervalSize: this.triggerIntervalSize, - timeUnitLabel + const dynamicPartText = i18n.translate( + 'xpack.watcher.models.thresholdWatch.thresholdWatchIntervalTitleDescription', + { + defaultMessage: 'This will run every {triggerIntervalSize} {timeUnitLabel}.', + values: { + triggerIntervalSize: this.triggerIntervalSize, + timeUnitLabel, + }, } - }); + ); return `${staticPart} ${dynamicPartText}`; } @@ -89,7 +99,7 @@ export class ThresholdWatch extends BaseWatch { thresholdComparator: this.thresholdComparator, timeWindowSize: this.timeWindowSize, timeWindowUnit: this.timeWindowUnit, - threshold: this.threshold + threshold: this.threshold, }); return result; @@ -104,11 +114,11 @@ export class ThresholdWatch extends BaseWatch { } static typeName = i18n.translate('xpack.watcher.models.thresholdWatch.typeName', { - defaultMessage: 'Threshold Alert' + defaultMessage: 'Threshold Alert', }); static iconClass = ''; static selectMessage = i18n.translate('xpack.watcher.models.thresholdWatch.selectMessageText', { - defaultMessage: 'Send an alert on a specific condition' + defaultMessage: 'Send an alert on a specific condition', }); static isCreatable = true; static selectSortOrder = 1; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/agg_types.ts b/x-pack/plugins/watcher/public/sections/watch_edit/agg_types.ts new file mode 100644 index 0000000000000..65ab537889ea4 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/agg_types.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AGG_TYPES } from '../../../common/constants'; + +export interface AggType { + text: string; + fieldRequired: boolean; + value: string; + validNormalizedTypes: string[]; +} +export const aggTypes: { [key: string]: AggType } = { + count: { + text: 'count()', + fieldRequired: false, + value: AGG_TYPES.COUNT, + validNormalizedTypes: [], + }, + avg: { + text: 'average()', + fieldRequired: true, + validNormalizedTypes: ['number'], + value: AGG_TYPES.AVERAGE, + }, + sum: { + text: 'sum()', + fieldRequired: true, + validNormalizedTypes: ['number'], + value: AGG_TYPES.SUM, + }, + min: { + text: 'min()', + fieldRequired: true, + validNormalizedTypes: ['number', 'date'], + value: AGG_TYPES.MIN, + }, + max: { + text: 'max()', + fieldRequired: true, + validNormalizedTypes: ['number', 'date'], + value: AGG_TYPES.MAX, + }, +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/comparators.ts b/x-pack/plugins/watcher/public/sections/watch_edit/comparators.ts new file mode 100644 index 0000000000000..63508732f8389 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/comparators.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { COMPARATORS } from '../../../common/constants'; + +export interface Comparator { + text: string; + value: string; +} +export const comparators: { [key: string]: Comparator } = { + [COMPARATORS.GREATER_THAN]: { + text: i18n.translate('xpack.watcher.thresholdWatchExpression.comparators.isAboveLabel', { + defaultMessage: 'Is above', + }), + value: COMPARATORS.GREATER_THAN, + }, + [COMPARATORS.LESS_THAN]: { + text: i18n.translate('xpack.watcher.thresholdWatchExpression.comparators.isBelowLabel', { + defaultMessage: 'Is below', + }), + value: COMPARATORS.LESS_THAN, + }, +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx new file mode 100644 index 0000000000000..df4f97ca7e88a --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx @@ -0,0 +1,617 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useContext, useEffect, useState } from 'react'; + +import { + EuiComboBox, + EuiComboBoxOptionProps, + EuiExpression, + EuiFieldNumber, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiPageContent, + EuiPopover, + EuiPopoverTitle, + EuiSelect, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { ThresholdWatch } from 'plugins/watcher/models/watch/threshold_watch'; +import { ErrableFormRow } from '../../../components/form_errors'; +import { fetchFields, getMatchingIndices } from '../../../lib/api'; +import { aggTypes } from '../agg_types'; +import { comparators } from '../comparators'; +import { groupByTypes } from '../group_by_types'; +import { timeUnits } from '../time_units'; +import { WatchContext } from './watch_context'; +const firstFieldOption = { + text: i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.timeFieldOptionLabel', { + defaultMessage: 'Select a field', + }), + value: '', +}; +const getTitle = (watch: any) => { + if (watch.isNew) { + const typeName = watch.typeName.toLowerCase(); + return i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.createNewTypeOfWatchTitle', { + defaultMessage: 'Create a new {typeName}', + values: { typeName }, + }); + } else { + return watch.name; + } +}; +const getFields = async (indices: string[]) => { + return await fetchFields(indices); +}; +const getTimeFieldOptions = (fields: any) => { + const options = [firstFieldOption]; + if (!fields.length) { + return options; + } + + fields.forEach((field: any) => { + if (field.type === 'date') { + options.push({ + text: field.name, + value: field.name, + }); + } + }); + return options; +}; +interface IOption { + label: string; + options: Array<{ value: string; label: string }>; +} +const getIndexOptions = async (patternString: string, indexPatterns: string[]) => { + const options: IOption[] = []; + if (!patternString) { + return options; + } + const matchingIndices = (await getMatchingIndices(patternString)) as string[]; + const matchingIndexPatterns = indexPatterns.filter(anIndexPattern => { + return anIndexPattern.includes(patternString); + }) as string[]; + if (matchingIndices) { + options.push({ + label: i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.indicesAndAliasesLabel', { + defaultMessage: 'Based on your indices/aliases', + }), + options: matchingIndices.map(matchingIndex => { + return { + label: matchingIndex, + value: matchingIndex, + }; + }), + }); + } + if (matchingIndexPatterns) { + options.push({ + label: i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.indexPatternLabel', { + defaultMessage: 'Based on your index patterns', + }), + options: matchingIndexPatterns.map(matchingIndexPattern => { + return { + label: matchingIndexPattern, + value: matchingIndexPattern, + }; + }), + }); + } + options.push({ + label: i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.chooseLabel', { + defaultMessage: 'Choose...', + }), + options: [ + { + value: patternString, + label: patternString, + }, + ], + }); + return options; +}; + +const ThresholdWatchEditUi = ({ + intl, + savedObjectsClient, +}: { + intl: InjectedIntl; + savedObjectsClient: any; +}) => { + // hooks + const [indexPatterns, setIndexPatterns] = useState([]); + const [fields, setFields] = useState([]); + const [indexOptions, setIndexOptions] = useState([]); + const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); + + const [aggFieldPopoverOpen, setAggFieldPopoverOpen] = useState(false); + const [groupByPopoverOpen, setGroupByPopoverOpen] = useState(false); + const [watchThresholdPopoverOpen, setWatchThresholdPopoverOpen] = useState(false); + const [watchDurationPopoverOpen, setWatchDurationPopoverOpen] = useState(false); + const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false); + const { watch, setWatch } = useContext(WatchContext); + const getIndexPatterns = async () => { + const { savedObjects } = await savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title'], + perPage: 10000, + }); + const titles = savedObjects.map((indexPattern: any) => indexPattern.attributes.title); + setIndexPatterns(titles); + }; + const loadData = async () => { + const theFields = await getFields(watch.index); + setFields(theFields); + setTimeFieldOptions(getTimeFieldOptions(fields)); + getIndexPatterns(); + }; + useEffect(() => { + loadData(); + }, []); + return ( + + + + +

{getTitle(watch)}

+
+ + + {watch.titleDescription} + +
+
+ + + + } + errorKey="watchName" + isShowingErrors={false} + errors={{}} + > + { + setWatch(new ThresholdWatch({ ...watch, name: e.target.value })); + }} + /> + + + + + } + errorKey="watchName" + isShowingErrors={false} + errors={{}} + helpText={ + + } + > + { + return { + label: anIndex, + value: anIndex, + }; + })} + onChange={async (selected: EuiComboBoxOptionProps[]) => { + watch.index = selected.map(aSelected => aSelected.value); + setWatch(new ThresholdWatch(watch)); + setWatch(new ThresholdWatch(watch)); + const indices = selected.map(s => s.value as string); + const theFields = await getFields(indices); + setFields(theFieldsO); + + setTimeFieldOptions(getTimeFieldOptions(fields)); + }} + onSearchChange={async search => { + setIndexOptions(await getIndexOptions(search, indexPatterns)); + }} + /> + + + + + } + errorKey="watchName" + isShowingErrors={false} + errors={{}} + > + { + watch.timeField = e.target.value; + setWatch(new ThresholdWatch(watch)); + }} + /> + + + + + + + { + watch.triggerIntervalSize = e.target.value; + setWatch(new ThresholdWatch(watch)); + }} + /> + + + { + watch.triggerIntervalUnit = e.target.value; + setWatch(new ThresholdWatch(watch)); + }} + options={[ + { + value: 's', + text: intl.formatMessage({ + id: 'xpack.watcher.sections.watchEdit.titlePanel.secondsLabel', + defaultMessage: 'seconds', + }), + }, + { + value: 'm', + text: intl.formatMessage({ + id: 'xpack.watcher.sections.watchEdit.titlePanel.minutesLabel', + defaultMessage: 'minutes', + }), + }, + { + value: 'd', + text: intl.formatMessage({ + id: 'xpack.watcher.sections.watchEdit.titlePanel.daysLabel', + defaultMessage: 'days', + }), + }, + { + value: 'h', + text: intl.formatMessage({ + id: 'xpack.watcher.sections.watchEdit.titlePanel.hoursLabel', + defaultMessage: 'hours', + }), + }, + ]} + /> + + + + + + + + + { + setAggTypePopoverOpen(true); + }} + /> + } + isOpen={aggTypePopoverOpen} + closePopover={() => { + setAggTypePopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ when + { + watch.aggType = e.target.value; + setWatch(new ThresholdWatch(watch)); + setAggTypePopoverOpen(false); + }} + options={Object.values(aggTypes)} + /> +
+
+
+ {watch.aggType && aggTypes[watch.aggType].fieldRequired ? ( + + { + setAggFieldPopoverOpen(true); + }} + /> + } + isOpen={aggFieldPopoverOpen} + closePopover={() => { + setAggFieldPopoverOpen(false); + }} + ownFocus + anchorPosition="downLeft" + > +
+ of + + + { + watch.aggField = e.target.value; + setWatch(new ThresholdWatch(watch)); + setAggFieldPopoverOpen(false); + }} + options={fields.reduce( + (options, field: any) => { + if ( + aggTypes[watch.aggType].validNormalizedTypes.includes( + field.normalizedType + ) + ) { + options.push({ + text: field.name, + value: field.name, + }); + } + return options; + }, + [ + { + text: 'select a field', + value: '', + }, + ] + )} + /> + + +
+
+
+ ) : null} + + { + setGroupByPopoverOpen(true); + }} + /> + } + isOpen={groupByPopoverOpen} + closePopover={() => { + setGroupByPopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ over + + + { + watch.groupBy = e.target.value; + setWatch(new ThresholdWatch(watch)); + }} + options={Object.values(groupByTypes)} + /> + + + {groupByTypes[watch.groupBy].sizeRequired ? ( + + + + + + { + watch.aggField = e.target.value; + setWatch(new ThresholdWatch(watch)); + setAggFieldPopoverOpen(false); + }} + options={fields.reduce( + (options, field: any) => { + if ( + groupByTypes[watch.groupBy].validNormalizedTypes.includes( + field.normalizedType + ) + ) { + options.push({ + text: field.name, + value: field.name, + }); + } + return options; + }, + [] as Array<{ text: string; value: string }> + )} + /> + + + ) : null} + +
+
+
+ + { + setWatchThresholdPopoverOpen(true); + }} + /> + } + isOpen={watchThresholdPopoverOpen} + closePopover={() => { + setWatchThresholdPopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ {comparators[watch.thresholdComparator].text} + + + { + watch.thresholdComparator = e.target.value; + setWatch(new ThresholdWatch(watch)); + }} + options={Object.values(comparators)} + /> + + + { + watch.threshold = parseInt(e.target.value, 10); + setWatch(new ThresholdWatch(watch)); + }} + /> + + +
+
+
+ + { + setWatchDurationPopoverOpen(true); + }} + /> + } + isOpen={watchDurationPopoverOpen} + closePopover={() => { + setWatchDurationPopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ For the last + + + { + watch.timeWindowSize = e.target.value; + setWatch(new ThresholdWatch(watch)); + }} + /> + + + { + watch.timeWindowUnit = e.target.value; + setWatch(new ThresholdWatch(watch)); + }} + options={Object.entries(timeUnits).map(([key, value]) => { + return { + text: + watch.timeWindowSize && parseInt(watch.timeWindowSize, 10) === 1 + ? value.labelSingular + : value.labelPlural, + value: key, + }; + })} + /> + + +
+
+
+
+
+
+ ); +}; +export const ThresholdWatchEdit = injectI18n(ThresholdWatchEditUi); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_context.ts b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_context.ts new file mode 100644 index 0000000000000..a786f7534e37a --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_context.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +export const WatchContext = React.createContext({} as any); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx new file mode 100644 index 0000000000000..45a46076fc903 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiLoadingSpinner } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; +import { Watch } from 'plugins/watcher/models/watch'; +import React, { useEffect, useState } from 'react'; +import { loadWatch } from '../../../lib/api'; +import { ThresholdWatchEdit } from './threshold_watch_edit_component'; +import { WatchContext } from './watch_context'; + +export const WatchEdit = ({ + watchId, + watchType, + savedObjectsClient, +}: { + watchId: string; + watchType: string; + savedObjectsClient: any; +}) => { + // hooks + const [watch, setWatch] = useState(null); + const getWatch = async () => { + let theWatch; + if (watchId) { + theWatch = await loadWatch(watchId); + setWatch(theWatch); + } else { + const WatchType = Watch.getWatchTypes()[watchType]; + if (WatchType) { + setWatch(new WatchType()); + } + } + }; + useEffect(() => { + getWatch(); + }, []); + if (!watch) { + return ; + } + let EditComponent = null; + if (watch.type === 'threshold') { + EditComponent = ThresholdWatchEdit; + } else { + EditComponent = EuiSpacer; + } + return ( + + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/group_by_types.ts b/x-pack/plugins/watcher/public/sections/watch_edit/group_by_types.ts new file mode 100644 index 0000000000000..37dee2f9946e3 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/group_by_types.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +export interface GroupByType { + text: string; + sizeRequired: boolean; + value: string; + validNormalizedTypes: string[]; +} +export const groupByTypes: { [key: string]: GroupByType } = { + all: { + text: i18n.translate('xpack.watcher.thresholdWatchExpression.groupByLabel.allDocumentsLabel', { + defaultMessage: 'all documents', + }), + sizeRequired: false, + value: 'all', + validNormalizedTypes: [], + }, + top: { + text: i18n.translate('xpack.watcher.thresholdWatchExpression.groupByLabel.topLabel', { + defaultMessage: 'top', + }), + sizeRequired: true, + value: 'top', + validNormalizedTypes: ['number', 'date', 'keyword'], + }, +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts b/x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts new file mode 100644 index 0000000000000..a4fd9e3fddbcf --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { TIME_UNITS as COMMON_TIME_UNITS } from '../../../common/constants/time_units'; + +interface TimeUnit { + labelPlural: string; + labelSingular: string; +} +export const timeUnits: { [key: string]: TimeUnit } = { + [COMMON_TIME_UNITS.SECOND]: { + labelPlural: i18n.translate('xpack.watcher.timeUnits.secondPluralLabel', { + defaultMessage: 'seconds', + }), + labelSingular: i18n.translate('xpack.watcher.timeUnits.secondSingularLabel', { + defaultMessage: 'second', + }), + }, + [COMMON_TIME_UNITS.MINUTE]: { + labelPlural: i18n.translate('xpack.watcher.timeUnits.minutePluralLabel', { + defaultMessage: 'minutes', + }), + labelSingular: i18n.translate('xpack.watcher.timeUnits.minuteSingularLabel', { + defaultMessage: 'minute', + }), + }, + [COMMON_TIME_UNITS.HOUR]: { + labelPlural: i18n.translate('xpack.watcher.timeUnits.hourPluralLabel', { + defaultMessage: 'hours', + }), + labelSingular: i18n.translate('xpack.watcher.timeUnits.hourSingularLabel', { + defaultMessage: 'hour', + }), + }, + [COMMON_TIME_UNITS.DAY]: { + labelPlural: i18n.translate('xpack.watcher.timeUnits.dayPluralLabel', { + defaultMessage: 'days', + }), + labelSingular: i18n.translate('xpack.watcher.timeUnits.daySingularLabel', { + defaultMessage: 'day', + }), + }, +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.html b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.html index b23df49ffe427..027183a5d6b15 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.html +++ b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.html @@ -1,3 +1,6 @@ + +
+
{ + render( + + + , + elem + ); +}; routes .when('/management/elasticsearch/watcher/watches/watch/:id/edit') .when('/management/elasticsearch/watcher/watches/new-watch/:watchType') - .defaults(/management\/elasticsearch\/watcher\/watches\/(new-watch\/:watchType|watch\/:id\/edit)/, { - template: template, - k7Breadcrumbs: getWatchDetailBreadcrumbs, - controller: class WatchEditRouteController { - constructor($injector) { - const $route = $injector.get('$route'); - this.watch = $route.current.locals.xpackWatch; - this.WATCH_TYPES = WATCH_TYPES; - } - }, - controllerAs: 'watchEditRoute', - resolve: { - watchTabs: ($injector) => { - const $route = $injector.get('$route'); - const watchId = $route.current.params.id; - updateWatchSections(watchId); - }, - xpackWatch: function ($injector) { - const $route = $injector.get('$route'); - const watchService = $injector.get('xpackWatcherWatchService'); - const licenseService = $injector.get('xpackWatcherLicenseService'); - const kbnUrl = $injector.get('kbnUrl'); + .defaults( + /management\/elasticsearch\/watcher\/watches\/(new-watch\/:watchType|watch\/:id\/edit)/, + { + template: template, + k7Breadcrumbs: getWatchDetailBreadcrumbs, + controller: class WatchEditRouteController { + constructor($injector, $scope, $http, Private) { + const $route = $injector.get('$route'); + this.watch = $route.current.locals.xpackWatch; + this.WATCH_TYPES = WATCH_TYPES; + const watchId = $route.current.params.id; + const watchType = $route.current.params.watchType; + // clean up previously rendered React app if one exists + // this happens because of React Router redirects + elem && unmountComponentAtNode(elem); + // NOTE: We depend upon Angular's $http service because it's decorated with interceptors, + // e.g. to check license status per request. + setHttpClient($http); + $scope.$$postDigest(() => { + elem = document.getElementById('watchEditReactRoot'); + const savedObjectsClient = Private(SavedObjectsClientProvider); - const watchId = $route.current.params.id; - const watchType = $route.current.params.watchType; + renderReact(elem, watchType, watchId, savedObjectsClient); + manageAngularLifecycle($scope, $route, elem); + }); + } + }, + controllerAs: 'watchEditRoute', + resolve: { + watchTabs: $injector => { + const $route = $injector.get('$route'); + const watchId = $route.current.params.id; + updateWatchSections(watchId); + }, + xpackWatch: function ($injector) { + const $route = $injector.get('$route'); + const watchService = $injector.get('xpackWatcherWatchService'); + const licenseService = $injector.get('xpackWatcherLicenseService'); + const kbnUrl = $injector.get('kbnUrl'); + const watchId = $route.current.params.id; + const watchType = $route.current.params.watchType; - if (!watchId) { - return licenseService.refreshLicense() - .then(() => { - return watchService.newWatch(watchType); - }) - .catch(err => { - return licenseService.checkValidity() - .then(() => { + if (!watchId) { + return licenseService + .refreshLicense() + .then(() => { + return watchService.newWatch(watchType); + }) + .catch(err => { + return licenseService.checkValidity().then(() => { if (err.status !== 403) { toastNotifications.addDanger(err.data.message); } @@ -60,25 +91,24 @@ routes kbnUrl.redirect('/management/elasticsearch/watcher/watches'); return Promise.reject(); }); - }); - } + }); + } - return watchService.loadWatch(watchId) - .catch(err => { - return licenseService.checkValidity() - .then(() => { - if (err.status !== 403) { - toastNotifications.addDanger(err.data.message); - } + return watchService.loadWatch(watchId).catch(err => { + return licenseService.checkValidity().then(() => { + if (err.status !== 403) { + toastNotifications.addDanger(err.data.message); + } - kbnUrl.redirect('/management/elasticsearch/watcher/watches'); - return Promise.reject(); - }); + kbnUrl.redirect('/management/elasticsearch/watcher/watches'); + return Promise.reject(); + }); }); + }, + checkLicense: $injector => { + const licenseService = $injector.get('xpackWatcherLicenseService'); + return licenseService.checkValidity(); + }, }, - checkLicense: ($injector) => { - const licenseService = $injector.get('xpackWatcherLicenseService'); - return licenseService.checkValidity(); - } } - }); + ); From 081bb7c74288a5bee37a1a3157499c54d9c3ca26 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 4 Apr 2019 16:29:58 -0400 Subject: [PATCH 20/20] port advanced watcher to react (#34188) * port advanced watcher to react * fix i18n * update execute trigger override text fields to number input and select fields * fix page title for edit mode * remove todo comments * add license validity check; pass kbnUrl service as prop * address review comments --- .../plugins/watcher/common/constants/index.ts | 1 + .../watcher/common/constants/time_units.ts | 1 + .../watcher/common/constants/watch_tabs.ts | 30 ++ ...{get_action_type.js => get_action_type.ts} | 9 +- .../get_action_type/{index.js => index.ts} | 0 .../watcher/common/types/watch_types.ts | 57 +++ .../components/confirm_watches_modal.tsx | 44 ++ x-pack/plugins/watcher/public/lib/api.ts | 13 + ...tation_links.js => documentation_links.ts} | 6 +- .../{index.js => index.ts} | 2 +- ...ion_link.js => make_documentation_link.ts} | 11 +- .../models/execute_details/execute_details.js | 38 +- .../plugins/watcher/public/models/index.d.ts | 17 + .../watcher/public/models/watch/base_watch.js | 2 +- .../components/json_watch_edit_component.tsx | 104 ++++ .../components/json_watch_edit_form.tsx | 225 ++++++++ .../components/json_watch_edit_simulate.tsx | 484 ++++++++++++++++++ .../json_watch_edit_simulate_results.tsx | 174 +++++++ .../threshold_watch_edit_component.tsx | 17 +- .../watch_edit/components/watch_edit.tsx | 42 +- .../watch_edit/json_watch_edit_actions.ts | 144 ++++++ .../public/sections/watch_edit/time_units.ts | 8 + .../sections/watch_edit/watch_edit_route.js | 14 +- .../models/execute_details/execute_details.js | 2 +- 24 files changed, 1400 insertions(+), 45 deletions(-) create mode 100644 x-pack/plugins/watcher/common/constants/watch_tabs.ts rename x-pack/plugins/watcher/common/lib/get_action_type/{get_action_type.js => get_action_type.ts} (57%) rename x-pack/plugins/watcher/common/lib/get_action_type/{index.js => index.ts} (100%) create mode 100644 x-pack/plugins/watcher/common/types/watch_types.ts create mode 100644 x-pack/plugins/watcher/public/components/confirm_watches_modal.tsx rename x-pack/plugins/watcher/public/lib/documentation_links/{documentation_links.js => documentation_links.ts} (71%) rename x-pack/plugins/watcher/public/lib/documentation_links/{index.js => index.ts} (79%) rename x-pack/plugins/watcher/public/lib/documentation_links/{make_documentation_link.js => make_documentation_link.ts} (59%) create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_component.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate_results.tsx create mode 100644 x-pack/plugins/watcher/public/sections/watch_edit/json_watch_edit_actions.ts diff --git a/x-pack/plugins/watcher/common/constants/index.ts b/x-pack/plugins/watcher/common/constants/index.ts index 4260688c6eb45..d9156e132c038 100644 --- a/x-pack/plugins/watcher/common/constants/index.ts +++ b/x-pack/plugins/watcher/common/constants/index.ts @@ -23,3 +23,4 @@ export { WATCH_HISTORY } from './watch_history'; export { WATCH_STATES } from './watch_states'; export { WATCH_TYPES } from './watch_types'; export { ERROR_CODES } from './error_codes'; +export { WATCH_TABS, WATCH_TAB_ID_EDIT, WATCH_TAB_ID_SIMULATE } from './watch_tabs'; diff --git a/x-pack/plugins/watcher/common/constants/time_units.ts b/x-pack/plugins/watcher/common/constants/time_units.ts index c861d47416a80..9b79f163e5831 100644 --- a/x-pack/plugins/watcher/common/constants/time_units.ts +++ b/x-pack/plugins/watcher/common/constants/time_units.ts @@ -5,6 +5,7 @@ */ export const TIME_UNITS: { [key: string]: string } = { + MILLISECOND: 'ms', SECOND: 's', MINUTE: 'm', HOUR: 'h', diff --git a/x-pack/plugins/watcher/common/constants/watch_tabs.ts b/x-pack/plugins/watcher/common/constants/watch_tabs.ts new file mode 100644 index 0000000000000..1d741b9610663 --- /dev/null +++ b/x-pack/plugins/watcher/common/constants/watch_tabs.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const WATCH_TAB_ID_EDIT = 'watchEditTab'; +export const WATCH_TAB_ID_SIMULATE = 'watchSimulateTab'; + +interface WatchTab { + id: string; + name: string; +} + +export const WATCH_TABS: WatchTab[] = [ + { + id: WATCH_TAB_ID_EDIT, + name: i18n.translate('xpack.watcher.sections.watchEdit.json.editTabLabel', { + defaultMessage: 'Edit', + }), + }, + { + id: WATCH_TAB_ID_SIMULATE, + name: i18n.translate('xpack.watcher.sections.watchEdit.json.simulateTabLabel', { + defaultMessage: 'Simulate', + }), + }, +]; diff --git a/x-pack/plugins/watcher/common/lib/get_action_type/get_action_type.js b/x-pack/plugins/watcher/common/lib/get_action_type/get_action_type.ts similarity index 57% rename from x-pack/plugins/watcher/common/lib/get_action_type/get_action_type.js rename to x-pack/plugins/watcher/common/lib/get_action_type/get_action_type.ts index 6fdaa81c64260..95aaef71cd2be 100644 --- a/x-pack/plugins/watcher/common/lib/get_action_type/get_action_type.js +++ b/x-pack/plugins/watcher/common/lib/get_action_type/get_action_type.ts @@ -4,14 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { keys, values, intersection } from 'lodash'; +import { intersection, keys, values } from 'lodash'; import { ACTION_TYPES } from '../../constants'; -export function getActionType(action) { - const type = intersection( - keys(action), - values(ACTION_TYPES) - )[0] || ACTION_TYPES.UNKNOWN; +export function getActionType(action: { [key: string]: { [key: string]: any } }) { + const type = intersection(keys(action), values(ACTION_TYPES))[0] || ACTION_TYPES.UNKNOWN; return type; } diff --git a/x-pack/plugins/watcher/common/lib/get_action_type/index.js b/x-pack/plugins/watcher/common/lib/get_action_type/index.ts similarity index 100% rename from x-pack/plugins/watcher/common/lib/get_action_type/index.js rename to x-pack/plugins/watcher/common/lib/get_action_type/index.ts diff --git a/x-pack/plugins/watcher/common/types/watch_types.ts b/x-pack/plugins/watcher/common/types/watch_types.ts new file mode 100644 index 0000000000000..545920496bc3a --- /dev/null +++ b/x-pack/plugins/watcher/common/types/watch_types.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ExecutedWatchResults { + id: string; + watchId: string; + details: any; + startTime: Date; + watchStatus: { + state: string; + actionStatuses: Array<{ state: string; lastExecutionReason: string }>; + }; +} + +export interface ExecutedWatchDetails { + triggerData: { + triggeredTime: Date; + scheduledTime: Date; + }; + ignoreCondition: boolean; + alternativeInput: any; + actionModes: { + [key: string]: string; + }; + recordExecution: boolean; + upstreamJson: any; +} + +export interface BaseWatch { + id: string; + type: string; + isNew: boolean; + name: string; + isSystemWatch: boolean; + watchStatus: any; + watchErrors: any; + typeName: string; + displayName: string; + upstreamJson: any; + resetActions: () => void; + createAction: (type: string, actionProps: {}) => void; + validate: () => { warning: { message: string } }; + actions: [ + { + id: string; + type: string; + } + ]; + watch: { + actions: { + [key: string]: { [key: string]: any }; + }; + }; +} diff --git a/x-pack/plugins/watcher/public/components/confirm_watches_modal.tsx b/x-pack/plugins/watcher/public/components/confirm_watches_modal.tsx new file mode 100644 index 0000000000000..61adccb45ebb2 --- /dev/null +++ b/x-pack/plugins/watcher/public/components/confirm_watches_modal.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export const ConfirmWatchesModal = ({ + modalOptions, + callback, +}: { + modalOptions: { message: string } | null; + callback: (isConfirmed?: boolean) => void; +}) => { + if (!modalOptions) { + return null; + } + return ( + + callback()} + onConfirm={() => { + callback(true); + }} + cancelButtonText={i18n.translate( + 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.cancelButtonLabel', + { defaultMessage: 'Cancel' } + )} + confirmButtonText={i18n.translate( + 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.saveButtonLabel', + { defaultMessage: 'Save' } + )} + > + {modalOptions.message} + + + ); +}; diff --git a/x-pack/plugins/watcher/public/lib/api.ts b/x-pack/plugins/watcher/public/lib/api.ts index 4b58d9c9c27c6..2f4056d2712cb 100644 --- a/x-pack/plugins/watcher/public/lib/api.ts +++ b/x-pack/plugins/watcher/public/lib/api.ts @@ -7,6 +7,8 @@ import { Watch } from 'plugins/watcher/models/watch'; import { __await } from 'tslib'; import chrome from 'ui/chrome'; import { ROUTES } from '../../common/constants'; +import { BaseWatch, ExecutedWatchDetails } from '../../common/types/watch_types'; + let httpClient: ng.IHttpService; export const setHttpClient = (anHttpClient: ng.IHttpService) => { httpClient = anHttpClient; @@ -63,3 +65,14 @@ export const fetchFields = async (indexes: string[]) => { } = await getHttpClient().post(`${basePath}/fields`, { indexes }); return fields; }; +export const createWatch = async (watch: BaseWatch) => { + const { data } = await getHttpClient().put(`${basePath}/watch/${watch.id}`, watch.upstreamJson); + return data; +}; +export const executeWatch = async (executeWatchDetails: ExecutedWatchDetails, watch: BaseWatch) => { + const { data } = await getHttpClient().put(`${basePath}/watch/execute`, { + executeDetails: executeWatchDetails.upstreamJson, + watch: watch.upstreamJson, + }); + return data; +}; diff --git a/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.js b/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.ts similarity index 71% rename from x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.js rename to x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.ts index 4a807d3749394..1868a4f372ae0 100644 --- a/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.js +++ b/x-pack/plugins/watcher/public/lib/documentation_links/documentation_links.ts @@ -8,6 +8,8 @@ import { makeDocumentationLink } from './make_documentation_link'; export const documentationLinks = { watcher: { - putWatchApi: makeDocumentationLink('{baseUrl}guide/en/elasticsearch/reference/{urlVersion}/watcher-api-put-watch.html') - } + putWatchApi: makeDocumentationLink( + '{baseUrl}guide/en/elasticsearch/reference/{urlVersion}/watcher-api-put-watch.html' + ), + }, }; diff --git a/x-pack/plugins/watcher/public/lib/documentation_links/index.js b/x-pack/plugins/watcher/public/lib/documentation_links/index.ts similarity index 79% rename from x-pack/plugins/watcher/public/lib/documentation_links/index.js rename to x-pack/plugins/watcher/public/lib/documentation_links/index.ts index 2972d0ca80603..98a45e81f4e70 100644 --- a/x-pack/plugins/watcher/public/lib/documentation_links/index.js +++ b/x-pack/plugins/watcher/public/lib/documentation_links/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { documentationLinks } from './documentation_links.js'; +export { documentationLinks } from './documentation_links'; diff --git a/x-pack/plugins/watcher/public/lib/documentation_links/make_documentation_link.js b/x-pack/plugins/watcher/public/lib/documentation_links/make_documentation_link.ts similarity index 59% rename from x-pack/plugins/watcher/public/lib/documentation_links/make_documentation_link.js rename to x-pack/plugins/watcher/public/lib/documentation_links/make_documentation_link.ts index ebe81ae0f3a0f..bcd0880a7324c 100644 --- a/x-pack/plugins/watcher/public/lib/documentation_links/make_documentation_link.js +++ b/x-pack/plugins/watcher/public/lib/documentation_links/make_documentation_link.ts @@ -13,13 +13,6 @@ const minor = semver.minor(metadata.version); const urlVersion = `${major}.${minor}`; const baseUrl = 'https://www.elastic.co/'; -/** - * - * @param {string} linkTemplate Link template containing {baseUrl} and {urlVersion} placeholders - * @return {string} Actual link, with placeholders in template replaced - */ -export function makeDocumentationLink(linkTemplate) { - return linkTemplate - .replace('{baseUrl}', baseUrl) - .replace('{urlVersion}', urlVersion); +export function makeDocumentationLink(linkTemplate: string) { + return linkTemplate.replace('{baseUrl}', baseUrl).replace('{urlVersion}', urlVersion); } diff --git a/x-pack/plugins/watcher/public/models/execute_details/execute_details.js b/x-pack/plugins/watcher/public/models/execute_details/execute_details.js index c6c27d18b77e2..855f805349f59 100644 --- a/x-pack/plugins/watcher/public/models/execute_details/execute_details.js +++ b/x-pack/plugins/watcher/public/models/execute_details/execute_details.js @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { TIME_UNITS } from '../../../common/constants'; +import moment from 'moment'; + export class ExecuteDetails { constructor(props = {}) { - this.triggeredTime = props.triggeredTime; + this.triggeredTimeValue = props.triggeredTimeValue; + this.triggeredTimeUnit = props.triggeredTimeUnit; + this.scheduledTimeValue = props.scheduledTimeValue; + this.scheduledTimeUnit = props.scheduledTimeUnit; this.scheduledTime = props.scheduledTime; this.ignoreCondition = props.ignoreCondition; this.alternativeInput = props.alternativeInput; @@ -14,14 +20,36 @@ export class ExecuteDetails { this.recordExecution = props.recordExecution; } + formatTime(timeUnit, value) { + let timeValue = moment(); + switch (timeUnit) { + case TIME_UNITS.SECOND: + timeValue = timeValue.add(value, 'seconds'); + break; + case TIME_UNITS.MINUTE: + timeValue = timeValue.add(value, 'minutes'); + break; + case TIME_UNITS.HOUR: + timeValue = timeValue.add(value, 'hours'); + break; + case TIME_UNITS.MILLISECOND: + timeValue = timeValue.add(value, 'milliseconds'); + break; + } + return timeValue.format(); + } + get upstreamJson() { + const hasTriggerTime = this.triggeredTimeValue !== ''; + const hasScheduleTime = this.scheduledTimeValue !== ''; + const formattedTriggerTime = hasTriggerTime ? this.formatTime(this.triggeredTimeUnit, this.triggeredTimeValue) : undefined; + const formattedScheduleTime = hasScheduleTime ? this.formatTime(this.scheduledTimeUnit, this.scheduledTimeValue) : undefined; const triggerData = { - triggeredTime: this.triggeredTime, - scheduledTime: this.scheduledTime, + triggeredTime: formattedTriggerTime, + scheduledTime: formattedScheduleTime, }; - return { - triggerData: triggerData, + triggerData, ignoreCondition: this.ignoreCondition, alternativeInput: this.alternativeInput, actionModes: this.actionModes, diff --git a/x-pack/plugins/watcher/public/models/index.d.ts b/x-pack/plugins/watcher/public/models/index.d.ts index 94668b900d358..61ed512248a49 100644 --- a/x-pack/plugins/watcher/public/models/index.d.ts +++ b/x-pack/plugins/watcher/public/models/index.d.ts @@ -9,3 +9,20 @@ declare module 'plugins/watcher/models/watch' { declare module 'plugins/watcher/models/watch/threshold_watch' { export const ThresholdWatch: any; } +declare module 'plugins/watcher/models/watch/json_watch' { + export const JsonWatch: any; +} + +declare module 'plugins/watcher/models/execute_details/execute_details' { + export const ExecuteDetails: any; +} + +declare module 'plugins/watcher/models/watch_history_item' { + export const WatchHistoryItem: any; +} + +// TODO: Remove once typescript definitions are in EUI +declare module '@elastic/eui' { + export const EuiCodeEditor: React.SFC; + export const EuiDescribedFormGroup: React.SFC; +} diff --git a/x-pack/plugins/watcher/public/models/watch/base_watch.js b/x-pack/plugins/watcher/public/models/watch/base_watch.js index 8363ebaf05cc3..5cd96dce91690 100644 --- a/x-pack/plugins/watcher/public/models/watch/base_watch.js +++ b/x-pack/plugins/watcher/public/models/watch/base_watch.js @@ -25,7 +25,7 @@ export class BaseWatch { * @param {array} props.actions Action definitions */ constructor(props = {}) { - this.id = get(props, 'id'); + this.id = get(props, 'id', ''); this.type = get(props, 'type'); this.isNew = get(props, 'isNew', true); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_component.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_component.tsx new file mode 100644 index 0000000000000..47e1b26dc366e --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_component.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext, useState } from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiPageContent, + EuiSpacer, + EuiTab, + EuiTabs, + EuiTitle, +} from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; +import { WATCH_TAB_ID_EDIT, WATCH_TAB_ID_SIMULATE, WATCH_TABS } from '../../../../common/constants'; +import { JsonWatchEditForm } from './json_watch_edit_form'; +import { JsonWatchEditSimulate } from './json_watch_edit_simulate'; +import { WatchContext } from './watch_context'; + +const JsonWatchEditUi = ({ + pageTitle, + kbnUrl, + licenseService, +}: { + pageTitle: string; + kbnUrl: any; + licenseService: any; +}) => { + const { watch } = useContext(WatchContext); + // hooks + const [selectedTab, setSelectedTab] = useState(WATCH_TAB_ID_EDIT); + const [watchErrors, setWatchErrors] = useState<{ [key: string]: string[] }>({ + watchId: [], + watchJson: [], + }); + const [isShowingWatchErrors, setIsShowingWatchErrors] = useState(false); + const [executeWatchJsonString, setExecuteWatchJsonString] = useState(''); + const [isShowingExecuteWatchErrors, setIsShowingExecuteWatchErrors] = useState(false); + const [executeWatchErrors, setExecuteWatchErrors] = useState<{ [key: string]: string[] }>({ + simulateExecutionInputOverride: [], + }); + // ace editor requires json to be in string format + const [watchJsonString, setWatchJsonString] = useState( + JSON.stringify(watch.watch, null, 2) + ); + return ( + + + + +

{pageTitle}

+
+
+
+ + {WATCH_TABS.map((tab, index) => ( + { + setSelectedTab(tab.id); + }} + isSelected={tab.id === selectedTab} + key={index} + > + {tab.name} + + ))} + + + {selectedTab === WATCH_TAB_ID_SIMULATE && ( + setExecuteWatchJsonString(json)} + errors={executeWatchErrors} + setErrors={(errors: { [key: string]: string[] }) => setExecuteWatchErrors(errors)} + isShowingErrors={isShowingExecuteWatchErrors} + setIsShowingErrors={(isShowingErrors: boolean) => + setIsShowingExecuteWatchErrors(isShowingErrors) + } + isDisabled={isShowingExecuteWatchErrors || isShowingWatchErrors} + /> + )} + {selectedTab === WATCH_TAB_ID_EDIT && ( + setWatchJsonString(json)} + errors={watchErrors} + setErrors={(errors: { [key: string]: string[] }) => setWatchErrors(errors)} + isShowingErrors={isShowingWatchErrors} + setIsShowingErrors={(isShowingErrors: boolean) => + setIsShowingWatchErrors(isShowingErrors) + } + /> + )} +
+ ); +}; + +export const JsonWatchEdit = injectI18n(JsonWatchEditUi); diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx new file mode 100644 index 0000000000000..d2e77c938cfad --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_form.tsx @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useContext, useState } from 'react'; + +import { + EuiButton, + EuiButtonEmpty, + EuiCodeEditor, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { JsonWatch } from 'plugins/watcher/models/watch/json_watch'; +import { ConfirmWatchesModal } from '../../../components/confirm_watches_modal'; +import { ErrableFormRow } from '../../../components/form_errors'; +import { documentationLinks } from '../../../lib/documentation_links'; +import { onWatchSave, saveWatch } from '../json_watch_edit_actions'; +import { WatchContext } from './watch_context'; + +const JSON_WATCH_IDS = { + ID: 'watchId', + NAME: 'watchName', + JSON: 'watchJson', +}; + +function validateId(id: string) { + const regex = /^[A-Za-z0-9\-\_]+$/; + if (!id) { + return i18n.translate('xpack.watcher.sections.watchEdit.json.error.requiredIdText', { + defaultMessage: 'ID is required', + }); + } else if (!regex.test(id)) { + return i18n.translate('xpack.watcher.sections.watchEdit.json.error.invalidIdText', { + defaultMessage: 'ID must only letters, underscores, dashes, and numbers.', + }); + } + return false; +} + +export const JsonWatchEditForm = ({ + kbnUrl, + licenseService, + watchJsonString, + setWatchJsonString, + errors, + setErrors, + isShowingErrors, + setIsShowingErrors, +}: { + kbnUrl: any; + licenseService: any; + watchJsonString: string; + setWatchJsonString: (json: string) => void; + errors: { [key: string]: string[] }; + setErrors: (errors: { [key: string]: string[] }) => void; + isShowingErrors: boolean; + setIsShowingErrors: (isShowingErrors: boolean) => void; +}) => { + const { watch, setWatch } = useContext(WatchContext); + // hooks + const [modal, setModal] = useState<{ message: string } | null>(null); + return ( + + { + if (isConfirmed) { + saveWatch(watch, kbnUrl, licenseService); + } + setModal(null); + }} + /> + + + ) => { + const id = e.target.value; + const error = validateId(id); + const newErrors = { ...errors, [JSON_WATCH_IDS.ID]: error ? [error] : [] }; + const isInvalidForm = !!Object.keys(newErrors).find( + errorKey => newErrors[errorKey].length >= 1 + ); + setErrors(newErrors); + setIsShowingErrors(isInvalidForm); + setWatch(new JsonWatch({ ...watch, id })); + }} + /> + + + ) => { + setWatch(new JsonWatch({ ...watch, name: e.target.value })); + }} + /> + + + {i18n.translate('xpack.watcher.sections.watchEdit.json.form.watchJsonLabel', { + defaultMessage: 'Watch JSON', + })}{' '} + ( + + {i18n.translate('xpack.watcher.sections.watchEdit.json.form.watchJsonDocLink', { + defaultMessage: 'Syntax', + })} + + ) + + } + errorKey={JSON_WATCH_IDS.JSON} + isShowingErrors={isShowingErrors} + fullWidth + errors={errors} + > + { + setWatchJsonString(json); + try { + const watchJson = JSON.parse(json); + if (watchJson && typeof watchJson === 'object') { + setWatch( + new JsonWatch({ + ...watch, + watch: watchJson, + }) + ); + const newErrors = { ...errors, [JSON_WATCH_IDS.JSON]: [] }; + const isInvalidForm = !!Object.keys(newErrors).find( + errorKey => newErrors[errorKey].length >= 1 + ); + setErrors(newErrors); + setIsShowingErrors(isInvalidForm); + } + } catch (e) { + setErrors({ + ...errors, + [JSON_WATCH_IDS.JSON]: [ + i18n.translate('xpack.watcher.sections.watchEdit.json.error.invalidJsonText', { + defaultMessage: 'Invalid JSON', + }), + ], + }); + setIsShowingErrors(true); + } + }} + /> + + + + { + const error = validateId(watch.id); + setErrors({ ...errors, [JSON_WATCH_IDS.ID]: error ? [error] : [] }); + setIsShowingErrors(!!error); + if (!error) { + const savedWatch = await onWatchSave(watch, kbnUrl, licenseService); + if (savedWatch && savedWatch.error) { + return setModal(savedWatch.error); + } + } + }} + > + {i18n.translate('xpack.watcher.sections.watchEdit.json.saveButtonLabel', { + defaultMessage: 'Save', + })} + + + + + {i18n.translate('xpack.watcher.sections.watchEdit.json.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + + + ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate.tsx new file mode 100644 index 0000000000000..51977ed724c75 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate.tsx @@ -0,0 +1,484 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useContext, useState } from 'react'; + +import { + EuiBasicTable, + EuiButton, + EuiCodeEditor, + EuiDescribedFormGroup, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiSwitch, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { map } from 'lodash'; +import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; +import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; +import { toastNotifications } from 'ui/notify'; +import { ACTION_MODES, TIME_UNITS } from '../../../../common/constants'; +import { getActionType } from '../../../../common/lib/get_action_type'; +import { BaseWatch, ExecutedWatchResults } from '../../../../common/types/watch_types'; +import { ErrableFormRow } from '../../../components/form_errors'; +import { executeWatch } from '../../../lib/api'; +import { WatchContext } from '../../../sections/watch_edit/components/watch_context'; +import { timeUnits } from '../time_units'; +import { JsonWatchEditSimulateResults } from './json_watch_edit_simulate_results'; + +interface TableDataRow { + actionId: string | undefined; + actionMode: string | undefined; + type: string; +} + +interface TableData extends Array {} + +const EXECUTE_DETAILS_INITIAL_STATE = { + triggeredTimeValue: 0, + triggeredTimeUnit: TIME_UNITS.MILLISECOND, + scheduledTimeValue: 0, + scheduledTimeUnit: TIME_UNITS.SECOND, + ignoreCondition: false, +}; + +const INPUT_OVERRIDE_ID = 'simulateExecutionInputOverride'; + +function getTableData(watch: BaseWatch) { + const actions = watch.watch && watch.watch.actions; + return map(actions, (action, actionId) => { + const type = getActionType(action); + return { + actionId, + type, + actionMode: ACTION_MODES.SIMULATE, + }; + }); +} + +function getActionModes(items: TableData) { + const result = items.reduce((itemsAccum: any, item) => { + if (item.actionId) { + itemsAccum[item && item.actionId] = item.actionMode; + } + return itemsAccum; + }, {}); + return result; +} + +export const JsonWatchEditSimulate = ({ + executeWatchJsonString, + setExecuteWatchJsonString, + errors, + setErrors, + isShowingErrors, + setIsShowingErrors, + isDisabled, +}: { + executeWatchJsonString: string; + setExecuteWatchJsonString: (json: string) => void; + errors: { [key: string]: string[] }; + setErrors: (errors: { [key: string]: string[] }) => void; + isShowingErrors: boolean; + setIsShowingErrors: (isShowingErrors: boolean) => void; + isDisabled: boolean; +}) => { + const { watch } = useContext(WatchContext); + const tableData = getTableData(watch); + + // hooks + const [executeDetails, setExecuteDetails] = useState( + new ExecuteDetails({ + ...EXECUTE_DETAILS_INITIAL_STATE, + actionModes: getActionModes(tableData), + }) + ); + const [executeResults, setExecuteResults] = useState(null); + + const columns = [ + { + field: 'actionId', + name: i18n.translate('xpack.watcher.sections.watchEdit.simulate.table.idColumnLabel', { + defaultMessage: 'ID', + }), + sortable: true, + truncateText: true, + }, + { + field: 'type', + name: i18n.translate('xpack.watcher.sections.watchEdit.simulate.table.typeColumnLabel', { + defaultMessage: 'Type', + }), + truncateText: true, + }, + { + field: 'actionMode', + name: i18n.translate('xpack.watcher.sections.watchEdit.simulate.table.modeColumnLabel', { + defaultMessage: 'Mode', + }), + render: ({}, row: { actionId: string }) => ( + { + setExecuteDetails( + new ExecuteDetails({ + ...executeDetails, + actionModes: { ...executeDetails.actionModes, [row.actionId]: e.target.value }, + }) + ); + }} + aria-label={i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.table.modeSelectLabel', + { + defaultMessage: 'Action modes', + } + )} + /> + ), + }, + ]; + + return ( + + {executeResults && ( + setExecuteResults(null)} + /> + )} + +

+ {i18n.translate('xpack.watcher.sections.watchEdit.simulate.pageDescription', { + defaultMessage: 'Modify the fields below to simulate a watch execution.', + })} +

+
+ + + + {i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.triggerOverridesTitle', + { defaultMessage: 'Trigger overrides' } + )} + + } + description={i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.triggerOverridesDescription', + { + defaultMessage: + 'These fields are parsed as the data of the trigger event that will be used during the watch execution.', + } + )} + > + + + + { + const value = e.target.value; + setExecuteDetails( + new ExecuteDetails({ + ...executeDetails, + scheduledTimeValue: value === '' ? value : parseInt(value, 10), + }) + ); + }} + /> + + + { + setExecuteDetails( + new ExecuteDetails({ + ...executeDetails, + scheduledTimeUnit: e.target.value, + }) + ); + }} + /> + + + + + + + { + const value = e.target.value; + setExecuteDetails( + new ExecuteDetails({ + ...executeDetails, + triggeredTimeValue: value === '' ? value : parseInt(value, 10), + }) + ); + }} + /> + + + { + setExecuteDetails( + new ExecuteDetails({ + ...executeDetails, + triggeredTimeUnit: e.target.value, + }) + ); + }} + /> + + + + + + {i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.inputOverridesTitle', + { defaultMessage: 'Input overrides' } + )} + + } + description={i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.inputOverridesDescription', + { + defaultMessage: + 'When present, the watch uses this object as a payload instead of executing its own input.', + } + )} + > + + { + setExecuteWatchJsonString(json); + try { + const alternativeInput = json === '' ? undefined : JSON.parse(json); + if ( + typeof alternativeInput === 'undefined' || + (alternativeInput && typeof alternativeInput === 'object') + ) { + setExecuteDetails( + new ExecuteDetails({ + ...executeDetails, + alternativeInput, + }) + ); + setIsShowingErrors(false); + setErrors({ ...errors, [INPUT_OVERRIDE_ID]: [] }); + } + } catch (e) { + setErrors({ + ...errors, + [INPUT_OVERRIDE_ID]: [ + i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.alternativeInputFieldError', + { + defaultMessage: 'Invalid JSON', + } + ), + ], + }); + setIsShowingErrors(true); + } + }} + /> + + + + {i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.conditionOverridesTitle', + { defaultMessage: 'Condition overrides' } + )} + + } + description={i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.conditionOverridesDescription', + { + defaultMessage: 'When enabled, the watch execution uses the Always Condition.', + } + )} + > + { + setExecuteDetails( + new ExecuteDetails({ ...executeDetails, ignoreCondition: e.target.checked }) + ); + }} + /> + + + {i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.actionOverridesTitle', + { defaultMessage: 'Action overrides' } + )} + + } + description={i18n.translate( + 'xpack.watcher.sections.watchEdit.simulate.form.actionOverridesDescription', + { + defaultMessage: + 'The action modes determine how to handle the watch actions as part of the watch execution.', + } + )} + > + + + + + { + try { + const executedWatch = await executeWatch(executeDetails, watch); + const formattedResults = WatchHistoryItem.fromUpstreamJson( + executedWatch.watchHistoryItem + ); + setExecuteResults(formattedResults); + } catch (e) { + return toastNotifications.addDanger(e.data.message); + } + }} + > + {i18n.translate('xpack.watcher.sections.watchEdit.simulate.form.saveButtonLabel', { + defaultMessage: 'Simulate watch', + })} + + +
+ ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate_results.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate_results.tsx new file mode 100644 index 0000000000000..cc67aefe98650 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/json_watch_edit_simulate_results.tsx @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { + EuiBasicTable, + EuiCodeBlock, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiHealth, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { find } from 'lodash'; +import { WATCH_STATES } from '../../../../common/constants'; +import { + BaseWatch, + ExecutedWatchDetails, + ExecutedWatchResults, +} from '../../../../common/types/watch_types'; + +const WATCH_ICON_COLORS = { + [WATCH_STATES.DISABLED]: 'subdued', + [WATCH_STATES.OK]: 'success', + [WATCH_STATES.FIRING]: 'warning', + [WATCH_STATES.ERROR]: 'danger', + [WATCH_STATES.CONFIG_ERROR]: 'danger', +}; + +export const JsonWatchEditSimulateResults = ({ + executeDetails, + executeResults, + onCloseFlyout, + watch, +}: { + executeDetails: ExecutedWatchDetails; + executeResults: ExecutedWatchResults; + onCloseFlyout: () => void; + watch: BaseWatch; +}) => { + const getTableData = () => { + const actions = watch.actions; + const actionStatuses = executeResults.watchStatus.actionStatuses; + const actionModes = executeDetails.actionModes; + const actionDetails = actions.map(action => { + const actionMode = actionModes[action.id]; + const actionStatus = find(actionStatuses, { id: action.id }); + + return { + actionId: action.id, + actionType: action.type, + actionMode, + actionState: actionStatus && actionStatus.state, + actionReason: actionStatus && actionStatus.lastExecutionReason, + }; + }); + return actionDetails; + }; + + const tableData = getTableData(); + + const columns = [ + { + field: 'actionId', + name: i18n.translate( + 'xpack.watcher.sections.watchEdit.simulateResults.table.actionColumnLabel', + { + defaultMessage: 'ID', + } + ), + sortable: true, + truncateText: true, + }, + { + field: 'actionType', + name: i18n.translate( + 'xpack.watcher.sections.watchEdit.simulateResults.table.typeColumnLabel', + { + defaultMessage: 'Type', + } + ), + truncateText: true, + }, + { + field: 'actionMode', + name: i18n.translate( + 'xpack.watcher.sections.watchEdit.simulateResults.table.modeColumnLabel', + { + defaultMessage: 'Mode', + } + ), + }, + { + field: 'actionState', + name: i18n.translate( + 'xpack.watcher.sections.watchEdit.simulateResults.table.stateColumnLabel', + { + defaultMessage: 'State', + } + ), + dataType: 'string', + render: (actionState: string) => { + return {actionState}; + }, + }, + { + field: 'actionReason', + name: i18n.translate( + 'xpack.watcher.sections.watchEdit.simulateResults.table.reasonColumnLabel', + { + defaultMessage: 'Reason', + } + ), + }, + ]; + + return ( + { + onCloseFlyout(); + }} + aria-labelledby="simulateResultsFlyOutTitle" + > + + +

+ {i18n.translate('xpack.watcher.sections.watchEdit.simulateResults.title', { + defaultMessage: 'Simulation results', + })}{' '} + + {executeResults.watchStatus.state} + +

+
+
+ + +
+ {i18n.translate( + 'xpack.watcher.sections.watchEdit.simulateResults.actionsSectionTitle', + { + defaultMessage: 'Actions', + } + )} +
+
+ + + + +
+ {i18n.translate( + 'xpack.watcher.sections.watchEdit.simulateResults.simulationOutputSectionTitle', + { + defaultMessage: 'Simulation output', + } + )} +
+
+ + + {JSON.stringify(executeResults.details, null, 2)} + +
+
+ ); +}; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx index df4f97ca7e88a..883800e57ea45 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit_component.tsx @@ -39,17 +39,6 @@ const firstFieldOption = { }), value: '', }; -const getTitle = (watch: any) => { - if (watch.isNew) { - const typeName = watch.typeName.toLowerCase(); - return i18n.translate('xpack.watcher.sections.watchEdit.titlePanel.createNewTypeOfWatchTitle', { - defaultMessage: 'Create a new {typeName}', - values: { typeName }, - }); - } else { - return watch.name; - } -}; const getFields = async (indices: string[]) => { return await fetchFields(indices); }; @@ -125,9 +114,11 @@ const getIndexOptions = async (patternString: string, indexPatterns: string[]) = const ThresholdWatchEditUi = ({ intl, savedObjectsClient, + pageTitle, }: { intl: InjectedIntl; savedObjectsClient: any; + pageTitle: string; }) => { // hooks const [indexPatterns, setIndexPatterns] = useState([]); @@ -164,7 +155,7 @@ const ThresholdWatchEditUi = ({ -

{getTitle(watch)}

+

{pageTitle}

@@ -229,7 +220,7 @@ const ThresholdWatchEditUi = ({ setWatch(new ThresholdWatch(watch)); const indices = selected.map(s => s.value as string); const theFields = await getFields(indices); - setFields(theFieldsO); + setFields(theFields); setTimeFieldOptions(getTimeFieldOptions(fields)); }} diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx index 45a46076fc903..51a8e692e4930 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx @@ -4,22 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLoadingSpinner } from '@elastic/eui'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { Watch } from 'plugins/watcher/models/watch'; import React, { useEffect, useState } from 'react'; +import { WATCH_TYPES } from '../../../../common/constants'; +import { BaseWatch } from '../../../../common/types/watch_types'; import { loadWatch } from '../../../lib/api'; +import { JsonWatchEdit } from './json_watch_edit_component'; import { ThresholdWatchEdit } from './threshold_watch_edit_component'; import { WatchContext } from './watch_context'; +const getTitle = (watch: BaseWatch) => { + if (watch.isNew) { + const typeName = watch.typeName.toLowerCase(); + return i18n.translate( + 'xpack.watcher.sections.watchEdit.json.titlePanel.createNewTypeOfWatchTitle', + { + defaultMessage: 'Create a new {typeName}', + values: { typeName }, + } + ); + } else { + return i18n.translate('xpack.watcher.sections.watchEdit.json.titlePanel.editWatchTitle', { + defaultMessage: 'Edit {watchName}', + values: { watchName: watch.name ? watch.name : watch.id }, + }); + } +}; + export const WatchEdit = ({ watchId, watchType, savedObjectsClient, + kbnUrl, + licenseService, }: { watchId: string; watchType: string; savedObjectsClient: any; + kbnUrl: any; + licenseService: any; }) => { // hooks const [watch, setWatch] = useState(null); @@ -41,15 +66,24 @@ export const WatchEdit = ({ if (!watch) { return ; } + const pageTitle = getTitle(watch); let EditComponent = null; - if (watch.type === 'threshold') { + if (watch.type === WATCH_TYPES.THRESHOLD) { EditComponent = ThresholdWatchEdit; } else { EditComponent = EuiSpacer; } + if (watch.type === WATCH_TYPES.JSON) { + EditComponent = JsonWatchEdit; + } return ( - + ); }; diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/json_watch_edit_actions.ts b/x-pack/plugins/watcher/public/sections/watch_edit/json_watch_edit_actions.ts new file mode 100644 index 0000000000000..2eec64dab2404 --- /dev/null +++ b/x-pack/plugins/watcher/public/sections/watch_edit/json_watch_edit_actions.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { toastNotifications } from 'ui/notify'; +import { ACTION_TYPES } from '../../../common/constants'; +import { BaseWatch } from '../../../common/types/watch_types'; +import { createWatch, loadWatch } from '../../lib/api'; + +/** + * Get the type from an action where a key defines its type. + * eg: { email: { ... } } | { slack: { ... } } + */ +function getTypeFromAction(action: { [key: string]: any }) { + const actionKeys = Object.keys(action); + let type; + Object.keys(ACTION_TYPES).forEach(k => { + if (actionKeys.includes(ACTION_TYPES[k])) { + type = ACTION_TYPES[k]; + } + }); + + return type ? type : ACTION_TYPES.UNKNOWN; +} + +function getPropsFromAction(type: string, action: { [key: string]: any }) { + if (type === ACTION_TYPES.SLACK) { + // Slack action has its props inside the "message" object + return action[type].message; + } + return action[type]; +} + +/** + * Actions instances are not automatically added to the Watch _actions_ Array + * when we add them in the Json editor. This method takes takes care of it. + */ +function createActionsForWatch(watchInstance: BaseWatch) { + watchInstance.resetActions(); + + let action; + let type; + let actionProps; + + Object.keys(watchInstance.watch.actions).forEach(k => { + action = watchInstance.watch.actions[k]; + type = getTypeFromAction(action); + actionProps = getPropsFromAction(type, action); + watchInstance.createAction(type, actionProps); + }); + return watchInstance; +} + +export async function saveWatch(watch: BaseWatch, kbnUrl: any, licenseService: any) { + try { + await createWatch(watch); + toastNotifications.addSuccess( + i18n.translate('xpack.watcher.sections.watchEdit.json.saveSuccessNotificationText', { + defaultMessage: "Saved '{watchDisplayName}'", + values: { + watchDisplayName: watch.displayName, + }, + }) + ); + // TODO: Not correctly redirecting back to /watches route + kbnUrl.change('/management/elasticsearch/watcher/watches', {}); + } catch (error) { + return licenseService + .checkValidity() + .then(() => toastNotifications.addDanger(error.data.message)); + } +} + +export async function validateActionsAndSaveWatch( + watch: BaseWatch, + kbnUrl: any, + licenseService: any +) { + const { warning } = watch.validate(); + if (warning) { + return { + error: { + message: warning.message, + }, + }; + } + // client validation passed, make request to create watch + saveWatch(watch, kbnUrl, licenseService); +} + +export async function onWatchSave( + watch: BaseWatch, + kbnUrl: any, + licenseService: any +): Promise { + const watchActions = watch.watch && watch.watch.actions; + const watchData = watchActions ? createActionsForWatch(watch) : watch; + + if (!watchData.isNew) { + return validateActionsAndSaveWatch(watch, kbnUrl, licenseService); + } + + try { + const existingWatch = await loadWatch(watchData.id); + if (existingWatch) { + return { + error: { + message: i18n.translate( + 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.descriptionText', + { + defaultMessage: + 'Watch with ID "{watchId}" {watchNameMessageFragment} already exists. Do you want to overwrite it?', + values: { + watchId: existingWatch.id, + watchNameMessageFragment: existingWatch.name + ? i18n.translate( + 'xpack.watcher.sections.watchEdit.json.saveConfirmModal.descriptionFragmentText', + { + defaultMessage: '(name: "{existingWatchName}")', + values: { + existingWatchName: existingWatch.name, + }, + } + ) + : '', + }, + } + ), + }, + }; + } + } catch (error) { + // Confirms watcher does not already exist + return licenseService.checkValidity().then(() => { + if (error.status === 404) { + return validateActionsAndSaveWatch(watchData, kbnUrl, licenseService); + } + return toastNotifications.addDanger(error.data.message); + }); + } +} diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts b/x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts index a4fd9e3fddbcf..3b5ed35044f48 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts +++ b/x-pack/plugins/watcher/public/sections/watch_edit/time_units.ts @@ -12,6 +12,14 @@ interface TimeUnit { labelSingular: string; } export const timeUnits: { [key: string]: TimeUnit } = { + [COMMON_TIME_UNITS.MILLISECOND]: { + labelPlural: i18n.translate('xpack.watcher.timeUnits.millisecondPluralLabel', { + defaultMessage: 'milliseconds', + }), + labelSingular: i18n.translate('xpack.watcher.timeUnits.millisecondSingularLabel', { + defaultMessage: 'millisecond', + }), + }, [COMMON_TIME_UNITS.SECOND]: { labelPlural: i18n.translate('xpack.watcher.timeUnits.secondPluralLabel', { defaultMessage: 'seconds', diff --git a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.js b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.js index bb155ee91d107..b6f3b51d8471b 100644 --- a/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.js +++ b/x-pack/plugins/watcher/public/sections/watch_edit/watch_edit_route.js @@ -23,10 +23,16 @@ import { manageAngularLifecycle } from '../../lib/manage_angular_lifecycle'; import { WatchEdit } from './components/watch_edit'; let elem; -const renderReact = async (elem, watchType, watchId, savedObjectsClient) => { +const renderReact = async (elem, watchType, watchId, savedObjectsClient, kbnUrl, licenseService) => { render( - + , elem ); @@ -42,6 +48,8 @@ routes controller: class WatchEditRouteController { constructor($injector, $scope, $http, Private) { const $route = $injector.get('$route'); + const kbnUrl = $injector.get('kbnUrl'); + const licenseService = $injector.get('xpackWatcherLicenseService'); this.watch = $route.current.locals.xpackWatch; this.WATCH_TYPES = WATCH_TYPES; const watchId = $route.current.params.id; @@ -56,7 +64,7 @@ routes elem = document.getElementById('watchEditReactRoot'); const savedObjectsClient = Private(SavedObjectsClientProvider); - renderReact(elem, watchType, watchId, savedObjectsClient); + renderReact(elem, watchType, watchId, savedObjectsClient, kbnUrl, licenseService); manageAngularLifecycle($scope, $route, elem); }); } diff --git a/x-pack/plugins/watcher/server/models/execute_details/execute_details.js b/x-pack/plugins/watcher/server/models/execute_details/execute_details.js index 2c67c4896cbcd..229bd29f07e2d 100644 --- a/x-pack/plugins/watcher/server/models/execute_details/execute_details.js +++ b/x-pack/plugins/watcher/server/models/execute_details/execute_details.js @@ -18,7 +18,7 @@ export class ExecuteDetails { get upstreamJson() { const triggerData = { triggered_time: this.triggerData.triggeredTime, - scheduled_time: this.triggerData.scheduledTime + scheduled_time: this.triggerData.scheduledTime, }; const result = {