Skip to content

Commit

Permalink
Add NoPermissionsError component and use it to surface 403 errors in …
Browse files Browse the repository at this point in the history
…WatchList, WatchStatus, and WatchEdit.
  • Loading branch information
cjcenizal committed Apr 23, 2019
1 parent 8490b27 commit 671c596
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 15 deletions.
27 changes: 27 additions & 0 deletions x-pack/plugins/watcher/public/components/no_permissions_error.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<EuiEmptyPrompt
iconType="securityApp"
iconColor={null}
title={
<h2>
<FormattedMessage
id="xpack.watcher.noPermissionsError.deniedPermissionTitle"
defaultMessage="You don't have privileges to use Watcher"
/>
</h2>
}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
};
}
};

Expand All @@ -65,37 +90,58 @@ 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) {
dispatch({ command: 'setWatch', payload: new WatchType() });
}
}
};

useEffect(() => {
getWatch();
}, []);

if (error && error.status === 403) {
return (
<EuiPageContent>
<NoPermissionsError />
</EuiPageContent>
);
}

if (!watch) {
return <EuiLoadingSpinner />;
}

const pageTitle = getTitle(watch);

let EditComponent = null;

if (watch.type === WATCH_TYPES.THRESHOLD) {
EditComponent = ThresholdWatchEdit;
} else {
EditComponent = JsonWatchEdit;
}

return (
<WatchContext.Provider value={{ watch, setWatchProperty, addAction }}>
<EditComponent pageTitle={pageTitle} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = {
Expand All @@ -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 (
<EuiPageContent>
<NoPermissionsError />
</EuiPageContent>
);
}

const columns = [
{
field: 'id',
Expand Down Expand Up @@ -291,4 +301,5 @@ const WatchListUi = ({ intl }: { intl: InjectedIntl }) => {
</EuiPageContent>
);
};

export const WatchList = injectI18n(WatchListUi);
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Fragment>
<EuiTitle size="m">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<boolean | undefined>(undefined);
const [detailWatchId, setDetailWatchId] = useState<string | undefined>(undefined);
const [watchesToDelete, setWatchesToDelete] = useState<string[]>([]);
Expand All @@ -84,21 +85,34 @@ 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.
setIsActivated(loadedWatch.watchStatus.isActive);
}

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 <NoPermissionsError />;
}

const pagination = {
initialPageSize: 10,
pageSizeOptions: [10, 50, 100],
Expand Down Expand Up @@ -338,4 +352,4 @@ const WatchHistoryUI = ({ intl, watchId }: { intl: InjectedIntl; watchId: string
);
};

export const WatchHistory = injectI18n(WatchHistoryUI);
export const WatchHistory = injectI18n(WatchHistoryUi);

0 comments on commit 671c596

Please sign in to comment.