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.
- checksums.yaml +4 -4
- data/bin/sequenceserver +2 -2
- data/lib/sequenceserver/api_errors.rb +32 -2
- data/lib/sequenceserver/blast/job.rb +20 -3
- data/lib/sequenceserver/blast/report.rb +74 -86
- data/lib/sequenceserver/blast/tasks.rb +38 -0
- data/lib/sequenceserver/config.rb +54 -20
- data/lib/sequenceserver/makeblastdb.rb +16 -2
- data/lib/sequenceserver/report.rb +0 -6
- data/lib/sequenceserver/routes.rb +66 -25
- data/lib/sequenceserver/sequence.rb +35 -7
- data/lib/sequenceserver/server.rb +1 -1
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +1 -1
- data/public/404.html +1 -1
- data/public/css/app.css +121 -0
- data/public/css/app.min.css +1 -0
- data/public/css/sequenceserver.css +0 -148
- data/public/css/sequenceserver.min.css +3 -3
- data/public/js/circos.js +2 -2
- data/public/js/collapse_preferences.js +37 -0
- data/public/js/databases.js +65 -37
- data/public/js/databases_tree.js +2 -1
- data/public/js/dnd.js +37 -50
- data/public/js/download_fasta.js +1 -0
- data/public/js/form.js +79 -50
- data/public/js/grapher.js +23 -37
- data/public/js/hits_overview.js +2 -2
- data/public/js/kablammo.js +2 -2
- data/public/js/length_distribution.js +3 -3
- data/public/js/null_plugins/grapher/histogram.js +25 -0
- data/public/js/null_plugins/options.js +3 -0
- data/public/js/null_plugins/query_stats.js +11 -0
- data/public/js/null_plugins/report_plugins.js +6 -1
- data/public/js/null_plugins/search_header_plugin.js +4 -0
- data/public/js/options.js +161 -56
- data/public/js/query.js +85 -59
- data/public/js/report.js +1 -1
- data/public/js/search.js +2 -0
- data/public/js/search_button.js +67 -56
- data/public/js/sidebar.js +10 -1
- data/public/js/tests/database.spec.js +5 -5
- data/public/js/tests/form.spec.js +98 -0
- data/public/js/tests/mock_data/databases.json +5 -5
- data/public/js/tests/mocks/circos.js +6 -0
- data/public/js/tests/report.spec.js +4 -3
- data/public/js/tests/search_query.spec.js +16 -6
- data/public/sequenceserver-report.min.js +46 -24
- data/public/sequenceserver-search.min.js +57 -13
- data/public/sequenceserver_logo.webp +0 -0
- data/views/blastn_options.erb +66 -66
- data/views/blastp_options.erb +59 -59
- data/views/blastx_options.erb +68 -68
- data/views/layout.erb +61 -3
- data/views/search.erb +33 -38
- data/views/search_layout.erb +153 -0
- data/views/tblastn_options.erb +57 -57
- data/views/tblastx_options.erb +64 -64
- metadata +51 -22
- data/lib/sequenceserver/makeblastdb-modified-with-cache.rb +0 -345
- data/public/SequenceServer_logo.png +0 -0
- 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.
|
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
|
-
|
24
|
-
return Graph.
|
27
|
+
graphId() {
|
28
|
+
return Graph.graphId(this.props);
|
25
29
|
}
|
26
30
|
|
27
31
|
render() {
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
43
|
-
data-target={'#' + this.collapseId()}
|
51
|
+
onClick={() => this.collapsePreferences.toggleCollapse()}
|
44
52
|
>
|
45
|
-
{this.
|
53
|
+
{this.collapsePreferences.renderCollapseIcon()}
|
46
54
|
{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.
|
85
|
+
id={this.graphId()}
|
86
86
|
className={cssClasses}
|
87
87
|
></div>
|
88
88
|
);
|
89
89
|
}
|
90
90
|
|
91
91
|
componentDidMount() {
|
92
|
-
Graphers[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
|
-
});
|
data/public/js/hits_overview.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import d3 from 'd3';
|
2
2
|
import _ from 'underscore';
|
3
|
-
import Grapher from '
|
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
|
20
|
+
static graphId(props) {
|
21
21
|
return 'alignment_'+props.query.number;
|
22
22
|
}
|
23
23
|
|
data/public/js/kablammo.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import d3 from 'd3';
|
2
2
|
import _ from 'underscore';
|
3
|
-
import Grapher from '
|
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
|
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 '
|
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
|
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
|
-
|
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);
|
@@ -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;
|
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
|
-
|
7
|
-
this.
|
8
|
-
|
9
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
18
|
-
return
|
19
|
-
<
|
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.
|
27
|
-
([key,
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
return
|
32
|
-
|
33
|
-
|
34
|
-
|
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> {description}
|
103
|
+
</label>
|
104
|
+
);
|
35
105
|
}
|
36
106
|
)
|
37
107
|
}
|
38
108
|
</ul>
|
39
|
-
|
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
|
-
|
164
|
+
|
165
|
+
showAdvancedOptionsHelp(e) {
|
42
166
|
const ids = ['blastn', 'tblastn', 'blastp', 'blastx', 'tblastx'];
|
43
|
-
const method = this.
|
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
|
-
|
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-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
}
|