growthtribe_xapi 0.0.1
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.
- 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
|