mixpanel_magic_lamp 1.0.0
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/.gitignore +4 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +14 -0
- data/MIT-LICENSE +20 -0
- data/README.md +258 -0
- data/Rakefile +7 -0
- data/lib/generators/mixpanel_magic_lamp/config_generator.rb +10 -0
- data/lib/generators/mixpanel_magic_lamp/templates/mixpanel_magic_lamp.rb +8 -0
- data/lib/mixpanel_magic_lamp/client.rb +48 -0
- data/lib/mixpanel_magic_lamp/configuration.rb +30 -0
- data/lib/mixpanel_magic_lamp/engine.rb +4 -0
- data/lib/mixpanel_magic_lamp/expression_builder.rb +85 -0
- data/lib/mixpanel_magic_lamp/formatter.rb +42 -0
- data/lib/mixpanel_magic_lamp/interface.rb +61 -0
- data/lib/mixpanel_magic_lamp/queue.rb +36 -0
- data/lib/mixpanel_magic_lamp/version.rb +3 -0
- data/lib/mixpanel_magic_lamp.rb +14 -0
- data/mixpanel_magic_lamp.gemspec +19 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6c38f0a54b2215f7234f635b0deceee30894e2a9
|
4
|
+
data.tar.gz: 4dd271143b3fa52bade4221828ae161c26614e26
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 93d6be32305f3927639065860f01d05856f5ac20a4e8cc9a4d086c2983d576e70fee63dfbfd51ee86f8edc30fb64ecb2f6e48765c7e8d5e752716b7f6e0cf744
|
7
|
+
data.tar.gz: 3474e0f5503df78eec61fbe54cf95d011cd72d846660d57b8e35592f8be4572fb2f7507c110608b7542ae511ecbde80c7664508117bbeee2fd683c4ab25c717d
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
## 0.1.0 (March 13, 2015)
|
2
|
+
|
3
|
+
* Interface and expression builder
|
4
|
+
* Generator for initialization
|
5
|
+
* First doc
|
6
|
+
|
7
|
+
## 1.0.0 (March 30, 2015)
|
8
|
+
|
9
|
+
* Monkey patch for ```Mixpanel::Client::prepare_paralell_request```
|
10
|
+
* Queue processor
|
11
|
+
* Formatter
|
12
|
+
* README info
|
13
|
+
* First offical release
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in mixpanel_magic_lamp.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
# Declare any dependencies that are still in development here instead of in
|
9
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
10
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
+
# your gem to rubygems.org.
|
12
|
+
|
13
|
+
# To use debugger
|
14
|
+
# gem 'debugger'
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Guillermo Guerrero
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
# Mixpanel Magic Lamp
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/mixpanel_magic_lamp)
|
4
|
+
|
5
|
+
Rub the magic lamp and your desired Mixpanel ORM will appear!
|
6
|
+
|
7
|
+
If you're using [Mixpanel](https://mixpanel.com/) for your web site analytics you probably thought
|
8
|
+
in make reports exporting your data through any **Mixpanel API** client. This gem is your answer
|
9
|
+
for not overwarming your head with so many doc, and it will remind you to ```ActiveRecord```.
|
10
|
+
|
11
|
+
## Install
|
12
|
+
You can install this *gem* by
|
13
|
+
|
14
|
+
```
|
15
|
+
$ gem install mixpanel_magic_lamp
|
16
|
+
```
|
17
|
+
|
18
|
+
Or bundle it on your app by adding this line at your *Gemfile*
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'mixpanel_magic_lamp'
|
22
|
+
```
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
To setup this gem you should add API keys and other config:
|
26
|
+
```ruby
|
27
|
+
MixpanelMagicLamp.configure do |config|
|
28
|
+
# Set your API Key/Secret
|
29
|
+
config.api_key = "YOUR MIXPANEL API KEY"
|
30
|
+
config.api_secret = "YOUR MIXPANEL API SECRET"
|
31
|
+
|
32
|
+
# Run query in parallel (recomended for better performance)
|
33
|
+
config.parallel = true
|
34
|
+
|
35
|
+
# Default interval on from/to dates when dates are not provided
|
36
|
+
config.interval = 30
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
### Rails config generator
|
41
|
+
Copy base config file on your **Rails app** by
|
42
|
+
```bash
|
43
|
+
rails generator mixpanel_magic_lamp:config
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
## Build your query
|
48
|
+
The most interesting feature from this library is probably the query builder, that
|
49
|
+
let you to build an 'ActiveRecord' like query to run API queries, it will remind you to
|
50
|
+
the Mixpanel UI:
|
51
|
+
|
52
|
+
### where
|
53
|
+
Start any query with this keyword, and extend is as long as you need.
|
54
|
+
This method accept a hash as first parameter where each pair of key/value are traslated
|
55
|
+
to "key == value" (*equals_to*), a second parameter may change union word:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Mixpanel.where(country: 'Spain', gender: 'Female').to_s
|
59
|
+
=> "(properties[\"country\"] == \"Spain\" and properties[\"gender\"] == \"Female\")"
|
60
|
+
|
61
|
+
Mixpanel.where({country: 'Spain', gender: 'Female'}, 'or').to_s
|
62
|
+
=> "(properties[\"country\"] == \"Spain\" or properties[\"gender\"] == \"Female\")"
|
63
|
+
```
|
64
|
+
|
65
|
+
|
66
|
+
Then you may append any existent query build to complete your API query:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
Mixpanel.where(country: 'Spain').or.is_set('source').to_s
|
70
|
+
=> "(properties[\"country\"] == \"Spain\") or (defined (properties[\"source\"]))"
|
71
|
+
```
|
72
|
+
|
73
|
+
### and/or
|
74
|
+
Concat as many query builders as you need with ```and``` and ```or``` operators:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Mixpanel.where(country: "Spain", browser: "Chrome")
|
78
|
+
.and.is_set('device_type')
|
79
|
+
.and.does_not_equal(user_type: 'bot')
|
80
|
+
.and.contains(url: '/sales')
|
81
|
+
=> "(properties[\"country\"] == \"Spain\" and properties[\"browser\"] == \"Chrome\") and (defined (properties[\"device_type\"])) and (properties[\"user_type\"] != \"bot\") and (\"/sales\" in (properties[\"url\"]))">
|
82
|
+
```
|
83
|
+
|
84
|
+
|
85
|
+
### on
|
86
|
+
Use it as **by** statement on your UI, in order to group segmentation:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
Mixpanel.on('country')
|
90
|
+
=> "properties[\"country\"]"
|
91
|
+
```
|
92
|
+
|
93
|
+
### Builders
|
94
|
+
|
95
|
+
#### equals
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Mixpanel.where.and.equals(country: 'Spain', user_type: 'human').to_s
|
99
|
+
=> "(properties[\"country\"] == \"Spain\" and properties[\"user_type\"] == \"human\")"
|
100
|
+
```
|
101
|
+
|
102
|
+
#### does_not_equal
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
Mixpanel.where.and.does_not_equal(country: 'Spain', user_type: 'human')
|
106
|
+
=> "(properties[\"country\"] != \"Spain\" and properties[\"user_type\"] != \"human\")"
|
107
|
+
```
|
108
|
+
|
109
|
+
#### contains
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
Mixpanel.where.and.contains(country: 'Spain', user_type: 'human').to_s
|
113
|
+
=> "(\"Spain\" in (properties[\"country\"]) and \"human\" in (properties[\"user_type\"]))"
|
114
|
+
```
|
115
|
+
|
116
|
+
#### does_not_contain
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
Mixpanel.where.and.does_not_contain(country: 'Spain', user_type: 'human').to_s
|
120
|
+
=> "(not \"Spain\" in (properties[\"country\"]) and not \"human\" in (properties[\"user_type\"]))"
|
121
|
+
```
|
122
|
+
|
123
|
+
#### is_set
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
Mixpanel.where.and.is_set(['country', 'user_type']).to_s
|
127
|
+
=> "(defined (properties[\"country\"]) and defined (properties[\"user_type\"]))"
|
128
|
+
```
|
129
|
+
|
130
|
+
#### is_not_set
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
Mixpanel.where.and.is_not_set(['country', 'user_type']).to_s
|
134
|
+
=> "(not defined (properties[\"country\"]) and not defined (properties[\"user_type\"]))"
|
135
|
+
```
|
136
|
+
|
137
|
+
|
138
|
+
## Actions
|
139
|
+
Mixpanel API client has a lot of possible actions, so far these are the supported actions:
|
140
|
+
|
141
|
+
### Segementation
|
142
|
+
Classic **Mixpanel** segmentation action, where you can specify *event name*, *from/to dates* and *any conditions* you want. Prepare and run as many request in parallel you need
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
interface = Mixpanel::Interface.new
|
146
|
+
|
147
|
+
page_visits_query = Mixpanel.where("product" => 'Finances')
|
148
|
+
.and.is_set("subproduct")
|
149
|
+
page_visits = interface.segmentation('Page visit', { from: Data.parse('2015-01-31'),
|
150
|
+
to: Date.today },
|
151
|
+
{ where: page_visits_query })
|
152
|
+
|
153
|
+
|
154
|
+
login_clicks_query = Mixpanel.where("device_type" => 'mobile')
|
155
|
+
login_clicks = interface.segmentation('Login', { from: Data.parse('2015-04-01'),
|
156
|
+
to: Date.today },
|
157
|
+
{ where: login_clicks_query })
|
158
|
+
|
159
|
+
# Run all the queued queries
|
160
|
+
interface.run!
|
161
|
+
|
162
|
+
# Present your data...
|
163
|
+
p page_visits[:data]
|
164
|
+
p login_clicks[:data]
|
165
|
+
```
|
166
|
+
|
167
|
+
### Segmentation by
|
168
|
+
Same as **segmentation** but grouping the output by any property you want to.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
interface = Mixpanel::Interface.new
|
172
|
+
|
173
|
+
page_visits_query = Mixpanel.where("product" => 'Finances')
|
174
|
+
.and.is_set("subproduct")
|
175
|
+
page_visits = interface.segmentation('Page visit', { from: Data.parse('2015-01-31'),
|
176
|
+
to: Date.today },
|
177
|
+
{ where: page_visits_query,
|
178
|
+
on: Mixpanel.on('device_type') })
|
179
|
+
|
180
|
+
# Run all the queued queries
|
181
|
+
interface.run!
|
182
|
+
|
183
|
+
# Present your data...
|
184
|
+
p page_visits[:data]
|
185
|
+
```
|
186
|
+
|
187
|
+
|
188
|
+
## Your own interface
|
189
|
+
Now you master all the above concepts, the best thing you can do is to build your own interface model for extracting your reports:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
module Mixpanel
|
193
|
+
class LandingPagesInterface < Mixpanel::Interface
|
194
|
+
|
195
|
+
def initialize
|
196
|
+
super()
|
197
|
+
end
|
198
|
+
|
199
|
+
def views(from: nil, to: nil)
|
200
|
+
where_context = Mixpanel.where('page_type' => 'landing')
|
201
|
+
.and.is_set('device_type')
|
202
|
+
|
203
|
+
segmentation 'Page view', { from: from, to: to },
|
204
|
+
{ where: where_context }
|
205
|
+
end
|
206
|
+
|
207
|
+
def views_by_devices(from: nil, to: nil)
|
208
|
+
where_context = Mixpanel.where('page_type' => 'landing')
|
209
|
+
.and.is_set('device_type')
|
210
|
+
|
211
|
+
segmentation_interval 'Page view', { from: from, to: to },
|
212
|
+
{ where: where_context,
|
213
|
+
on: Mixpanel.on('device_type') }
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Initialize your interface
|
220
|
+
landing_interface = Mixpanel::LandingPagesInterface.new
|
221
|
+
|
222
|
+
# Prepare all the queries
|
223
|
+
landing_last_week_views = landing_interface.views(from: 14.days.ago, to: 7.days.ago)
|
224
|
+
landing_this_week_views = landing_interface.views(from: 6.days.ago, to: Date.today)
|
225
|
+
landing_last_week_view_by_devices = landing_interface.views_by_devices(from: 14.days.ago, to: 7.days.ago)
|
226
|
+
|
227
|
+
# Run all the queries
|
228
|
+
landing_interface.run!
|
229
|
+
|
230
|
+
# Retrieve your data!
|
231
|
+
p landing_last_week_views[:data]
|
232
|
+
p landing_this_week_views[:data]
|
233
|
+
p landing_last_week_view_by_devices[:data]
|
234
|
+
|
235
|
+
```
|
236
|
+
|
237
|
+
|
238
|
+
## On top of mixpanel_client Gem
|
239
|
+
This ORM is build on top [mixpanel_client](https://github.com/keolo/mixpanel_client#mixpanel-data-api-client) gem.
|
240
|
+
|
241
|
+
You'll find the oficial mixpanel info [here](https://mixpanel.com/docs/api-documentation/data-export-api#libs-ruby).
|
242
|
+
|
243
|
+
### Monkey patching
|
244
|
+
On this gem you'll find a ```Mixpanel::Client``` class monkey patch to avoid exceptions raise
|
245
|
+
when any of the parallel request fails, i.e all the requests will run until completion and return
|
246
|
+
their correspondent HTTP status (error or success) and body as usual.
|
247
|
+
See it [here](https://github.com/gguerrero/mixpanel_magic_lamp/blob/master/lib/mixpanel_magic_lamp/client.rb).
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
## Contribution
|
252
|
+
If you have cool idea for improving this Gem or any bug fix just open a pull request and
|
253
|
+
I'll be glad to have a look and merge it if seems fine.
|
254
|
+
|
255
|
+
|
256
|
+
## License
|
257
|
+
|
258
|
+
This project rocks and uses [MIT-LICENSE](https://github.com/gguerrero/mixpanel_magic_lamp/blob/master/MIT-LICENSE).
|
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module MixpanelMagicLamp
|
2
|
+
class ConfigGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
|
5
|
+
desc "Generates the config initializer file for Mixpanel Magic Lamp options"
|
6
|
+
def copy_initializer_file
|
7
|
+
copy_file "mixpanel_magic_lamp.rb", "config/initializers/mixpanel_magic_lamp.rb"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# MONKEY PATCH!
|
2
|
+
# -------------
|
3
|
+
#
|
4
|
+
# Avoid 'fail' that makes all request to stop running...
|
5
|
+
# Original 4.1.1 lib/mixpanel/client.rb source:
|
6
|
+
#
|
7
|
+
# def prepare_parallel_request
|
8
|
+
# request = ::Typhoeus::Request.new(@uri)
|
9
|
+
#
|
10
|
+
# request.on_complete do |response|
|
11
|
+
# if response.success?
|
12
|
+
# Utils.to_hash(response.body, @format)
|
13
|
+
# elsif response.timed_out?
|
14
|
+
# fail TimeoutError
|
15
|
+
# elsif response.code == 0
|
16
|
+
# # Could not get an http response, something's wrong
|
17
|
+
# fail HTTPError, response.curl_error_message
|
18
|
+
# else
|
19
|
+
# # Received a non-successful http response
|
20
|
+
# if response.body && response.body != ''
|
21
|
+
# error_message = JSON.parse(response.body)['error']
|
22
|
+
# else
|
23
|
+
# error_message = response.code.to_s
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# fail HTTPError, error_message
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# request
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
#
|
34
|
+
module Mixpanel
|
35
|
+
|
36
|
+
class Client
|
37
|
+
def prepare_parallel_request
|
38
|
+
request = ::Typhoeus::Request.new(@uri)
|
39
|
+
|
40
|
+
request.on_complete do |response|
|
41
|
+
Utils.to_hash(response.body, @format) if response.success?
|
42
|
+
end
|
43
|
+
|
44
|
+
request
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MixpanelMagicLamp
|
2
|
+
class << self
|
3
|
+
attr_accessor :configuration
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.configure
|
7
|
+
self.configuration ||= Configuration.new
|
8
|
+
yield(configuration)
|
9
|
+
end
|
10
|
+
|
11
|
+
class Configuration
|
12
|
+
attr_accessor :api_key,
|
13
|
+
:api_secret,
|
14
|
+
:parallel,
|
15
|
+
:interval
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@parallel = true
|
19
|
+
@interval = 30
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class ApiKeyMissingError < StandardError
|
25
|
+
def initialize
|
26
|
+
super "Missing API key and/or secret. Please, configure them."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module MixpanelMagicLamp
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
def where(*args)
|
5
|
+
MixpanelMagicLamp::InstanceMethods::ExpressionBuilder.new(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def on(property)
|
9
|
+
"properties[\"#{property}\"]"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
class ExpressionBuilder
|
15
|
+
attr_reader :expression
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
@expression = ''
|
19
|
+
equals(*args) if args.any?
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
@expression
|
24
|
+
end
|
25
|
+
|
26
|
+
def and
|
27
|
+
@expression += ' and ' if @expression.present? and not @expression =~ /(and|or)\s*$/
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def or
|
32
|
+
@expression += ' or ' if @expression.present? and not @expression =~ /(and|or)\s*$/
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def equals(args = {}, union = 'and')
|
37
|
+
@expression += join(args, union, "properties[\":name\"] == \":value\"")
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def does_not_equal(args = {}, union = 'and')
|
42
|
+
@expression += join(args, union, "properties[\":name\"] != \":value\"")
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def contains(args = {}, union = 'and')
|
47
|
+
@expression += join(args, union, "\":value\" in (properties[\":name\"])")
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def does_not_contain(args = {}, union = 'and')
|
52
|
+
@expression += join(args, union, "not \":value\" in (properties[\":name\"])")
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_set(args = [], union = 'and')
|
57
|
+
@expression += join(args, union, "defined (properties[\":name\"])")
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_not_set(args = [], union = 'and')
|
62
|
+
@expression += join(args, union, "not defined (properties[\":name\"])")
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def join(args, union, exp)
|
68
|
+
args = [args] unless args.is_a? Array or args.is_a? Hash
|
69
|
+
built_in = '(' + args.compact.map do |name, values|
|
70
|
+
if values.nil?
|
71
|
+
exp.gsub(':name', name)
|
72
|
+
else
|
73
|
+
values = [values] unless values.is_a? Array
|
74
|
+
values.map do |value|
|
75
|
+
exp.gsub(':name', name.to_s).gsub(':value', value.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end.flatten.join(" #{union} ") + ')'
|
79
|
+
|
80
|
+
built_in == '()' ? '' : built_in
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module MixpanelMagicLamp
|
2
|
+
|
3
|
+
class Formatter
|
4
|
+
|
5
|
+
DEFAULT = 'values'
|
6
|
+
|
7
|
+
def initialize(request)
|
8
|
+
@response = request.response.handled_response.dup
|
9
|
+
end
|
10
|
+
|
11
|
+
def convert(format: DEFAULT)
|
12
|
+
send :"to_#{format}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_values
|
16
|
+
@response['data']['values']
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_line
|
20
|
+
@response['data']
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_pie
|
24
|
+
@response['data']['series'] = [ @response['data']['series'].first,
|
25
|
+
@response['data']['series'].last ]
|
26
|
+
date_for_value = @response['data']['series'].first
|
27
|
+
|
28
|
+
@response['data']['values'].each do |section, values|
|
29
|
+
@response['data']['values'][section] = @response['data']['values'][section][date_for_value]
|
30
|
+
end
|
31
|
+
|
32
|
+
@response['data']
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(method, *args)
|
36
|
+
puts "Format '#{method}' not available. Formatting as 'to_#{DEFAULT}' instead."
|
37
|
+
send :"to_#{DEFAULT}"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'mixpanel_client'
|
2
|
+
|
3
|
+
module MixpanelMagicLamp
|
4
|
+
|
5
|
+
module InstanceMethods
|
6
|
+
class Interface < ::Mixpanel::Client
|
7
|
+
|
8
|
+
attr_reader :queue
|
9
|
+
|
10
|
+
def initialize(interval: nil, parallel: nil, unit: 'day', type: 'unique')
|
11
|
+
if MixpanelMagicLamp.configuration.api_key.nil? or
|
12
|
+
MixpanelMagicLamp.configuration.api_secret.nil?
|
13
|
+
raise MixpanelMagicLamp::ApiKeyMissingError
|
14
|
+
end
|
15
|
+
|
16
|
+
@parallel = parallel.nil? ? MixpanelMagicLamp.configuration.parallel : parallel
|
17
|
+
@interval = interval.nil? ? MixpanelMagicLamp.configuration.interval : interval
|
18
|
+
@from = @interval.days.ago.to_date
|
19
|
+
@to = Date.today
|
20
|
+
@unit = unit
|
21
|
+
@type = type
|
22
|
+
|
23
|
+
@queue = MixpanelMagicLamp::Queue.new
|
24
|
+
|
25
|
+
super api_key: MixpanelMagicLamp.configuration.api_key,
|
26
|
+
api_secret: MixpanelMagicLamp.configuration.api_secret,
|
27
|
+
parallel: @parallel
|
28
|
+
end
|
29
|
+
|
30
|
+
def segmentation(event, dates = {}, options = {})
|
31
|
+
dates = { from: dates[:from] || @from, to: dates[:to] || @to }
|
32
|
+
@queue.push request('segmentation',
|
33
|
+
{ event: event,
|
34
|
+
type: @type,
|
35
|
+
unit: @unit,
|
36
|
+
from_date: dates[:from].strftime('%Y-%m-%d'),
|
37
|
+
to_date: dates[:to].strftime('%Y-%m-%d') }.merge(options)),
|
38
|
+
format: 'line'
|
39
|
+
end
|
40
|
+
|
41
|
+
def segmentation_interval(event, dates = {}, options = {})
|
42
|
+
dates = { from: dates[:from].to_date || @from, to: dates[:to].to_date || @to }
|
43
|
+
|
44
|
+
@queue.push request('segmentation',
|
45
|
+
{ event: event,
|
46
|
+
type: @type,
|
47
|
+
interval: (dates[:to] - dates[:from]).to_i + 1,
|
48
|
+
from_date: dates[:from].strftime('%Y-%m-%d'),
|
49
|
+
to_date: dates[:to].strftime('%Y-%m-%d') }.merge(options)),
|
50
|
+
format: 'pie'
|
51
|
+
end
|
52
|
+
|
53
|
+
def run!
|
54
|
+
run_parallel_requests
|
55
|
+
@queue.process!
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module MixpanelMagicLamp
|
4
|
+
|
5
|
+
class Queue < Array
|
6
|
+
|
7
|
+
def push(request, opts = {})
|
8
|
+
item = {
|
9
|
+
request: request,
|
10
|
+
format: opts.delete(:format),
|
11
|
+
status: nil,
|
12
|
+
response: nil,
|
13
|
+
data: nil }
|
14
|
+
|
15
|
+
self << item and return item
|
16
|
+
end
|
17
|
+
|
18
|
+
def process!
|
19
|
+
self.each do |request|
|
20
|
+
next unless request[:status].nil?
|
21
|
+
request[:status] = request[:request].response.code
|
22
|
+
|
23
|
+
if request[:request].response.success?
|
24
|
+
formatter = MixpanelMagicLamp::Formatter.new(request[:request])
|
25
|
+
request[:data] = formatter.convert format: request[:format]
|
26
|
+
else
|
27
|
+
request[:response] = JSON.parse(request[:request].response.body)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'mixpanel_client'
|
2
|
+
require 'mixpanel_magic_lamp/configuration'
|
3
|
+
require 'mixpanel_magic_lamp/engine' if defined? Rails
|
4
|
+
require 'mixpanel_magic_lamp/client'
|
5
|
+
require 'mixpanel_magic_lamp/expression_builder'
|
6
|
+
require 'mixpanel_magic_lamp/formatter'
|
7
|
+
require 'mixpanel_magic_lamp/queue'
|
8
|
+
require 'mixpanel_magic_lamp/interface'
|
9
|
+
|
10
|
+
# Include and extend the magic lamp
|
11
|
+
Mixpanel.extend MixpanelMagicLamp::ClassMethods
|
12
|
+
module Mixpanel
|
13
|
+
include MixpanelMagicLamp::InstanceMethods
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "mixpanel_magic_lamp/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "mixpanel_magic_lamp"
|
9
|
+
s.version = MixpanelMagicLamp::VERSION
|
10
|
+
s.authors = ["Guillermo Guerrero"]
|
11
|
+
s.email = ["g.guerrero.bus@gmail.com"]
|
12
|
+
s.homepage = "https://github.com/gguerrero/mixpanel_client_interface"
|
13
|
+
s.summary = "Mixpanel client ORM!"
|
14
|
+
s.description = "Mixpanel client ORM for easy querying and reporting data."
|
15
|
+
s.license = "MIT"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.add_dependency "mixpanel_client", ">= 4.1.1"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mixpanel_magic_lamp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillermo Guerrero
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mixpanel_client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.1.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.1.1
|
27
|
+
description: Mixpanel client ORM for easy querying and reporting data.
|
28
|
+
email:
|
29
|
+
- g.guerrero.bus@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- CHANGELOG.md
|
36
|
+
- Gemfile
|
37
|
+
- MIT-LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- lib/generators/mixpanel_magic_lamp/config_generator.rb
|
41
|
+
- lib/generators/mixpanel_magic_lamp/templates/mixpanel_magic_lamp.rb
|
42
|
+
- lib/mixpanel_magic_lamp.rb
|
43
|
+
- lib/mixpanel_magic_lamp/client.rb
|
44
|
+
- lib/mixpanel_magic_lamp/configuration.rb
|
45
|
+
- lib/mixpanel_magic_lamp/engine.rb
|
46
|
+
- lib/mixpanel_magic_lamp/expression_builder.rb
|
47
|
+
- lib/mixpanel_magic_lamp/formatter.rb
|
48
|
+
- lib/mixpanel_magic_lamp/interface.rb
|
49
|
+
- lib/mixpanel_magic_lamp/queue.rb
|
50
|
+
- lib/mixpanel_magic_lamp/version.rb
|
51
|
+
- mixpanel_magic_lamp.gemspec
|
52
|
+
homepage: https://github.com/gguerrero/mixpanel_client_interface
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata: {}
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 2.4.8
|
73
|
+
signing_key:
|
74
|
+
specification_version: 4
|
75
|
+
summary: Mixpanel client ORM!
|
76
|
+
test_files: []
|