sequenceserver 3.0.1 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/sequenceserver +2 -2
- data/lib/sequenceserver/api_errors.rb +56 -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/blast.rb +6 -0
- data/lib/sequenceserver/config.rb +54 -20
- data/lib/sequenceserver/database.rb +13 -0
- 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 +34 -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
|
}
|