From b8039877d87684efa8482b9c629ddecec712913e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Morel?= Date: Mon, 25 Nov 2019 17:24:05 +0100 Subject: [PATCH 1/2] handle 403 errors in descriptions and openers --- .../detail/components/forbiddenDescription.js | 28 +++++++++++ src/app/business/common/reducers/item.js | 4 ++ src/app/business/common/sagas/index.js | 20 ++++++++ .../detail/components/tabs/index.js | 14 +++++- .../detail/components/tabs/redux.js | 3 +- src/app/business/routes/algo/sagas/index.js | 20 +------- .../detail/components/tabs/index.js | 46 +++++++++++++++---- .../detail/components/tabs/redux.js | 5 +- .../business/routes/dataset/reducers/item.js | 4 ++ .../business/routes/dataset/sagas/index.js | 23 +++------- .../detail/components/browseRelatedLinks.js | 6 +-- .../detail/components/tabs/index.js | 14 +++++- .../detail/components/tabs/redux.js | 3 +- .../business/routes/objective/sagas/index.js | 16 +------ 14 files changed, 139 insertions(+), 67 deletions(-) create mode 100644 src/app/business/common/components/detail/components/forbiddenDescription.js diff --git a/src/app/business/common/components/detail/components/forbiddenDescription.js b/src/app/business/common/components/detail/components/forbiddenDescription.js new file mode 100644 index 00000000..03b1dac8 --- /dev/null +++ b/src/app/business/common/components/detail/components/forbiddenDescription.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from '@emotion/styled'; +import {RoundedButton} from '@substrafoundation/substra-ui'; +import {spacingNormal} from '../../../../../../../assets/css/variables/spacing'; + +const Span = styled('span')` + margin-right: ${spacingNormal}; +`; + +const ForbiddenDescription = ({model, setTabIndex, permissionsTabIndex}) => ( + <> + + {`You do not have enough permissions to see this ${model}'s description.`} + + setTabIndex(permissionsTabIndex)}> + Learn more + + +); + +ForbiddenDescription.propTypes = { + model: PropTypes.string.isRequired, + setTabIndex: PropTypes.func.isRequired, + permissionsTabIndex: PropTypes.number.isRequired, +}; + +export default ForbiddenDescription; diff --git a/src/app/business/common/reducers/item.js b/src/app/business/common/reducers/item.js index a78d0c83..2e8d71ba 100644 --- a/src/app/business/common/reducers/item.js +++ b/src/app/business/common/reducers/item.js @@ -2,6 +2,7 @@ export const initialState = { init: false, loading: false, descLoading: false, + descForbidden: false, error: null, results: [], tabIndex: 0, @@ -36,6 +37,7 @@ export default (actionTypes) => (state = initialState, {type, payload}) => { return { ...state, descLoading: true, + descForbidden: false, }; case actionTypes.item.description.SUCCESS: return { @@ -46,11 +48,13 @@ export default (actionTypes) => (state = initialState, {type, payload}) => { ...(c.pkhash === payload.id ? [{...c, description: {...c.description, content: payload.desc}}] : [c]), ], []), descLoading: false, + descForbidden: false, }; case actionTypes.item.description.FAILURE: return { ...state, descLoading: false, + descForbidden: payload.status === 403, }; case actionTypes.item.tabIndex.SET: return { diff --git a/src/app/business/common/sagas/index.js b/src/app/business/common/sagas/index.js index e7d9751b..b2fa8846 100644 --- a/src/app/business/common/sagas/index.js +++ b/src/app/business/common/sagas/index.js @@ -6,6 +6,7 @@ import {replace} from 'redux-first-router'; import {omit} from 'lodash'; import cookie from 'cookie-parse'; +import {fetchRaw} from '../../../entities/fetchEntities'; import {fetchRefresh} from '../../user/api'; import {refresh as refreshActions, signOut} from '../../user/actions'; @@ -114,6 +115,24 @@ export const setOrderSaga = function* setOrderSaga({payload}) { replace(newUrl); }; +export const fetchItemDescriptionSaga = (actions) => function* fetchItemDescription({payload: {pkhash, url}}) { + let jwt = getJWTFromCookie(); + if (!jwt) { + jwt = yield tryRefreshToken(actions.description.failure); + } + + if (jwt) { + const {res, error, status} = yield call(fetchRaw, url, jwt); + if (res && status === 200) { + yield put(actions.item.description.success({pkhash, desc: res})); + } + else { + console.error(error, status); + yield put(actions.item.description.failure({pkhash, status})); + } + } +}; + export default { fetchListSaga, fetchPersistentSaga, @@ -121,4 +140,5 @@ export default { setOrderSaga, getJWTFromCookie, tryRefreshToken, + fetchItemDescriptionSaga, }; diff --git a/src/app/business/routes/algo/components/detail/components/tabs/index.js b/src/app/business/routes/algo/components/detail/components/tabs/index.js index 6196cb36..3166284f 100644 --- a/src/app/business/routes/algo/components/detail/components/tabs/index.js +++ b/src/app/business/routes/algo/components/detail/components/tabs/index.js @@ -16,13 +16,14 @@ import Tab from '../../../../../../common/components/detail/components/tabs'; import Description from '../../../../../../common/components/detail/components/description'; import {spacingNormal} from '../../../../../../../../../assets/css/variables/spacing'; import PermissionsTabPanelContent from '../../../../../../common/components/detail/components/permissionsTabPanelContent'; +import ForbiddenDescription from '../../../../../../common/components/detail/components/forbiddenDescription'; const Span = styled('span')` margin-right: ${spacingNormal}; `; const AlgoTabs = ({ -descLoading, item, tabIndex, setTabIndex, downloadFile, +descLoading, descForbidden, item, tabIndex, setTabIndex, downloadFile, }) => ( {descLoading && } - {!descLoading && } + {!descLoading && descForbidden && ( + + )} + {!descLoading && !descForbidden && } The algorithm's source code and Dockerfile are packaged within a zip or tar.gz file. @@ -57,6 +65,7 @@ descLoading, item, tabIndex, setTabIndex, downloadFile, AlgoTabs.propTypes = { item: PropTypes.shape(), descLoading: PropTypes.bool, + descForbidden: PropTypes.bool, tabIndex: PropTypes.number, setTabIndex: PropTypes.func, downloadFile: PropTypes.func.isRequired, @@ -65,6 +74,7 @@ AlgoTabs.propTypes = { AlgoTabs.defaultProps = { item: null, descLoading: false, + descForbidden: false, tabIndex: 0, setTabIndex: noop, }; diff --git a/src/app/business/routes/algo/components/detail/components/tabs/redux.js b/src/app/business/routes/algo/components/detail/components/tabs/redux.js index a74e44c3..2e03ae68 100644 --- a/src/app/business/routes/algo/components/detail/components/tabs/redux.js +++ b/src/app/business/routes/algo/components/detail/components/tabs/redux.js @@ -9,6 +9,7 @@ import actions from '../../../../actions'; const mapStateToProps = (state, {model}) => ({ item: getItem(state, model), descLoading: state[model].item.descLoading, + descForbidden: state[model].item.descForbidden, tabIndex: state[model].item.tabIndex, }); @@ -16,4 +17,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ setTabIndex: actions.item.tabIndex.set, }, dispatch); -export default connect(mapStateToProps, mapDispatchToProps)(onlyUpdateForKeys(['item', 'descLoading', 'tabIndex'])(Tabs)); +export default connect(mapStateToProps, mapDispatchToProps)(onlyUpdateForKeys(['item', 'descLoading', 'descForbidden', 'tabIndex'])(Tabs)); diff --git a/src/app/business/routes/algo/sagas/index.js b/src/app/business/routes/algo/sagas/index.js index 63a2e709..e7177d90 100644 --- a/src/app/business/routes/algo/sagas/index.js +++ b/src/app/business/routes/algo/sagas/index.js @@ -11,9 +11,8 @@ import { fetchListApi, fetchStandardAlgoApi, fetchCompositeAlgoApi, fetchAggregateAlgoApi, } from '../api'; import { - fetchItemSaga, setOrderSaga, tryRefreshToken, getJWTFromCookie, + fetchItemSaga, setOrderSaga, tryRefreshToken, getJWTFromCookie, fetchItemDescriptionSaga, } from '../../../common/sagas'; -import {fetchRaw} from '../../../../entities/fetchEntities'; import {getItem} from '../../../common/selector'; import {signOut} from '../../../user/actions'; @@ -176,21 +175,6 @@ function* setTabIndexSaga({payload}) { yield manageTabs(payload); } -function* fetchItemDescriptionSaga({payload: {pkhash, url}}) { - let jwt = getJWTFromCookie(); - if (!jwt) { - jwt = yield tryRefreshToken(actions.description.failure); - } - - if (jwt) { - const {res, status} = yield call(fetchRaw, url, jwt); - - if (res && status === 200) { - yield put(actions.item.description.success({pkhash, desc: res})); - } - } -} - function* downloadItemSaga({payload: {url}}) { let status; let filename; @@ -232,7 +216,7 @@ const sagas = function* sagas() { takeEvery(actionTypes.item.REQUEST, fetchItem), - takeLatest(actionTypes.item.description.REQUEST, fetchItemDescriptionSaga), + takeLatest(actionTypes.item.description.REQUEST, fetchItemDescriptionSaga(actions)), takeEvery(actionTypes.item.download.REQUEST, downloadItemSaga), diff --git a/src/app/business/routes/dataset/components/detail/components/tabs/index.js b/src/app/business/routes/dataset/components/detail/components/tabs/index.js index 01306155..13e2d7e9 100644 --- a/src/app/business/routes/dataset/components/detail/components/tabs/index.js +++ b/src/app/business/routes/dataset/components/detail/components/tabs/index.js @@ -9,6 +9,7 @@ import { Tabs, TabPanel, TabList, + RoundedButton, } from '@substrafoundation/substra-ui'; import Tab from '../../../../../../common/components/detail/components/tabs'; @@ -18,8 +19,10 @@ import CopyInput from '../../../../../../common/components/detail/components/cop import {fontNormalMonospace, monospaceFamily} from '../../../../../../../../../assets/css/variables/font'; import {ice} from '../../../../../../../../../assets/css/variables/colors'; +import {spacingNormal} from '../../../../../../../../../assets/css/variables/spacing'; import Description from '../../../../../../common/components/detail/components/description'; import PermissionsTabPanelContent from '../../../../../../common/components/detail/components/permissionsTabPanelContent'; +import ForbiddenDescription from '../../../../../../common/components/detail/components/forbiddenDescription'; const Code = styled('code')` font-family: ${monospaceFamily}; @@ -29,8 +32,12 @@ const Code = styled('code')` padding: 1px 3px; `; +const Span = styled('span')` + margin-right: ${spacingNormal}; +`; + const DatasetTabs = ({ -descLoading, item, tabIndex, openerLoading, setTabIndex, addNotification, + loading, descLoading, descForbidden, item, tabIndex, openerLoading, openerForbidden, setTabIndex, addNotification, }) => ( {descLoading && } - {!descLoading && } + {!descLoading && descForbidden && ( + + )} + {!descLoading && !descForbidden && } {openerLoading && } - {!openerLoading && item && item.opener && item.opener.content && ( + {!openerLoading && openerForbidden && ( + <> + You do not have enough permissions to see this dataset's opener. + setTabIndex(4)}> + Learn more + + + )} + {!openerLoading && !openerForbidden && item && item.opener && item.opener.content && ( - {descLoading && } - {!descLoading && item && item.trainDataSampleKeys && !!item.trainDataSampleKeys.length && ( + {loading && } + {!loading && item && item.trainDataSampleKeys && !!item.trainDataSampleKeys.length && ( )} - {!descLoading && item && item.trainDataSampleKeys && !item.trainDataSampleKeys.length && ( + {!loading && item && (!item.trainDataSampleKeys || (item.trainDataSampleKeys && !item.trainDataSampleKeys.length)) && ( <>

No train data samples setup yet. @@ -83,11 +105,11 @@ descLoading, item, tabIndex, openerLoading, setTabIndex, addNotification, )} - {descLoading && } - {!descLoading && item && item.testDataSampleKeys && !!item.testDataSampleKeys.length && ( + {loading && } + {!loading && item && item.testDataSampleKeys && !!item.testDataSampleKeys.length && ( )} - {!descLoading && item && item.testDataSampleKeys && !item.testDataSampleKeys.length && ( + {!loading && item && (!item.testDataSampleKeys || (item.testDataSampleKeys && !item.testDataSampleKeys.length)) && ( <>

No test data samples setup yet. @@ -120,18 +142,24 @@ descLoading, item, tabIndex, openerLoading, setTabIndex, addNotification, DatasetTabs.propTypes = { item: PropTypes.shape(), + loading: PropTypes.bool, descLoading: PropTypes.bool, + descForbidden: PropTypes.bool, tabIndex: PropTypes.number, openerLoading: PropTypes.bool, + openerForbidden: PropTypes.bool, setTabIndex: PropTypes.func, addNotification: PropTypes.func.isRequired, }; DatasetTabs.defaultProps = { item: null, + loading: false, descLoading: false, + descForbidden: false, tabIndex: 0, openerLoading: false, + openerForbidden: false, setTabIndex: noop, }; diff --git a/src/app/business/routes/dataset/components/detail/components/tabs/redux.js b/src/app/business/routes/dataset/components/detail/components/tabs/redux.js index caf5a204..4a109dd7 100644 --- a/src/app/business/routes/dataset/components/detail/components/tabs/redux.js +++ b/src/app/business/routes/dataset/components/detail/components/tabs/redux.js @@ -9,8 +9,11 @@ import actions from '../../../../actions'; const mapStateToProps = (state, {model, addNotification}) => ({ item: getItem(state, model), + loading: state[model].item.loading, descLoading: state[model].item.descLoading, + descForbidden: state[model].item.descForbidden, openerLoading: state[model].item.openerLoading, + openerForbidden: state[model].item.openerForbidden, tabIndex: state[model].item.tabIndex, addNotification, }); @@ -20,4 +23,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ }, dispatch); -export default connect(mapStateToProps, mapDispatchToProps)(onlyUpdateForKeys(['item', 'descLoading', 'openerLoading', 'tabIndex'])(Tabs)); +export default connect(mapStateToProps, mapDispatchToProps)(onlyUpdateForKeys(['item', 'loading', 'descLoading', 'descForbidden', 'openerLoading', 'openerForbidden', 'tabIndex'])(Tabs)); diff --git a/src/app/business/routes/dataset/reducers/item.js b/src/app/business/routes/dataset/reducers/item.js index cf42c5d1..c1168baf 100644 --- a/src/app/business/routes/dataset/reducers/item.js +++ b/src/app/business/routes/dataset/reducers/item.js @@ -3,6 +3,7 @@ import baseReducerBuilder, {initialState as baseInitialState} from '../../../com const initialState = { ...baseInitialState, openerLoading: false, + openerForbidden: false, }; export default (actionTypes) => { @@ -59,6 +60,7 @@ export default (actionTypes) => { return { ...state, openerLoading: true, + openerForbidden: false, }; case actionTypes.item.opener.SUCCESS: return { @@ -77,11 +79,13 @@ export default (actionTypes) => { }] : [c]), ], []), openerLoading: false, + openerForbidden: false, }; case actionTypes.item.opener.FAILURE: return { ...state, openerLoading: false, + openerForbidden: payload.status === 403, }; default: return reducedState; diff --git a/src/app/business/routes/dataset/sagas/index.js b/src/app/business/routes/dataset/sagas/index.js index 2e684a78..ec735246 100644 --- a/src/app/business/routes/dataset/sagas/index.js +++ b/src/app/business/routes/dataset/sagas/index.js @@ -10,6 +10,7 @@ import actions, {actionTypes} from '../actions'; import {fetchItemApi, fetchListApi} from '../api'; import { fetchItemSaga, fetchListSaga, fetchPersistentSaga, getJWTFromCookie, setOrderSaga, tryRefreshToken, + fetchItemDescriptionSaga, } from '../../../common/sagas'; import {fetchRaw} from '../../../../entities/fetchEntities'; import {getItem} from '../../../common/selector'; @@ -90,20 +91,6 @@ function* setTabIndexSaga({payload}) { yield manageTabs(payload); } -function* fetchItemDescriptionSaga({payload: {pkhash, url}}) { - let jwt = getJWTFromCookie(); - if (!jwt) { - jwt = yield tryRefreshToken(actions.description.failure); - } - - if (jwt) { - const {res, status} = yield call(fetchRaw, url, jwt); - if (res && status === 200) { - yield put(actions.item.description.success({pkhash, desc: res})); - } - } -} - function* fetchItemOpenerSaga({payload: {pkhash, url}}) { let jwt = getJWTFromCookie(); if (!jwt) { @@ -111,10 +98,14 @@ function* fetchItemOpenerSaga({payload: {pkhash, url}}) { } if (jwt) { - const {res, status} = yield call(fetchRaw, url, jwt); + const {res, error, status} = yield call(fetchRaw, url, jwt); if (res && status === 200) { yield put(actions.item.opener.success({pkhash, openerContent: res})); } + else { + console.error(error, status); + yield put(actions.item.opener.failure({pkhash, status})); + } } } @@ -160,7 +151,7 @@ const sagas = function* sagas() { takeEvery(actionTypes.item.REQUEST, fetchItem), - takeLatest(actionTypes.item.description.REQUEST, fetchItemDescriptionSaga), + takeLatest(actionTypes.item.description.REQUEST, fetchItemDescriptionSaga(actions)), takeLatest(actionTypes.item.opener.REQUEST, fetchItemOpenerSaga), takeEvery(actionTypes.item.download.REQUEST, downloadItemSaga), diff --git a/src/app/business/routes/objective/components/detail/components/browseRelatedLinks.js b/src/app/business/routes/objective/components/detail/components/browseRelatedLinks.js index fc2ac944..f28e1eab 100644 --- a/src/app/business/routes/objective/components/detail/components/browseRelatedLinks.js +++ b/src/app/business/routes/objective/components/detail/components/browseRelatedLinks.js @@ -1,4 +1,4 @@ -import React, {Fragment} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import {bindActionCreators} from 'redux'; @@ -18,10 +18,10 @@ const BrowseRelatedLinks = ({ const filter = `objective:name:${item ? encodeURIComponent(item.name) : ''}`; return ( - + <> - + ); }; diff --git a/src/app/business/routes/objective/components/detail/components/tabs/index.js b/src/app/business/routes/objective/components/detail/components/tabs/index.js index 5ecdab3b..13bbc463 100644 --- a/src/app/business/routes/objective/components/detail/components/tabs/index.js +++ b/src/app/business/routes/objective/components/detail/components/tabs/index.js @@ -13,6 +13,7 @@ import { import Tab from '../../../../../../common/components/detail/components/tabs'; import PermissionsTabPanelContent from '../../../../../../common/components/detail/components/permissionsTabPanelContent'; +import ForbiddenDescription from '../../../../../../common/components/detail/components/forbiddenDescription'; import Description from '../../../../../../common/components/detail/components/description'; import {spacingNormal} from '../../../../../../../../../assets/css/variables/spacing'; @@ -21,7 +22,7 @@ const Span = styled('span')` `; const ObjectiveTabs = ({ -descLoading, item, downloadFile, tabIndex, setTabIndex, +descLoading, descForbidden, item, downloadFile, tabIndex, setTabIndex, }) => ( {descLoading && } - {!descLoading && } + {!descLoading && descForbidden && ( + + )} + {!descLoading && !descForbidden && } The objective's metrics code and Dockerfile are packaged within a zip or tar.gz file. @@ -56,6 +64,7 @@ descLoading, item, downloadFile, tabIndex, setTabIndex, ObjectiveTabs.propTypes = { item: PropTypes.shape(), descLoading: PropTypes.bool, + descForbidden: PropTypes.bool, tabIndex: PropTypes.number, downloadFile: PropTypes.func, setTabIndex: PropTypes.func, @@ -64,6 +73,7 @@ ObjectiveTabs.propTypes = { ObjectiveTabs.defaultProps = { item: null, descLoading: false, + descForbidden: false, tabIndex: 0, downloadFile: noop, setTabIndex: noop, diff --git a/src/app/business/routes/objective/components/detail/components/tabs/redux.js b/src/app/business/routes/objective/components/detail/components/tabs/redux.js index a38da2a9..587275fe 100644 --- a/src/app/business/routes/objective/components/detail/components/tabs/redux.js +++ b/src/app/business/routes/objective/components/detail/components/tabs/redux.js @@ -10,6 +10,7 @@ import actions from '../../../../actions'; const mapStateToProps = (state, {model}) => ({ item: getItem(state, model), descLoading: state[model].item.descLoading, + descForbidden: state[model].item.descLoading, tabIndex: state[model].item.tabIndex, }); @@ -17,4 +18,4 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ setTabIndex: actions.item.tabIndex.set, }, dispatch); -export default connect(mapStateToProps, mapDispatchToProps)(onlyUpdateForKeys(['item', 'descLoading', 'tabIndex'])(Tabs)); +export default connect(mapStateToProps, mapDispatchToProps)(onlyUpdateForKeys(['item', 'descLoading', 'descForbidden', 'tabIndex'])(Tabs)); diff --git a/src/app/business/routes/objective/sagas/index.js b/src/app/business/routes/objective/sagas/index.js index 7ab450ad..b2d9abc3 100644 --- a/src/app/business/routes/objective/sagas/index.js +++ b/src/app/business/routes/objective/sagas/index.js @@ -10,8 +10,8 @@ import actions, {actionTypes} from '../actions'; import {fetchListApi, fetchItemApi} from '../api'; import { fetchListSaga, fetchPersistentSaga, fetchItemSaga, setOrderSaga, getJWTFromCookie, tryRefreshToken, + fetchItemDescriptionSaga, } from '../../../common/sagas'; -import {fetchRaw} from '../../../../entities/fetchEntities'; import {getItem} from '../../../common/selector'; function* fetchList(request) { @@ -84,18 +84,6 @@ function* setTabIndexSaga({payload}) { yield manageTabs(payload); } -function* fetchItemDescriptionSaga({payload: {pkhash, url}}) { - let jwt = getJWTFromCookie(); - if (!jwt) { - jwt = yield tryRefreshToken(actions.description.failure); - } - if (jwt) { - const {res, status} = yield call(fetchRaw, url, jwt); - if (res && status === 200) { - yield put(actions.item.description.success({pkhash, desc: res})); - } - } -} function* downloadItemSaga({payload: {url}}) { let status; @@ -136,7 +124,7 @@ const sagas = function* sagas() { takeLatest(actionTypes.persistent.REQUEST, fetchPersistent), takeEvery(actionTypes.item.REQUEST, fetchItem), - takeLatest(actionTypes.item.description.REQUEST, fetchItemDescriptionSaga), + takeLatest(actionTypes.item.description.REQUEST, fetchItemDescriptionSaga(actions)), takeEvery(actionTypes.item.download.REQUEST, downloadItemSaga), From 85780d8d5a8506d1378e7ccdbb2ecaeb0b2113e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Morel?= Date: Mon, 27 Jan 2020 10:44:41 +0100 Subject: [PATCH 2/2] refactor forbidden components --- .../detail/components/forbiddenDescription.js | 28 ------------- .../detail/components/forbiddenResource.js | 39 +++++++++++++++++++ .../detail/components/tabs/index.js | 5 ++- .../detail/components/tabs/index.js | 23 +++++------ .../detail/components/tabs/index.js | 5 ++- 5 files changed, 54 insertions(+), 46 deletions(-) delete mode 100644 src/app/business/common/components/detail/components/forbiddenDescription.js create mode 100644 src/app/business/common/components/detail/components/forbiddenResource.js diff --git a/src/app/business/common/components/detail/components/forbiddenDescription.js b/src/app/business/common/components/detail/components/forbiddenDescription.js deleted file mode 100644 index 03b1dac8..00000000 --- a/src/app/business/common/components/detail/components/forbiddenDescription.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import styled from '@emotion/styled'; -import {RoundedButton} from '@substrafoundation/substra-ui'; -import {spacingNormal} from '../../../../../../../assets/css/variables/spacing'; - -const Span = styled('span')` - margin-right: ${spacingNormal}; -`; - -const ForbiddenDescription = ({model, setTabIndex, permissionsTabIndex}) => ( - <> - - {`You do not have enough permissions to see this ${model}'s description.`} - - setTabIndex(permissionsTabIndex)}> - Learn more - - -); - -ForbiddenDescription.propTypes = { - model: PropTypes.string.isRequired, - setTabIndex: PropTypes.func.isRequired, - permissionsTabIndex: PropTypes.number.isRequired, -}; - -export default ForbiddenDescription; diff --git a/src/app/business/common/components/detail/components/forbiddenResource.js b/src/app/business/common/components/detail/components/forbiddenResource.js new file mode 100644 index 00000000..d63e9b6b --- /dev/null +++ b/src/app/business/common/components/detail/components/forbiddenResource.js @@ -0,0 +1,39 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import styled from '@emotion/styled'; +import {RoundedButton} from '@substrafoundation/substra-ui'; +import {spacingNormal} from '../../../../../../../assets/css/variables/spacing'; + +const Span = styled('span')` + margin-right: ${spacingNormal}; +`; + +class ForbiddenResource extends Component { + handleClick = () => { + const {setTabIndex, permissionsTabIndex} = this.props; + setTabIndex(permissionsTabIndex); + } + + render() { + const {model, resource} = this.props; + return ( + <> + + {`You do not have enough permissions to see this ${model}'s ${resource}.`} + + + Learn more + + + ); + } +} + +ForbiddenResource.propTypes = { + resource: PropTypes.string.isRequired, + model: PropTypes.string.isRequired, + setTabIndex: PropTypes.func.isRequired, + permissionsTabIndex: PropTypes.number.isRequired, +}; + +export default ForbiddenResource; diff --git a/src/app/business/routes/algo/components/detail/components/tabs/index.js b/src/app/business/routes/algo/components/detail/components/tabs/index.js index 3166284f..b951d773 100644 --- a/src/app/business/routes/algo/components/detail/components/tabs/index.js +++ b/src/app/business/routes/algo/components/detail/components/tabs/index.js @@ -16,7 +16,7 @@ import Tab from '../../../../../../common/components/detail/components/tabs'; import Description from '../../../../../../common/components/detail/components/description'; import {spacingNormal} from '../../../../../../../../../assets/css/variables/spacing'; import PermissionsTabPanelContent from '../../../../../../common/components/detail/components/permissionsTabPanelContent'; -import ForbiddenDescription from '../../../../../../common/components/detail/components/forbiddenDescription'; +import ForbiddenResource from '../../../../../../common/components/detail/components/forbiddenResource'; const Span = styled('span')` margin-right: ${spacingNormal}; @@ -37,7 +37,8 @@ descLoading, descForbidden, item, tabIndex, setTabIndex, downloadFile, {descLoading && } {!descLoading && descForbidden && ( - ( @@ -53,7 +47,8 @@ const DatasetTabs = ({ {descLoading && } {!descLoading && descForbidden && ( - {openerLoading && } {!openerLoading && openerForbidden && ( - <> - You do not have enough permissions to see this dataset's opener. - setTabIndex(4)}> - Learn more - - + )} {!openerLoading && !openerForbidden && item && item.opener && item.opener.content && ( {descLoading && } {!descLoading && descForbidden && ( -