quorum 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %>