ncs_mdes_warehouse 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +27 -0
- data/Rakefile +2 -2
- data/lib/ncs_navigator/warehouse.rb +2 -0
- data/lib/ncs_navigator/warehouse/configuration.rb +50 -0
- data/lib/ncs_navigator/warehouse/hooks.rb +21 -0
- data/lib/ncs_navigator/warehouse/hooks/etl_status_email.rb +88 -0
- data/lib/ncs_navigator/warehouse/mailer_templates/etl_status_email/failure_message.text.erb +8 -0
- data/lib/ncs_navigator/warehouse/mailer_templates/etl_status_email/success_message.text.erb +7 -0
- data/lib/ncs_navigator/warehouse/stringify_trace.rb +31 -0
- data/lib/ncs_navigator/warehouse/transform_load.rb +23 -3
- data/lib/ncs_navigator/warehouse/transform_status.rb +11 -2
- data/lib/ncs_navigator/warehouse/transformers/database.rb +13 -1
- data/lib/ncs_navigator/warehouse/transformers/enum_transformer.rb +65 -20
- data/lib/ncs_navigator/warehouse/transformers/subprocess_transformer.rb +4 -1
- data/lib/ncs_navigator/warehouse/transformers/vdr_xml/reader.rb +8 -0
- data/lib/ncs_navigator/warehouse/version.rb +1 -1
- data/ncs_mdes_warehouse.gemspec +1 -0
- data/spec/navigator.ini +3 -0
- data/spec/ncs_navigator/warehouse/configuration_spec.rb +79 -2
- data/spec/ncs_navigator/warehouse/data_mapper_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse/database_initializer_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse/hooks/etl_status_email_spec.rb +126 -0
- data/spec/ncs_navigator/warehouse/models/mdes_model_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse/postgresql/pgpass_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse/stringify_trace_spec.rb +58 -0
- data/spec/ncs_navigator/warehouse/table_modeler_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse/transform_load_spec.rb +119 -5
- data/spec/ncs_navigator/warehouse/transform_status_spec.rb +32 -1
- data/spec/ncs_navigator/warehouse/transformers/database_spec.rb +11 -1
- data/spec/ncs_navigator/warehouse/transformers/enum_transformer_spec.rb +75 -1
- data/spec/ncs_navigator/warehouse/transformers/sampling_units_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse/transformers/subprocess_transformer_spec.rb +7 -1
- data/spec/ncs_navigator/warehouse/transformers/vdr_xml/reader_spec.rb +14 -3
- data/spec/ncs_navigator/warehouse/transformers/vdr_xml_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse/xml_emitter_spec.rb +1 -1
- data/spec/ncs_navigator/warehouse_spec.rb +1 -1
- data/spec/spec_helper.rb +3 -1
- 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
|
-
|
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
|
data/ncs_mdes_warehouse.gemspec
CHANGED
data/spec/navigator.ini
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
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 }
|
@@ -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
|
+
|
@@ -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
|
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 '
|
166
|
-
|
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
|
-
|
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
|