Skip to content

Commit

Permalink
Merge pull request #226 from hammerlab/cql-box
Browse files Browse the repository at this point in the history
CQL QueryBox in the Examine page.
  • Loading branch information
danvk committed Nov 20, 2014
2 parents 5ab039c + d5b547c commit 7ea35a8
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 144 deletions.
20 changes: 10 additions & 10 deletions __tests__/js/query-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ describe('Query Language', function() {
}

it('should parse simple filters', function() {
expectParse('A < 10', {filters:[{type: '<', value:'10', columnName:'A'}]});
expectParse('B = ABC', {filters:[{type: '=', value:'ABC', columnName:'B'}]});
expectParse('A < 10', {filters:[{type: '<', filterValue:'10', columnName:'A'}]});
expectParse('B = ABC', {filters:[{type: '=', filterValue:'ABC', columnName:'B'}]});
expectParse('INFO.DP like "ABC"', {
filters:[{type: 'LIKE', value:'ABC', columnName:'INFO.DP'}]
filters:[{type: 'LIKE', filterValue:'ABC', columnName:'INFO.DP'}]
});
expectParse('INFO:AF rlike "^.*$"', {
filters:[{type: 'RLIKE', value:'^.*$', columnName:'INFO:AF'}]
filters:[{type: 'RLIKE', filterValue:'^.*$', columnName:'INFO:AF'}]
});
});

Expand All @@ -41,22 +41,22 @@ describe('Query Language', function() {
});

it('should parse quoted filters with spaces', function() {
expectParse('A like \'A"" C\'', {filters:[{type: 'LIKE', value:'A"" C', columnName:'A'}]});
expectParse('A like \'A"" C\'', {filters:[{type: 'LIKE', filterValue:'A"" C', columnName:'A'}]});
});

it('should parse compound filters', function() {
expectParse('A < 10 AND B >= ABC', {
filters: [
{type: '<', value:'10', columnName:'A'},
{type: '>=', value:'ABC', columnName:'B'}
{type: '<', filterValue:'10', columnName:'A'},
{type: '>=', filterValue:'ABC', columnName:'B'}
]
});
});

it('should parse compound filters with ranges', function() {
expectParse('20:1234- AND A < 10', {
range: {contig:'20', start: '1234'},
filters: [{type: '<', value: '10', columnName: 'A'}]
filters: [{type: '<', filterValue: '10', columnName: 'A'}]
});
});

Expand Down Expand Up @@ -88,8 +88,8 @@ describe('Query Language', function() {
{
range: {contig: 'X', start: '345', end: '4567'},
filters: [
{type: '<=', value: '10', columnName: 'A'},
{type: '=', value: 'ABC', columnName: 'B'}
{type: '<=', filterValue: '10', columnName: 'A'},
{type: '=', filterValue: 'ABC', columnName: 'B'}
],
sortBy:[{order:'asc', columnName: 'INFO.DP'}]
});
Expand Down
92 changes: 87 additions & 5 deletions cycledash/static/css/examine.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ body {
.vcf-table input {
border: none;
padding: 11px;
background: #dddddd;
background: #ddd;
color: #1f2f1f;
width: 45px;
margin-left: 3px;
Expand Down Expand Up @@ -82,10 +82,7 @@ th.attr.selected > span {
color: #969696;
}
th.uber-column {
border-right: 1px solid black;
}
th.uber-column:last-child {
border: none;
border-left: 1px solid #bbb;
}
.vcf-table .tooltip {
display: none;
Expand Down Expand Up @@ -309,3 +306,88 @@ text {
font-size: 1.2em;
font-weight: 700;
}

/* CQL Typeahead */
.query-container * { box-sizing: border-box; }
.typeahead-input {
position: relative;
padding-right: 350px; /* matches width of Stats Table */
margin-bottom: 20px;
/* reference, alternates */
}
.twitter-typeahead {
width: 100%;
padding-right: 20px;
}
.query-input, .twitter-typeahead .tt-query, .twitter-typeahead .tt-hint {
width: 100%;
height: 40px;
padding: 8px 12px;
font-size: 24px;
line-height: 30px;
border: 2px solid #ccc;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
outline: none;
padding-left: 30px;
}
.query-status {
position: absolute;
left: 10px;
top: 50%;
margin-top: -8px;
width: 16px;
height: 16px;
z-index: 1;
}
.query-status.good {
background: green;
}
.query-status.bad {
background: red;
}

.tt-hint {
color: gray;
}

.error-message {
min-height: 20px;
}

/* Styles from http://stackoverflow.com/a/20205623/388951 */
.tt-query {
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}

.tt-hint {
color: #999
}

.tt-dropdown-menu {
min-width: 422px;
margin-top: 12px;
padding: 8px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 8px;
box-shadow: 0 5px 10px rgba(0,0,0,.2);
}

.tt-suggestion {
padding: 3px 20px;
font-size: 18px;
line-height: 24px;
}

.tt-suggestion.tt-cursor {
color: #fff;
background-color: #0097cf;

}

.tt-suggestion p {
margin: 0;
}
33 changes: 20 additions & 13 deletions cycledash/static/js/examine/RecordActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@


var ACTION_TYPES = {
SORT_BY: 'SORT_BY',
UPDATE_FILTER: 'UPDATE_FILTER',
SELECT_RECORD_RANGE: 'SELECT_RECORD_RANGE',
REQUEST_PAGE: 'REQUEST_PAGE',
SELECT_COLUMN: 'SELECT_COLUMN',
SELECT_RECORD: 'SELECT_RECORD',
SET_QUERY: 'SET_QUERY',
SORT_BY: 'SORT_BY',
UPDATE_FILTER: 'UPDATE_FILTER',
UPDATE_RANGE: 'UPDATE_RANGE',
UPDATE_VARIANT_TYPE: 'UPDATE_VARIANT_TYPE',
SELECT_COLUMN: 'SELECT_COLUMN'
};

function getRecordActions(dispatcher) {
return {
updateSorter: function({columnName, order}) {
updateSortBy: function({columnName, order}) {
dispatcher.dispatch({
actionType: ACTION_TYPES.SORT_BY,
columnName,
Expand All @@ -29,14 +30,26 @@ function getRecordActions(dispatcher) {
filterValue
});
},
updateRecordRange: function({contig, start, end}) {
updateRange: function({contig, start, end}) {
dispatcher.dispatch({
actionType: ACTION_TYPES.SELECT_RECORD_RANGE,
actionType: ACTION_TYPES.UPDATE_RANGE,
start,
end,
contig
});
},
updateVariantType: function(variantType) {
dispatcher.dispatch({
actionType: ACTION_TYPES.UPDATE_VARIANT_TYPE,
variantType
});
},
setQuery: function(query) {
dispatcher.dispatch({
actionType: ACTION_TYPES.SET_QUERY,
query
});
},
requestPage: function() {
dispatcher.dispatch({
actionType: ACTION_TYPES.REQUEST_PAGE
Expand All @@ -48,12 +61,6 @@ function getRecordActions(dispatcher) {
record
});
},
updateVariantType: function(variantType) {
dispatcher.dispatch({
actionType: ACTION_TYPES.UPDATE_VARIANT_TYPE,
variantType
});
},
selectColumn: function({columnName, info, name}) {
dispatcher.dispatch({
actionType: ACTION_TYPES.SELECT_COLUMN,
Expand Down
23 changes: 20 additions & 3 deletions cycledash/static/js/examine/RecordStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var RECORD_LIMIT = 250;
var DEFAULT_SORT_BYS = [{columnName: 'contig', order: 'asc'},
{columnName: 'position', order: 'asc'}];

var ENTIRE_GENOME = {start: null, end: null, chromosome: types.ALL_CHROMOSOMES};

function createRecordStore(vcfId, dispatcher) {
// Initial state of the store. This is mutable. There be monsters.
Expand All @@ -36,8 +37,8 @@ function createRecordStore(vcfId, dispatcher) {
selectedColumns = [],

filters = [],
sortBys = [{columnName: 'position', order: 'asc'}],
range = {start: null, end: null, chromosome: types.ALL_CHROMOSOMES},
sortBys = DEFAULT_SORT_BYS,
range = ENTIRE_GENOME,
variantType = 'ALL', // TODO(ihodes): implement

contigs = [],
Expand All @@ -64,7 +65,7 @@ function createRecordStore(vcfId, dispatcher) {
updateFilters(action.columnName, action.filterValue, action.type);
updateGenotypes({append: false});
break;
case ACTION_TYPES.SELECT_RECORD_RANGE:
case ACTION_TYPES.UPDATE_RANGE:
updateRange(action.contig, action.start, action.end);
updateGenotypes({append: false});
break;
Expand All @@ -88,6 +89,10 @@ function createRecordStore(vcfId, dispatcher) {
case ACTION_TYPES.SELECT_RECORD:
selectedRecord = action.record;
break;
case ACTION_TYPES.SET_QUERY:
setQuery(action.query);
updateGenotypes({append: false});
break;
}
// Required: lets the dispatcher to know that the Store is done processing.
return true;
Expand Down Expand Up @@ -192,6 +197,18 @@ function createRecordStore(vcfId, dispatcher) {
range = {contig, start, end};
}

/**
* Sets sortBy, range and filters all in one go.
* Unlike the update* methods, this clobbers whatever was there before.
*
* NB: mutates store state!
*/
function setQuery(query) {
filters = query.filters || [];
sortBys = query.sortBy || DEFAULT_SORT_BYS;
range = query.range || ENTIRE_GENOME;
}

// Initialize the RecordStore with basic information (columns, the contigs
// in the VCF), and request first records to display.
$.when(deferredSpec(vcfId), deferredContigs(vcfId))
Expand Down
22 changes: 7 additions & 15 deletions cycledash/static/js/examine/components/ExaminePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
var _ = require('underscore'),
React = require('react'),
idiogrammatik = require('idiogrammatik.js'),

AttributeCharts = require('./AttributeCharts'),
BioDalliance = require('./BioDalliance'),
StatsSummary = require('./StatsSummary'),
VCFTable = require('./VCFTable'),
Karyogram = require('./Karyogram'),
QueryBox = require('./QueryBox'),
LoadingStatus = require('./LoadingStatus');


Expand All @@ -30,18 +30,8 @@ var ExaminePage = React.createClass({
this.setState(this.props.recordStore.getState());
});
},
handleRangeChange: function({contig, start, end}) {
this.props.recordActions.updateRecordRange({contig, start, end});
},
handleContigChange: function(contig) {
var start = null, end = null;
this.props.recordActions.updateRecordRange({contig, start, end});
},
handleFilterUpdate: function({columnName, filterValue, type}) {
this.props.recordActions.updateFilters({columnName, filterValue, type});
},
handleSortByChange: function({columnName, order}) {
this.props.recordActions.updateSorter({columnName, order});
this.props.recordActions.updateSortBy({columnName, order});
},
handleRequestPage: function() {
this.props.recordActions.requestPage();
Expand Down Expand Up @@ -72,6 +62,9 @@ var ExaminePage = React.createClass({
this.props.recordActions.selectRecord(records[newIdx]);
}
},
handleQueryChange: function(parsedQuery) {
this.props.recordActions.setQuery(parsedQuery);
},
render: function() {
var state = this.state, props = this.props;
return (
Expand All @@ -95,6 +88,8 @@ var ExaminePage = React.createClass({
handlePreviousRecord={this.handlePreviousRecord}
handleNextRecord={this.handleNextRecord}
handleClose={() => this.handleSelectRecord(null)} />
<QueryBox columns={state.columns}
handleQueryChange={this.handleQueryChange} />
<VCFTable ref="vcfTable"
hasLoaded={state.hasLoaded}
records={state.records}
Expand All @@ -106,10 +101,7 @@ var ExaminePage = React.createClass({
sortBys={state.sortBys}
handleSortByChange={this.handleSortByChange}
handleChartChange={this.handleChartChange}
handleFilterUpdate={this.handleFilterUpdate}
handleContigChange={this.handleContigChange}
handleRequestPage={this.handleRequestPage}
handleRangeChange={this.handleRangeChange}
handleSelectRecord={this.handleSelectRecord} />
</div>
);
Expand Down
Loading

0 comments on commit 7ea35a8

Please sign in to comment.