Skip to content

Commit

Permalink
Merge pull request #85 from superphy/rc6.3.0
Browse files Browse the repository at this point in the history
Merge: add search functionality
  • Loading branch information
kevinkle authored Jun 21, 2018
2 parents 0586695 + 181a480 commit 33525bc
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 23 deletions.
3 changes: 3 additions & 0 deletions src/Routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Subtyping from './containers/Subtyping'
import Metadata from './containers/Metadata'
import Database from './containers/Database'
import Panseq from './containers/Panseq'
import Search from './containers/Search'
// others
import Results from './containers/Results'
import VisibleResult from './containers/VisibleResult'
Expand All @@ -20,6 +21,7 @@ export const METADATA = dirpath + '/metadata'
export const DATABASE = dirpath + '/database'
export const PANSEQ = dirpath + '/panseq'
export const RESULTS = dirpath + '/results'
export const SEARCH = dirpath + '/search'
export const VISIBLE_RESULT = dirpath + '/results/:hash'

const renderMergedProps = (component, ...rest) => {
Expand All @@ -45,6 +47,7 @@ const Routes = (token) => (
<PropsRoute path={METADATA} component={Metadata} token={token.token} />
<PropsRoute path={DATABASE} component={Database} token={token.token} />
<PropsRoute path={PANSEQ} component={Panseq} token={token.token} />
<PropsRoute path={SEARCH} component={Search} token={token.token} />
<PropsRoute exact path={RESULTS} component={Results} token={token.token}/>
<PropsRoute path={VISIBLE_RESULT} component={VisibleResult} token={token.token} />
</Switch>
Expand Down
48 changes: 34 additions & 14 deletions src/components/ResultSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,34 @@ import { API_ROOT } from '../middleware/api'
// Table
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';

class ResultDatabase extends Component {
const sidebar = 200;

class ResultSearch extends Component {
constructor(props) {
super(props);
this.state = { width: 0, height: 0 };
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
}
componentWillMount() {
this.updateWindowDimensions();
window.addEventListener('resize', this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWindowDimensions);
}
updateWindowDimensions() {
this.setState({ width: window.innerWidth, height: window.innerHeight });
}
calcWidth = ( dec ) => {
let workableWidth = this.state.width - sidebar;
return String(workableWidth*dec)
}
render() {
const { results } = this.props
const { results } = this.props;
const options = {
searchPosition: 'left'
searchPosition: 'left',
defaultSortName: 'analysis',
defaultSortOrder: 'asc',
};
if (results.pending){
return <div>Waiting for server response...<CircularProgress key="progress" id='contentLoadingProgress' /></div>
Expand All @@ -20,21 +43,18 @@ class ResultDatabase extends Component {
} else if (results.fulfilled){
console.log(results)
return (
<div>
<p># of Genome Files: {results.value.length}</p>
<BootstrapTable data={results.value} exportCSV search options={options}>
<TableHeaderColumn dataField='spfyId' isKey dataSort filter={ { type: 'NumberFilter', placeholder: 'Please enter a value' } } width='180' csvHeader='Spfy ID'>Spfy ID</TableHeaderColumn>
<TableHeaderColumn dataField='Genome' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width='360' csvHeader='Filename'>Filename</TableHeaderColumn>
<TableHeaderColumn dataField='submitted' dataSort filter={ { type: 'DateFilter', placeholder: 'Please enter a value' } } csvHeader='Submitted'>Submitted</TableHeaderColumn>
<TableHeaderColumn dataField='otype' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } csvHeader='O-Type'>O-Type</TableHeaderColumn>
<TableHeaderColumn dataField='htype' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } csvHeader='H-Type'>H-Type</TableHeaderColumn>
</BootstrapTable>
</div>
<BootstrapTable data={results.value} exportCSV search options={options}>
<TableHeaderColumn isKey dataField='filename' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.20)} csvHeader='Filename'>Filename</TableHeaderColumn>
<TableHeaderColumn dataField='date' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.05)} csvHeader='Date Submitted'>Date Submitted</TableHeaderColumn>
<TableHeaderColumn dataField='contigid' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.10)} csvHeader='Contig ID'>Contig ID</TableHeaderColumn>
<TableHeaderColumn dataField='analysis' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.10)} csvHeader='Analysis'>Analysis</TableHeaderColumn>
<TableHeaderColumn dataField='hitname' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.10)} csvHeader='Hit'>Hit</TableHeaderColumn>
</BootstrapTable>
);
}
}
}

export default connect(props => ({
results: {url: API_ROOT + `results/${props.jobId}`}
}))(ResultDatabase)
}))(ResultSearch)
3 changes: 2 additions & 1 deletion src/components/ResultSubtyping.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ResultSubtyping extends Component {
}
calcWidth = ( dec ) => {
let workableWidth = this.state.width - sidebar;
return workableWidth*dec
return String(workableWidth*dec)
}
render() {
const { results } = this.props;
Expand All @@ -47,6 +47,7 @@ class ResultSubtyping extends Component {
<TableHeaderColumn dataField='analysis' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.10)} csvHeader='Analysis'>Analysis</TableHeaderColumn>
<TableHeaderColumn dataField='hitname' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.10)} csvHeader='Hit'>Hit</TableHeaderColumn>
<TableHeaderColumn dataField='longname' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.05)} csvHeader='Long Hitname'>Long Hitname</TableHeaderColumn>
<TableHeaderColumn dataField='aro' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.05)} csvHeader='ARO Link'>ARO Link</TableHeaderColumn>
<TableHeaderColumn dataField='hitorientation' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.05)} csvHeader='Orientation'>Orientation</TableHeaderColumn>
<TableHeaderColumn dataField='hitstart' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.05)} csvHeader='Start'>Start</TableHeaderColumn>
<TableHeaderColumn dataField='hitstop' dataSort filter={ { type: 'TextFilter', placeholder: 'Please enter a value' } } width={this.calcWidth(0.05)} csvHeader='Stop'>Stop</TableHeaderColumn>
Expand Down
5 changes: 5 additions & 0 deletions src/components/ResultsTemplates.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ResultBulk from './ResultBulk'
import ResultMetadata from './ResultMetadata'
import ResultPanseq from './ResultsPanseq'
import { RedirectToken } from '../components/RedirectToken'
import ResultSearch from './ResultSearch';

const ResultsTemplates = ({ job, ...props }) => {
switch (job.analysis) {
Expand Down Expand Up @@ -44,6 +45,10 @@ const ResultsTemplates = ({ job, ...props }) => {
return <RedirectToken token={props.token}>
<ResultDatabase jobId={job.hash} />
</RedirectToken>
case "search":
return <RedirectToken token={props.token}>
<ResultSearch jobId={job.hash} />
</RedirectToken>
default:
return <div>ERROR: no matching analysis view found.</div>
}
Expand Down
5 changes: 3 additions & 2 deletions src/containers/Fishers.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const fdescrip = (step, nextStep, prevStep) => (
<Media>
<img src={ontology} alt="ontology" />
<MediaOverlay>
<CardTitle title="Compare Graph Nodes" subtitle="via Fisher's Exact Test">
<CardTitle title="Statistical Comparison" subtitle="using Fisher's Exact Test">
</CardTitle>
</MediaOverlay>
</Media>
Expand Down Expand Up @@ -79,7 +79,8 @@ const fdescrip = (step, nextStep, prevStep) => (
<h5>Rationale:</h5>
Because analysis modules in biology tend to have different result
formats, graph storage allows comparisons between the results of
different types without explicit joins. As in the above example, our
different types without explicit joins. We treat all nodes in the graph
database as available for anlaysis. As in the above example, our
O-type analysis was developed internally whereas the RGI tool was
developed by the CARD initiative <a href='https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5210516/'>doi: 10.1093/nar/gkw1004</a>.
</p>
Expand Down
145 changes: 145 additions & 0 deletions src/containers/Search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { Component } from 'react';
// react-md
import {
TextField,
Button,
Collapse,
} from 'react-md';
// redux
import { connect } from 'react-redux'
import { addJob } from '../actions'
// axios
import axios from 'axios'
import { API_ROOT } from '../middleware/api'
// router
import Loading from '../components/Loading'
import { RedirectToken } from '../components/RedirectToken'

class Search extends Component {
constructor(props) {
super(props);
this.state = {
st: 'gi|1370526529|gb|CP027599.1|',
collapsed: false,
submitted: false,
open: false,
jobId: "",
hasResult: false,
}
}
toggle = () => {
this.setState({ collapsed: !this.state.collapsed });
};
_updateSt = (value) => {
this.setState({ st: value });
}
_handleSubmit = (e) => {
e.preventDefault() // disable default HTML form behavior
// Create form.
var data = new FormData()
data.append('st', this.state.st)
// POST
axios.post(API_ROOT + 'search', data)
.then(response => {
console.log("RESPONSE")
console.log(response)
let jobId = response.data
this.setState({ jobId })
// Handle the return.
this.props.dispatch(addJob(jobId,
'search',
new Date().toLocaleString(),
String('Search for ' + this.state.st + ' at: ' + new Date().toLocaleString())
))
this.setState({ hasResult: true })
})
};
render(){
const { st, hasResult, collapsed } = this.state;
const { token } = this.props;
return (
<div>
<RedirectToken token={token} />
{/* actual form */}
{(!hasResult)?
<form className="md-text-container md-grid">
<div className="md-cell md-cell--12">
{/* Help Text */}
<Button hidden raised primary swapTheming label='Help' onClick={this.toggle}>
help_outline
</Button>
<Collapse collapsed={collapsed}>
<div>
<br />
<h6>Description:</h6>
<p>
We use the <a href='https://biopython.org/wiki/SeqIO'>SeqIO </a>
library to parse the identifiers from an isolate.
Specifically, we use the record.id which uses the combined
gi gb accession numbers.
</p>
<p>
For example, a <b>GenBank</b> record:
<br/>
<br/>
Escherichia coli strain 97-3250 chromosome, complete genome
<br/>
5,942,969 bp circular DNA
<br/>
CP027599.1 GI:1370526529
<br/>
<br/>
With fasta header:
<br/>
<br/>
>gi|1370526529|gb|CP027599.1| Escherichia coli strain 97-3250 chromosome, complete genome
<br/>
<br/>
Would have a record.id of <b>gi|1370526529|gb|CP027599.1|</b>
<br/>
<br/>
For <b>shotgun sequences</b>, search is indexed by sequence ids.
Seraching for any accession in a shotgun sequence will return results
for all sequences in the isolate (not just for the single sequence).
<br/>
For example, a sequence with header:
<br/>
<br/>
>MOKN01000001.1 Escherichia coli strain 443 BN4_443_1_(paired)_contig_1, whole genome shotgun sequence
<br/>
<br/>
Would have a record.id of <b>MOKN01000001.1</b>
</p>
</div>
</Collapse>
</div>
{/* End: Help Text */}
<div className="md-cell md-cell--12">
<h5>Search By record.id</h5>
<TextField
id="st"
value={st}
onChange={this._updateSt}
helpText="gi|1370526529|gb|CP027599.1|"
/>
</div>
<div className="md-cell md-cell--12">
<Button
raised
secondary
type="submit"
label="Submit"
onClick={this._handleSubmit}
/>
</div>
</form> :
<Loading jobId={this.state.jobId} />
}
</div>
)
}
}

Search = connect()(Search)

export default Search
31 changes: 25 additions & 6 deletions src/middleware/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react'
const ROOT = window.location.protocol + '//' + window.location.hostname + ':8000/'
// const ROOT = 'https://lfz.corefacility.ca/superphy/spfyapi/'
// const ROOT = 'http://10.139.14.212:8000/'
// const ROOT = 'http://192.168.5.19:8090/'
// const ROOT = 'http://192.168.5.19:8000/'
// const ROOT = 'https://spfy.enchartus.ca/'
export const API_ROOT = ROOT + 'api/v0/'
Expand All @@ -12,7 +13,9 @@ export const version = 'v.6.3.0'
export const analyses = [
{
'analysis':'subtyping',
'description':'Serotype, Virulence Factors, Antimicrobial Resistance, Shiga-toxin & Intimin',
'description':(
<p style={{color:'gray'}}>Serotype, Virulence Factors, Antimicrobial Resistance, Shiga-toxin & Intimin</p>
),
'text':(
<p>
Upload genome files & determine associated subtypes.
Expand All @@ -25,23 +28,39 @@ export const analyses = [
},{
'analysis':'fishers',
'pseudonym':'statistical comparison',
'description':"Group database nodes and compare them using Fisher's Exact Test",
'description': (
<p style={{color:'gray'}}>Identify predictive markers for groups of bacteria based on genome content or
<br/> metadata, using Fisher's Exact Test</p>
),
'text':'Select groups from uploaded genomes & compare for a chosen target datum.'
}
},{
'analysis': 'search',
'pseudonym': 'search by record.id',
'description': (
<p style={{ color: 'gray' }}>Search database results by record.id</p>
),
'text': ''
},
]

export const extra = [
{
'analysis': 'database',
'description': 'Status check of database connection',
'description': (
<p style={{color:'gray'}}>Status check of database connection</p>
),
'text': ''
},{
'analysis': 'metadata',
'description': 'Submit metadata in the form of a .csv for upload to the database',
'description': (
<p style={{color:'gray'}}>Submit metadata in the form of a .csv for upload to the database</p>
),
'text': ''
},{
'analysis': 'panseq',
'description': 'Load a pan-genome into the database for secondary analyses',
'description': (
<p style={{color:'gray'}}>Load a pan-genome into the database for secondary analyses</p>
),
'text': (
<p>
Upload genomes & split into pan-genome regions.
Expand Down

0 comments on commit 33525bc

Please sign in to comment.