Skip to content

Commit

Permalink
Add project_name to vcfs & enable filtering by it
Browse files Browse the repository at this point in the history
This involved rewriting the /runs page in React.

Will need alter the DB to add project_name TEXT to the vcfs table.
  • Loading branch information
ihodes committed Feb 14, 2015
1 parent 4e8b8ff commit 62b4a1a
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 80 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ JSON should be POSTed to `/runs` with following fields:

**Optional**<br />
`dataset` -- The name of the dataset on which the caller was run (e.g. Dream Chromosome 20).<br />
`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.<br />
`normalPath` -- The path on HDFS of the normalBAM on which the caller was run.<br />
`params` -- Params that the caller was run with, or other notes relevant to the run.<br />
1 change: 0 additions & 1 deletion cycledash/static/css/runs.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ tr.run.selected {
font-weight: 500;
}
tr.run-info {
display: none;
background: rgb(247, 252, 253);
}
tr.run-info ul {
Expand Down
178 changes: 156 additions & 22 deletions cycledash/static/js/runs.js
Original file line number Diff line number Diff line change
@@ -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 => <option value={name} key={name ? name : NO_FILTER}>{name ? name : NO_FILTER}</option>);
var runs = this.filteredRuns();
var rows = runs.map((run) => {
var rows = [<RunRow run={run} key={run.id} handleClick={this.handleClickRun(run.id)} />];
if (run.id === this.state.selectedRunId) {
rows.push(<RowValues run={run} runKeyVals={this.props.runKeyVals} key={'row-values-'+run.id} />);
}
return rows;
});
return (
<div>
<h5>Filter runs by project name:&nbsp;&nbsp;
<select value={this.state.projectFilter}
onChange={this.handleProjectFilter}>{projects}</select>
</h5>
<table className='runs table condenses table-hover' >
<thead>
<tr>
<th></th>
<th className='caller-name'>Caller Name</th>
<th className='dataset'>Dataset</th>
<th className='date'>Submitted On</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>
);
}
});

var RunRow = React.createClass({
propTypes: {
run: React.PropTypes.object.isRequired,
handleClick: React.PropTypes.func.isRequired
},
render: function() {
var run = this.props.run;
return (
<tr className='run' onClick={this.props.handleClick}>
<td className='run-id'>
<span className='run-id'>{ run.id }</span>
<a className='btn btn-default btn-xs' href='/runs/{ run.id }/examine'>Examine</a>
</td>
<td className='caller-name'>{ run.caller_name }</td>
<td className='dataset'>{ run.dataset_name }</td>
<td className='date' title='{ run.created_at }'>{ run.created_at }</td>
<RowLabels run={run} />
<RowComments run={run} />
</tr>
);
}
});

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(<dt key={'dt'+key}>{ title }</dt>);
acc.push(<dd key={'dd'+key}>{ run[key] }</dd>);
}
return acc;
}, []);
return (
<tr className='run-info'>
<td colSpan='6'>
<dl className='dl-horizontal'>
{descriptions}
</dl>
</td>
</tr>
);
}
});

var RowLabels = React.createClass({
propTypes: {
run: React.PropTypes.object.isRequired
},
render: function() {
var run = this.props.run;
return (
<td className='labels'>
{ run.validation_vcf ? <span className='label label-info' title='Is a validation VCF'>validation</span> : null }
{ run.tumor_bam_uri ? <span className='label label-info' title='Has an associated tumor BAM'>tumor</span> : null }
{ run.normal_bam_uri ? <span className='label label-info' title='Has an associated normal BAM'>normal</span> : null }
</td>
);
}
});

var RowComments = React.createClass({
propTypes: {
run: React.PropTypes.object.isRequired
},
render: function() {
var run = this.props.run;
return (
<td className='comments'>
<span className={'comment-bubble' + (run.num_comments ? '' : ' no-comment') }></span>
<span className={ run.num_comments ? '' : 'no-comment' }>
{ run.num_comments }
</span>
</td>
);
}
});


///////////////////////////////////////
// Non-React code for form handling: //
///////////////////////////////////////

// Upload the file and place its path in the element.
function upload(file, fileInputElement) {
Expand Down Expand Up @@ -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(<RunsPage runs={runs} runKeyVals={runKeyVals} />, el);

d3.select('#show-submit')
.on('click', function() {
Expand Down
7 changes: 6 additions & 1 deletion cycledash/templates/macros/run_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ <h2><button type="button" class="close" aria-hidden="true">&times;</button></h3>
<input class="form-control" type="text" name="dataset"
placeholder="DREAM training chr20">
</div>
<div class="form-group">
<label>Project:</label>
<input class="form-control" type="text" name="projectName"
placeholder="PT0123">
</div>
<div class="form-group">
<label>VCF Path:</label>
<input class="uploadable form-control" type="text" name="vcfPath"
Expand Down Expand Up @@ -38,7 +43,7 @@ <h4>Optional <small>all paths should be HDFS paths to canonical data</small></h4
<textarea class="form-control" rows="3" name="params"
placeholder="command line parameters here"></textarea>
</div>

<button type="submit" class="btn btn-success btn-block">Submit New Run</button>
</form>
{%- endmacro -%}
64 changes: 9 additions & 55 deletions cycledash/templates/runs.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,58 +19,12 @@ <h4 class="last-comments">Last {{ last_comments|length}} Comments:</h4>
<a href="/comments" class="all-comments">See all…</a>
{%- endif %}

<table class="runs table condenses table-hover">
<thead>
<tr>
<th></th>
<th class="caller-name">Caller Name</th>
<th class="dataset">Dataset</th>
<th class="date">Submitted On</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{%- for run in runs %}
<tr class="run" data-runId="{{ run['id'] }}">
<td class="run-id">
<span class="run-id">{{ run['id'] }}</span>
<a class="btn btn-default btn-xs" href="/runs/{{ run['id'] }}/examine">Examine</a>
</td>
<td class="caller-name">{{ run['caller_name'] }}</td>
<td class="dataset">{{ run['dataset_name'] }}</td>
<td class="date" title="{{ run['created_at'] }}">{{ run['created_at'].strftime('%Y-%m-%d') }}</td>
<td class="labels">
{%- if run['validation_vcf'] %}<span class="label label-info" title="Is a validation VCF">validation</span>{% endif -%}
{%- if run['tumor_bam_uri'] %}<span class="label label-info" title="Has an associated tumor BAM">tumor</span>{% endif -%}
{%- if run['normal_bam_uri'] %}<span class="label label-info" title="Has an associated normal BAM">normal</span>{% endif -%}
</td>
<td class="comments">
<span class="comment-bubble {{ '' if run['num_comments'] else 'no-comment'}}">
</span>
<span class="{{ '' if run['num_comments'] else 'no-comment' }}">
{{ run['num_comments'] }}
</span>
</td>
</tr>
<tr class="run-info" data-runId="{{ run['id'] }}">
<td colspan=6>
<dl class="dl-horizontal">
{% for name, key in run_kvs.iteritems() %}
{%- if run.get(key) %}
<dt>{{ name }}</dt> <dd>{{ run[key] }}</dd>
{% endif -%}
{% endfor %}
</dl>
</td>
</tr>
{% endfor -%}
</tbody>
</table>
<main>
<script type="text/javascript" src="/static/js/dist/bundled.js"></script>
<script>
window.activateRunsUI();
</script>

{% endblock %}
<div id="runs"></div>
</main>
<script type="text/javascript" src="/static/js/dist/bundled.js"></script>
<script>
window.renderRunPage(document.getElementById('runs'),
{{ runs | tojson }},
{{ run_kvs | tojson}});
</script>
{% endblock %}
2 changes: 2 additions & 0 deletions cycledash/validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def is_path(s):
'is_validation': bool,
'params': unicode,
'dataset': unicode,
'project_name': unicode,
'vcf_header': unicode
})

Expand All @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion cycledash/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
3 changes: 3 additions & 0 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file modified tests/pdifftests/runs-info.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/pdifftests/runs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 62b4a1a

Please sign in to comment.