intellisense-ruby 0.6.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.
@@ -0,0 +1,269 @@
1
+
2
+ require 'time'
3
+ require 'thread'
4
+ require 'intellisense-ruby/defaults'
5
+ require 'intellisense-ruby/consumer'
6
+ require 'intellisense-ruby/request'
7
+ require 'json'
8
+
9
+ module IntellisenseRuby
10
+
11
+ class Client
12
+
13
+ # public: Creates a new client
14
+ #
15
+ # options - Hash
16
+ # :secret - String of your project's secret
17
+ # :max_queue_size - Fixnum of the max calls to remain queued (optional)
18
+ # :on_error - Proc which handles error calls from the API
19
+ def initialize(options = {})
20
+
21
+ @queue = Queue.new
22
+ @secret = options[:secret]
23
+ @max_queue_size = options[:max_queue_size] || IntellisenseRuby::Defaults::Queue::MAX_SIZE
24
+
25
+ check_api_key
26
+
27
+ @consumer = IntellisenseRuby::Consumer.new(@queue, @secret, options)
28
+ @thread = Thread.new { @consumer.run }
29
+
30
+ @logger = options[:logger]
31
+ log('DEBUG: ############## SENSOR initialized!')
32
+
33
+ end
34
+
35
+ # public: describe entities in the graph
36
+ #
37
+ # entity - Hash
38
+ # :type - type of entity (program, course, user, etc.)
39
+ # :entity_id - entity Id
40
+ # :properties - Hash of entity properties (optional, but recommended)
41
+ # :timestamp - Time of when the event occurred. (optional)
42
+ #
43
+ #
44
+ def describe(entity)
45
+
46
+ log('DEBUG: ############## describing entity' + entity.to_s)
47
+
48
+ check_api_key
49
+
50
+ type = entity[:type]
51
+ entity_id = entity[:entity_id].to_s
52
+ properties = entity[:properties] || {}
53
+ timestamp = entity[:timestamp] || Time.new
54
+
55
+ check_timestamp(timestamp)
56
+
57
+ if type.nil? || type.empty?
58
+ fail ArgumentError, 'Must supply type as a non-empty string'
59
+ end
60
+
61
+ if entity_id.nil? || entity_id.empty?
62
+ fail ArgumentError, 'Must supply entity_id as a non-empty string'
63
+ end
64
+
65
+ fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash
66
+
67
+ enqueue({ type: type,
68
+ entityId: entity_id,
69
+ properties: properties,
70
+ timestamp: timestamp.iso8601,
71
+ action: 'describe'})
72
+ end
73
+
74
+ # public: describe entities in the graph
75
+ #
76
+ # entity - Hash
77
+ # :type - type of entity (program, course, user, etc.)
78
+ # :id - entity Id
79
+ # :properties - Hash of entity properties (optional, but recommended)
80
+ # :timestamp - Time of when the event occurred. (optional)
81
+ #
82
+ #
83
+ def track(learning_event)
84
+
85
+ log('DEBUG: ############## recording learning event!')
86
+
87
+ check_api_key
88
+
89
+ type = options[:type]
90
+ entity_id = options[:entity_id].to_s
91
+ properties = options[:properties] || {}
92
+ timestamp = options[:timestamp] || Time.new
93
+
94
+ check_timestamp(timestamp)
95
+
96
+ if type.nil? || type.empty?
97
+ fail ArgumentError, 'Must supply type as a non-empty string'
98
+ end
99
+
100
+ fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash
101
+
102
+ enqueue({ type: type,
103
+ entityId: entity_id,
104
+ properties: properties,
105
+ timestamp: timestamp.iso8601,
106
+ action: 'measure'})
107
+ end
108
+
109
+
110
+ # public: Synchronously waits until the consumer has flushed the queue.
111
+ # Use only for scripts which are not long-running, and will
112
+ # specifically exit
113
+ #
114
+ def flush
115
+ while !@queue.empty? || @consumer.is_requesting?
116
+ sleep(0.1)
117
+ end
118
+ end
119
+
120
+ # public: Tracks an event
121
+ #
122
+ # options - Hash
123
+ # :event - String of event name.
124
+ # :user_id - String of the user id.
125
+ # :properties - Hash of event properties. (optional)
126
+ # :timestamp - Time of when the event occurred. (optional)
127
+ # :context - Hash of context. (optional)
128
+ def track(options)
129
+
130
+ check_api_key
131
+
132
+ event = options[:event]
133
+ user_id = options[:user_id].to_s
134
+ properties = options[:properties] || {}
135
+ timestamp = options[:timestamp] || Time.new
136
+ context = options[:context] || {}
137
+
138
+ ensure_user(user_id)
139
+ check_timestamp(timestamp)
140
+
141
+ if event.nil? || event.empty?
142
+ fail ArgumentError, 'Must supply event as a non-empty string'
143
+ end
144
+
145
+ fail ArgumentError, 'Properties must be a Hash' unless properties.is_a? Hash
146
+
147
+ add_context(context)
148
+
149
+ enqueue({ event: event,
150
+ userId: user_id,
151
+ context: context,
152
+ properties: properties,
153
+ timestamp: timestamp.iso8601,
154
+ action: 'track' })
155
+ end
156
+
157
+ # public: Identifies a user
158
+ #
159
+ # options - Hash
160
+ # :user_id - String of the user id
161
+ # :traits - Hash of user traits. (optional)
162
+ # :timestamp - Time of when the event occurred. (optional)
163
+ # :context - Hash of context. (optional)
164
+ def identify(options)
165
+
166
+ check_api_key
167
+
168
+ user_id = options[:user_id].to_s
169
+ traits = options[:traits] || {}
170
+ timestamp = options[:timestamp] || Time.new
171
+ context = options[:context] || {}
172
+
173
+ ensure_user(user_id)
174
+ check_timestamp(timestamp)
175
+
176
+ fail ArgumentError, 'Must supply traits as a hash' unless traits.is_a? Hash
177
+
178
+ add_context(context)
179
+
180
+ enqueue({ userId: user_id,
181
+ context: context,
182
+ traits: traits,
183
+ timestamp: timestamp.iso8601,
184
+ action: 'identify' })
185
+ end
186
+
187
+ # public: Aliases a user from one id to another
188
+ #
189
+ # options - Hash
190
+ # :from - String of the id to alias from
191
+ # :to - String of the id to alias to
192
+ # :timestamp - Time of when the alias occured (optional)
193
+ # :context - Hash of context (optional)
194
+ def alias(options)
195
+
196
+ check_api_key
197
+
198
+ from = options[:from].to_s
199
+ to = options[:to].to_s
200
+ timestamp = options[:timestamp] || Time.new
201
+ context = options[:context] || {}
202
+
203
+ ensure_user(from)
204
+ ensure_user(to)
205
+ check_timestamp(timestamp)
206
+
207
+ add_context(context)
208
+
209
+ enqueue({ from: from,
210
+ to: to,
211
+ context: context,
212
+ timestamp: timestamp.iso8601,
213
+ action: 'alias' })
214
+ end
215
+
216
+ # public: Returns the number of queued messages
217
+ #
218
+ # returns Fixnum of messages in the queue
219
+ def queued_messages
220
+ @queue.length
221
+ end
222
+
223
+ private
224
+
225
+ # private: Enqueues the action.
226
+ #
227
+ # returns Boolean of whether the item was added to the queue.
228
+ def enqueue(action)
229
+ queue_full = @queue.length >= @max_queue_size
230
+ @queue << action.to_json unless queue_full
231
+
232
+ !queue_full
233
+ end
234
+
235
+ # private: Ensures that a user id was passed in.
236
+ #
237
+ # user_id - String of the user id
238
+ #
239
+ def ensure_user(user_id)
240
+ fail ArgumentError, 'Must supply a non-empty user_id' if user_id.empty?
241
+ end
242
+
243
+ # private: Adds contextual information to the call
244
+ #
245
+ # context - Hash of call context
246
+ def add_context(context)
247
+ context[:library] = 'intellisense-ruby'
248
+ end
249
+
250
+ # private: Checks that the secret is properly initialized
251
+ def check_api_key
252
+ fail 'Secret must be initialized' if @secret.nil?
253
+ end
254
+
255
+ # private: Checks the timstamp option to make sure it is a Time.
256
+ def check_timestamp(timestamp)
257
+ fail ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
258
+ end
259
+
260
+ # private: log the requested message
261
+ def log(message)
262
+ if !@logger.nil?
263
+ @logger.debug(message)
264
+ else
265
+ puts message
266
+ end
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,106 @@
1
+
2
+ require 'intellisense-ruby/defaults'
3
+ require 'intellisense-ruby/request'
4
+ require 'intellisense-ruby/sqs_publisher'
5
+
6
+ module IntellisenseRuby
7
+
8
+ class Consumer
9
+
10
+ # public: Creates a new consumer
11
+ #
12
+ # The consumer continuously takes messages off the queue
13
+ # and makes requests to the intellify api
14
+ #
15
+ # queue - Queue synchronized between client and consumer
16
+ # secret - String of the project's secret
17
+ # options - Hash of consumer options
18
+ # batch_size - Fixnum of how many items to send in a batch
19
+ # on_error - Proc of what to do on an error
20
+ #
21
+ def initialize(queue, secret, options = {})
22
+ @queue = queue
23
+ @secret = secret
24
+ @batch_size = options[:batch_size] || IntellisenseRuby::Defaults::Queue::BATCH_SIZE
25
+ @on_error = options[:on_error] || Proc.new { |status, error| }
26
+ @transport = options[:transport] || IntellisenseRuby::Defaults::Request::TRANSPORT
27
+
28
+ @current_batch = []
29
+
30
+ @mutex = Mutex.new
31
+
32
+ @logger = options[:logger]
33
+ log('DEBUG ############## SENSOR initialized! with transport ' + @transport)
34
+
35
+ end
36
+
37
+ # public: Continuously runs the loop to check for new events
38
+ #
39
+ def run
40
+ while true
41
+ flush
42
+ end
43
+ end
44
+
45
+ # public: Flush some events from our queue
46
+ #
47
+ def flush
48
+ # Block until we have something to send
49
+ item = @queue.pop()
50
+
51
+ # Synchronize on additions to the current batch
52
+ @mutex.synchronize {
53
+ @current_batch << item
54
+ until @current_batch.length >= @batch_size || @queue.empty?
55
+ @current_batch << @queue.pop()
56
+ end
57
+ }
58
+
59
+ #log("about to send messages " + @current_batch)
60
+
61
+ case @transport
62
+ when 'http'
63
+ puts 'using http transport'
64
+ req = IntellisenseRuby::Request.new
65
+ res = req.post(@secret, @current_batch)
66
+ @on_error.call(res.status, res.error) unless res.status == 200
67
+ @mutex.synchronize {
68
+ @current_batch = []
69
+ }
70
+ when 'sqs'
71
+ log('DEBUG ############## SENSOR using SQS transport to send ' + @current_batch.to_s)
72
+ sqs = IntellisenseRuby::SQS_Publisher.new logger: @logger
73
+ sqs.send_message(@current_batch)
74
+ @mutex.synchronize {
75
+ @current_batch = []
76
+ }
77
+ else
78
+ # cannot do anything
79
+ puts 'UNKNOWN transport - error'
80
+ end
81
+
82
+ end
83
+
84
+ # public: Check whether we have outstanding requests.
85
+ #
86
+ def is_requesting?
87
+ requesting = nil
88
+ @mutex.synchronize {
89
+ requesting = !@current_batch.empty?
90
+ }
91
+ requesting
92
+ end
93
+
94
+ private
95
+
96
+ # private: log the requested message
97
+ def log(message)
98
+ if !@logger.nil?
99
+ @logger.debug(message)
100
+ else
101
+ puts message
102
+ end
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module IntellisenseRuby
3
+ module Defaults
4
+
5
+ module Request
6
+ TRANSPORT = 'http'
7
+ BASE_URL = 'http://www.intellifylearning.com/intellify' unless defined? IntellisenseRuby::Defaults::Request::BASE_URL
8
+ PATH = '/v1/import' unless defined? IntellisenseRuby::Defaults::Request::PATH
9
+ SSL = { verify: false } unless defined? IntellisenseRuby::Defaults::Request::SSL
10
+ HEADERS = { accept: 'application/json' } unless defined? IntellisenseRuby::Defaults::Request::HEADERS
11
+ end
12
+
13
+ module Queue
14
+ BATCH_SIZE = 100 unless defined? IntellisenseRuby::Defaults::Queue::BATCH_SIZE
15
+ MAX_SIZE = 10000 unless defined? IntellisenseRuby::Defaults::Queue::MAX_SIZE
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ require 'multi_json'
2
+
3
+ module IntellisenseRuby
4
+
5
+ #
6
+ # JSON Wrapper module using MultiJson 1.3
7
+ #
8
+ module JSON
9
+ if MultiJson.respond_to?(:dump)
10
+ def self.dump(*args)
11
+ MultiJson.dump(*args)
12
+ end
13
+
14
+ def self.load(*args)
15
+ MultiJson.load(*args)
16
+ end
17
+ else
18
+ def self.dump(*args)
19
+ MultiJson.encode(*args)
20
+ end
21
+
22
+ def self.load(*args)
23
+ MultiJson.decode(*args)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,53 @@
1
+
2
+ require 'intellisense-ruby/defaults'
3
+ require 'intellisense-ruby/response'
4
+ require 'intellisense-ruby/json'
5
+ require 'faraday'
6
+ require 'faraday_middleware'
7
+
8
+ module IntellisenseRuby
9
+
10
+ class Request
11
+
12
+ # public: Creates a new request object to send analytics batch
13
+ #
14
+ def initialize(options = {})
15
+
16
+ options[:url] ||= IntellisenseRuby::Defaults::Request::BASE_URL
17
+ options[:ssl] ||= IntellisenseRuby::Defaults::Request::SSL
18
+ options[:headers] ||= IntellisenseRuby::Defaults::Request::HEADERS
19
+ @path = options[:path] || IntellisenseRuby::Defaults::Request::PATH
20
+
21
+ @conn = Faraday.new(options) do |faraday|
22
+ faraday.request :json
23
+ faraday.response :json, :content_type => /\bjson$/
24
+ faraday.adapter Faraday.default_adapter
25
+ end
26
+ end
27
+
28
+ # public: Posts the secret and batch of messages to the API.
29
+ #
30
+ # returns - Response of the status and error if it exists
31
+ def post(secret, batch)
32
+
33
+ status, error = nil, nil
34
+
35
+ begin
36
+ res = @conn.post do |req|
37
+ req.options[:timeout] = 8
38
+ req.options[:open_timeout] = 3
39
+ req.url(@path)
40
+ req.body = IntellisenseRuby::JSON::dump(secret: secret, batch: batch)
41
+ end
42
+ status = res.status
43
+ error = res.body["error"]
44
+
45
+ rescue Exception => err
46
+ status = -1
47
+ error = "Connection error: #{err}"
48
+ end
49
+
50
+ IntellisenseRuby::Response.new(status, error)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module IntellisenseRuby
3
+
4
+ class Response
5
+
6
+ attr_reader :status
7
+ attr_reader :error
8
+
9
+ # public: Simple class to wrap responses from the API
10
+ #
11
+ #
12
+ def initialize(status = 200, error = nil)
13
+ @status = status
14
+ @error = error
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ require 'intellisense-ruby/defaults'
2
+ require 'intellisense-ruby/request'
3
+ require 'aws-sdk'
4
+ require 'pp'
5
+
6
+ module IntellisenseRuby
7
+
8
+ class SQS_Publisher
9
+
10
+ def initialize(options = {})
11
+
12
+ @sqs = AWS::SQS.new(
13
+ :access_key_id => 'AKIAJPYU646KQOWWZ4SA',
14
+ :secret_access_key => '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')
15
+
16
+ @sqs_queue = @sqs.queues['https://sqs.us-east-1.amazonaws.com/691677686063/yoda-events']
17
+
18
+ @logger = options[:logger]
19
+ log('DEBUG ############## SQS_Publisher created! with queue ' + @sqs_queue.to_s)
20
+
21
+ #queue_urls = @sqs.queues.collect(&:url)
22
+ #queue = @sqs.queues[queue_urls[0]]
23
+ #pp queue
24
+ # msg = queue.send_message("TEST")
25
+ # puts "Sent message: #{msg.id}"
26
+
27
+ end
28
+
29
+ def send_message(message = {})
30
+
31
+ return false unless @sqs_queue
32
+
33
+ log('DEBUG ############## SQS_Publisher created! sending message ' + message.to_s)
34
+
35
+ val = message.to_s
36
+ msg = @sqs_queue.send_message(val)
37
+ puts "Sent message: #{msg.id}"
38
+
39
+ end
40
+
41
+ private
42
+ # private: log the requested message
43
+ def log(message)
44
+ if !@logger.nil?
45
+ @logger.debug(message)
46
+ else
47
+ puts message
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,3 @@
1
+ module IntellisenseRuby
2
+ VERSION = '0.6.5'
3
+ end
@@ -0,0 +1,50 @@
1
+ require 'intellisense-ruby/version'
2
+ require 'intellisense-ruby/client'
3
+
4
+ module IntellisenseRuby
5
+
6
+ module ClassMethods
7
+
8
+ # By default use a single client for the module
9
+ def init(options = {})
10
+ @client = IntellisenseRuby::Client.new options
11
+ end
12
+
13
+ def describe(entity)
14
+ return false unless @client
15
+ @client.describe entity
16
+ end
17
+
18
+ def track(learning_event)
19
+ return false unless @client
20
+ @client.measure event
21
+ end
22
+
23
+ def track(options)
24
+ return false unless @client
25
+ @client.track(options)
26
+ end
27
+
28
+ def identify(options)
29
+ return false unless @client
30
+ @client.identify(options)
31
+ end
32
+
33
+ def alias(options)
34
+ return false unless @client
35
+ @client.alias(options)
36
+ end
37
+
38
+ def flush
39
+ return false unless @client
40
+ @client.flush
41
+ end
42
+
43
+ end
44
+
45
+ extend ClassMethods
46
+
47
+ end
48
+
49
+ # Alias for IntellisenseRuby
50
+ Intellisense = IntellisenseRuby