flapjack 0.8.10 → 0.8.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -1
- data/bin/flapjack +10 -1
- data/bin/flapjack-nagios-receiver +1 -2
- data/bin/simulate-failed-check +12 -4
- data/etc/flapjack_config.yaml.example +2 -1
- data/flapjack.gemspec +1 -0
- data/lib/flapjack/data/contact.rb +46 -26
- data/lib/flapjack/data/entity.rb +28 -0
- data/lib/flapjack/data/entity_check.rb +52 -11
- data/lib/flapjack/data/event.rb +9 -3
- data/lib/flapjack/data/notification_rule.rb +8 -0
- data/lib/flapjack/gateways/api.rb +0 -1
- data/lib/flapjack/gateways/api/entity_check_presenter.rb +2 -1
- data/lib/flapjack/gateways/email.rb +1 -2
- data/lib/flapjack/gateways/jabber.rb +3 -3
- data/lib/flapjack/gateways/jsonapi.rb +186 -38
- data/lib/flapjack/gateways/jsonapi/check_methods.rb +120 -0
- data/lib/flapjack/gateways/jsonapi/{entity_check_presenter.rb → check_presenter.rb} +7 -6
- data/lib/flapjack/gateways/jsonapi/contact_methods.rb +61 -352
- data/lib/flapjack/gateways/jsonapi/entity_methods.rb +117 -248
- data/lib/flapjack/gateways/jsonapi/medium_methods.rb +179 -0
- data/lib/flapjack/gateways/jsonapi/notification_rule_methods.rb +124 -0
- data/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods.rb +128 -0
- data/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +4 -5
- data/lib/flapjack/gateways/jsonapi/report_methods.rb +143 -0
- data/lib/flapjack/gateways/web.rb +1 -0
- data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +165 -101
- data/lib/flapjack/gateways/web/public/js/contacts.js +34 -46
- data/lib/flapjack/gateways/web/public/js/select2.js +232 -90
- data/lib/flapjack/gateways/web/public/js/select2.min.js +4 -4
- data/lib/flapjack/gateways/web/views/check.html.erb +11 -2
- data/lib/flapjack/processor.rb +6 -6
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/data/entity_check_spec.rb +1 -1
- data/spec/lib/flapjack/data/event_spec.rb +10 -9
- data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +25 -25
- data/spec/lib/flapjack/gateways/api_spec.rb +23 -1
- data/spec/lib/flapjack/gateways/email_spec.rb +40 -2
- data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +134 -0
- data/spec/lib/flapjack/gateways/jsonapi/{entity_check_presenter_spec.rb → check_presenter_spec.rb} +17 -17
- data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +27 -232
- data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +217 -687
- data/spec/lib/flapjack/gateways/jsonapi/medium_methods_spec.rb +232 -0
- data/spec/lib/flapjack/gateways/jsonapi/notification_rule_methods_spec.rb +131 -0
- data/spec/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods_spec.rb +113 -0
- data/spec/lib/flapjack/gateways/jsonapi/report_methods_spec.rb +546 -0
- data/spec/lib/flapjack/gateways/jsonapi_spec.rb +10 -1
- data/spec/lib/flapjack/gateways/web_spec.rb +1 -0
- data/spec/support/jsonapi_helper.rb +62 -0
- metadata +36 -8
- data/lib/flapjack/gateways/jsonapi/entity_presenter.rb +0 -75
- data/spec/lib/flapjack/gateways/jsonapi/entity_presenter_spec.rb +0 -108
@@ -0,0 +1,124 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
|
5
|
+
require 'flapjack/data/contact'
|
6
|
+
require 'flapjack/data/notification_rule'
|
7
|
+
|
8
|
+
module Flapjack
|
9
|
+
|
10
|
+
module Gateways
|
11
|
+
|
12
|
+
class JSONAPI < Sinatra::Base
|
13
|
+
|
14
|
+
module NotificationRuleMethods
|
15
|
+
|
16
|
+
# module Helpers
|
17
|
+
# end
|
18
|
+
|
19
|
+
def self.registered(app)
|
20
|
+
app.helpers Flapjack::Gateways::JSONAPI::Helpers
|
21
|
+
# app.helpers Flapjack::Gateways::JSONAPI::NotificationRuleMethods::Helpers
|
22
|
+
|
23
|
+
# Creates a notification rule or rules for a contact
|
24
|
+
app.post '/contacts/:contact_id/notification_rules' do
|
25
|
+
rules_data = params[:notification_rules]
|
26
|
+
|
27
|
+
if rules_data.nil? || !rules_data.is_a?(Enumerable)
|
28
|
+
halt err(422, "No valid notification rules were submitted")
|
29
|
+
end
|
30
|
+
|
31
|
+
contact = find_contact(params[:contact_id])
|
32
|
+
|
33
|
+
errors = []
|
34
|
+
rules_data.each do |rule_data|
|
35
|
+
errors << Flapjack::Data::NotificationRule.prevalidate_data(symbolize(rule_data), {:logger => logger})
|
36
|
+
end
|
37
|
+
errors.compact!
|
38
|
+
|
39
|
+
unless errors.nil? || errors.empty?
|
40
|
+
halt err(422, *errors)
|
41
|
+
end
|
42
|
+
|
43
|
+
rules = []
|
44
|
+
errors = []
|
45
|
+
rules_data.each do |rule_data|
|
46
|
+
rule_data = symbolize(rule_data)
|
47
|
+
rule_or_errors = contact.add_notification_rule(rule_data, :logger => logger)
|
48
|
+
if rule_or_errors.respond_to?(:critical_media)
|
49
|
+
rules << rule_or_errors
|
50
|
+
else
|
51
|
+
errors << rule_or_errors
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if rules.empty?
|
56
|
+
halt err(422, *errors)
|
57
|
+
else
|
58
|
+
if errors.empty?
|
59
|
+
status 201
|
60
|
+
else
|
61
|
+
logger.warn("Errors during bulk notification rules creation: " + errors.join(', '))
|
62
|
+
status 200
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
ids = rules.map {|r| r.id}
|
67
|
+
location(ids)
|
68
|
+
|
69
|
+
rules_json = rules.map {|r| r.to_json}.join(',')
|
70
|
+
'{"notification_rules":[' + rules_json + ']}'
|
71
|
+
end
|
72
|
+
|
73
|
+
# get one or more notification rules
|
74
|
+
app.get '/notification_rules/:id' do
|
75
|
+
rules_json = params[:id].split(',').collect {|rule_id|
|
76
|
+
find_rule(rule_id).to_jsonapi
|
77
|
+
}.join(', ')
|
78
|
+
|
79
|
+
'{"notification_rules":[' + rules_json + ']}'
|
80
|
+
end
|
81
|
+
|
82
|
+
app.patch '/notification_rules/:id' do
|
83
|
+
params[:id].split(',').collect {|rule_id|
|
84
|
+
find_rule(rule_id)
|
85
|
+
}.each do |rule|
|
86
|
+
apply_json_patch('notification_rules') do |op, property, linked, value|
|
87
|
+
case op
|
88
|
+
when 'replace'
|
89
|
+
case property
|
90
|
+
when 'entities', 'regex_entities', 'tags', 'regex_tags',
|
91
|
+
'time_restrictions', 'unknown_media', 'warning_media',
|
92
|
+
'critical_media', 'unknown_blackhole', 'warning_blackhole',
|
93
|
+
'critical_blackhole'
|
94
|
+
|
95
|
+
rule.update({property.to_sym => value}, :logger => logger)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
status 204
|
102
|
+
end
|
103
|
+
|
104
|
+
# Deletes one or more notification rules
|
105
|
+
app.delete '/notification_rules/:id' do
|
106
|
+
params[:id].split(',').each do |rule_id|
|
107
|
+
rule = find_rule(rule_id)
|
108
|
+
logger.debug("rule to delete: #{rule.inspect}, contact_id: #{rule.contact_id}")
|
109
|
+
contact = find_contact(rule.contact_id)
|
110
|
+
contact.delete_notification_rule(rule)
|
111
|
+
end
|
112
|
+
|
113
|
+
status 204
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
|
5
|
+
require 'flapjack/data/contact'
|
6
|
+
|
7
|
+
module Flapjack
|
8
|
+
|
9
|
+
module Gateways
|
10
|
+
|
11
|
+
class JSONAPI < Sinatra::Base
|
12
|
+
|
13
|
+
module PagerdutyCredentialMethods
|
14
|
+
|
15
|
+
SEMAPHORE_CONTACT_MASS_UPDATE = 'contact_mass_update'
|
16
|
+
|
17
|
+
# module Helpers
|
18
|
+
# end
|
19
|
+
|
20
|
+
def self.registered(app)
|
21
|
+
app.helpers Flapjack::Gateways::JSONAPI::Helpers
|
22
|
+
# app.helpers Flapjack::Gateways::JSONAPI::PagerdutyCredentialMethods::Helpers
|
23
|
+
|
24
|
+
# Creates/overwrites pagerduty credentials for a contact
|
25
|
+
app.post '/contacts/:contact_id/pagerduty_credentials' do
|
26
|
+
|
27
|
+
pagerduty_credentials_data = params[:pagerduty_credentials]
|
28
|
+
|
29
|
+
if pagerduty_credentials_data.nil? || !pagerduty_credentials_data.is_a?(Enumerable)
|
30
|
+
halt err(422, "No valid pagerduty credentials were submitted")
|
31
|
+
end
|
32
|
+
|
33
|
+
fields = ['service_key', 'subdomain', 'username', 'password']
|
34
|
+
|
35
|
+
pagerduty_credential = pagerduty_credentials_data.last
|
36
|
+
|
37
|
+
if pagerduty_credential.nil? || !pagerduty_credential.is_a?(Hash)
|
38
|
+
halt err(422, "No valid pagerduty credentials were submitted")
|
39
|
+
end
|
40
|
+
|
41
|
+
if (fields | pagerduty_credential.keys).size != fields.size
|
42
|
+
halt err(422, "Pagerduty credential data has incorrect fields")
|
43
|
+
end
|
44
|
+
|
45
|
+
semaphore = obtain_semaphore(SEMAPHORE_CONTACT_MASS_UPDATE)
|
46
|
+
contact = Flapjack::Data::Contact.find_by_id(params[:contact_id], :redis => redis)
|
47
|
+
if contact.nil?
|
48
|
+
semaphore.release
|
49
|
+
halt err(422, "Contact id '#{params[:contact_id]}' could not be loaded")
|
50
|
+
end
|
51
|
+
|
52
|
+
pagerduty_credential_data = fields.inject({}) do |memo, field|
|
53
|
+
memo[field] = pagerduty_credential[field]
|
54
|
+
memo
|
55
|
+
end
|
56
|
+
|
57
|
+
contact.set_pagerduty_credentials(pagerduty_credential_data)
|
58
|
+
semaphore.release
|
59
|
+
|
60
|
+
pagerduty_credential_data['links'] = {'contacts' => [contact.id]}
|
61
|
+
|
62
|
+
status 201
|
63
|
+
'{"pagerduty_credentials":[' + pagerduty_credential_data.to_json + ']}'
|
64
|
+
end
|
65
|
+
|
66
|
+
app.get %r{^/pagerduty_credentials(?:/)?([^/]+)?$} do
|
67
|
+
contacts = if params[:captures] && params[:captures][0]
|
68
|
+
params[:captures][0].split(',').uniq.collect {|c_id| find_contact(c_id)}
|
69
|
+
else
|
70
|
+
Flapjack::Data::Contact.all(:redis => redis)
|
71
|
+
end
|
72
|
+
|
73
|
+
pagerduty_credentials_data = contacts.inject([]) do |memo, contact|
|
74
|
+
pdc = contact.pagerduty_credentials.clone
|
75
|
+
|
76
|
+
pdc['links'] = {'contacts' => [contact.id]}
|
77
|
+
memo << pdc
|
78
|
+
memo
|
79
|
+
end
|
80
|
+
|
81
|
+
'{"pagerduty_credentials":' + pagerduty_credentials_data.to_json + '}'
|
82
|
+
end
|
83
|
+
|
84
|
+
# update one or more sets of pagerduty credentials
|
85
|
+
app.patch '/pagerduty_credentials/:contact_id' do
|
86
|
+
params[:contact_id].split(',').uniq.collect {|c_id| find_contact(c_id)}.each do |contact|
|
87
|
+
apply_json_patch('pagerduty_credentials') do |op, property, linked, value|
|
88
|
+
if 'replace'.eql?(op)
|
89
|
+
|
90
|
+
pdc = contact.pagerduty_credentials.clone
|
91
|
+
|
92
|
+
case property
|
93
|
+
when 'service_key'
|
94
|
+
pdc['service_key'] = value
|
95
|
+
contact.set_pagerduty_credentials(pdc)
|
96
|
+
when 'subdomain'
|
97
|
+
pdc['subdomain'] = value
|
98
|
+
contact.set_pagerduty_credentials(pdc)
|
99
|
+
when 'username'
|
100
|
+
pdc['username'] = value
|
101
|
+
contact.set_pagerduty_credentials(pdc)
|
102
|
+
when 'password'
|
103
|
+
pdc['password'] = value
|
104
|
+
contact.set_pagerduty_credentials(pdc)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
status 204
|
111
|
+
end
|
112
|
+
|
113
|
+
app.delete '/pagerduty_credentials/:contact_id' do
|
114
|
+
params[:contact_id].split(',').uniq.collect {|c_id| find_contact(c_id) }.each do |contact|
|
115
|
+
contact.delete_pagerduty_credentials
|
116
|
+
end
|
117
|
+
status 204
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -9,12 +9,11 @@ module Flapjack
|
|
9
9
|
class JsonParamsParser < Struct.new(:app)
|
10
10
|
def call(env)
|
11
11
|
t = type(env)
|
12
|
-
if env['rack.input']
|
12
|
+
if env['rack.input'] && !input_parsed?(env) && type_match?(t)
|
13
13
|
env['rack.request.form_input'] = env['rack.input']
|
14
14
|
json_data = env['rack.input'].read
|
15
15
|
env['rack.input'].rewind
|
16
|
-
|
17
|
-
data = Oj.load(json_data)
|
16
|
+
data = json_data.empty? ? {} : Oj.load(json_data)
|
18
17
|
env['rack.request.form_hash'] = data.empty? ? {} :
|
19
18
|
(('application/json-patch+json'.eql?(t)) ? {'ops' => data} : data)
|
20
19
|
end
|
@@ -26,8 +25,8 @@ module Flapjack
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def type(env)
|
29
|
-
return
|
30
|
-
env['CONTENT_TYPE'].split(/\s*[;,]\s*/, 2).first
|
28
|
+
return if env['CONTENT_TYPE'].nil?
|
29
|
+
env['CONTENT_TYPE'].split(/\s*[;,]\s*/, 2).first
|
31
30
|
end
|
32
31
|
|
33
32
|
def type_match?(t)
|
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
|
5
|
+
require 'flapjack/data/entity'
|
6
|
+
require 'flapjack/data/entity_check'
|
7
|
+
|
8
|
+
require 'flapjack/gateways/jsonapi/check_presenter'
|
9
|
+
|
10
|
+
module Flapjack
|
11
|
+
|
12
|
+
module Gateways
|
13
|
+
|
14
|
+
class JSONAPI < Sinatra::Base
|
15
|
+
|
16
|
+
module ReportMethods
|
17
|
+
|
18
|
+
module Helpers
|
19
|
+
|
20
|
+
def load_api_data(entity_ids, event_ids, action, &block)
|
21
|
+
result_type = case action
|
22
|
+
when 'status'
|
23
|
+
'statuses'
|
24
|
+
when 'outage'
|
25
|
+
'outages'
|
26
|
+
when 'scheduled_maintenance'
|
27
|
+
'scheduled_maintenances'
|
28
|
+
when 'unscheduled_maintenance'
|
29
|
+
'unscheduled_maintenances'
|
30
|
+
when 'downtime'
|
31
|
+
'downtimes'
|
32
|
+
end
|
33
|
+
|
34
|
+
entities = if entity_ids.nil?
|
35
|
+
Flapjack::Data::Entity.all(:redis => redis)
|
36
|
+
elsif !entity_ids.empty?
|
37
|
+
entity_ids.collect {|entity_id| find_entity_by_id(entity_id) }
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
checks = if event_ids.nil?
|
43
|
+
Flapjack::Data::Entity.all(:redis => redis).collect {|entity|
|
44
|
+
entity.check_list.collect {|check_name|
|
45
|
+
find_entity_check(entity, check_name)
|
46
|
+
}
|
47
|
+
}.flatten(2)
|
48
|
+
elsif !event_ids.empty?
|
49
|
+
event_ids.collect {|event_id| find_entity_check_by_name(*event_id.split(':', 2)) }
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
entities_by_name = {}
|
55
|
+
entity_checks_by_entity_name = {}
|
56
|
+
|
57
|
+
(entities || []).each do |entity|
|
58
|
+
entities_by_name[entity.name] = entity
|
59
|
+
check_list_names = entity.check_list
|
60
|
+
entity_checks_by_entity_name[entity.name] = check_list_names.collect {|check_name|
|
61
|
+
find_entity_check_by_name(entity.name, check_name)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
(checks || []).each do |check|
|
66
|
+
check_entity = check.entity
|
67
|
+
check_entity_name = check_entity.name
|
68
|
+
entities_by_name[check_entity_name] ||= check_entity
|
69
|
+
|
70
|
+
entity_checks_by_entity_name[check_entity_name] ||= []
|
71
|
+
entity_checks_by_entity_name[check_entity_name] << check
|
72
|
+
end
|
73
|
+
|
74
|
+
entity_data = entities_by_name.inject([]) do |memo, (entity_name, entity)|
|
75
|
+
memo << {
|
76
|
+
'id' => entity.id,
|
77
|
+
'name' => entity_name,
|
78
|
+
'links' => {
|
79
|
+
'checks' => entity_checks_by_entity_name[entity_name].collect {|entity_check|
|
80
|
+
"#{entity_name}:#{entity_check.check}"
|
81
|
+
},
|
82
|
+
}
|
83
|
+
}
|
84
|
+
memo
|
85
|
+
end
|
86
|
+
|
87
|
+
entity_check_data = entity_checks_by_entity_name.inject([]) do |memo, (entity_name, entity_checks)|
|
88
|
+
memo += entity_checks.collect do |entity_check|
|
89
|
+
entity_check_name = entity_check.check
|
90
|
+
{
|
91
|
+
'id' => "#{entity_name}:#{entity_check_name}",
|
92
|
+
'name' => entity_check_name,
|
93
|
+
result_type => yield(Flapjack::Gateways::JSONAPI::CheckPresenter.new(entity_check))
|
94
|
+
}
|
95
|
+
end
|
96
|
+
memo
|
97
|
+
end
|
98
|
+
|
99
|
+
[entity_data, entity_check_data]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.registered(app)
|
105
|
+
app.helpers Flapjack::Gateways::JSONAPI::Helpers
|
106
|
+
app.helpers Flapjack::Gateways::JSONAPI::ReportMethods::Helpers
|
107
|
+
|
108
|
+
app.get %r{^/(status|outage|(?:un)?scheduled_maintenance|downtime)_report/(entities|checks)(?:/([^/]+))?$} do
|
109
|
+
entities_or_checks = params[:captures][1]
|
110
|
+
action = params[:captures][0]
|
111
|
+
|
112
|
+
args = []
|
113
|
+
unless 'status'.eql?(action)
|
114
|
+
args += [validate_and_parsetime(params[:start_time]),
|
115
|
+
validate_and_parsetime(params[:end_time])]
|
116
|
+
end
|
117
|
+
|
118
|
+
entity_data, check_data = case entities_or_checks
|
119
|
+
when 'entities'
|
120
|
+
entity_ids = params[:captures][2].nil? ? nil : params[:captures][2].split(',')
|
121
|
+
load_api_data(entity_ids, [], action) {|presenter|
|
122
|
+
presenter.send(action, *args)
|
123
|
+
}
|
124
|
+
when 'checks'
|
125
|
+
entity_check_names = params[:captures][2].nil? ? nil : params[:captures][2].split(',')
|
126
|
+
load_api_data([], entity_check_names, action) {|presenter|
|
127
|
+
presenter.send(action, *args)
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
"{\"#{action}_reports\":" + entity_data.to_json +
|
132
|
+
",\"linked\":{\"checks\":" + check_data.to_json + "}}"
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -1,133 +1,197 @@
|
|
1
1
|
|
2
2
|
var toolbox = {};
|
3
3
|
|
4
|
-
toolbox.
|
5
|
-
return _.
|
6
|
-
return item.id === id;
|
7
|
-
});
|
4
|
+
toolbox.getMainCollection = function (response) {
|
5
|
+
return _.without(_.keys(response), 'links', 'linked', 'meta')[0];
|
8
6
|
};
|
9
7
|
|
10
|
-
toolbox.
|
11
|
-
|
8
|
+
toolbox.batchRequest = function(klass, ids, amount, success) {
|
9
|
+
// batch requests to avoid GET length limits
|
10
|
+
var grouped = _.groupBy(ids, function(element, index){
|
11
|
+
return Math.floor(index/75);
|
12
|
+
});
|
12
13
|
|
13
|
-
_.each(
|
14
|
-
var
|
14
|
+
_.each(grouped, function(ids, index) {
|
15
|
+
var linkedClass = klass.extend({
|
16
|
+
url: function() { return(klass.prototype.url.call() + "/" + ids.join(',')); }
|
17
|
+
});
|
18
|
+
var linkedCollection = new linkedClass();
|
19
|
+
linkedCollection.fetch({'reset' : true, 'success' : success});
|
20
|
+
});
|
21
|
+
}
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
23
|
+
toolbox.savePatch = function(model, attrs, patch) {
|
24
|
+
var patch_json = JSON.stringify(patch);
|
25
|
+
return model.save(attrs, {
|
26
|
+
data: patch_json,
|
27
|
+
patch: true,
|
28
|
+
contentType: 'application/json-patch+json'
|
29
|
+
});
|
30
|
+
};
|
31
|
+
|
32
|
+
Backbone.JSONAPIModel = Backbone.Model.extend({
|
33
|
+
|
34
|
+
resolveLink: function(name, klass, superset) {
|
35
|
+
if ( !_.isUndefined(this.get('links')) && !_.isUndefined(this.get('links')[name]) ) {
|
36
|
+
context = this;
|
37
|
+
var records = superset.filter(function(obj) {
|
38
|
+
return(context.get('links')[name].indexOf(obj.get('id')) > -1);
|
39
|
+
});
|
40
|
+
if ( _.isUndefined(this.get('linked')) ) {
|
41
|
+
this.set('linked', {});
|
42
|
+
}
|
43
|
+
if ( _.isUndefined(this.get('linked')[name]) ) {
|
44
|
+
this.get('linked')[name] = new klass();
|
45
|
+
}
|
46
|
+
this.get('linked')[name].add(records);
|
19
47
|
}
|
48
|
+
},
|
49
|
+
|
50
|
+
resolveLinks: function(name_klass_h) {
|
51
|
+
var context = this;
|
20
52
|
|
21
|
-
|
53
|
+
_.each(name_klass_h, function(klass, name) {
|
22
54
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
55
|
+
if ( !_.isUndefined(context.get('links')) &&
|
56
|
+
!_.isUndefined(context.get('links')[name]) &&
|
57
|
+
!_.isEmpty(context.get('links')[name]) ) {
|
58
|
+
var success = function(resultCollection, response, options) {
|
59
|
+
context.resolveLink(name, klass, resultCollection);
|
60
|
+
}
|
61
|
+
|
62
|
+
toolbox.batchRequest(klass, context.get('links')[name], 75, success);
|
28
63
|
}
|
64
|
+
});
|
65
|
+
},
|
66
|
+
|
67
|
+
parse: function (response) {
|
68
|
+
if (response === undefined) {
|
29
69
|
return;
|
30
70
|
}
|
71
|
+
if (response._alreadyBBJSONAPIParsed) {
|
72
|
+
delete response._alreadyBBJSONAPIParsed;
|
73
|
+
return response;
|
74
|
+
}
|
75
|
+
var mainCollection = toolbox.getMainCollection(response);
|
76
|
+
var obj = response[mainCollection][0];
|
31
77
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
78
|
+
return obj;
|
79
|
+
},
|
80
|
+
|
81
|
+
// makes sense to call this with model.patch(model.changedAttributes),
|
82
|
+
// if that value isn't false
|
83
|
+
patch: function(urlType, attrs) {
|
84
|
+
if (attrs == null) {
|
85
|
+
attrs = {};
|
40
86
|
}
|
41
|
-
});
|
42
87
|
|
43
|
-
|
88
|
+
var context = this;
|
44
89
|
|
45
|
-
|
46
|
-
|
47
|
-
|
90
|
+
var patch = _.inject(attrs, function(memo, val, key) {
|
91
|
+
// skip if not a simple attribute value
|
92
|
+
if ( (key == 'links') || _.isObject(val) || _.isArray(val) ) {
|
93
|
+
return memo;
|
94
|
+
}
|
48
95
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
var context = this;
|
55
|
-
return _.map(response[mainCollection], function (obj) {
|
56
|
-
toolbox.resolveLinks(context, obj, (response['linked'] || {}));
|
57
|
-
obj._alreadyBBJSONAPIParsed = true;
|
58
|
-
return obj;
|
59
|
-
});
|
60
|
-
};
|
96
|
+
memo.push({
|
97
|
+
op: 'replace',
|
98
|
+
path: '/' + urlType + '/0/' + key,
|
99
|
+
value: val
|
100
|
+
});
|
61
101
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
102
|
+
return memo;
|
103
|
+
}, new Array());
|
104
|
+
|
105
|
+
toolbox.savePatch(this, attrs, patch);
|
106
|
+
},
|
107
|
+
|
108
|
+
// singular operation only -- TODO batch up and submit en masse
|
109
|
+
addLinked: function(urlType, type, obj) {
|
110
|
+
var patch = [{
|
111
|
+
op: 'add',
|
112
|
+
path: '/' + urlType + '/0/links/' + type + '/-',
|
113
|
+
value: obj.get('id')
|
114
|
+
}];
|
115
|
+
|
116
|
+
toolbox.savePatch(this, {}, patch);
|
117
|
+
this.get('linked')[type].add(obj);
|
118
|
+
},
|
119
|
+
|
120
|
+
// singular operation only -- TODO batch up and submit en masse
|
121
|
+
removeLinked: function(urlType, type, obj) {
|
122
|
+
var patch = [{
|
123
|
+
op: 'remove',
|
124
|
+
path: '/' + urlType + '/0/links/' + type + '/' + obj.get('id'),
|
125
|
+
}];
|
126
|
+
|
127
|
+
toolbox.savePatch(this, {}, patch);
|
128
|
+
this.get('linked')[type].remove(obj);
|
129
|
+
},
|
130
|
+
|
131
|
+
sync: function(method, model, options) {
|
132
|
+
if ( method == 'create' ) {
|
133
|
+
options['contentType'] = 'application/vnd.api+json';
|
134
|
+
}
|
135
|
+
Backbone.sync(method, model, options);
|
69
136
|
}
|
70
|
-
var mainCollection = toolbox.getMainCollection(response);
|
71
|
-
var obj = response[mainCollection][0];
|
72
|
-
toolbox.resolveLinks(this, obj, (response['linked'] || {}));
|
73
|
-
return obj;
|
74
|
-
};
|
75
137
|
|
76
|
-
|
77
|
-
var patch_json = JSON.stringify(patch);
|
78
|
-
return model.save(attrs, {
|
79
|
-
data: patch_json,
|
80
|
-
patch: true,
|
81
|
-
contentType: 'application/json-patch+json'
|
82
|
-
});
|
83
|
-
};
|
138
|
+
});
|
84
139
|
|
85
|
-
// makes sense to call this with model.patch(model.changedAttributes),
|
86
|
-
// if that value isn't false
|
87
|
-
Backbone.Model.prototype.patch = function(attrs) {
|
88
|
-
if (attrs == null) {
|
89
|
-
attrs = {};
|
90
|
-
}
|
91
140
|
|
92
|
-
|
141
|
+
Backbone.JSONAPICollection = Backbone.Collection.extend({
|
93
142
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
return memo;
|
143
|
+
resolveLinks: function(name_klass_h) {
|
144
|
+
if ( _.isUndefined(this.linked) ) {
|
145
|
+
this.linked = {};
|
98
146
|
}
|
99
147
|
|
100
|
-
|
101
|
-
op: 'replace',
|
102
|
-
path: '/' + context.urlType + '/0/' + key,
|
103
|
-
value: val
|
104
|
-
});
|
148
|
+
var context = this;
|
105
149
|
|
106
|
-
|
107
|
-
}, new Array());
|
150
|
+
_.each(name_klass_h, function(klass, name) {
|
108
151
|
|
109
|
-
|
110
|
-
|
152
|
+
if ( !_.isUndefined(context.links[name]) && !_.isEmpty(context.links[name]) ) {
|
153
|
+
context.linked[name] = new klass();
|
111
154
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
value: obj.get('id')
|
118
|
-
}];
|
155
|
+
var success = function(resultCollection, response, options) {
|
156
|
+
context.forEach(function(obj, index) {
|
157
|
+
obj.resolveLink(name, klass, resultCollection);
|
158
|
+
});
|
159
|
+
}
|
119
160
|
|
120
|
-
|
121
|
-
|
122
|
-
};
|
161
|
+
toolbox.batchRequest(klass, context.links[name], 75, success);
|
162
|
+
}
|
163
|
+
});
|
164
|
+
},
|
123
165
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
path: '/' + this.urlType + '/0/links/' + type + '/' + obj.get('id'),
|
129
|
-
}];
|
166
|
+
parse: function (response) {
|
167
|
+
if (response === undefined) {
|
168
|
+
return;
|
169
|
+
}
|
130
170
|
|
131
|
-
|
132
|
-
|
133
|
-
|
171
|
+
this.links = {};
|
172
|
+
var context = this;
|
173
|
+
|
174
|
+
var mainCollection = toolbox.getMainCollection(response);
|
175
|
+
var objects = _.map(response[mainCollection], function (obj) {
|
176
|
+
_.each(obj.links, function(ids, name) {
|
177
|
+
if ( _.isUndefined(context.links[name]) ) {
|
178
|
+
context.links[name] = new Array();
|
179
|
+
}
|
180
|
+
if ( _.isArray(ids) ) {
|
181
|
+
context.links[name] = context.links[name].concat(ids);
|
182
|
+
}
|
183
|
+
});
|
184
|
+
|
185
|
+
obj._alreadyBBJSONAPIParsed = true;
|
186
|
+
return obj;
|
187
|
+
});
|
188
|
+
|
189
|
+
this.links = _.reduce(this.links, function(memo, ids, name) {
|
190
|
+
memo[name] = _.uniq(ids);
|
191
|
+
return(memo);
|
192
|
+
}, {});
|
193
|
+
|
194
|
+
return objects;
|
195
|
+
}
|
196
|
+
|
197
|
+
});
|