sequenceserver 3.1.2 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequenceserver/blast/tasks.rb +1 -1
  3. data/lib/sequenceserver/routes.rb +1 -1
  4. data/lib/sequenceserver/version.rb +1 -1
  5. data/lib/sequenceserver.rb +1 -1
  6. data/public/css/app.min.css +1 -1
  7. data/public/css/sequenceserver.css +0 -18
  8. data/public/css/sequenceserver.min.css +2 -2
  9. data/public/js/alignment_exporter.js +16 -28
  10. data/public/js/cloud_share_modal.js +42 -42
  11. data/public/js/form.js +12 -10
  12. data/public/js/grapher.js +4 -4
  13. data/public/js/hit.js +3 -3
  14. data/public/js/hits.js +276 -0
  15. data/public/js/jquery_world.js +1 -1
  16. data/public/js/mailto.js +1 -3
  17. data/public/js/null_plugins/report_plugins.js +1 -0
  18. data/public/js/options.js +2 -6
  19. data/public/js/query.js +1 -1
  20. data/public/js/report.js +68 -252
  21. data/public/js/report_root.js +7 -5
  22. data/public/js/search.js +28 -11
  23. data/public/js/sequence.js +158 -158
  24. data/public/js/sequence_modal.js +28 -36
  25. data/public/js/sidebar.js +7 -6
  26. data/public/js/tests/alignment_exporter.spec.js +38 -0
  27. data/public/js/tests/cloud_share_modal.spec.js +75 -0
  28. data/public/js/tests/report.spec.js +37 -15
  29. data/public/packages/jquery-ui@1.13.3.js +19070 -0
  30. data/public/sequenceserver-report.min.js +3 -2481
  31. data/public/sequenceserver-report.min.js.LICENSE.txt +300 -0
  32. data/public/sequenceserver-report.min.js.map +1 -0
  33. data/public/sequenceserver-search.min.js +3 -2382
  34. data/public/sequenceserver-search.min.js.LICENSE.txt +292 -0
  35. data/public/sequenceserver-search.min.js.map +1 -0
  36. data/views/layout.erb +3 -7
  37. data/views/search.erb +1 -1
  38. data/views/search_layout.erb +1 -1
  39. metadata +11 -5
  40. data/public/config.js +0 -147
  41. data/public/packages/jquery-ui@1.11.4.js +0 -16624
data/public/js/report.js CHANGED
@@ -3,10 +3,8 @@ import React, { Component } from 'react';
3
3
  import _ from 'underscore';
4
4
 
5
5
  import Sidebar from './sidebar';
6
+ import Hits from './hits';
6
7
  import Circos from './circos';
7
- import { ReportQuery } from './query';
8
- import Hit from './hit';
9
- import HSP from './hsp';
10
8
  import AlignmentExporter from './alignment_exporter';
11
9
  import ReportPlugins from 'report_plugins';
12
10
 
@@ -21,11 +19,6 @@ class Report extends Component {
21
19
  super(props);
22
20
  // Properties below are internal state used to render results in small
23
21
  // slices (see updateState).
24
- this.numUpdates = 0;
25
- this.nextQuery = 0;
26
- this.nextHit = 0;
27
- this.nextHSP = 0;
28
- this.maxHSPs = 3; // max HSPs to render in a cycle
29
22
  this.state = {
30
23
  user_warning: null,
31
24
  download_links: [],
@@ -34,8 +27,8 @@ class Report extends Component {
34
27
  program: '',
35
28
  program_version: '',
36
29
  submitted_at: '',
37
- queries: [],
38
30
  results: [],
31
+ queries: [],
39
32
  querydb: [],
40
33
  params: [],
41
34
  stats: [],
@@ -43,7 +36,6 @@ class Report extends Component {
43
36
  allQueriesLoaded: false,
44
37
  cloud_sharing_enabled: false,
45
38
  };
46
- this.prepareAlignmentOfSelectedHits = this.prepareAlignmentOfSelectedHits.bind(this);
47
39
  this.prepareAlignmentOfAllHits = this.prepareAlignmentOfAllHits.bind(this);
48
40
  this.setStateFromJSON = this.setStateFromJSON.bind(this);
49
41
  this.plugins = new ReportPlugins(this);
@@ -58,31 +50,66 @@ class Report extends Component {
58
50
  }
59
51
 
60
52
  pollPeriodically(path, callback, errCallback) {
61
- var intervals = [200, 400, 800, 1200, 2000, 3000, 5000];
53
+ var intervals = [200, 400, 800, 1200, 2000, 3000, 5000];
62
54
  function poll() {
63
- $.getJSON(path).complete(function (jqXHR) {
64
- switch (jqXHR.status) {
65
- case 202:
66
- var interval;
67
- if (intervals.length === 1) {
68
- interval = intervals[0];
55
+ fetch(path)
56
+ .then(response => {
57
+ // Handle HTTP status codes
58
+ if (!response.ok) throw response;
59
+
60
+ return response.text().then(data => {
61
+ if (data) {
62
+ data = parseJSON(data);
63
+ };
64
+ return { status: response.status, data }
65
+ });
66
+ })
67
+ .then(({ status, data }) => {
68
+ switch (status) {
69
+ case 202:
70
+ var interval;
71
+ if (intervals.length === 1) {
72
+ interval = intervals[0];
73
+ } else {
74
+ interval = intervals.shift();
75
+ }
76
+ setTimeout(poll, interval);
77
+ break;
78
+ case 200:
79
+ callback(data);
80
+ break;
81
+ }
82
+ })
83
+ .catch(error => {
84
+ if (error.text) {
85
+ error.text().then(errData => {
86
+ errData = parseJSON(errData);
87
+ switch (error.status) {
88
+ case 400:
89
+ case 422:
90
+ case 500:
91
+ errCallback(errData);
92
+ break;
93
+ default:
94
+ console.error("Unhandled error:", error.status);
95
+ }
96
+ });
69
97
  } else {
70
- interval = intervals.shift();
98
+ console.error("Network error:", error);
71
99
  }
72
- setTimeout(poll, interval);
73
- break;
74
- case 200:
75
- callback(jqXHR.responseJSON);
76
- break;
77
- case 400:
78
- case 422:
79
- case 500:
80
- errCallback(jqXHR.responseJSON);
81
- break;
82
- }
83
- });
100
+ });
84
101
  }
85
102
 
103
+ function parseJSON(str) {
104
+ let parsedJson = str;
105
+ try {
106
+ parsedJson = JSON.parse(str);
107
+ } catch (e) {
108
+ console.error("Error parsing JSON:", e);
109
+ }
110
+
111
+ return parsedJson;
112
+ }
86
113
  poll();
87
114
  }
88
115
 
@@ -113,139 +140,6 @@ class Report extends Component {
113
140
  this.toggleTable();
114
141
  }
115
142
 
116
- /**
117
- * Called for the first time after as BLAST results have been retrieved from
118
- * the server and added to this.state by fetchResults. Only summary overview
119
- * and circos would have been rendered at this point. At this stage we kick
120
- * start iteratively adding 1 HSP to the page every 25 milli-seconds.
121
- */
122
- componentDidUpdate(prevProps, prevState) {
123
- // Log to console how long the last update take?
124
- // console.log((Date.now() - this.lastTimeStamp) / 1000);
125
-
126
- // Lock sidebar in its position on the first update.
127
- if (this.nextQuery == 0 && this.nextHit == 0 && this.nextHSP == 0) {
128
- this.affixSidebar();
129
- }
130
-
131
- // Queue next update if we have not rendered all results yet.
132
- if (this.nextQuery < this.state.queries.length) {
133
- // setTimeout is used to clear call stack and space out
134
- // the updates giving the browser a chance to respond
135
- // to user interactions.
136
- setTimeout(() => this.updateState(), 25);
137
- } else {
138
- this.componentFinishedUpdating();
139
- }
140
-
141
- this.plugins.componentDidUpdate(prevProps, prevState);
142
- }
143
-
144
- /**
145
- * Push next slice of results to React for rendering.
146
- */
147
- updateState() {
148
- var results = [];
149
- var numHSPsProcessed = 0;
150
- while (this.nextQuery < this.state.queries.length) {
151
- var query = this.state.queries[this.nextQuery];
152
-
153
- // We may see a query multiple times during rendering because only
154
- // 3 hsps are rendered in each cycle, but we want to create the
155
- // corresponding Query component only the first time we see it.
156
- if (this.nextHit == 0 && this.nextHSP == 0) {
157
- results.push(
158
- <ReportQuery
159
- key={'Query_' + query.id}
160
- query={query}
161
- program={this.state.program}
162
- querydb={this.state.querydb}
163
- showQueryCrumbs={this.state.queries.length > 1}
164
- non_parse_seqids={this.state.non_parse_seqids}
165
- imported_xml={this.state.imported_xml}
166
- veryBig={this.state.veryBig}
167
- />
168
- );
169
-
170
- results.push(...this.plugins.queryResults(query));
171
- }
172
-
173
- while (this.nextHit < query.hits.length) {
174
- var hit = query.hits[this.nextHit];
175
- // We may see a hit multiple times during rendering because only
176
- // 10 hsps are rendered in each cycle, but we want to create the
177
- // corresponding Hit component only the first time we see it.
178
- if (this.nextHSP == 0) {
179
- results.push(
180
- <Hit
181
- key={'Query_' + query.number + '_Hit_' + hit.number}
182
- query={query}
183
- hit={hit}
184
- algorithm={this.state.program}
185
- querydb={this.state.querydb}
186
- selectHit={this.selectHit}
187
- imported_xml={this.state.imported_xml}
188
- non_parse_seqids={this.state.non_parse_seqids}
189
- showQueryCrumbs={this.state.queries.length > 1}
190
- showHitCrumbs={query.hits.length > 1}
191
- veryBig={this.state.veryBig}
192
- onChange={this.prepareAlignmentOfSelectedHits}
193
- {...this.props}
194
- />
195
- );
196
- }
197
-
198
- while (this.nextHSP < hit.hsps.length) {
199
- // Get nextHSP and increment the counter.
200
- var hsp = hit.hsps[this.nextHSP++];
201
- results.push(
202
- <HSP
203
- key={
204
- 'Query_' +
205
- query.number +
206
- '_Hit_' +
207
- hit.number +
208
- '_HSP_' +
209
- hsp.number
210
- }
211
- query={query}
212
- hit={hit}
213
- hsp={hsp}
214
- algorithm={this.state.program}
215
- showHSPNumbers={hit.hsps.length > 1}
216
- {...this.props}
217
- />
218
- );
219
- numHSPsProcessed++;
220
- if (numHSPsProcessed == this.maxHSPs) break;
221
- }
222
- // Are we here because we have iterated over all hsps of a hit,
223
- // or because of the break clause in the inner loop?
224
- if (this.nextHSP == hit.hsps.length) {
225
- this.nextHit = this.nextHit + 1;
226
- this.nextHSP = 0;
227
- }
228
- if (numHSPsProcessed == this.maxHSPs) break;
229
- }
230
-
231
- // Are we here because we have iterated over all hits of a query,
232
- // or because of the break clause in the inner loop?
233
- if (this.nextHit == query.hits.length) {
234
- this.nextQuery = this.nextQuery + 1;
235
- this.nextHit = 0;
236
- }
237
- if (numHSPsProcessed == this.maxHSPs) break;
238
- }
239
-
240
- // Push the components to react for rendering.
241
- this.numUpdates++;
242
- this.lastTimeStamp = Date.now();
243
- this.setState({
244
- results: this.state.results.concat(results),
245
- veryBig: this.numUpdates >= 250,
246
- });
247
- }
248
-
249
143
  /**
250
144
  * Called after all results have been rendered.
251
145
  */
@@ -282,6 +176,7 @@ class Report extends Component {
282
176
  );
283
177
  }
284
178
 
179
+ /* eslint-disable */
285
180
  /**
286
181
  * Return results JSX.
287
182
  */
@@ -300,12 +195,20 @@ class Report extends Component {
300
195
  <div className="col-md-9">
301
196
  {this.overviewJSX()}
302
197
  {this.circosJSX()}
303
- {this.plugins.generateStats()}
198
+ {this.plugins.generateStats(this.state.queries)}
304
199
  {this.state.results}
200
+ <Hits
201
+ state={this.state}
202
+ componentFinishedUpdating={(_) => this.componentFinishedUpdating(_)}
203
+ populate_hsp_array={this.populate_hsp_array.bind(this)}
204
+ plugins={this.plugins}
205
+ {...this.props}
206
+ />
305
207
  </div>
306
208
  </div>
307
209
  );
308
210
  }
211
+ /* eslint-enable */
309
212
 
310
213
 
311
214
  warningJSX() {
@@ -477,20 +380,7 @@ class Report extends Component {
477
380
  );
478
381
  }
479
382
 
480
- /**
481
- * Affixes the sidebar.
482
- */
483
- affixSidebar() {
484
- var $sidebar = $('.sidebar');
485
- var sidebarOffset = $sidebar.offset();
486
- if (sidebarOffset) {
487
- $sidebar.affix({
488
- offset: {
489
- top: sidebarOffset.top,
490
- },
491
- });
492
- }
493
- }
383
+
494
384
 
495
385
  /**
496
386
  * For the query in viewport, highlights corresponding entry in the index.
@@ -499,84 +389,10 @@ class Report extends Component {
499
389
  $('body').scrollspy({ target: '.sidebar' });
500
390
  }
501
391
 
502
- /**
503
- * Event-handler when hit is selected
504
- * Adds glow to hit component.
505
- * Updates number of Fasta that can be downloaded
506
- */
507
- selectHit(id) {
508
- var checkbox = $('#' + id);
509
- var num_checked = $('.hit-links :checkbox:checked').length;
510
-
511
- if (!checkbox || !checkbox.val()) {
512
- return;
513
- }
514
-
515
- var $hit = $(checkbox.data('target'));
516
-
517
- // Highlight selected hit and enable 'Download FASTA/Alignment of
518
- // selected' links.
519
- if (checkbox.is(':checked')) {
520
- $hit.addClass('glow');
521
- $hit.next('.hsp').addClass('glow');
522
- $('.download-fasta-of-selected').enable();
523
- $('.download-alignment-of-selected').enable();
524
- } else {
525
- $hit.removeClass('glow');
526
- $hit.next('.hsp').removeClass('glow');
527
- $('.download-fasta-of-selected').attr('href', '#').removeAttr('download');
528
- }
529
-
530
- var $a = $('.download-fasta-of-selected');
531
- var $b = $('.download-alignment-of-selected');
532
-
533
- if (num_checked >= 1) {
534
- $a.find('.text-bold').html(num_checked);
535
- $b.find('.text-bold').html(num_checked);
536
- }
537
-
538
- if (num_checked == 0) {
539
- $a.addClass('disabled').find('.text-bold').html('');
540
- $b.addClass('disabled').find('.text-bold').html('');
541
- }
542
- }
543
392
  populate_hsp_array(hit, query_id){
544
393
  return hit.hsps.map(hsp => Object.assign(hsp, {hit_id: hit.id, query_id}));
545
394
  }
546
395
 
547
- prepareAlignmentOfSelectedHits() {
548
- var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
549
- return this.value;
550
- }).get();
551
-
552
- if(!sequence_ids.length){
553
- // remove attributes from link if sequence_ids array is empty
554
- $('.download-alignment-of-selected').attr('href', '#').removeAttr('download');
555
- return;
556
-
557
- }
558
- if(this.state.alignment_blob_url){
559
- // always revoke existing url if any because this method will always create a new url
560
- window.URL.revokeObjectURL(this.state.alignment_blob_url);
561
- }
562
- var hsps_arr = [];
563
- var aln_exporter = new AlignmentExporter();
564
- const self = this;
565
- _.each(this.state.queries, _.bind(function (query) {
566
- _.each(query.hits, function (hit) {
567
- if (_.indexOf(sequence_ids, hit.id) != -1) {
568
- hsps_arr = hsps_arr.concat(self.populate_hsp_array(hit, query.id));
569
- }
570
- });
571
- }, this));
572
- const filename = 'alignment-' + sequence_ids.length + '_hits.txt';
573
- const blob_url = aln_exporter.prepare_alignments_for_export(hsps_arr, filename);
574
- // set required download attributes for link
575
- $('.download-alignment-of-selected').attr('href', blob_url).attr('download', filename);
576
- // track new url for future removal
577
- this.setState({alignment_blob_url: blob_url});
578
- }
579
-
580
396
  prepareAlignmentOfAllHits() {
581
397
  // Get number of hits and array of all hsps.
582
398
  var num_hits = 0;
@@ -17,6 +17,8 @@ class Page extends Component {
17
17
  this.showErrorModal = this.showErrorModal.bind(this);
18
18
  this.getCharacterWidth = this.getCharacterWidth.bind(this);
19
19
  this.hspChars = createRef();
20
+ this.sequenceModal = createRef();
21
+ this.errorModal = createRef();
20
22
  }
21
23
  componentDidMount() {
22
24
  var job_id = location.pathname.split('/').pop();
@@ -24,11 +26,11 @@ class Page extends Component {
24
26
  }
25
27
 
26
28
  showSequenceModal(url) {
27
- this.refs.sequenceModal.show(url);
29
+ this.sequenceModal.current.show(url);
28
30
  }
29
31
 
30
32
  showErrorModal(errorData, beforeShow) {
31
- this.refs.errorModal.show(errorData, beforeShow);
33
+ this.errorModal.current.show(errorData, beforeShow);
32
34
  }
33
35
 
34
36
  getCharacterWidth() {
@@ -60,11 +62,11 @@ class Page extends Component {
60
62
  <canvas id="png-exporter" hidden></canvas>
61
63
 
62
64
  <SequenceModal
63
- ref="sequenceModal"
65
+ ref={this.sequenceModal}
64
66
  showErrorModal={(...args) => this.showErrorModal(...args)}
65
67
  />
66
68
 
67
- <ErrorModal ref="errorModal" />
69
+ <ErrorModal ref={this.errorModal} />
68
70
  </div>
69
71
  );
70
72
  }
@@ -72,4 +74,4 @@ class Page extends Component {
72
74
 
73
75
 
74
76
  const root = createRoot(document.getElementById('view'));
75
- root.render(<Page />);
77
+ root.render(<Page />);
data/public/js/search.js CHANGED
@@ -1,32 +1,49 @@
1
- import "./jquery_world";
2
- import React, { Component } from "react";
3
- import { createRoot } from "react-dom/client";
4
- import { DnD } from "./dnd";
5
- import { Form } from "./form";
6
- import { SearchHeaderPlugin } from "search_header_plugin";
1
+ import './jquery_world';
2
+ import React, { Component } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { DnD } from './dnd';
5
+ import { Form } from './form';
6
+ import { SearchHeaderPlugin } from 'search_header_plugin';
7
7
 
8
8
  /**
9
9
  * Clear sessionStorage on reload.
10
10
  */
11
- if (performance.navigation.type == performance.navigation.TYPE_RELOAD) {
11
+ const navigationEntry = performance.getEntriesByType('navigation')[0];
12
+ if (navigationEntry && navigationEntry.type === 'reload') {
12
13
  sessionStorage.clear();
13
14
  history.replaceState(null, "", location.href.split("?")[0]);
14
15
  }
15
16
 
16
17
  class Page extends Component {
18
+ constructor(props) {
19
+ super(props);
20
+ this.dnd = React.createRef();
21
+ this.form = React.createRef();
22
+ }
23
+
17
24
  componentDidMount() {
18
- this.refs.dnd.setState({ query: this.refs.form.refs.query });
25
+ this.dnd.current.setState({ query: this.form.current.query.current })
19
26
  }
27
+
20
28
  render() {
21
29
  return (
22
30
  <div>
23
31
  <SearchHeaderPlugin />
24
- <DnD ref="dnd" />
25
- <Form ref="form" />
32
+ <DnD ref={this.dnd} />
33
+ <Form ref={this.form} />
26
34
  </div>
27
35
  );
28
36
  }
29
37
  }
30
38
 
31
- const root = createRoot(document.getElementById("view"));
39
+ const root = createRoot(document.getElementById('view'));
32
40
  root.render(<Page />);
41
+
42
+ document.addEventListener('DOMContentLoaded', () => {
43
+ const closeButton = document.querySelector('.js--close-help');
44
+ if (closeButton) {
45
+ closeButton.addEventListener('click', function() {
46
+ document.querySelector('[data-help-modal]').classList.add('hidden');
47
+ });
48
+ }
49
+ });