tricle 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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CONTRIBUTING.md +23 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +204 -0
- data/Rakefile +19 -0
- data/lib/tricle.rb +4 -0
- data/lib/tricle/abstract_method_error.rb +8 -0
- data/lib/tricle/email_helper.rb +66 -0
- data/lib/tricle/mail_preview.rb +10 -0
- data/lib/tricle/mailer.rb +75 -0
- data/lib/tricle/metric.rb +23 -0
- data/lib/tricle/presenters/group.rb +20 -0
- data/lib/tricle/presenters/list.rb +27 -0
- data/lib/tricle/presenters/metric.rb +51 -0
- data/lib/tricle/presenters/report.rb +40 -0
- data/lib/tricle/presenters/section.rb +9 -0
- data/lib/tricle/range_data.rb +27 -0
- data/lib/tricle/tasks.rb +19 -0
- data/lib/tricle/templates/email.css +47 -0
- data/lib/tricle/templates/email.html.erb +45 -0
- data/lib/tricle/templates/email.text.erb +2 -0
- data/lib/tricle/version.rb +3 -0
- data/screenshot.png +0 -0
- data/spec/app/group_test_mailer.rb +14 -0
- data/spec/app/list_test_mailer.rb +10 -0
- data/spec/app/test_mailer.rb +12 -0
- data/spec/app/test_metric.rb +39 -0
- data/spec/app/test_metric_with_long_name.rb +4 -0
- data/spec/app/uber_test_mailer.rb +20 -0
- data/spec/config/timecop.rb +7 -0
- data/spec/fixture_generator +18 -0
- data/spec/fixtures/weeks.csv +13 -0
- data/spec/presenters/group_spec.rb +17 -0
- data/spec/presenters/metric_spec.rb +42 -0
- data/spec/presenters/report_spec.rb +19 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/unit/abstract_method_error_spec.rb +16 -0
- data/spec/unit/email_helper_spec.rb +43 -0
- data/spec/unit/mail_preview_spec.rb +15 -0
- data/spec/unit/mailer_spec.rb +66 -0
- data/spec/unit/metric_spec.rb +18 -0
- data/spec/unit/range_data_spec.rb +24 -0
- data/spec/unit/test_metric_spec.rb +20 -0
- data/tricle.gemspec +37 -0
- metadata +324 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8ae60bb0e6e671d65f0c1901458bdb6a2a8ea730
|
4
|
+
data.tar.gz: 4714ec2150c42c605587d3cea5fd05382a60f4ab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0b2c91ada726e91f976c1cb5ed6c421c9a8a8e4a8d6f633f599bd644e71aa80af59157835cbad37ad844952abe3d60a59e5fe272d1a92c672133d44d871e9c1
|
7
|
+
data.tar.gz: 365357668547fc809ace7212a7d2a9931850b8d69d97f0d85b7e0713b23c205cddcbee6ecb96596c92ae1caba151ba5786441594dc8492e26c279e41df122f5a
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
To set up, run
|
4
|
+
|
5
|
+
```bash
|
6
|
+
bundle
|
7
|
+
```
|
8
|
+
|
9
|
+
To run tests,
|
10
|
+
|
11
|
+
```bash
|
12
|
+
bundle exec rspec
|
13
|
+
# or, to run continuously:
|
14
|
+
bundle exec guard
|
15
|
+
```
|
16
|
+
|
17
|
+
To see a live preview with dummy data:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
gem install shotgun
|
21
|
+
shotgun
|
22
|
+
open http://localhost:9393/test_mailer.html
|
23
|
+
```
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Aidan Feldman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# Tricle [](https://travis-ci.org/artsy/tricle) [](https://codeclimate.com/github/artsy/tricle)
|
2
|
+
|
3
|
+
Automated metrics reporting via email. It's datastore-agnostic, so you can query SQL, MongoDB, external APIs, etc. to generate the stats you need. See [here](https://github.com/afeld/tricle-afeld) for an example implementation ([live demo](http://tricle.afeld.me/weekly_metrics)).
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
### Gem
|
10
|
+
|
11
|
+
This gem can be used within an existing project (e.g. a Rails app), or standalone.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# Gemfile
|
15
|
+
gem 'tricle', '~> 0.1.0'
|
16
|
+
|
17
|
+
# Rakefile
|
18
|
+
require 'tricle/tasks'
|
19
|
+
|
20
|
+
# your/config/file.rb
|
21
|
+
# unless you already have ActionMailer set up
|
22
|
+
ActionMailer::Base.raise_delivery_errors = true
|
23
|
+
ActionMailer::Base.smtp_settings = {
|
24
|
+
# ...
|
25
|
+
}
|
26
|
+
```
|
27
|
+
|
28
|
+
See [the ActionMailer guide](http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration) for configuration details. Finally, execute:
|
29
|
+
|
30
|
+
```bash
|
31
|
+
bundle
|
32
|
+
```
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
### Metrics
|
37
|
+
|
38
|
+
For each metric you want to report, create a new subclass of `Tricle::Metric` that implements `#size_for_range` and `#total`:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class MyMetric < Tricle::Metric
|
42
|
+
|
43
|
+
# Retrieve the value of this metric for the provided time period. Generally
|
44
|
+
# this will be the count/value added/removed. Not necessary if #items_for_range
|
45
|
+
# is defined.
|
46
|
+
#
|
47
|
+
# @param start_at [Time]
|
48
|
+
# @param end_at [Time] non-inclusive
|
49
|
+
# @return [Fixnum]
|
50
|
+
def size_for_range(start_at, end_at)
|
51
|
+
# ...
|
52
|
+
end
|
53
|
+
|
54
|
+
# Retrieve the cumulative value for this metric.
|
55
|
+
#
|
56
|
+
# @return [Fixnum] the grand total
|
57
|
+
def total
|
58
|
+
# ...
|
59
|
+
end
|
60
|
+
|
61
|
+
# Optional: only necessary if using `list` for this Metric within your Mailer.
|
62
|
+
#
|
63
|
+
# @param start_at [Time]
|
64
|
+
# @param end_at [Time] non-inclusive
|
65
|
+
# @return [Enumerator]
|
66
|
+
def items_for_range(start_at, end_at)
|
67
|
+
# ...
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
ActiveRecord example:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# metrics/new_users.rb
|
77
|
+
class NewUsers < Tricle::Metric
|
78
|
+
|
79
|
+
def size_for_range(start_at, end_at)
|
80
|
+
self.size_for_range(start_at, end_at).size
|
81
|
+
end
|
82
|
+
|
83
|
+
def total
|
84
|
+
self.users.count
|
85
|
+
end
|
86
|
+
|
87
|
+
def items_for_range(start_at, end_at)
|
88
|
+
self.size_for_range(start_at, end_at)
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def size_for_range(start_at, end_at)
|
95
|
+
self.users.where('created_at >= ? AND created_at < ?', start_at, end_at)
|
96
|
+
end
|
97
|
+
|
98
|
+
# You can add whatever helper methods in that class that you need.
|
99
|
+
def users
|
100
|
+
# non-deleted Users
|
101
|
+
User.where(deleted_at: nil)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
### Mailers
|
108
|
+
|
109
|
+
Mailers specify how a particular set of Metrics should be sent. You can define one or multiple, to send different metrics to different groups of people.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
class MyMailer < Tricle::Mailer
|
113
|
+
|
114
|
+
# accepts the same options as ActionMailer... see "Default Hash" at
|
115
|
+
# http://rubydoc.info/gems/actionmailer/ActionMailer/Base
|
116
|
+
default(
|
117
|
+
# ...
|
118
|
+
)
|
119
|
+
|
120
|
+
metric MyMetric1
|
121
|
+
metric MyMetric2
|
122
|
+
# ...
|
123
|
+
|
124
|
+
# optional: metrics can be grouped
|
125
|
+
group "Group 1 Name" do
|
126
|
+
metric MyMetric3
|
127
|
+
# ...
|
128
|
+
end
|
129
|
+
group "Group 2 Name" do
|
130
|
+
metric MyMetric4
|
131
|
+
# ...
|
132
|
+
end
|
133
|
+
|
134
|
+
# optional: list the items for the specified Metric
|
135
|
+
list MyMetric2 do |item|
|
136
|
+
# return the HTML string for each particular item
|
137
|
+
end
|
138
|
+
# ...
|
139
|
+
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
e.g.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
# mailers/weekly_insights.rb
|
147
|
+
class WeeklyInsights < Tricle::Mailer
|
148
|
+
|
149
|
+
default(
|
150
|
+
to: ['theteam@mycompany.com', 'theboss@mycompany.com'],
|
151
|
+
from: 'noreply@mycompany.com'
|
152
|
+
)
|
153
|
+
|
154
|
+
metric NewUsers
|
155
|
+
|
156
|
+
list NewUsers do |user|
|
157
|
+
<<-MARKUP
|
158
|
+
<h3>#{user.name}</h3>
|
159
|
+
<div>#{user.location}</div>
|
160
|
+
<a href="mailto:#{user.email}>#{user.email}</a>
|
161
|
+
MARKUP
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
The subject line will be based on the Mailer class name.
|
168
|
+
|
169
|
+
### Previewing
|
170
|
+
|
171
|
+
Since you'd probably like to preview your mailers before sending them, set up the `Tricle::MailPreview` Rack app (which uses [MailView](https://github.com/37signals/mail_view)).
|
172
|
+
|
173
|
+
#### Within a Rails app
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
# config/initializers/tricle.rb
|
177
|
+
require 'tricle/mail_preview'
|
178
|
+
|
179
|
+
# config/routes.rb
|
180
|
+
if Rails.env.development?
|
181
|
+
mount MailPreview => 'mail_view'
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
and navigate to [localhost:3000/mail_view](http://localhost:3000/mail_view).
|
186
|
+
|
187
|
+
#### Standalone
|
188
|
+
|
189
|
+
```bash
|
190
|
+
bundle exec rake tricle:preview
|
191
|
+
open http://localhost:8080
|
192
|
+
```
|
193
|
+
|
194
|
+
## Deploying
|
195
|
+
|
196
|
+
To send all Tricle emails, run
|
197
|
+
|
198
|
+
```bash
|
199
|
+
rake tricle:emails:send
|
200
|
+
```
|
201
|
+
|
202
|
+
### Cron Setup
|
203
|
+
|
204
|
+
TODO
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
require_relative 'spec/config/timecop'
|
5
|
+
|
6
|
+
reset_time
|
7
|
+
|
8
|
+
# used for the preview
|
9
|
+
require_relative 'lib/tricle'
|
10
|
+
require_relative 'spec/app/group_test_mailer.rb'
|
11
|
+
require_relative 'spec/app/list_test_mailer.rb'
|
12
|
+
require_relative 'spec/app/test_mailer.rb'
|
13
|
+
require_relative 'spec/app/uber_test_mailer.rb'
|
14
|
+
|
15
|
+
require_relative 'lib/tricle/tasks'
|
16
|
+
|
17
|
+
RSpec::Core::RakeTask.new(:spec)
|
18
|
+
|
19
|
+
task :default => :spec
|
data/lib/tricle.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'active_support/core_ext/date/calculations'
|
2
|
+
require 'active_support/core_ext/numeric/time'
|
3
|
+
|
4
|
+
module Tricle
|
5
|
+
module EmailHelper
|
6
|
+
def weeks_ago(n)
|
7
|
+
Date.today.beginning_of_week.weeks_ago(n)
|
8
|
+
end
|
9
|
+
|
10
|
+
def format_date(date)
|
11
|
+
date.strftime('%-m/%-d/%y')
|
12
|
+
end
|
13
|
+
|
14
|
+
def number_with_delimiter(number)
|
15
|
+
# from http://stackoverflow.com/a/11466770/358804
|
16
|
+
number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
17
|
+
end
|
18
|
+
|
19
|
+
def percent_change(new_val, old_val)
|
20
|
+
if old_val == 0
|
21
|
+
new_val >= 0 ? '+' : '-'
|
22
|
+
else
|
23
|
+
fraction = (new_val - old_val) / old_val.to_f
|
24
|
+
sprintf('%+.1f%', fraction * 100.0)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def percent_change_cell(new_val, old_val)
|
29
|
+
cls = (new_val >= old_val) ? 'positive' : 'negative'
|
30
|
+
pct_str = percent_change(new_val, old_val)
|
31
|
+
old_val_str = number_with_delimiter(old_val.round)
|
32
|
+
%[<td class="#{cls}"><div>#{pct_str}</div><div>#{old_val_str}</div></td>].html_safe
|
33
|
+
end
|
34
|
+
|
35
|
+
def dates_range_str(start_at, end_at)
|
36
|
+
"#{ self.format_date(start_at) } - #{ self.format_date(end_at) }"
|
37
|
+
end
|
38
|
+
|
39
|
+
def dates_cell(start_at, end_at)
|
40
|
+
range = dates_range_str(start_at, end_at)
|
41
|
+
%[<div class="date-range">(#{range})</div>].html_safe
|
42
|
+
end
|
43
|
+
|
44
|
+
def single_week_dates_cell(start_at)
|
45
|
+
dates_cell(start_at, start_at.end_of_week)
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_week_dates_cell
|
49
|
+
single_week_dates_cell(weeks_ago(1))
|
50
|
+
end
|
51
|
+
|
52
|
+
def previous_week_dates_cell
|
53
|
+
single_week_dates_cell(weeks_ago(2))
|
54
|
+
end
|
55
|
+
|
56
|
+
def quarter_dates_cell
|
57
|
+
dates_cell(weeks_ago(13), weeks_ago(1).end_of_week)
|
58
|
+
end
|
59
|
+
|
60
|
+
def list_markup(list)
|
61
|
+
start_at = self.weeks_ago(1).to_time
|
62
|
+
end_at = start_at + 7.days
|
63
|
+
list.items_markup(start_at, end_at).html_safe
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'action_mailer'
|
2
|
+
require 'active_support/descendants_tracker'
|
3
|
+
require 'premailer'
|
4
|
+
|
5
|
+
require_relative 'email_helper'
|
6
|
+
require_relative 'presenters/report'
|
7
|
+
|
8
|
+
|
9
|
+
module Tricle
|
10
|
+
class Mailer < ActionMailer::Base
|
11
|
+
include ActiveSupport::DescendantsTracker
|
12
|
+
|
13
|
+
class_attribute :report
|
14
|
+
helper Tricle::EmailHelper
|
15
|
+
self.view_paths = File.dirname(__FILE__)
|
16
|
+
|
17
|
+
CSS = File.read(File.join(File.dirname(__FILE__), 'templates', 'email.css')).freeze
|
18
|
+
|
19
|
+
|
20
|
+
def subject
|
21
|
+
"Your #{self.class.name.titleize}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def premailer(message)
|
25
|
+
# message.text_part.body = Premailer.new(message.text_part.body.to_s, with_html_string: true).to_plain_text
|
26
|
+
message.html_part.body = Premailer.new(message.html_part.body.to_s, css_string: CSS.dup, with_html_string: true).to_inline_css
|
27
|
+
message
|
28
|
+
end
|
29
|
+
|
30
|
+
def email(options = {})
|
31
|
+
options = {
|
32
|
+
subject: self.subject
|
33
|
+
}.merge(options)
|
34
|
+
|
35
|
+
@report = self.report
|
36
|
+
|
37
|
+
message = mail(options) do |format|
|
38
|
+
format.html { render 'templates/email' }
|
39
|
+
format.text { render 'templates/email' }
|
40
|
+
end
|
41
|
+
|
42
|
+
premailer(message)
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
def inherited(klass)
|
47
|
+
klass.report = Tricle::Presenters::Report.new
|
48
|
+
super(klass)
|
49
|
+
end
|
50
|
+
|
51
|
+
def group(title)
|
52
|
+
self.report.add_group(title)
|
53
|
+
yield if block_given?
|
54
|
+
end
|
55
|
+
|
56
|
+
def metric(klass)
|
57
|
+
self.report.add_metric(klass)
|
58
|
+
end
|
59
|
+
|
60
|
+
def list(klass, &block)
|
61
|
+
self.report.add_list(klass, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def send_all
|
65
|
+
mailers = Tricle::Mailer.descendants
|
66
|
+
puts "Sending #{mailers.size} emails..."
|
67
|
+
mailers.each do |klass|
|
68
|
+
puts "Sending #{klass.name}..."
|
69
|
+
klass.email.deliver
|
70
|
+
end
|
71
|
+
puts "Done."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|