ncs_mdes_warehouse 0.4.1 → 0.5.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/CHANGELOG.md +27 -0
  2. data/Rakefile +2 -2
  3. data/lib/ncs_navigator/warehouse.rb +2 -0
  4. data/lib/ncs_navigator/warehouse/configuration.rb +50 -0
  5. data/lib/ncs_navigator/warehouse/hooks.rb +21 -0
  6. data/lib/ncs_navigator/warehouse/hooks/etl_status_email.rb +88 -0
  7. data/lib/ncs_navigator/warehouse/mailer_templates/etl_status_email/failure_message.text.erb +8 -0
  8. data/lib/ncs_navigator/warehouse/mailer_templates/etl_status_email/success_message.text.erb +7 -0
  9. data/lib/ncs_navigator/warehouse/stringify_trace.rb +31 -0
  10. data/lib/ncs_navigator/warehouse/transform_load.rb +23 -3
  11. data/lib/ncs_navigator/warehouse/transform_status.rb +11 -2
  12. data/lib/ncs_navigator/warehouse/transformers/database.rb +13 -1
  13. data/lib/ncs_navigator/warehouse/transformers/enum_transformer.rb +65 -20
  14. data/lib/ncs_navigator/warehouse/transformers/subprocess_transformer.rb +4 -1
  15. data/lib/ncs_navigator/warehouse/transformers/vdr_xml/reader.rb +8 -0
  16. data/lib/ncs_navigator/warehouse/version.rb +1 -1
  17. data/ncs_mdes_warehouse.gemspec +1 -0
  18. data/spec/navigator.ini +3 -0
  19. data/spec/ncs_navigator/warehouse/configuration_spec.rb +79 -2
  20. data/spec/ncs_navigator/warehouse/data_mapper_spec.rb +1 -1
  21. data/spec/ncs_navigator/warehouse/database_initializer_spec.rb +1 -1
  22. data/spec/ncs_navigator/warehouse/hooks/etl_status_email_spec.rb +126 -0
  23. data/spec/ncs_navigator/warehouse/models/mdes_model_spec.rb +1 -1
  24. data/spec/ncs_navigator/warehouse/postgresql/pgpass_spec.rb +1 -1
  25. data/spec/ncs_navigator/warehouse/stringify_trace_spec.rb +58 -0
  26. data/spec/ncs_navigator/warehouse/table_modeler_spec.rb +1 -1
  27. data/spec/ncs_navigator/warehouse/transform_load_spec.rb +119 -5
  28. data/spec/ncs_navigator/warehouse/transform_status_spec.rb +32 -1
  29. data/spec/ncs_navigator/warehouse/transformers/database_spec.rb +11 -1
  30. data/spec/ncs_navigator/warehouse/transformers/enum_transformer_spec.rb +75 -1
  31. data/spec/ncs_navigator/warehouse/transformers/sampling_units_spec.rb +1 -1
  32. data/spec/ncs_navigator/warehouse/transformers/subprocess_transformer_spec.rb +7 -1
  33. data/spec/ncs_navigator/warehouse/transformers/vdr_xml/reader_spec.rb +14 -3
  34. data/spec/ncs_navigator/warehouse/transformers/vdr_xml_spec.rb +1 -1
  35. data/spec/ncs_navigator/warehouse/xml_emitter_spec.rb +1 -1
  36. data/spec/ncs_navigator/warehouse_spec.rb +1 -1
  37. data/spec/spec_helper.rb +3 -1
  38. metadata +307 -154
@@ -3,6 +3,7 @@ require 'childprocess'
3
3
  require 'forwardable'
4
4
  require 'json'
5
5
  require 'fileutils'
6
+ require 'bundler'
6
7
 
7
8
  module NcsNavigator::Warehouse::Transformers
8
9
  ##
@@ -99,7 +100,9 @@ module NcsNavigator::Warehouse::Transformers
99
100
  shell.say "Spawning subprocess transformer `#{exec_and_args.join(' ')}`"
100
101
 
101
102
  FileUtils.cd directory do
102
- process.start
103
+ Bundler.with_clean_env do
104
+ process.start
105
+ end
103
106
  end
104
107
 
105
108
  log.info " PID is #{process.pid}"
@@ -39,6 +39,14 @@ class NcsNavigator::Warehouse::Transformers::VdrXml
39
39
  @record_count = 0
40
40
  end
41
41
 
42
+ def name
43
+ if @filename
44
+ "VDR XML #{@filename}"
45
+ else
46
+ "VDR XML #{@io.inspect}"
47
+ end
48
+ end
49
+
42
50
  ##
43
51
  # Reads the XML and yields each encountered record as a warehouse
44
52
  # model instance to the provided block. It does not validate the
@@ -1,5 +1,5 @@
1
1
  module NcsNavigator
2
2
  module Warehouse
3
- VERSION = '0.4.1'
3
+ VERSION = '0.5.0'
4
4
  end
5
5
  end
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  end
38
38
 
39
39
  s.add_dependency 'bcdatabase', '~> 1.1'
40
+ s.add_dependency 'actionmailer', '~> 3.0'
40
41
 
41
42
  s.add_development_dependency 'rspec', '~> 2.6'
42
43
  s.add_development_dependency 'rake', '~> 0.9.2'
data/spec/navigator.ini CHANGED
@@ -11,3 +11,6 @@ uri = "https://locahost/core"
11
11
 
12
12
  [PSC]
13
13
  uri = "https://localhost/psc"
14
+
15
+ [SMTP]
16
+ host = "smtp.ncs.gov"
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  module NcsNavigator::Warehouse
4
4
  describe Configuration do
@@ -41,6 +41,54 @@ module NcsNavigator::Warehouse
41
41
  end
42
42
  end
43
43
 
44
+ describe 'add_post_etl_hook' do
45
+ let(:success_hook) {
46
+ Object.new.tap do |o|
47
+ class << o
48
+ def etl_succeeded; end
49
+ end
50
+ end
51
+ }
52
+
53
+ let(:failure_hook) {
54
+ Object.new.tap do |o|
55
+ class << o
56
+ def etl_failed; end
57
+ end
58
+ end
59
+ }
60
+
61
+ it 'adds a hook with an `etl_succeeded` method' do
62
+ config.add_post_etl_hook(success_hook)
63
+ config.post_etl_hooks.should == [success_hook]
64
+ end
65
+
66
+ it 'adds a hook with an `etl_failed` method' do
67
+ config.add_post_etl_hook(failure_hook)
68
+ config.post_etl_hooks.should == [failure_hook]
69
+ end
70
+
71
+ describe 'with an object without either etl callback method' do
72
+ it 'gives a helpful message if the object is constructable' do
73
+ lambda { config.add_post_etl_hook(String) }.
74
+ should raise_error('String does not have an etl_succeeded or etl_failed method. Perhaps you meant String.new?')
75
+ end
76
+
77
+ it 'gives a helpful message if the object is an instance' do
78
+ lambda { config.add_post_etl_hook('not a good hook') }.
79
+ should raise_error('"not a good hook" does not have an etl_succeeded or etl_failed method.')
80
+ end
81
+
82
+ it 'does not add the object as a hook' do
83
+ begin
84
+ config.add_post_etl_hook('not a good hook')
85
+ rescue
86
+ end
87
+ config.post_etl_hooks.should be_empty
88
+ end
89
+ end
90
+ end
91
+
44
92
  describe '#mdes_version=' do
45
93
  context 'for a known version', :slow, :use_mdes, :modifies_warehouse_state do
46
94
  it 'makes the models available' do
@@ -141,7 +189,10 @@ module NcsNavigator::Warehouse
141
189
 
142
190
  describe '#shell_io' do
143
191
  it 'is $stderr by default' do
144
- config.shell_io.should be($stderr)
192
+ config.shell_io.object_id.should be($stderr.object_id)
193
+ # `should be($stderr)` and `should equal($stderr)` do not work
194
+ # with RSpec 2.10 and ci_reporter 1.6.6 because `should` is
195
+ # apparently delegated to the wrapped stream.
145
196
  end
146
197
 
147
198
  it 'can be set' do
@@ -281,6 +332,32 @@ module NcsNavigator::Warehouse
281
332
  end
282
333
  end
283
334
 
335
+ describe '#set_up_action_mailer' do
336
+ before do
337
+ config.set_up_action_mailer
338
+ end
339
+
340
+ it 'sets the SMTP settings from navigator.ini' do
341
+ ActionMailer::Base.smtp_settings[:address].should == 'smtp.ncs.gov'
342
+ end
343
+
344
+ it 'sets the delivery mode to SMTP' do
345
+ ActionMailer::Base.delivery_method.should == :smtp
346
+ end
347
+
348
+ it 'sets the template path appropriately' do
349
+ ActionMailer::Base.view_paths.collect(&:to_s).should == [
350
+ File.expand_path('../../../../lib/ncs_navigator/warehouse/mailer_templates', __FILE__)
351
+ ]
352
+ end
353
+
354
+ it 'only gets set up once' do
355
+ ActionMailer::Base.delivery_method = :test
356
+ config.set_up_action_mailer
357
+ ActionMailer::Base.delivery_method.should == :test
358
+ end
359
+ end
360
+
284
361
  describe 'bcdatabase' do
285
362
  describe 'group', :modifies_warehouse_state do
286
363
  subject { config.bcdatabase_group }
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  module NcsNavigator::Warehouse::DataMapper
4
4
  shared_examples 'a string-based NCS type' do
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  module NcsNavigator::Warehouse
4
4
  describe DatabaseInitializer do
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ module NcsNavigator::Warehouse::Hooks
4
+ describe EtlStatusEmail do
5
+ let(:config) {
6
+ NcsNavigator::Warehouse::Configuration.new.tap do |c|
7
+ c.log_file = tmpdir + "#{File.basename(__FILE__)}.log"
8
+ c.output_level = :quiet
9
+ end
10
+ }
11
+
12
+ let(:mailer) {
13
+ EtlStatusEmail.new(:to => to)
14
+ }
15
+
16
+ let(:to) {
17
+ ['jane@gcsc.net', 'joe@northwestern.edu']
18
+ }
19
+
20
+ let(:statuses) {
21
+ [
22
+ NcsNavigator::Warehouse::TransformStatus.memory_only('SP',
23
+ :start_time => Time.parse('2011-04-03T00:06:23'),
24
+ :end_time => Time.parse('2011-04-03T00:53:00'),
25
+ :record_count => 5,
26
+ :position => 0),
27
+ NcsNavigator::Warehouse::TransformStatus.memory_only('Co',
28
+ :start_time => Time.parse('2011-04-03T00:53:01'),
29
+ :end_time => Time.parse('2011-04-03T02:32:32'),
30
+ :record_count => 11,
31
+ :position => 1)
32
+ ]
33
+ }
34
+
35
+ let(:hook_args) {
36
+ {
37
+ :configuration => config,
38
+ :transform_statuses => statuses
39
+ }
40
+ }
41
+
42
+ let(:message) { ActionMailer::Base.deliveries.first }
43
+
44
+ before do
45
+ config.set_up_action_mailer
46
+ ActionMailer::Base.delivery_method = :test
47
+ end
48
+
49
+ after do
50
+ ActionMailer::Base.deliveries.clear
51
+ end
52
+
53
+ shared_examples 'an ETL result call' do
54
+ it 'sends an e-mail message' do
55
+ ActionMailer::Base.deliveries.size.should == 1
56
+ end
57
+
58
+ it 'sends the message to the configured addresses' do
59
+ message.to.should == to
60
+ end
61
+
62
+ it 'sends the message to the appropriate people based on their staff roles' do
63
+ pending 'would be nice: #2083'
64
+ end
65
+
66
+ describe 'the message content' do
67
+ it 'includes the duration' do
68
+ message.body.should =~ /2:26:09/
69
+ end
70
+
71
+ it 'includes the record count' do
72
+ message.body.should =~ /16 total records/
73
+ end
74
+
75
+ it 'includes a link to the warehouse dashboard' do
76
+ pending "there isn't one"
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '#etl_succeeded' do
82
+ before do
83
+ mailer.etl_succeeded(hook_args)
84
+ end
85
+
86
+ include_examples 'an ETL result call'
87
+
88
+ describe 'the message subject' do
89
+ it 'indicates that the ETL succeeded' do
90
+ message.subject.should == '[NCS Navigator] Warehouse load successful'
91
+ end
92
+ end
93
+
94
+ describe 'the message content' do
95
+ it 'indicates that the ETL succeeded' do
96
+ message.body.should =~ /All transformations executed successfully./
97
+ end
98
+ end
99
+ end
100
+
101
+ describe '#etl_failed' do
102
+ before do
103
+ statuses[1].transform_errors <<
104
+ NcsNavigator::Warehouse::TransformError.new(:message => 'Not good enough')
105
+
106
+ mailer.etl_failed(hook_args)
107
+ end
108
+
109
+ include_examples 'an ETL result call'
110
+
111
+ describe 'the message subject' do
112
+ it 'indicates that the ETL failed' do
113
+ message.subject.should == '[NCS Navigator] Warehouse load failed'
114
+ end
115
+ end
116
+
117
+ describe 'the message content' do
118
+ it 'indicates that the ETL failed' do
119
+ message.body.
120
+ should =~ /1 transformation executed successfully.\s+1 transformation failed./
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../../../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  module NcsNavigator::Warehouse::Models
4
4
  module Spec
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../../../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  module NcsNavigator::Warehouse::PostgreSQL
4
4
  describe Pgpass do
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ module NcsNavigator::Warehouse
4
+ describe StringifyTrace do
5
+ describe '#stringify_trace' do
6
+ it 'is available when mixed in' do
7
+ host = Class.new do
8
+ include StringifyTrace
9
+
10
+ def m
11
+ stringify_trace(['foo'])
12
+ end
13
+ end.new
14
+
15
+ lambda { host.m }.should_not raise_error
16
+ end
17
+ end
18
+
19
+ describe '.stringify_trace' do
20
+ let(:simple_trace) {
21
+ [
22
+ "(irb):2:in `irb_binding'",
23
+ "workspace.rb:80:in `eval'",
24
+ "foo/caller.rb:1024:in `<main>'"
25
+ ]
26
+ }
27
+
28
+ it 'aligns everything nicely' do
29
+ StringifyTrace.stringify_trace(simple_trace).should == <<-EXPECTED.chomp
30
+ (irb): 2:in `irb_binding'
31
+ workspace.rb: 80:in `eval'
32
+ foo/caller.rb:1024:in `<main>'
33
+ EXPECTED
34
+ end
35
+
36
+ it 'includes lines that cannot be parsed' do
37
+ StringifyTrace.stringify_trace(simple_trace + ['something up over here']).
38
+ should == <<-EXPECTED.chomp
39
+ (irb): 2:in `irb_binding'
40
+ workspace.rb: 80:in `eval'
41
+ foo/caller.rb:1024:in `<main>'
42
+ something up over here
43
+ EXPECTED
44
+ end
45
+
46
+ it 'aligns lines without specific context info' do
47
+ StringifyTrace.stringify_trace(simple_trace + ['bar/zap.rb:423']).
48
+ should == <<-EXPECTED.chomp
49
+ (irb): 2:in `irb_binding'
50
+ workspace.rb: 80:in `eval'
51
+ foo/caller.rb:1024:in `<main>'
52
+ bar/zap.rb: 423
53
+ EXPECTED
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  require 'ncs_navigator/mdes'
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path('../../../spec_helper', __FILE__)
1
+ require 'spec_helper'
2
2
 
3
3
  module NcsNavigator::Warehouse
4
4
  describe TransformLoad, :modifies_warehouse_state do
@@ -114,12 +114,13 @@ module NcsNavigator::Warehouse
114
114
 
115
115
  describe 'with a failure' do
116
116
  let(:runs) { [] }
117
+ let(:statuses) { TransformStatus.all(:order => [:id.desc]) }
117
118
 
118
119
  before do
119
120
  config.add_transformer(BlockTransformer.new { |s| runs << 'A' })
120
121
  config.add_transformer(BlockTransformer.new { |s|
121
122
  runs << 'B'
122
- fail 'No thanks.'
123
+ fail 'No thanks'
123
124
  })
124
125
  config.add_transformer(BlockTransformer.new { |s| runs << 'C3' })
125
126
  @result = loader.run
@@ -132,6 +133,11 @@ module NcsNavigator::Warehouse
132
133
  it 'returns false' do
133
134
  @result.should == false
134
135
  end
136
+
137
+ it 'records the error including the trace' do
138
+ statuses[1].transform_errors.first.message.
139
+ should =~ /No thanks\n.*#{File.basename(__FILE__)}:\s*\d+/
140
+ end
135
141
  end
136
142
 
137
143
  describe 'with an integrity error on transaction commit' do
@@ -162,10 +168,86 @@ module NcsNavigator::Warehouse
162
168
  end
163
169
  end
164
170
 
165
- describe 'sending e-mail' do
166
- it 'sends on success'
171
+ describe 'with post-ETL hooks' do
172
+ let(:hook_a) { RecordingHook.new }
173
+ let(:hook_success) { RecordingHook.new(:succeeded) }
174
+ let(:hook_failure) { RecordingHook.new(:failed) }
175
+ let(:hook_error) {
176
+ Class.new do
177
+ def etl_succeeded(ts)
178
+ fail 'Hook broke'
179
+ end
180
+ alias :etl_failed :etl_succeeded
181
+ end.new
182
+ }
183
+
184
+ let(:hooks_invoked) {
185
+ [hook_a, hook_success, hook_failure].map(&:invoked?)
186
+ }
187
+
188
+ before do
189
+ config.add_post_etl_hook(hook_a)
190
+ config.add_post_etl_hook(hook_success)
191
+ config.add_post_etl_hook(hook_failure)
192
+ end
193
+
194
+ shared_examples 'all hooks' do
195
+ before do
196
+ loader.run
197
+ end
198
+
199
+ it 'sends each hook the statuses' do
200
+ hook_a.transform_statuses.size.should == 1
201
+ end
202
+
203
+ it 'sends each hook the configuration' do
204
+ hook_a.configuration.should == config
205
+ end
206
+
207
+ it 'runs all the hooks even when one fails' do
208
+ config.post_etl_hooks.unshift hook_error
209
+
210
+ loader.run
211
+
212
+ hooks_invoked.should == expected_invoke_pattern
213
+ end
214
+ end
215
+
216
+ describe 'when the transform fails' do
217
+ let(:expected_invoke_pattern) { [true, false, true] }
218
+
219
+ before do
220
+ config.add_transformer(BlockTransformer.new { |s| fail 'Nope.' })
221
+ end
222
+
223
+ include_examples 'all hooks'
167
224
 
168
- it 'sends on failure'
225
+ it 'runs all the hooks that have an etl_failed method' do
226
+ hooks_invoked.should == expected_invoke_pattern
227
+ end
228
+
229
+ it 'sends each hook the overall status' do
230
+ hook_a.should be_failure
231
+ end
232
+ end
233
+
234
+ describe 'when the transform succeeds' do
235
+ let(:expected_invoke_pattern) { [true, true, false] }
236
+
237
+ before do
238
+ config.add_transformer(BlockTransformer.new { })
239
+ end
240
+
241
+ include_examples 'all hooks'
242
+
243
+ it 'runs all the hooks that have an etl_succeeded method' do
244
+ hooks_invoked.should == expected_invoke_pattern
245
+ end
246
+
247
+ it 'sends each hook the overall status' do
248
+ hook_a.should be_success
249
+ end
250
+ end
169
251
  end
170
252
 
171
253
  # This is a crappy test; it would be better if it could be done
@@ -195,5 +277,37 @@ module NcsNavigator::Warehouse
195
277
  @block.call(status)
196
278
  end
197
279
  end
280
+
281
+ class ::RecordingHook
282
+ attr_reader :transform_statuses, :configuration
283
+
284
+ def initialize(*modes)
285
+ @invoked = false
286
+ @modes = modes.empty? ? [:succeeded, :failed] : modes
287
+
288
+ @modes.each do |mode|
289
+ instance_eval <<-RUBY
290
+ def etl_#{mode}(args)
291
+ @invoked = true
292
+ @transform_statuses = args[:transform_statuses]
293
+ @configuration = args[:configuration]
294
+ @success = #{mode == :succeeded}
295
+ end
296
+ RUBY
297
+ end
298
+ end
299
+
300
+ def invoked?
301
+ @invoked
302
+ end
303
+
304
+ def success?
305
+ @success
306
+ end
307
+
308
+ def failure?
309
+ !@success.nil? && !@success
310
+ end
311
+ end
198
312
  end
199
313
  end