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.
- 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
|