reparty 0.3.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +6 -0
- data/README.md +19 -7
- data/app/mailers/report_mailer.rb +14 -1
- data/app/views/report_mailer/_mixpanel_funnel.html.erb +33 -0
- data/app/views/report_mailer/daily.html.erb +5 -0
- data/app/views/report_mailer/weekly.html.erb +62 -0
- data/lib/reparty/config.rb +7 -2
- data/lib/reparty/email.rb +8 -0
- data/lib/reparty/report/active_record.rb +1 -1
- data/lib/reparty/report/mixpanel_funnel.rb +38 -0
- data/lib/reparty/report/sendgrid.rb +1 -1
- data/lib/reparty/report.rb +5 -2
- data/lib/reparty/version.rb +1 -1
- data/lib/reparty.rb +3 -1
- data/lib/tasks/reparty.rake +10 -0
- data/reparty.gemspec +1 -0
- data/spec/cassettes/mixpanel.yml +145 -0
- data/spec/reparty_spec.rb +15 -5
- data/spec/report/mixpanel_funnel_spec.rb +38 -0
- data/spec/report_spec.rb +9 -12
- data/spec/tasks/reparty_task_spec.rb +38 -0
- data/spec/vcr_helper.rb +4 -0
- metadata +27 -4
data/History.md
CHANGED
data/README.md
CHANGED
@@ -3,18 +3,22 @@
|
|
3
3
|
__A business analytics reporting party!__
|
4
4
|
|
5
5
|
Although initially concentrating on a daily email report, Reparty intends to be an easy
|
6
|
-
tool for
|
6
|
+
tool for generating reports focused on business analytics about your app.
|
7
7
|
|
8
|
-
|
8
|
+
A modular design is used, so reporting on a variety of different data sources is possible.
|
9
|
+
Custom modules can be created within your app. The currently bundled modules are:
|
9
10
|
|
10
11
|
* ActiveRecord, including arbitrary sum/count columns
|
11
|
-
*
|
12
|
-
*
|
13
|
-
|
12
|
+
* SendGrid
|
13
|
+
* Mixpanel (just funnels for now)
|
14
|
+
|
15
|
+
Other modules will be added over time. This is in use in production at my company, SalesLoft,
|
16
|
+
so focus will be on the modules that best suit our needs. Feel free to submit a pull request
|
17
|
+
with any modules you have created.
|
14
18
|
|
15
19
|
## Configuration
|
16
20
|
|
17
|
-
|
21
|
+
You can configure Reparty in an initalizer using a config block:
|
18
22
|
|
19
23
|
Reparty.config do |config|
|
20
24
|
config.from = "test@test.com"
|
@@ -23,10 +27,18 @@ Whilst not completely done, this is how you'll configure it for emailing a daily
|
|
23
27
|
config.add_report Reparty::Report::ActiveRecord, "New User Signups", :user
|
24
28
|
end
|
25
29
|
|
30
|
+
Better documentation of Reparty and each individual module's configuration is coming. For now,
|
31
|
+
you'll just have to read the code (sorry!). Check the test suite for examples.
|
32
|
+
|
26
33
|
## Usage
|
27
34
|
|
28
35
|
Generating an email is as simple as a rake task:
|
29
36
|
|
30
37
|
rake reparty:email[someone@somewhere.com]
|
31
38
|
|
32
|
-
You can run this in a cron job or other scheduling system.
|
39
|
+
You can run this in a cron job or other scheduling system. A weekly email is currently hacked
|
40
|
+
in to the code, which you can run using it's separate rake task:
|
41
|
+
|
42
|
+
rake reparty:weekly_email[someone@somewhere.com]
|
43
|
+
|
44
|
+
This will likely change in the future, so watch out when upgrading!
|
@@ -2,7 +2,6 @@ ActionMailer::Base.view_paths = File.expand_path('../../views/', __FILE__)
|
|
2
2
|
|
3
3
|
class ReportMailer < ActionMailer::Base
|
4
4
|
def daily(to)
|
5
|
-
|
6
5
|
@title = Reparty::Email.title
|
7
6
|
|
8
7
|
@reports = Reparty::Email.reports
|
@@ -15,4 +14,18 @@ class ReportMailer < ActionMailer::Base
|
|
15
14
|
subject: Reparty::Email.subject
|
16
15
|
)
|
17
16
|
end
|
17
|
+
|
18
|
+
def weekly(to)
|
19
|
+
@title = Reparty::Email.weekly_title
|
20
|
+
|
21
|
+
@reports = Reparty::Email.weekly_reports
|
22
|
+
@reports.each {|r| r.attach(attachments)}
|
23
|
+
attachments.inline['spacer.gif'] = File.read(Reparty.root + 'app/assets/images/spacer.gif')
|
24
|
+
|
25
|
+
mail(
|
26
|
+
from: Reparty::Email.from,
|
27
|
+
to: to,
|
28
|
+
subject: Reparty::Email.subject
|
29
|
+
)
|
30
|
+
end
|
18
31
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<table width="100%" cellpadding="0" cellspacing="25" border="0">
|
2
|
+
<tr>
|
3
|
+
<td align="left">
|
4
|
+
<h3 style="margin:0"><%= report.title %></h3>
|
5
|
+
</td>
|
6
|
+
</tr>
|
7
|
+
<tr>
|
8
|
+
<td align="center">
|
9
|
+
<table class="data" cellpadding="5" cellspacing="0" border="0">
|
10
|
+
<% (report.funnel_data.first[1]["steps"].size+1).times.each do |i| %>
|
11
|
+
<% if i == 0 %>
|
12
|
+
<thead><tr>
|
13
|
+
<th width="100"></th>
|
14
|
+
<% report.funnel_data.each do |data| %>
|
15
|
+
<th><%= Date.parse(data[0]).strftime("%b %-d") %></th>
|
16
|
+
<% end %>
|
17
|
+
</tr></thead>
|
18
|
+
<% else %>
|
19
|
+
<tr>
|
20
|
+
<% report.funnel_data.each_with_index do |data, j| %>
|
21
|
+
<% if j == 0 %><td class="first"><%= data[1]["steps"][i-1]["goal"] %></td><% end %>
|
22
|
+
<td>
|
23
|
+
<%= number_to_percentage data[1]["steps"][i-1]["step_conv_ratio"]*100, precision: 2 %>
|
24
|
+
(<%= data[1]["steps"][i-1]["count"] %>)
|
25
|
+
</td>
|
26
|
+
<% end %>
|
27
|
+
</tr>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
</table>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
</table>
|
@@ -10,6 +10,11 @@
|
|
10
10
|
.report h3 { margin: 0; color: #444444; font-size: 16px; line-height: 24px; }
|
11
11
|
.report ul { margin-top: 5px; margin-bottom: 0; list-style-type: square; }
|
12
12
|
.report ul li { color: #888888; font-size: 14px; line-height: 20px; }
|
13
|
+
|
14
|
+
.data thead tr { background-color: #eef5fc; }
|
15
|
+
.data th { font-size: 14px; text-align: center; }
|
16
|
+
.data td { font-size: 13px; text-align: center; }
|
17
|
+
.data td.first { background-color: #ecedee; color: #666666; font-size: 11px; text-align: left; }
|
13
18
|
</style>
|
14
19
|
</head>
|
15
20
|
<body bgcolor="#eeeeee" topmargin="0" leftmargin="0" marginheight="0" marginwidth="0">
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
5
|
+
<style type="text/css">
|
6
|
+
body { margin: 0; padding: 0; background: #eeeeee; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; }
|
7
|
+
|
8
|
+
.title h1 { margin: 0; color: #999999; font-weight: normal; font-size: 25px; text-shadow: 0 1px 0 #ffffff; }
|
9
|
+
|
10
|
+
.report h3 { margin: 0; color: #444444; font-size: 16px; line-height: 24px; }
|
11
|
+
.report ul { margin-top: 5px; margin-bottom: 0; list-style-type: square; }
|
12
|
+
.report ul li { color: #888888; font-size: 14px; line-height: 20px; }
|
13
|
+
|
14
|
+
.data thead tr { background-color: #eef5fc; }
|
15
|
+
.data th { font-size: 14px; text-align: center; }
|
16
|
+
.data td { font-size: 13px; text-align: center; }
|
17
|
+
.data td.first { background-color: #ecedee; color: #666666; font-size: 11px; text-align: left; }
|
18
|
+
</style>
|
19
|
+
</head>
|
20
|
+
<body bgcolor="#eeeeee" topmargin="0" leftmargin="0" marginheight="0" marginwidth="0">
|
21
|
+
|
22
|
+
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#eeeeee">
|
23
|
+
<tr>
|
24
|
+
<td bgcolor="#eeeeee" width="100%">
|
25
|
+
|
26
|
+
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="main">
|
27
|
+
<tr><td><img border="0" src="<%= attachments["spacer.gif"].url %>" width="1" height="25"><br></td></tr>
|
28
|
+
|
29
|
+
<tr>
|
30
|
+
<td width="600" class="title">
|
31
|
+
<h1><%= @title %></h1>
|
32
|
+
</td>
|
33
|
+
</tr>
|
34
|
+
|
35
|
+
<tr><td><img border="0" src="<%= attachments["spacer.gif"].url %>" width="1" height="25"><br></td></tr>
|
36
|
+
|
37
|
+
<% @reports.each do |report| -%>
|
38
|
+
<tr>
|
39
|
+
<td bgcolor="#ffffff">
|
40
|
+
|
41
|
+
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center">
|
42
|
+
<tr>
|
43
|
+
<td bgcolor="<%= report.color %>" nowrap><img border="0" src="<%= attachments["spacer.gif"].url %>" width="5" height="1"></td>
|
44
|
+
<td class="report">
|
45
|
+
<%= render partial: report.class.name.demodulize.underscore, locals:{report:report} %>
|
46
|
+
</td>
|
47
|
+
</tr>
|
48
|
+
</table>
|
49
|
+
|
50
|
+
</td>
|
51
|
+
</tr>
|
52
|
+
|
53
|
+
<tr><td><img border="0" src="<%= attachments["spacer.gif"].url %>" width="1" height="25"><br></td></tr>
|
54
|
+
<% end -%>
|
55
|
+
</table>
|
56
|
+
|
57
|
+
</td>
|
58
|
+
</tr>
|
59
|
+
</table>
|
60
|
+
|
61
|
+
</body>
|
62
|
+
</html>
|
data/lib/reparty/config.rb
CHANGED
@@ -2,15 +2,20 @@ require "ostruct"
|
|
2
2
|
|
3
3
|
module Reparty
|
4
4
|
class Config < ::OpenStruct
|
5
|
-
attr_reader :reports
|
5
|
+
attr_reader :reports, :weekly_reports
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@reports = []
|
9
|
+
@weekly_reports = []
|
9
10
|
super
|
10
11
|
end
|
11
12
|
|
12
13
|
def add_report(report, *args, &block)
|
13
|
-
@reports << report.new(*args, &block)
|
14
|
+
@reports << report.new(1, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_weekly_report(report, *args, &block)
|
18
|
+
@weekly_reports << report.new(7, *args, &block)
|
14
19
|
end
|
15
20
|
end
|
16
21
|
end
|
data/lib/reparty/email.rb
CHANGED
@@ -5,6 +5,10 @@ module Reparty
|
|
5
5
|
Reparty.reports
|
6
6
|
end
|
7
7
|
|
8
|
+
def weekly_reports
|
9
|
+
Reparty.weekly_reports
|
10
|
+
end
|
11
|
+
|
8
12
|
def from
|
9
13
|
Reparty.configuration.from || 'nobody@nowhere.com'
|
10
14
|
end
|
@@ -16,6 +20,10 @@ module Reparty
|
|
16
20
|
def title
|
17
21
|
Reparty.configuration.title || 'Your Daily Report:'
|
18
22
|
end
|
23
|
+
|
24
|
+
def weekly_title
|
25
|
+
Reparty.configuration.weekly_title || 'Your Weekly Report:'
|
26
|
+
end
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'mixpanel_client'
|
2
|
+
|
3
|
+
module Reparty
|
4
|
+
class Report
|
5
|
+
class MixpanelFunnel < Report
|
6
|
+
attr_reader :funnel_id, :api_key, :api_secret
|
7
|
+
|
8
|
+
def initialize(*args, &block)
|
9
|
+
super(args.shift, args.shift)
|
10
|
+
|
11
|
+
@funnel_id = args.shift
|
12
|
+
@api_key = args.shift
|
13
|
+
@api_secret = args.shift
|
14
|
+
|
15
|
+
@color = "#7548a2"
|
16
|
+
end
|
17
|
+
|
18
|
+
def client
|
19
|
+
@client ||= Mixpanel::Client.new({api_key: api_key, api_secret: api_secret})
|
20
|
+
end
|
21
|
+
|
22
|
+
def funnel_data
|
23
|
+
from_date = (DateTime.now-(interval*7)).strftime("%Y-%m-%d")
|
24
|
+
to_date = (DateTime.now-1).strftime("%Y-%m-%d")
|
25
|
+
|
26
|
+
@funnel_data ||= Hash[client.request(
|
27
|
+
"funnels",
|
28
|
+
{
|
29
|
+
funnel_id: funnel_id,
|
30
|
+
interval: interval,
|
31
|
+
from_date: from_date,
|
32
|
+
to_date: to_date
|
33
|
+
}
|
34
|
+
)["data"].sort.reverse]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/reparty/report.rb
CHANGED
@@ -2,11 +2,14 @@ require 'gruff'
|
|
2
2
|
|
3
3
|
module Reparty
|
4
4
|
class Report
|
5
|
-
attr_reader :title, :color
|
5
|
+
attr_reader :interval, :title, :color
|
6
|
+
|
7
|
+
def initialize(interval, title)
|
8
|
+
@interval = interval
|
6
9
|
|
7
|
-
def initialize(title)
|
8
10
|
raise "Report: title must be defined" if title.blank?
|
9
11
|
@title = title
|
12
|
+
|
10
13
|
@color = "#832701"
|
11
14
|
end
|
12
15
|
|
data/lib/reparty/version.rb
CHANGED
data/lib/reparty.rb
CHANGED
@@ -6,6 +6,7 @@ require "reparty/email"
|
|
6
6
|
require "reparty/report"
|
7
7
|
require "reparty/report/active_record"
|
8
8
|
require "reparty/report/sendgrid"
|
9
|
+
require "reparty/report/mixpanel_funnel"
|
9
10
|
|
10
11
|
require "reparty/engine"
|
11
12
|
|
@@ -16,7 +17,7 @@ module Reparty
|
|
16
17
|
end
|
17
18
|
|
18
19
|
attr_accessor :configuration
|
19
|
-
attr_reader :reports
|
20
|
+
attr_reader :reports, :weekly_reports
|
20
21
|
|
21
22
|
def config
|
22
23
|
self.configuration = Config.new
|
@@ -24,6 +25,7 @@ module Reparty
|
|
24
25
|
yield(configuration)
|
25
26
|
|
26
27
|
@reports = self.configuration.reports
|
28
|
+
@weekly_reports = self.configuration.weekly_reports
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
data/lib/tasks/reparty.rake
CHANGED
@@ -8,4 +8,14 @@ namespace :reparty do
|
|
8
8
|
|
9
9
|
ReportMailer.daily(args.address).deliver
|
10
10
|
end
|
11
|
+
|
12
|
+
desc 'Generate a weekly email to be sent'
|
13
|
+
task :weekly_email, [:address] => :environment do |t,args|
|
14
|
+
unless args.address
|
15
|
+
puts "Error: An address is required."
|
16
|
+
next
|
17
|
+
end
|
18
|
+
|
19
|
+
ReportMailer.weekly(args.address).deliver
|
20
|
+
end
|
11
21
|
end
|
data/reparty.gemspec
CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_dependency "gruff"
|
26
26
|
spec.add_dependency "roadie"
|
27
27
|
spec.add_dependency "httparty"
|
28
|
+
spec.add_dependency "mixpanel_client"
|
28
29
|
|
29
30
|
spec.add_development_dependency "bundler", "~> 1.3"
|
30
31
|
spec.add_development_dependency "rake"
|
@@ -0,0 +1,145 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://mixpanel.com/api/2.0/funnels?api_key=<MIXPANEL_KEY>&expire=1371655510&format=json&from_date=2013-06-12&funnel_id=<MIXPANEL_FUNNEL>&interval=1&sig=822d5273993b8b06e71358246bfa2d6f&to_date=2013-06-18
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- ! '*/*'
|
12
|
+
User-Agent:
|
13
|
+
- Ruby
|
14
|
+
response:
|
15
|
+
status:
|
16
|
+
code: 200
|
17
|
+
message: OK
|
18
|
+
headers:
|
19
|
+
Server:
|
20
|
+
- nginx/1.1.19
|
21
|
+
Date:
|
22
|
+
- Wed, 19 Jun 2013 15:15:10 GMT
|
23
|
+
Content-Type:
|
24
|
+
- application/json
|
25
|
+
Content-Length:
|
26
|
+
- '3161'
|
27
|
+
Connection:
|
28
|
+
- keep-alive
|
29
|
+
Vary:
|
30
|
+
- Accept-Encoding
|
31
|
+
Cache-Control:
|
32
|
+
- no-cache, no-store
|
33
|
+
body:
|
34
|
+
encoding: US-ASCII
|
35
|
+
string: ! '{"meta": {"dates": ["2013-06-12", "2013-06-13", "2013-06-14", "2013-06-15",
|
36
|
+
"2013-06-16", "2013-06-17", "2013-06-18"]}, "data": {"2013-06-17": {"steps":
|
37
|
+
[{"count": 848, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
38
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 29, "step_conv_ratio":
|
39
|
+
0.03419811320754717, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.03419811320754717,
|
40
|
+
"avg_time": 25755, "event": "Viewed JCA Stream"}], "analysis": {"completion":
|
41
|
+
29, "starting_amount": 848, "steps": 2, "worst": 1}}, "2013-06-16": {"steps":
|
42
|
+
[{"count": 571, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
43
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 14, "step_conv_ratio":
|
44
|
+
0.024518388791593695, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.024518388791593695,
|
45
|
+
"avg_time": 109055, "event": "Viewed JCA Stream"}], "analysis": {"completion":
|
46
|
+
14, "starting_amount": 571, "steps": 2, "worst": 1}}, "2013-06-15": {"steps":
|
47
|
+
[{"count": 776, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
48
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 20, "step_conv_ratio":
|
49
|
+
0.02577319587628866, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.02577319587628866,
|
50
|
+
"avg_time": 195452, "event": "Viewed JCA Stream"}], "analysis": {"completion":
|
51
|
+
20, "starting_amount": 776, "steps": 2, "worst": 1}}, "2013-06-14": {"steps":
|
52
|
+
[{"count": 901, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
53
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 35, "step_conv_ratio":
|
54
|
+
0.038845726970033294, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.038845726970033294,
|
55
|
+
"avg_time": 148191, "event": "Viewed JCA Stream"}], "analysis": {"completion":
|
56
|
+
35, "starting_amount": 901, "steps": 2, "worst": 1}}, "2013-06-13": {"steps":
|
57
|
+
[{"count": 959, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
58
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 44, "step_conv_ratio":
|
59
|
+
0.045881126173096975, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.045881126173096975,
|
60
|
+
"avg_time": 197465, "event": "Viewed JCA Stream"}], "analysis": {"completion":
|
61
|
+
44, "starting_amount": 959, "steps": 2, "worst": 1}}, "2013-06-12": {"steps":
|
62
|
+
[{"count": 980, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
63
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 41, "step_conv_ratio":
|
64
|
+
0.04183673469387755, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.04183673469387755,
|
65
|
+
"avg_time": 206732, "event": "Viewed JCA Stream"}], "analysis": {"completion":
|
66
|
+
41, "starting_amount": 980, "steps": 2, "worst": 1}}, "2013-06-18": {"steps":
|
67
|
+
[{"count": 1071, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
68
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 23, "step_conv_ratio":
|
69
|
+
0.021475256769374416, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0.021475256769374416,
|
70
|
+
"avg_time": 15241, "event": "Viewed JCA Stream"}], "analysis": {"completion":
|
71
|
+
23, "starting_amount": 1071, "steps": 2, "worst": 1}}}}'
|
72
|
+
http_version:
|
73
|
+
recorded_at: Wed, 19 Jun 2013 15:15:10 GMT
|
74
|
+
- request:
|
75
|
+
method: get
|
76
|
+
uri: https://mixpanel.com/api/2.0/funnels?api_key=<MIXPANEL_KEY>&expire=1371655958&format=json&from_date=2013-05-01&funnel_id=<MIXPANEL_FUNNEL>&interval=7&sig=1d9e9bb9e518724bd091831b21c80f61&to_date=2013-06-18
|
77
|
+
body:
|
78
|
+
encoding: US-ASCII
|
79
|
+
string: ''
|
80
|
+
headers:
|
81
|
+
Accept:
|
82
|
+
- ! '*/*'
|
83
|
+
User-Agent:
|
84
|
+
- Ruby
|
85
|
+
response:
|
86
|
+
status:
|
87
|
+
code: 200
|
88
|
+
message: OK
|
89
|
+
headers:
|
90
|
+
Server:
|
91
|
+
- nginx/1.1.19
|
92
|
+
Date:
|
93
|
+
- Wed, 19 Jun 2013 15:22:39 GMT
|
94
|
+
Content-Type:
|
95
|
+
- application/json
|
96
|
+
Content-Length:
|
97
|
+
- '2939'
|
98
|
+
Connection:
|
99
|
+
- keep-alive
|
100
|
+
Vary:
|
101
|
+
- Accept-Encoding
|
102
|
+
Cache-Control:
|
103
|
+
- no-cache, no-store
|
104
|
+
body:
|
105
|
+
encoding: US-ASCII
|
106
|
+
string: ! '{"meta": {"dates": ["2013-05-01", "2013-05-08", "2013-05-15", "2013-05-22",
|
107
|
+
"2013-05-29", "2013-06-05", "2013-06-12"]}, "data": {"2013-05-15": {"steps":
|
108
|
+
[{"count": 0, "step_conv_ratio": 1, "goal": "Opened LinkedIn Email", "overall_conv_ratio":
|
109
|
+
1, "avg_time": null, "event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio":
|
110
|
+
0, "goal": "Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null,
|
111
|
+
"event": "Viewed JCA Stream"}], "analysis": {"completion": 0, "starting_amount":
|
112
|
+
0, "steps": 2, "worst": 1}}, "2013-05-01": {"steps": [{"count": 0, "step_conv_ratio":
|
113
|
+
1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
|
114
|
+
"event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
|
115
|
+
"Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
|
116
|
+
JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
|
117
|
+
2, "worst": 1}}, "2013-06-12": {"steps": [{"count": 1842, "step_conv_ratio":
|
118
|
+
1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
|
119
|
+
"event": "Opened LinkedIn Email"}, {"count": 77, "step_conv_ratio": 0.041802388707926165,
|
120
|
+
"goal": "Viewed JCA Stream", "overall_conv_ratio": 0.041802388707926165, "avg_time":
|
121
|
+
131583, "event": "Viewed JCA Stream"}], "analysis": {"completion": 77, "starting_amount":
|
122
|
+
1842, "steps": 2, "worst": 1}}, "2013-05-08": {"steps": [{"count": 0, "step_conv_ratio":
|
123
|
+
1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
|
124
|
+
"event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
|
125
|
+
"Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
|
126
|
+
JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
|
127
|
+
2, "worst": 1}}, "2013-05-29": {"steps": [{"count": 0, "step_conv_ratio":
|
128
|
+
1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
|
129
|
+
"event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
|
130
|
+
"Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
|
131
|
+
JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
|
132
|
+
2, "worst": 1}}, "2013-05-22": {"steps": [{"count": 0, "step_conv_ratio":
|
133
|
+
1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
|
134
|
+
"event": "Opened LinkedIn Email"}, {"count": 0, "step_conv_ratio": 0, "goal":
|
135
|
+
"Viewed JCA Stream", "overall_conv_ratio": 0, "avg_time": null, "event": "Viewed
|
136
|
+
JCA Stream"}], "analysis": {"completion": 0, "starting_amount": 0, "steps":
|
137
|
+
2, "worst": 1}}, "2013-06-05": {"steps": [{"count": 1588, "step_conv_ratio":
|
138
|
+
1, "goal": "Opened LinkedIn Email", "overall_conv_ratio": 1, "avg_time": null,
|
139
|
+
"event": "Opened LinkedIn Email"}, {"count": 82, "step_conv_ratio": 0.05163727959697733,
|
140
|
+
"goal": "Viewed JCA Stream", "overall_conv_ratio": 0.05163727959697733, "avg_time":
|
141
|
+
415671, "event": "Viewed JCA Stream"}], "analysis": {"completion": 82, "starting_amount":
|
142
|
+
1588, "steps": 2, "worst": 1}}}}'
|
143
|
+
http_version:
|
144
|
+
recorded_at: Wed, 19 Jun 2013 15:22:39 GMT
|
145
|
+
recorded_with: VCR 2.4.0
|
data/spec/reparty_spec.rb
CHANGED
@@ -12,11 +12,21 @@ describe Reparty do
|
|
12
12
|
Reparty.configuration.sender.should == "test@test.com"
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
config
|
15
|
+
describe "adds reports" do
|
16
|
+
subject do
|
17
|
+
Reparty.config do |config|
|
18
|
+
config.add_report Reparty::Report, "Some Report"
|
19
|
+
config.add_weekly_report Reparty::Report, "Some Weekly Report"
|
20
|
+
end
|
21
|
+
Reparty
|
18
22
|
end
|
19
|
-
|
20
|
-
|
23
|
+
|
24
|
+
its("reports.first") { should be_kind_of(Reparty::Report) }
|
25
|
+
its("reports.first.title") { should == "Some Report" }
|
26
|
+
its("reports.size") { should == 1 }
|
27
|
+
|
28
|
+
its("weekly_reports.first") { should be_kind_of(Reparty::Report) }
|
29
|
+
its("weekly_reports.first.title") { should == "Some Weekly Report" }
|
30
|
+
its("weekly_reports.size") { should == 1 }
|
21
31
|
end
|
22
32
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
interval_matcher = lambda do |request_made, request_stored|
|
4
|
+
CGI.parse(URI(request_made.uri).query).fetch("interval",nil) == CGI.parse(URI(request_stored.uri).query).fetch("interval",nil)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Reparty::Report::MixpanelFunnel, vcr: { cassette_name: "mixpanel", match_requests_on: [:method, :host, :path, interval_matcher] } do
|
8
|
+
let(:funnel_id) { ENV.fetch("MIXPANEL_FUNNEL", 123456) }
|
9
|
+
let(:mixpanel_key) { ENV.fetch("MIXPANEL_KEY", "abc123") }
|
10
|
+
let(:mixpanel_secret) { ENV.fetch("MIXPANEL_SECRET", "cba321") }
|
11
|
+
|
12
|
+
subject do
|
13
|
+
Reparty.config do |config|
|
14
|
+
config.add_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", funnel_id, mixpanel_key, mixpanel_secret
|
15
|
+
end
|
16
|
+
Reparty.reports.last
|
17
|
+
end
|
18
|
+
|
19
|
+
it { should be_kind_of(Reparty::Report::MixpanelFunnel) }
|
20
|
+
its(:funnel_id) { should == funnel_id }
|
21
|
+
its(:api_key) { should == mixpanel_key }
|
22
|
+
its(:api_secret) { should == mixpanel_secret }
|
23
|
+
its(:color) { should == "#7548a2" }
|
24
|
+
its(:client) { should be_kind_of(Mixpanel::Client)}
|
25
|
+
|
26
|
+
its("funnel_data.size") { should == 7 }
|
27
|
+
|
28
|
+
describe "weekly interval" do
|
29
|
+
subject do
|
30
|
+
Reparty.config do |config|
|
31
|
+
config.add_weekly_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", funnel_id, mixpanel_key, mixpanel_secret
|
32
|
+
end
|
33
|
+
Reparty.weekly_reports.last
|
34
|
+
end
|
35
|
+
|
36
|
+
its("funnel_data.size") { should == 7 }
|
37
|
+
end
|
38
|
+
end
|
data/spec/report_spec.rb
CHANGED
@@ -1,27 +1,24 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Reparty::Report do
|
4
|
-
|
4
|
+
let(:interval) { 1 }
|
5
|
+
let(:title) { "title" }
|
5
6
|
|
6
|
-
|
7
|
+
subject { Reparty::Report.new(interval, title) }
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
}.to_not raise_exception
|
12
|
-
|
13
|
-
expect {
|
14
|
-
Reparty::Report.new(nil)
|
15
|
-
}.to raise_exception
|
9
|
+
its(:color) { should == "#832701"}
|
10
|
+
its(:interval) { should == interval}
|
11
|
+
its(:title) { should == title}
|
16
12
|
|
13
|
+
it "requires a title" do
|
17
14
|
expect {
|
18
|
-
Reparty::Report.new("")
|
15
|
+
Reparty::Report.new(interval, "")
|
19
16
|
}.to raise_exception
|
20
17
|
end
|
21
18
|
|
22
19
|
it "has an attach method" do
|
23
20
|
expect {
|
24
|
-
|
21
|
+
subject.attach(nil)
|
25
22
|
}.to_not raise_exception
|
26
23
|
end
|
27
24
|
end
|
@@ -42,10 +42,48 @@ describe "reparty:email" do
|
|
42
42
|
].each{|u| User.create!(u) }
|
43
43
|
|
44
44
|
Reparty.config do |config|
|
45
|
+
config.add_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", ENV["MIXPANEL_FUNNEL"], ENV["MIXPANEL_KEY"], ENV["MIXPANEL_SECRET"]
|
45
46
|
config.add_report Reparty::Report::ActiveRecord, "New User Signups", :user
|
46
47
|
config.add_report Reparty::Report::Sendgrid, "Sendgrid Emails", ENV["SENDGRID_USER"], ENV["SENDGRID_PASSWORD"]
|
47
48
|
end
|
48
49
|
|
50
|
+
subject.invoke("test@test.com")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "reparty:weekly_email" do
|
55
|
+
include_context "rake"
|
56
|
+
|
57
|
+
before(:each) do
|
58
|
+
User.delete_all
|
59
|
+
|
60
|
+
Reparty.config do |config|
|
61
|
+
config.add_weekly_report Reparty::Report::ActiveRecord, "New User Signups", :user
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
its(:prerequisites) { should include("environment") }
|
66
|
+
|
67
|
+
it "requires an address" do
|
68
|
+
STDOUT.should_receive(:puts)
|
69
|
+
subject.invoke
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should generate an email" do
|
73
|
+
expect{
|
74
|
+
subject.invoke("test@test.com")
|
75
|
+
}.to change(ActionMailer::Base, :deliveries)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should send out via letter_opener", letteropener: true do
|
79
|
+
require 'letter_opener'
|
80
|
+
ActionMailer::Base.add_delivery_method :letter_opener, LetterOpener::DeliveryMethod, :location => File.expand_path('../../../tmp/letter_opener', __FILE__)
|
81
|
+
ActionMailer::Base.delivery_method = :letter_opener
|
82
|
+
|
83
|
+
Reparty.config do |config|
|
84
|
+
config.add_weekly_report Reparty::Report::MixpanelFunnel, "Mixpanel Funnel", ENV["MIXPANEL_FUNNEL"], ENV["MIXPANEL_KEY"], ENV["MIXPANEL_SECRET"]
|
85
|
+
end
|
86
|
+
|
49
87
|
subject.invoke("test@test.com")
|
50
88
|
end
|
51
89
|
end
|
data/spec/vcr_helper.rb
CHANGED
@@ -10,4 +10,8 @@ VCR.configure do |c|
|
|
10
10
|
|
11
11
|
c.filter_sensitive_data("<SENDGRID_USER>") { ENV.fetch("SENDGRID_USER","admin@someplace.com") }
|
12
12
|
c.filter_sensitive_data("<SENDGRID_PASSWORD>") { ENV.fetch("SENDGRID_PASSWORD","asdfasdf") }
|
13
|
+
|
14
|
+
c.filter_sensitive_data("<MIXPANEL_FUNNEL>") { ENV.fetch("MIXPANEL_FUNNEL",123456) }
|
15
|
+
c.filter_sensitive_data("<MIXPANEL_KEY>") { ENV.fetch("MIXPANEL_KEY","abc123") }
|
16
|
+
c.filter_sensitive_data("<MIXPANEL_SECRET>") { ENV.fetch("MIXPANEL_SECRET","cba321") }
|
13
17
|
end
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: reparty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.5.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Tim Dorr
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: railties
|
@@ -123,6 +123,22 @@ dependencies:
|
|
123
123
|
version: '0'
|
124
124
|
none: false
|
125
125
|
prerelease: false
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: mixpanel_client
|
128
|
+
type: :runtime
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
none: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ! '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
none: false
|
141
|
+
prerelease: false
|
126
142
|
- !ruby/object:Gem::Dependency
|
127
143
|
name: bundler
|
128
144
|
type: :development
|
@@ -254,22 +270,27 @@ files:
|
|
254
270
|
- app/assets/images/spacer.gif
|
255
271
|
- app/mailers/report_mailer.rb
|
256
272
|
- app/views/report_mailer/_active_record.html.erb
|
273
|
+
- app/views/report_mailer/_mixpanel_funnel.html.erb
|
257
274
|
- app/views/report_mailer/_sendgrid.html.erb
|
258
275
|
- app/views/report_mailer/daily.html.erb
|
276
|
+
- app/views/report_mailer/weekly.html.erb
|
259
277
|
- lib/reparty.rb
|
260
278
|
- lib/reparty/config.rb
|
261
279
|
- lib/reparty/email.rb
|
262
280
|
- lib/reparty/engine.rb
|
263
281
|
- lib/reparty/report.rb
|
264
282
|
- lib/reparty/report/active_record.rb
|
283
|
+
- lib/reparty/report/mixpanel_funnel.rb
|
265
284
|
- lib/reparty/report/sendgrid.rb
|
266
285
|
- lib/reparty/version.rb
|
267
286
|
- lib/tasks/reparty.rake
|
268
287
|
- reparty.gemspec
|
288
|
+
- spec/cassettes/mixpanel.yml
|
269
289
|
- spec/cassettes/sendgrid.yml
|
270
290
|
- spec/email_spec.rb
|
271
291
|
- spec/reparty_spec.rb
|
272
292
|
- spec/report/active_record_spec.rb
|
293
|
+
- spec/report/mixpanel_funnel_spec.rb
|
273
294
|
- spec/report/sendgrid_spec.rb
|
274
295
|
- spec/report_spec.rb
|
275
296
|
- spec/spec_helper.rb
|
@@ -289,7 +310,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
289
310
|
- !ruby/object:Gem::Version
|
290
311
|
segments:
|
291
312
|
- 0
|
292
|
-
hash:
|
313
|
+
hash: 1358861647946779491
|
293
314
|
version: '0'
|
294
315
|
none: false
|
295
316
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
@@ -298,7 +319,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
298
319
|
- !ruby/object:Gem::Version
|
299
320
|
segments:
|
300
321
|
- 0
|
301
|
-
hash:
|
322
|
+
hash: 1358861647946779491
|
302
323
|
version: '0'
|
303
324
|
none: false
|
304
325
|
requirements: []
|
@@ -308,10 +329,12 @@ signing_key:
|
|
308
329
|
specification_version: 3
|
309
330
|
summary: Stupid easy business analytics
|
310
331
|
test_files:
|
332
|
+
- spec/cassettes/mixpanel.yml
|
311
333
|
- spec/cassettes/sendgrid.yml
|
312
334
|
- spec/email_spec.rb
|
313
335
|
- spec/reparty_spec.rb
|
314
336
|
- spec/report/active_record_spec.rb
|
337
|
+
- spec/report/mixpanel_funnel_spec.rb
|
315
338
|
- spec/report/sendgrid_spec.rb
|
316
339
|
- spec/report_spec.rb
|
317
340
|
- spec/spec_helper.rb
|