totalizer 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|