learndot_api 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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: []