sequenceserver 2.2.0 → 3.0.1

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/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