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 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
@@ -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: []