sequenceserver 2.2.0 → 3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sequenceserver might be problematic. Click here for more details.

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/circos.js CHANGED
@@ -1,14 +1,18 @@
1
1
  import d3 from 'd3';
2
2
  import Circos from '../packages/circosJS@1.7.0';
3
- import _ from 'underscore';
3
+ import _ from 'underscore';
4
4
 
5
5
  import Grapher from './grapher';
6
6
  import * as Helpers from './visualisation_helpers';
7
7
  import Utils from './utils';
8
8
 
9
9
  class Graph {
10
+ static canCollapse() {
11
+ return true;
12
+ }
13
+
10
14
  static name() {
11
- return 'Queries and their top hits: chord diagram';
15
+ return 'Chord diagram of queries and their top hits';
12
16
  }
13
17
 
14
18
  static className() {
@@ -408,7 +412,7 @@ class Graph {
408
412
  .attr('dy', '-0.25em')
409
413
  .attr('x', -175)
410
414
  .style('font-size', '14px')
411
- .text('Circos looks great with less than 16 queries');
415
+ .text('Chord diagram looks great with fewer than 16 queries');
412
416
  }
413
417
 
414
418
  layoutReset() {
data/public/js/dnd.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import React, { Component } from 'react';
2
2
 
3
- /**
3
+ /**
4
4
  * Drag n drop widget.
5
5
  */
6
6
  export class DnD extends Component {
@@ -74,7 +74,7 @@ export class DnD extends Component {
74
74
  }
75
75
 
76
76
  var file = files[0];
77
- if (file.size > 10 * 1048576) {
77
+ if (file.size > 250 * 1048576) {
78
78
  dndError('dnd-large-file');
79
79
  return;
80
80
  }
@@ -141,7 +141,7 @@ export class DnD extends Component {
141
141
  style={{ display: 'none' }}>
142
142
  <div
143
143
  className="col-md-6 col-md-offset-3">
144
- Too big a file. Can only do less than 10 MB. &gt;_&lt;
144
+ Too big a file. Can only do less than 250 MB. &gt;_&lt;
145
145
  </div>
146
146
  </div>
147
147
 
@@ -0,0 +1,35 @@
1
+ const convertChunk = (fastqChunk) => {
2
+ fastqChunk[0] = '>' + fastqChunk[0].substring(1);
3
+ return fastqChunk.slice(0, 2);
4
+ };
5
+
6
+ const isValidFastq = (fastqChunk) => {
7
+ if (fastqChunk.length !== 4) {
8
+ return false;
9
+ }
10
+
11
+ return fastqChunk[0][0] === '@' && fastqChunk[2][0] === '+' && fastqChunk[1].length === fastqChunk[3].length;
12
+ };
13
+
14
+ export const fastqToFasta = (sequence) => {
15
+ let trimmedSequence = sequence.trim();
16
+ // return unmodified if sequence does not look like fastq
17
+ if (!trimmedSequence.startsWith('@')) {
18
+ return sequence;
19
+ }
20
+
21
+ const sequenceLines = trimmedSequence.split('\n');
22
+ const fastaChunks = [];
23
+
24
+ for (let i = 0; i < sequenceLines.length; i += 4) {
25
+ const fastqChunk = sequenceLines.slice(i, i + 4);
26
+ if (isValidFastq(fastqChunk)) {
27
+ fastaChunks.push(...convertChunk(fastqChunk));
28
+ } else {
29
+ // return unmodified sequence if it does not look like valid fastq
30
+ return sequence;
31
+ }
32
+ }
33
+
34
+ return fastaChunks.join('\n');
35
+ };
data/public/js/form.js CHANGED
@@ -21,14 +21,14 @@ export class Form extends Component {
21
21
  this.useTreeWidget = this.useTreeWidget.bind(this);
22
22
  this.determineBlastMethod = this.determineBlastMethod.bind(this);
23
23
  this.handleSequenceTypeChanged = this.handleSequenceTypeChanged.bind(this);
24
- this.handleDatabaseTypeChanaged = this.handleDatabaseTypeChanaged.bind(this);
24
+ this.handleDatabaseTypeChanged = this.handleDatabaseTypeChanged.bind(this);
25
25
  this.handleAlgoChanged = this.handleAlgoChanged.bind(this);
26
26
  this.handleFormSubmission = this.handleFormSubmission.bind(this);
27
27
  this.formRef = createRef();
28
28
  }
29
29
 
30
30
  componentDidMount() {
31
- /**
31
+ /**
32
32
  * Fetch data to initialise the search interface from the server. These
33
33
  * include list of databases to search against, advanced options to
34
34
  * apply when an algorithm is selected, and a query sequence that
@@ -71,7 +71,7 @@ export class Form extends Component {
71
71
  }
72
72
  });
73
73
 
74
- // show overlay to create visual feedback on button click
74
+ // show overlay to create visual feedback on button click
75
75
  $('#method').on('click', () => {
76
76
  $('#overlay').css('display', 'block');
77
77
  });
@@ -102,6 +102,7 @@ export class Form extends Component {
102
102
  }
103
103
  });
104
104
  }
105
+
105
106
  determineBlastMethod() {
106
107
  var database_type = this.databaseType;
107
108
  var sequence_type = this.sequenceType;
@@ -146,7 +147,7 @@ export class Form extends Component {
146
147
  });
147
148
  }
148
149
 
149
- handleDatabaseTypeChanaged(type) {
150
+ handleDatabaseTypeChanged(type) {
150
151
  this.databaseType = type;
151
152
  this.refs.button.setState({
152
153
  hasQuery: !this.refs.query.isEmpty(),
@@ -174,24 +175,27 @@ export class Form extends Component {
174
175
  return (
175
176
  <div className="container">
176
177
  <div id="overlay" style={{ position: 'absolute', top: 0, left: 0, width: '100vw', height: '100vw', background: 'rgba(0, 0, 0, 0.2)', display: 'none', zIndex: 99 }} />
178
+
179
+ <div className="notifications" id="notifications">
180
+ <FastqNotification />
181
+ <NucleotideNotification />
182
+ <ProteinNotification />
183
+ <MixedNotification />
184
+ </div>
185
+
177
186
  <form id="blast" ref={this.formRef} onSubmit={this.handleFormSubmission} className="form-horizontal">
178
187
  <div className="form-group query-container">
179
188
  <SearchQueryWidget ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged} />
180
189
  </div>
181
- <div className="notifications" id="notifications">
182
- <NucleotideNotification />
183
- <ProteinNotification />
184
- <MixedNotification />
185
- </div>
186
190
  {this.useTreeWidget() ?
187
191
  <DatabasesTree ref="databases"
188
192
  databases={this.state.databases} tree={this.state.tree}
189
193
  preSelectedDbs={this.state.preSelectedDbs}
190
- onDatabaseTypeChanged={this.handleDatabaseTypeChanaged} />
194
+ onDatabaseTypeChanged={this.handleDatabaseTypeChanged} />
191
195
  :
192
196
  <Databases ref="databases" databases={this.state.databases}
193
197
  preSelectedDbs={this.state.preSelectedDbs}
194
- onDatabaseTypeChanged={this.handleDatabaseTypeChanaged} />
198
+ onDatabaseTypeChanged={this.handleDatabaseTypeChanged} />
195
199
  }
196
200
  <div className="form-group">
197
201
  <Options ref="opts" />
@@ -243,6 +247,21 @@ class NucleotideNotification extends Component {
243
247
  }
244
248
  }
245
249
 
250
+ class FastqNotification extends Component {
251
+ render() {
252
+ return (<div
253
+ className="notification row"
254
+ id="fastq-sequence-notification"
255
+ style={{ display: 'none' }}>
256
+ <div
257
+ className="alert-info col-md-6 col-md-offset-3">
258
+ Detected FASTQ and automatically converted to FASTA.
259
+ </div>
260
+ </div>
261
+ );
262
+ }
263
+ }
264
+
246
265
  class MixedNotification extends Component {
247
266
  render() {
248
267
  return (
data/public/js/grapher.js CHANGED
@@ -1,7 +1,7 @@
1
- import _ from "underscore";
2
- import React, { createRef } from "react";
1
+ import _ from 'underscore';
2
+ import React, { createRef } from 'react';
3
3
 
4
- import "./svgExporter"; // create handlers for SVG and PNG download buttons
4
+ import './svgExporter'; // create handlers for SVG and PNG download buttons
5
5
 
6
6
  // Each instance of Grapher is added to this object once the component has been
7
7
  // mounted. This is so that grapher can be iterated over and redrawn on window
@@ -13,123 +13,133 @@ var Graphers = {};
13
13
  // graphs collapsible, to redraw graphs when window is resized, and SVG and PNG
14
14
  // export buttons and functionality.
15
15
  export default function Grapher(Graph) {
16
- return class extends React.Component {
17
- constructor(props) {
18
- super(props);
19
- this.state = { collapsed: this.props.collapsed };
20
- this.svgContainerRef = createRef();
21
- }
22
-
23
- collapseId() {
24
- return Graph.collapseId(this.props);
25
- }
26
-
27
- render() {
28
- var cssClasses = Graph.className() + " grapher";
29
- return (
30
- <div ref="grapher" className={cssClasses}>
31
- <div className="grapher-header">
32
- <h4
33
- className="caption"
34
- data-toggle="collapse"
35
- data-target={"#" + this.collapseId()}
36
- >
37
- {this.state.collapsed ? this.plusIcon() : this.minusIcon()}
16
+ return class extends React.Component {
17
+ constructor(props) {
18
+ super(props);
19
+ this.state = { collapsed: Graph.canCollapse() && this.props.collapsed };
20
+ this.svgContainerRef = createRef();
21
+ }
22
+
23
+ collapseId() {
24
+ return Graph.collapseId(this.props);
25
+ }
26
+
27
+ render() {
28
+ var cssClasses = Graph.className() + ' grapher';
29
+ return (
30
+ <div ref="grapher" className={cssClasses}>
31
+ {this.header()}
32
+ {this.svgContainerJSX()}
33
+ </div>
34
+ );
35
+ }
36
+
37
+ header() {
38
+ if(Graph.canCollapse()) {
39
+ return <div className="grapher-header">
40
+ <h4
41
+ className="caption"
42
+ data-toggle="collapse"
43
+ data-target={'#' + this.collapseId()}
44
+ >
45
+ {this.state.collapsed ? this.plusIcon() : this.minusIcon()}
38
46
  &nbsp;{Graph.name()}
39
- </h4>
40
- {!this.state.collapsed && this.graphLinksJSX()}
41
- </div>
42
- {this.svgContainerJSX()}
43
- </div>
44
- );
45
- }
46
-
47
- minusIcon() {
48
- return <i className="fa fa-minus-square-o"></i>;
49
- }
50
-
51
- plusIcon() {
52
- return <i className="fa fa-plus-square-o"></i>;
53
- }
54
-
55
- graphLinksJSX() {
56
- return (
57
- <div className="hit-links graph-links">
58
- <a href="#" className="btn-link export-to-svg">
59
- <i className="fa fa-download" /> SVG
60
- </a>
61
- <span className="line">|</span>
62
- <a href="#" className="btn-link export-to-png">
63
- <i className="fa fa-download" /> PNG
64
- </a>
65
- </div>
66
- );
67
- }
68
-
69
- svgContainerJSX() {
70
- var cssClasses = Graph.className() + " svg-container collapse";
71
- if (!this.state.collapsed) cssClasses += " in";
72
- return (
73
- <div
74
- ref={this.svgContainerRef}
75
- id={this.collapseId()}
76
- className={cssClasses}
77
- ></div>
78
- );
79
- }
80
-
81
- componentDidMount() {
82
- Graphers[this.collapseId()] = this;
83
-
84
- // Draw visualisation for the first time. Visualisations are
85
- // redrawn when browser window is resized.
86
- this.draw();
87
- }
88
-
89
- componentDidUpdate() {
90
- // Re-draw visualisation when the component change state.
91
- this.draw();
92
- }
93
- svgContainer() {
94
- return $(this.svgContainerRef.current);
95
- }
96
-
97
- draw() {
98
- // Clean slate.
99
- this.svgContainer().empty();
100
- this.graph = null;
101
-
102
- // Draw if uncollapsed.
103
- if (this.state.collapsed) {
104
- return;
105
- }
106
- this.graph = new Graph(this.svgContainer(), this.props);
107
- this.svgContainer()
108
- .find("svg")
109
- .attr("data-name", Graph.dataName(this.props));
110
- }
111
- };
47
+ </h4>
48
+ {!this.state.collapsed && this.graphLinksJSX()}
49
+ </div>;
50
+ } else {
51
+ return <div className="grapher-header">
52
+ {!this.state.collapsed && this.graphLinksJSX()}
53
+ </div>;
54
+ }
55
+ }
56
+
57
+ minusIcon() {
58
+ return <i className="fa fa-minus-square-o"></i>;
59
+ }
60
+
61
+ plusIcon() {
62
+ return <i className="fa fa-plus-square-o"></i>;
63
+ }
64
+
65
+ graphLinksJSX() {
66
+ return (
67
+ <div className="hit-links graph-links">
68
+ <a href="#" className="btn-link export-to-svg">
69
+ <i className="fa fa-download" /> SVG
70
+ </a>
71
+ <span className="line">|</span>
72
+ <a href="#" className="btn-link export-to-png">
73
+ <i className="fa fa-download" /> PNG
74
+ </a>
75
+ </div>
76
+ );
77
+ }
78
+
79
+ svgContainerJSX() {
80
+ var cssClasses = Graph.className() + ' svg-container collapse';
81
+ if (!this.state.collapsed) cssClasses += ' in';
82
+ return (
83
+ <div
84
+ ref={this.svgContainerRef}
85
+ id={this.collapseId()}
86
+ className={cssClasses}
87
+ ></div>
88
+ );
89
+ }
90
+
91
+ componentDidMount() {
92
+ Graphers[this.collapseId()] = this;
93
+
94
+ // Draw visualisation for the first time. Visualisations are
95
+ // redrawn when browser window is resized.
96
+ this.draw();
97
+ }
98
+
99
+ componentDidUpdate() {
100
+ // Re-draw visualisation when the component change state.
101
+ this.draw();
102
+ }
103
+ svgContainer() {
104
+ return $(this.svgContainerRef.current);
105
+ }
106
+
107
+ draw() {
108
+ // Clean slate.
109
+ this.svgContainer().empty();
110
+ this.graph = null;
111
+
112
+ // Draw if uncollapsed.
113
+ if (this.state.collapsed) {
114
+ return;
115
+ }
116
+ this.graph = new Graph(this.svgContainer(), this.props);
117
+ this.svgContainer()
118
+ .find('svg')
119
+ .attr('data-name', Graph.dataName(this.props));
120
+ }
121
+ };
112
122
  }
113
123
 
114
124
  // Redraw if window resized.
115
125
  $(window).resize(
116
- _.debounce(function () {
117
- _.each(Graphers, (grapher) => {
118
- grapher.draw();
119
- });
120
- }, 125)
126
+ _.debounce(function () {
127
+ _.each(Graphers, (grapher) => {
128
+ grapher.draw();
129
+ });
130
+ }, 125)
121
131
  );
122
132
 
123
133
  // Swap-icon and toggle .graph-links on collapse.
124
- $("body").on("hidden.bs.collapse", ".collapse", function () {
125
- var component = Graphers[$(this).attr("id")];
126
- if (component) {
127
- component.setState({ collapsed: true });
128
- }
134
+ $('body').on('hidden.bs.collapse', '.collapse', function () {
135
+ var component = Graphers[$(this).attr('id')];
136
+ if (component) {
137
+ component.setState({ collapsed: true });
138
+ }
129
139
  });
130
- $("body").on("shown.bs.collapse", ".collapse", function () {
131
- var component = Graphers[$(this).attr("id")];
132
- if (component) {
133
- component.setState({ collapsed: false });
134
- }
140
+ $('body').on('shown.bs.collapse', '.collapse', function () {
141
+ var component = Graphers[$(this).attr('id')];
142
+ if (component) {
143
+ component.setState({ collapsed: false });
144
+ }
135
145
  });
data/public/js/hit.js CHANGED
@@ -4,6 +4,7 @@ import _ from 'underscore';
4
4
  import HSPOverview from './kablammo';
5
5
  import downloadFASTA from './download_fasta';
6
6
  import AlignmentExporter from './alignment_exporter'; // to download textual alignment
7
+ import HitButtons from 'hit_buttons';
7
8
 
8
9
  /**
9
10
  * Component for each hit. Receives props from Report. Has no state.
@@ -26,13 +27,14 @@ export default class extends Component {
26
27
  this.hitLinks = this.hitLinks.bind(this);
27
28
  this.viewSequenceButton = this.viewSequenceButton.bind(this);
28
29
  this.downloadFASTAButton = this.downloadFASTAButton.bind(this);
30
+ this.hit_buttons = new HitButtons(this);
29
31
  }
30
32
  shouldComponentUpdate() {
31
33
  return !this.props.hit;
32
34
  }
33
35
  /**
34
- * Returns accession number of the hit sequence.
35
- */
36
+ * Returns accession number of the hit sequence.
37
+ */
36
38
  accession() {
37
39
  return this.props.hit.accession;
38
40
  }
@@ -142,6 +144,10 @@ export default class extends Component {
142
144
  }
143
145
  btns.push(this.downloadAlignmentButton());
144
146
 
147
+ this.hit_buttons.buttons().forEach((button) => {
148
+ btns.push(button);
149
+ });
150
+
145
151
  return (
146
152
  <div className="hit-links">
147
153
  <label>
@@ -5,9 +5,12 @@ import * as Helpers from './visualisation_helpers';
5
5
  import Utils from './utils';
6
6
 
7
7
  class Graph {
8
+ static canCollapse() {
9
+ return true;
10
+ }
8
11
 
9
12
  static name() {
10
- return 'Graphical overview of hits';
13
+ return 'Graphical overview of aligning hit sequences to the query';
11
14
  }
12
15
 
13
16
  static className() {
@@ -1,7 +1,6 @@
1
1
  import $ from 'jquery';
2
2
  import '../packages/jquery-ui@1.11.4';
3
3
  import 'bootstrap';
4
- import 'webshim';
5
4
 
6
5
  global.$ = $;
7
6
  /**
@@ -18,6 +18,10 @@ import * as Helpers from './visualisation_helpers';
18
18
  */
19
19
 
20
20
  class Graph {
21
+ static canCollapse() {
22
+ return true;
23
+ }
24
+
21
25
  static name() {
22
26
  return 'Graphical overview of aligning region(s)';
23
27
  }
@@ -8,8 +8,12 @@ import * as Helpers from './visualisation_helpers';
8
8
  */
9
9
 
10
10
  class Graph {
11
+ static canCollapse() {
12
+ return true;
13
+ }
14
+
11
15
  static name() {
12
- return 'Length distribution of matching sequences';
16
+ return 'Length distribution of matching hit sequences';
13
17
  }
14
18
 
15
19
  static className() {
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ class DownloadLinks extends React.Component{
4
+ render () {}
5
+ }
6
+
7
+ export default DownloadLinks;
@@ -0,0 +1,11 @@
1
+ class HitButtons {
2
+ constructor(hit) {
3
+ this.hit = hit;
4
+ }
5
+
6
+ buttons() {
7
+ return [];
8
+ }
9
+ }
10
+
11
+ export default HitButtons;
@@ -0,0 +1,18 @@
1
+
2
+ class ReportPlugins {
3
+ constructor(parent) {
4
+ this.parent = parent;
5
+ }
6
+
7
+ init() {
8
+ }
9
+
10
+ componentDidUpdate(_prevProps, _prevState) {
11
+ }
12
+
13
+ queryResults(_query) {
14
+ return [];
15
+ }
16
+ }
17
+
18
+ export default ReportPlugins;
data/public/js/query.js CHANGED
@@ -4,6 +4,7 @@ import _ from 'underscore';
4
4
  import HitsOverview from './hits_overview';
5
5
  import LengthDistribution from './length_distribution'; // length distribution of hits
6
6
  import Utils from './utils';
7
+ import { fastqToFasta } from './fastq_to_fasta';
7
8
 
8
9
  /**
9
10
  * Query component displays query defline, graphical overview, length
@@ -65,9 +66,10 @@ export class ReportQuery extends Component {
65
66
 
66
67
  noHitsJSX() {
67
68
  return <div className="section-content">
68
- <strong> ****** No hits found ****** </strong>
69
+ <strong> ****** No BLAST hits found ****** </strong>
69
70
  </div>;
70
71
  }
72
+
71
73
  render() {
72
74
  return (
73
75
  <div className="resultn" id={this.domID()}
@@ -102,6 +104,7 @@ export class SearchQueryWidget extends Component {
102
104
  this.indicateNormal = this.indicateNormal.bind(this);
103
105
  this.type = this.type.bind(this);
104
106
  this.guessSequenceType = this.guessSequenceType.bind(this);
107
+ this.preProcessSequence = this.preProcessSequence.bind(this);
105
108
  this.notify = this.notify.bind(this);
106
109
 
107
110
  this.textareaRef = createRef()
@@ -119,6 +122,8 @@ export class SearchQueryWidget extends Component {
119
122
 
120
123
  componentDidUpdate() {
121
124
  this.hideShowButton();
125
+ this.preProcessSequence();
126
+
122
127
  var type = this.type();
123
128
  if (!type || type !== this._type) {
124
129
  this._type = type;
@@ -131,7 +136,7 @@ export class SearchQueryWidget extends Component {
131
136
 
132
137
  /**
133
138
  * Returns query sequence if no argument is provided (or null or undefined
134
- * is provided as argument). Otherwise, sets query sequenced to the given
139
+ * is provided as argument). Otherwise, sets query sequence to the given
135
140
  * value and returns `this`.
136
141
  *
137
142
  * Default/initial state of query sequence is an empty string. Caller must
@@ -239,7 +244,12 @@ export class SearchQueryWidget extends Component {
239
244
  * of directly calling this method.
240
245
  */
241
246
  type() {
242
- var sequences = this.value().split(/>.*/);
247
+ let sequence = this.value().trim();
248
+ // FASTQ detected, but we don't know if conversion has succeeded yet
249
+ // will notify separately if it does
250
+ if (sequence.startsWith('@') ) { return undefined; }
251
+
252
+ var sequences = sequence.split(/>.*/);
243
253
 
244
254
  var type, tmp;
245
255
 
@@ -262,6 +272,16 @@ export class SearchQueryWidget extends Component {
262
272
  return type;
263
273
  }
264
274
 
275
+ preProcessSequence() {
276
+ var sequence = this.value();
277
+ var updatedSequence = fastqToFasta(sequence);
278
+
279
+ if (sequence !== updatedSequence) {
280
+ this.value(updatedSequence);
281
+ this.notify('fastq');
282
+ }
283
+ }
284
+
265
285
  /**
266
286
  * Guesses and returns the type of the given sequence (nucleotide,
267
287
  * protein).
@@ -289,9 +309,9 @@ export class SearchQueryWidget extends Component {
289
309
  }
290
310
 
291
311
  notify(type) {
292
- clearTimeout(this.notification_timeout);
293
312
  this.indicateNormal();
294
- $('.notifications .active').hide().removeClass('active');
313
+ clearTimeout(this.notification_timeout);
314
+ // $('.notifications .active').hide().removeClass('active');
295
315
 
296
316
  if (type) {
297
317
  $('#' + type + '-sequence-notification').show('drop', { direction: 'up' }).addClass('active');
@@ -369,7 +389,7 @@ class HitsTable extends Component {
369
389
  <div className="table-hit-overview">
370
390
  <h4 className="caption" data-toggle="collapse" data-target={'#Query_' + this.props.query.number + 'HT_' + this.props.query.number}>
371
391
  <i className="fa fa-minus-square-o"></i>&nbsp;
372
- <span>Sequences producing significant alignments</span>
392
+ <span>Hit sequences producing significant alignments</span>
373
393
  </h4>
374
394
  <div className="collapsed in" id={'Query_' + this.props.query.number + 'HT_' + this.props.query.number}>
375
395
  <table