quorum 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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