diff --git a/src/Routes.jsx b/src/Routes.jsx index bd91454..2451e97 100644 --- a/src/Routes.jsx +++ b/src/Routes.jsx @@ -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' @@ -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) => { @@ -45,6 +47,7 @@ const Routes = (token) => ( + diff --git a/src/components/ResultSearch.js b/src/components/ResultSearch.js index dc0246e..1b10736 100644 --- a/src/components/ResultSearch.js +++ b/src/components/ResultSearch.js @@ -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
Waiting for server response...
@@ -20,16 +43,13 @@ class ResultDatabase extends Component { } else if (results.fulfilled){ console.log(results) return ( -
-

# of Genome Files: {results.value.length}

- - Spfy ID - Filename - Submitted - O-Type - H-Type - -
+ + Filename + Date Submitted + Contig ID + Analysis + Hit + ); } } @@ -37,4 +57,4 @@ class ResultDatabase extends Component { export default connect(props => ({ results: {url: API_ROOT + `results/${props.jobId}`} -}))(ResultDatabase) +}))(ResultSearch) diff --git a/src/components/ResultSubtyping.js b/src/components/ResultSubtyping.js index e3e2802..2156f29 100644 --- a/src/components/ResultSubtyping.js +++ b/src/components/ResultSubtyping.js @@ -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; @@ -47,6 +47,7 @@ class ResultSubtyping extends Component { Analysis Hit Long Hitname + ARO Link Orientation Start Stop diff --git a/src/components/ResultsTemplates.js b/src/components/ResultsTemplates.js index 4d94e4c..2463258 100644 --- a/src/components/ResultsTemplates.js +++ b/src/components/ResultsTemplates.js @@ -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) { @@ -44,6 +45,10 @@ const ResultsTemplates = ({ job, ...props }) => { return + case "search": + return + + default: return
ERROR: no matching analysis view found.
} diff --git a/src/containers/Fishers.js b/src/containers/Fishers.js index e62eec9..4d4cbde 100644 --- a/src/containers/Fishers.js +++ b/src/containers/Fishers.js @@ -47,7 +47,7 @@ const fdescrip = (step, nextStep, prevStep) => ( ontology - + @@ -79,7 +79,8 @@ const fdescrip = (step, nextStep, prevStep) => (
Rationale:
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 doi: 10.1093/nar/gkw1004.

diff --git a/src/containers/Search.js b/src/containers/Search.js new file mode 100644 index 0000000..fd6f305 --- /dev/null +++ b/src/containers/Search.js @@ -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 ( +
+ + {/* actual form */} + {(!hasResult)? +
+
+ {/* Help Text */} + + +
+
+
Description:
+

+ We use the SeqIO + library to parse the identifiers from an isolate. + Specifically, we use the record.id which uses the combined + gi gb accession numbers. +

+

+ For example, a GenBank record: +
+
+ Escherichia coli strain 97-3250 chromosome, complete genome +
+ 5,942,969 bp circular DNA +
+ CP027599.1 GI:1370526529 +
+
+ With fasta header: +
+
+ >gi|1370526529|gb|CP027599.1| Escherichia coli strain 97-3250 chromosome, complete genome +
+
+ Would have a record.id of gi|1370526529|gb|CP027599.1| +
+
+ For shotgun sequences, 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). +
+ For example, a sequence with header: +
+
+ >MOKN01000001.1 Escherichia coli strain 443 BN4_443_1_(paired)_contig_1, whole genome shotgun sequence +
+
+ Would have a record.id of MOKN01000001.1 +

+
+
+
+ {/* End: Help Text */} +
+
Search By record.id
+ +
+
+
+
: + + } +
+ ) + } +} + +Search = connect()(Search) + +export default Search diff --git a/src/middleware/api.js b/src/middleware/api.js index cdfd72c..aa5d170 100644 --- a/src/middleware/api.js +++ b/src/middleware/api.js @@ -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/' @@ -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':( +

Serotype, Virulence Factors, Antimicrobial Resistance, Shiga-toxin & Intimin

+ ), 'text':(

Upload genome files & determine associated subtypes. @@ -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': ( +

Identify predictive markers for groups of bacteria based on genome content or +
metadata, using Fisher's Exact Test

+ ), 'text':'Select groups from uploaded genomes & compare for a chosen target datum.' - } + },{ + 'analysis': 'search', + 'pseudonym': 'search by record.id', + 'description': ( +

Search database results by record.id

+ ), + 'text': '' + }, ] export const extra = [ { 'analysis': 'database', - 'description': 'Status check of database connection', + 'description': ( +

Status check of database connection

+ ), 'text': '' },{ 'analysis': 'metadata', - 'description': 'Submit metadata in the form of a .csv for upload to the database', + 'description': ( +

Submit metadata in the form of a .csv for upload to the database

+ ), 'text': '' },{ 'analysis': 'panseq', - 'description': 'Load a pan-genome into the database for secondary analyses', + 'description': ( +

Load a pan-genome into the database for secondary analyses

+ ), 'text': (

Upload genomes & split into pan-genome regions.