growthtribe_xapi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +28 -0
  3. data/CHANGELOG.md +2 -0
  4. data/CONTRIBUTING.md +7 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +78 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +299 -0
  9. data/Rakefile +12 -0
  10. data/bin/rspec +29 -0
  11. data/lib/growthtribe_xapi.rb +2 -0
  12. data/lib/xapi.rb +224 -0
  13. data/lib/xapi/about.rb +15 -0
  14. data/lib/xapi/activity.rb +37 -0
  15. data/lib/xapi/activity_definition.rb +131 -0
  16. data/lib/xapi/agent.rb +44 -0
  17. data/lib/xapi/agent_account.rb +33 -0
  18. data/lib/xapi/attachment.rb +64 -0
  19. data/lib/xapi/context.rb +54 -0
  20. data/lib/xapi/context_activities.rb +102 -0
  21. data/lib/xapi/documents/activity_profile_document.rb +15 -0
  22. data/lib/xapi/documents/agent_profile_document.rb +15 -0
  23. data/lib/xapi/documents/document.rb +20 -0
  24. data/lib/xapi/documents/state_document.rb +15 -0
  25. data/lib/xapi/enum.rb +42 -0
  26. data/lib/xapi/errors.rb +9 -0
  27. data/lib/xapi/group.rb +37 -0
  28. data/lib/xapi/interaction_component.rb +32 -0
  29. data/lib/xapi/interaction_type.rb +58 -0
  30. data/lib/xapi/lrs_response.rb +14 -0
  31. data/lib/xapi/query_result_format.rb +6 -0
  32. data/lib/xapi/remote_lrs.rb +416 -0
  33. data/lib/xapi/result.rb +46 -0
  34. data/lib/xapi/score.rb +39 -0
  35. data/lib/xapi/statement.rb +53 -0
  36. data/lib/xapi/statement_ref.rb +31 -0
  37. data/lib/xapi/statements/statements_base.rb +70 -0
  38. data/lib/xapi/statements_query.rb +42 -0
  39. data/lib/xapi/statements_query_v095.rb +42 -0
  40. data/lib/xapi/statements_result.rb +17 -0
  41. data/lib/xapi/sub_statement.rb +19 -0
  42. data/lib/xapi/tcapi_version.rb +27 -0
  43. data/lib/xapi/team.rb +44 -0
  44. data/lib/xapi/team_analytics_query.rb +36 -0
  45. data/lib/xapi/verb.rb +35 -0
  46. data/lib/xapi/version.rb +4 -0
  47. data/spec/fixtures/about.json +10 -0
  48. data/spec/fixtures/statement.json +33 -0
  49. data/spec/spec_helper.rb +107 -0
  50. data/spec/support/helpers.rb +60 -0
  51. data/spec/xapi/activity_definition_spec.rb +37 -0
  52. data/spec/xapi/activity_spec.rb +23 -0
  53. data/spec/xapi/agent_account_spec.rb +13 -0
  54. data/spec/xapi/agent_spec.rb +24 -0
  55. data/spec/xapi/attachment_spec.rb +26 -0
  56. data/spec/xapi/context_activities_spec.rb +57 -0
  57. data/spec/xapi/context_spec.rb +32 -0
  58. data/spec/xapi/group_spec.rb +15 -0
  59. data/spec/xapi/interaction_component_spec.rb +18 -0
  60. data/spec/xapi/remote_lrs_spec.rb +46 -0
  61. data/spec/xapi/result_spec.rb +24 -0
  62. data/spec/xapi/score_spec.rb +12 -0
  63. data/spec/xapi/statement_ref_spec.rb +19 -0
  64. data/spec/xapi/statement_spec.rb +73 -0
  65. data/spec/xapi/sub_statement_spec.rb +30 -0
  66. data/spec/xapi/verb_spec.rb +17 -0
  67. data/xapi.gemspec +30 -0
  68. metadata +244 -0
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ module Xapi
3
+ # InteractionComponent Class Description
4
+ class InteractionComponent
5
+
6
+ attr_accessor :id, :description
7
+
8
+ def initialize(options={}, &block)
9
+ json = options.fetch(:json, nil)
10
+ if json
11
+ attributes = JSON.parse(json)
12
+ self.id = attributes['id'] if attributes['id']
13
+ self.description = attributes['description'] if attributes['description']
14
+ else
15
+ self.id = options.fetch(:id, nil)
16
+ self.description = options.fetch(:description, nil)
17
+
18
+ if block_given?
19
+ block[self]
20
+ end
21
+ end
22
+ end
23
+
24
+ def serialize(version)
25
+ node = {}
26
+ node['id'] = id if id
27
+ node['description'] = description if description
28
+ node
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ module Xapi
3
+ # Possible interaction types
4
+ module InteractionType
5
+
6
+ CHOICE = {
7
+ to_s: 'choice',
8
+ get_value: proc{'choice'}
9
+ }
10
+
11
+ SEQUENCING = {
12
+ to_s: 'sequencing',
13
+ get_value: proc{'sequencing'}
14
+ }
15
+
16
+ LIKERT = {
17
+ to_s: 'likert',
18
+ get_value: proc{'likert'}
19
+ }
20
+
21
+ MATCHING = {
22
+ to_s: 'matching',
23
+ get_value: proc{'matching'}
24
+ }
25
+
26
+ PERFORMANCE = {
27
+ to_s: 'performance',
28
+ get_value: proc{'performance'}
29
+ }
30
+
31
+ TRUE_FALSE = {
32
+ to_s: 'true-false',
33
+ get_value: proc{'true-false'}
34
+ }
35
+
36
+ FILL_IN = {
37
+ to_s: 'fill-in',
38
+ get_value: proc{'fill-in'}
39
+ }
40
+
41
+ NUMERIC = {
42
+ to_s: 'numeric',
43
+ get_value: proc{'numeric'}
44
+ }
45
+
46
+ OTHER = {
47
+ to_s: 'other',
48
+ get_value: proc{'other'}
49
+ }
50
+
51
+ def type_by_string(type)
52
+ values.select{|v| v.to_s == type}.first
53
+ end
54
+
55
+ extend Xapi::Enum
56
+
57
+ end
58
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ module Xapi
3
+ class LrsResponse
4
+ attr_accessor :success, :error_message, :content, :status
5
+
6
+ def initialize(&block)
7
+ if block_given?
8
+ block[self]
9
+ end
10
+ end
11
+
12
+
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ module Xapi
3
+ module QueryResultFormat
4
+
5
+ end
6
+ end
@@ -0,0 +1,416 @@
1
+ # encoding: utf-8
2
+ require 'faraday'
3
+ require 'addressable/uri'
4
+ require 'xapi/tcapi_version'
5
+ module Xapi
6
+ # Class used to communicate with a TCAPI endpoint synchronously
7
+ class RemoteLRS
8
+ include Xapi::TCAPIVersion
9
+
10
+ VALID_PARAMS = %w(end_point user_name password version)
11
+
12
+ attr_accessor :end_point, :user_name, :password, :version
13
+
14
+ def initialize(options={}, &block)
15
+ setup_options(options)
16
+ yield_or_eval(&block) if block_given?
17
+ @version ||= latest_version
18
+ end
19
+
20
+ def about
21
+ response = connection.get("#{path}about")
22
+ LrsResponse.new do |lrs|
23
+ lrs.status = response.status
24
+ if response.status== 200
25
+ lrs.content = About.new(json: response.body)
26
+ lrs.success = true
27
+ else
28
+ lrs.success = false
29
+ end
30
+ end
31
+ end
32
+
33
+ def save_statement(statement)
34
+ # TODO: Complete this
35
+ if statement.id
36
+ response = connection.put do |req|
37
+ req.url("#{path}statements")
38
+ req.headers['Content-Type'] = 'application/json'
39
+ req.params.merge!({'statementId' => statement.id})
40
+ req.body = statement.serialize(latest_version).to_json
41
+ end
42
+ else
43
+ response = connection.post do |req|
44
+ req.url("#{path}statements")
45
+ req.headers['Content-Type'] = 'application/json'
46
+ req.body = statement.serialize(latest_version).to_json
47
+ end
48
+ end
49
+ LrsResponse.new do |lrs|
50
+ lrs.status = response.status
51
+ lrs.content = statement
52
+ if response.status == 200
53
+ statement.id = JSON.parse(response.body).first
54
+ lrs.success = true
55
+ elsif response.status == 204
56
+ lrs.success = true
57
+ else
58
+ lrs.success = false
59
+ end
60
+ end
61
+ end
62
+
63
+ def save_statements(statements)
64
+ # TODO: Complete this
65
+ if statements.empty?
66
+ return LrsResponse.new do |lrs|
67
+ lrs.success = true
68
+ end
69
+ end
70
+ response = connection.post do |req|
71
+ req.url("#{path}statements")
72
+ req.headers['Content-Type'] = 'application/json'
73
+ req.body = statements.map {|s| s.serialize(latest_version)}.to_json
74
+ end
75
+ LrsResponse.new do |lrs|
76
+ lrs.status = response.status
77
+ if response.status == 200
78
+ lrs.content = statements
79
+ ids = JSON.parse(response.body)
80
+ statements.each_with_index do |statement, index|
81
+ statement.id = ids[index]
82
+ end
83
+ lrs.success = true
84
+ else
85
+ lrs.success = false
86
+ end
87
+ end
88
+ end
89
+
90
+ def retrieve_statement(id)
91
+ get_statement(id, 'statementId')
92
+ end
93
+
94
+ def retrieve_voided_statement(id)
95
+ param_name = version == TCAPIVersion.V095 ? 'statementId' : 'voidedStatementId'
96
+ get_statement(id, param_name)
97
+ end
98
+
99
+ def query_statements(statement_query)
100
+ # TODO: Complete this
101
+ query = statement_query
102
+ unless query
103
+ query = version == TCAPIVersion::V095 ? StatementsQueryV095.new : StatementsQuery.new
104
+ end
105
+ # Error if the query parameters don't match the LRS version
106
+ raise Errors::IncompatibleTCAPIVersion, "Attempted to issue #{version} query using a #{query.version} set of query parameters." unless version == query.version
107
+
108
+ response = connection.get do |req|
109
+ req.url("#{path}statements")
110
+ req.params.merge!(query.parameter_map)
111
+ end
112
+ LrsResponse.new do |lrs|
113
+ lrs.status = response.status
114
+ if response.status == 200
115
+ # TODO: FIX THIS
116
+ lrs.success = true
117
+ lrs.content = StatementsResult.new(json: JSON.parse(response.body))
118
+ else
119
+ lrs.success = false
120
+ end
121
+ end
122
+ end
123
+
124
+ def query_team_analytics(team_analytics_query)
125
+ # TODO: Complete this
126
+ query = team_analytics_query
127
+ query = TeamAnalyticsQuery.new unless query
128
+
129
+ response = connection.get do |req|
130
+ req.url("#{path}team_analytics")
131
+ req.params.merge!(query.parameter_map)
132
+ end
133
+ LrsResponse.new do |lrs|
134
+ lrs.status = response.status
135
+ if response.status == 200
136
+ lrs.success = true
137
+ lrs.content = JSON.parse(response.body)
138
+ else
139
+ lrs.success = false
140
+ end
141
+ end
142
+ end
143
+
144
+ def more_statements(more_url)
145
+ # TODO: Complete this
146
+ # more_url is relative to the endpoint's server root
147
+ response = connection.get do |req|
148
+ req.url("#{path}#{more_url}")
149
+ end
150
+ LrsResponse.new do |lrs|
151
+ lrs.status = response.status
152
+ if response.status == 200
153
+ # TODO: FIX THIS
154
+ lrs.success = true
155
+ lrs.content = StatementsResult.new(json: response.body)
156
+ else
157
+ lrs.success = false
158
+ end
159
+ end
160
+ end
161
+
162
+ def retrieve_state_ids(activity, agent, registration)
163
+ # TODO: Complete this
164
+ query_params = {
165
+ 'activityId' => activity.id,
166
+ 'agent' => agent.serialize(version).to_json
167
+ }
168
+ query_params['registration'] = registration if registration
169
+ get_profile_keys('activities/state', query_params)
170
+ end
171
+
172
+ def retrieve_state(id, activity, agent, registration)
173
+ # TODO: Complete this
174
+ query_params = {
175
+ 'stateId' => id,
176
+ 'activityId' => activity.id,
177
+ 'agent' => agent.serialize(version).to_json
178
+ }
179
+ document = StateDocument.new do |sd|
180
+ sd.id = id
181
+ sd.activity = activity
182
+ sd.agent = agent
183
+ end
184
+ lrs_response = get_document('activities/state', query_params, document)
185
+ if lrs_response.status == 200
186
+ lrs_response.content = document
187
+ end
188
+ lrs_response
189
+ end
190
+
191
+ def save_state(state)
192
+ # TODO: Complete this
193
+ query_params = {
194
+ 'stateId' => state.id,
195
+ 'activityId' => state.activity.id,
196
+ 'agent' => state.agent.serialize(version).to_json
197
+ }
198
+ save_document('activities/state', query_params, state)
199
+ end
200
+
201
+ def delete_state(state)
202
+ # TODO: Complete this
203
+ query_params = {
204
+ 'stateId' => state.id,
205
+ 'activityId' => state.activity.id,
206
+ 'agent' => state.agent.serialize(version).to_json
207
+ }
208
+ query_params['registration'] = state.registration if state.registration
209
+ delete_document('activities/state', query_params)
210
+ end
211
+
212
+ def clear_state(activity, agent, registration)
213
+ # TODO: Complete this
214
+ query_params = {
215
+ 'activityId' => activity.id,
216
+ 'agent' => agent.serialize(version).to_json
217
+ }
218
+ query_params['registration'] = registration if registration
219
+ delete_document('activities/state', query_params)
220
+ end
221
+
222
+ def retrieve_activity_profile_ids(activity)
223
+ # TODO: Complete this
224
+ query_params = {
225
+ 'activityId' => activity.id
226
+ }
227
+ get_profile_keys('activities/profile', query_params)
228
+ end
229
+
230
+ def retrieve_activity_profile(id, activity)
231
+ # TODO: Complete this
232
+ query_params = {
233
+ 'profileId' => id,
234
+ 'activityId' => activity.id,
235
+ }
236
+ document = Documents::ActivityProfileDocument.new do |apd|
237
+ apd.id = id
238
+ apd.activity = activity
239
+ end
240
+ get_document('activities/profile', query_params, document)
241
+ end
242
+
243
+ def save_activity_profile(profile)
244
+ # TODO: Complete this
245
+ query_params = {
246
+ 'profileId' => profile.id,
247
+ 'activityId' => profile.activity.id,
248
+ }
249
+ save_document('activities/profile', query_params, profile)
250
+ end
251
+
252
+ def delete_activity_profile(profile)
253
+ # TODO: Complete this
254
+ query_params = {
255
+ 'profileId' => profile.id,
256
+ 'activityId' => profile.activity.id,
257
+ }
258
+ delete_document('activities/profile', query_params)
259
+ end
260
+
261
+ def retrieve_agent_profile_ids(agent)
262
+ # TODO: Complete this
263
+ query_params = {
264
+ 'agent' => agent.serialize(version).to_json
265
+ }
266
+ get_profile_keys('agents/profile', query_params)
267
+ end
268
+
269
+ def retrieve_agent_profile(id, agent)
270
+ # TODO: Complete this
271
+ query_params = {
272
+ 'profileId' => id,
273
+ 'agent' => agent.serialize(version).to_json
274
+ }
275
+ document = Documents::AgentProfileDocument.new do |apd|
276
+ apd.id = id
277
+ apd.agent = agent
278
+ end
279
+ get_document('agents/profile', query_params, document)
280
+ end
281
+
282
+ def save_agent_profile(profile)
283
+ # TODO: Complete this
284
+ query_params = {
285
+ 'profileId' => profile.id,
286
+ 'agent' => profile.agent.serialize(version).to_json
287
+ }
288
+ save_document('agents/profile', query_params, profile)
289
+ end
290
+
291
+ def delete_agent_profile(profile)
292
+ # TODO: Complete this
293
+ query_params = {
294
+ 'profileId' => profile.id,
295
+ 'agent' => profile.agent.serialize(version).to_json
296
+ }
297
+ delete_document('agents/profile', query_params)
298
+ end
299
+
300
+ private
301
+
302
+ def setup_options(options={})
303
+ options.each_pair do |key, value|
304
+ if value && VALID_PARAMS.include?(key.to_s)
305
+ instance_variable_set("@#{key}", value)
306
+ end
307
+ end
308
+ end
309
+
310
+ def yield_or_eval(&block)
311
+ return unless block
312
+ block.arity < 1 ? instance_eval(&block) : block[self]
313
+ end
314
+
315
+ def uri
316
+ @uri ||= Addressable::URI.parse(end_point)
317
+ end
318
+
319
+ def path
320
+ @path ||= uri.path
321
+ @path += '/' unless @path.end_with?('/')
322
+ @path
323
+ end
324
+
325
+ def connection
326
+ base_url = "#{uri.scheme}://#{uri.host}"
327
+ base_url = "#{base_url}:#{uri.port}" if uri.port
328
+ @connection ||= Faraday.new(:url => base_url) do |faraday|
329
+ faraday.request :url_encoded # form-encode POST params
330
+ faraday.response :logger # log requests to STDOUT
331
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
332
+ faraday.headers['X-Experience-API-Version'] = version.to_s
333
+ faraday.basic_auth(user_name, password)
334
+ end
335
+ end
336
+
337
+ def get_statement(id, parameter)
338
+ response = connection.get do |req|
339
+ req.url("#{path}statements")
340
+ req.headers['Content-Type'] = 'application/json'
341
+ req.params.merge!({parameter => id})
342
+ end
343
+ LrsResponse.new do |lrs|
344
+ lrs.status = response.status
345
+ if response.status== 200
346
+ lrs.content = Statement.new(json: response.body)
347
+ lrs.success = true
348
+ else
349
+ lrs.success = false
350
+ end
351
+ end
352
+ end
353
+
354
+ def get_document(resource, params, document)
355
+ response = connection.get do |req|
356
+ req.url("#{path}#{resource}")
357
+ req.params.merge!(params)
358
+ end
359
+ LrsResponse.new do |lrs|
360
+ lrs.status = response.status
361
+ if response.status == 200
362
+ lrs.success = true
363
+ lrs.content = JSON.parse(response.body)
364
+ # TODO FIX THIS
365
+ elsif response.status == 404
366
+ lrs.success = true
367
+ else
368
+ lrs.success = false
369
+ end
370
+ end
371
+ end
372
+
373
+ def delete_document(resource, params)
374
+ response = connection.delete do |req|
375
+ req.url("#{path}#{resource}")
376
+ req.params.merge!(params)
377
+ end
378
+ LrsResponse.new do |lrs|
379
+ lrs.status = response.status
380
+ if response.status == 204
381
+ lrs.success = true
382
+ else
383
+ lrs.success = false
384
+ end
385
+ end
386
+ end
387
+
388
+ def save_document(resource, params, document)
389
+ # TODO: Complete this
390
+ response = connection.put do |req|
391
+ req.url("#{path}#{resource}")
392
+ req.headers['Content-Type'] = 'application/json'
393
+ req.headers['If-Match'] = document.etag if document.etag
394
+ req.params.merge!(params)
395
+ req.body = document.content
396
+ end
397
+ LrsResponse.new do |lrs|
398
+ lrs.status = response.status
399
+ if response.status == 204
400
+ lrs.success = true
401
+ else
402
+ lrs.success = false
403
+ end
404
+ end
405
+ end
406
+
407
+ def get_profile_keys(resource, params)
408
+ # TODO FIX THIS
409
+ connection.get do |req|
410
+ req.url("#{path}#{resource}")
411
+ req.params.merge!(params)
412
+ end
413
+ end
414
+
415
+ end
416
+ end