growthtribe_xapi 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +28 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +78 -0
- data/LICENSE.txt +22 -0
- data/README.md +299 -0
- data/Rakefile +12 -0
- data/bin/rspec +29 -0
- data/lib/growthtribe_xapi.rb +2 -0
- data/lib/xapi.rb +224 -0
- data/lib/xapi/about.rb +15 -0
- data/lib/xapi/activity.rb +37 -0
- data/lib/xapi/activity_definition.rb +131 -0
- data/lib/xapi/agent.rb +44 -0
- data/lib/xapi/agent_account.rb +33 -0
- data/lib/xapi/attachment.rb +64 -0
- data/lib/xapi/context.rb +54 -0
- data/lib/xapi/context_activities.rb +102 -0
- data/lib/xapi/documents/activity_profile_document.rb +15 -0
- data/lib/xapi/documents/agent_profile_document.rb +15 -0
- data/lib/xapi/documents/document.rb +20 -0
- data/lib/xapi/documents/state_document.rb +15 -0
- data/lib/xapi/enum.rb +42 -0
- data/lib/xapi/errors.rb +9 -0
- data/lib/xapi/group.rb +37 -0
- data/lib/xapi/interaction_component.rb +32 -0
- data/lib/xapi/interaction_type.rb +58 -0
- data/lib/xapi/lrs_response.rb +14 -0
- data/lib/xapi/query_result_format.rb +6 -0
- data/lib/xapi/remote_lrs.rb +416 -0
- data/lib/xapi/result.rb +46 -0
- data/lib/xapi/score.rb +39 -0
- data/lib/xapi/statement.rb +53 -0
- data/lib/xapi/statement_ref.rb +31 -0
- data/lib/xapi/statements/statements_base.rb +70 -0
- data/lib/xapi/statements_query.rb +42 -0
- data/lib/xapi/statements_query_v095.rb +42 -0
- data/lib/xapi/statements_result.rb +17 -0
- data/lib/xapi/sub_statement.rb +19 -0
- data/lib/xapi/tcapi_version.rb +27 -0
- data/lib/xapi/team.rb +44 -0
- data/lib/xapi/team_analytics_query.rb +36 -0
- data/lib/xapi/verb.rb +35 -0
- data/lib/xapi/version.rb +4 -0
- data/spec/fixtures/about.json +10 -0
- data/spec/fixtures/statement.json +33 -0
- data/spec/spec_helper.rb +107 -0
- data/spec/support/helpers.rb +60 -0
- data/spec/xapi/activity_definition_spec.rb +37 -0
- data/spec/xapi/activity_spec.rb +23 -0
- data/spec/xapi/agent_account_spec.rb +13 -0
- data/spec/xapi/agent_spec.rb +24 -0
- data/spec/xapi/attachment_spec.rb +26 -0
- data/spec/xapi/context_activities_spec.rb +57 -0
- data/spec/xapi/context_spec.rb +32 -0
- data/spec/xapi/group_spec.rb +15 -0
- data/spec/xapi/interaction_component_spec.rb +18 -0
- data/spec/xapi/remote_lrs_spec.rb +46 -0
- data/spec/xapi/result_spec.rb +24 -0
- data/spec/xapi/score_spec.rb +12 -0
- data/spec/xapi/statement_ref_spec.rb +19 -0
- data/spec/xapi/statement_spec.rb +73 -0
- data/spec/xapi/sub_statement_spec.rb +30 -0
- data/spec/xapi/verb_spec.rb +17 -0
- data/xapi.gemspec +30 -0
- 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,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
|