sequenceserver 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequenceserver +2 -2
  3. data/lib/sequenceserver/api_errors.rb +1 -1
  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 +32 -21
  11. data/lib/sequenceserver/version.rb +1 -1
  12. data/lib/sequenceserver.rb +1 -1
  13. data/public/css/app.css +121 -0
  14. data/public/css/app.min.css +1 -0
  15. data/public/css/sequenceserver.css +0 -148
  16. data/public/css/sequenceserver.min.css +3 -3
  17. data/public/js/circos.js +2 -2
  18. data/public/js/collapse_preferences.js +37 -0
  19. data/public/js/databases.js +65 -37
  20. data/public/js/databases_tree.js +2 -1
  21. data/public/js/dnd.js +37 -50
  22. data/public/js/form.js +78 -50
  23. data/public/js/grapher.js +23 -37
  24. data/public/js/hits_overview.js +2 -2
  25. data/public/js/kablammo.js +2 -2
  26. data/public/js/length_distribution.js +3 -3
  27. data/public/js/null_plugins/grapher/histogram.js +25 -0
  28. data/public/js/null_plugins/options.js +3 -0
  29. data/public/js/null_plugins/query_stats.js +11 -0
  30. data/public/js/null_plugins/report_plugins.js +6 -1
  31. data/public/js/null_plugins/search_header_plugin.js +4 -0
  32. data/public/js/options.js +161 -56
  33. data/public/js/query.js +85 -59
  34. data/public/js/report.js +1 -1
  35. data/public/js/search.js +2 -0
  36. data/public/js/search_button.js +67 -56
  37. data/public/js/sidebar.js +1 -1
  38. data/public/js/tests/database.spec.js +5 -5
  39. data/public/js/tests/{advanced_parameters.spec.js → form.spec.js} +35 -1
  40. data/public/js/tests/mock_data/databases.json +5 -5
  41. data/public/js/tests/mocks/circos.js +6 -0
  42. data/public/js/tests/report.spec.js +4 -3
  43. data/public/js/tests/search_query.spec.js +5 -6
  44. data/public/sequenceserver-report.min.js +45 -23
  45. data/public/sequenceserver-search.min.js +57 -13
  46. data/public/sequenceserver_logo.webp +0 -0
  47. data/views/blastn_options.erb +66 -66
  48. data/views/blastp_options.erb +59 -59
  49. data/views/blastx_options.erb +68 -68
  50. data/views/layout.erb +60 -3
  51. data/views/search.erb +33 -38
  52. data/views/search_layout.erb +152 -0
  53. data/views/tblastn_options.erb +57 -57
  54. data/views/tblastx_options.erb +64 -64
  55. metadata +31 -22
  56. data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +0 -345
  57. data/public/SequenceServer_logo.png +0 -0
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
  }