diff --git a/README.md b/README.md index 0ab5295..f8a980d 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ JSON should be POSTed to `/runs` with following fields: **Optional**
`dataset` -- The name of the dataset on which the caller was run (e.g. Dream Chromosome 20).
+`projectName` -- The name of the project this run belongs to. `tumorPath` -- The path on HDFS of the tumor BAM on which the caller was run.
`normalPath` -- The path on HDFS of the normalBAM on which the caller was run.
`params` -- Params that the caller was run with, or other notes relevant to the run.
diff --git a/cycledash/static/css/runs.css b/cycledash/static/css/runs.css index 841e96d..395352b 100644 --- a/cycledash/static/css/runs.css +++ b/cycledash/static/css/runs.css @@ -42,7 +42,6 @@ tr.run.selected { font-weight: 500; } tr.run-info { - display: none; background: rgb(247, 252, 253); } tr.run-info ul { diff --git a/cycledash/static/js/runs.js b/cycledash/static/js/runs.js index 8de2a87..a900d9c 100644 --- a/cycledash/static/js/runs.js +++ b/cycledash/static/js/runs.js @@ -1,5 +1,157 @@ 'use strict'; -var d3 = require('d3'); +var d3 = require('d3'), + React = require('react'), + _ = require('underscore'); + + + ////////////////////////// + // React Run page code: // +////////////////////////// +var NO_FILTER = '----'; + +var RunsPage = React.createClass({ + propTypes: { + runs: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + runKeyVals: React.PropTypes.object.isRequired + }, + getInitialState: function() { + return {selectedRunId: null, projectFilter: null}; + }, + filteredRuns: function() { + return this.props.runs.filter(run => (this.state.projectFilter === null) || this.state.projectFilter.match(run.project_name) ); + }, + handleClickRun: function(runId) { + return () => { + this.setState({selectedRunId: this.state.selectedRunId === runId ? null : runId}); + }; + }, + handleProjectFilter: function(evt) { + var val = evt.target.value; + this.setState({projectFilter: val === NO_FILTER ? null : val}); + }, + render: function() { + var projectNames = _.unique(_.pluck(this.props.runs, 'project_name')); + var projects = projectNames.map(name => ); + var runs = this.filteredRuns(); + var rows = runs.map((run) => { + var rows = []; + if (run.id === this.state.selectedRunId) { + rows.push(); + } + return rows; + }); + return ( +
+
Filter runs by project name:   + +
+ + + + + + + + + + + + + {rows} + +
Caller NameDatasetSubmitted On
+
+ ); + } +}); + +var RunRow = React.createClass({ + propTypes: { + run: React.PropTypes.object.isRequired, + handleClick: React.PropTypes.func.isRequired + }, + render: function() { + var run = this.props.run; + return ( + + + { run.id } + Examine + + { run.caller_name } + { run.dataset_name } + { run.created_at } + + + + ); + } +}); + +var RowValues = React.createClass({ + propTypes: { + run: React.PropTypes.object.isRequired, + runKeyVals: React.PropTypes.object.isRequired + }, + render: function() { + var run = this.props.run, + descriptions = _.reduce(this.props.runKeyVals, function(acc, key, title) { + if (run[key]) { + acc.push(
{ title }
); + acc.push(
{ run[key] }
); + } + return acc; + }, []); + return ( + + +
+ {descriptions} +
+ + + ); + } +}); + +var RowLabels = React.createClass({ + propTypes: { + run: React.PropTypes.object.isRequired + }, + render: function() { + var run = this.props.run; + return ( + + { run.validation_vcf ? validation : null } + { run.tumor_bam_uri ? tumor : null } + { run.normal_bam_uri ? normal : null } + + ); + } +}); + +var RowComments = React.createClass({ + propTypes: { + run: React.PropTypes.object.isRequired + }, + render: function() { + var run = this.props.run; + return ( + + + + { run.num_comments } + + + ); + } +}); + + + /////////////////////////////////////// + // Non-React code for form handling: // +/////////////////////////////////////// // Upload the file and place its path in the element. function upload(file, fileInputElement) { @@ -37,27 +189,9 @@ function extractError(response) { return json.message || json.error || 'Upload failed.'; } -window.activateRunsUI = function() { - d3.selectAll('tr.run') - .on('click', function(d, i) { - // Ignore clicks on links in the row. - var tag = d3.event.target.tagName; - if (tag == 'A' || tag == 'INPUT') return; - - var selected = d3.select(this).attr('data-selected'); - var $this = d3.select(this); - if (selected == 'true') { - $this.attr('data-selected', false) - .attr('class', 'run'); - d3.select($this.node().nextElementSibling) - .style('display', 'none'); - } else { - $this.attr('data-selected', true) - .attr('class', 'run selected'); - d3.select($this.node().nextElementSibling) - .style('display', 'table-row'); - } - }); + +window.renderRunPage = function(el, runs, runKeyVals) { + React.render(, el); d3.select('#show-submit') .on('click', function() { diff --git a/cycledash/templates/macros/run_form.html b/cycledash/templates/macros/run_form.html index 123cf8f..e1066c1 100644 --- a/cycledash/templates/macros/run_form.html +++ b/cycledash/templates/macros/run_form.html @@ -11,6 +11,11 @@

+
+ + +
Optional all paths should be HDFS paths to canonical data
- + {%- endmacro -%} diff --git a/cycledash/templates/runs.html b/cycledash/templates/runs.html index cd42c4a..b8f0498 100644 --- a/cycledash/templates/runs.html +++ b/cycledash/templates/runs.html @@ -19,58 +19,12 @@

Last {{ last_comments|length}} Comments:

See all… {%- endif %} - - - - - - - - - - - - - {%- for run in runs %} - - - - - - - - - - - - {% endfor -%} - -
Caller NameDatasetSubmitted On
- {{ run['id'] }} - Examine - {{ run['caller_name'] }}{{ run['dataset_name'] }}{{ run['created_at'].strftime('%Y-%m-%d') }} - {%- if run['validation_vcf'] %}validation{% endif -%} - {%- if run['tumor_bam_uri'] %}tumor{% endif -%} - {%- if run['normal_bam_uri'] %}normal{% endif -%} - - - - - {{ run['num_comments'] }} - -
-
- {% for name, key in run_kvs.iteritems() %} - {%- if run.get(key) %} -
{{ name }}
{{ run[key] }}
- {% endif -%} - {% endfor %} -
-
-
- - - - {% endblock %} +
+
+ + +{% endblock %} diff --git a/cycledash/validations.py b/cycledash/validations.py index ee29ea3..fe32061 100644 --- a/cycledash/validations.py +++ b/cycledash/validations.py @@ -23,6 +23,7 @@ def is_path(s): 'is_validation': bool, 'params': unicode, 'dataset': unicode, + 'project_name': unicode, 'vcf_header': unicode }) @@ -35,6 +36,7 @@ def is_path(s): 'is_validation': bool, 'params': unicode, 'dataset': unicode, + 'project_name': unicode, 'vcf_header': unicode, 'true_positive': Coerce(int), 'false_positive': Coerce(int), diff --git a/cycledash/views.py b/cycledash/views.py index a8794f7..4dcbae5 100644 --- a/cycledash/views.py +++ b/cycledash/views.py @@ -38,7 +38,8 @@ RUN_ADDL_KVS = {'Tumor BAM': 'tumor_bam_uri', 'Normal BAM': 'normal_bam_uri', 'VCF URI': 'uri', - 'Notes': 'notes'} + 'Notes': 'notes', + 'Project': 'project_name'} VCF_FILENAME = 'cycledash-run-{}.vcf' diff --git a/schema.sql b/schema.sql index a25ad1f..3a4a099 100644 --- a/schema.sql +++ b/schema.sql @@ -3,6 +3,9 @@ CREATE TABLE vcfs ( created_at TIMESTAMP DEFAULT statement_timestamp() NOT NULL, caller_name TEXT NOT NULL, -- Name of the caller this came from. dataset_name TEXT NOT NULL, -- Name of the dataset VCF is from (e.g. Synth4, PC7114) + + project_name TEXT, -- Name of the project (research, patient, etc) this VCF is part of. + tumor_bam_uri TEXT, -- URI of tumor BAM normal_bam_uri TEXT, -- URI of normal BAM validation_vcf BOOLEAN DEFAULT false, -- whether or not this is a validation VCF diff --git a/tests/pdifftests/runs-info.png b/tests/pdifftests/runs-info.png index da491be..a352f59 100644 Binary files a/tests/pdifftests/runs-info.png and b/tests/pdifftests/runs-info.png differ diff --git a/tests/pdifftests/runs.png b/tests/pdifftests/runs.png index ea93a69..d30b89b 100644 Binary files a/tests/pdifftests/runs.png and b/tests/pdifftests/runs.png differ