kgrift 1.3.108

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/KGrift/Gemfile +22 -0
  3. data/KGrift/README.md +66 -0
  4. data/KGrift/bin/kgrift +11 -0
  5. data/KGrift/grifter.yml +224 -0
  6. data/KGrift/internal_test_graphs/basic_test_graph_definition.yml +2915 -0
  7. data/KGrift/internal_test_graphs/unicode_test_graph_definition.yml +3070 -0
  8. data/KGrift/knewton_grifts/analytics_grifts.rb +103 -0
  9. data/KGrift/knewton_grifts/async_helper_grifts.rb +63 -0
  10. data/KGrift/knewton_grifts/authenticator_grifts.rb +46 -0
  11. data/KGrift/knewton_grifts/basic_grifts.rb +29 -0
  12. data/KGrift/knewton_grifts/batch_grifts.rb +14 -0
  13. data/KGrift/knewton_grifts/content_collection_grifts.rb +204 -0
  14. data/KGrift/knewton_grifts/content_collection_v1_grifts.rb +521 -0
  15. data/KGrift/knewton_grifts/content_eid_grifts.rb +41 -0
  16. data/KGrift/knewton_grifts/copy_grifts.rb +151 -0
  17. data/KGrift/knewton_grifts/deprecated_graph_and_taxonomy_grifts.rb +353 -0
  18. data/KGrift/knewton_grifts/goal_grifts.rb +203 -0
  19. data/KGrift/knewton_grifts/graph_and_taxonomy_grifts.rb +136 -0
  20. data/KGrift/knewton_grifts/graph_create_grifts.rb +34 -0
  21. data/KGrift/knewton_grifts/graph_query_grifts.rb +448 -0
  22. data/KGrift/knewton_grifts/graph_tools_grifts.rb +151 -0
  23. data/KGrift/knewton_grifts/graph_validation_grifts.rb +447 -0
  24. data/KGrift/knewton_grifts/helper_grifts.rb +92 -0
  25. data/KGrift/knewton_grifts/jmeter_data_grifts.rb +56 -0
  26. data/KGrift/knewton_grifts/learning_instance_grifts.rb +46 -0
  27. data/KGrift/knewton_grifts/looper_grifts.rb +34 -0
  28. data/KGrift/knewton_grifts/moxy_grifts.rb +64 -0
  29. data/KGrift/knewton_grifts/oauth_grifts.rb +182 -0
  30. data/KGrift/knewton_grifts/partner_grifts.rb +70 -0
  31. data/KGrift/knewton_grifts/partner_support_grifts.rb +85 -0
  32. data/KGrift/knewton_grifts/recommendation_setup_grifts.rb +215 -0
  33. data/KGrift/knewton_grifts/registration_grifts.rb +159 -0
  34. data/KGrift/knewton_grifts/registration_info_grifts.rb +23 -0
  35. data/KGrift/knewton_grifts/report_grifts.rb +122 -0
  36. data/KGrift/knewton_grifts/shell_command_grifts.rb +21 -0
  37. data/KGrift/knewton_grifts/student_flow_grifts.rb +560 -0
  38. data/KGrift/knewton_grifts/tag_grifts.rb +41 -0
  39. data/KGrift/knewton_grifts/test_data_grifts.rb +328 -0
  40. data/KGrift/knewton_grifts/test_user_grifts.rb +264 -0
  41. data/KGrift/lib/dtrace.rb +20 -0
  42. data/KGrift/lib/kgrift.rb +7 -0
  43. data/KGrift/test_data_generators/basic_book_and_taxonomies.rb +35 -0
  44. data/KGrift/test_data_generators/lo_test_graph.rb +34 -0
  45. data/KGrift/test_data_generators/partner_owned_book_and_taxonomies.rb +28 -0
  46. data/KGrift/test_data_generators/partner_owned_book_and_taxonomies_unicode.rb +28 -0
  47. data/KGrift/test_data_generators/sandcastle_book_and_taxonomies.rb +13 -0
  48. data/KGrift/test_data_generators/sandcastle_book_and_taxonomies.yml +3709 -0
  49. data/KGrift/test_data_generators/sandcastle_graph.rb +8 -0
  50. data/KGrift/test_data_generators/sandcastle_graph_definition.json +4483 -0
  51. data/KGrift/test_data_generators/sandcastle_graph_full.rb +7 -0
  52. data/KGrift/test_data_generators/sandcastle_taxonomies.yml +378 -0
  53. data/KGrift/test_data_generators/sandcastle_with_taxons.rb +56 -0
  54. data/KGrift/test_data_generators/sandcastle_with_taxons.yml +3994 -0
  55. data/KGrift/test_data_generators/test_users_and_partners.rb +76 -0
  56. data/kgrift.gemspec +43 -0
  57. metadata +144 -0
@@ -0,0 +1,103 @@
1
+ ANALYTICS_VALID_OPTION_KEYS = [:goal_id, 'goal_id', :start_date, 'start_date', :end_date, 'end_date', :taxon_id, 'taxon_id']
2
+
3
+
4
+ def get_registration_metric_row reg_id, metric, overrides={}
5
+ spewy.post "/v0/registrations/#{reg_id}/metrics/#{metric}/rows", {
6
+ }.merge(overrides)
7
+ end
8
+
9
+ def get_registration_expected_score reg_id, overrides={}
10
+ get_registration_metric_row reg_id, 'expected-score', overrides
11
+ end
12
+
13
+ def get_registration_proficiency reg_id, overrides={}
14
+ get_registration_metric_row reg_id, 'proficiency', overrides
15
+ end
16
+
17
+ def get_registration_active_time reg_id, overrides={}
18
+ get_registration_metric_row reg_id, 'active-time', overrides
19
+ end
20
+
21
+ def get_registration_readiness_forecast reg_id, overrides={}
22
+ get_registration_metric_row reg_id, 'readiness-forecast', overrides
23
+ end
24
+
25
+ def get_li_metric_row li_id, metric, overrides={}
26
+ spewy.post "/v0/learning-instances/#{li_id}/metrics/#{metric}/rows", {
27
+ }.merge(overrides)
28
+ end
29
+
30
+ def get_li_expected_score li_id, overrides={}
31
+ get_li_metric_row li_id, 'expected-score', overrides
32
+ end
33
+
34
+ def get_li_proficiency li_id, overrides={}
35
+ get_li_metric_row li_id, 'proficiency', overrides
36
+ end
37
+
38
+ def get_li_active_time li_id, overrides={}
39
+ get_li_metric_row li_id, 'active-time', overrides
40
+ end
41
+
42
+ def get_li_readiness_forecast li_id, overrides={}
43
+ get_li_metric_row li_id, 'readiness-forecast', overrides
44
+ end
45
+
46
+ def get_expected_score_for_reg_and_goal reg_id, goal_id
47
+ spewy_get "/v0/registrations/#{reg_id}/metrics/expected-score?goal_id=#{goal_id}"
48
+ end
49
+
50
+
51
+ def get_expected_score_point_estimates_for_reg_and_goal reg_id, goal_id
52
+ #return a trimmed down payload for easier inspection
53
+ # HASH: module_id -> point_estimate
54
+ full_response = get_expected_score_for_reg_and_goal reg_id, goal_id
55
+ trimmed = {}
56
+ full_response['goals'][0]['target_modules'].each do |tm_data|
57
+ trimmed[tm_data['module_id']] = tm_data['expected_scores'][0]['point_estimate']
58
+ end
59
+ trimmed
60
+ end
61
+
62
+
63
+ def get_expected_score reg_or_LI_label, reg_or_LI, options={}
64
+ Log.info "Getting expected-score for #{reg_or_LI_label} #{reg_or_LI} with options: #{options}"
65
+ #select valid options
66
+ valid_options = options.values_at(*ANALYTICS_VALID_OPTION_KEYS)
67
+ append_http_path = valid_options.zip(ANALYTICS_VALID_OPTION_KEYS).select{|v,k| v}.map{ |v,k| k.to_s + '=' + v}
68
+ #select http path by label
69
+ spewy.get "/v0/#{reg_or_LI_label.to_s+'s'}/#{reg_or_LI}/metrics/expected-score?" + append_http_path.join('&')
70
+ end
71
+
72
+ def get_active_time reg_or_LI_label, reg_or_LI, options={}
73
+ Log.info "Getting active-time for #{reg_or_LI_label} #{reg_or_LI} with options: #{options}"
74
+ #select valid options
75
+ valid_options = options.values_at(*ANALYTICS_VALID_OPTION_KEYS)
76
+ append_http_path = valid_options.zip(ANALYTICS_VALID_OPTION_KEYS).select{|v,k| v}.map{ |v,k| k.to_s + '=' + v}
77
+ #select http path by label
78
+ spewy.get "/v0/#{reg_or_LI_label.to_s+'s'}/#{reg_or_LI}/metrics/active-time?" + append_http_path.join('&')
79
+ end
80
+
81
+ def get_readiness_forecast reg_or_LI_label, reg_or_LI, options={}
82
+ Log.info "Getting readiness-forecast for #{reg_or_LI_label} #{reg_or_LI} with options: #{options}"
83
+ #select valid options
84
+ valid_options = options.values_at(*ANALYTICS_VALID_OPTION_KEYS)
85
+ append_http_path = valid_options.zip(ANALYTICS_VALID_OPTION_KEYS).select{|v,k| v}.map{ |v,k| k.to_s + '=' + v}
86
+ #select http path by label
87
+ spewy.get "/v0/#{reg_or_LI_label.to_s+'s'}/#{reg_or_LI}/metrics/readiness-forecast?" + append_http_path.join('&')
88
+ end
89
+
90
+ def get_proficiency reg_or_LI_label, reg_or_LI, options={}
91
+ Log.info "Getting proficiency for #{reg_or_LI_label} #{reg_or_LI} with options: #{options}"
92
+ #select valid options
93
+ valid_options = options.values_at(*ANALYTICS_VALID_OPTION_KEYS)
94
+ append_http_path = valid_options.zip(ANALYTICS_VALID_OPTION_KEYS).select{|v,k| v}.map{ |v,k| k.to_s + '=' + v}
95
+ #select http path by label
96
+ spewy.get "/v0/#{reg_or_LI_label.to_s+'s'}/#{reg_or_LI}/metrics/proficiency?" + append_http_path.join('&')
97
+ end
98
+
99
+ # spewy_get is handy for making adhoc calls right off the cmd line:
100
+ # kgrift -e QA -v spewy_get "/v0/someurl?someparam=abc"
101
+ def spewy_get full_url, options={}
102
+ spewy.get full_url, options
103
+ end
@@ -0,0 +1,63 @@
1
+ require 'rspec/expectations'
2
+
3
+ def eventually options={}, &blk
4
+ timeout = options[:timeout] || 10
5
+ interval = options[:interval] || 1
6
+ time_limit = Time.now + timeout
7
+ loop do
8
+ begin
9
+ yield blk
10
+ rescue Grifter::RequestException => error
11
+ raise error if error.code >= 500
12
+ rescue RSpec::Expectations::ExpectationNotMetError, ExpectationError => error
13
+ #nothing to check here... just keep waiting
14
+ end
15
+ return if error.nil?
16
+ if Time.now >= time_limit
17
+ Log.debug "eventually condition timed out: #{error}"
18
+ raise error
19
+ end
20
+ Log.debug "eventually condition not yet met, will keep trying for #{(time_limit - Time.now).to_i.to_s} seconds: #{error}"
21
+ sleep interval
22
+ end
23
+ end
24
+
25
+
26
+ #this helper is useful for waiting until a POST'd object acutally creates
27
+ def wait_for_code expected_response_code=200, polling_options={}, &blk
28
+ #these options, if set, will cause us to check the response that comes back.
29
+ #to ensure we are getting the kind of asyc error we expect.
30
+ #If another error happens, we will stop immediately
31
+ error_code_must_be = polling_options[:error_code_must_be]
32
+ error_message_must_match = polling_options[:error_message_must_match]
33
+
34
+ eventually polling_options do
35
+ begin
36
+ yield blk
37
+ rescue Grifter::RequestException => e
38
+ #a 5xx should never happen, raise it if it does
39
+ if e.code.to_i >= 500
40
+ raise e
41
+ end
42
+ if error_code_must_be
43
+ raise e unless e.code.to_i == error_code_must_be.to_i
44
+ end
45
+ if error_message_must_match
46
+ raise e unless JSON.parse(e.body)['message'] =~ error_message_must_match
47
+ end
48
+ end
49
+ raise e unless kapi.last_response.status.to_i == expected_response_code.to_i
50
+ end
51
+ end
52
+
53
+ def time_it options={}, &blk
54
+ start_time = Time.now
55
+ yield blk
56
+ end_time = Time.now
57
+ block_took = (end_time - start_time)
58
+ Log.info "TIMING LOG: It took #{block_took} to execute the block: #{options.fetch(:name, 'No block name given')}"
59
+ return block_took
60
+ end
61
+
62
+ #Define new exception class for asynchronous expectation errors
63
+ class ::ExpectationError < RuntimeError; end
@@ -0,0 +1,46 @@
1
+ # used for testing all kinds of authentication cases
2
+
3
+ # create a partner with test users
4
+ #
5
+ def create_test_partner options={}
6
+ options = {
7
+ 'client_id' => random_string(23) + '-' + random_string(8),
8
+ 'client_secret' => random_string(48),
9
+ 'num_system_accounts' => 2,
10
+
11
+ }.merge(options)
12
+
13
+ partner = {
14
+ 'client_id' => options['client_id'],
15
+ 'client_secret' => options['client_secret'],
16
+ }
17
+
18
+ partner_credentials = {
19
+ 'client_id' => partner['client_id'],
20
+ 'client_secret' => partner['client_secret'],
21
+ }
22
+
23
+ external_id_prefix = random_string(16) + '-'
24
+
25
+ as_account :knerd do
26
+
27
+ #make the partner
28
+ partner['partner_id'] = create_partner({'name' => partner['client_id'], 'password' => partner['client_secret']})['id']
29
+
30
+ #make system accounts
31
+ partner['system_accounts'] = {}
32
+ options['num_system_accounts'].times do |i|
33
+ sys_acct = create_system_user partner['partner_id']
34
+ sys_acct['type'] = 'system account'
35
+ sys_acct['account_id'] = sys_acct['id']
36
+ partner['system_accounts'][sys_acct['id']] = sys_acct
37
+ end
38
+ end
39
+
40
+ #return everything
41
+ return_data = {
42
+ 'partner' => partner,
43
+ }
44
+ Log.debug JSON.pretty_generate(return_data)
45
+ return_data
46
+ end
@@ -0,0 +1,29 @@
1
+
2
+
3
+ def wait_for_api_to_respond timeout_secs=60, poll_interval=1
4
+ Log.info "Waiting up to #{timeout_secs} for the platform to respond"
5
+ #when this call returns a non-error response, we are up
6
+ kapi.get '/v0/healthcheck', {:additional_headers => {"accept" => "*/*", "content-type" => "text/plain"}}
7
+ Log.info "The system has awoken, proceeding"
8
+ true
9
+ rescue Exception => e
10
+ timeout_secs -= poll_interval
11
+ Log.debug "Try failed, #{timeout_secs} secs left."
12
+ if timeout_secs < 1
13
+ raise TimeoutError.new 'The system never came up'
14
+ end
15
+ sleep poll_interval
16
+ wait_for_api_to_respond timeout_secs
17
+ end
18
+
19
+ # kapi_get is handy for making adhoc calls right off the cmd line:
20
+ # kgrift -e QA -v kapi_get "/v0/someurl?someparam=abc"
21
+ def kapi_get full_url, options={}
22
+ kapi.get full_url, options
23
+ end
24
+ alias :bivins_get :kapi_get
25
+
26
+
27
+ def urlencode path_component
28
+ URI.encode_www_form_component(path_component.force_encoding("UTF-8"))
29
+ end
@@ -0,0 +1,14 @@
1
+ def create_single_request relative_url, http_method, body
2
+ {"relative_url" => relative_url,
3
+ "method" => http_method,
4
+ "body" => body }
5
+ end
6
+
7
+ def create_batch_request requests
8
+ {"requests"=>requests}
9
+ end
10
+
11
+ def create_account_payload
12
+ external_user_id = "external-user-id" + random_string
13
+ {"external_user_id" => external_user_id}
14
+ end
@@ -0,0 +1,204 @@
1
+
2
+ ## COCO endpoints ##
3
+
4
+ # return a list of all inventories
5
+ def get_inventories
6
+ Log.info "getting all inventories"
7
+ coco.get "/v0/cc/admin/inventories"
8
+ end
9
+
10
+ # return a list of all inventories
11
+ def get_inventories_for_partner partner_id
12
+ Log.info "getting all inventories for partner_id: #{partner_id}"
13
+ coco.get "/v0/cc/admin/#{partner_id}/inventories"
14
+ end
15
+
16
+ # get an inventory and all modules in it
17
+ def get_inventory inventory_id
18
+ Log.info "Getting inventory - inventory_id: #{inventory_id}"
19
+ coco.get "/v0/cc/inventory/#{inventory_id}"
20
+ end
21
+
22
+ # get an inventory and all modules in it
23
+ def get_inventory_admin partner_id, inventory_id, query_params={}
24
+ Log.info "Admin getting inventory - partner_id: #{partner_id} - inventory_id: #{inventory_id}"
25
+ coco.get "/v0/cc/admin/#{partner_id}/#{inventory_id}?#{URI.encode_www_form query_params}"
26
+ end
27
+
28
+ # get a inventory module
29
+ def get_inventory_module inventory_id, module_id
30
+ Log.info "Getting module - inventory_id: #{inventory_id} - module_id: #{module_id}"
31
+ coco.get "/v0/cc/inventory/#{URI.encode inventory_id}/module/#{URI.encode module_id}"
32
+ end
33
+
34
+ # get a single inventory module
35
+ def get_inventory_module_admin partner_id, inventory_id, module_id
36
+ Log.info "Admin getting module - partner_id: #{partner_id} - inventory_id: #{inventory_id} - module_id: #{module_id}"
37
+ coco.get "/v0/cc/admin/#{partner_id}/#{URI.encode inventory_id}/module/#{URI.encode module_id}"
38
+ end
39
+
40
+ # get the transaction log of an inventory,
41
+ def get_inventory_log partner_id, inventory_id, params={}
42
+ params = {
43
+ 'start' => days_from_now(-7),
44
+ 'coalesce' => false,
45
+ }.merge(params)
46
+ # format the timestamp if we need to
47
+ if params['start'].respond_to? :utc
48
+ params['start'] = params['start'].utc.iso8601(3)
49
+ end
50
+ Log.info "Getting inventory log '#{inventory_id}:#{partner_id}'"
51
+ coco.post "/v0/cc/admin/#{partner_id}/#{inventory_id}/log", params
52
+ end
53
+
54
+ # Make a new inventory
55
+ def create_inventory_admin partner_id, new_inventory_id=nil
56
+ new_inventory_id = "KGriftTestInventory_#{random_string(16)}" if new_inventory_id.nil?
57
+ Log.info "Admin creating an inventory - partner_id: #{partner_id}"
58
+ # response = create_modules_admin partner_id, new_inventory_id
59
+ response = coco.post "/v0/cc/admin/#{partner_id}/#{URI.encode new_inventory_id}/inventory", ''
60
+
61
+ {
62
+ 'id' => new_inventory_id,
63
+ 'inventory_id' => new_inventory_id,
64
+ 'partner_id' => partner_id,
65
+ 'inventory' => response,
66
+ }
67
+ end
68
+
69
+ def ensure_inventory_exists partner_id, inventory_id
70
+ create_inventory_admin partner_id, inventory_id
71
+ rescue Grifter::RequestException => e
72
+ # CoCo returns a 422 if the inventory already exists
73
+ # any other response code is not allowed, but we'll catch 422
74
+ raise e unless e.code.to_i == 422
75
+ end
76
+
77
+ # delete an inventory
78
+ def delete_inventory_admin partner_id, inventory_id
79
+ Log.info "Admin deleting an inventory - partner_id: #{partner_id}"
80
+ coco.delete "/v0/cc/admin/#{partner_id}/#{inventory_id}/inventory"
81
+ end
82
+
83
+ def ensure_inventory_does_not_exist partner_id, inventory_id
84
+ delete_inventory_admin partner_id, inventory_id
85
+ rescue Grifter::RequestException => e
86
+ # if the inventory does not an exist a 404 is returned. For our purposes, that is fine
87
+ # so we'll allow the request to fail if 404 comes back
88
+ raise e unless e.code.to_i == 404
89
+ end
90
+
91
+ def delete_all_inventories_for_partner partner_id
92
+ invs = get_inventories_for_partner partner_id
93
+ inv_ids = invs['partner_inventories'].map{|i| i['inventory_id']}
94
+ inv_ids.each do |inv_id|
95
+ delete_inventory_admin partner_id, inv_id
96
+ end
97
+ end
98
+
99
+ # build out a basic module object, ready to be created directly
100
+ # or you can merge in customizations
101
+ def test_module_object params={}
102
+ params = {
103
+ "module_id" => 'KGrift test module ' + random_uuid,
104
+ "module_name" => random_string,
105
+ "module_version" => random_string,
106
+ "module_type" => ['instruction', 'assessment', 'both'].sample,
107
+ "module_url" => "http://kgriftlms.com/#{random_string}",
108
+ 'taxons' => [],
109
+ 'module_metadata' => [],
110
+ 'containers' => [],
111
+ 'notification_url' => nil,
112
+ 'content_summary' => "TESTING: #{random_string}",
113
+ }.merge(params)
114
+ end
115
+
116
+ # create a batch of modules against some inventory
117
+ def create_modules inventory_id, modules=[]
118
+ modules << test_module_object if modules.empty?
119
+ Log.info "Creating '#{modules.length}' modules for inventory_id: #{inventory_id}"
120
+ coco.post "/v0/cc/inventory/#{inventory_id}/modules", modules
121
+ end
122
+
123
+ # create a batch of modules against some inventory
124
+ def create_modules_admin partner_id, inventory_id, modules=[]
125
+ modules << test_module_object if modules.empty?
126
+ Log.info "Admin creating '#{modules.length}' modules for inventory - partner_id: #{partner_id} - inventory_id: #{inventory_id}"
127
+ coco.post "/v0/cc/admin/#{partner_id}/#{inventory_id}/modules", modules
128
+ end
129
+
130
+ # delete a module
131
+ def delete_module inventory_id, module_id
132
+ Log.info "Deleting inventory module - inventory_id: #{inventory_id} - module_id: #{module_id}"
133
+ #grifter doesn't actually let you put a body on a delete if you use coco.delete
134
+ #but we can get around that like so:
135
+ # coco.do_request :delete, "/v0/cc/inventory/#{inventory_id}/module", { 'module_id' => module_id }
136
+ coco.delete "/v0/cc/inventory/#{inventory_id}/module/#{URI.encode module_id}"
137
+ end
138
+
139
+ # delete a module as a knerd
140
+ def delete_module_admin partner_id, inventory_id, module_id
141
+ Log.info "Admin deleting inventory module - partner_id: #{partner_id} - inventory_id: #{inventory_id} - module_id: #{module_id}"
142
+ #grifter doesn't actually let you put a body on a delete if you use coco.delete
143
+ #but we can get around that like so:
144
+ # coco.do_request :delete, "/v0/cc/admin/#{partner_id}/#{inventory_id}/module", { 'module_id' => module_id }
145
+ # coco.delete "/v0/cc/admin/#{partner_id}/#{inventory_id}/module/#{URI.encode module_id}"
146
+ coco.do_request :delete, "/v0/cc/admin/#{partner_id}/#{inventory_id}/module/#{URI.encode module_id}", {}
147
+ end
148
+
149
+ def update_modules inventory_id, modules
150
+ Log.info "Updating '#{modules.length}' modules for inventory - inventory_id: #{inventory_id}"
151
+ coco.put "/v0/cc/inventory/#{inventory_id}/modules", modules
152
+ end
153
+
154
+ def update_module inventory_id, mod
155
+ update_modules inventory_id, [mod]
156
+ end
157
+
158
+ def update_modules_admin partner_id, inventory_id, modules
159
+ Log.info "Updating '#{modules.length}' modules for inventory - partner_id: #{partner_id} - inventory_id: #{inventory_id}"
160
+ coco.put "/v0/cc/admin/#{partner_id}/#{inventory_id}/modules", modules
161
+ end
162
+
163
+ def mark_module_mapped partner_id, inventory_id, module_id, module_version
164
+ Log.info "Marking module mapped for inventory - partner_id: #{partner_id} - inventory_id: #{inventory_id} - module_id: #{module_id} - module_version: #{module_version}"
165
+ coco.put "/v0/cc/admin/#{partner_id}/#{inventory_id}/map", {
166
+ 'module_id' => module_id,
167
+ 'module_version' => module_version,
168
+ }
169
+ end
170
+
171
+
172
+
173
+ # CoCo stats endpoints
174
+ #
175
+
176
+ def get_partner_stats partner_id
177
+ Log.info "getting coco partner stats - partner_id: #{partner_id}"
178
+ coco.get "/v0/cc/stats/#{partner_id}"
179
+ end
180
+
181
+ def get_partner_stats_sum partner_id
182
+ Log.info "getting coco partner stats sum - partner_id: #{partner_id}"
183
+ coco.get "/v0/cc/stats/#{partner_id}/sum"
184
+ end
185
+
186
+ def get_inventory_stats partner_id, inventory_id
187
+ Log.info "getting inventory stats - partner_id: #{partner_id} - inventory_id: #{inventory_id}"
188
+ # coco.post "/v0/cc/stats/#{partner_id}/#{inventory_id}", {}
189
+ coco.get "/v0/cc/stats/#{partner_id}/#{inventory_id}"
190
+ end
191
+
192
+
193
+ def get_inventory_last_updated partner_id, inventory_id
194
+ Log.info "Admin getting inventory last updated - partner_id: #{partner_id} - inventory_id: #{inventory_id}"
195
+ stats = get_inventory_stats partner_id, inventory_id
196
+ stats['last_updated']
197
+ end
198
+
199
+ def get_inventory_total_modules partner_id, inventory_id
200
+ Log.info "Admin getting inventory total modules - partner_id: #{partner_id} - inventory_id: #{inventory_id}"
201
+ stats = get_inventory_stats partner_id, inventory_id
202
+ stats['num_modules']
203
+ end
204
+