sequenceserver 2.2.0 → 3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/COPYRIGHT.txt +1 -1
- data/bin/sequenceserver +4 -2
- data/lib/sequenceserver/blast/error.rb +53 -0
- data/lib/sequenceserver/blast/job.rb +2 -43
- data/lib/sequenceserver/job.rb +21 -11
- data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +345 -0
- data/lib/sequenceserver/makeblastdb.rb +26 -12
- data/lib/sequenceserver/routes.rb +29 -3
- data/lib/sequenceserver/server.rb +1 -1
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +3 -0
- data/public/404.html +27 -0
- data/public/config.js +0 -6
- data/public/css/grapher.css +1 -1
- data/public/css/sequenceserver.css +22 -11
- data/public/css/sequenceserver.min.css +2 -2
- data/public/js/circos.js +7 -3
- data/public/js/dnd.js +3 -3
- data/public/js/fastq_to_fasta.js +35 -0
- data/public/js/form.js +30 -11
- data/public/js/grapher.js +123 -113
- data/public/js/hit.js +8 -2
- data/public/js/hits_overview.js +4 -1
- data/public/js/jquery_world.js +0 -1
- data/public/js/kablammo.js +4 -0
- data/public/js/length_distribution.js +5 -1
- data/public/js/null_plugins/download_links.js +7 -0
- data/public/js/null_plugins/hit_buttons.js +11 -0
- data/public/js/null_plugins/report_plugins.js +18 -0
- data/public/js/query.js +26 -6
- data/public/js/report.js +33 -17
- data/public/js/search.js +0 -8
- data/public/js/sidebar.js +11 -1
- data/public/js/tests/mock_data/sequences.js +18 -1
- data/public/js/tests/search_query.spec.js +12 -3
- data/public/sequenceserver-report.min.js +76 -42
- data/public/sequenceserver-search.min.js +34 -33
- data/views/layout.erb +9 -12
- 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(
|
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
|
-
|
75
|
+
callback(jqXHR.responseJSON);
|
71
76
|
break;
|
72
|
-
case 404:
|
73
77
|
case 400:
|
78
|
+
case 422:
|
74
79
|
case 500:
|
75
|
-
|
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
|
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.
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
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
|
});
|