quorum 0.3.1 → 0.3.2

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.
@@ -1,9 +1,9 @@
1
1
  <div id="show">
2
2
  <h1>Search Results</h1>
3
3
  <p class="small right"><%= link_to raw("Search Again &raquo;"), :jobs %></p>
4
- <!-- Tabs -->
5
- <!-- To remove an algorithm from the view, simply comment out the -->
6
- <!-- div#tabs ul li and corresponding div#tabs-(tab number) -->
4
+ <%# Tabs %>
5
+ <%# To remove an algorithm from the view, simply comment out the %>
6
+ <%# div#tabs ul li and corresponding div#tabs-(tab number) %>
7
7
  <div id="tabs">
8
8
  <ul>
9
9
  <li><a href="#tabs-1">Blastn</a></li>
@@ -12,7 +12,7 @@
12
12
  <li><a href="#tabs-4">Blastp</a></li>
13
13
  </ul>
14
14
 
15
- <!-- Search results per algorithm -->
15
+ <%# Search results per algorithm %>
16
16
  <div id="tabs-1">
17
17
  <h2>Blastn</h2>
18
18
  <div id="blastn-results">
@@ -41,10 +41,10 @@
41
41
  </div>
42
42
  </div>
43
43
  </div>
44
- <!-- End Tabs -->
44
+ <%# End Tabs %>
45
45
  <p class="small right"><%= link_to raw("Search Again &raquo;"), :jobs %></p>
46
46
 
47
- <!-- Detailed report element -->
47
+ <%# Detailed report element %>
48
48
  <div id="detailed_report_dialog" title="Quorum Report Details"></div>
49
49
  </div>
50
50
  <div id="quorum">
@@ -54,7 +54,7 @@
54
54
  </p>
55
55
  </div>
56
56
 
57
- <!-- Templates -->
57
+ <%# Templates %>
58
58
  <%= render :partial => "quorum/jobs/templates/blast_template" %>
59
59
  <%= render :partial => "quorum/jobs/templates/blast_detailed_report_template" %>
60
60
 
@@ -63,10 +63,24 @@
63
63
  //
64
64
  // Asynchronously poll Quorum for search results.
65
65
  //
66
- // pollResults(id, interval)
66
+ // QUORUM.pollResults(id, callback, callback_obj, interval)
67
67
  // id: Job.id
68
+ // callback: Define your own callback function to override the default.
69
+ // callback should expect the following params.
70
+ // id: Job.id (Int),
71
+ // data: JSON data (JSON),
72
+ // algorithm: Supported algorithm (String).
73
+ // callback_obj: Callback object.
68
74
  // interval: poll interval in milliseconds. 5000 default.
69
75
  //
76
+ // Example:
77
+ // QUORUM.pollResults(<%= @jobs.id %>, MYAPP.myCoolCallback, MYAPP, 5000);
78
+ //
79
+ // Example callback:
80
+ // MYAPP.myCoolCallback = function(id, data, algo) {
81
+ // // awesome logic
82
+ // };
83
+ //
70
84
  QUORUM.pollResults(<%= @jobs.id %>);
71
85
  });
72
86
  </script>
@@ -1,4 +1,4 @@
1
- <!-- Detailed Report Template -->
1
+ <%# Detailed Report Template %>
2
2
  <script type="text/template" id="detailed_report_template">
3
3
  <h3>Query Accession {{= query }}</h3>
4
4
  {{ _.each(data, function(v) { }}
@@ -19,7 +19,7 @@
19
19
  <p class="small">Hit Description: {{= v.hit_def }}</p>
20
20
  <table class="report_details">
21
21
  <tr>
22
- <td>E-value: {{= v.evalue }}</td>
22
+ <td>E-value: {{= QUORUM.formatEvalue(v.evalue) }}</td>
23
23
  <td>Bit Score: {{= v.bit_score }}</td>
24
24
  <td></td>
25
25
  </tr>
@@ -1,4 +1,4 @@
1
- <!-- Blast Results Template -->
1
+ <%# Blast Results Template %>
2
2
  <script type="text/template" id="blast_template">
3
3
  {{ if (data[0].results === false) { }}
4
4
  <p><strong>Your search returned 0 hits.</strong></p>
@@ -56,7 +56,7 @@
56
56
  {{= v.id }},
57
57
  '{{= v.query }}',
58
58
  '{{= algo }}')">
59
- {{= v.evalue }}
59
+ {{= QUORUM.formatEvalue(v.evalue) }}
60
60
  </a>
61
61
  </td>
62
62
  </tr>
@@ -232,18 +232,6 @@ module Quorum
232
232
  end
233
233
  end
234
234
 
235
- #
236
- # Make the E-value look pretty.
237
- #
238
- def format_evalue(evalue)
239
- evalue = evalue.to_s
240
- e = evalue.slice!(/e.*/)
241
- unless e.nil?
242
- e = " x 10<sup>" << e.sub(/e/, '') << "</sup>"
243
- end
244
- evalue.to_f.round(1).to_s << e.to_s
245
- end
246
-
247
235
  #
248
236
  # Format Blast report hit_display_id and hit_def.
249
237
  #
@@ -295,7 +283,7 @@ module Quorum
295
283
  @data[:hsp_num] = hsp.hsp_num
296
284
  @data[:bit_score] = hsp.bit_score
297
285
  @data[:score] = hsp.score
298
- @data[:evalue] = format_evalue(hsp.evalue)
286
+ @data[:evalue] = hsp.evalue
299
287
  @data[:query_from] = hsp.query_from
300
288
  @data[:query_to] = hsp.query_to
301
289
  @data[:hit_from] = hsp.hit_from
@@ -29,13 +29,13 @@ module Quorum
29
29
  # Removes instance files.
30
30
  #
31
31
  def remove_files(files)
32
- unless Dir.glob(files).empty?
33
- `rm #{files}`
34
- else
35
- log(
32
+ if Dir.glob(files).empty?
33
+ log(
36
34
  "remove_files",
37
35
  "Unable to remove #{files}"
38
36
  )
37
+ else
38
+ `rm #{files}`
39
39
  end
40
40
  end
41
41
 
@@ -1,3 +1,3 @@
1
1
  module Quorum
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
@@ -24,9 +24,7 @@ module Workers
24
24
  Net::SSH.start(ssh_host, ssh_user, ssh_options) do |ssh|
25
25
  ssh.open_channel do |ch|
26
26
  ch.exec(cmd) do |ch, success|
27
- unless success
28
- Rails.logger.warn "Channel Net::SSH exec() failed. :'("
29
- else
27
+ if success
30
28
  # Capture STDOUT from ch.exec()
31
29
  if stdout
32
30
  ch.on_data do |ch, data|
@@ -37,6 +35,8 @@ module Workers
37
35
  ch.on_request("exit-status") do |ch, data|
38
36
  exit_status = data.read_long
39
37
  end
38
+ else
39
+ Rails.logger.warn "Channel Net::SSH exec() failed. :'("
40
40
  end
41
41
  end
42
42
  end
Binary file
Binary file
@@ -13,9 +13,9 @@ module Quorum
13
13
  class QuorumJob < ActiveRecord::Base
14
14
  self.table_name = "quorum_jobs"
15
15
 
16
- has_one :quorum_blastn_job,
16
+ has_one :quorum_blastn_job,
17
17
  :foreign_key => "job_id"
18
- has_many :quorum_blastn_job_reports,
18
+ has_many :quorum_blastn_job_reports,
19
19
  :foreign_key => "blastn_job_id"
20
20
 
21
21
  has_one :quorum_blastx_job,
@@ -217,7 +217,7 @@ module Quorum
217
217
  File.open(@na_fasta, "w") { |f| f << @na_sequence }
218
218
  File.open(@aa_fasta, "w") { |f| f << @aa_sequence }
219
219
 
220
- @out = File.join(@tmp, @hash + ".out.xml")
220
+ @out = File.join(@tmp, @hash + ".out.xml")
221
221
  File.new(@out, "w")
222
222
 
223
223
  case @algorithm
@@ -232,18 +232,6 @@ module Quorum
232
232
  end
233
233
  end
234
234
 
235
- #
236
- # Make the E-value look pretty.
237
- #
238
- def format_evalue(evalue)
239
- evalue = evalue.to_s
240
- e = evalue.slice!(/e.*/)
241
- unless e.nil?
242
- e = " x 10<sup>" << e.sub(/e/, '') << "</sup>"
243
- end
244
- evalue.to_f.round(1).to_s << e.to_s
245
- end
246
-
247
235
  #
248
236
  # Format Blast report hit_display_id and hit_def.
249
237
  #
@@ -269,7 +257,7 @@ module Quorum
269
257
 
270
258
  #
271
259
  # Parse and save Blast results using bio-blastxmlparser.
272
- # Only save Blast results if results.bit_score > @min_score.
260
+ # Only save Blast results if results.bit_score > @min_score.
273
261
  #
274
262
  def parse_and_save_results
275
263
  # Helper to avoid having to perform a query.
@@ -295,7 +283,7 @@ module Quorum
295
283
  @data[:hsp_num] = hsp.hsp_num
296
284
  @data[:bit_score] = hsp.bit_score
297
285
  @data[:score] = hsp.score
298
- @data[:evalue] = format_evalue(hsp.evalue)
286
+ @data[:evalue] = hsp.evalue
299
287
  @data[:query_from] = hsp.query_from
300
288
  @data[:query_to] = hsp.query_to
301
289
  @data[:hit_from] = hsp.hit_from
@@ -311,7 +299,7 @@ module Quorum
311
299
 
312
300
  # Hsps are only reported if a query hit against the Blast db.
313
301
  # Only save the @data if bit_score exists.
314
- if @data[:bit_score] &&
302
+ if @data[:bit_score] &&
315
303
  (@data[:bit_score].to_i > @min_score.to_i)
316
304
  @data[:results] = true
317
305
  @data["#{@algorithm}_job_id".to_sym] = @job.method(@job_association).call.job_id
@@ -410,7 +398,7 @@ module Quorum
410
398
  # Execute Blast on a given dataset.
411
399
  #
412
400
  def execute_blast
413
- generate_blast_cmd
401
+ generate_blast_cmd
414
402
  @logger.log("NCBI Blast", @cmd)
415
403
  system(@cmd)
416
404
  parse_and_save_results
@@ -8,3 +8,8 @@
8
8
  <div id="tabs-2">Tab 2</div>
9
9
  <div id="tabs-3">Tab 3</div>
10
10
  </div>
11
+
12
+ <div id="detailed_report_dialog" title="Quorum Report Details"></div>
13
+ <script type="text/template" id="blast_template"></script>
14
+ <script type="text/template" id="detailed_report_template"></script>
15
+ <a id="download_sequence">Download Sequence</a>
@@ -1,9 +1,73 @@
1
1
  //
2
- // Test the methods not covered in RSpec request specs.
2
+ // QUORUM Specs
3
3
  //
4
4
 
5
5
  describe("QUORUM", function() {
6
6
 
7
+ //
8
+ // Spec covers QUORUM.algorithms.
9
+ //
10
+ // QUORUM.algorithms should be an array of strings.
11
+ //
12
+ it("contains an array of strings", function() {
13
+ expect(QUORUM.algorithms.join('')).toMatch(/[a-zA-Z]/g);
14
+ });
15
+
16
+ //
17
+ // Spec covers QUORUM.pollResults.
18
+ //
19
+ // QUORUM.pollResults calls itself via setTimeout() if the returned JSON
20
+ // dataset is empty.
21
+ //
22
+ // If callback is defined, call callback function. Otherwise call default
23
+ // anonymous function buildTemplate().
24
+ //
25
+ it("fetches JSON and calls user defined callback function", function() {
26
+ spyOn($, 'getJSON');
27
+ spyOn(window, 'setTimeout');
28
+ var id = 1,
29
+ callback = jasmine.createSpy(),
30
+ data = 'foo';
31
+
32
+ QUORUM.pollResults(id, callback, null, 5000, ['a']);
33
+
34
+ // setTimeout()
35
+ $.getJSON.mostRecentCall.args[1]('');
36
+ expect(window.setTimeout).toHaveBeenCalled();
37
+
38
+ // callback()
39
+ $.getJSON.mostRecentCall.args[1](data);
40
+ expect(callback).toHaveBeenCalledWith(id, data, 'a');
41
+ });
42
+
43
+ //
44
+ // Spec covers QUORUM.viewDetailedReport.
45
+ //
46
+ // Open detailed report dialog modal box and load template.
47
+ //
48
+ it("renders modal box containing detailed report", function() {
49
+ loadFixtures('quorum_tabs.html');
50
+
51
+ spyOn($, 'getJSON');
52
+ spyOn(QUORUM, 'autoScroll');
53
+ var id = 1,
54
+ focus_id = 1,
55
+ query = 'foo',
56
+ algo = 'a',
57
+ data = 'bar';
58
+
59
+ QUORUM.viewDetailedReport(id, focus_id, query, algo);
60
+
61
+ expect($("#detailed_report_dialog")).toBeVisible();
62
+
63
+ // Fetch JSON to build the template and scroll to focus_id.
64
+ $.getJSON.mostRecentCall.args[1](data);
65
+ expect(QUORUM.autoScroll).toHaveBeenCalledWith(focus_id, false);
66
+
67
+ // Close the dialog box.
68
+ $("#detailed_report_dialog").dialog('close');
69
+ });
70
+
7
71
  //
8
72
  // Spec covers QUORUM.formatSequenceReport & QUORUM.addBaseTitleIndex.
9
73
  //
@@ -52,6 +116,10 @@ describe("QUORUM", function() {
52
116
  expect(report).toEqual("<p class='small'>Alignment (Mouse over for positions):</p><span class='small'><pre>\nqseq <a rel='quorum-tipsy' title=10>E</a><a rel='quorum-tipsy' title=11>L</a><a rel='quorum-tipsy' title=12>V</a><a rel='quorum-tipsy' title=13>I</a><a rel='quorum-tipsy' title=14>S</a>\n ELVIS\nhseq <a rel='quorum-tipsy' title=121>E</a><a rel='quorum-tipsy' title=122>L</a><a rel='quorum-tipsy' title=123>V</a><a rel='quorum-tipsy' title=124>I</a><a rel='quorum-tipsy' title=125>S</a>\n\n</pre></span>");
53
117
  });
54
118
 
119
+ //
120
+ // jQuery tipsy plugin should display anchor's title attribute on mouseover
121
+ // and hide on mouseout.
122
+ //
55
123
  it("should display title via jquery.tipsy on mouse over hide on mouse out", function() {
56
124
  loadFixtures('formatted_sequence.html');
57
125
  $('a[rel=quorum-tipsy]').tipsy({ gravity: 's' });
@@ -63,6 +131,12 @@ describe("QUORUM", function() {
63
131
  expect($('.tipsy')).not.toBeVisible();
64
132
  });
65
133
 
134
+ //
135
+ // Spec covers QUORUM.formatStrand.
136
+ //
137
+ // If number is > 0 print forward.
138
+ // If number is < 0 print reverse.
139
+ //
66
140
  it("prints hit strand as forward / forward for + / + intergers", function() {
67
141
  expect(QUORUM.formatStrand(1, 1)).toEqual("forward / forward");
68
142
  });
@@ -79,26 +153,127 @@ describe("QUORUM", function() {
79
153
  expect(QUORUM.formatStrand(-1, -1)).toEqual("reverse / reverse");
80
154
  });
81
155
 
156
+ //
157
+ // Spec covers QUORUM.formatEvalue
158
+ //
159
+ // Format Blast Evalues for HTML.
160
+ //
161
+ it("returns empty string if evalue is not set", function() {
162
+ expect(QUORUM.formatEvalue("")).toEqual("");
163
+ expect(QUORUM.formatEvalue(null)).toEqual("");
164
+ expect(QUORUM.formatEvalue(undefined)).toEqual("");
165
+ });
166
+
167
+ it("formats blast evalue", function() {
168
+ expect(QUORUM.formatEvalue("0")).toEqual("0.0");
169
+ expect(QUORUM.formatEvalue("0.0")).toEqual("0.0");
170
+ expect(QUORUM.formatEvalue("1.23966346466766e-65")).toEqual("1.2 x 10<sup>-65</sup>");
171
+ expect(QUORUM.formatEvalue("1.2966346466766e-165")).toEqual("1.3 x 10<sup>-165</sup>");
172
+ expect(QUORUM.formatEvalue("1.23456789")).toEqual("1.2");
173
+ });
174
+
175
+ //
176
+ // Spec covers QUORUM.displayHspLinks.
82
177
  //
83
178
  // Only print links to HSPs whers data id != focus.
84
179
  //
85
180
  it("prints HSP links", function() {
86
- var focus = 1;
87
- var group = "1,2";
88
- var data = [
89
- {"id":1,"hsp_group":"1,2","hsp_num":1},
90
- {"id":2,"hsp_group":"1,2","hsp_num":2}
91
- ];
92
- var hsps = QUORUM.displayHspLinks(focus, group, data);
181
+ var focus = 1,
182
+ group = "1,2",
183
+ data = [
184
+ {"id":1,"hsp_group":"1,2","hsp_num":1},
185
+ {"id":2,"hsp_group":"1,2","hsp_num":2}
186
+ ];
187
+
188
+ var hsps = QUORUM.displayHspLinks(focus, group, data);
93
189
  expect(hsps).toEqual("Related <a onclick=\"(QUORUM.openWindow('http://www.ncbi.nlm.nih.gov/books/NBK62051/def-item/blast_glossary.HSP','HSP', 800, 300))\">HSPs</a>: 1 <a onclick='(QUORUM.autoScroll(2, true))'>2</a> ");
94
190
  });
95
191
 
192
+ //
193
+ // QUORUM.displayHspLinks should return an empty string if group is not set.
194
+ //
195
+ it("prints HSP links", function() {
196
+ var focus = 1,
197
+ group = null,
198
+ data = [
199
+ {"id":1,"hsp_group":"1,2","hsp_num":1},
200
+ {"id":2,"hsp_group":"1,2","hsp_num":2}
201
+ ];
202
+
203
+ var hsps = QUORUM.displayHspLinks(focus, group, data);
204
+ expect(hsps).toEqual("");
205
+ });
206
+
207
+ //
208
+ // Spec covers QUORUM.downloadSequence.
209
+ //
210
+ // Download a Blast hit sequence file.
211
+ //
212
+ it("sends request to server to extract Blast hit sequence", function() {
213
+ loadFixtures("quorum_tabs.html");
214
+
215
+ spyOn($, 'getJSON');
216
+ spyOn(QUORUM, 'getSequenceFile');
217
+ var id = 1,
218
+ algo_id = 1,
219
+ algo = 'a',
220
+ el = $("#download_sequence"),
221
+ data = [{meta_id:"foo"}];
222
+
223
+ QUORUM.downloadSequence(id, algo_id, algo, el);
224
+
225
+ expect(el.html()).toEqual('Fetching sequence...');
226
+
227
+ $.getJSON.mostRecentCall.args[1](data);
228
+ expect(QUORUM.getSequenceFile).toHaveBeenCalledWith(id, data[0].meta_id, el);
229
+ });
230
+
231
+ //
232
+ // Spec covers QUORUM.getSequenceFile.
233
+ //
234
+ // Poll application for Blast hit sequence file and force browser to
235
+ // download via iframe.
236
+ //
237
+ it("polls server to extract Blast hit sequence, once found, force browser to download via iframe", function() {
238
+ loadFixtures("quorum_tabs.html");
239
+
240
+ spyOn($, 'get');
241
+ spyOn(window, 'setTimeout');
242
+ var id = 1,
243
+ meta_id = 'foo',
244
+ el = $("#download_sequence"),
245
+ data = 'bar',
246
+ error = 'error';
247
+
248
+ QUORUM.getSequenceFile(id, meta_id, el);
249
+
250
+ // setTimeout()
251
+ $.get.mostRecentCall.args[1]('');
252
+ expect(window.setTimeout).toHaveBeenCalled();
253
+
254
+ // Print error message
255
+ $.get.mostRecentCall.args[1](error);
256
+ expect(el.html()).toEqual(error);
257
+
258
+ // Force browser to download file.
259
+ $.get.mostRecentCall.args[1](data);
260
+ expect(el.html()).toEqual('Sequence Downloaded Successfully');
261
+ expect($('iframe.quorum_sequence_download')).toBeDefined();
262
+ $('.quorum_sequence_download').remove();
263
+ });
264
+
265
+ //
266
+ // Spec covers QUORUM.openWindow.
267
+ //
268
+ // Open a URL in a new window.
269
+ //
96
270
  it("opens url in a new window", function() {
97
271
  spyOn(window, 'open');
98
- var url = "http://google.com";
99
- var name = "Google";
100
- var width = 300;
101
- var height = 300;
272
+ var url = "http://google.com",
273
+ name = "Google",
274
+ width = 300,
275
+ height = 300;
276
+
102
277
  QUORUM.openWindow(url, name, width, height);
103
278
  expect(window.open).toHaveBeenCalledWith(url, name, "width=" + width + ",height=" + height + ",scrollbars=yes");
104
279
  });
@@ -49,8 +49,9 @@ describe "Jobs" do
49
49
 
50
50
  context "javascript", @javascript do
51
51
  before(:all) do
52
- Capybara.current_driver = :selenium
53
52
  Capybara.default_wait_time = 5
53
+ Capybara.server_port = 53331
54
+ Capybara.current_driver = :selenium
54
55
  end
55
56
  before(:each) do
56
57
  ResqueSpec.reset!
@@ -142,12 +143,30 @@ describe "Jobs" do
142
143
  end
143
144
  end
144
145
 
145
- describe "GET /quorum/jobs/unknown_id" do
146
- it "displays notice and renders form" do
146
+ describe "GET /quorum/jobs/id" do
147
+ it "displays notice and renders form with invalid id" do
147
148
  visit job_path('12893479812347912')
148
149
  page.should have_content("The data you requested is unavailable. Please check your URL and try again.")
149
150
  current_path.should eq(new_job_path)
150
151
  end
151
152
  end
152
153
 
154
+ describe "GET /quorum/jobs/id/get_quorum_search_results" do
155
+ it "renders JSON results => false with invalid id" do
156
+ visit "/quorum/jobs/23542352345/get_quorum_search_results.json"
157
+ page.should have_content("[{\"results\":false}]")
158
+ end
159
+ end
160
+
161
+ describe "GET /quorum/jobs/id/get_quorum_blast_hit_sequence" do
162
+ it "renders empty JSON with invalid id" do
163
+ visit "/quorum/jobs/23542352345/get_quorum_blast_hit_sequence.json"
164
+ page.should have_content("[]")
165
+ end
166
+
167
+ it "renders empty JSON with invalid id and valid params" do
168
+ visit "/quorum/jobs/23542352345/get_quorum_blast_hit_sequence.json?algo=blastn"
169
+ page.should have_content("[]")
170
+ end
171
+ end
153
172
  end
@@ -4,7 +4,7 @@ require 'generators/templates/logger'
4
4
  describe "Quorum::Logger" do
5
5
  describe "#log" do
6
6
  before(:all) do
7
- @args = File.join(::Rails.root.to_s, "log")
7
+ @args = File.join(::Rails.root.to_s, "log")
8
8
  @logger = Quorum::Logger.new(@args)
9
9
  end
10
10
 
@@ -21,7 +21,7 @@ describe "Quorum::Logger" do
21
21
  it "records program, message, exits and removes files" do
22
22
  lambda {
23
23
  @logger.log(
24
- "RSpec", "This is a test.", 1,
24
+ "RSpec", "This is a test.", 1,
25
25
  File.join(@args, "quorum.log")
26
26
  )
27
27
  }.should raise_error(SystemExit)
@@ -31,5 +31,5 @@ describe "Quorum::Logger" do
31
31
  ).should be_false
32
32
 
33
33
  end
34
- end
34
+ end
35
35
  end