mixpannenkoek 0.0.3 → 0.0.4
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/.travis.yml +9 -0
- data/CONTRIBUTORS.md +3 -0
- data/README.md +7 -5
- data/lib/mixpannenkoek/query.rb +39 -20
- data/lib/mixpannenkoek/version.rb +1 -1
- data/spec/lib/mixpannenkoek/query_spec.rb +26 -5
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c522161ceeb1a1cb920c8df995fbbdc742a9afa8
|
4
|
+
data.tar.gz: 9700672e659aa9477cadc2ca4bfb4d4a2dcaafca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14103a289695a37071c7f700705436665f95085d172477f885a8563f16b6d9c144dc54874ea3b435275842082854abb567ac4c03d3ef6cf5c5a19215790a12b0
|
7
|
+
data.tar.gz: 8cef5cadd63a7ecd662fba07f4154e8e17c41bc0f4dc576449ef529c08b7f90596cd59b36f72ffe79d4c7eda8a16dc8f66a69f8f30d7ad5feccdd196b53129f9
|
data/.travis.yml
ADDED
data/CONTRIBUTORS.md
ADDED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Mixpannenkoek
|
2
2
|
|
3
|
-
|
3
|
+
Mixpannenkoek implements a fluent query interface for Mixpanel. It uses the [mixpanel_client](https://github.com/keolo/mixpanel_client) gem.
|
4
|
+
|
5
|
+
Read the [introductory blogpost](http://devblog.springest.com/introducing-mixpannenkoek).
|
4
6
|
|
5
7
|
[](https://codeclimate.com/github/Springest/mixpannenkoek)
|
6
8
|
|
@@ -20,7 +22,7 @@ Or install it yourself as:
|
|
20
22
|
|
21
23
|
## Usage
|
22
24
|
|
23
|
-
Begin by creating and configuring a model. In Rails you might place this in `app/models/
|
25
|
+
Begin by creating and configuring a model. In Rails you might place this in `app/models/funnel/conversions.rb`. You *must* set the api_key, api_secret, and endpoint. These properties are heritable, so in the following example, `Funnel::Conversions` will have the api_key, api_secret and endpoint set in `Funnel::Base`.
|
24
26
|
|
25
27
|
```ruby
|
26
28
|
class Funnel::Base < Mixpannenkoek::Base
|
@@ -38,7 +40,7 @@ With mixpanel_client, you might run a query like this:
|
|
38
40
|
|
39
41
|
```ruby
|
40
42
|
client = Mixpanel::Client.new(
|
41
|
-
api_key: 'MY_API_KEY',
|
43
|
+
api_key: 'MY_API_KEY',
|
42
44
|
api_secret: 'MY_API_SECRET'
|
43
45
|
)
|
44
46
|
|
@@ -59,7 +61,7 @@ With mixpannenkoek, you would write it like this (making use of the models defin
|
|
59
61
|
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')
|
60
62
|
```
|
61
63
|
|
62
|
-
`where`
|
64
|
+
`where` and `where.not` allow you to easily build the `where` parameter of the request.
|
63
65
|
|
64
66
|
`group` corresponds to the `on` parameter.
|
65
67
|
|
@@ -125,7 +127,7 @@ Funnel::Conversions.where(date: 31.days.ago..1.day.ago).response_data
|
|
125
127
|
"goal"=>"View signup",
|
126
128
|
"overall_conv_ratio"=>0.12362030905077263,
|
127
129
|
"step_conv_ratio"=>0.12362030905077263}]}}
|
128
|
-
|
130
|
+
|
129
131
|
Funnel::Conversions.where(date: 31.days.ago..1.day.ago)['2010-05-24']['steps'][0]['count']
|
130
132
|
#=> 762
|
131
133
|
|
data/lib/mixpannenkoek/query.rb
CHANGED
@@ -8,17 +8,23 @@ module Mixpannenkoek
|
|
8
8
|
|
9
9
|
attr_accessor :where, :group, :klass
|
10
10
|
|
11
|
-
def initialize(klass, where = {}, vars = {}, group = nil)
|
11
|
+
def initialize(klass, where = {}, where_not = {}, vars = {}, group = nil)
|
12
12
|
@where = where
|
13
|
+
@where_not = where_not
|
13
14
|
@vars = vars
|
14
15
|
@group = group
|
15
16
|
@klass = klass
|
16
17
|
end
|
17
18
|
|
18
|
-
def where(condition)
|
19
|
+
def where(condition = nil)
|
20
|
+
return self if condition.nil?
|
19
21
|
chain(where: @where.merge(condition))
|
20
22
|
end
|
21
23
|
|
24
|
+
def not(condition)
|
25
|
+
chain(where_not: @where_not.merge(condition))
|
26
|
+
end
|
27
|
+
|
22
28
|
def set(variable)
|
23
29
|
chain(vars: @vars.merge(variable))
|
24
30
|
end
|
@@ -56,26 +62,24 @@ module Mixpannenkoek
|
|
56
62
|
def query
|
57
63
|
query = @vars
|
58
64
|
|
59
|
-
if @where && @where != {}
|
65
|
+
if (@where && @where != {}) || (@where_not && @where_not != {})
|
66
|
+
query[:where] = []
|
67
|
+
|
60
68
|
extract_dates(query, @where)
|
61
69
|
|
62
|
-
query[:where]
|
63
|
-
|
70
|
+
query[:where] += @where.map do |key,value|
|
71
|
+
where_clause(key, value)
|
72
|
+
end
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
else
|
69
|
-
%Q(properties["#{key}"] == "#{value}")
|
70
|
-
end
|
71
|
-
end.compact
|
74
|
+
query[:where] += @where_not.map do |key,value|
|
75
|
+
where_not_clause(key, value)
|
76
|
+
end
|
72
77
|
|
73
|
-
query[:where]
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
78
|
+
if query[:where].compact != []
|
79
|
+
query[:where] = query[:where].compact.join(' and ')
|
80
|
+
else
|
81
|
+
query.delete(:where)
|
82
|
+
end
|
79
83
|
end
|
80
84
|
|
81
85
|
query[:on] = %Q(properties["#{@group}"]) if @group
|
@@ -85,8 +89,8 @@ module Mixpannenkoek
|
|
85
89
|
|
86
90
|
private
|
87
91
|
|
88
|
-
def chain(klass: @klass, where: @where, vars: @vars, group: @group)
|
89
|
-
self.class.new(klass, where, vars, group)
|
92
|
+
def chain(klass: @klass, where: @where, where_not: @where_not, vars: @vars, group: @group)
|
93
|
+
self.class.new(klass, where, where_not, vars, group)
|
90
94
|
end
|
91
95
|
|
92
96
|
def mixpanel_client
|
@@ -125,5 +129,20 @@ module Mixpannenkoek
|
|
125
129
|
query[:from_date] = query[:from_date].strftime('%Y-%m-%d') if query[:from_date].respond_to? :strftime
|
126
130
|
query[:to_date] = query[:to_date].strftime('%Y-%m-%d') if query[:to_date].respond_to? :strftime
|
127
131
|
end
|
132
|
+
|
133
|
+
def where_clause(key, value, operator = '==', join = 'or')
|
134
|
+
return nil if key == :date
|
135
|
+
|
136
|
+
case value
|
137
|
+
when Array
|
138
|
+
%Q((#{value.map { |val| %Q(properties["#{key}"] #{operator} "#{val}") }.join(" #{join} ")}))
|
139
|
+
else
|
140
|
+
%Q(properties["#{key}"] #{operator} "#{value}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def where_not_clause(key, value)
|
145
|
+
where_clause(key, value, '!=', 'and')
|
146
|
+
end
|
128
147
|
end
|
129
148
|
end
|
@@ -57,6 +57,27 @@ describe Mixpannenkoek::Base do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
describe '#where.not' do
|
61
|
+
subject { Mixpannenkoek::TestQuery.where(date: date_range).where.not(subject_name: 'Subject ABC').request_parameters[1] }
|
62
|
+
it 'sets :where' do
|
63
|
+
expect(subject).to include({ where: 'properties["subject_name"] != "Subject ABC"' })
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'called twice' do
|
67
|
+
subject { Mixpannenkoek::TestQuery.where(date: date_range).where.not(subject_name: 'Subject ABC').where.not(training_name: 'Training XYZ').request_parameters[1] }
|
68
|
+
it 'sets multiple :where conditions' do
|
69
|
+
expect(subject).to include({ where: 'properties["subject_name"] != "Subject ABC" and properties["training_name"] != "Training XYZ"' })
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'called with value: []' do
|
74
|
+
subject { Mixpannenkoek::TestQuery.where(date: date_range).where.not(subject_name: ['Subject ABC', 'Subject XYZ']).request_parameters[1] }
|
75
|
+
it 'sets multiple :where conditions' do
|
76
|
+
expect(subject).to include({ where: '(properties["subject_name"] != "Subject ABC" and properties["subject_name"] != "Subject XYZ")' })
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
60
81
|
describe '#group' do
|
61
82
|
subject { Mixpannenkoek::TestQuery.where(date: date_range).group('subject_name').request_parameters[1] }
|
62
83
|
it 'sets :on' do
|
@@ -73,7 +94,7 @@ describe Mixpannenkoek::Base do
|
|
73
94
|
|
74
95
|
describe '#set' do
|
75
96
|
subject { Mixpannenkoek::TestQuery.set(funnel_id: 12345).where(date: date_range).request_parameters[1] }
|
76
|
-
it {
|
97
|
+
it { expect(subject).to include({ funnel_id: 12345 }) }
|
77
98
|
|
78
99
|
context 'called twice' do
|
79
100
|
subject { Mixpannenkoek::TestQuery.set(funnel_id: 12345).set(event: 'click').where(date: date_range).request_parameters[1] }
|
@@ -92,7 +113,7 @@ describe Mixpannenkoek::Base do
|
|
92
113
|
|
93
114
|
context 'missing api_key' do
|
94
115
|
subject { Mixpannenkoek::TestQuery.where(date: date_range).to_hash }
|
95
|
-
before { Mixpannenkoek::TestQuery.
|
116
|
+
before { allow(Mixpannenkoek::TestQuery).to receive(:api_key) { nil } }
|
96
117
|
it 'raises Mixpannenkoek::Query::MissingConfiguration' do
|
97
118
|
expect { subject }.to raise_error Mixpannenkoek::Query::MissingConfiguration, 'The mixpanel api_key has not been configured'
|
98
119
|
end
|
@@ -100,7 +121,7 @@ describe Mixpannenkoek::Base do
|
|
100
121
|
|
101
122
|
context 'missing api_secret' do
|
102
123
|
subject { Mixpannenkoek::TestQuery.where(date: date_range).to_hash }
|
103
|
-
before { Mixpannenkoek::TestQuery.
|
124
|
+
before { allow(Mixpannenkoek::TestQuery).to receive(:api_secret) { nil } }
|
104
125
|
it 'raises Mixpannenkoek::Query::MissingConfiguration' do
|
105
126
|
expect { subject }.to raise_error Mixpannenkoek::Query::MissingConfiguration, 'The mixpanel api_secret has not been configured'
|
106
127
|
end
|
@@ -111,7 +132,7 @@ describe Mixpannenkoek::Base do
|
|
111
132
|
subject { Mixpannenkoek::TestQuery.where(date: date_range).results }
|
112
133
|
|
113
134
|
let(:response_data) { JSON.parse(File.open("#{File.dirname(__FILE__)}/../../fixtures/funnel_response_data.json").read) }
|
114
|
-
before { Mixpanel::Client.
|
135
|
+
before { allow_any_instance_of(Mixpanel::Client).to receive(:request).and_return(response_data) }
|
115
136
|
|
116
137
|
it 'returns a Mixpannenkoek::Results::Base object' do
|
117
138
|
expect(subject).to be_a Mixpannenkoek::Results::Base
|
@@ -119,7 +140,7 @@ describe Mixpannenkoek::Base do
|
|
119
140
|
end
|
120
141
|
|
121
142
|
describe '#method_missing' do
|
122
|
-
before { Mixpanel::Client.
|
143
|
+
before { allow_any_instance_of(Mixpanel::Client).to receive(:request).and_return({ "data" => { "2014-01-01" => [{ 'count' => '1' }, { 'count' => '4' }] } }) }
|
123
144
|
it 'delegates all calls to #results.send(*args)' do
|
124
145
|
expect(Mixpannenkoek::TestQuery.where(date: date_range).keys).to eq ['2014-01-01']
|
125
146
|
end
|
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.4
|
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-
|
11
|
+
date: 2014-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mixpanel_client
|
@@ -75,6 +75,8 @@ extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
76
76
|
files:
|
77
77
|
- .gitignore
|
78
|
+
- .travis.yml
|
79
|
+
- CONTRIBUTORS.md
|
78
80
|
- Gemfile
|
79
81
|
- LICENSE.txt
|
80
82
|
- README.md
|