quorum 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Gemfile.lock +35 -28
  2. data/HISTORY.md +4 -0
  3. data/app/assets/javascripts/quorum/application.js +1 -0
  4. data/app/assets/javascripts/quorum/jobs.js +5 -345
  5. data/app/assets/javascripts/quorum/quorum.js +275 -0
  6. data/app/assets/javascripts/quorum/template_settings.js +8 -0
  7. data/app/assets/javascripts/quorum/utilities.js +12 -0
  8. data/app/assets/stylesheets/quorum/application.css +0 -4
  9. data/app/assets/stylesheets/quorum/autohint.css +4 -0
  10. data/app/controllers/quorum/jobs_controller.rb +9 -9
  11. data/app/views/quorum/jobs/form/_blastn_form.html.erb +62 -62
  12. data/app/views/quorum/jobs/form/_blastp_form.html.erb +62 -62
  13. data/app/views/quorum/jobs/form/_blastx_form.html.erb +62 -62
  14. data/app/views/quorum/jobs/form/_tblastn_form.html.erb +62 -62
  15. data/app/views/quorum/jobs/show.html.erb +1 -1
  16. data/app/views/quorum/jobs/templates/_blast_detailed_report_template.html.erb +22 -22
  17. data/app/views/quorum/jobs/templates/_blast_template.html.erb +29 -29
  18. data/app/views/shared/_error_messages.html.erb +8 -8
  19. data/lib/quorum/version.rb +1 -1
  20. data/lib/tasks/jasmine.rake +8 -0
  21. data/quorum.gemspec +1 -0
  22. data/spec/data/seqs_not_fa.txt +16 -16
  23. data/spec/javascripts/fixtures/formatted_sequence.html +6 -0
  24. data/spec/javascripts/fixtures/quorum_search_form.html +461 -0
  25. data/spec/javascripts/fixtures/quorum_tabs.html +10 -0
  26. data/spec/javascripts/helpers/jasmine-jquery.js +288 -0
  27. data/spec/javascripts/jobs_spec.js +99 -0
  28. data/spec/javascripts/jquery/jquery-ui.min.js +791 -0
  29. data/spec/javascripts/jquery/jquery.min.js +4 -0
  30. data/spec/javascripts/jquery/jquery_ujs.js +373 -0
  31. data/spec/javascripts/quorum_spec.js +106 -0
  32. data/spec/javascripts/string_spec.js +18 -0
  33. data/spec/javascripts/support/jasmine.yml +84 -0
  34. data/spec/javascripts/support/jasmine_config.rb +23 -0
  35. data/spec/javascripts/support/jasmine_runner.rb +33 -0
  36. data/spec/requests/jobs_spec.rb +34 -33
  37. data/vendor/assets/javascripts/jquery.autohint.js +87 -0
  38. metadata +62 -26
@@ -0,0 +1,275 @@
1
+ //
2
+ // QUORUM
3
+ //---------------------------------------------------------------------------//
4
+
5
+ var QUORUM = {
6
+
7
+ //
8
+ // Supported algorithms.
9
+ //
10
+ algorithms: ["blastn", "blastx", "tblastn", "blastp"],
11
+
12
+ //
13
+ // Poll quorum search results asynchronously and insert them into
14
+ // the DOM via #blast_template.
15
+ //
16
+ pollResults: function(id, interval, algos) {
17
+
18
+ // Set the default poll interval to 5 seconds.
19
+ interval = interval || 5000;
20
+
21
+ // Algorithms
22
+ algos = algos || QUORUM.algorithms;
23
+
24
+ _.each(algos, function(a) {
25
+ $.getJSON(
26
+ '/quorum/jobs/' + id + '/get_quorum_search_results.json?algo=' + a,
27
+ function(data) {
28
+ if (data.length === 0) {
29
+ setTimeout(function() {
30
+ QUORUM.pollResults(id, interval, [a]);
31
+ }, interval);
32
+ } else {
33
+ $('#' + a + '-results').empty();
34
+ var temp = _.template(
35
+ $('#blast_template').html(), {
36
+ data: data,
37
+ algo: a
38
+ }
39
+ );
40
+ $('#' + a + '-results').html(temp);
41
+ return;
42
+ }
43
+ }
44
+ );
45
+ });
46
+ },
47
+
48
+ //
49
+ // Display jQuery UI modal box containing detailed report of all hits
50
+ // to the same query. After the modal box is inserted into the DOM,
51
+ // automatically scroll to the highlighted hit.
52
+ //
53
+ viewDetailedReport: function(id, focus_id, query, algo) {
54
+ // Create the modal box.
55
+ $('#detailed_report_dialog').html(
56
+ "<p class='center'>" +
57
+ "Loading... <img src='/assets/quorum/loading.gif' alt='Loading'>" +
58
+ "</p>"
59
+ ).dialog({
60
+ modal: true,
61
+ width: 850,
62
+ position: 'top'
63
+ });
64
+
65
+ $.getJSON(
66
+ '/quorum/jobs/' + id + '/get_quorum_search_results.json?algo=' + algo +
67
+ '&query=' + query,
68
+ function(data) {
69
+ var temp = _.template(
70
+ $('#detailed_report_template').html(), {
71
+ data: data,
72
+ query: query,
73
+ algo: algo
74
+ }
75
+ );
76
+
77
+ // Insert the detailed report data.
78
+ $('#detailed_report_dialog').empty().html(temp);
79
+
80
+ // Add tipsy to the sequence data.
81
+ $('a[rel=quorum-tipsy]').tipsy({ gravity: 's' });
82
+
83
+ // Highlight the selected id.
84
+ $('#' + focus_id).addClass("ui-state-highlight");
85
+
86
+ // Automatically scroll to the selected id.
87
+ QUORUM.autoScroll(focus_id, false);
88
+ }
89
+ );
90
+ },
91
+
92
+ //
93
+ // Helper to add title sequence position attribute for tipsy.
94
+ //
95
+ // If from > to decrement index; otherwise increment.
96
+ // If the algo is tblastn and type is hit OR algo is blastx and type is query,
97
+ // increment / decrement by 3; otherwise increment / decrement by 1.
98
+ //
99
+ addBaseTitleIndex: function(bases, from, to, algo, type) {
100
+ var forward = true;
101
+ var value = 1;
102
+ var index = from;
103
+
104
+ if (from > to) {
105
+ forward = false;
106
+ }
107
+
108
+ // Set value to 3 for the below.
109
+ if ((type === "hit" && algo === "tblastn") ||
110
+ (type === "query" && algo === "blastx")) {
111
+ value = 3;
112
+ }
113
+
114
+ // Add tipsy to each base.
115
+ return _.map(bases.split(''), function(c) {
116
+ var str = "<a rel='quorum-tipsy' title=" + index + ">" + c + "</a>";
117
+ forward ? index += value : index -= value;
118
+ return str;
119
+ }).join('');
120
+ },
121
+
122
+ //
123
+ // Format sequence data for detailed report.
124
+ //
125
+ // If q_from > q_to or h_from > h_to, subtract by increment; otherwise add
126
+ // by increment.
127
+ //
128
+ // If algo is tblastn or blastx, multiple increment by 3.
129
+ //
130
+ formatSequenceReport: function(qseq, midline, hseq, q_from, q_to, h_from, h_to, algo) {
131
+ var max = qseq.length; // max length
132
+ var increment = 60; // increment value
133
+ var s = 0; // start position
134
+ var e = increment; // end position
135
+ var seq = "\n"; // seq string to return
136
+
137
+ while(true) {
138
+ seq += "qseq " + QUORUM.addBaseTitleIndex(qseq.slice(s, e), q_from, q_to, algo, 'query') + "\n";
139
+ seq += " " + midline.slice(s, e) + "\n";
140
+ seq += "hseq " + QUORUM.addBaseTitleIndex(hseq.slice(s, e), h_from, h_to, algo, 'hit') + "\n\n";
141
+
142
+ if (e >= max) {
143
+ break;
144
+ }
145
+
146
+ s += increment;
147
+ e += increment;
148
+
149
+ // If the algorithm is blastx, increment * 3 only for qseq.
150
+ if (algo === "blastx") {
151
+ q_from < q_to ? q_from += (increment * 3) : q_from -= (increment * 3);
152
+ } else {
153
+ q_from < q_to ? q_from += increment : q_from -= increment;
154
+ }
155
+
156
+ // If the algorithm is tblastn, increment * 3 only for hseq.
157
+ if (algo === "tblastn") {
158
+ h_from < h_to ? h_from += (increment * 3) : h_from -= (increment * 3);
159
+ } else {
160
+ h_from < h_to ? h_from += increment : h_from -= increment;
161
+ }
162
+ }
163
+ return "<p class='small'>Alignment (Mouse over for positions):</p>" +
164
+ "<span class='small'><pre>" + seq + "</pre></span>";
165
+ },
166
+
167
+ //
168
+ // Format Query and Hit Strand.
169
+ //
170
+ // If query_frame or hit_frame < 0, print 'reverse'; print 'forward' otherwise.
171
+ //
172
+ formatStrand: function(qstrand, hstrand) {
173
+ var q = "";
174
+ var h = "";
175
+
176
+ qstrand < 0 ? q = "reverse" : q = "forward";
177
+ hstrand < 0 ? h = "reverse" : h = "forward";
178
+
179
+ return q + " / " + h;
180
+ },
181
+
182
+ //
183
+ // Display links to Hsps in the same group.
184
+ //
185
+ displayHspLinks: function(focus, group, data) {
186
+ if (group !== null) {
187
+ var str = "Related <a onclick=\"(QUORUM.openWindow(" +
188
+ "'http://www.ncbi.nlm.nih.gov/books/NBK62051/def-item/blast_glossary.HSP'," +
189
+ "'HSP', 800, 300))\">HSPs</a>: ";
190
+
191
+ var ids = _.map(group.split(","), function(i) { return parseInt(i, 10); });
192
+
193
+ var selected = _(data).chain()
194
+ .reject(function(d) { return !_.include(ids, d.id); })
195
+ .sortBy(function(d) { return d.id; })
196
+ .value();
197
+
198
+ _.each(selected, function(e) {
199
+ if (e.id !== focus) {
200
+ str += "<a onclick='(QUORUM.autoScroll(" + e.id + ", true))'>" + e.hsp_num + "</a> ";
201
+ } else {
202
+ str += e.hsp_num + " ";
203
+ }
204
+ });
205
+ return str;
206
+ }
207
+ },
208
+
209
+ //
210
+ // Download Blast hit sequence.
211
+ //
212
+ downloadSequence: function(id, algo_id, algo, el) {
213
+ $(el).html('Fetching sequence...');
214
+
215
+ $.getJSON(
216
+ "/quorum/jobs/" + id + "/get_quorum_blast_hit_sequence.json?algo_id=" +
217
+ algo_id + "&algo=" + algo,
218
+ function(data) {
219
+ QUORUM.getSequenceFile(id, data[0].meta_id, el);
220
+ }
221
+ );
222
+ },
223
+
224
+ //
225
+ // Poll application for Blast hit sequence.
226
+ //
227
+ getSequenceFile: function(id, meta_id, el) {
228
+ var url = "/quorum/jobs/" + id +
229
+ "/send_quorum_blast_hit_sequence?meta_id=" + meta_id;
230
+ $.get(
231
+ url,
232
+ function(data) {
233
+ if (data.length === 0) {
234
+ setTimeout(function() { QUORUM.getSequenceFile(id, meta_id, el) }, 2500);
235
+ } else {
236
+ if (data.indexOf("error") !== -1) {
237
+ // Print error message.
238
+ $(el).addClass('ui-state-error').html(data);
239
+ } else {
240
+ // Force browser to download file via iframe.
241
+ $(el).addClass('ui-state-highlight').html('Sequence Downloaded Successfully');
242
+ $('.quorum_sequence_download').remove();
243
+ $('body').append('<iframe class="quorum_sequence_download"></iframe>');
244
+ $('.quorum_sequence_download').attr('src', url).hide();
245
+ }
246
+ }
247
+ }
248
+ );
249
+ },
250
+
251
+ //
252
+ // Autoscroll to given div id.
253
+ //
254
+ autoScroll: function(id, highlight) {
255
+ $('html, body').animate({
256
+ scrollTop: $('#' + id).offset().top
257
+ }, 1000);
258
+
259
+ if (highlight) {
260
+ $('#' + id).effect("highlight", {}, 4000);
261
+ }
262
+ },
263
+
264
+ //
265
+ // Open URL in new window.
266
+ //
267
+ openWindow: function(url, name, width, height) {
268
+
269
+ var windowSize = "width=" + width + ",height=" + height + ",scrollbars=yes";
270
+
271
+ window.open(url, name, windowSize);
272
+ }
273
+
274
+ };
275
+
@@ -0,0 +1,8 @@
1
+ //
2
+ // Mustache style Underscore.js templating.
3
+ //---------------------------------------------------------------------------//
4
+ _.templateSettings = {
5
+ evaluate: /\{\{(.+?)\}\}/g,
6
+ interpolate: /\{\{\=(.+?)\}\}/g
7
+ };
8
+
@@ -0,0 +1,12 @@
1
+ //
2
+ // Truncate string to length n using word boundary.
3
+ //
4
+ String.prototype.trunc = function(n) {
5
+ var longStr = this.length > n;
6
+ var str = longStr ? this.slice(0, n) : this;
7
+
8
+ longStr ? str = str.slice(0, str.lastIndexOf(' ')) : str;
9
+
10
+ return longStr ? str + '...' : str;
11
+ }
12
+
@@ -194,10 +194,6 @@ form label {
194
194
  font-size: 12px;
195
195
  }
196
196
 
197
- .auto-hint {
198
- color: #aaa;
199
- }
200
-
201
197
  #loading {
202
198
  padding: 5px;
203
199
  }
@@ -0,0 +1,4 @@
1
+ .auto-hint {
2
+ color: #aaa;
3
+ }
4
+
@@ -61,13 +61,13 @@ module Quorum
61
61
  queued = "#{params[:algo]}_job".to_sym
62
62
  report = "#{params[:algo]}_job_reports".to_sym
63
63
 
64
- begin
65
- job = Job.find(params[:id])
66
- rescue ActiveRecord::RecordNotFound => e
67
- json = empty
68
- else
69
- if job.method(queued).call.present?
70
- if job.method(report).call.present?
64
+ begin
65
+ job = Job.find(params[:id])
66
+ rescue ActiveRecord::RecordNotFound => e
67
+ json = empty
68
+ else
69
+ if job.method(queued).call.present?
70
+ if job.method(report).call.present?
71
71
  if params[:query]
72
72
  json = job.method(report).call.by_query(params[:query]).default_order
73
73
  else
@@ -76,8 +76,8 @@ module Quorum
76
76
  else
77
77
  json = []
78
78
  end
79
- end
80
- end
79
+ end
80
+ end
81
81
  end
82
82
 
83
83
  respond_with json
@@ -4,68 +4,68 @@
4
4
  <%= n.check_box :queue %>
5
5
  </p>
6
6
  <div id="blastn" class="toggle">
7
- <table class="options">
8
- <tr>
9
- <td>
10
- <label>Database(s)</label>
11
- </td>
12
- <td>
7
+ <table class="options">
8
+ <tr>
9
+ <td>
10
+ <label>Database(s)</label>
11
+ </td>
12
+ <td>
13
13
  <%= n.select :blast_dbs, @blast_dbs[:blastn], {}, { :multiple => true } %>
14
- </td>
15
- <td>
16
- <%= n.label :filter, "DUST" %>
17
- </td>
18
- <td>
19
- <%= n.check_box :filter %>
20
- </td>
21
- </tr>
22
- <tr>
23
- <td>
24
- <%= n.label :expectation %>
25
- </td>
26
- <td>
27
- <%= n.text_field :expectation, :size => 15, :title => "5e-20",
28
- :class => "auto-hint" %>
29
- </td>
30
- <td>
31
- <%= n.label :min_bit_score %>
32
- </td>
33
- <td>
34
- <%= n.text_field :min_bit_score, :size => 15, :title => "0",
35
- :class => "auto-hint" %>
36
- </td>
37
- </tr>
38
- <tr>
39
- <td>
40
- <%= n.label :max_score %>
41
- </td>
42
- <td>
43
- <%= n.text_field :max_score, :size => 15, :title => "25",
44
- :class => "auto-hint" %>
45
- </td>
46
- <td>
47
- <%= n.label :gapped_alignments %>
48
- </td>
49
- <td>
50
- <%= n.select :gapped_alignments, [['No', false], ['Yes', true]] %>
51
- </td>
52
- </tr>
53
- <tr>
54
- <td>
55
- <%= n.label :gap_opening_extension %>
56
- </td>
57
- <td>
58
- <%=
59
- n.select :gap_opening_extension,
60
- options_for_select(
61
- @job.blastn_job.gap_opening_extension_values,
62
- @job.blastn_job.gap_opening_extension
63
- )
64
- %>
65
- </td>
66
- <td></td>
67
- <td></td>
68
- </tr>
69
- </table>
14
+ </td>
15
+ <td>
16
+ <%= n.label :filter, "DUST" %>
17
+ </td>
18
+ <td>
19
+ <%= n.check_box :filter %>
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <td>
24
+ <%= n.label :expectation %>
25
+ </td>
26
+ <td>
27
+ <%= n.text_field :expectation, :size => 15, :title => "5e-20",
28
+ :class => "auto-hint" %>
29
+ </td>
30
+ <td>
31
+ <%= n.label :min_bit_score %>
32
+ </td>
33
+ <td>
34
+ <%= n.text_field :min_bit_score, :size => 15, :title => "0",
35
+ :class => "auto-hint" %>
36
+ </td>
37
+ </tr>
38
+ <tr>
39
+ <td>
40
+ <%= n.label :max_score %>
41
+ </td>
42
+ <td>
43
+ <%= n.text_field :max_score, :size => 15, :title => "25",
44
+ :class => "auto-hint" %>
45
+ </td>
46
+ <td>
47
+ <%= n.label :gapped_alignments %>
48
+ </td>
49
+ <td>
50
+ <%= n.select :gapped_alignments, [['No', false], ['Yes', true]] %>
51
+ </td>
52
+ </tr>
53
+ <tr>
54
+ <td>
55
+ <%= n.label :gap_opening_extension %>
56
+ </td>
57
+ <td>
58
+ <%=
59
+ n.select :gap_opening_extension,
60
+ options_for_select(
61
+ @job.blastn_job.gap_opening_extension_values,
62
+ @job.blastn_job.gap_opening_extension
63
+ )
64
+ %>
65
+ </td>
66
+ <td></td>
67
+ <td></td>
68
+ </tr>
69
+ </table>
70
70
  </div>
71
71
  <% end %>
@@ -4,68 +4,68 @@
4
4
  <%= n.check_box :queue %>
5
5
  </p>
6
6
  <div id="blastp" class="toggle">
7
- <table class="options">
8
- <tr>
9
- <td>
10
- <label>Database(s)</label>
11
- </td>
12
- <td>
7
+ <table class="options">
8
+ <tr>
9
+ <td>
10
+ <label>Database(s)</label>
11
+ </td>
12
+ <td>
13
13
  <%= n.select :blast_dbs, @blast_dbs[:blastp], {}, { :multiple => true } %>
14
- </td>
15
- <td>
16
- <%= n.label :filter, "SEG" %>
17
- </td>
18
- <td>
19
- <%= n.check_box :filter %>
20
- </td>
21
- </tr>
22
- <tr>
23
- <td>
24
- <%= n.label :expectation %>
25
- </td>
26
- <td>
27
- <%= n.text_field :expectation, :size => 15, :title => "5e-20",
28
- :class => "auto-hint" %>
29
- </td>
30
- <td>
31
- <%= n.label :min_bit_score %>
32
- </td>
33
- <td>
34
- <%= n.text_field :min_bit_score, :size => 15, :title => "0",
35
- :class => "auto-hint" %>
36
- </td>
37
- </tr>
38
- <tr>
39
- <td>
40
- <%= n.label :max_score %>
41
- </td>
42
- <td>
43
- <%= n.text_field :max_score, :size => 15, :title => "25",
44
- :class => "auto-hint" %>
45
- </td>
46
- <td>
47
- <%= n.label :gapped_alignments %>
48
- </td>
49
- <td>
50
- <%= n.select :gapped_alignments, [['No', false], ['Yes', true]] %>
51
- </td>
52
- </tr>
53
- <tr>
54
- <td>
55
- <%= n.label :gap_opening_extension %>
56
- </td>
57
- <td>
58
- <%=
59
- n.select :gap_opening_extension,
60
- options_for_select(
61
- @job.blastn_job.gap_opening_extension_values,
62
- @job.blastn_job.gap_opening_extension
63
- )
64
- %>
65
- </td>
66
- <td></td>
67
- <td></td>
68
- </tr>
69
- </table>
14
+ </td>
15
+ <td>
16
+ <%= n.label :filter, "SEG" %>
17
+ </td>
18
+ <td>
19
+ <%= n.check_box :filter %>
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <td>
24
+ <%= n.label :expectation %>
25
+ </td>
26
+ <td>
27
+ <%= n.text_field :expectation, :size => 15, :title => "5e-20",
28
+ :class => "auto-hint" %>
29
+ </td>
30
+ <td>
31
+ <%= n.label :min_bit_score %>
32
+ </td>
33
+ <td>
34
+ <%= n.text_field :min_bit_score, :size => 15, :title => "0",
35
+ :class => "auto-hint" %>
36
+ </td>
37
+ </tr>
38
+ <tr>
39
+ <td>
40
+ <%= n.label :max_score %>
41
+ </td>
42
+ <td>
43
+ <%= n.text_field :max_score, :size => 15, :title => "25",
44
+ :class => "auto-hint" %>
45
+ </td>
46
+ <td>
47
+ <%= n.label :gapped_alignments %>
48
+ </td>
49
+ <td>
50
+ <%= n.select :gapped_alignments, [['No', false], ['Yes', true]] %>
51
+ </td>
52
+ </tr>
53
+ <tr>
54
+ <td>
55
+ <%= n.label :gap_opening_extension %>
56
+ </td>
57
+ <td>
58
+ <%=
59
+ n.select :gap_opening_extension,
60
+ options_for_select(
61
+ @job.blastn_job.gap_opening_extension_values,
62
+ @job.blastn_job.gap_opening_extension
63
+ )
64
+ %>
65
+ </td>
66
+ <td></td>
67
+ <td></td>
68
+ </tr>
69
+ </table>
70
70
  </div>
71
71
  <% end %>