sequenceserver 2.2.0 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/COPYRIGHT.txt +1 -1
  3. data/bin/sequenceserver +4 -2
  4. data/lib/sequenceserver/blast/error.rb +53 -0
  5. data/lib/sequenceserver/blast/job.rb +2 -43
  6. data/lib/sequenceserver/job.rb +21 -11
  7. data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +345 -0
  8. data/lib/sequenceserver/makeblastdb.rb +26 -12
  9. data/lib/sequenceserver/routes.rb +29 -3
  10. data/lib/sequenceserver/server.rb +1 -1
  11. data/lib/sequenceserver/version.rb +1 -1
  12. data/lib/sequenceserver.rb +3 -0
  13. data/public/404.html +27 -0
  14. data/public/config.js +0 -6
  15. data/public/css/grapher.css +1 -1
  16. data/public/css/sequenceserver.css +22 -11
  17. data/public/css/sequenceserver.min.css +2 -2
  18. data/public/js/circos.js +7 -3
  19. data/public/js/dnd.js +3 -3
  20. data/public/js/fastq_to_fasta.js +35 -0
  21. data/public/js/form.js +30 -11
  22. data/public/js/grapher.js +123 -113
  23. data/public/js/hit.js +8 -2
  24. data/public/js/hits_overview.js +4 -1
  25. data/public/js/jquery_world.js +0 -1
  26. data/public/js/kablammo.js +4 -0
  27. data/public/js/length_distribution.js +5 -1
  28. data/public/js/null_plugins/download_links.js +7 -0
  29. data/public/js/null_plugins/hit_buttons.js +11 -0
  30. data/public/js/null_plugins/report_plugins.js +18 -0
  31. data/public/js/query.js +26 -6
  32. data/public/js/report.js +33 -17
  33. data/public/js/search.js +0 -8
  34. data/public/js/sidebar.js +11 -1
  35. data/public/js/tests/mock_data/sequences.js +18 -1
  36. data/public/js/tests/search_query.spec.js +12 -3
  37. data/public/sequenceserver-report.min.js +76 -42
  38. data/public/sequenceserver-search.min.js +34 -33
  39. data/views/layout.erb +9 -12
  40. metadata +32 -23
data/public/js/report.js CHANGED
@@ -8,15 +8,14 @@ import { ReportQuery } from './query';
8
8
  import Hit from './hit';
9
9
  import HSP from './hsp';
10
10
  import AlignmentExporter from './alignment_exporter';
11
-
12
-
13
-
11
+ import ReportPlugins from 'report_plugins';
14
12
 
15
13
  /**
16
14
  * Renders entire report.
17
15
  *
18
16
  * Composed of Query and Sidebar components.
19
17
  */
18
+
20
19
  class Report extends Component {
21
20
  constructor(props) {
22
21
  super(props);
@@ -47,15 +46,21 @@ class Report extends Component {
47
46
  this.prepareAlignmentOfSelectedHits = this.prepareAlignmentOfSelectedHits.bind(this);
48
47
  this.prepareAlignmentOfAllHits = this.prepareAlignmentOfAllHits.bind(this);
49
48
  this.setStateFromJSON = this.setStateFromJSON.bind(this);
49
+ this.plugins = new ReportPlugins(this);
50
50
  }
51
+
51
52
  /**
52
53
  * Fetch results.
53
54
  */
54
55
  fetchResults() {
56
+ const path = location.pathname + '.json' + location.search;
57
+ this.pollPeriodically(path, this.setStateFromJSON, this.props.showErrorModal);
58
+ }
59
+
60
+ pollPeriodically(path, callback, errCallback) {
55
61
  var intervals = [200, 400, 800, 1200, 2000, 3000, 5000];
56
- var component = this;
57
62
  function poll() {
58
- $.getJSON(location.pathname + '.json' + location.search).complete(function (jqXHR) {
63
+ $.getJSON(path).complete(function (jqXHR) {
59
64
  switch (jqXHR.status) {
60
65
  case 202:
61
66
  var interval;
@@ -67,12 +72,12 @@ class Report extends Component {
67
72
  setTimeout(poll, interval);
68
73
  break;
69
74
  case 200:
70
- component.setStateFromJSON(jqXHR.responseJSON);
75
+ callback(jqXHR.responseJSON);
71
76
  break;
72
- case 404:
73
77
  case 400:
78
+ case 422:
74
79
  case 500:
75
- component.props.showErrorModal(jqXHR.responseJSON);
80
+ errCallback(jqXHR.responseJSON);
76
81
  break;
77
82
  }
78
83
  });
@@ -93,6 +98,7 @@ class Report extends Component {
93
98
  this.setState(responseJSON, this.prepareAlignmentOfAllHits);
94
99
  }
95
100
  }
101
+
96
102
  /**
97
103
  * Called as soon as the page has loaded and the user sees the loading spinner.
98
104
  * We use this opportunity to setup services that make use of delegated events
@@ -100,6 +106,7 @@ class Report extends Component {
100
106
  */
101
107
  componentDidMount() {
102
108
  this.fetchResults();
109
+ this.plugins.init();
103
110
  // This sets up an event handler which enables users to select text from
104
111
  // hit header without collapsing the hit.
105
112
  this.preventCollapseOnSelection();
@@ -112,7 +119,7 @@ class Report extends Component {
112
119
  * and circos would have been rendered at this point. At this stage we kick
113
120
  * start iteratively adding 1 HSP to the page every 25 milli-seconds.
114
121
  */
115
- componentDidUpdate() {
122
+ componentDidUpdate(prevProps, prevState) {
116
123
  // Log to console how long the last update take?
117
124
  // console.log((Date.now() - this.lastTimeStamp) / 1000);
118
125
 
@@ -130,6 +137,8 @@ class Report extends Component {
130
137
  } else {
131
138
  this.componentFinishedUpdating();
132
139
  }
140
+
141
+ this.plugins.componentDidUpdate(prevProps, prevState);
133
142
  }
134
143
 
135
144
  /**
@@ -140,13 +149,14 @@ class Report extends Component {
140
149
  var numHSPsProcessed = 0;
141
150
  while (this.nextQuery < this.state.queries.length) {
142
151
  var query = this.state.queries[this.nextQuery];
152
+
143
153
  // We may see a query multiple times during rendering because only
144
- // 3 hsps or are rendered in each cycle, but we want to create the
154
+ // 3 hsps are rendered in each cycle, but we want to create the
145
155
  // corresponding Query component only the first time we see it.
146
156
  if (this.nextHit == 0 && this.nextHSP == 0) {
147
157
  results.push(
148
158
  <ReportQuery
149
- key={'Query_' + query.number}
159
+ key={'Query_' + query.id}
150
160
  query={query}
151
161
  program={this.state.program}
152
162
  querydb={this.state.querydb}
@@ -156,6 +166,8 @@ class Report extends Component {
156
166
  veryBig={this.state.veryBig}
157
167
  />
158
168
  );
169
+
170
+ results.push(...this.plugins.queryResults(query));
159
171
  }
160
172
 
161
173
  while (this.nextHit < query.hits.length) {
@@ -190,11 +202,11 @@ class Report extends Component {
190
202
  <HSP
191
203
  key={
192
204
  'Query_' +
193
- query.number +
194
- '_Hit_' +
195
- hit.number +
196
- '_HSP_' +
197
- hsp.number
205
+ query.number +
206
+ '_Hit_' +
207
+ hit.number +
208
+ '_HSP_' +
209
+ hsp.number
198
210
  }
199
211
  query={query}
200
212
  hit={hit}
@@ -261,6 +273,9 @@ class Report extends Component {
261
273
  <br />
262
274
  You can bookmark the page and come back to it later or share the
263
275
  link with someone.
276
+ <br />
277
+ <br />
278
+ { process.env.targetEnv === 'cloud' && <b>If the job takes more than 10 minutes to complete, we will send you an email upon completion.</b> }
264
279
  </p>
265
280
  </div>
266
281
  </div>
@@ -451,7 +466,7 @@ class Report extends Component {
451
466
  toggleTable() {
452
467
  $('body').on(
453
468
  'mousedown',
454
- '.resultn > .section-content > .table-hit-overview > .caption',
469
+ '.resultn .caption[data-toggle="collapse"]',
455
470
  function (event) {
456
471
  var $this = $(this);
457
472
  $this.on('mouseup mousemove', function handler(event) {
@@ -509,6 +524,7 @@ class Report extends Component {
509
524
  } else {
510
525
  $hit.removeClass('glow');
511
526
  $hit.next('.hsp').removeClass('glow');
527
+ $('.download-fasta-of-selected').attr('href', '#').removeAttr('download');
512
528
  }
513
529
 
514
530
  var $a = $('.download-fasta-of-selected');
data/public/js/search.js CHANGED
@@ -3,14 +3,6 @@ import React, { Component } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
4
  import { DnD } from "./dnd";
5
5
  import { Form } from "./form";
6
- /**
7
- * Load necessary polyfills.
8
- */
9
- $.webshims.setOptions(
10
- "basePath",
11
- "/vendor/npm/webshim@1.15.8/js-webshim/minified/shims/"
12
- );
13
- $.webshims.polyfill("forms");
14
6
 
15
7
  /**
16
8
  * Clear sessionStorage on reload.
data/public/js/sidebar.js CHANGED
@@ -4,7 +4,7 @@ import _ from 'underscore';
4
4
  import downloadFASTA from './download_fasta';
5
5
  import asMailtoHref from './mailto';
6
6
  import CloudShareModal from './cloud_share_modal';
7
-
7
+ import DownloadLinks from 'download_links';
8
8
  /**
9
9
  * checks whether code is being run by jest
10
10
  */
@@ -172,6 +172,9 @@ export default class extends Component {
172
172
  var sequence_ids = $('.hit-links :checkbox:checked').map(function () {
173
173
  return this.value;
174
174
  }).get();
175
+ if (sequence_ids.length === 0) {
176
+ return false;
177
+ }
175
178
  var database_ids = _.map(this.props.data.querydb, _.iteratee('id'));
176
179
  downloadFASTA(sequence_ids, database_ids);
177
180
  return false;
@@ -347,6 +350,7 @@ export default class extends Component {
347
350
  </a>
348
351
  </li>
349
352
  }
353
+ <DownloadLinks imported_xml={this.props.data.imported_xml} search_id={this.props.data.search_id} />
350
354
  </ul>
351
355
  </div>
352
356
  );
@@ -406,6 +410,12 @@ export default class extends Component {
406
410
  {this.topPanelJSX()}
407
411
  {this.downloadsPanelJSX()}
408
412
  {this.sharingPanelJSX()}
413
+ <div className="referral-panel">
414
+ <div className="section-header-sidebar">
415
+ <h4>Recommend SequenceServer</h4>
416
+ <p><a href="https://sequenceserver.com/referral-program" target="_blank">Earn up to $100 per signup</a></p>
417
+ </div>
418
+ </div>
409
419
  </div>
410
420
  );
411
421
  }
@@ -29,4 +29,21 @@ GAGATGGAAATGGCCGATTACCCGCTCGCCTATGATATTTCCCCGTATCTTCCGCCGTTC
29
29
  CTGTCGCGAGCGAGGGCACGGGGAATGTTAGACGGTCGCTTCGCCGGCAGACGCTACCGA
30
30
  AGGGAGTCGCGGGGCATTCACGAGGAGTGTTGCATCAACGGATGTACGATAAACGAATTG
31
31
  ACCAGCTACTGCGGCCCC
32
- `;
32
+ `;
33
+
34
+ export const FASTQ_SEQUENCE =
35
+ `@SRR001666.1 071112_SLXA-EAS1_s_7:5:1:817:345 length=72
36
+ GGGTGATGGCCGCTGCCGATGGCGTCAAATCCCACCAAGTTACCCTTAACAACTTAAGGGTTTTCAAATAGA
37
+ +SRR001666.1 071112_SLXA-EAS1_s_7:5:1:817:345 length=72
38
+ IIIIIIIIIIIIIIIIIIIIIIIIIIIIII9IG9ICIIIIIIIIIIIIIIIIIIIIDIIIIIII>IIIIII/
39
+ @SRR001666.2 071112_SLXA-EAS1_s_7:5:1:801:338 length=72
40
+ GTTCAGGGATACGACGTTTGTATTTTAAGAATCTGAAGCAGAAGTCGATGATAATACGCGTCGTTTTATCAT
41
+ +SRR001666.2 071112_SLXA-EAS1_s_7:5:1:801:338 length=72
42
+ IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII6IBIIIIIIIIIIIIIIIIIIIIIIIGII>IIIII-I)8I
43
+ `;
44
+
45
+ export const FASTA_OF_FASTQ_SEQUENCE =
46
+ `>SRR001666.1 071112_SLXA-EAS1_s_7:5:1:817:345 length=72
47
+ GGGTGATGGCCGCTGCCGATGGCGTCAAATCCCACCAAGTTACCCTTAACAACTTAAGGGTTTTCAAATAGA
48
+ >SRR001666.2 071112_SLXA-EAS1_s_7:5:1:801:338 length=72
49
+ GTTCAGGGATACGACGTTTGTATTTTAAGAATCTGAAGCAGAAGTCGATGATAATACGCGTCGTTTTATCAT`;
@@ -3,7 +3,7 @@
3
3
  import { render, screen, fireEvent } from '@testing-library/react';
4
4
  import { SearchQueryWidget } from '../query';
5
5
  import { Form } from '../form';
6
- import { AMINO_ACID_SEQUENCE, NUCLEOTIDE_SEQUENCE } from './mock_data/sequences';
6
+ import { AMINO_ACID_SEQUENCE, NUCLEOTIDE_SEQUENCE, FASTQ_SEQUENCE, FASTA_OF_FASTQ_SEQUENCE } from './mock_data/sequences';
7
7
  import '@testing-library/jest-dom/extend-expect';
8
8
  import '@testing-library/react/dont-cleanup-after-each';
9
9
 
@@ -16,7 +16,7 @@ describe('SEARCH COMPONENT', () => {
16
16
  } />).container;
17
17
  inputEl = screen.getByRole('textbox', { name: '' });
18
18
  });
19
-
19
+
20
20
  test('should render the search component textarea', () => {
21
21
  expect(inputEl).toHaveClass('form-control');
22
22
  });
@@ -47,7 +47,7 @@ describe('SEARCH COMPONENT', () => {
47
47
  expect(activeNotification.id).toBe('nucleotide-sequence-notification');
48
48
  expect(alertWrapper).toHaveTextContent('Detected: nucleotide sequence(s).');
49
49
  });
50
-
50
+
51
51
  test('should correctly detect the mixed sequences and show error notification', () => {
52
52
  fireEvent.change(inputEl, { target: { value: `${NUCLEOTIDE_SEQUENCE}${AMINO_ACID_SEQUENCE}` } });
53
53
  const activeNotification = container.querySelector('.notification.active');
@@ -55,4 +55,13 @@ describe('SEARCH COMPONENT', () => {
55
55
  const alertWrapper = activeNotification.children[0];
56
56
  expect(alertWrapper).toHaveTextContent('Error: mixed nucleotide and amino-acid sequences detected.');
57
57
  });
58
+
59
+ test('should correctly detect FASTQ and convert it to FASTA', () => {
60
+ fireEvent.change(inputEl, { target: { value: FASTQ_SEQUENCE } });
61
+ const activeNotification = container.querySelector('.notification.active');
62
+ const alertWrapper = activeNotification.children[0];
63
+ expect(activeNotification.id).toBe('fastq-sequence-notification');
64
+ expect(alertWrapper).toHaveTextContent('Detected FASTQ and automatically converted to FASTA.');
65
+ expect(inputEl).toHaveValue(FASTA_OF_FASTQ_SEQUENCE);
66
+ });
58
67
  });