-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Decoupling tabs in the experiment page and display them after validating #375
base: develop
Are you sure you want to change the base?
Changes from 16 commits
f0677d3
a21e6ee
7a48932
a071970
aa55ced
585972d
25ef163
13e7ee2
532389c
1f9aa46
2c05e1a
ca946ac
ea37496
fcaab7c
05d2fb2
35bceb9
99088cf
b1cf7e5
5eaf719
dcf06f8
e84663d
08588ac
bef7792
a60205f
53c0ce8
7189295
d4179e1
4d3abc1
bf01724
21731ac
865c176
fea148e
707dd79
0473eeb
a690dfc
4ef6772
10a0f14
76ed29e
f9a1240
2887bfb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import { BrowserRouter, Route, Redirect, Switch, NavLink, withRouter } from 'react-router-dom' | ||
import {BrowserRouter, Route, Redirect, Switch, NavLink, withRouter} from 'react-router-dom' | ||
|
||
import URI from 'urijs' | ||
|
||
|
@@ -10,130 +10,169 @@ import SupplementaryInformationRoute from './SupplementaryInformationRoute' | |
import DownloadsRoute from './DownloadsRoute' | ||
|
||
const RoutePropTypes = { | ||
match: PropTypes.object.isRequired, | ||
location: PropTypes.object.isRequired, | ||
history: PropTypes.object.isRequired | ||
match: PropTypes.object.isRequired, | ||
location: PropTypes.object.isRequired, | ||
history: PropTypes.object.isRequired | ||
} | ||
|
||
const TabCommonPropTypes = { | ||
atlasUrl: PropTypes.string.isRequired, | ||
experimentAccession: PropTypes.string.isRequired, | ||
species: PropTypes.string.isRequired, | ||
accessKey: PropTypes.string, | ||
resourcesUrl: PropTypes.string | ||
atlasUrl: PropTypes.string.isRequired, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You already externalised the |
||
experimentAccession: PropTypes.string.isRequired, | ||
species: PropTypes.string.isRequired, | ||
accessKey: PropTypes.string, | ||
resourcesUrl: PropTypes.string | ||
} | ||
|
||
// What component each tab type should render, coupled to ExperimentController.java | ||
const tabTypeComponent = { | ||
'results' : TSnePlotViewRoute, | ||
'experiment-design' : ExperimentDesignRoute, | ||
'supplementary-information' : SupplementaryInformationRoute, | ||
'downloads' : DownloadsRoute | ||
} | ||
let tabTypeComponent = [] | ||
|
||
const TopRibbon = ({tabs, routeProps}) => | ||
<ul className={`tabs`}> | ||
{ | ||
tabs.map((tab) => | ||
<li title={tab.name} key={tab.type} className={`tabs-title`}> | ||
<NavLink to={{pathname:`/${tab.type}`, search: routeProps.location.search, hash: routeProps.location.hash}} | ||
activeClassName={`active`}> | ||
{tab.name} | ||
</NavLink> | ||
</li> | ||
)} | ||
</ul> | ||
function isThisTabType(tab, tabType) { | ||
return tab.type === tabType; | ||
} | ||
|
||
const isObjectEmpty = (objectName) => { | ||
return ( | ||
objectName && | ||
Object.keys(objectName).length === 0 && | ||
objectName.constructor === Object | ||
); | ||
}; | ||
|
||
const tabTypes = [ | ||
{ type: 'results', key: 'ks', component: TSnePlotViewRoute, optionsKey: 'plotTypesAndOptions' }, | ||
{ type: 'experiment-design', key: 'table.data', component: ExperimentDesignRoute }, | ||
{ type: 'supplementary-information', key: 'sections', component: SupplementaryInformationRoute }, | ||
{ type: 'resources', key: 'data', component: DownloadsRoute } | ||
]; | ||
|
||
const getNestedProperty = (obj, path) => path.split('.').reduce((acc, key) => acc?.[key], obj); | ||
|
||
const isNonEmptyArray = (value) => !isObjectEmpty(value) && Array.isArray(value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we move this code: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of the validation checks before displaying a relevant tab. We are getting empty for some experiments. |
||
|
||
const enableExperimentPageTab = (tab) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole function is a bit too complex for me and hard to understand what is happening inside this function. It is really hard to follow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, we will. Thanks. We will do Monday. I will try to improve variable naming. |
||
for (let { type, key, component, optionsKey } of tabTypes) { | ||
if (isThisTabType(tab, type)) { | ||
const propValue = getNestedProperty(tab.props, key); | ||
const optionsValue = optionsKey ? getNestedProperty(tab.props, optionsKey) : true; | ||
|
||
if (isNonEmptyArray(propValue) && (!optionsKey || !isObjectEmpty(optionsValue))) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens in |
||
tabTypeComponent.push({ [type]: component }); | ||
return tab.name; | ||
} | ||
} | ||
} | ||
return null; | ||
}; | ||
|
||
const TopRibbon = ({tabs, routeProps}) => { | ||
tabTypeComponent = []; | ||
return <ul className={`tabs`}> | ||
{ | ||
tabs.map((tab) => ( | ||
<li title={tab.name} key={tab.type} className={`tabs-title`}> | ||
<NavLink to={{ | ||
pathname: `/${tab.type}`, | ||
search: routeProps.location.search, | ||
hash: routeProps.location.hash | ||
}} | ||
activeClassName={`active`}> | ||
{ enableExperimentPageTab(tab) } | ||
</NavLink> | ||
</li>)) | ||
} | ||
</ul> | ||
} | ||
TopRibbon.propTypes = { | ||
tabs: PropTypes.arrayOf(PropTypes.shape({ | ||
type: PropTypes.string.isRequired, | ||
name: PropTypes.string.isRequired, | ||
props: PropTypes.object | ||
})).isRequired, | ||
routeProps: PropTypes.shape(RoutePropTypes) | ||
tabs: PropTypes.arrayOf(PropTypes.shape({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can move all the propTypes from this class to the |
||
type: PropTypes.string.isRequired, | ||
name: PropTypes.string.isRequired, | ||
props: PropTypes.object | ||
})).isRequired, | ||
routeProps: PropTypes.shape(RoutePropTypes) | ||
} | ||
|
||
|
||
const TabContent = ({type, tabProps, commonProps, routeProps}) => { | ||
// Pass in the search from location | ||
const Tab = tabTypeComponent[type] | ||
// Pass in the search from location | ||
const Tab = tabTypeComponent[type] | ||
|
||
return ( | ||
Tab ? <Tab {...tabProps} {...commonProps} {...routeProps}/> : null | ||
) | ||
return ( | ||
Tab ? <Tab {...tabProps} {...commonProps} {...routeProps}/> : null | ||
) | ||
} | ||
|
||
TabContent.propTypes = { | ||
type: PropTypes.string.isRequired, | ||
tabProps: PropTypes.object, | ||
commonProps: PropTypes.shape(TabCommonPropTypes), | ||
routeProps: PropTypes.shape(RoutePropTypes) | ||
type: PropTypes.string.isRequired, | ||
tabProps: PropTypes.object, | ||
commonProps: PropTypes.shape(TabCommonPropTypes), | ||
routeProps: PropTypes.shape(RoutePropTypes) | ||
} | ||
|
||
const RedirectWithSearchAndHash = (props) => | ||
<Redirect to={{ pathname: props.pathname, search: props.location.search, hash: props.location.hash}} /> | ||
<Redirect to={{pathname: props.pathname, search: props.location.search, hash: props.location.hash}}/> | ||
|
||
RedirectWithSearchAndHash.propTypes = { | ||
pathname: PropTypes.string.isRequired, | ||
location: PropTypes.shape({ | ||
search: PropTypes.string.isRequired, | ||
hash: PropTypes.string.isRequired | ||
}).isRequired | ||
pathname: PropTypes.string.isRequired, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with you, I will try to do that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This one is fixed. Please have a look. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know what you meant by |
||
location: PropTypes.shape({ | ||
search: PropTypes.string.isRequired, | ||
hash: PropTypes.string.isRequired | ||
}).isRequired | ||
} | ||
|
||
const RedirectWithLocation = withRouter(RedirectWithSearchAndHash) | ||
|
||
const ExperimentPageRouter = ({atlasUrl, resourcesUrl, experimentAccession, species, accessKey, tabs}) => { | ||
const tabCommonProps = { | ||
atlasUrl, | ||
resourcesUrl, | ||
experimentAccession, | ||
species, | ||
accessKey | ||
} | ||
|
||
return ( | ||
<BrowserRouter | ||
basename={URI(`experiments/${experimentAccession}`, URI(atlasUrl).path()).toString()}> | ||
<div> | ||
<Route | ||
path={`/`} | ||
render={ | ||
(routeProps) => | ||
<TopRibbon | ||
tabs={tabs} | ||
routeProps={routeProps} /> | ||
} /> | ||
<Switch> | ||
{ | ||
tabs.map((tab) => | ||
<Route | ||
key={tab.type} | ||
path={`/${tab.type}`} | ||
render={ | ||
(routeProps) => | ||
<TabContent | ||
type={tab.type} | ||
tabProps={tab.props} | ||
commonProps={tabCommonProps} | ||
routeProps={routeProps} /> | ||
} /> | ||
) | ||
} | ||
<RedirectWithLocation pathname={`/${tabs[0].type}`} /> | ||
</Switch> | ||
</div> | ||
</BrowserRouter> | ||
) | ||
|
||
const tabCommonProps = { | ||
atlasUrl, | ||
resourcesUrl, | ||
experimentAccession, | ||
species, | ||
accessKey | ||
} | ||
|
||
return ( | ||
<BrowserRouter | ||
basename={URI(`experiments/${experimentAccession}`, URI(atlasUrl).path()).toString()}> | ||
<div> | ||
<Route | ||
path={`/`} | ||
render={ | ||
(routeProps) => | ||
<TopRibbon | ||
tabs={tabs} | ||
routeProps={routeProps}/> | ||
}/> | ||
<Switch> | ||
{ | ||
tabs.map((tab) => | ||
<Route | ||
key={tab.type} | ||
path={`/${tab.type}`} | ||
render={ | ||
(routeProps) => | ||
<TabContent | ||
type={tab.type} | ||
tabProps={tab.props} | ||
commonProps={tabCommonProps} | ||
routeProps={routeProps}/> | ||
}/> | ||
) | ||
} | ||
<RedirectWithLocation pathname={`/${tabs[0].type}`}/> | ||
</Switch> | ||
</div> | ||
</BrowserRouter> | ||
) | ||
} | ||
|
||
ExperimentPageRouter.propTypes = { | ||
...TabCommonPropTypes, | ||
tabs: PropTypes.arrayOf(PropTypes.shape({ | ||
type: PropTypes.string.isRequired, | ||
name: PropTypes.string.isRequired, | ||
props: PropTypes.object.isRequired | ||
})).isRequired | ||
...TabCommonPropTypes, | ||
tabs: PropTypes.arrayOf(PropTypes.shape({ | ||
type: PropTypes.string.isRequired, | ||
name: PropTypes.string.isRequired, | ||
props: PropTypes.object.isRequired | ||
})).isRequired | ||
} | ||
|
||
export default ExperimentPageRouter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clean the code and make this file smaller, I would externalise all the
proptypes
to another file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you group together the code parts that belongs to
PropTypes
?Thanks