mixpannenkoek 0.0.2 → 0.0.3
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 +4 -4
- data/README.md +101 -21
- data/lib/mixpannenkoek/base.rb +20 -15
- data/lib/mixpannenkoek/class_inheritable_attribute.rb +21 -13
- data/lib/mixpannenkoek/query.rb +11 -4
- data/lib/mixpannenkoek/version.rb +1 -1
- data/spec/lib/mixpannenkoek/query_spec.rb +11 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc559cc6c5f4a55a318e78cfea0dd26fc5abcb8d
|
4
|
+
data.tar.gz: ba9b0af6737d9c8c4c7fbfdf7bd495ce53d0383d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3bcaaef791d0848be8bf5bfd31e0434ecd51d3318037655bc2892ab2e56177707a1077c1fb75750cda43bf6065c2b7360d402200e4e21c1a60e3965fd840cd7
|
7
|
+
data.tar.gz: 90a9757a0c9db3d992688481ae9d0acd76d5e2e2176809851715d367668948b9cdc24ee7ef606d0a5b3c6d4a56d8f208a4202648ff7824299cb16e7bb3441a10
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Mixpannenkoek
|
2
2
|
|
3
|
-
This gem implements a fluent query interface for mixpanel_client.
|
3
|
+
This gem implements a fluent query interface for the [mixpanel_client](https://github.com/keolo/mixpanel_client) gem.
|
4
|
+
|
5
|
+
[](https://codeclimate.com/github/Springest/mixpannenkoek)
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -18,46 +20,124 @@ Or install it yourself as:
|
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
Begin by configuring a model.
|
23
|
+
Begin by creating and configuring a model. In Rails you might place this in `app/models/conversion_funnel.rb`. You *must* set the api_key, api_secret, and endpoint. These properties are heritable, so in the following example, `OtherFunnel` will have the api_key, api_secret and endpoint already set.
|
22
24
|
|
23
25
|
```ruby
|
24
|
-
class
|
26
|
+
class Funnel::Base < Mixpannenkoek::Base
|
25
27
|
set_api_key 'MY_API_KEY'
|
26
28
|
set_api_secret 'MY_API_SECRET'
|
27
|
-
set_endpoint 'funnels'
|
28
|
-
|
29
|
+
set_endpoint 'funnels' # or any other endpoint
|
30
|
+
end
|
31
|
+
|
32
|
+
class Funnel::Conversions < Funnel::Base
|
29
33
|
default_scope { set(funnel_id: 123456) }
|
30
34
|
end
|
31
35
|
```
|
32
36
|
|
33
|
-
|
37
|
+
With mixpanel_client, you might run a query like this:
|
34
38
|
|
35
39
|
```ruby
|
36
|
-
|
37
|
-
|
38
|
-
|
40
|
+
client = Mixpanel::Client.new(
|
41
|
+
api_key: 'MY_API_KEY',
|
42
|
+
api_secret: 'MY_API_SECRET'
|
43
|
+
)
|
44
|
+
|
45
|
+
data = client.request(
|
46
|
+
'funnels',
|
47
|
+
funnel_id: 123456,
|
48
|
+
from_date: '2014-01-01',
|
49
|
+
to_date: '2014-01-31',
|
50
|
+
interval: 31,
|
51
|
+
on: 'properties["traffic_source"]',
|
52
|
+
where: 'properties["user_type"] = "visitor" AND properties["landing_page"] = "homepage"',
|
53
|
+
)
|
39
54
|
```
|
40
55
|
|
41
|
-
|
56
|
+
With mixpannenkoek, you would write it like this (making use of the models defined above):
|
42
57
|
|
43
58
|
```ruby
|
44
|
-
|
45
|
-
#=> [1, 4, 2]
|
59
|
+
Funnel::Conversions.where(date: Date.parse('2014-01-01')..Date.parse('2014-01-31')).set(interval: 31).group('traffic_source').where(user_type: 'visitor').where(landing_page: 'homepage')
|
46
60
|
```
|
47
61
|
|
48
|
-
|
62
|
+
`where` allows you to easily build the `where` parameter of the request.
|
63
|
+
|
64
|
+
`group` corresponds to the `on` parameter.
|
65
|
+
|
66
|
+
`set` sets any other parameters in the request (in this case, `funnel_id` is mandatory).
|
67
|
+
|
68
|
+
This gem also supports `default_scope`, which is also heritable. Some of the parameters above might instead be set in the model, to save time.
|
69
|
+
|
49
70
|
```ruby
|
50
|
-
class
|
51
|
-
default_scope { set(
|
71
|
+
class Funnel::Conversions < Funnel::Base
|
72
|
+
default_scope { set(funnel_id: 123456) }
|
73
|
+
default_scope { set(interval: 31) }
|
52
74
|
default_scope { where(user_type: 'visitor') }
|
53
75
|
end
|
76
|
+
```
|
54
77
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
78
|
+
Building up the query then becomes a little bit easier:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
Funnel::Conversions.where(date: Date.parse('2014-01-01')..Date.parse('2014-01-31')).group('traffic_source').where(landing_page: 'homepage')
|
82
|
+
```
|
83
|
+
|
84
|
+
Note: you are not required to set the `funnel_id` in the model itself. The following queries are possible:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
Funnel::Base.set(funnel_id: 987654).where(date: range)
|
88
|
+
```
|
89
|
+
|
90
|
+
Operating on the response data is also fluent. Just call a method (including `[]`). This will trigger a request to the mixpanel API and make the response data available:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
Funnel::Conversions.where(date: 31.days.ago..1.day.ago).response_data
|
94
|
+
#=> {"2010-05-24"=>
|
95
|
+
{"analysis"=>
|
96
|
+
{"completion"=>0.0646793595800525,
|
97
|
+
"starting_amount"=>762,
|
98
|
+
"steps"=>3,
|
99
|
+
"worst"=>2},
|
100
|
+
"steps"=>
|
101
|
+
[{"count"=>762,
|
102
|
+
"goal"=>"pages",
|
103
|
+
"overall_conv_ratio"=>1.0,
|
104
|
+
"step_conv_ratio"=>1.0},
|
105
|
+
{"count"=>69,
|
106
|
+
"goal"=>"View signup",
|
107
|
+
"overall_conv_ratio"=>0.09055118110236221,
|
108
|
+
"step_conv_ratio"=>0.09055118110236221},
|
109
|
+
{"count"=>10,
|
110
|
+
"goal"=>"View docs",
|
111
|
+
"overall_conv_ratio"=>0.0646793595800525,
|
112
|
+
"step_conv_ratio"=>0.7142857142857143}]},
|
113
|
+
"2010-05-31"=>
|
114
|
+
{"analysis"=>
|
115
|
+
{"completion"=>0.12362030905077263,
|
116
|
+
"starting_amount"=>906,
|
117
|
+
"steps"=>2,
|
118
|
+
"worst"=>2},
|
119
|
+
"steps"=>
|
120
|
+
[{"count"=>906,
|
121
|
+
"goal"=>"homepage",
|
122
|
+
"overall_conv_ratio"=>1.0,
|
123
|
+
"step_conv_ratio"=>1.0},
|
124
|
+
{"count"=>112,
|
125
|
+
"goal"=>"View signup",
|
126
|
+
"overall_conv_ratio"=>0.12362030905077263,
|
127
|
+
"step_conv_ratio"=>0.12362030905077263}]}}
|
128
|
+
|
129
|
+
Funnel::Conversions.where(date: 31.days.ago..1.day.ago)['2010-05-24']['steps'][0]['count']
|
130
|
+
#=> 762
|
131
|
+
|
132
|
+
Funnel::Conversions.where(date: 31.days.ago..1.day.ago).map { |date,data| data['steps'].last['count'] }
|
133
|
+
#=> [10, 112]
|
134
|
+
```
|
135
|
+
|
136
|
+
Query objects are also immutable. So it's possible to organize your code in the following manner:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
query_1 = Funnel::Conversions.where(date: 31.days.ago..1.day.ago)
|
140
|
+
query_2 = query_1.where(traffic_source: 'google') # this leaves query_1 unchanged
|
61
141
|
```
|
62
142
|
|
63
143
|
## Contributing
|
data/lib/mixpannenkoek/base.rb
CHANGED
@@ -5,33 +5,28 @@ module Mixpannenkoek
|
|
5
5
|
extend ::Mixpannenkoek::ClassInheritableAttribute
|
6
6
|
class_inheritable_attribute :_api_key, :_api_secret, :_endpoint, :_default_scope
|
7
7
|
|
8
|
-
# Public: the mixpanel api key
|
9
8
|
def self.set_api_key(api_key = nil, &block)
|
10
|
-
|
11
|
-
self._api_key = api_key || block
|
9
|
+
self._api_key = value_or_block(api_key, &block)
|
12
10
|
end
|
13
11
|
|
14
|
-
def self.
|
15
|
-
self.
|
12
|
+
def self.set_api_secret(api_secret = nil, &block)
|
13
|
+
self._api_secret = value_or_block(api_secret, &block)
|
16
14
|
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
raise ArgumentError if !api_secret.nil? && !block.nil?
|
21
|
-
self._api_secret = api_secret || block
|
16
|
+
def self.set_endpoint(endpoint = nil, &block)
|
17
|
+
self._endpoint = value_or_block(endpoint, &block)
|
22
18
|
end
|
23
19
|
|
24
|
-
def self.
|
25
|
-
self.
|
20
|
+
def self.api_key
|
21
|
+
value_from_block(self._api_key)
|
26
22
|
end
|
27
23
|
|
28
|
-
def self.
|
29
|
-
|
30
|
-
self._endpoint = endpoint || block
|
24
|
+
def self.api_secret
|
25
|
+
value_from_block(self._api_secret)
|
31
26
|
end
|
32
27
|
|
33
28
|
def self.endpoint
|
34
|
-
self._endpoint
|
29
|
+
value_from_block(self._endpoint)
|
35
30
|
end
|
36
31
|
|
37
32
|
### Class methods (for convenience)
|
@@ -63,5 +58,15 @@ module Mixpannenkoek
|
|
63
58
|
self._default_scope ||= []
|
64
59
|
self._default_scope.map{ |p| p.call }
|
65
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def self.value_or_block(value, &block)
|
64
|
+
raise ArgumentError unless !!value ^ !!block
|
65
|
+
value || block
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.value_from_block(value_or_proc)
|
69
|
+
value_or_proc.respond_to?(:call) ? value_or_proc.call : value_or_proc
|
70
|
+
end
|
66
71
|
end
|
67
72
|
end
|
@@ -2,20 +2,28 @@ module Mixpannenkoek
|
|
2
2
|
module ClassInheritableAttribute
|
3
3
|
def class_inheritable_attribute(*attributes)
|
4
4
|
attributes.map(&:to_sym).each do |attribute|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
create_setter(attribute)
|
6
|
+
create_getter(attribute)
|
7
|
+
end
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
def create_setter(attribute)
|
11
|
+
define_singleton_method("#{attribute}=") do |value|
|
12
|
+
@@class_inheritable_attributes ||= {}
|
13
|
+
@@class_inheritable_attributes[attribute] ||= {}
|
14
|
+
|
15
|
+
@@class_inheritable_attributes[attribute][self.name] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_getter(attribute)
|
20
|
+
define_singleton_method(attribute) do
|
21
|
+
if @@class_inheritable_attributes[attribute] && @@class_inheritable_attributes[attribute].has_key?(self.name)
|
22
|
+
@@class_inheritable_attributes[attribute][self.name]
|
23
|
+
elsif superclass.respond_to?(attribute)
|
24
|
+
superclass.send(attribute)
|
25
|
+
else
|
26
|
+
nil
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
data/lib/mixpannenkoek/query.rb
CHANGED
@@ -57,10 +57,7 @@ module Mixpannenkoek
|
|
57
57
|
query = @vars
|
58
58
|
|
59
59
|
if @where && @where != {}
|
60
|
-
|
61
|
-
query[:from_date] = @where[:date].first.strftime('%Y-%m-%d')
|
62
|
-
query[:to_date] = @where[:date].last.strftime('%Y-%m-%d')
|
63
|
-
end
|
60
|
+
extract_dates(query, @where)
|
64
61
|
|
65
62
|
query[:where] = @where.map do |key,value|
|
66
63
|
next if key == :date
|
@@ -118,5 +115,15 @@ module Mixpannenkoek
|
|
118
115
|
time = ::Benchmark.ms(&block)
|
119
116
|
Rails.logger.info " Mixpanel (#{time.round(1)}ms) #{request_parameters.inspect}" if defined? Rails
|
120
117
|
end
|
118
|
+
|
119
|
+
def extract_dates(query, where)
|
120
|
+
return unless where[:date]
|
121
|
+
|
122
|
+
query[:from_date] = where[:date].first
|
123
|
+
query[:to_date] = where[:date].last
|
124
|
+
|
125
|
+
query[:from_date] = query[:from_date].strftime('%Y-%m-%d') if query[:from_date].respond_to? :strftime
|
126
|
+
query[:to_date] = query[:to_date].strftime('%Y-%m-%d') if query[:to_date].respond_to? :strftime
|
127
|
+
end
|
121
128
|
end
|
122
129
|
end
|
@@ -43,6 +43,17 @@ describe Mixpannenkoek::Base do
|
|
43
43
|
it 'sets :to_date' do
|
44
44
|
expect(subject).to include({ to_date: '2014-01-05' })
|
45
45
|
end
|
46
|
+
|
47
|
+
context 'date strings' do
|
48
|
+
let(:date_range) { '2014-01-01'..'2014-01-31' }
|
49
|
+
it 'sets :from_date' do
|
50
|
+
expect(subject).to include({ from_date: '2014-01-01' })
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'sets :to_date' do
|
54
|
+
expect(subject).to include({ to_date: '2014-01-31' })
|
55
|
+
end
|
56
|
+
end
|
46
57
|
end
|
47
58
|
end
|
48
59
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mixpannenkoek
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Kraan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-05-
|
11
|
+
date: 2014-05-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mixpanel_client
|