-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* rebase * study viewer * dont crash homepage * access * fix logic * requestor redir * fix title * fetch doc data * fix trigger * object file download * fix button logic * requset error handle * fix: useGuppyForExplorer * fix lgtm * doc * fix doc * remove dar dau * datasets * use accessibleValidationValue * read-storage only * no default for title * better gql query generation * openMode and defaultOpenTitle * sv config as array * doc * put projectIsOpenData back * fix: reset to trigger re-fetch * error msg * more fix about array of configs * hide download button if no file * fix crash for table items * link in table * link in table * fix: don't flood guppy if request fails * fix: filedmapping for ssv * fix file optional * hide help msg when needed * update doc * clean up * fix login redirect flow * disable request access btn when needed * check access before sends out * batch request * clean
- Loading branch information
Showing
14 changed files
with
1,084 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Study Viewer | ||
|
||
Example configuration: | ||
|
||
``` | ||
[ | ||
{ | ||
"dataType": "dataset", | ||
"title": "Datasets", // page title | ||
"titleField": "name", // row title | ||
"listItemConfig": { // required | ||
// displayed outside of table: | ||
"blockFields": ["short_description"], | ||
// displayed in table: | ||
"tableFields": ["condition", ...], | ||
}, | ||
"singleItemConfig": { //optional, if omitted, "listItemConfig" block will be used for both pages | ||
// displayed outside of table: | ||
"blockFields": ["long_description"], | ||
// displayed in table: | ||
"tableFields": ["condition", ...], | ||
}, | ||
"fieldMapping": [...], | ||
"rowAccessor": "project_id", // rows unique ID | ||
"downloadField": "object_id", // GUID | ||
"fileDataType": "clinicalTrialFile", // ES index of the clinical trial object files, optional | ||
"docDataType": "openAccessFile", // ES index of the open access documents, optional | ||
"openMode": "open-first", // optional, configure how the study viewer list do with each collapsible card on initial loading, see details in notes | ||
"openFirstRowAccessor": "", // optional, only works if `openMode` is `open-first` | ||
}, | ||
{ | ||
....another study viewer config | ||
} | ||
] | ||
``` | ||
|
||
## Notes | ||
|
||
1. The configuration above is subject to change. After `Tube` supports generating nested ES document then we can remove the `fileDataType` and `docDataType` fields. | ||
2. Required fields for `fileData` and `docData` ES indices are: `file_name`, `file_size`, `data_format` and `data_type`. For `fileData`, additional required field is `object_id`; and for `docData`, `doc_url` is also required. | ||
3. The field `rowAccessor` should be a field that exists in all 3 ES indices. The study viewer will use that field to cross query with different ES indices. | ||
4. About `openMode` and `openFirstRowAccessor`, the list view of study browser supports 3 display modes on initial loading: | ||
- `open-all`: opens all collapsible cards by default. And this is the default option if `openMode` is omitted in the config | ||
- `close-all`: closes all collapsible cards by default | ||
- `open-first`: opens the first collapsible card in the list and keeps all other cards closing | ||
- When in `open-first` mode, user can specify a value using `openFirstRowAccessor`. The study viewer will try to find a study with that title in the list and bring it to the top of the list in order to open it. | ||
5. The access request logic depends on `Requestor`, for more info, see [Requestor](https://github.com/uc-cdis/requestor/). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import _ from 'lodash'; | ||
import { Space, Typography, Spin, Result } from 'antd'; | ||
import { FileOutlined, FilePdfOutlined } from '@ant-design/icons'; | ||
import BackLink from '../components/BackLink'; | ||
import { humanFileSize } from '../utils.js'; | ||
import { ReduxStudyDetails, fetchDataset, fetchFiles, resetMultipleStudyData, fetchStudyViewerConfig } from './reduxer'; | ||
import getReduxStore from '../reduxStore'; | ||
import './StudyViewer.css'; | ||
|
||
const { Title } = Typography; | ||
|
||
class SingleStudyViewer extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
dataType: undefined, | ||
rowAccessor: undefined, | ||
}; | ||
} | ||
|
||
static getDerivedStateFromProps(nextProps, prevState) { | ||
const newState = {}; | ||
if (nextProps.match.params.dataType | ||
&& nextProps.match.params.dataType !== prevState.dataType) { | ||
newState.dataType = nextProps.match.params.dataType; | ||
} | ||
if (nextProps.match.params.rowAccessor | ||
&& nextProps.match.params.rowAccessor !== prevState.rowAccessor) { | ||
newState.rowAccessor = nextProps.match.params.rowAccessor; | ||
} | ||
return Object.keys(newState).length ? newState : null; | ||
} | ||
|
||
render() { | ||
if (this.props.noConfigError) { | ||
this.props.history.push('/not-found'); | ||
} | ||
if (!this.props.dataset) { | ||
if (this.state.dataType && this.state.rowAccessor) { | ||
getReduxStore().then( | ||
store => | ||
Promise.allSettled( | ||
[ | ||
store.dispatch(fetchDataset(decodeURIComponent(this.state.dataType), | ||
decodeURIComponent(this.state.rowAccessor))), | ||
store.dispatch(fetchFiles(decodeURIComponent(this.state.dataType), 'object', decodeURIComponent(this.state.rowAccessor))), | ||
store.dispatch(fetchFiles(decodeURIComponent(this.state.dataType), 'open-access', decodeURIComponent(this.state.rowAccessor))), | ||
store.dispatch(resetMultipleStudyData()), | ||
], | ||
)); | ||
} | ||
return ( | ||
<div className='study-viewer'> | ||
<div className='study-viewer_loading'> | ||
<Spin size='large' tip='Loading data...' /> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
const studyViewerConfig = fetchStudyViewerConfig(this.state.dataType); | ||
const dataset = this.props.dataset; | ||
const backURL = this.props.location.pathname.substring(0, this.props.location.pathname.lastIndexOf('/')); | ||
if (_.isEmpty(dataset)) { | ||
return ( | ||
<div className='study-viewer'> | ||
<BackLink url={backURL} label='Back' /> | ||
<Result | ||
title='No data available' | ||
/> | ||
</div> | ||
); | ||
} | ||
return ( | ||
<div className='study-viewer'> | ||
<BackLink url={backURL} label='Back' /> | ||
<Space className='study-viewer__space' direction='vertical'> | ||
<div className='study-viewer__title'> | ||
<Title level={4}>{dataset.title}</Title> | ||
</div> | ||
<div className='study-viewer__details'> | ||
<ReduxStudyDetails | ||
data={dataset} | ||
fileData={this.props.fileData} | ||
studyViewerConfig={studyViewerConfig} | ||
/> | ||
<div className='study-viewer__details-sidebar'> | ||
<Space direction='vertical' style={{ width: '100%' }}> | ||
{(this.props.docData.length > 0) ? | ||
<div className='study-viewer__details-sidebar-box'> | ||
<Space className='study-viewer__details-sidebar-space' direction='vertical'> | ||
<div className='h3-typo'>Study Documents</div> | ||
{this.props.docData.map((doc) => { | ||
const iconComponent = (doc.data_format === 'PDF') ? <FilePdfOutlined /> : <FileOutlined />; | ||
const linkText = `${doc.file_name} (${doc.data_format} - ${humanFileSize(doc.file_size)})`; | ||
const linkComponent = <a href={doc.doc_url}>{linkText}</a>; | ||
return (<div key={doc.file_name}> | ||
{iconComponent} | ||
{linkComponent} | ||
</div>); | ||
})} | ||
</Space> | ||
</div> | ||
: null | ||
} | ||
</Space> | ||
</div> | ||
</div> | ||
</Space> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
SingleStudyViewer.propTypes = { | ||
dataset: PropTypes.object, | ||
docData: PropTypes.array, | ||
fileData: PropTypes.array, | ||
noConfigError: PropTypes.string, | ||
history: PropTypes.object.isRequired, | ||
location: PropTypes.object.isRequired, | ||
}; | ||
|
||
SingleStudyViewer.defaultProps = { | ||
dataset: undefined, | ||
docData: [], | ||
fileData: [], | ||
noConfigError: undefined, | ||
}; | ||
|
||
export default SingleStudyViewer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Card, Collapse } from 'antd'; | ||
import { PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons'; | ||
import { ReduxStudyDetails } from './reduxer'; | ||
import './StudyViewer.css'; | ||
|
||
const { Panel } = Collapse; | ||
|
||
class StudyCard extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
panelExpanded: props.initialPanelExpandStatus, | ||
}; | ||
} | ||
|
||
onCollapseChange = () => { | ||
this.setState(prevState => ({ | ||
panelExpanded: !prevState.panelExpanded, | ||
})); | ||
}; | ||
|
||
render() { | ||
return ( | ||
<Card | ||
className='study-viewer__card' | ||
title={this.props.data.title} | ||
> | ||
<Collapse | ||
defaultActiveKey={(this.state.panelExpanded) ? ['1'] : []} | ||
expandIcon={({ isActive }) => | ||
((isActive) ? <MinusCircleOutlined /> : <PlusCircleOutlined />)} | ||
onChange={this.onCollapseChange} | ||
ghost | ||
> | ||
<Panel | ||
header={(this.state.panelExpanded) ? 'Hide details' : 'Show details'} | ||
key='1' | ||
> | ||
<ReduxStudyDetails | ||
data={this.props.data} | ||
fileData={this.props.fileData} | ||
studyViewerConfig={this.props.studyViewerConfig} | ||
displayLearnMoreBtn | ||
/> | ||
</Panel> | ||
</Collapse> | ||
</Card> | ||
); | ||
} | ||
} | ||
|
||
StudyCard.propTypes = { | ||
data: PropTypes.shape({ | ||
accessRequested: PropTypes.bool.isRequired, | ||
title: PropTypes.string.isRequired, | ||
rowAccessorValue: PropTypes.string.isRequired, | ||
blockData: PropTypes.object, | ||
tableData: PropTypes.object, | ||
accessibleValidationValue: PropTypes.string, | ||
fileData: PropTypes.array, | ||
docData: PropTypes.array, | ||
}).isRequired, | ||
fileData: PropTypes.array, | ||
studyViewerConfig: PropTypes.object, | ||
initialPanelExpandStatus: PropTypes.bool.isRequired, | ||
}; | ||
|
||
StudyCard.defaultProps = { | ||
fileData: [], | ||
studyViewerConfig: {}, | ||
}; | ||
|
||
export default StudyCard; |
Oops, something went wrong.