flapjack 0.6.61 → 0.7.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/Gemfile +2 -1
- data/README.md +8 -4
- data/features/events.feature +269 -146
- data/features/notification_rules.feature +93 -0
- data/features/steps/events_steps.rb +162 -21
- data/features/steps/notifications_steps.rb +1 -1
- data/features/steps/time_travel_steps.rb +30 -19
- data/features/support/env.rb +71 -1
- data/flapjack.gemspec +3 -0
- data/lib/flapjack/data/contact.rb +256 -57
- data/lib/flapjack/data/entity.rb +2 -1
- data/lib/flapjack/data/entity_check.rb +22 -7
- data/lib/flapjack/data/global.rb +1 -0
- data/lib/flapjack/data/message.rb +2 -0
- data/lib/flapjack/data/notification_rule.rb +172 -0
- data/lib/flapjack/data/tag.rb +7 -2
- data/lib/flapjack/data/tag_set.rb +16 -0
- data/lib/flapjack/executive.rb +147 -13
- data/lib/flapjack/filters/delays.rb +21 -9
- data/lib/flapjack/gateways/api.rb +407 -27
- data/lib/flapjack/gateways/pagerduty.rb +1 -1
- data/lib/flapjack/gateways/web.rb +50 -22
- data/lib/flapjack/gateways/web/views/self_stats.haml +2 -0
- data/lib/flapjack/utility.rb +10 -0
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/data/contact_spec.rb +103 -6
- data/spec/lib/flapjack/data/global_spec.rb +2 -0
- data/spec/lib/flapjack/data/message_spec.rb +6 -0
- data/spec/lib/flapjack/data/notification_rule_spec.rb +22 -0
- data/spec/lib/flapjack/data/notification_spec.rb +6 -0
- data/spec/lib/flapjack/gateways/api_spec.rb +727 -4
- data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -0
- data/spec/lib/flapjack/gateways/web_spec.rb +11 -1
- data/spec/spec_helper.rb +10 -0
- data/tmp/notification_rules.rb +73 -0
- data/tmp/test_json_post.rb +16 -0
- data/tmp/test_notification_rules_api.rb +170 -0
- metadata +59 -2
@@ -104,6 +104,7 @@ describe Flapjack::Gateways::Jabber, :logger => true do
|
|
104
104
|
|
105
105
|
attempts = 0
|
106
106
|
|
107
|
+
EventMachine::Synchrony.should_receive(:sleep).with(5).exactly(1).times
|
107
108
|
EventMachine::Synchrony.should_receive(:sleep).with(2).exactly(3).times
|
108
109
|
fj.should_receive(:connect).exactly(4).times.and_return {
|
109
110
|
attempts +=1
|
@@ -16,6 +16,15 @@ describe Flapjack::Gateways::Web, :sinatra => true, :logger => true do
|
|
16
16
|
|
17
17
|
let(:redis) { mock('redis') }
|
18
18
|
|
19
|
+
before(:all) do
|
20
|
+
Flapjack::Gateways::Web.class_eval {
|
21
|
+
set :raise_errors, true
|
22
|
+
}
|
23
|
+
Flapjack::Gateways::Web.instance_variable_get('@middleware').delete_if {|m|
|
24
|
+
m[0] == Rack::FiberPool
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
19
28
|
before(:each) do
|
20
29
|
Flapjack::RedisPool.should_receive(:new).and_return(redis)
|
21
30
|
Flapjack::Gateways::Web.instance_variable_set('@config', {})
|
@@ -174,6 +183,7 @@ describe Flapjack::Gateways::Web, :sinatra => true, :logger => true do
|
|
174
183
|
|
175
184
|
post "/scheduled_maintenances/#{entity_name_esc}/ping?"+
|
176
185
|
"start_time=1+day+ago&duration=30+minutes&summary=wow"
|
186
|
+
|
177
187
|
last_response.status.should == 302
|
178
188
|
end
|
179
189
|
|
@@ -227,7 +237,7 @@ describe Flapjack::Gateways::Web, :sinatra => true, :logger => true do
|
|
227
237
|
contact = mock('contact')
|
228
238
|
contact.should_receive(:name).twice.and_return("Smithson Smith")
|
229
239
|
contact.should_receive(:media).exactly(3).times.and_return({})
|
230
|
-
contact.should_receive(:
|
240
|
+
contact.should_receive(:entities).with(:checks => true).and_return([])
|
231
241
|
|
232
242
|
Flapjack::Data::Contact.should_receive(:find_by_id).
|
233
243
|
with('0362', :redis => redis).and_return(contact)
|
data/spec/spec_helper.rb
CHANGED
@@ -9,6 +9,8 @@ end
|
|
9
9
|
$testing = true
|
10
10
|
|
11
11
|
FLAPJACK_ENV = ENV["FLAPJACK_ENV"] || 'test'
|
12
|
+
ENV['RACK_ENV'] = ENV["FLAPJACK_ENV"]
|
13
|
+
|
12
14
|
require 'bundler'
|
13
15
|
Bundler.require(:default, :test)
|
14
16
|
|
@@ -37,6 +39,11 @@ class MockLogger
|
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
42
|
+
JsonSpec.configure do
|
43
|
+
# so we include id
|
44
|
+
exclude_keys "created_at", "updated_at"
|
45
|
+
end
|
46
|
+
|
40
47
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
41
48
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
42
49
|
# Require this file using `require "spec_helper"` to ensure that it is only
|
@@ -76,6 +83,8 @@ RSpec.configure do |config|
|
|
76
83
|
config.around(:each, :logger => true) do |example|
|
77
84
|
@logger = MockLogger.new
|
78
85
|
example.run
|
86
|
+
#messages = @logger.messages.compact
|
87
|
+
#p "logger: " + messages.join(", ") unless messages.empty?
|
79
88
|
@logger.messages.clear
|
80
89
|
end
|
81
90
|
|
@@ -85,4 +94,5 @@ RSpec.configure do |config|
|
|
85
94
|
|
86
95
|
config.include HamlViewHelper, :haml_view => true
|
87
96
|
config.include Rack::Test::Methods, :sinatra => true
|
97
|
+
config.include JsonSpec::Helpers, :json => true
|
88
98
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
#tuple = [
|
6
|
+
# [ alice, sms ],
|
7
|
+
# [ alice, email ],
|
8
|
+
# [ boby, email ],
|
9
|
+
# [ carol, sms ],
|
10
|
+
#]
|
11
|
+
|
12
|
+
event
|
13
|
+
|
14
|
+
# delete media based on entity name(s), tags, severity, time of day
|
15
|
+
# first get all rules matching entity and time
|
16
|
+
tuple.map! do |contact, media|
|
17
|
+
rules = contact.notification_rules
|
18
|
+
# filter based on tags, severity, time of day
|
19
|
+
matchers = contact.notification_rules do |rule|
|
20
|
+
rule.match_entity?(event) && rule.match_time?(event)
|
21
|
+
end
|
22
|
+
matchers.empty? ? nil : [ contact, media, matchers ]
|
23
|
+
end
|
24
|
+
|
25
|
+
# matchers are rules of the contact that have matched the current event
|
26
|
+
# for time and entity
|
27
|
+
|
28
|
+
tuple.compact!
|
29
|
+
|
30
|
+
# tuple = [
|
31
|
+
# [ alice, sms, matchers ],
|
32
|
+
# [ boby, email, matchers ],
|
33
|
+
# ]
|
34
|
+
|
35
|
+
# delete the matcher for all entities if there are more specific matchers
|
36
|
+
tuple = tuple.map do |contact, media, matchers|
|
37
|
+
if matchers.lengh > 1
|
38
|
+
have_specific = matchers.detect do |matcher|
|
39
|
+
matcher.entities or matcher.entity_tags
|
40
|
+
end
|
41
|
+
if have_specific
|
42
|
+
# delete the rule for all entities
|
43
|
+
matchers.map! do |matcher|
|
44
|
+
matcher.entities.nil? and matcher.entity_tags.nil? ? nil : matcher
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
[contact, media, matchers]
|
49
|
+
end
|
50
|
+
|
51
|
+
# delete media based on blackholes
|
52
|
+
tuple = tuple.find_all do |contact, media, matchers|
|
53
|
+
matchers.none? {|matcher| matcher.blackhole? }
|
54
|
+
end
|
55
|
+
|
56
|
+
# tuple = [
|
57
|
+
# [ alice, sms, matchers ],
|
58
|
+
# ]
|
59
|
+
|
60
|
+
# delete media based on notification interval
|
61
|
+
tuple.find_all do |contact, media, matchers|
|
62
|
+
#interval = matchers.sort_by {|matcher| matcher.interval }.first # => 5
|
63
|
+
interval = contact.interval_for_media(media) # => 5
|
64
|
+
# use an expiring key to block a notification for a given (contact, media) for the interval
|
65
|
+
not_notified_within(interval) # => true
|
66
|
+
end
|
67
|
+
|
68
|
+
# tuple = [
|
69
|
+
# [ alice, sms, matchers ],
|
70
|
+
# ]
|
71
|
+
|
72
|
+
tuple.each {|contact, media, matchers| dispatch_message(media, contact) }
|
73
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'HTTParty'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
@payload ={
|
7
|
+
"email" => "phil@gmail.com",
|
8
|
+
"token" => "mytokenstuff",
|
9
|
+
"content" => "here is some content",
|
10
|
+
"notification_type" => "1",
|
11
|
+
"name" => "here is a name",
|
12
|
+
"auto_action" => "true"
|
13
|
+
}
|
14
|
+
|
15
|
+
HTTParty.post( 'http://localhost:4091/notification_rules', :body => JSON.dump(@payload), :options => { :headers => { 'ContentType' => 'application/json' } })
|
16
|
+
|
@@ -0,0 +1,170 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "httparty"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
class Foo
|
7
|
+
|
8
|
+
include HTTParty
|
9
|
+
base_uri "http://localhost:4091"
|
10
|
+
|
11
|
+
@summary = ""
|
12
|
+
|
13
|
+
#test_entity = "foo-app-1.example.com"
|
14
|
+
test_entity = "localhost"
|
15
|
+
test_check = "PING"
|
16
|
+
test_contact = "21"
|
17
|
+
test_email = '{
|
18
|
+
"address": "dmitri@example.com",
|
19
|
+
"interval": 900
|
20
|
+
}'
|
21
|
+
test_timezone = '{
|
22
|
+
"timezone": "Australia/Broken_Hill"
|
23
|
+
}'
|
24
|
+
get_urls = [
|
25
|
+
"/entities",
|
26
|
+
"/checks/#{test_entity}",
|
27
|
+
"/status/#{test_entity}",
|
28
|
+
"/status/#{test_entity}/#{test_check}",
|
29
|
+
"/outages/#{test_entity}",
|
30
|
+
"/outages/#{test_entity}/#{test_check}",
|
31
|
+
"/unscheduled_maintenances/#{test_entity}",
|
32
|
+
"/unscheduled_maintenances/#{test_entity}/#{test_check}",
|
33
|
+
"/scheduled_maintenances/#{test_entity}",
|
34
|
+
"/scheduled_maintenances/#{test_entity}/#{test_check}",
|
35
|
+
"/downtime/#{test_entity}",
|
36
|
+
"/downtime/#{test_entity}/#{test_check}",
|
37
|
+
"/contacts",
|
38
|
+
"/contacts/#{test_contact}",
|
39
|
+
"/contacts/#{test_contact}/notification_rules",
|
40
|
+
"/contacts/#{test_contact}/media",
|
41
|
+
"/contacts/#{test_contact}/media/email",
|
42
|
+
"/contacts/#{test_contact}/timezone",
|
43
|
+
]
|
44
|
+
|
45
|
+
test_rule = '{
|
46
|
+
"contact_id": "21",
|
47
|
+
"entity_tags": [
|
48
|
+
"database",
|
49
|
+
"physical"
|
50
|
+
],
|
51
|
+
"entities": [
|
52
|
+
"localhost"
|
53
|
+
],
|
54
|
+
"time_restrictions": [
|
55
|
+
{
|
56
|
+
"TODO": "TODO"
|
57
|
+
}
|
58
|
+
],
|
59
|
+
"warning_media": [
|
60
|
+
"email"
|
61
|
+
],
|
62
|
+
"critical_media": [
|
63
|
+
"sms",
|
64
|
+
"email"
|
65
|
+
],
|
66
|
+
"warning_blackhole": false,
|
67
|
+
"critical_blackhole": false
|
68
|
+
}'
|
69
|
+
post_urls = {
|
70
|
+
"/scheduled_maintenances/#{test_entity}/#{test_check}" => '{
|
71
|
+
"start_time": 1361791228,
|
72
|
+
"duration": 3600,
|
73
|
+
"@summary": "SHUT IT ALL DOWN!"
|
74
|
+
}',
|
75
|
+
"/acknowledgements/#{test_entity}/#{test_check}" => '{
|
76
|
+
"duration": 3600,
|
77
|
+
"@summary": "AL - working on it"
|
78
|
+
}',
|
79
|
+
"/test_notifications/#{test_entity}/#{test_check}" => '',
|
80
|
+
"/entities" => '{
|
81
|
+
"entities": [
|
82
|
+
{
|
83
|
+
"id": "825",
|
84
|
+
"name": "foo.example.com",
|
85
|
+
"contacts": [
|
86
|
+
"21",
|
87
|
+
"22"
|
88
|
+
],
|
89
|
+
"tags": [
|
90
|
+
"foo"
|
91
|
+
]
|
92
|
+
}
|
93
|
+
]
|
94
|
+
}',
|
95
|
+
"/contacts" => '{
|
96
|
+
"contacts": [
|
97
|
+
{
|
98
|
+
"id": "21",
|
99
|
+
"first_name": "Ada",
|
100
|
+
"last_name": "Lovelace",
|
101
|
+
"email": "ada@example.com",
|
102
|
+
"media": {
|
103
|
+
"sms": "+61412345678",
|
104
|
+
"email": "ada@example.com"
|
105
|
+
},
|
106
|
+
"tags": [
|
107
|
+
"legend",
|
108
|
+
"first computer programmer"
|
109
|
+
]
|
110
|
+
}
|
111
|
+
]
|
112
|
+
}',
|
113
|
+
"/notification_rules" => test_rule,
|
114
|
+
}
|
115
|
+
|
116
|
+
def self.do_get(url)
|
117
|
+
response = get(url)
|
118
|
+
response_body = response.body ? response.body[0..300] : nil
|
119
|
+
puts "GET #{url}", "#{response.code} - #{response.message}", response.headers.inspect, response_body
|
120
|
+
puts "---------------------------------------------------"
|
121
|
+
@summary += "#{response.code} GET #{url}\n"
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.do_post(url, body)
|
125
|
+
response = post(url, :body => body, :headers => {'Content-Type' => 'application/json'})
|
126
|
+
response_body = response.body ? response.body[0..300] : nil
|
127
|
+
puts "POST #{url}", body, "#{response.code} - #{response.message}", response.headers.inspect, response_body
|
128
|
+
puts "---------------------------------------------------"
|
129
|
+
@summary += "#{response.code} POST #{url}\n"
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.do_put(url, body)
|
133
|
+
response = put(url, :body => body, :headers => {'Content-Type' => 'application/json'})
|
134
|
+
response_body = response.body ? response.body[0..300] : nil
|
135
|
+
puts "PUT #{url}", body, "#{response.code} - #{response.message}", response.headers.inspect, response_body
|
136
|
+
puts "---------------------------------------------------"
|
137
|
+
@summary += "#{response.code} PUT #{url}\n"
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.do_delete(url)
|
141
|
+
response = delete(url)
|
142
|
+
response_body = response.body ? response.body[0..300] : nil
|
143
|
+
puts "DELETE #{url}", "#{response.code} - #{response.message}", response.headers.inspect, response_body
|
144
|
+
puts "---------------------------------------------------"
|
145
|
+
@summary += "#{response.code} DELETE #{url}\n"
|
146
|
+
end
|
147
|
+
|
148
|
+
get_urls.each do |url|
|
149
|
+
do_get(url)
|
150
|
+
end
|
151
|
+
|
152
|
+
post_urls.each_pair do |url, data|
|
153
|
+
do_post(url, data)
|
154
|
+
end
|
155
|
+
|
156
|
+
rule = JSON.parse(get('/contacts/21/notification_rules').body).last
|
157
|
+
rule_id = rule['id']
|
158
|
+
raise RuntimeError unless rule_id
|
159
|
+
puts "****** NOTIFICATION RULE ID TO PICK ON (PUT, DELETE) IS: #{rule_id} ******"
|
160
|
+
|
161
|
+
do_get("/notification_rules/#{rule_id}")
|
162
|
+
do_put("/notification_rules/#{rule_id}", test_rule)
|
163
|
+
do_delete("/contacts/21/media/email")
|
164
|
+
do_put("/contacts/21/media/email", test_email)
|
165
|
+
do_delete("/contacts/21/timezone")
|
166
|
+
do_put("/contacts/21/timezone", test_timezone)
|
167
|
+
|
168
|
+
puts "\nSummary:\n"
|
169
|
+
puts @summary
|
170
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flapjack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-
|
14
|
+
date: 2013-04-18 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: dante
|
@@ -301,6 +301,54 @@ dependencies:
|
|
301
301
|
- - ! '>='
|
302
302
|
- !ruby/object:Gem::Version
|
303
303
|
version: '0'
|
304
|
+
- !ruby/object:Gem::Dependency
|
305
|
+
name: activesupport
|
306
|
+
requirement: !ruby/object:Gem::Requirement
|
307
|
+
none: false
|
308
|
+
requirements:
|
309
|
+
- - ! '>='
|
310
|
+
- !ruby/object:Gem::Version
|
311
|
+
version: '0'
|
312
|
+
type: :runtime
|
313
|
+
prerelease: false
|
314
|
+
version_requirements: !ruby/object:Gem::Requirement
|
315
|
+
none: false
|
316
|
+
requirements:
|
317
|
+
- - ! '>='
|
318
|
+
- !ruby/object:Gem::Version
|
319
|
+
version: '0'
|
320
|
+
- !ruby/object:Gem::Dependency
|
321
|
+
name: ice_cube
|
322
|
+
requirement: !ruby/object:Gem::Requirement
|
323
|
+
none: false
|
324
|
+
requirements:
|
325
|
+
- - ! '>='
|
326
|
+
- !ruby/object:Gem::Version
|
327
|
+
version: '0'
|
328
|
+
type: :runtime
|
329
|
+
prerelease: false
|
330
|
+
version_requirements: !ruby/object:Gem::Requirement
|
331
|
+
none: false
|
332
|
+
requirements:
|
333
|
+
- - ! '>='
|
334
|
+
- !ruby/object:Gem::Version
|
335
|
+
version: '0'
|
336
|
+
- !ruby/object:Gem::Dependency
|
337
|
+
name: tzinfo
|
338
|
+
requirement: !ruby/object:Gem::Requirement
|
339
|
+
none: false
|
340
|
+
requirements:
|
341
|
+
- - ! '>='
|
342
|
+
- !ruby/object:Gem::Version
|
343
|
+
version: '0'
|
344
|
+
type: :runtime
|
345
|
+
prerelease: false
|
346
|
+
version_requirements: !ruby/object:Gem::Requirement
|
347
|
+
none: false
|
348
|
+
requirements:
|
349
|
+
- - ! '>='
|
350
|
+
- !ruby/object:Gem::Version
|
351
|
+
version: '0'
|
304
352
|
- !ruby/object:Gem::Dependency
|
305
353
|
name: rake
|
306
354
|
requirement: !ruby/object:Gem::Requirement
|
@@ -359,6 +407,7 @@ files:
|
|
359
407
|
- dist/puppet/sqlite3/manifests/dev.pp
|
360
408
|
- etc/flapjack_config.yaml.example
|
361
409
|
- features/events.feature
|
410
|
+
- features/notification_rules.feature
|
362
411
|
- features/notifications.feature
|
363
412
|
- features/packaging-lintian.feature
|
364
413
|
- features/steps/events_steps.rb
|
@@ -381,7 +430,9 @@ files:
|
|
381
430
|
- lib/flapjack/data/global.rb
|
382
431
|
- lib/flapjack/data/message.rb
|
383
432
|
- lib/flapjack/data/notification.rb
|
433
|
+
- lib/flapjack/data/notification_rule.rb
|
384
434
|
- lib/flapjack/data/tag.rb
|
435
|
+
- lib/flapjack/data/tag_set.rb
|
385
436
|
- lib/flapjack/executive.rb
|
386
437
|
- lib/flapjack/filters/acknowledgement.rb
|
387
438
|
- lib/flapjack/filters/base.rb
|
@@ -422,6 +473,7 @@ files:
|
|
422
473
|
- spec/lib/flapjack/data/event_spec.rb
|
423
474
|
- spec/lib/flapjack/data/global_spec.rb
|
424
475
|
- spec/lib/flapjack/data/message_spec.rb
|
476
|
+
- spec/lib/flapjack/data/notification_rule_spec.rb
|
425
477
|
- spec/lib/flapjack/data/notification_spec.rb
|
426
478
|
- spec/lib/flapjack/data/tag_spec.rb
|
427
479
|
- spec/lib/flapjack/executive_spec.rb
|
@@ -464,9 +516,12 @@ files:
|
|
464
516
|
- tmp/create_events_ok_failure_ack.rb
|
465
517
|
- tmp/dummy_entities.json
|
466
518
|
- tmp/generate_nagios_test_hosts.rb
|
519
|
+
- tmp/notification_rules.rb
|
467
520
|
- tmp/parse_config_yaml.rb
|
468
521
|
- tmp/redis_delete_all_keys.rb
|
469
522
|
- tmp/test_entities.json
|
523
|
+
- tmp/test_json_post.rb
|
524
|
+
- tmp/test_notification_rules_api.rb
|
470
525
|
homepage: http://flapjack-project.com/
|
471
526
|
licenses: []
|
472
527
|
post_install_message:
|
@@ -493,6 +548,7 @@ specification_version: 3
|
|
493
548
|
summary: Intelligent, scalable, distributed monitoring notification system.
|
494
549
|
test_files:
|
495
550
|
- features/events.feature
|
551
|
+
- features/notification_rules.feature
|
496
552
|
- features/notifications.feature
|
497
553
|
- features/packaging-lintian.feature
|
498
554
|
- features/steps/events_steps.rb
|
@@ -509,6 +565,7 @@ test_files:
|
|
509
565
|
- spec/lib/flapjack/data/event_spec.rb
|
510
566
|
- spec/lib/flapjack/data/global_spec.rb
|
511
567
|
- spec/lib/flapjack/data/message_spec.rb
|
568
|
+
- spec/lib/flapjack/data/notification_rule_spec.rb
|
512
569
|
- spec/lib/flapjack/data/notification_spec.rb
|
513
570
|
- spec/lib/flapjack/data/tag_spec.rb
|
514
571
|
- spec/lib/flapjack/executive_spec.rb
|