mixpanel_magic_lamp 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/mixpanel_magic_lamp.svg)](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: []
|