learndot_api 0.2.5
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/CHANGELOG +19 -0
- data/README.md +26 -0
- data/examples/test.rb +31 -0
- data/lib/learndot/api.rb +133 -0
- data/lib/learndot/courses.rb +63 -0
- data/lib/learndot/events.rb +83 -0
- data/lib/learndot/training_credits.rb +31 -0
- data/lib/learndot.rb +33 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fddcda6752cdbc9181405d9e0c38975994d3dfad
|
4
|
+
data.tar.gz: 4d53953a494753a0e15ed0a48561f85037afcfb7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 866abb02763938394d832541e2bb9e9d6d01a42f4d74e78d3152f2604607f00f38ff453e2b5bcb54c6891b4bb9ba3afd2d3936b3c3fdbe81abe730d0a76881c5
|
7
|
+
data.tar.gz: 121d4f7976a0f002237a82204c2af4bb789ffa35d202dffe5d6f37939cade91df231605c46b68b468456dd6a48a9f59da0e50b68bcc4804b1f69c81485688280
|
data/CHANGELOG
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
v0.2.5
|
2
|
+
* Add support for connecting to staging environment
|
3
|
+
|
4
|
+
v0.2.4
|
5
|
+
* Change endpoint used so that we can record private classes too
|
6
|
+
|
7
|
+
v0.2.3
|
8
|
+
* Support for "courses" object in learndot
|
9
|
+
|
10
|
+
v0.2.2
|
11
|
+
* search public and private events
|
12
|
+
|
13
|
+
v0.2.1
|
14
|
+
* Added ability to retrieve session URLs
|
15
|
+
* Update session notes
|
16
|
+
|
17
|
+
v0.2.0
|
18
|
+
|
19
|
+
Refactored into a library. Please see example usage in the `examples directory.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Learndot API wrapper
|
2
|
+
===============
|
3
|
+
|
4
|
+
This is a simple wrapper for the Learndot API. It's not very complete since it
|
5
|
+
was built primary to support our own specific needs. If you need an API added,
|
6
|
+
please file an issue, or contact us and let us know.
|
7
|
+
|
8
|
+
Configuration
|
9
|
+
===========
|
10
|
+
|
11
|
+
The API requires a token. This should be saved in one of two places:
|
12
|
+
|
13
|
+
1. `~/.learndot_token`
|
14
|
+
1. An environment variable of `$LEARNDOT_TOKEN`.
|
15
|
+
|
16
|
+
You can get your token at https://{learndot-url}/admin/api/token
|
17
|
+
|
18
|
+
Usage
|
19
|
+
==========
|
20
|
+
|
21
|
+
See the `examples` directory for usage samples.
|
22
|
+
|
23
|
+
Contact
|
24
|
+
==========
|
25
|
+
|
26
|
+
eduteam@puppet.com
|
data/examples/test.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require 'learndot'
|
3
|
+
require 'learndot/events'
|
4
|
+
|
5
|
+
class String
|
6
|
+
def trim(size)
|
7
|
+
if self.size > size
|
8
|
+
"#{self[0...(size - 1)]}…"
|
9
|
+
else
|
10
|
+
self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
conditions = {
|
16
|
+
'startTime' => [Learndot.timestamp(), Learndot.timestamp(1*7*24*60*60)],
|
17
|
+
'status' => ['CONFIRMED', 'TENTATIVE'],
|
18
|
+
}
|
19
|
+
|
20
|
+
ld = Learndot.new(true)
|
21
|
+
events = ld.events.retrieve(conditions)
|
22
|
+
|
23
|
+
puts ' Date | Course | Location | #'
|
24
|
+
puts '------------------------------------------------------------------------------'
|
25
|
+
events.each do |delivery|
|
26
|
+
printf("%10s | %-30s | %-27s | %2s\n",
|
27
|
+
delivery[:start_time],
|
28
|
+
delivery[:course_name].trim(30),
|
29
|
+
delivery[:city],
|
30
|
+
delivery[:enrollment_count])
|
31
|
+
end
|
data/lib/learndot/api.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
class Learndot::API
|
6
|
+
attr_writer :logger
|
7
|
+
|
8
|
+
def initialize(token = nil, debug = false, staging = false)
|
9
|
+
@logger = Logger.new(STDOUT)
|
10
|
+
@logger.level = debug ? Logger::DEBUG : Logger::WARN
|
11
|
+
@logger.formatter = proc { |level, date, name, msg|
|
12
|
+
"#{level}: #{msg}\n"
|
13
|
+
}
|
14
|
+
|
15
|
+
token ||= get_token
|
16
|
+
|
17
|
+
# Set the base_url to the staging or production endpoint
|
18
|
+
@base_url = staging ? "https://puppetlabs-staging.trainingrocket.com/api/rest/v2" : "https://learn.puppet.com/api/rest/v2"
|
19
|
+
|
20
|
+
@headers = {
|
21
|
+
"TrainingRocket-Authorization" => token,
|
22
|
+
"Learndot Enterprise-Authorization" => token,
|
23
|
+
"Content-Type" => "application/json",
|
24
|
+
"Accept" => "application/json; charset=utf-8"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def search(entity, conditions = {}, query = {})
|
29
|
+
endpoint = "/manage/#{entity}/search"
|
30
|
+
query[:asc] ||= false
|
31
|
+
query[:or] ||= false
|
32
|
+
|
33
|
+
if query.include? 'page'
|
34
|
+
api_post(endpoint, conditions, query)
|
35
|
+
else
|
36
|
+
page do |count|
|
37
|
+
query[:page] = count
|
38
|
+
api_post(endpoint, conditions, query)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def count(entity, conditions = {})
|
44
|
+
endpoint = "/manage/#{entity}/search"
|
45
|
+
|
46
|
+
num_records = api_post(endpoint, conditions)['size']
|
47
|
+
num_records.is_a?(Integer) ? num_records : 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# keep seperate from create to avoid accidental record creation
|
51
|
+
def update(entity, conditions, id)
|
52
|
+
endpoint = "/manage/#{entity}/#{id}"
|
53
|
+
api_post(endpoint, conditions)
|
54
|
+
end
|
55
|
+
|
56
|
+
def create(entity, conditions)
|
57
|
+
endpoint = "/manage/#{entity}"
|
58
|
+
api_post(endpoint, conditions)
|
59
|
+
end
|
60
|
+
|
61
|
+
############### Private methods ###############
|
62
|
+
def get_token
|
63
|
+
path = File.expand_path('~/.learndot_token')
|
64
|
+
|
65
|
+
File.exists?(path) ? File.read(path).strip : ENV['LEARNDOT_TOKEN']
|
66
|
+
end
|
67
|
+
|
68
|
+
def api_post(endpoint, conditions = {}, query = {})
|
69
|
+
url = @base_url + endpoint
|
70
|
+
@logger.debug "POST: #{url}"
|
71
|
+
@logger.debug " * Query params: #{query.inspect}"
|
72
|
+
@logger.debug " * Conditions: #{conditions.inspect}"
|
73
|
+
|
74
|
+
|
75
|
+
response = HTTParty.post(url, {
|
76
|
+
headers: @headers,
|
77
|
+
query: query,
|
78
|
+
body: conditions.to_json,
|
79
|
+
})
|
80
|
+
@logger.debug "#{response.code}: #{response.message}"
|
81
|
+
raise response.message unless response.code == 200
|
82
|
+
|
83
|
+
sleep 1 # dear god learndot
|
84
|
+
response
|
85
|
+
end
|
86
|
+
|
87
|
+
def api_get(endpoint, query = {})
|
88
|
+
url = @base_url + endpoint
|
89
|
+
@logger.debug "GET: #{url}"
|
90
|
+
@logger.debug " * Query params: #{query.inspect}"
|
91
|
+
@logger.debug " * Conditions: #{conditions.inspect}"
|
92
|
+
|
93
|
+
response = HTTParty.get(url, { headers: @headers, query: query })
|
94
|
+
@logger.debug "#{response.code}: #{response.message}"
|
95
|
+
raise response.message unless response.code == 200
|
96
|
+
|
97
|
+
sleep 1 # dear god learndot
|
98
|
+
response
|
99
|
+
end
|
100
|
+
|
101
|
+
def hash_response(response)
|
102
|
+
result_hash = {}
|
103
|
+
if response['size'].is_a?(Integer)
|
104
|
+
response['results'].each do | result |
|
105
|
+
result_hash[result['id']] = result
|
106
|
+
end
|
107
|
+
end
|
108
|
+
return result_hash
|
109
|
+
end
|
110
|
+
|
111
|
+
# Call a method provided as a block until there's no more data to get back
|
112
|
+
def page
|
113
|
+
raise 'page() requires a block' unless block_given?
|
114
|
+
|
115
|
+
response = yield(1)
|
116
|
+
num_records = response['size']
|
117
|
+
|
118
|
+
if num_records.is_a?(Integer) && num_records > 25
|
119
|
+
pages = (num_records / 25) + 1
|
120
|
+
# start at 2 since first call returned first page
|
121
|
+
for counter in 2..pages
|
122
|
+
@logger.debug "Retrieving page #{counter} of #{pages}"
|
123
|
+
results = yield(counter)['results']
|
124
|
+
response['results'].concat(results) if results
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
hash_response(response)
|
129
|
+
end
|
130
|
+
private :api_post, :api_get, :hash_response, :get_token, :page
|
131
|
+
############### End private methods ###############
|
132
|
+
|
133
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Learndot::Courses
|
2
|
+
def initialize(api)
|
3
|
+
@api = api
|
4
|
+
end
|
5
|
+
|
6
|
+
def retrieve(conditions, options = {orderBy: 'Name', asc: true})
|
7
|
+
courses = @api.search(:learning_component, conditions, options)
|
8
|
+
|
9
|
+
if !courses.empty?
|
10
|
+
course_ids = courses.collect { | k, c | c['id'] }.uniq
|
11
|
+
|
12
|
+
courses = @api.search(:learning_component, { 'id' => course_ids })
|
13
|
+
|
14
|
+
courses.collect do | course_id, course |
|
15
|
+
course[:id] = course['id']
|
16
|
+
course[:name] = course['name']
|
17
|
+
course[:components] = course['components']
|
18
|
+
|
19
|
+
course
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def enrollment_count(course_id)
|
25
|
+
enrollments = @api.search(:enrolment, { 'componentId' => [course_id] })
|
26
|
+
|
27
|
+
if ! enrollments.empty?
|
28
|
+
enrollment_ids = enrollments.collect { | k, cs | cs['id'] }
|
29
|
+
enrollment_conditions = {
|
30
|
+
'id' => enrollment_ids,
|
31
|
+
}
|
32
|
+
count = @api.count('enrolment', enrollment_conditions)
|
33
|
+
end
|
34
|
+
|
35
|
+
return count || 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def enrolled(course_id)
|
39
|
+
enrollments = @api.search(:enrolment, { 'componentId' => [course_id] })
|
40
|
+
return [] if enrollments.empty?
|
41
|
+
|
42
|
+
conditions = {
|
43
|
+
'id' => enrollments.collect { | k, cs | cs['id'] },
|
44
|
+
}
|
45
|
+
enrollments = @api.search(:enrolment, conditions)
|
46
|
+
return [] if enrollments.empty?
|
47
|
+
|
48
|
+
conditions = {
|
49
|
+
'id' => enrollments.collect { | k, cs | cs['contactId'] },
|
50
|
+
}
|
51
|
+
contacts = @api.search(:contact, conditions)
|
52
|
+
|
53
|
+
contacts.collect do | k, cs |
|
54
|
+
{ :id => cs['id'], :name => cs['_displayName_'], :email => cs['email'] }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Learndot
|
60
|
+
def courses
|
61
|
+
Learndot::Courses.new(self.api)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class Learndot::Events
|
2
|
+
def initialize(api)
|
3
|
+
@api = api
|
4
|
+
end
|
5
|
+
|
6
|
+
def retrieve(conditions, options = {orderBy: 'startTime', asc: true})
|
7
|
+
classes = @api.search(:course_event, conditions, options)
|
8
|
+
|
9
|
+
if !classes.empty?
|
10
|
+
course_ids = classes.collect { | k, c | c['courseId'] }.uniq
|
11
|
+
location_ids = classes.collect { | k, c | c['locationId'] }.uniq
|
12
|
+
organizer_ids = classes.collect { | k, c | c['organizerId'] }.uniq
|
13
|
+
|
14
|
+
courses = @api.search(:course, { 'id' => course_ids })
|
15
|
+
locations = @api.search(:location, { 'id' => location_ids })
|
16
|
+
organizers = @api.search(:contact, { 'id' => organizer_ids })
|
17
|
+
|
18
|
+
classes.collect do | class_id, klass |
|
19
|
+
location = locations[klass['locationId']]
|
20
|
+
|
21
|
+
klass[:learndot_id] = klass['id'] # for consistency
|
22
|
+
klass[:city] = location['online'] ? location['name'] : location['address']['city']
|
23
|
+
klass[:course_name] = courses[klass['courseId']]['name']
|
24
|
+
klass[:organizer] = organizers[klass['organizerId']] ? organizers[klass['organizerId']]['_displayName_'] : ''
|
25
|
+
klass[:enrollment_count] = enrollment_count(class_id)
|
26
|
+
klass[:start_time] = Date.parse(klass['startTime'])
|
27
|
+
klass[:end_time] = Date.parse(klass['finalEndTime'])
|
28
|
+
klass[:notes] = klass['notes']
|
29
|
+
klass[:session_url] = klass['sessionUrl']
|
30
|
+
|
31
|
+
klass
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def enrollment_count(class_id)
|
37
|
+
sessions = @api.search(:course_session, { 'eventId' => [class_id] })
|
38
|
+
|
39
|
+
if ! sessions.empty?
|
40
|
+
enrolment_ids = sessions.collect { | k, cs | cs['enrolmentId'] }
|
41
|
+
enrollment_conditions = {
|
42
|
+
'id' => enrolment_ids,
|
43
|
+
'status' => ['TENTATIVE', 'APPROVED', 'CONFIRMED']
|
44
|
+
}
|
45
|
+
count = @api.count('enrolment', enrollment_conditions)
|
46
|
+
end
|
47
|
+
|
48
|
+
return count || 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def enrolled(class_id)
|
52
|
+
sessions = @api.search(:course_session, { 'eventId' => [class_id] })
|
53
|
+
return [] if sessions.empty?
|
54
|
+
|
55
|
+
conditions = {
|
56
|
+
'id' => sessions.collect { | k, cs | cs['enrolmentId'] },
|
57
|
+
'status' => ['TENTATIVE', 'APPROVED', 'CONFIRMED']
|
58
|
+
}
|
59
|
+
enrollments = @api.search(:enrolment, conditions)
|
60
|
+
return [] if enrollments.empty?
|
61
|
+
|
62
|
+
conditions = {
|
63
|
+
'id' => enrollments.collect { | k, cs | cs['contactId'] },
|
64
|
+
}
|
65
|
+
contacts = @api.search(:contact, conditions)
|
66
|
+
|
67
|
+
contacts.collect do | k, cs |
|
68
|
+
{ :id => cs['id'], :name => cs['_displayName_'], :email => cs['email'] }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_notes(class_id, notes)
|
73
|
+
conditions = { 'notes' => notes }
|
74
|
+
@api.update(:course_event, conditions, class_id)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
class Learndot
|
80
|
+
def events
|
81
|
+
Learndot::Events.new(self.api)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Learndot::TrainingCredits
|
2
|
+
def initialize(api)
|
3
|
+
@api = api
|
4
|
+
end
|
5
|
+
|
6
|
+
def find_accounts(email)
|
7
|
+
api_get('/credit', {email: email})
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_account(conditions)
|
11
|
+
api_post('/credit', conditions)
|
12
|
+
end
|
13
|
+
|
14
|
+
def adjust(tc_account_id, conditions)
|
15
|
+
api_post("credit/#{tc_account_id}/adjust", conditions)
|
16
|
+
end
|
17
|
+
|
18
|
+
def history(tc_account_id)
|
19
|
+
endpoint = "/credit/#{tc_account_id}/transactions"
|
20
|
+
|
21
|
+
page do |count|
|
22
|
+
api_get(endpoint, {page: count})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Learndot
|
28
|
+
def training_credits
|
29
|
+
Learndot::TrainingCredits.new(self.api)
|
30
|
+
end
|
31
|
+
end
|
data/lib/learndot.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# This class serves as a "wrapper" for subclasses. Each time a subclass is
|
2
|
+
# called, it will monkeypatch itself into this one. Then you'll have a
|
3
|
+
# factory method to return a properly scoped subclass.
|
4
|
+
#
|
5
|
+
# For example:
|
6
|
+
#
|
7
|
+
# require 'learndot'
|
8
|
+
# require 'learndot/events'
|
9
|
+
#
|
10
|
+
# conditions = {
|
11
|
+
# 'startTime' => [Learndot.timestamp(), Learndot.timestamp(3*7*24*60*60)],
|
12
|
+
# 'status' => ['CONFIRMED', 'TENTATIVE'],
|
13
|
+
# }
|
14
|
+
#
|
15
|
+
# ld = Learndot.new
|
16
|
+
# ld.events.retrieve(conditions)
|
17
|
+
#
|
18
|
+
class Learndot
|
19
|
+
require 'learndot/api'
|
20
|
+
attr_reader :api
|
21
|
+
|
22
|
+
def initialize(debug = false, staging = false)
|
23
|
+
@api = Learndot::API.new(nil, debug, staging)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.timestamp(time = nil)
|
27
|
+
time ||= Time.new
|
28
|
+
time = Time.new + time if time.is_a? Numeric
|
29
|
+
raise "timestamp() expects a Time object or number of seconds from now as an integer." unless time.class == Time
|
30
|
+
|
31
|
+
time.strftime('%Y-%m-%d %H:%M:%S')
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: learndot_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Marrero
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httparty
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.13.7
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.13.7
|
27
|
+
description: Methods to retrieve records from Learndot Enterprise API
|
28
|
+
email: michael.marrero@puppet.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- CHANGELOG
|
34
|
+
- README.md
|
35
|
+
- examples/test.rb
|
36
|
+
- lib/learndot.rb
|
37
|
+
- lib/learndot/api.rb
|
38
|
+
- lib/learndot/courses.rb
|
39
|
+
- lib/learndot/events.rb
|
40
|
+
- lib/learndot/training_credits.rb
|
41
|
+
homepage: http://learn.puppet.com
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.6.7
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Learndot API
|
65
|
+
test_files: []
|