reparty 0.3.2 → 0.5.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.
- 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
|