sequenceserver 3.0.1 → 3.1.1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequenceserver +2 -2
  3. data/lib/sequenceserver/api_errors.rb +32 -2
  4. data/lib/sequenceserver/blast/job.rb +20 -3
  5. data/lib/sequenceserver/blast/report.rb +74 -86
  6. data/lib/sequenceserver/blast/tasks.rb +38 -0
  7. data/lib/sequenceserver/config.rb +54 -20
  8. data/lib/sequenceserver/makeblastdb.rb +16 -2
  9. data/lib/sequenceserver/report.rb +0 -6
  10. data/lib/sequenceserver/routes.rb +66 -25
  11. data/lib/sequenceserver/sequence.rb +35 -7
  12. data/lib/sequenceserver/server.rb +1 -1
  13. data/lib/sequenceserver/version.rb +1 -1
  14. data/lib/sequenceserver.rb +1 -1
  15. data/public/404.html +1 -1
  16. data/public/css/app.css +121 -0
  17. data/public/css/app.min.css +1 -0
  18. data/public/css/sequenceserver.css +0 -148
  19. data/public/css/sequenceserver.min.css +3 -3
  20. data/public/js/circos.js +2 -2
  21. data/public/js/collapse_preferences.js +37 -0
  22. data/public/js/databases.js +65 -37
  23. data/public/js/databases_tree.js +2 -1
  24. data/public/js/dnd.js +37 -50
  25. data/public/js/download_fasta.js +1 -0
  26. data/public/js/form.js +79 -50
  27. data/public/js/grapher.js +23 -37
  28. data/public/js/hits_overview.js +2 -2
  29. data/public/js/kablammo.js +2 -2
  30. data/public/js/length_distribution.js +3 -3
  31. data/public/js/null_plugins/grapher/histogram.js +25 -0
  32. data/public/js/null_plugins/options.js +3 -0
  33. data/public/js/null_plugins/query_stats.js +11 -0
  34. data/public/js/null_plugins/report_plugins.js +6 -1
  35. data/public/js/null_plugins/search_header_plugin.js +4 -0
  36. data/public/js/options.js +161 -56
  37. data/public/js/query.js +85 -59
  38. data/public/js/report.js +1 -1
  39. data/public/js/search.js +2 -0
  40. data/public/js/search_button.js +67 -56
  41. data/public/js/sidebar.js +10 -1
  42. data/public/js/tests/database.spec.js +5 -5
  43. data/public/js/tests/form.spec.js +98 -0
  44. data/public/js/tests/mock_data/databases.json +5 -5
  45. data/public/js/tests/mocks/circos.js +6 -0
  46. data/public/js/tests/report.spec.js +4 -3
  47. data/public/js/tests/search_query.spec.js +16 -6
  48. data/public/sequenceserver-report.min.js +46 -24
  49. data/public/sequenceserver-search.min.js +57 -13
  50. data/public/sequenceserver_logo.webp +0 -0
  51. data/views/blastn_options.erb +66 -66
  52. data/views/blastp_options.erb +59 -59
  53. data/views/blastx_options.erb +68 -68
  54. data/views/layout.erb +61 -3
  55. data/views/search.erb +33 -38
  56. data/views/search_layout.erb +153 -0
  57. data/views/tblastn_options.erb +57 -57
  58. data/views/tblastx_options.erb +64 -64
  59. metadata +51 -22
  60. data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +0 -345
  61. data/public/SequenceServer_logo.png +0 -0
  62. data/public/js/tests/advanced_parameters.spec.js +0 -36
data/public/js/grapher.js CHANGED
@@ -2,6 +2,7 @@ import _ from 'underscore';
2
2
  import React, { createRef } from 'react';
3
3
 
4
4
  import './svgExporter'; // create handlers for SVG and PNG download buttons
5
+ import CollapsePreferences from './collapse_preferences';
5
6
 
6
7
  // Each instance of Grapher is added to this object once the component has been
7
8
  // mounted. This is so that grapher can be iterated over and redrawn on window
@@ -16,22 +17,30 @@ export default function Grapher(Graph) {
16
17
  return class extends React.Component {
17
18
  constructor(props) {
18
19
  super(props);
19
- this.state = { collapsed: Graph.canCollapse() && this.props.collapsed };
20
+ this.name = Graph.name();
21
+ this.collapsePreferences = new CollapsePreferences(this);
22
+ let isCollapsed = this.collapsePreferences.preferenceStoredAsCollapsed();
23
+ this.state = { collapsed: Graph.canCollapse() && (this.props.collapsed || isCollapsed) };
20
24
  this.svgContainerRef = createRef();
21
25
  }
22
26
 
23
- collapseId() {
24
- return Graph.collapseId(this.props);
27
+ graphId() {
28
+ return Graph.graphId(this.props);
25
29
  }
26
30
 
27
31
  render() {
28
- var cssClasses = Graph.className() + ' grapher';
29
- return (
30
- <div ref="grapher" className={cssClasses}>
31
- {this.header()}
32
- {this.svgContainerJSX()}
33
- </div>
34
- );
32
+ // Do not render when Graph.name() is null
33
+ if (Graph.name() === null) {
34
+ return null;
35
+ } else {
36
+ var cssClasses = Graph.className() + ' grapher';
37
+ return (
38
+ <div ref="grapher" className={cssClasses}>
39
+ {this.header()}
40
+ {this.svgContainerJSX()}
41
+ </div>
42
+ );
43
+ }
35
44
  }
36
45
 
37
46
  header() {
@@ -39,10 +48,9 @@ export default function Grapher(Graph) {
39
48
  return <div className="grapher-header">
40
49
  <h4
41
50
  className="caption"
42
- data-toggle="collapse"
43
- data-target={'#' + this.collapseId()}
51
+ onClick={() => this.collapsePreferences.toggleCollapse()}
44
52
  >
45
- {this.state.collapsed ? this.plusIcon() : this.minusIcon()}
53
+ {this.collapsePreferences.renderCollapseIcon()}
46
54
  &nbsp;{Graph.name()}
47
55
  </h4>
48
56
  {!this.state.collapsed && this.graphLinksJSX()}
@@ -54,14 +62,6 @@ export default function Grapher(Graph) {
54
62
  }
55
63
  }
56
64
 
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
65
  graphLinksJSX() {
66
66
  return (
67
67
  <div className="hit-links graph-links">
@@ -82,14 +82,14 @@ export default function Grapher(Graph) {
82
82
  return (
83
83
  <div
84
84
  ref={this.svgContainerRef}
85
- id={this.collapseId()}
85
+ id={this.graphId()}
86
86
  className={cssClasses}
87
87
  ></div>
88
88
  );
89
89
  }
90
90
 
91
91
  componentDidMount() {
92
- Graphers[this.collapseId()] = this;
92
+ Graphers[this.graphId()] = this;
93
93
 
94
94
  // Draw visualisation for the first time. Visualisations are
95
95
  // redrawn when browser window is resized.
@@ -129,17 +129,3 @@ $(window).resize(
129
129
  });
130
130
  }, 125)
131
131
  );
132
-
133
- // Swap-icon and toggle .graph-links on collapse.
134
- $('body').on('hidden.bs.collapse', '.collapse', function () {
135
- var component = Graphers[$(this).attr('id')];
136
- if (component) {
137
- component.setState({ collapsed: true });
138
- }
139
- });
140
- $('body').on('shown.bs.collapse', '.collapse', function () {
141
- var component = Graphers[$(this).attr('id')];
142
- if (component) {
143
- component.setState({ collapsed: false });
144
- }
145
- });
@@ -1,6 +1,6 @@
1
1
  import d3 from 'd3';
2
2
  import _ from 'underscore';
3
- import Grapher from './grapher';
3
+ import Grapher from 'grapher';
4
4
  import * as Helpers from './visualisation_helpers';
5
5
  import Utils from './utils';
6
6
 
@@ -17,7 +17,7 @@ class Graph {
17
17
  return 'alignment-overview';
18
18
  }
19
19
 
20
- static collapseId(props) {
20
+ static graphId(props) {
21
21
  return 'alignment_'+props.query.number;
22
22
  }
23
23
 
@@ -1,6 +1,6 @@
1
1
  import d3 from 'd3';
2
2
  import _ from 'underscore';
3
- import Grapher from './grapher';
3
+ import Grapher from 'grapher';
4
4
  import * as Helpers from './visualisation_helpers';
5
5
 
6
6
  /**
@@ -30,7 +30,7 @@ class Graph {
30
30
  return 'kablammo';
31
31
  }
32
32
 
33
- static collapseId(props) {
33
+ static graphId(props) {
34
34
  return 'kablammo_'+props.query.number+'_'+props.hit.number;
35
35
  }
36
36
 
@@ -1,6 +1,6 @@
1
1
  import d3 from 'd3';
2
2
  import _ from 'underscore';
3
- import Grapher from './grapher';
3
+ import Grapher from 'grapher';
4
4
  import * as Helpers from './visualisation_helpers';
5
5
 
6
6
  /**
@@ -20,7 +20,7 @@ class Graph {
20
20
  return 'length-distribution';
21
21
  }
22
22
 
23
- static collapseId(props) {
23
+ static graphId(props) {
24
24
  return 'length_'+props.query.number;
25
25
  }
26
26
 
@@ -254,7 +254,7 @@ class Graph {
254
254
  .attr('dy', '0')
255
255
  .attr('transform','rotate(-90)');
256
256
 
257
- var yContainer = this.svg.append('g')
257
+ this.svg.append('g')
258
258
  .attr('class','axis axis--y')
259
259
  .attr('transform','translate('+this._margin.left+',0)')
260
260
  .call(y_axis);
@@ -0,0 +1,25 @@
1
+ import Grapher from 'grapher';
2
+
3
+ class Graph {
4
+ static canCollapse() {
5
+ return false;
6
+ }
7
+
8
+ static name() {
9
+ return null;
10
+ }
11
+
12
+ static className() {
13
+ return null;
14
+ }
15
+
16
+ static dataName(_props) {
17
+ return null;
18
+ }
19
+
20
+ static graphId() {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ export default Grapher(Graph);
@@ -0,0 +1,3 @@
1
+ import { Options } from '../options';
2
+
3
+ export { Options };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+
3
+ class QueryStats extends React.Component{
4
+ render () {}
5
+
6
+ componentDidUpdate() {
7
+ console.log("Query stats:", this.props)
8
+ }
9
+ }
10
+
11
+ export default QueryStats;
@@ -1,3 +1,4 @@
1
+ import Histogram from 'histogram';
1
2
 
2
3
  class ReportPlugins {
3
4
  constructor(parent) {
@@ -13,6 +14,10 @@ class ReportPlugins {
13
14
  queryResults(_query) {
14
15
  return [];
15
16
  }
17
+
18
+ generateStats() {
19
+ return (<Histogram />);
20
+ }
16
21
  }
17
22
 
18
- export default ReportPlugins;
23
+ export default ReportPlugins;
@@ -0,0 +1,4 @@
1
+ export const SearchHeaderPlugin = () => {
2
+ return '';
3
+ };
4
+
data/public/js/options.js CHANGED
@@ -3,80 +3,185 @@ import React, { Component } from 'react';
3
3
  export class Options extends Component {
4
4
  constructor(props) {
5
5
  super(props);
6
- this.state = { preOpts: {}, value: '', method: '' };
7
- this.updateBox = this.updateBox.bind(this);
8
- this.optionsJSX = this.optionsJSX.bind(this);
9
- this.showAdvancedOptions = this.showAdvancedOptions.bind(this);
6
+
7
+ this.state = {
8
+ textValue: '',
9
+ objectValue: this.defaultObjectValue(),
10
+ paramsMode: 'advanced'
11
+ };
12
+
13
+ this.onTextValueChanged = this.onTextValueChanged.bind(this);
14
+ this.optionsPresetsJSX = this.optionsPresetsJSX.bind(this);
15
+ this.advancedParamsJSX = this.advancedParamsJSX.bind(this);
16
+ this.showAdvancedOptionsHelp = this.showAdvancedOptionsHelp.bind(this);
17
+ }
18
+
19
+
20
+ defaultObjectValue() {
21
+ return {
22
+ max_target_seqs: '',
23
+ evalue: '',
24
+ task: ''
25
+ };
26
+ };
27
+
28
+ componentDidUpdate(prevProps) {
29
+ if (prevProps.predefinedOptions !== this.props.predefinedOptions) {
30
+ let defaultOptions = this.props.predefinedOptions.default || {attributes: []};
31
+ let advancedOptions = this.props.predefinedOptions['last search'] || defaultOptions || {attributes: []};
32
+ let initialTextValue = advancedOptions.attributes.join(' ').trim();
33
+ let parsedOptions = this.parsedOptions(initialTextValue);
34
+ this.setState({ textValue: initialTextValue, objectValue: parsedOptions});
35
+ }
36
+ }
37
+
38
+ onTextValueChanged(textValue) {
39
+ let parsedOptions = this.parsedOptions(textValue.toString());
40
+
41
+ this.setState({
42
+ textValue: textValue.toString(),
43
+ objectValue: parsedOptions
44
+ });
10
45
  }
11
46
 
12
- updateBox(value) {
13
- this.setState({ value: value });
47
+ parsedOptions(textValue) {
48
+ const words = textValue.split(" ");
49
+ let parsedOptions = this.defaultObjectValue();
50
+ // Iterate through the words in steps of 2, treating each pair as an option and its potential value
51
+ for (let i = 0; i < words.length; i += 2) {
52
+ // Ensure there is a pair available
53
+ if (words[i]) {
54
+ if (words[i].startsWith("-")) {
55
+ const optionName = words[i].substring(1).trim();
56
+
57
+ if (words[i + 1]) {
58
+ // Use the second word as the value for this option
59
+ parsedOptions[optionName] = words[i + 1];
60
+ } else {
61
+ // No value found for this option, set it to null or a default value
62
+ parsedOptions[optionName] = null;
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ return parsedOptions;
14
69
  }
15
70
 
71
+ optionsPresetsJSX() {
72
+ return (
73
+ <div id="options-presets" className="w-full">
74
+ { Object.keys(this.props.predefinedOptions).length > 1 && <>
75
+ <h3 className="w-full font-medium border-b border-seqorange mb-2">Settings</h3>
76
+
77
+ <p className="text-sm">Choose a predefined setting or customize BLAST parameters.</p>
78
+ {this.presetListJSX()}
79
+ </>}
80
+ </div>
81
+ );
82
+ }
16
83
 
17
- optionsJSX() {
18
- return <span className="input-group-btn dropdown">
19
- <button className="btn bnt-sm btn-default dropdown-toggle"
20
- data-toggle="dropdown">
21
- <i className="fa fa-caret-down"></i>
22
- </button>
23
- <ul id='advanced-params-dropdown'
24
- className="dropdown-menu dropdown-menu-right">
84
+ presetListJSX() {
85
+ return (
86
+ <ul className="text-sm my-1">
25
87
  {
26
- Object.entries(this.state.preOpts).map(
27
- ([key, value], index) => {
28
- value = value.join(' ');
29
- if (value.trim() === this.state.value.trim())
30
- var className = 'yellow-background';
31
- return <li key={index} className={className}
32
- onClick={() => this.updateBox(value)}>
33
- <strong>{key}:</strong>&nbsp;{value}
34
- </li>;
88
+ Object.entries(this.props.predefinedOptions).map(
89
+ ([key, config], index) => {
90
+ let textValue = config.attributes.join(' ').trim();
91
+ let description = config.description || textValue;
92
+
93
+ return (
94
+ <label key={index} className={`block w-full px-2 py-1 hover:bg-gray-200 cursor-pointer`}>
95
+ <input
96
+ type="radio"
97
+ name="predefinedOption"
98
+ value={textValue}
99
+ checked={textValue === this.state.textValue}
100
+ onChange={() => this.onTextValueChanged(textValue)}
101
+ />
102
+ <strong className="ml-2">{key}:</strong>&nbsp;{description}
103
+ </label>
104
+ );
35
105
  }
36
106
  )
37
107
  }
38
108
  </ul>
39
- </span>;
109
+ )
110
+ }
111
+
112
+ advancedParamsJSX() {
113
+ if (this.state.paramsMode !== 'advanced') {
114
+ return null;
115
+ }
116
+
117
+ let classNames = 'flex-grow block px-4 py-1 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 text-base';
118
+
119
+ if (this.state.textValue) {
120
+ classNames += ' bg-yellow-100';
121
+ }
122
+
123
+ return(
124
+ <div className="w-full">
125
+ <div className="flex items-end">
126
+ <label className="flex items-center" htmlFor="advanced">
127
+ Advanced parameters
128
+ </label>
129
+
130
+ {this.props.blastMethod &&
131
+ <button
132
+ className="text-seqblue ml-2"
133
+ type="button"
134
+ onClick={this.showAdvancedOptionsHelp}
135
+ data-toggle="modal" data-target="#help">
136
+ See available options
137
+ <i className="fa fa-question-circle ml-1 w-3 h-4 fill-current"></i>
138
+ </button>
139
+ }
140
+
141
+ {!this.props.blastMethod &&
142
+ <span className="text-gray-600 ml-2 text-sm hidden sm:block">
143
+ Select databases and fill in the query to see options.
144
+ </span>
145
+ }
146
+ </div>
147
+
148
+ <div className='flex-grow flex w-full'>
149
+ <input type="text" className={classNames}
150
+ onChange={e => this.onTextValueChanged(e.target.value)}
151
+ id="advanced"
152
+ name="advanced"
153
+ value={this.state.textValue}
154
+ placeholder="eg: -evalue 1.0e-5 -num_alignments 100"
155
+ title="View, and enter advanced parameters."
156
+ />
157
+ </div>
158
+ <div className="text-sm text-gray-600 mt-2">
159
+ Options as they would appear in a command line when calling BLAST eg: <i>-evalue 1.0e-5 -num_alignments 100</i>
160
+ </div>
161
+ </div>
162
+ )
40
163
  }
41
- showAdvancedOptions(e) {
164
+
165
+ showAdvancedOptionsHelp(e) {
42
166
  const ids = ['blastn', 'tblastn', 'blastp', 'blastx', 'tblastx'];
43
- const method = this.state.method.toLowerCase();
167
+ const method = this.props.blastMethod.toLowerCase();
44
168
  // hide options for other algorithms and only show for selected algorithm
45
169
  for (const id of ids) {
46
- $(`#${id}`)[id === method ? 'show' : 'hide']();
170
+ if (id === method) {
171
+ document.getElementById(id).classList.remove('hidden')
172
+ } else {
173
+ document.getElementById(id).classList.add('hidden');
174
+ }
47
175
  }
176
+ document.querySelector('[data-help-modal]').classList.remove('hidden')
48
177
  }
178
+
49
179
  render() {
50
- var classNames = 'form-control';
51
- if (this.state.value.trim()) {
52
- classNames += ' yellow-background';
53
- }
54
180
  return (
55
- <div className="col-md-7">
56
- <div className="form-group">
57
- <div className="col-md-12">
58
- <div className="input-group">
59
- <label className="control-label" htmlFor="advanced">
60
- Advanced parameters:
61
- {/* only show link to advanced parameters if blast method is known */}
62
- {this.state.method && <sup style={{ marginLeft: '2px' }}>
63
- <a href=''
64
- onClick={this.showAdvancedOptions}
65
- data-toggle="modal" data-target="#help">
66
- <i className="fa fa-question-circle"></i>
67
- </a>
68
- </sup>}
69
- </label>
70
- <input type="text" className={classNames}
71
- onChange={e => this.updateBox(e.target.value)}
72
- id="advanced" name="advanced" value={this.state.value}
73
- placeholder="eg: -evalue 1.0e-5 -num_alignments 100"
74
- title="View, and enter advanced parameters."
75
- />
76
- {Object.keys(this.state.preOpts).length > 1 && this.optionsJSX()}
77
- </div>
78
- </div>
79
- </div>
181
+ <div className="flex-grow flex flex-col items-start sm:items-center space-y-4">
182
+ {this.optionsPresetsJSX()}
183
+
184
+ {this.advancedParamsJSX()}
80
185
  </div>
81
186
  );
82
187
  }