diff --git a/cycledash/static/css/examine.css b/cycledash/static/css/examine.css index 2989886..32a25fe 100644 --- a/cycledash/static/css/examine.css +++ b/cycledash/static/css/examine.css @@ -273,6 +273,12 @@ dt { font-size: 1.5em; color: white; } +.query-status.loading { + background: url(/static/img/loader.gif) no-repeat; +} +.query-status.loading:before { + content: ""; +} .tt-hint { color: gray; } diff --git a/cycledash/static/img/loader.gif b/cycledash/static/img/loader.gif index ab48712..a4b5178 100644 Binary files a/cycledash/static/img/loader.gif and b/cycledash/static/img/loader.gif differ diff --git a/cycledash/static/js/examine/RecordActions.js b/cycledash/static/js/examine/RecordActions.js index 412a421..0da0e69 100644 --- a/cycledash/static/js/examine/RecordActions.js +++ b/cycledash/static/js/examine/RecordActions.js @@ -5,10 +5,7 @@ var ACTION_TYPES = { REQUEST_PAGE: 'REQUEST_PAGE', 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', + SORT_BY: 'SORT_BY' }; function getRecordActions(dispatcher) { @@ -20,28 +17,6 @@ function getRecordActions(dispatcher) { order }); }, - updateFilters: function({columnName, type, filterValue}) { - dispatcher.dispatch({ - actionType: ACTION_TYPES.UPDATE_FILTER, - columnName, - type, - filterValue - }); - }, - updateRange: function({contig, start, end}) { - dispatcher.dispatch({ - 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, diff --git a/cycledash/static/js/examine/RecordStore.js b/cycledash/static/js/examine/RecordStore.js index db8a58a..eb7a85c 100644 --- a/cycledash/static/js/examine/RecordStore.js +++ b/cycledash/static/js/examine/RecordStore.js @@ -30,6 +30,7 @@ var ENTIRE_GENOME = {start: null, end: null, contig: types.ALL_CHROMOSOMES}; function createRecordStore(run, dispatcher, opt_testDataSource) { // Initial state of the store. This is mutable. There be monsters. var vcfId = run.id, + hasPendingRequest = false, hasLoaded = false, loadError = null, @@ -58,18 +59,13 @@ function createRecordStore(run, dispatcher, opt_testDataSource) { var dataSource = opt_testDataSource || networkDataSource; + var currentPendingRequest = null; + function receiver(action) { switch(action.actionType) { case ACTION_TYPES.SORT_BY: updateSortBys(action.columnName, action.order); - updateGenotypes({append: false}); - break; - case ACTION_TYPES.UPDATE_FILTER: - updateFilters(action.columnName, action.filterValue, action.type); - updateGenotypes({append: false}); - break; - case ACTION_TYPES.UPDATE_RANGE: - updateRange(action.contig, action.start, action.end); + clearPendingRequests(); updateGenotypes({append: false}); break; case ACTION_TYPES.REQUEST_PAGE: @@ -81,6 +77,7 @@ function createRecordStore(run, dispatcher, opt_testDataSource) { break; case ACTION_TYPES.SET_QUERY: setQuery(action.query); + clearPendingRequests(); updateGenotypes({append: false}); break; } @@ -119,23 +116,34 @@ function createRecordStore(run, dispatcher, opt_testDataSource) { // table is now invalidated). if (!append) selectedRecord = null; + currentPendingRequest = query; + hasPendingRequest = true; + notifyChange(); // notify of pending request $.when(deferredGenotypes(vcfId, query)) .done(response => { - hasLoaded = true; + if (!_.isEqual(currentPendingRequest, query)) { + return; // A subsequent request has superceded this one. + } if (append) { - // TODO: BUG: This can result in a out-of-order records, if a later - // XHR returns before an earlier XHR. records = records.concat(response.records); } else { stats = response.stats; records = response.records; } + hasLoaded = true; + hasPendingRequest = false; notifyChange(); }); } var updateGenotypes = _.debounce(_.throttle(_updateGenotypes, 500 /* ms */), 500 /* ms */); + // All pending requests are obsolete. Ignore them for purposes of the UI. + function clearPendingRequests() { + hasPendingRequest = false; + currentPendingRequest = null; + } + // Returns a JS object query for sending to the backend. function queryFrom(range, filters, sortBy, page, limit) { if (sortBy[0].columnName == 'position') { @@ -169,26 +177,6 @@ function createRecordStore(run, dispatcher, opt_testDataSource) { if (val) return decodeURIComponent(val.split('=')[1]); } - /** - * Updates the filters by columnName and filterValue. Removes previous any - * previous filter which applies to the columnName, and then appends the new - * filter. - * - * NB: mutates store state! - */ - function updateFilters(columnName, filterValue, type) { - // TODO(ihodes): be careful with how we remove filters: we could have two+ - // filters applied to a given columnName, e.g. selection a - // range of interesting sample:DP - var filter = _.find(filters, f => _.isEqual(columnName, f.columnName)); - if (filter) { - filters = _.without(filters, filter); - } - if (filterValue.length > 0) { - filters.push({columnName, filterValue, type}); - } - } - /** * Updates the sortBys by columnName and order. * @@ -200,15 +188,6 @@ function createRecordStore(run, dispatcher, opt_testDataSource) { sortBys = [{columnName, order}]; } - /** - * Updates the range. - * - * NB: mutates store state! - */ - function updateRange(contig, start, end) { - range = {contig, start, end}; - } - /** * Sets sortBy, range and filters all in one go. * Unlike the update* methods, this clobbers whatever was there before. @@ -250,6 +229,7 @@ function createRecordStore(run, dispatcher, opt_testDataSource) { var query = queryFrom(range, filters, sortBys, page, limit); return { hasLoaded, + hasPendingRequest, loadError, records, stats, diff --git a/cycledash/static/js/examine/components/ExaminePage.js b/cycledash/static/js/examine/components/ExaminePage.js index 643e47c..c987549 100644 --- a/cycledash/static/js/examine/components/ExaminePage.js +++ b/cycledash/static/js/examine/components/ExaminePage.js @@ -63,6 +63,7 @@ var ExaminePage = React.createClass({ stats={state.stats} />