totalizer 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +195 -68
- data/lib/tasks/totalizer.rake +26 -0
- data/lib/totalizer/base.rb +48 -0
- data/lib/totalizer/errors.rb +3 -0
- data/lib/totalizer/factory.rb +30 -52
- data/lib/totalizer/logger.rb +9 -0
- data/lib/totalizer/message.rb +40 -31
- data/lib/totalizer/metric.rb +1 -1
- data/lib/totalizer/notifier/action_mailer_notifier.rb +9 -0
- data/lib/totalizer/notifier/base_notifier.rb +4 -0
- data/lib/totalizer/notifier/email_notifier.rb +23 -0
- data/lib/totalizer/notifier/log_notifier.rb +14 -0
- data/lib/totalizer/notifier/mandrill_mailer_notifier.rb +9 -0
- data/lib/totalizer/notifier/slack_notifier.rb +16 -0
- data/lib/totalizer/notifier.rb +6 -0
- data/lib/totalizer/railtie.rb +12 -0
- data/lib/totalizer/version.rb +1 -1
- data/lib/totalizer.rb +4 -0
- data/spec/lib/totalizer/factory_spec.rb +20 -61
- data/spec/lib/totalizer/metric_spec.rb +10 -2
- data/spec/lib/totalizer_spec.rb +91 -0
- data/spec/spec_helper.rb +1 -0
- data/totalizer.gemspec +3 -0
- metadata +57 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64378db2593e41ea508fbd469dd20f36bb010652
|
4
|
+
data.tar.gz: aa0ea7c47ed3bed604157154b31bca1f21fcd1d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee4d5424e72d83a47e1ea9fa91de4d5b144072d90ece8c5e69aebfa1c10b5aec40ce10b6277181c2e889de34721e2fc629cf392e1ccac0fc44bbb512b53ea880
|
7
|
+
data.tar.gz: 91462bfd5e919eedf7291b8bb4d9359df1816c7fa9fbba7b19ec87a90dd6f82d5e8c15621ddee4fc8b013c3a49531214308914cf5c874a2c495b603adda8f60c
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
# Totalizer - Calculate important metrics in your Rails application.
|
1
|
+
# Totalizer - Calculate and share important metrics in your Rails application.
|
2
2
|
|
3
|
-
|
3
|
+
Totalizer makes it easy for Ruby on Rails developers to report on Acquisition, Activation, Activity, Engagement, Retention and Churn.
|
4
|
+
|
5
|
+
By defining two key metrics, Growth and Key Activity, all five reports can be generated.
|
6
|
+
|
7
|
+
Metrics are only worthwhile if your team sees them so Totalizer includes notifiers for Slack and email.
|
4
8
|
|
5
9
|
### Installation
|
6
10
|
|
@@ -16,13 +20,172 @@ Or install it yourself as:
|
|
16
20
|
|
17
21
|
$ gem install totalizer
|
18
22
|
|
19
|
-
|
23
|
+
### Configuration
|
24
|
+
|
25
|
+
You need to define your Growth Metric and Key Activity Metric in order to use Totalizer. You can do this by creating a Totalizer initializer file in your Rails application:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# config/initializers/totalizer.rb
|
29
|
+
|
30
|
+
Totalizer.growth_metric = Totalizer::Metric.new model: User
|
31
|
+
Totalizer.activity_metric = Totalizer::Metric.new model: Post, map: 'user_id'
|
32
|
+
```
|
33
|
+
|
34
|
+
The Growth Metric and the Key Activity Metric need to map to the same id to allow comparison.
|
35
|
+
|
36
|
+
#### Metrics
|
37
|
+
|
38
|
+
A Metric is a calculation based on one of your models for a duration of time.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
metric = Totalizer::Metric.new(model: User)
|
42
|
+
|
43
|
+
metric.value
|
44
|
+
#=> 10 (the number of records for the period)
|
45
|
+
|
46
|
+
metric.start
|
47
|
+
#=> 20 (the number of records before the period)
|
48
|
+
|
49
|
+
metric.finish
|
50
|
+
#=> 30 (the number of records at the end of the period)
|
51
|
+
|
52
|
+
metric.rate
|
53
|
+
#=> 0.5 (how much the number changed over the period)
|
54
|
+
```
|
55
|
+
|
56
|
+
##### Parameters
|
57
|
+
|
58
|
+
You can pass the following parameters into the Metric:
|
59
|
+
+ `model` (required): The Rails model class that Totalizer will query.
|
60
|
+
+ `date`: When to start measuring your records. Default is `now`.
|
61
|
+
+ `duration`: Duration (in days) to measure your records from. Must be an integer. Default is `7`.
|
62
|
+
+ `filter`: Write a custom query to determine which records to use in calculation. For example to find all users who created a public response you could pass in: `filter: "is_public = true"`.
|
63
|
+
+ `map`: Which field to map records on. For example, to find unique users who did a response you could pass in: `map: 'user_id'`. Default is `id`.
|
64
|
+
|
65
|
+
#### Notifiers
|
66
|
+
|
67
|
+
By default, running the Totalizer rake tasks will output the metrics to your logs
|
68
|
+
|
69
|
+
##### Action Mailer
|
70
|
+
|
71
|
+
You can configure Totalizer to email metrics using ActionMailer, just ensure you have ActionMailer configured.
|
72
|
+
|
73
|
+
To do this add the following lines to your initializer:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
Totalizer.notifiers = {
|
77
|
+
action_mailer: {
|
78
|
+
from: %{"notifier" <notifier@example.com>},
|
79
|
+
to: %w{team@example.com}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
```
|
83
|
+
|
84
|
+
You can also optionally include: `subject`.
|
85
|
+
|
86
|
+
##### Mandrill Mailer
|
87
|
+
|
88
|
+
If you are using [mandrill_mailer gem](https://github.com/renz45/mandrill_mailer) in your application then you can configure Totalizer to email metrics using MandrillMailer, just ensure you have MandrillMailer configured.
|
89
|
+
|
90
|
+
To do this add the following lines to your initializer:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
Totalizer.notifiers = {
|
94
|
+
mandrill_mailer: {
|
95
|
+
from: %{"notifier" <notifier@example.com>},
|
96
|
+
to: %w{team@example.com}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
```
|
100
|
+
|
101
|
+
You can also optionally include: `subject`.
|
20
102
|
|
21
|
-
|
103
|
+
##### Slack Notifier
|
22
104
|
|
23
|
-
|
105
|
+
You can configure Totalizer to post to Slack by including the [slack-notifier gem](https://github.com/stevenosloan/slack-notifier) in your Gemfile.
|
24
106
|
|
25
|
-
|
107
|
+
```ruby
|
108
|
+
gem 'slack-notifier'
|
109
|
+
```
|
110
|
+
|
111
|
+
Then add the following lines to your initializer:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
Totalizer.notifiers = {
|
115
|
+
slack: {
|
116
|
+
webhook_url: ENV['SLACK_WEBHOOK_URL'],
|
117
|
+
channel: ENV['SLACK_CHANNEL']
|
118
|
+
}
|
119
|
+
}
|
120
|
+
```
|
121
|
+
|
122
|
+
You can also optionally include: `username`, `icon_emoji` and `color`.
|
123
|
+
|
124
|
+
### Usage
|
125
|
+
|
126
|
+
Totalizer comes with two rake tasks you can run.
|
127
|
+
|
128
|
+
### Daily
|
129
|
+
|
130
|
+
You don’t need to review every metric every day, but it’s good practice to monitor growth and activity on a daily basis so you can respond to any spikes or dips.
|
131
|
+
|
132
|
+
Totalizer generates your Acquisition and Activity metrics for the previous day and the previous week.
|
133
|
+
|
134
|
+
```
|
135
|
+
$ rake totalizer:daily
|
136
|
+
```
|
137
|
+
|
138
|
+
This will generate the following report:
|
139
|
+
|
140
|
+
```
|
141
|
+
Acquisition
|
142
|
+
Yesterday: 20 (∆ 10%)
|
143
|
+
Last 7 days: 100 (∆ 7%)
|
144
|
+
Signed up this period (with rate of change)
|
145
|
+
Activity
|
146
|
+
Yesterday: 60 (∆ 9%)
|
147
|
+
Last 7 days: 120 (∆ 8%)
|
148
|
+
Key activities this period (with rate of change)
|
149
|
+
```
|
150
|
+
|
151
|
+
### Weekly
|
152
|
+
|
153
|
+
Every week it’s important to review your important metrics to ensure you are trending in the right direction.
|
154
|
+
|
155
|
+
Totalizer generates your Activation, Engagement, Retention and Churn for the previous week and previous month.
|
156
|
+
|
157
|
+
```
|
158
|
+
$ rake totalizer:weekly
|
159
|
+
```
|
160
|
+
|
161
|
+
This will generate the following report:
|
162
|
+
|
163
|
+
```
|
164
|
+
Activation
|
165
|
+
Created this period and did key activity
|
166
|
+
Last 7 days: 77 → 58 (75.32%)
|
167
|
+
Last 30 days: 444 → 355 (79.95%)
|
168
|
+
Engagement
|
169
|
+
Created before this period and did key activity this period
|
170
|
+
Last 7 days: 56.18% (150/267)
|
171
|
+
Last 30 days: 34.14% (210/615)
|
172
|
+
Retention
|
173
|
+
Did key activity the previous period and again this period
|
174
|
+
Last 7 days: 61.19% (134/219)
|
175
|
+
Last 30 days: 69.42% (722/1040)
|
176
|
+
Churn
|
177
|
+
Did key activity last period but not this period over the total who did key activity last period plus new users
|
178
|
+
Last 7 days: 6.21% (9/145)
|
179
|
+
Last 30 days: 3.29% (34/1032)
|
180
|
+
```
|
181
|
+
|
182
|
+
***
|
183
|
+
|
184
|
+
In addition to Metric, you can also use the following underlying objects.
|
185
|
+
|
186
|
+
### Factory
|
187
|
+
|
188
|
+
The Totalizer Factory is what generates the Acquisition, Activation, Engagement, Retention and Churn reports.
|
26
189
|
|
27
190
|
By defining one growth metric, like new user creation, and one key activity metric, like creating a post, you can generate all five reports.
|
28
191
|
|
@@ -36,20 +199,16 @@ factory = Totalizer::Factory.new(growth_metric: growth_metric, activity_metric:
|
|
36
199
|
|
37
200
|
This will return a message object for each calculation.
|
38
201
|
|
39
|
-
|
202
|
+
##### Parameters
|
40
203
|
|
41
204
|
You can pass the following parameters into the Factory:
|
42
205
|
|
43
206
|
+ `growth_metric`: (required) a Totalizer metric representing growth, usually a User model.
|
44
207
|
+ `activity_metric`: (required) a Totalizer metric representing the key activity a user should do within your application.
|
45
208
|
+ `date`: When to start measuring your records from. Must be a DateTime. Default is `now`.
|
46
|
-
+ `
|
47
|
-
+ `activation_duration`: Duration (in days) to measure your activation. Must be an integer. Default is `7`.
|
48
|
-
+ `engagement_duration`: Duration (in days) to measure your engagement. Must be an integer. Default is `7`.
|
49
|
-
+ `retention_duration`: Duration (in days) to measure your retention. Must be an integer. Default is `30`.
|
50
|
-
+ `churn_duration`: Duration (in days) to measure your churn. Must be an integer. Default is `30`.
|
209
|
+
+ `duration`: Duration (in days) to measure your records. Must be an integer. Default is `7`.
|
51
210
|
|
52
|
-
|
211
|
+
#### Acquisition
|
53
212
|
|
54
213
|
```ruby
|
55
214
|
growth_metric = Totalizer::Metric.new(model: User)
|
@@ -67,7 +226,7 @@ acquisition.text
|
|
67
226
|
#=> "74 (Growth rate: 10%)"
|
68
227
|
```
|
69
228
|
|
70
|
-
|
229
|
+
#### Activation
|
71
230
|
|
72
231
|
```ruby
|
73
232
|
growth_metric = Totalizer::Metric.new(model: User)
|
@@ -85,7 +244,7 @@ activation.text
|
|
85
244
|
#=> "63/90 (Conversion rate: 70%)"
|
86
245
|
```
|
87
246
|
|
88
|
-
|
247
|
+
#### Engagement
|
89
248
|
|
90
249
|
```ruby
|
91
250
|
growth_metric = Totalizer::Metric.new(model: User)
|
@@ -103,7 +262,7 @@ engagement.text
|
|
103
262
|
#=> "42/350 (Engagement rate: 12%)"
|
104
263
|
```
|
105
264
|
|
106
|
-
|
265
|
+
#### Retention
|
107
266
|
|
108
267
|
```ruby
|
109
268
|
growth_metric = Totalizer::Metric.new(model: User)
|
@@ -121,7 +280,7 @@ retention.text
|
|
121
280
|
#=> "42/75 56%"
|
122
281
|
```
|
123
282
|
|
124
|
-
|
283
|
+
#### Churn
|
125
284
|
|
126
285
|
```ruby
|
127
286
|
growth_metric = Totalizer::Metric.new(model: User)
|
@@ -139,57 +298,15 @@ churn.text
|
|
139
298
|
#=> "33/75 44%"
|
140
299
|
```
|
141
300
|
|
142
|
-
## Make your metrics visible
|
143
|
-
|
144
|
-
Metrics are only worthwhile if the team actually sees them.
|
145
|
-
|
146
|
-
With the results of your Factory you can:
|
147
|
-
+ Post them to Slack
|
148
|
-
+ Send them via email
|
149
|
-
+ Create a dashboard view
|
150
|
-
|
151
|
-
***
|
152
|
-
|
153
|
-
You can also access the underlying objects directly.
|
154
|
-
|
155
|
-
### Metric
|
156
|
-
|
157
|
-
A Metric is a calculation based on one of your models for a duration of time. To create a Metric just use this in your Rails application:
|
158
|
-
|
159
|
-
```ruby
|
160
|
-
metric = Totalizer::Metric.new(model: User)
|
161
|
-
|
162
|
-
metric.value
|
163
|
-
#=> 10 (the number of records for the period)
|
164
|
-
|
165
|
-
metric.start
|
166
|
-
#=> 20 (the number of records before the period)
|
167
|
-
|
168
|
-
metric.finish
|
169
|
-
#=> 30 (the number of records at the end of the period)
|
170
|
-
|
171
|
-
metric.rate
|
172
|
-
#=> 0.5 (how much the number changed over the period)
|
173
|
-
```
|
174
|
-
|
175
|
-
##### Parameters
|
176
|
-
|
177
|
-
You can pass the following parameters into the Metric:
|
178
|
-
+ `model` (required): The Rails model class that Totalizer will query.
|
179
|
-
+ `date`: When to start measuring your records. Default is `now`.
|
180
|
-
+ `duration`: Duration (in days) to measure your records from. Must be an integer. Default is `7`.
|
181
|
-
+ `filter`: Write a custom query to determine which records to use in calculation. For example to find all users who created a public response you could pass in: `filter: "is_public = true"`.
|
182
|
-
+ `map`: Which field to map records on. For example, to find unique users who did a response you could pass in: `map: 'user_id'`. Default is `id`.
|
183
|
-
|
184
301
|
### Step
|
185
302
|
|
186
303
|
A step allows you to easily compare two sets of ids to see who converted.
|
187
304
|
|
188
|
-
To create a
|
305
|
+
To create a Step just use this in your Rails application:
|
189
306
|
|
190
307
|
```ruby
|
191
308
|
first_metric = Totalizer::Metric.new(model: User)
|
192
|
-
second_metric = Totalizer::Metric.new(model:
|
309
|
+
second_metric = Totalizer::Metric.new(model: Post, map: 'user_id')
|
193
310
|
step = Totalizer::Step.new first_step_metric.ids, second_step_metric.ids
|
194
311
|
|
195
312
|
step.start
|
@@ -208,15 +325,13 @@ step.ids
|
|
208
325
|
You can use the result of one step to feed into another step. Continuing on the example above, you could do the following:
|
209
326
|
|
210
327
|
```ruby
|
211
|
-
third_metric = Totalizer::Metric.new(model:
|
328
|
+
third_metric = Totalizer::Metric.new(model: Comment, map: 'user_id')
|
212
329
|
next_step = Totalizer::Step.new step.ids, third_metric.ids
|
213
330
|
```
|
214
331
|
|
215
|
-
***
|
216
|
-
|
217
332
|
## Manual Calculations
|
218
333
|
|
219
|
-
You can also report on Acquisition, Activation, Retention, Engagement and Churn yourself without using a Factory.
|
334
|
+
You can also report on Acquisition, Activity, Activation, Retention, Engagement and Churn yourself without using a Factory.
|
220
335
|
|
221
336
|
### Acquisition
|
222
337
|
|
@@ -230,11 +345,23 @@ acquisition.rate
|
|
230
345
|
#=> 10
|
231
346
|
```
|
232
347
|
|
348
|
+
### Activity
|
349
|
+
|
350
|
+
```ruby
|
351
|
+
activity = Totalizer::Metric.new(model: Post, map: 'user_id')
|
352
|
+
|
353
|
+
acquisition.value
|
354
|
+
#=> 20
|
355
|
+
|
356
|
+
acquisition.rate
|
357
|
+
#=> 5
|
358
|
+
```
|
359
|
+
|
233
360
|
### Activation
|
234
361
|
|
235
362
|
```ruby
|
236
363
|
sign_up = Totalizer::Metric.new(model: User, duration: 7)
|
237
|
-
do_action = Totalizer::Metric.new(model:
|
364
|
+
do_action = Totalizer::Metric.new(model: Post, map: 'user_id')
|
238
365
|
activation = Totalizer::Step.new sign_up.ids, do_action.ids
|
239
366
|
|
240
367
|
activation.start
|
@@ -251,7 +378,7 @@ activation.rate
|
|
251
378
|
|
252
379
|
```ruby
|
253
380
|
sign_up = Totalizer::Metric.new(model: User, duration: 7)
|
254
|
-
do_action = Totalizer::Metric.new(model:
|
381
|
+
do_action = Totalizer::Metric.new(model: Post, map: 'user_id')
|
255
382
|
|
256
383
|
existing_active = (sign_up.start_ids & do_action.ids).size
|
257
384
|
#=> 14
|
@@ -315,7 +442,7 @@ lost_existing_customers.to_f / (new_and_existing_customers - lost_existing_custo
|
|
315
442
|
|
316
443
|
The MIT License (MIT)
|
317
444
|
|
318
|
-
Copyright (c)
|
445
|
+
Copyright (c) 2016 Michael Dijkstra
|
319
446
|
|
320
447
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
321
448
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -0,0 +1,26 @@
|
|
1
|
+
namespace :totalizer do
|
2
|
+
task validate: :environment do
|
3
|
+
abort '! Growth metric not defined. Please define your growth metric before running this task. See https://github.com/micdijkstra/totalizer' unless Totalizer.growth_metric
|
4
|
+
abort '! Activity metric not defined. Please define your growth metric before running this task. See https://github.com/micdijkstra/totalizer' unless Totalizer.activity_metric
|
5
|
+
end
|
6
|
+
|
7
|
+
task daily: :validate do
|
8
|
+
Totalizer.logger.info "Totalizer: Daily"
|
9
|
+
messages = {
|
10
|
+
acquisition: [Totalizer.generate(:acquisition, 1), Totalizer.generate(:acquisition, 7)],
|
11
|
+
activity: [Totalizer.generate(:activity, 1), Totalizer.generate(:activity, 7)],
|
12
|
+
}
|
13
|
+
Totalizer.notify messages
|
14
|
+
end
|
15
|
+
|
16
|
+
task weekly: :validate do
|
17
|
+
Totalizer.logger.info "Totalizer: Daily"
|
18
|
+
messages = {
|
19
|
+
activation: [Totalizer.generate(:activation, 7), Totalizer.generate(:activation, 30)],
|
20
|
+
engagement: [Totalizer.generate(:engagement, 7), Totalizer.generate(:engagement, 30)],
|
21
|
+
retention: [Totalizer.generate(:retention, 7), Totalizer.generate(:retention, 30)],
|
22
|
+
churn: [Totalizer.generate(:churn, 7), Totalizer.generate(:churn, 30)],
|
23
|
+
}
|
24
|
+
Totalizer.notify messages
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Totalizer
|
2
|
+
extend self
|
3
|
+
NOTIFIERS = { log: Totalizer::LogNotifier, action_mailer: Totalizer::ActionMailerNotifier, mandrill_mailer: Totalizer::MandrillMailerNotifier, slack: Totalizer::SlackNotifier }
|
4
|
+
attr_reader :growth_metric, :activity_metric
|
5
|
+
|
6
|
+
def growth_metric= metric
|
7
|
+
@growth_metric = validate_metric(metric)
|
8
|
+
end
|
9
|
+
|
10
|
+
def activity_metric= metric
|
11
|
+
@activity_metric = validate_metric(metric)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_metric metric
|
15
|
+
raise Errors::InvalidMetric unless metric.kind_of?(Totalizer::Metric)
|
16
|
+
metric
|
17
|
+
end
|
18
|
+
|
19
|
+
def factory
|
20
|
+
@factory ||= Totalizer::Factory.new growth_metric, activity_metric
|
21
|
+
end
|
22
|
+
|
23
|
+
def date= date
|
24
|
+
factory.date = date
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate build, duration=nil
|
28
|
+
factory.duration = duration
|
29
|
+
factory.send(build)
|
30
|
+
end
|
31
|
+
|
32
|
+
def notifiers
|
33
|
+
@notifiers ||= {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def notifiers= notifiers
|
37
|
+
@notifiers = notifiers
|
38
|
+
end
|
39
|
+
|
40
|
+
def notify message_groups
|
41
|
+
notifiers.merge({ log: {} }).each { |notifier| fire_notification(notifier, message_groups) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def fire_notification notifier_options, message_groups
|
45
|
+
notifier = NOTIFIERS[notifier_options.first]
|
46
|
+
notifier.call(message_groups, notifier_options.last)
|
47
|
+
end
|
48
|
+
end
|
data/lib/totalizer/errors.rb
CHANGED
data/lib/totalizer/factory.rb
CHANGED
@@ -1,81 +1,59 @@
|
|
1
1
|
module Totalizer
|
2
2
|
class Factory
|
3
|
-
attr_accessor :growth_metric, :activity_metric, :date, :
|
3
|
+
attr_accessor :growth_metric, :activity_metric, :date, :duration
|
4
4
|
|
5
|
-
def initialize params
|
6
|
-
self.growth_metric =
|
7
|
-
self.activity_metric =
|
5
|
+
def initialize growth_metric, activity_metric, params={}
|
6
|
+
self.growth_metric = growth_metric
|
7
|
+
self.activity_metric = activity_metric
|
8
8
|
self.date = params[:date] || DateTime.now
|
9
|
-
self.
|
10
|
-
self.activation_duration = params[:activation_duration] || 7
|
11
|
-
self.engagement_duration = params[:engagement_duration] || 7
|
12
|
-
self.retention_duration = params[:retention_duration] || 30
|
13
|
-
self.churn_duration = params[:churn_duration] || 30
|
9
|
+
self.duration = params[:duration] || 7
|
14
10
|
validate_attributes!
|
15
11
|
end
|
16
12
|
|
17
13
|
def acquisition
|
18
|
-
|
14
|
+
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: duration)
|
15
|
+
AcqusitionMessage.new(growth_metric, duration)
|
19
16
|
end
|
20
17
|
|
21
18
|
def activation
|
22
|
-
|
19
|
+
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: duration)
|
20
|
+
activity_metric = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: duration)
|
21
|
+
step = Totalizer::Step.new growth_metric.ids, activity_metric.ids
|
22
|
+
ActivationMessage.new(step, duration)
|
23
|
+
end
|
24
|
+
|
25
|
+
def activity
|
26
|
+
activity_metric = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: duration, map: 'id')
|
27
|
+
ActivityMessage.new(activity_metric, duration)
|
23
28
|
end
|
24
29
|
|
25
30
|
def engagement
|
26
|
-
|
31
|
+
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: duration)
|
32
|
+
activity_metric = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: duration)
|
33
|
+
EngagementMessage.new(growth_metric, activity_metric, duration)
|
27
34
|
end
|
28
35
|
|
29
36
|
def retention
|
30
|
-
|
37
|
+
previous_period = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date - duration.days, duration: duration)
|
38
|
+
this_period = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: duration)
|
39
|
+
step = Totalizer::Step.new previous_period.ids, this_period.ids
|
40
|
+
RetentionMessage.new(step, duration)
|
31
41
|
end
|
32
42
|
|
33
43
|
def churn
|
34
|
-
|
44
|
+
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: duration)
|
45
|
+
previous_activity_metric = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date - duration.days, duration: duration)
|
46
|
+
this_activity_metc = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: duration)
|
47
|
+
ChurnMessage.new(growth_metric, previous_activity_metric, this_activity_metc, duration)
|
35
48
|
end
|
36
49
|
|
37
50
|
private
|
38
51
|
|
39
|
-
def calculate_acquisition
|
40
|
-
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: acquisition_duration)
|
41
|
-
AcqusitionMessage.new(growth_metric, acquisition_duration)
|
42
|
-
end
|
43
|
-
|
44
|
-
def calculate_activation
|
45
|
-
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: activation_duration)
|
46
|
-
activity_metric = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: activation_duration)
|
47
|
-
step = Totalizer::Step.new growth_metric.ids, activity_metric.ids
|
48
|
-
ActivationMessage.new(step, activation_duration)
|
49
|
-
end
|
50
|
-
|
51
|
-
def calculate_engagement
|
52
|
-
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: engagement_duration)
|
53
|
-
activity_metric = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: engagement_duration)
|
54
|
-
EngagementMessage.new(growth_metric, activity_metric, engagement_duration)
|
55
|
-
end
|
56
|
-
|
57
|
-
def calculate_retention
|
58
|
-
previous_period = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date - retention_duration.days, duration: retention_duration)
|
59
|
-
this_period = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: retention_duration)
|
60
|
-
step = Totalizer::Step.new previous_period.ids, this_period.ids
|
61
|
-
RetentionMessage.new(step, retention_duration)
|
62
|
-
end
|
63
|
-
|
64
|
-
def calculate_churn
|
65
|
-
growth_metric = Totalizer::Metric.new self.growth_metric.attributes.merge(date: date, duration: churn_duration)
|
66
|
-
previous_activity_metric = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date - churn_duration.days, duration: churn_duration)
|
67
|
-
this_activity_metc = Totalizer::Metric.new self.activity_metric.attributes.merge(date: date, duration: churn_duration)
|
68
|
-
ChurnMessage.new(growth_metric, previous_activity_metric, this_activity_metc, churn_duration)
|
69
|
-
end
|
70
|
-
|
71
52
|
def validate_attributes!
|
72
|
-
raise Errors::
|
73
|
-
raise Errors::
|
53
|
+
raise Errors::InvalidMetric unless growth_metric.kind_of?(Totalizer::Metric)
|
54
|
+
raise Errors::InvalidMetric unless activity_metric.kind_of?(Totalizer::Metric)
|
74
55
|
raise Errors::InvalidDate unless date.kind_of?(DateTime)
|
75
|
-
raise Errors::InvalidDuration unless
|
76
|
-
raise Errors::InvalidDuration unless activation_duration.kind_of?(Integer)
|
77
|
-
raise Errors::InvalidDuration unless retention_duration.kind_of?(Integer)
|
78
|
-
raise Errors::InvalidDuration unless churn_duration.kind_of?(Integer)
|
56
|
+
raise Errors::InvalidDuration unless duration.kind_of?(Integer)
|
79
57
|
end
|
80
58
|
end
|
81
59
|
end
|
data/lib/totalizer/message.rb
CHANGED
@@ -1,67 +1,76 @@
|
|
1
1
|
module Totalizer
|
2
2
|
class Message
|
3
|
-
attr_accessor :
|
3
|
+
attr_accessor :description, :text, :duration
|
4
4
|
|
5
|
-
def
|
6
|
-
|
5
|
+
def period_string
|
6
|
+
if duration > 1
|
7
|
+
"Last #{duration} #{'day'.pluralize(duration)}"
|
8
|
+
else
|
9
|
+
duration == 0 ? 'Today' : 'Yesterday'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def percentage_string value
|
14
|
+
"#{(value.to_f * 100).round(2).to_s.gsub(/.0$/, '')}%"
|
7
15
|
end
|
8
16
|
end
|
9
17
|
|
10
|
-
class
|
11
|
-
def initialize
|
18
|
+
class MetricMessage < Message
|
19
|
+
def initialize metric, duration
|
12
20
|
self.duration = duration
|
13
|
-
self.
|
14
|
-
|
15
|
-
|
21
|
+
self.text = "#{period_string}: #{metric.value} (∆ #{percentage_string(metric.rate)})"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class AcqusitionMessage < MetricMessage
|
26
|
+
def initialize metric, duration
|
27
|
+
super
|
28
|
+
self.description = "Signed up this period (with rate of change)"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class ActivityMessage < MetricMessage
|
33
|
+
def initialize metric, duration
|
34
|
+
super
|
35
|
+
self.description = "Key activities this period (with rate of change)"
|
16
36
|
end
|
17
37
|
end
|
18
38
|
|
19
|
-
class
|
39
|
+
class ActivationMessage < Message
|
20
40
|
def initialize step, duration
|
21
41
|
self.duration = duration
|
22
|
-
self.text = "#{step.
|
42
|
+
self.text = "#{period_string}: #{step.start} → #{step.finish} (#{percentage_string(step.rate)})"
|
43
|
+
self.description = "Created this period and did key activity"
|
23
44
|
end
|
24
45
|
end
|
25
46
|
|
26
47
|
class EngagementMessage < Message
|
27
48
|
def initialize growth_metric, activity_metric, duration
|
28
49
|
self.duration = duration
|
29
|
-
self.title = 'Engagement'
|
30
50
|
|
31
51
|
existing_active = (growth_metric.start_ids & activity_metric.ids).size
|
32
|
-
|
33
|
-
self.
|
34
|
-
self.text = "#{existing_active}/#{growth_metric.start} (Engagement rate: #{(existing_active.to_f / growth_metric.start.to_f * 100).round(0)}%)"
|
52
|
+
self.text = "#{period_string}: #{percentage_string existing_active.to_f / growth_metric.start.to_f} (#{existing_active}/#{growth_metric.start})"
|
53
|
+
self.description = "Created before this period and did key activity this period"
|
35
54
|
end
|
36
55
|
end
|
37
56
|
|
38
|
-
class
|
57
|
+
class RetentionMessage < Message
|
39
58
|
def initialize step, duration
|
40
|
-
|
41
|
-
self.
|
42
|
-
self.
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class RetentionMessage < StepMessage
|
47
|
-
def initialize step, duration
|
48
|
-
super
|
49
|
-
self.title = 'Retention'
|
50
|
-
self.pretext = "Did key activity more than #{days_string} ago and again in the last #{days_string}"
|
59
|
+
self.duration = duration
|
60
|
+
self.text = "#{period_string}: #{percentage_string(step.rate)} (#{step.finish}/#{step.start})"
|
61
|
+
self.description = "Did key activity the previous period and again this period"
|
51
62
|
end
|
52
63
|
end
|
53
64
|
|
54
65
|
class ChurnMessage < Message
|
55
66
|
def initialize growth_metric, previous_activity_metric, this_activity_metc, duration
|
56
67
|
self.duration = duration
|
57
|
-
self.title = 'Churn'
|
58
|
-
self.pretext = "Acquired more than #{days_string} ago and did not do key activity in last #{days_string} over total acquired"
|
59
68
|
|
60
|
-
new_and_existing = growth_metric.
|
69
|
+
new_and_existing = previous_activity_metric.value + growth_metric.value
|
61
70
|
lost_existing = (previous_activity_metric.ids - this_activity_metc.ids).size
|
62
71
|
final = new_and_existing - lost_existing
|
63
|
-
|
64
|
-
self.
|
72
|
+
self.text = "#{period_string}: #{percentage_string lost_existing.to_f / new_and_existing.to_f} (#{lost_existing}/#{new_and_existing})"
|
73
|
+
self.description = "Did key activity last period but not this period over the total who did key activity last period plus new users"
|
65
74
|
end
|
66
75
|
end
|
67
76
|
end
|
data/lib/totalizer/metric.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'action_mailer'
|
2
|
+
|
3
|
+
module Totalizer
|
4
|
+
class EmailNotifier < BaseNotifier
|
5
|
+
DEFAULT_OPTIONS = { subject: '[Totalizer] Your important metrics', from: 'totalizer@totalizer.io' }
|
6
|
+
|
7
|
+
def self.call(message_groups, options)
|
8
|
+
body = ""
|
9
|
+
message_groups.each do |message_type, messages|
|
10
|
+
body += "\n#{message_type.to_s.capitalize}\n"
|
11
|
+
description = messages.map{ |message| message.description }.uniq.join("\n")
|
12
|
+
body += "#{description}\n"
|
13
|
+
|
14
|
+
text = messages.map{ |message| message.text }.join("\n")
|
15
|
+
body += "#{text}\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
body += "\n— Totalizer"
|
19
|
+
|
20
|
+
self.send body, DEFAULT_OPTIONS.merge(options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Totalizer
|
2
|
+
class LogNotifier < BaseNotifier
|
3
|
+
def self.call(message_groups, options)
|
4
|
+
message_groups.each do |message_type, messages|
|
5
|
+
Totalizer.logger.info message_type.to_s.capitalize
|
6
|
+
|
7
|
+
description = messages.map{ |message| message.description }.uniq.join(", ")
|
8
|
+
Totalizer.logger.info " #{description}"
|
9
|
+
|
10
|
+
messages.each { |message| Totalizer.logger.info " #{message.text}" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'mandrill_mailer'
|
2
|
+
|
3
|
+
module Totalizer
|
4
|
+
class MandrillMailerNotifier< EmailNotifier
|
5
|
+
def self.send body, options
|
6
|
+
MandrillMailer::MessageMailer.mandrill_mail(from: options[:from], to: options[:to], subject: options[:subject], text: body).deliver_now
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'slack-notifier'
|
2
|
+
|
3
|
+
module Totalizer
|
4
|
+
class SlackNotifier < BaseNotifier
|
5
|
+
DEFAULT_OPTIONS = { username: 'Totalizer', icon_emoji: ":nerd_face:", color: '#5767ff' }
|
6
|
+
|
7
|
+
def self.call(message_groups, options)
|
8
|
+
notifier = Slack::Notifier.new options[:webhook_url], DEFAULT_OPTIONS.merge(options)
|
9
|
+
message_groups.each do |message_type, messages|
|
10
|
+
text = messages.map{ |message| message.text }.join("\n")
|
11
|
+
description = messages.map{ |message| message.description }.uniq.join("\n")
|
12
|
+
notifier.ping message_type.to_s.capitalize, attachments: [{ fallback: text, color: options[:color], text: text, footer: description }]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require "totalizer/notifier/base_notifier"
|
2
|
+
require "totalizer/notifier/log_notifier"
|
3
|
+
require 'totalizer/notifier/email_notifier'
|
4
|
+
require 'totalizer/notifier/action_mailer_notifier'
|
5
|
+
require 'totalizer/notifier/mandrill_mailer_notifier'
|
6
|
+
require 'totalizer/notifier/slack_notifier'
|
data/lib/totalizer/version.rb
CHANGED
data/lib/totalizer.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require "totalizer/version"
|
2
|
+
require 'totalizer/logger'
|
2
3
|
require 'totalizer/errors'
|
3
4
|
require 'totalizer/metric'
|
4
5
|
require 'totalizer/step'
|
5
6
|
require 'totalizer/factory'
|
6
7
|
require 'totalizer/message'
|
8
|
+
require 'totalizer/notifier'
|
9
|
+
require 'totalizer/railtie' if defined?(Rails)
|
10
|
+
require "totalizer/base"
|
@@ -5,39 +5,27 @@ describe Totalizer::Factory do
|
|
5
5
|
let(:activity_metric) { Totalizer::Metric.new model: Post, map: 'user_id' }
|
6
6
|
let(:duration) { nil }
|
7
7
|
let(:date) { nil }
|
8
|
-
let(:factory) { Totalizer::Factory.new growth_metric
|
8
|
+
let(:factory) { Totalizer::Factory.new growth_metric, activity_metric, date: date }
|
9
9
|
|
10
10
|
describe "Validate" do
|
11
11
|
it "requires metrics" do
|
12
|
-
expect{ Totalizer::Factory.new(
|
12
|
+
expect{ Totalizer::Factory.new('fake', 'metric') }.to raise_exception(Totalizer::Errors::InvalidMetric)
|
13
13
|
end
|
14
14
|
|
15
15
|
it "requires activity metric" do
|
16
|
-
expect{ Totalizer::Factory.new(growth_metric
|
16
|
+
expect{ Totalizer::Factory.new(growth_metric, 'metric') }.to raise_exception(Totalizer::Errors::InvalidMetric)
|
17
17
|
end
|
18
18
|
|
19
19
|
it "requires growth metric" do
|
20
|
-
expect{ Totalizer::Factory.new(
|
20
|
+
expect{ Totalizer::Factory.new('fake', activity_metric) }.to raise_exception(Totalizer::Errors::InvalidMetric)
|
21
21
|
end
|
22
22
|
|
23
23
|
it "requires a valid datetime" do
|
24
|
-
expect{ Totalizer::Factory.new(growth_metric
|
24
|
+
expect{ Totalizer::Factory.new(growth_metric, activity_metric, date: 'Whenever') }.to raise_exception(Totalizer::Errors::InvalidDate)
|
25
25
|
end
|
26
26
|
|
27
|
-
it "requires a
|
28
|
-
expect{ Totalizer::Factory.new(growth_metric
|
29
|
-
end
|
30
|
-
|
31
|
-
it "requires a activation duration" do
|
32
|
-
expect{ Totalizer::Factory.new(growth_metric: growth_metric, activity_metric: activity_metric, activation_duration: 'Whatever') }.to raise_exception(Totalizer::Errors::InvalidDuration)
|
33
|
-
end
|
34
|
-
|
35
|
-
it "requires a retention duration" do
|
36
|
-
expect{ Totalizer::Factory.new(growth_metric: growth_metric, activity_metric: activity_metric, retention_duration: 'Whatever') }.to raise_exception(Totalizer::Errors::InvalidDuration)
|
37
|
-
end
|
38
|
-
|
39
|
-
it "requires a churn duration" do
|
40
|
-
expect{ Totalizer::Factory.new(growth_metric: growth_metric, activity_metric: activity_metric, churn_duration: 'Whatever') }.to raise_exception(Totalizer::Errors::InvalidDuration)
|
27
|
+
it "requires a duration" do
|
28
|
+
expect{ Totalizer::Factory.new(growth_metric, activity_metric, duration: 'Whatever') }.to raise_exception(Totalizer::Errors::InvalidDuration)
|
41
29
|
end
|
42
30
|
end
|
43
31
|
|
@@ -47,7 +35,7 @@ describe Totalizer::Factory do
|
|
47
35
|
end
|
48
36
|
|
49
37
|
it "defaults to 7 day acquisition duration" do
|
50
|
-
expect(factory.
|
38
|
+
expect(factory.duration).to eq 7
|
51
39
|
end
|
52
40
|
end
|
53
41
|
|
@@ -69,72 +57,43 @@ describe Totalizer::Factory do
|
|
69
57
|
end
|
70
58
|
|
71
59
|
describe "Acquisition" do
|
72
|
-
it "returns
|
73
|
-
expect(factory.acquisition.
|
74
|
-
end
|
75
|
-
|
76
|
-
it "returns pretext" do
|
77
|
-
expect(factory.acquisition.pretext).to eq 'Signed up in the last 7 days'
|
60
|
+
it "returns text" do
|
61
|
+
expect(factory.acquisition.text).to eq "Last 7 days: 2 (∆ 50%)"
|
78
62
|
end
|
63
|
+
end
|
79
64
|
|
65
|
+
describe "Activity" do
|
80
66
|
it "returns text" do
|
81
|
-
expect(factory.
|
67
|
+
expect(factory.activity.text).to eq "Last 7 days: 2 (∆ 50%)"
|
82
68
|
end
|
83
69
|
end
|
84
70
|
|
85
71
|
describe "Activation" do
|
86
|
-
it "returns title" do
|
87
|
-
expect(factory.activation.title).to eq 'Activation'
|
88
|
-
end
|
89
|
-
|
90
|
-
it "returns pretext" do
|
91
|
-
expect(factory.activation.pretext).to eq 'Signed up in the last 7 days and did key activity'
|
92
|
-
end
|
93
|
-
|
94
72
|
it "returns text" do
|
95
|
-
expect(factory.activation.text).to eq "
|
73
|
+
expect(factory.activation.text).to eq "Last 7 days: 2 → 1 (50%)"
|
96
74
|
end
|
97
75
|
end
|
98
76
|
|
99
77
|
describe "Engagement" do
|
100
|
-
it "returns title" do
|
101
|
-
expect(factory.engagement.title).to eq 'Engagement'
|
102
|
-
end
|
103
|
-
|
104
|
-
it "returns pretext" do
|
105
|
-
expect(factory.engagement.pretext).to eq 'Signed up more than 7 days ago and did key activity in the last 7 days'
|
106
|
-
end
|
107
|
-
|
108
78
|
it "returns text" do
|
109
|
-
expect(factory.engagement.text).to eq "
|
79
|
+
expect(factory.engagement.text).to eq "Last 7 days: 25% (1/4)"
|
110
80
|
end
|
111
81
|
end
|
112
82
|
|
113
83
|
describe "Retention" do
|
114
|
-
it "returns title" do
|
115
|
-
expect(factory.retention.title).to eq 'Retention'
|
116
|
-
end
|
117
|
-
|
118
|
-
it "returns pretext" do
|
119
|
-
expect(factory.retention.pretext).to eq 'Did key activity more than 7 days ago and again in the last 7 days'
|
120
|
-
end
|
121
|
-
|
122
84
|
it "returns text" do
|
123
|
-
expect(factory.retention.text).to eq "
|
85
|
+
expect(factory.retention.text).to eq "Last 7 days: 25% (1/4)"
|
124
86
|
end
|
125
87
|
end
|
126
88
|
|
127
89
|
describe "Churn" do
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
it "returns pretext" do
|
133
|
-
expect(factory.churn.pretext).to eq 'Acquired more than 7 days ago and did not do key activity in last 7 days over total acquired'
|
90
|
+
let(:user_7) { FactoryGirl.create :user, created_at: 13.days.ago }
|
91
|
+
before do
|
92
|
+
FactoryGirl.create :post, user_id: user_7.id, created_at: 13.days.ago
|
134
93
|
end
|
135
94
|
|
136
95
|
it "returns text" do
|
137
|
-
expect(factory.churn.text).to eq "
|
96
|
+
expect(factory.churn.text).to eq "Last 7 days: 57.14% (4/7)"
|
138
97
|
end
|
139
98
|
end
|
140
99
|
end
|
@@ -66,7 +66,7 @@ describe Totalizer::Metric do
|
|
66
66
|
expect(metric.finish).to eq 3
|
67
67
|
end
|
68
68
|
|
69
|
-
it "calculates the rate of
|
69
|
+
it "calculates the rate of change" do
|
70
70
|
expect(metric.rate).to eq 2
|
71
71
|
end
|
72
72
|
|
@@ -77,6 +77,14 @@ describe Totalizer::Metric do
|
|
77
77
|
expect{ metric.value }.to raise_exception(ActiveRecord::StatementInvalid)
|
78
78
|
end
|
79
79
|
end
|
80
|
+
|
81
|
+
describe 'with 0 records' do
|
82
|
+
let(:metric) { Totalizer::Metric.new(model: User, filter: ['created_at > ?', DateTime.now] ) }
|
83
|
+
|
84
|
+
it "calculates the rate of change" do
|
85
|
+
expect(metric.rate).to eq 0
|
86
|
+
end
|
87
|
+
end
|
80
88
|
end
|
81
89
|
|
82
90
|
describe "Filter" do
|
@@ -109,7 +117,7 @@ describe Totalizer::Metric do
|
|
109
117
|
expect(metric.finish).to eq 2
|
110
118
|
end
|
111
119
|
|
112
|
-
it "calculates the rate of
|
120
|
+
it "calculates the rate of change" do
|
113
121
|
expect(metric.rate).to eq 1
|
114
122
|
end
|
115
123
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Totalizer do
|
4
|
+
describe "Validate" do
|
5
|
+
it "require a growth metrics" do
|
6
|
+
expect{ Totalizer.growth_metric = 'fake' }.to raise_exception(Totalizer::Errors::InvalidMetric)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "require an activity metric" do
|
10
|
+
expect{ Totalizer.activity_metric = 'fake' }.to raise_exception(Totalizer::Errors::InvalidMetric)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "Initialize" do
|
15
|
+
let(:growth_metric) { Totalizer::Metric.new model: User }
|
16
|
+
let(:activity_metric) { Totalizer::Metric.new model: Post, map: 'user_id' }
|
17
|
+
|
18
|
+
before do
|
19
|
+
Totalizer.growth_metric = growth_metric
|
20
|
+
Totalizer.activity_metric = activity_metric
|
21
|
+
end
|
22
|
+
|
23
|
+
it "creates a factory" do
|
24
|
+
expect(Totalizer.factory).not_to eq nil
|
25
|
+
expect(Totalizer.factory.kind_of?(Totalizer::Factory)).to eq true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".notify" do
|
30
|
+
let(:growth_metric) { Totalizer::Metric.new model: User }
|
31
|
+
let(:message) { Totalizer::AcqusitionMessage.new(growth_metric, 1) }
|
32
|
+
let(:messages) { { activation: [message] } }
|
33
|
+
before do
|
34
|
+
allow(Totalizer::ActionMailerNotifier).to receive(:send)
|
35
|
+
allow(Totalizer::MandrillMailerNotifier).to receive(:send)
|
36
|
+
allow( Slack::Notifier::DefaultHTTPClient).to receive(:post)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "does not send to Slack by default" do
|
40
|
+
Totalizer.notify messages
|
41
|
+
expect(Totalizer::SlackNotifier).not_to receive(:call)
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "with action mailer notifier configured" do
|
45
|
+
before do
|
46
|
+
Totalizer.notifiers = {
|
47
|
+
action_mailer: {
|
48
|
+
to: 'admin@example.com'
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sends Email" do
|
54
|
+
Totalizer.notify messages
|
55
|
+
expect(Totalizer::ActionMailerNotifier).to respond_to(:send).with(2).arguments
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "with mandrill mailer notifier configured" do
|
60
|
+
before do
|
61
|
+
Totalizer.notifiers = {
|
62
|
+
mandrill_mailer: {
|
63
|
+
to: 'admin@example.com'
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
it "sends Email" do
|
69
|
+
Totalizer.notify messages
|
70
|
+
expect(Totalizer::MandrillMailerNotifier).to respond_to(:send).with(2).arguments
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
describe "with Slack notifier configured" do
|
76
|
+
before do
|
77
|
+
Totalizer.notifiers = {
|
78
|
+
slack: {
|
79
|
+
webhook_url: 'http://slack.webhook.url',
|
80
|
+
channel: '#general'
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "sends to Slack" do
|
86
|
+
Totalizer.notify messages
|
87
|
+
expect(Totalizer::SlackNotifier).to respond_to(:call).with(2).arguments
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -11,6 +11,7 @@ require 'active_record'
|
|
11
11
|
require 'factory_girl'
|
12
12
|
require 'database_cleaner'
|
13
13
|
require 'timecop'
|
14
|
+
require 'mandrill_mailer/offline'
|
14
15
|
|
15
16
|
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
16
17
|
load File.dirname(__FILE__) + '/support/schema.rb'
|
data/totalizer.gemspec
CHANGED
@@ -26,4 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "factory_girl_rails"
|
27
27
|
spec.add_development_dependency "database_cleaner"
|
28
28
|
spec.add_development_dependency "timecop"
|
29
|
+
spec.add_development_dependency "actionmailer"
|
30
|
+
spec.add_development_dependency "mandrill_mailer"
|
31
|
+
spec.add_development_dependency "slack-notifier"
|
29
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: totalizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Dijkstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -122,6 +122,48 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: actionmailer
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: mandrill_mailer
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: slack-notifier
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
125
167
|
description: Provides tools to Ruby on Rails developers to create calculations for
|
126
168
|
acquisiton, activation, engagement, retention and churn.
|
127
169
|
email:
|
@@ -135,16 +177,28 @@ files:
|
|
135
177
|
- LICENSE.txt
|
136
178
|
- README.md
|
137
179
|
- Rakefile
|
180
|
+
- lib/tasks/totalizer.rake
|
138
181
|
- lib/totalizer.rb
|
182
|
+
- lib/totalizer/base.rb
|
139
183
|
- lib/totalizer/errors.rb
|
140
184
|
- lib/totalizer/factory.rb
|
185
|
+
- lib/totalizer/logger.rb
|
141
186
|
- lib/totalizer/message.rb
|
142
187
|
- lib/totalizer/metric.rb
|
188
|
+
- lib/totalizer/notifier.rb
|
189
|
+
- lib/totalizer/notifier/action_mailer_notifier.rb
|
190
|
+
- lib/totalizer/notifier/base_notifier.rb
|
191
|
+
- lib/totalizer/notifier/email_notifier.rb
|
192
|
+
- lib/totalizer/notifier/log_notifier.rb
|
193
|
+
- lib/totalizer/notifier/mandrill_mailer_notifier.rb
|
194
|
+
- lib/totalizer/notifier/slack_notifier.rb
|
195
|
+
- lib/totalizer/railtie.rb
|
143
196
|
- lib/totalizer/step.rb
|
144
197
|
- lib/totalizer/version.rb
|
145
198
|
- spec/lib/totalizer/factory_spec.rb
|
146
199
|
- spec/lib/totalizer/metric_spec.rb
|
147
200
|
- spec/lib/totalizer/step_spec.rb
|
201
|
+
- spec/lib/totalizer_spec.rb
|
148
202
|
- spec/spec_helper.rb
|
149
203
|
- spec/support/factories.rb
|
150
204
|
- spec/support/models.rb
|
@@ -178,6 +232,7 @@ test_files:
|
|
178
232
|
- spec/lib/totalizer/factory_spec.rb
|
179
233
|
- spec/lib/totalizer/metric_spec.rb
|
180
234
|
- spec/lib/totalizer/step_spec.rb
|
235
|
+
- spec/lib/totalizer_spec.rb
|
181
236
|
- spec/spec_helper.rb
|
182
237
|
- spec/support/factories.rb
|
183
238
|
- spec/support/models.rb
|