flapjack 0.8.10 → 0.8.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile +1 -1
  4. data/bin/flapjack +10 -1
  5. data/bin/flapjack-nagios-receiver +1 -2
  6. data/bin/simulate-failed-check +12 -4
  7. data/etc/flapjack_config.yaml.example +2 -1
  8. data/flapjack.gemspec +1 -0
  9. data/lib/flapjack/data/contact.rb +46 -26
  10. data/lib/flapjack/data/entity.rb +28 -0
  11. data/lib/flapjack/data/entity_check.rb +52 -11
  12. data/lib/flapjack/data/event.rb +9 -3
  13. data/lib/flapjack/data/notification_rule.rb +8 -0
  14. data/lib/flapjack/gateways/api.rb +0 -1
  15. data/lib/flapjack/gateways/api/entity_check_presenter.rb +2 -1
  16. data/lib/flapjack/gateways/email.rb +1 -2
  17. data/lib/flapjack/gateways/jabber.rb +3 -3
  18. data/lib/flapjack/gateways/jsonapi.rb +186 -38
  19. data/lib/flapjack/gateways/jsonapi/check_methods.rb +120 -0
  20. data/lib/flapjack/gateways/jsonapi/{entity_check_presenter.rb → check_presenter.rb} +7 -6
  21. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +61 -352
  22. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +117 -248
  23. data/lib/flapjack/gateways/jsonapi/medium_methods.rb +179 -0
  24. data/lib/flapjack/gateways/jsonapi/notification_rule_methods.rb +124 -0
  25. data/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods.rb +128 -0
  26. data/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +4 -5
  27. data/lib/flapjack/gateways/jsonapi/report_methods.rb +143 -0
  28. data/lib/flapjack/gateways/web.rb +1 -0
  29. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +165 -101
  30. data/lib/flapjack/gateways/web/public/js/contacts.js +34 -46
  31. data/lib/flapjack/gateways/web/public/js/select2.js +232 -90
  32. data/lib/flapjack/gateways/web/public/js/select2.min.js +4 -4
  33. data/lib/flapjack/gateways/web/views/check.html.erb +11 -2
  34. data/lib/flapjack/processor.rb +6 -6
  35. data/lib/flapjack/version.rb +1 -1
  36. data/spec/lib/flapjack/data/entity_check_spec.rb +1 -1
  37. data/spec/lib/flapjack/data/event_spec.rb +10 -9
  38. data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +25 -25
  39. data/spec/lib/flapjack/gateways/api_spec.rb +23 -1
  40. data/spec/lib/flapjack/gateways/email_spec.rb +40 -2
  41. data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -1
  42. data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +134 -0
  43. data/spec/lib/flapjack/gateways/jsonapi/{entity_check_presenter_spec.rb → check_presenter_spec.rb} +17 -17
  44. data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +27 -232
  45. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +217 -687
  46. data/spec/lib/flapjack/gateways/jsonapi/medium_methods_spec.rb +232 -0
  47. data/spec/lib/flapjack/gateways/jsonapi/notification_rule_methods_spec.rb +131 -0
  48. data/spec/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods_spec.rb +113 -0
  49. data/spec/lib/flapjack/gateways/jsonapi/report_methods_spec.rb +546 -0
  50. data/spec/lib/flapjack/gateways/jsonapi_spec.rb +10 -1
  51. data/spec/lib/flapjack/gateways/web_spec.rb +1 -0
  52. data/spec/support/jsonapi_helper.rb +62 -0
  53. metadata +36 -8
  54. data/lib/flapjack/gateways/jsonapi/entity_presenter.rb +0 -75
  55. 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'] and not input_parsed?(env) and type_match?(t)
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 unless env['CONTENT_TYPE']
30
- env['CONTENT_TYPE'].split(/\s*[;,]\s*/, 2).first.downcase
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
@@ -214,6 +214,7 @@ module Flapjack
214
214
  @check_last_change = last_change
215
215
  @check_summary = entity_check.summary
216
216
  @check_details = entity_check.details
217
+ @check_perfdata = entity_check.perfdata
217
218
 
218
219
  @last_notifications = last_notification_data(entity_check)
219
220
 
@@ -1,133 +1,197 @@
1
1
 
2
2
  var toolbox = {};
3
3
 
4
- toolbox.findById = function (array, id) {
5
- return _.find(array, function (item) {
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.resolveLinks = function(context, obj, linked) {
11
- if ( _.isUndefined(obj.links) ) { return; }
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(obj.links, function(ids, name) {
14
- var linkage_type = context.linkages[name];
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
- if ( _.isUndefined(linkage_type) ) {
17
- obj.links[name] = null;
18
- return;
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
- var linked_for_name = linked[name];
53
+ _.each(name_klass_h, function(klass, name) {
22
54
 
23
- if ( _.isUndefined(linked_for_name) ) {
24
- if ( _.isArray(ids) ) {
25
- obj.links[name] = new linkage_type();
26
- } else {
27
- obj.links[name] = null;
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
- if ( _.isArray(ids) ) {
33
- var collection = new linkage_type();
34
- collection.add(_.map(ids, function(id) {
35
- return new collection.model(toolbox.findById(linked_for_name, id));
36
- }));
37
- obj.links[name] = collection;
38
- } else {
39
- obj.links[name] = new linkage_type(toolbox.findById(linked_for_name, ids));
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
- toolbox.getMainCollection = function (response) {
46
- return _.without(_.keys(response), 'links', 'linked', 'meta')[0];
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
- Backbone.Collection.prototype.parse = function (response) {
50
- if (response === undefined) {
51
- return;
52
- }
53
- var mainCollection = toolbox.getMainCollection(response);
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
- Backbone.Model.prototype.parse = function (response) {
63
- if (response === undefined) {
64
- return;
65
- }
66
- if (response._alreadyBBJSONAPIParsed) {
67
- delete response._alreadyBBJSONAPIParsed;
68
- return response;
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
- toolbox.savePatch = function(model, attrs, patch) {
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
- var context = this;
141
+ Backbone.JSONAPICollection = Backbone.Collection.extend({
93
142
 
94
- var patch = _.inject(attrs, function(memo, val, key) {
95
- // skip if not a simple attribute value
96
- if ( (key == 'links') || _.isObject(val) || _.isArray(val) ) {
97
- return memo;
143
+ resolveLinks: function(name_klass_h) {
144
+ if ( _.isUndefined(this.linked) ) {
145
+ this.linked = {};
98
146
  }
99
147
 
100
- memo.push({
101
- op: 'replace',
102
- path: '/' + context.urlType + '/0/' + key,
103
- value: val
104
- });
148
+ var context = this;
105
149
 
106
- return memo;
107
- }, new Array());
150
+ _.each(name_klass_h, function(klass, name) {
108
151
 
109
- toolbox.savePatch(this, attrs, patch);
110
- };
152
+ if ( !_.isUndefined(context.links[name]) && !_.isEmpty(context.links[name]) ) {
153
+ context.linked[name] = new klass();
111
154
 
112
- // singular operation only -- TODO batch up and submit en masse
113
- Backbone.Model.prototype.addLinked = function(type, obj) {
114
- var patch = [{
115
- op: 'add',
116
- path: '/' + this.urlType + '/0/links/' + type + '/-',
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
- toolbox.savePatch(this, {}, patch);
121
- this.get('links')[type].add(obj);
122
- };
161
+ toolbox.batchRequest(klass, context.links[name], 75, success);
162
+ }
163
+ });
164
+ },
123
165
 
124
- // singular operation only -- TODO batch up and submit en masse
125
- Backbone.Model.prototype.removeLinked = function(type, obj) {
126
- var patch = [{
127
- op: 'remove',
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
- toolbox.savePatch(this, {}, patch);
132
- this.get('links')[type].remove(obj);
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
+ });