sequenceserver 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequenceserver/api_errors.rb +18 -0
  3. data/lib/sequenceserver/blast/hit.rb +1 -2
  4. data/lib/sequenceserver/blast/job.rb +15 -5
  5. data/lib/sequenceserver/blast/report.rb +5 -1
  6. data/lib/sequenceserver/blast/tasks.rb +1 -1
  7. data/lib/sequenceserver/database.rb +5 -1
  8. data/lib/sequenceserver/job.rb +26 -10
  9. data/lib/sequenceserver/makeblastdb.rb +2 -1
  10. data/lib/sequenceserver/routes.rb +38 -13
  11. data/lib/sequenceserver/version.rb +1 -1
  12. data/lib/sequenceserver/zip_file_generator.rb +1 -1
  13. data/lib/sequenceserver.rb +1 -1
  14. data/public/404.html +8 -16
  15. data/public/css/app.css +794 -111
  16. data/public/css/app.min.css +1 -1
  17. data/public/css/sequenceserver.min.css +1 -8
  18. data/public/js/alignment_exporter.js +16 -28
  19. data/public/js/circos.js +103 -96
  20. data/public/{packages/circosJS@1.7.0.js → js/circosjs.js} +156 -778
  21. data/public/js/cloud_share_modal.js +95 -98
  22. data/public/js/collapse_preferences.js +13 -15
  23. data/public/js/databases.js +4 -3
  24. data/public/js/databases_tree.js +51 -30
  25. data/public/js/error_modal.js +35 -22
  26. data/public/js/exporter.js +2 -0
  27. data/public/js/form.js +55 -17
  28. data/public/js/grapher.js +94 -92
  29. data/public/js/hit.js +24 -22
  30. data/public/js/hits.js +283 -0
  31. data/public/js/hits_overview.js +8 -27
  32. data/public/js/hsp.js +140 -144
  33. data/public/js/jquery_world.js +25 -16
  34. data/public/js/kablammo.js +14 -10
  35. data/public/js/length_distribution.js +23 -37
  36. data/public/js/mailto.js +1 -3
  37. data/public/js/null_plugins/report_plugins.js +4 -5
  38. data/public/js/options.js +19 -25
  39. data/public/js/query.js +36 -35
  40. data/public/js/report/alignment_results.js +18 -0
  41. data/public/js/report/graphical_overview.js +38 -0
  42. data/public/js/report/report_header.js +28 -0
  43. data/public/js/report/run_summary.js +43 -0
  44. data/public/js/report.js +124 -348
  45. data/public/js/report_root.js +10 -8
  46. data/public/js/search.js +31 -11
  47. data/public/js/search_button.js +85 -78
  48. data/public/js/sequence.js +169 -180
  49. data/public/js/sequence_modal.js +58 -65
  50. data/public/js/share_url.js +5 -5
  51. data/public/js/sidebar.js +152 -89
  52. data/public/js/svgExporter.js +8 -3
  53. data/public/js/tests/alignment_exporter.spec.js +38 -0
  54. data/public/js/tests/cloud_share_modal.spec.js +75 -0
  55. data/public/js/tests/form.spec.js +1 -1
  56. data/public/js/tests/report.spec.js +37 -15
  57. data/public/js/tests/visualisation_helpers.spec.js +28 -0
  58. data/public/js/visualisation_helpers.js +24 -27
  59. data/public/packages/jquery-ui@1.13.3.js +19070 -0
  60. data/public/sequenceserver-report.min.js +3 -2481
  61. data/public/sequenceserver-report.min.js.LICENSE.txt +213 -0
  62. data/public/sequenceserver-report.min.js.map +1 -0
  63. data/public/sequenceserver-search.min.js +3 -2382
  64. data/public/sequenceserver-search.min.js.LICENSE.txt +262 -0
  65. data/public/sequenceserver-search.min.js.map +1 -0
  66. data/views/error.erb +1 -1
  67. data/views/layout.erb +122 -133
  68. data/views/report.erb +3 -2
  69. data/views/search.erb +10 -14
  70. metadata +21 -28
  71. data/public/config.js +0 -147
  72. data/public/css/fonts.css +0 -31
  73. data/public/css/grapher.css +0 -670
  74. data/public/css/sequenceserver.css +0 -527
  75. data/public/packages/jquery-ui@1.11.4.js +0 -16624
  76. data/public/vendor/github/mbostock/d3@3.5.6/d3.js +0 -9506
  77. data/public/vendor/github/mbostock/d3@3.5.6.js +0 -1
  78. data/views/search_layout.erb +0 -153
data/public/js/hits.js ADDED
@@ -0,0 +1,283 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { Component } from 'react';
3
+ import _ from 'underscore';
4
+
5
+ import { ReportQuery } from './query';
6
+ import Hit from './hit';
7
+ import HSP from './hsp';
8
+ import AlignmentExporter from './alignment_exporter';
9
+ /* eslint-enable no-unused-vars */
10
+
11
+ class Hits extends Component {
12
+ constructor(props) {
13
+ super(props);
14
+ this.numUpdates = 0;
15
+ this.nextQuery = 0;
16
+ this.nextHit = 0;
17
+ this.nextHSP = 0;
18
+ this.maxHSPs = 10; // max HSPs to render in a cycle
19
+ this.state = props.state;
20
+ this.state.pluginResults = [];
21
+ this.prepareAlignmentOfSelectedHits = this.prepareAlignmentOfSelectedHits.bind(this);
22
+ }
23
+
24
+ componentDidMount() {
25
+ this.props.plugins.init(this.onPluginResultsFetched.bind(this));
26
+ this.componentDidUpdate(this.props, this.state);
27
+ }
28
+
29
+ onPluginResultsFetched(pluginResults) {
30
+ this.setState({ pluginResults: pluginResults });
31
+ }
32
+
33
+ replacePluginResults(pluginResults) {
34
+ if (!pluginResults) return;
35
+
36
+ const updatedResults = this.props.plugins.replacePluginResults(this.state.results, pluginResults);
37
+ this.setState({ results: updatedResults, pluginResults: pluginResults });
38
+ }
39
+
40
+ /**
41
+ * Called for the first time after as BLAST results have been retrieved from
42
+ * the server and added to this.state by fetchResults. Only summary overview
43
+ * and circos would have been rendered at this point. At this stage we kick
44
+ * start iteratively adding 1 HSP to the page every 25 milli-seconds.
45
+ */
46
+ componentDidUpdate(_prevProps, prevState) {
47
+ // Log to console how long the last update take?
48
+ // console.log((Date.now() - this.lastTimeStamp) / 1000);
49
+
50
+ // Queue next update if we have not rendered all results yet.
51
+ if (this.nextQuery < this.state.queries.length) {
52
+ // setTimeout is used to clear call stack and space out
53
+ // the updates giving the browser a chance to respond
54
+ // to user interactions.
55
+ setTimeout(() => this.updateState(), 25);
56
+ } else {
57
+ this.props.componentFinishedUpdating();
58
+ }
59
+
60
+ if (this.state.pluginResults.length > 0 && prevState.pluginResults.length == 0) {
61
+ this.replacePluginResults(this.state.pluginResults);
62
+ }
63
+ }
64
+
65
+ isFirstUpdate() {
66
+ return this.nextQuery == 0 && this.nextHit == 0 && this.nextHSP == 0;
67
+ }
68
+
69
+ /* eslint complexity: ["error", 6] */
70
+ /* ---------------------
71
+ * Push next slice of results to React for rendering.
72
+ */
73
+ updateState() {
74
+ var results = { items: [], numHSPsProcessed: 0 };
75
+ this.processQueries(results);
76
+
77
+ // Push the components to react for rendering.
78
+ this.numUpdates++;
79
+ this.lastTimeStamp = Date.now();
80
+ this.setState({
81
+ results: this.state.results.concat(results.items),
82
+ veryBig: this.numUpdates >= 250,
83
+ });
84
+ }
85
+
86
+ processQueries(results) {
87
+ while (this.nextQuery < this.state.queries.length) {
88
+ var query = this.state.queries[this.nextQuery];
89
+
90
+ // We may see a query multiple times during rendering because only
91
+ // 10 hsps are rendered in each cycle, but we want to create the
92
+ // corresponding Query component only the first time we see it.
93
+ if (this.nextHit == 0 && this.nextHSP == 0) {
94
+ results.items.push(this.renderReportQuery(query));
95
+ results.items.push(this.props.plugins.queryResult(query, this.state.pluginResults));
96
+ }
97
+
98
+ this.processHits(results, query);
99
+ this.iterateLoops(['nextQuery', 'nextHit'], query.hits.length);
100
+ if (results.numHSPsProcessed == this.maxHSPs) break;
101
+ }
102
+ }
103
+
104
+ processHits(results, query) {
105
+ while (this.nextHit < query.hits.length) {
106
+ var hit = query.hits[this.nextHit];
107
+ // We may see a hit multiple times during rendering because only
108
+ // 3 hsps are rendered in each cycle, but we want to create the
109
+ // corresponding Hit component only the first time we see it.
110
+ if (this.nextHSP == 0) results.items.push(this.renderHit(query, hit));
111
+
112
+ this.processHSPS(results, query, hit);
113
+ this.iterateLoops(['nextHit', 'nextHSP'], hit.hsps.length);
114
+ if (results.numHSPsProcessed == this.maxHSPs) break;
115
+ }
116
+ }
117
+
118
+ processHSPS(results, query, hit) {
119
+ while (this.nextHSP < hit.hsps.length) {
120
+ // Get nextHSP and increment the counter.
121
+ var hsp = hit.hsps[this.nextHSP++];
122
+ results.items.push(
123
+ this.renderHsp(query, hit, hsp)
124
+ );
125
+ results.numHSPsProcessed++;
126
+ if (results.numHSPsProcessed == this.maxHSPs) break;
127
+ }
128
+ }
129
+
130
+ /*
131
+ * this function check if 2nd argument is reach end of it
132
+ */
133
+ iterateLoops(args, length) {
134
+ if (this[args[1]] != length) return;
135
+
136
+ this[args[0]]++;
137
+ this[args[1]] = 0;
138
+ }
139
+
140
+ renderHsp(query, hit, hsp) {
141
+ return (
142
+ <HSP
143
+ key={
144
+ 'Query_' +
145
+ query.number +
146
+ '_Hit_' +
147
+ hit.number +
148
+ '_HSP_' +
149
+ hsp.number
150
+ }
151
+ query={query}
152
+ hit={hit}
153
+ hsp={hsp}
154
+ algorithm={this.state.program}
155
+ showHSPNumbers={hit.hsps.length > 1}
156
+ {...this.props}
157
+ />
158
+ );
159
+ }
160
+
161
+ renderHit(query, hit) {
162
+ return (
163
+ <Hit
164
+ key={'Query_' + query.number + '_Hit_' + hit.number}
165
+ query={query}
166
+ hit={hit}
167
+ algorithm={this.state.program}
168
+ querydb={this.state.querydb}
169
+ selectHit={this.selectHit}
170
+ imported_xml={this.state.imported_xml}
171
+ non_parse_seqids={this.state.non_parse_seqids}
172
+ showQueryCrumbs={this.state.queries.length > 1}
173
+ showHitCrumbs={query.hits.length > 1}
174
+ veryBig={this.state.veryBig}
175
+ onChange={this.prepareAlignmentOfSelectedHits}
176
+ {...this.props}
177
+ />
178
+ );
179
+ }
180
+
181
+ renderReportQuery(query) {
182
+ return (
183
+ <ReportQuery
184
+ key={'Query_' + query.id}
185
+ query={query}
186
+ program={this.state.program}
187
+ querydb={this.state.querydb}
188
+ showQueryCrumbs={this.state.queries.length > 1}
189
+ non_parse_seqids={this.state.non_parse_seqids}
190
+ imported_xml={this.state.imported_xml}
191
+ veryBig={this.state.veryBig}
192
+ />
193
+ );
194
+ }
195
+
196
+ /* eslint complexity: ["error", 6] */
197
+ /* -----------------------------------
198
+ * Event-handler when hit is selected
199
+ * Adds border-seqorange to hit component.
200
+ * Updates number of Fasta that can be downloaded
201
+ */
202
+ selectHit(id) {
203
+ var checkbox = $('#' + id);
204
+ var num_checked = $('.hit-links :checkbox:checked').length;
205
+
206
+ if (!checkbox || !checkbox.val()) return;
207
+
208
+ var $hit = $(checkbox.data('target'));
209
+
210
+ // Highlight selected hit and enable 'Download FASTA/Alignment of
211
+ // selected' links.
212
+ if (checkbox.is(':checked')) {
213
+ $hit.addClass('border-seqorange');
214
+ $hit.next('.hsp').addClass('border-seqorange');
215
+ $hit.removeClass('border-transparent');
216
+ $hit.next('.hsp').removeClass('border-transparent');
217
+ $('.download-fasta-of-selected').enable();
218
+ $('.download-alignment-of-selected').enable();
219
+ } else {
220
+ $hit.removeClass('border-seqorange');
221
+ $hit.next('.hsp').removeClass('border-seqorange');
222
+ $hit.addClass('border-transparent');
223
+ $hit.next('.hsp').addClass('border-transparent');
224
+ $('.download-fasta-of-selected').attr('href', '#').removeAttr('download');
225
+ }
226
+
227
+ var $a = $('.download-fasta-of-selected');
228
+ var $b = $('.download-alignment-of-selected');
229
+
230
+ if (num_checked >= 1) {
231
+ $a.find('.font-bold').html(num_checked);
232
+ $b.find('.font-bold').html(num_checked);
233
+ $a.find('.font-bold').addClass('px-1').removeClass('px-0.5');
234
+ $b.find('.font-bold').addClass('px-1').removeClass('px-0.5');
235
+ }
236
+
237
+ if (num_checked == 0) {
238
+ $a.addClass('disabled').find('.font-bold').html('');
239
+ $b.addClass('disabled').find('.font-bold').html('');
240
+ $a.find('.font-bold').removeClass('px-1').addClass('px-0.5');
241
+ $b.find('.font-bold').removeClass('px-1').addClass('px-0.5');
242
+ }
243
+ }
244
+
245
+ prepareAlignmentOfSelectedHits() {
246
+ var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
247
+ return this.value;
248
+ }).get();
249
+
250
+ if(!sequence_ids.length){
251
+ // remove attributes from link if sequence_ids array is empty
252
+ $('.download-alignment-of-selected').attr('href', '#').removeAttr('download');
253
+ return;
254
+
255
+ }
256
+ if(this.state.alignment_blob_url){
257
+ // always revoke existing url if any because this method will always create a new url
258
+ window.URL.revokeObjectURL(this.state.alignment_blob_url);
259
+ }
260
+ var hsps_arr = [];
261
+ var aln_exporter = new AlignmentExporter();
262
+ const self = this;
263
+ _.each(this.state.queries, _.bind(function (query) {
264
+ _.each(query.hits, function (hit) {
265
+ if (_.indexOf(sequence_ids, hit.id) != -1) {
266
+ hsps_arr = hsps_arr.concat(self.props.populate_hsp_array(hit, query.id));
267
+ }
268
+ });
269
+ }, this));
270
+ const filename = 'alignment-' + sequence_ids.length + '_hits.txt';
271
+ const blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, filename);
272
+ // set required download attributes for link
273
+ $('.download-alignment-of-selected').attr('href', blob_url).attr('download', filename);
274
+ // track new url for future removal
275
+ this.setState({alignment_blob_url: blob_url});
276
+ }
277
+
278
+ render() {
279
+ return this.state.results;
280
+ }
281
+ }
282
+
283
+ export default Hits;
@@ -1,4 +1,4 @@
1
- import d3 from 'd3';
1
+ import * as d3 from 'd3';
2
2
  import _ from 'underscore';
3
3
  import Grapher from 'grapher';
4
4
  import * as Helpers from './visualisation_helpers';
@@ -58,13 +58,6 @@ class Graph {
58
58
  return hits;
59
59
  }
60
60
 
61
- setupTooltip() {
62
- this.svg_container.find('[data-toggle="tooltip"]').tooltip({
63
- 'placement': 'top', 'container': 'body', 'html': 'true',
64
- 'delay': 0, 'white-space': 'nowrap'
65
- });
66
- }
67
-
68
61
  setupClick($graphDiv) {
69
62
  $('a', $graphDiv).click(function (evt) {
70
63
  evt.preventDefault();
@@ -87,7 +80,7 @@ class Graph {
87
80
  $graphDiv
88
81
  .append(
89
82
  $('<button/>')
90
- .addClass('btn btn-link more')
83
+ .addClass('btn btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer more')
91
84
  .attr('type', 'button')
92
85
  .attr('data-parent-query', $queryDiv.attr('id'))
93
86
  .html('View More&nbsp;')
@@ -97,7 +90,7 @@ class Graph {
97
90
  .addClass('fa fa-angle-double-down')
98
91
  ),
99
92
  $('<button/>')
100
- .addClass('btn btn-link less')
93
+ .addClass('btn btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer less')
101
94
  .attr('type', 'button')
102
95
  .attr('data-parent-query', $queryDiv.attr('id'))
103
96
  .html('View Less&nbsp;')
@@ -143,7 +136,6 @@ class Graph {
143
136
  countHits();
144
137
  this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW, opts, hits);
145
138
  initButtons();
146
- this.setupTooltip();
147
139
  e.stopPropagation();
148
140
  },this));
149
141
 
@@ -161,7 +153,6 @@ class Graph {
161
153
  this.graphIt($queryDiv, $graphDiv, shownHits, MIN_HITS_TO_SHOW - shownHits, opts, hits);
162
154
  initButtons();
163
155
  }
164
- this.setupTooltip();
165
156
  e.stopPropagation();
166
157
  },this));
167
158
  }
@@ -262,9 +253,7 @@ class Graph {
262
253
  .append('g')
263
254
  .attr('transform', 'translate(' + options.margin / 2 + ', ' + (1.5 * options.margin) + ')');
264
255
 
265
- var x = d3.scale
266
- .linear()
267
- .range([0, width - options.margin]);
256
+ var x = d3.scaleLinear().range([0, width - options.margin]);
268
257
 
269
258
  x.domain([1, queryLen]);
270
259
 
@@ -274,10 +263,7 @@ class Graph {
274
263
  var _tValues = x.ticks(11);
275
264
  _tValues.pop();
276
265
 
277
- var xAxis = d3.svg
278
- .axis()
279
- .scale(x)
280
- .orient('top')
266
+ var xAxis = d3.axisBottom(x)
281
267
  .tickValues(_tValues.concat([1, queryLen]))
282
268
  .tickFormat(formatter);
283
269
 
@@ -294,16 +280,14 @@ class Graph {
294
280
  .attr('y','2px')
295
281
  .attr('transform','rotate(-90)');
296
282
 
297
- var y = d3.scale
298
- .ordinal()
299
- .rangeBands([0, height - 3 * options.margin - 2 * options.legend], 0.3);
283
+ var y = d3.scaleBand()
284
+ .range([0, height - 3 * options.margin - 2 * options.legend], 0.3);
300
285
 
301
286
  y.domain(hits.map(function (d) {
302
287
  return d.hitId;
303
288
  }));
304
289
 
305
- var gradScale = d3.scale
306
- .log()
290
+ var gradScale = d3.scaleLog()
307
291
  .domain([
308
292
  d3.min([1e-5, d3.min(hits.map(function (d) {
309
293
  if (parseFloat(d.hitEvalue) === 0.0) return undefined;
@@ -369,7 +353,6 @@ class Graph {
369
353
  d3.select(this)
370
354
  .attr('xlink:href', '#' + q_i + '_hit_' + (i+1))
371
355
  .append('rect')
372
- .attr('data-toggle', 'tooltip')
373
356
  .attr('title', alt_tooltip)
374
357
  .attr('class','bar')
375
358
  .attr('x', function (d) {
@@ -393,8 +376,6 @@ class Graph {
393
376
  if (index === 0) {
394
377
  this.graphControls($queryDiv, $graphDiv, true, opts, inhits);
395
378
  }
396
- // Refresh tooltip each time graph is redrawn.
397
- this.setupTooltip();
398
379
  // Ensure clicking on 'rect' takes user to the relevant hit on all
399
380
  // browsers.
400
381
  this.setupClick($graphDiv);