From 671c59693185cef082395ae7491bf3b506408533 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 22 Apr 2019 21:10:11 -0700 Subject: [PATCH] Add NoPermissionsError component and use it to surface 403 errors in WatchList, WatchStatus, and WatchEdit. --- .../components/no_permissions_error.tsx | 27 ++++++++ .../watch_edit/components/watch_edit.tsx | 68 ++++++++++++++++--- .../watch_list/components/watch_list.tsx | 11 +++ .../watch_status/components/watch_detail.tsx | 6 ++ .../watch_status/components/watch_history.tsx | 22 ++++-- 5 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/watcher/public/components/no_permissions_error.tsx diff --git a/x-pack/plugins/watcher/public/components/no_permissions_error.tsx b/x-pack/plugins/watcher/public/components/no_permissions_error.tsx new file mode 100644 index 000000000000000..8a01f01f067f30f --- /dev/null +++ b/x-pack/plugins/watcher/public/components/no_permissions_error.tsx @@ -0,0 +1,27 @@ +/* + * 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 { EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export function NoPermissionsError() { + return ( + + + + } + /> + ); +} 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 46de27ab9583896..bd135215100f3ce 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,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLoadingSpinner } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { Watch } from 'plugins/watcher/models/watch'; import React, { useEffect, useReducer } from 'react'; import { isEqual } from 'lodash'; + +import { EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Watch } from 'plugins/watcher/models/watch'; import { WATCH_TYPES } from '../../../../common/constants'; import { BaseWatch } from '../../../../common/types/watch_types'; +import { NoPermissionsError } from '../../../components/no_permissions_error'; import { loadWatch } from '../../../lib/api'; import { JsonWatchEdit } from './json_watch_edit'; import { ThresholdWatchEdit } from './threshold_watch_edit'; @@ -33,22 +35,45 @@ const getTitle = (watch: BaseWatch) => { }); } }; + const watchReducer = (state: any, action: any) => { const { command, payload } = action; + const { watch } = state; + switch (command) { case 'setWatch': - return payload; + return { + ...state, + watch: payload, + }; + case 'setProperty': const { property, value } = payload; - if (isEqual(state[property], value)) { + if (isEqual(watch[property], value)) { return state; } else { - return new (Watch.getWatchTypes())[state.type]({ ...state, [property]: value }); + return new (Watch.getWatchTypes())[watch.type]({ + ...state, + watch: { + ...watch, + [property]: value, + }, + }); } + case 'addAction': - const newWatch = new (Watch.getWatchTypes())[state.type](state); + const newWatch = new (Watch.getWatchTypes())[watch.type](watch); newWatch.addAction(payload); - return newWatch; + return { + ...state, + watch: newWatch, + }; + + case 'setError': + return { + ...state, + error: payload, + }; } }; @@ -65,17 +90,24 @@ export const WatchEdit = ({ }; }) => { // hooks - const [watch, dispatch] = useReducer(watchReducer, null); + const [{ watch, error }, dispatch] = useReducer(watchReducer, { watch: null }); + const setWatchProperty = (property: string, value: any) => { dispatch({ command: 'setProperty', payload: { property, value } }); }; + const addAction = (action: any) => { dispatch({ command: 'addAction', payload: action }); }; + const getWatch = async () => { if (id) { - const theWatch = await loadWatch(id); - dispatch({ command: 'setWatch', payload: theWatch }); + try { + const loadedWatch = await loadWatch(id); + dispatch({ command: 'setWatch', payload: loadedWatch }); + } catch (error) { + dispatch({ command: 'setError', payload: error }); + } } else if (type) { const WatchType = Watch.getWatchTypes()[type]; if (WatchType) { @@ -83,19 +115,33 @@ export const WatchEdit = ({ } } }; + useEffect(() => { getWatch(); }, []); + + if (error && error.status === 403) { + return ( + + + + ); + } + if (!watch) { return ; } + const pageTitle = getTitle(watch); + let EditComponent = null; + if (watch.type === WATCH_TYPES.THRESHOLD) { EditComponent = ThresholdWatchEdit; } else { EditComponent = JsonWatchEdit; } + return ( 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 6ea823e4a8b2c91..53af613473abf85 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 @@ -25,6 +25,7 @@ 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 { NoPermissionsError } from '../../../components/no_permissions_error'; import { loadWatches } from '../../../lib/api'; const stateToIcon: { [key: string]: JSX.Element } = { @@ -44,12 +45,21 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { const { isLoading: isWatchesLoading, data: watches, + error, } = loadWatches(REFRESH_INTERVALS.WATCH_LIST); const availableWatches = useMemo(() => ( watches ? watches.filter((watch: any) => !deletedWatches.includes(watch.id)) : undefined ), [watches, deletedWatches]); + if (error && error.status === 403) { + return ( + + + + ); + } + const columns = [ { field: 'id', @@ -291,4 +301,5 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => { ); }; + export const WatchList = injectI18n(WatchListUi); diff --git a/x-pack/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx b/x-pack/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx index 8b4b811f0d99cf3..6f7b602209bac0a 100644 --- a/x-pack/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx @@ -60,10 +60,16 @@ const WatchDetailUi = ({ intl, watchId }: { intl: InjectedIntl; watchId: string ]; const { + error, data: watchDetail, isLoading, } = loadWatchDetail(watchId); + // Another part of the UI will surface the no-permissions error. + if (error && error.status === 403) { + return null; + } + return ( diff --git a/x-pack/plugins/watcher/public/sections/watch_status/components/watch_history.tsx b/x-pack/plugins/watcher/public/sections/watch_status/components/watch_history.tsx index b04786d1911dc97..9075b7a2b16841d 100644 --- a/x-pack/plugins/watcher/public/sections/watch_status/components/watch_history.tsx +++ b/x-pack/plugins/watcher/public/sections/watch_status/components/watch_history.tsx @@ -27,6 +27,7 @@ import { import { goToWatchList } from '../../../lib/navigation'; import { DeleteWatchesModal } from '../../../components/delete_watches_modal'; +import { NoPermissionsError } from '../../../components/no_permissions_error'; import { WatchActionStatus } from './watch_action_status'; import { activateWatch, @@ -75,7 +76,7 @@ const watchHistoryTimeSpanOptions = [ }, ]; -const WatchHistoryUI = ({ intl, watchId }: { intl: InjectedIntl; watchId: string }) => { +const WatchHistoryUi = ({ intl, watchId }: { intl: InjectedIntl; watchId: string }) => { const [isActivated, setIsActivated] = useState(undefined); const [detailWatchId, setDetailWatchId] = useState(undefined); const [watchesToDelete, setWatchesToDelete] = useState([]); @@ -84,7 +85,7 @@ const WatchHistoryUI = ({ intl, watchId }: { intl: InjectedIntl; watchId: string watchHistoryTimeSpanOptions[0].value ); - const { data: loadedWatch } = loadWatchDetail(watchId); + const { error: watchDetailError, data: loadedWatch } = loadWatchDetail(watchId); if (loadedWatch && isActivated === undefined) { // Set initial value for isActivated based on the watch we just loaded. @@ -92,13 +93,26 @@ const WatchHistoryUI = ({ intl, watchId }: { intl: InjectedIntl; watchId: string } const { + error: historyError, data: history, isLoading, } = loadWatchHistory(watchId, watchHistoryTimeSpan); - const { data: watchHistoryDetails } = loadWatchHistoryDetail(detailWatchId); + const { + error: watchHistoryDetailsError, + data: watchHistoryDetails, + } = loadWatchHistoryDetail(detailWatchId); + const executionDetail = watchHistoryDetails ? JSON.stringify(watchHistoryDetails.details, null, 2) : ''; + if ( + watchDetailError && watchDetailError.status === 403 + || historyError && historyError.status === 403 + || watchHistoryDetailsError && watchHistoryDetailsError.status === 403 + ) { + return ; + } + const pagination = { initialPageSize: 10, pageSizeOptions: [10, 50, 100], @@ -338,4 +352,4 @@ const WatchHistoryUI = ({ intl, watchId }: { intl: InjectedIntl; watchId: string ); }; -export const WatchHistory = injectI18n(WatchHistoryUI); +export const WatchHistory = injectI18n(WatchHistoryUi);