mail_spy 0.1.0 → 0.1.1
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/app/assets/images/mail_spy/background_tile.gif +0 -0
- data/app/assets/images/mail_spy/mailspy_sprite.png +0 -0
- data/app/assets/images/mail_spy/spy.ico +0 -0
- data/app/assets/javascripts/mail_spy/lib/backbone.js +1256 -0
- data/app/assets/javascripts/mail_spy/lib/underscore.js +999 -0
- data/app/assets/javascripts/mail_spy/reports.js +2 -0
- data/app/assets/stylesheets/mail_spy/application.css +62 -0
- data/app/assets/stylesheets/mail_spy/reports.css.scss +128 -0
- data/app/controllers/mail_spy/reports_controller.rb +29 -0
- data/app/helpers/mail_spy/reports_helper.rb +4 -0
- data/app/models/mail_spy/email.rb +24 -2
- data/app/views/layouts/mail_spy/application.html.erb +3 -0
- data/app/views/mail_spy/reports/campaigns.html.erb +123 -33
- data/lib/mail_spy.rb +1 -0
- data/lib/mail_spy/manager.rb +12 -31
- data/lib/mail_spy/version.rb +1 -1
- data/test/dummy/db/schema.rb +16 -0
- data/test/dummy/log/test.log +443 -0
- data/test/functional/mail_spy/reports_controller_test.rb +9 -0
- data/test/unit/helpers/mail_spy/reports_helper_test.rb +6 -0
- metadata +67 -31
- data/app/assets/stylesheets/mail_spy/global.css.scss +0 -0
@@ -9,4 +9,66 @@
|
|
9
9
|
* compiled file, but it's generally better to create a new file per style scope.
|
10
10
|
*
|
11
11
|
*= require_self
|
12
|
+
*= require_tree
|
12
13
|
*/
|
14
|
+
|
15
|
+
/*************************************** Reset Styles */
|
16
|
+
|
17
|
+
html, body, div, span, applet, object, iframe,
|
18
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
19
|
+
a, abbr, acronym, address, big, cite, code,
|
20
|
+
del, dfn, em, img, ins, kbd, q, s, samp,
|
21
|
+
small, strike, strong, sub, sup, tt, var,
|
22
|
+
b, u, i, center,
|
23
|
+
dl, dt, dd, ol, ul, li,
|
24
|
+
fieldset, form, label, legend,
|
25
|
+
table, caption, tbody, tfoot, thead, tr, th, td,
|
26
|
+
article, aside, canvas, details, embed,
|
27
|
+
figure, figcaption, footer, header, hgroup,
|
28
|
+
menu, nav, output, ruby, section, summary,
|
29
|
+
time, mark, audio, video {
|
30
|
+
margin: 0;
|
31
|
+
padding: 0;
|
32
|
+
border: 0;
|
33
|
+
font-size: 100%;
|
34
|
+
font: inherit;
|
35
|
+
vertical-align: baseline;
|
36
|
+
}
|
37
|
+
/* HTML5 display-role reset for older browsers */
|
38
|
+
article, aside, details, figcaption, figure,
|
39
|
+
footer, header, hgroup, menu, nav, section {
|
40
|
+
display: block;
|
41
|
+
}
|
42
|
+
body {
|
43
|
+
line-height: 1;
|
44
|
+
}
|
45
|
+
ol, ul {
|
46
|
+
list-style: none;
|
47
|
+
}
|
48
|
+
blockquote, q {
|
49
|
+
quotes: none;
|
50
|
+
}
|
51
|
+
blockquote:before, blockquote:after,
|
52
|
+
q:before, q:after {
|
53
|
+
content: '';
|
54
|
+
content: none;
|
55
|
+
}
|
56
|
+
table {
|
57
|
+
border-collapse: collapse;
|
58
|
+
border-spacing: 0;
|
59
|
+
}
|
60
|
+
|
61
|
+
/*************************************** End Reset */
|
62
|
+
/*************************************** New Global Styles */
|
63
|
+
|
64
|
+
body{
|
65
|
+
font-family: 'Spinnaker', sans-serif;
|
66
|
+
color:#fff;
|
67
|
+
background-image: url(background_tile.gif);
|
68
|
+
}
|
69
|
+
h1{
|
70
|
+
font-size:24px;
|
71
|
+
}
|
72
|
+
.Deemph{
|
73
|
+
font-size:.72em;
|
74
|
+
}
|
@@ -0,0 +1,128 @@
|
|
1
|
+
/*
|
2
|
+
Place all the styles related to the matching controller here.
|
3
|
+
They will automatically be included in application.css.
|
4
|
+
*/
|
5
|
+
$colorHighlight:#80ff7a;
|
6
|
+
$colorMedlight:#214d30;
|
7
|
+
$colorSemiLowlight:#c3c3c3;
|
8
|
+
$colorLowlight:#3e4041;
|
9
|
+
$colorHighlightBad:#62222a;
|
10
|
+
$colorMedlightBad:#663137;
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
._active{
|
15
|
+
color:$colorHighlight;
|
16
|
+
}
|
17
|
+
|
18
|
+
/**************************** Sprites for mailspy_sprite.png */
|
19
|
+
.MsSprite{
|
20
|
+
background-image:url(mailspy_sprite.png);
|
21
|
+
background-repeat:no-repeat;
|
22
|
+
display:inline-block;
|
23
|
+
*display:inline; zoom:1;
|
24
|
+
vertical-align:middle;
|
25
|
+
text-indent:-9999px;
|
26
|
+
|
27
|
+
&.Logo{
|
28
|
+
background-position:0 0;
|
29
|
+
width:180px;
|
30
|
+
height:50px;
|
31
|
+
}
|
32
|
+
|
33
|
+
&.Nav{
|
34
|
+
width: 27px;
|
35
|
+
height: 34px;
|
36
|
+
background-position-x:-50px;
|
37
|
+
|
38
|
+
&.Campaign{
|
39
|
+
background-position-y:-100px;
|
40
|
+
}
|
41
|
+
&.History{
|
42
|
+
background-position-y:-150px;
|
43
|
+
}
|
44
|
+
&.Template{
|
45
|
+
background-position-y:-200px;
|
46
|
+
}
|
47
|
+
&.Menu{
|
48
|
+
background-position-y:-250px;
|
49
|
+
}
|
50
|
+
|
51
|
+
&._active{
|
52
|
+
background-position-x:0;
|
53
|
+
}
|
54
|
+
&:hover{
|
55
|
+
background-position-x:-100px;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
&.People{
|
60
|
+
background-position: -200px 0px;
|
61
|
+
height:60px;
|
62
|
+
width:80px;
|
63
|
+
}
|
64
|
+
&.Mail{
|
65
|
+
background-position: -200px -100px;
|
66
|
+
height:60px;
|
67
|
+
width:80px;
|
68
|
+
}
|
69
|
+
&.Pointer{
|
70
|
+
background-position: -200px -200px;
|
71
|
+
height:60px;
|
72
|
+
width:80px;
|
73
|
+
}
|
74
|
+
&.Money{
|
75
|
+
background-position: -200px -300px;
|
76
|
+
height:60px;
|
77
|
+
width:80px;
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
/**************************** End mailspy_sprite.png*/
|
82
|
+
/**************************** Report Components */
|
83
|
+
.KpiContainer{
|
84
|
+
margin:1em;
|
85
|
+
}
|
86
|
+
.Kpi{
|
87
|
+
display:inline-block;
|
88
|
+
*display:inline; zoom:1;
|
89
|
+
width:200px;
|
90
|
+
text-align:center;
|
91
|
+
|
92
|
+
.Icon{
|
93
|
+
display:block;
|
94
|
+
margin:8px auto;
|
95
|
+
}
|
96
|
+
kpi{
|
97
|
+
color:$colorMedlight;
|
98
|
+
font-size:1.2em;
|
99
|
+
margin:.2em;
|
100
|
+
}
|
101
|
+
figure{
|
102
|
+
color:$colorHighlight;
|
103
|
+
font-size:1.8em;
|
104
|
+
margin:.2em;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
.CampaignContainer{
|
109
|
+
background-color:rgba(0,0,0,.4);
|
110
|
+
padding:.2em 1em 1em 1em;
|
111
|
+
margin:2em 0;
|
112
|
+
}
|
113
|
+
|
114
|
+
.FunnelContainer{
|
115
|
+
padding:.2em 1em 1em 1em;
|
116
|
+
margin:2em 0;
|
117
|
+
thead{
|
118
|
+
td{
|
119
|
+
color:$colorLowlight;
|
120
|
+
text-align:left;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
td{
|
124
|
+
padding:.5em 2em;
|
125
|
+
color:$colorHighlight;
|
126
|
+
text-align:left;
|
127
|
+
}
|
128
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class ReportsController < ApplicationController
|
3
|
+
|
4
|
+
def campaigns
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
def emails
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def email
|
13
|
+
@email = MailSpy.create_email({
|
14
|
+
:campaign => "2012-valentines",
|
15
|
+
:stream => "a-yikes",
|
16
|
+
:component => "a",
|
17
|
+
:schedule_at => Date.today()+1, # Should never be used
|
18
|
+
:subject => "Just a test",
|
19
|
+
:template_values => {},
|
20
|
+
:from => "EMAIL PREVIEW",
|
21
|
+
:reply_to => "NOREPLY",
|
22
|
+
:to => "nowhere"
|
23
|
+
})
|
24
|
+
@email[:cache_bust] = true
|
25
|
+
render :text=>@email.parsed_html_content, :html_safe => true
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -68,13 +68,35 @@ module MailSpy
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def html_erb
|
71
|
-
|
71
|
+
load_template("html.erb")
|
72
72
|
end
|
73
73
|
|
74
74
|
def text_erb
|
75
75
|
load_template("text.erb")
|
76
76
|
end
|
77
77
|
|
78
|
+
def self.deliver_batch(current_time, start_id, offset, total, step=200, num_threads=100)
|
79
|
+
wq = WorkQueue.new(num_threads, step*2)
|
80
|
+
processed = 0
|
81
|
+
while true
|
82
|
+
emails = self.where(
|
83
|
+
:schedule_at.lte => current_time,
|
84
|
+
:sent => false,
|
85
|
+
:failed => false,
|
86
|
+
:_id.gt => start_id
|
87
|
+
).limit(step).offset(offset)
|
88
|
+
|
89
|
+
break if total >= processed || emails.blank?
|
90
|
+
emails.each do |email|
|
91
|
+
wq.enqueue_b do
|
92
|
+
email.deliver
|
93
|
+
end
|
94
|
+
end
|
95
|
+
processed += emails.count
|
96
|
+
end
|
97
|
+
wq.join
|
98
|
+
processed
|
99
|
+
end
|
78
100
|
|
79
101
|
def deliver
|
80
102
|
begin
|
@@ -97,7 +119,7 @@ module MailSpy
|
|
97
119
|
|
98
120
|
# Check the cache using our convention
|
99
121
|
path = "#{self.campaign}/#{self.stream}/#{self.component}.#{suffix}"
|
100
|
-
return @@template_cache[path] if @@template_cache[path].present?
|
122
|
+
return @@template_cache[path] if @@template_cache[path].present? && !self[:cache_bust]
|
101
123
|
|
102
124
|
# Load the object from s3
|
103
125
|
s3 = AWS::S3.new(:access_key_id => MailSpy.aws_access_key_id,
|
@@ -2,8 +2,11 @@
|
|
2
2
|
<html>
|
3
3
|
<head>
|
4
4
|
<title>MailSpy</title>
|
5
|
+
<link rel="icon" href="/assets/mail_spy/spy.ico" type="image/x-icon">
|
6
|
+
<link rel="shortcut icon" href="/assets/mail_spy/spy.ico" type="image/x-icon">
|
5
7
|
<%= stylesheet_link_tag "mail_spy/application", :media => "all" %>
|
6
8
|
<%= javascript_include_tag "mail_spy/application" %>
|
9
|
+
<link href='http://fonts.googleapis.com/css?family=Spinnaker' rel='stylesheet' type='text/css'>
|
7
10
|
<%= csrf_meta_tags %>
|
8
11
|
</head>
|
9
12
|
<body>
|
@@ -1,48 +1,138 @@
|
|
1
1
|
<% @campaigns = [{
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
:name=>"Valentine's Day",
|
3
|
+
:first_sent => Date.today()-14,
|
4
|
+
:last_sent => Date.today(),
|
5
|
+
:kpis=>{
|
6
|
+
"Population"=> {
|
7
|
+
:icon=>"People",
|
8
|
+
:figure=>1000000
|
9
|
+
},
|
10
|
+
"Emails Sent"=>{
|
11
|
+
:icon=>"Mail",
|
12
|
+
:figure=>66478
|
13
|
+
},
|
14
|
+
"Click-Throughs"=>{
|
15
|
+
:icon=>"Pointer",
|
16
|
+
:figure=>1549
|
17
|
+
},
|
18
|
+
"Total Purchases"=>{
|
19
|
+
:icon=>"Money",
|
20
|
+
:figure=>1214
|
21
|
+
}
|
22
|
+
},
|
23
|
+
:purchase_funnel=>{
|
24
|
+
"delivered"=>66478,
|
25
|
+
"opened"=>7643,
|
26
|
+
"clicked"=>1549,
|
27
|
+
"purchased"=>5
|
28
|
+
}
|
29
|
+
|
5
30
|
},{
|
6
|
-
:name=>"Mother's Day"
|
31
|
+
:name=>"Mother's Day",
|
32
|
+
:last_sent => Date.today(),
|
33
|
+
:sent=>1221,
|
34
|
+
:kpis=>{},
|
35
|
+
:purchase_funnel=>{}
|
7
36
|
}] %>
|
8
|
-
|
9
|
-
<%# ------------------------------------- Backbone Templates %>
|
37
|
+
|
10
38
|
<script type="text/javascript">
|
39
|
+
$(function(){
|
40
|
+
|
41
|
+
getValue = function(_o){
|
42
|
+
try{
|
43
|
+
if(typeof(_o) === 'number' ||
|
44
|
+
typeof(parseInt(_o,10) === 'number')){
|
45
|
+
return _o;
|
46
|
+
} else if (_o.r && _o.c){
|
11
47
|
|
12
|
-
|
13
|
-
|
14
|
-
|
48
|
+
}
|
49
|
+
} catch(e){
|
50
|
+
console.log("Can't evaluate:");
|
51
|
+
console.log(_o);
|
52
|
+
console.log(e);
|
53
|
+
}
|
54
|
+
};
|
55
|
+
|
56
|
+
$.each($("[data-operator]"),function(i,e){
|
57
|
+
var lhs, rhs,
|
58
|
+
result = "NA",
|
59
|
+
op = $(this).attr("data-operator"),
|
60
|
+
ops = $(this).attr("data-operands");
|
61
|
+
|
62
|
+
try{
|
63
|
+
ops = ops.split("|");
|
64
|
+
lhs = $.parseJSON(ops[0]);
|
65
|
+
rhs = $.parseJSON(ops[1]);
|
66
|
+
console.log(lhs + " : " + rhs);
|
67
|
+
|
68
|
+
//Switch operators
|
69
|
+
if(op == "+"){
|
70
|
+
result = getValue(lhs) + getValue(rhs);
|
71
|
+
} else if(op == "%"){
|
72
|
+
result = (lhs + "/" + rhs);
|
15
73
|
}
|
16
|
-
})
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
var search_view = new SearchView({ el: $("#search_container") });
|
30
|
-
</script>
|
31
|
-
<%# ------------------------------------- Backbone Templates %>
|
32
|
-
<script type="text/template" id="campaign_template">
|
33
|
-
<div class="Campaign">
|
34
|
-
<h2><%= c[:name] %></h2>
|
35
|
-
</div>
|
74
|
+
} catch(e) {
|
75
|
+
console.log(e);
|
76
|
+
}
|
77
|
+
|
78
|
+
$(this).html(result);
|
79
|
+
});
|
80
|
+
|
81
|
+
|
82
|
+
$.each($("[data-graph]"),function(i,e){
|
83
|
+
$(this).html($(this).attr("data-value"));
|
84
|
+
});
|
85
|
+
|
86
|
+
});
|
36
87
|
</script>
|
88
|
+
|
37
89
|
<%# ------------------------------------- Start actual view %>
|
38
90
|
|
39
|
-
<
|
91
|
+
<div class="MsSprite Logo">Mail Spy</div>
|
92
|
+
|
93
|
+
<h1 class="_active">
|
94
|
+
<div class="MsSprite Nav Campaign _active"></div> All Campaigns
|
95
|
+
</h1>
|
40
96
|
|
41
97
|
<div class="CampaignReport">
|
42
98
|
<%# For each campaign %>
|
43
99
|
<% @campaigns.each do |c| %>
|
44
|
-
|
45
|
-
|
46
|
-
|
100
|
+
<div class="CampaignContainer">
|
101
|
+
<h1 ><%= c[:name] %></h1>
|
102
|
+
|
103
|
+
<div class="KpiContainer">
|
104
|
+
<% c[:kpis].each do |kpi, kpi_data|%>
|
105
|
+
<div class="Kpi">
|
106
|
+
<div class="MsSprite <%= kpi_data[:icon] %> Icon"></div>
|
107
|
+
<kpi><%= kpi %></kpi>
|
108
|
+
<figure><%= kpi_data[:figure] %></figure>
|
109
|
+
</div>
|
110
|
+
<% end %>
|
111
|
+
</div>
|
112
|
+
<div class="FunnelContainer">
|
113
|
+
<h2>
|
114
|
+
Purchase Funnel
|
115
|
+
</h2>
|
116
|
+
<table>
|
117
|
+
<thead>
|
118
|
+
<tr>
|
119
|
+
<td>Action</td>
|
120
|
+
<td>Raw</td>
|
121
|
+
<td>%</td>
|
122
|
+
<td></td>
|
123
|
+
</tr>
|
124
|
+
</thead>
|
125
|
+
<% c[:purchase_funnel].each do |step, step_data|%>
|
126
|
+
<tr>
|
127
|
+
<td><%= step %></td>
|
128
|
+
<td><%=step_data%></funnelCell>
|
129
|
+
<td data-operator="%" data-operands='{"r":-0,"c":-1}|{"r":-1,"c":-1}'></td>
|
130
|
+
<td data-operator="+" data-operands='6|9'></td>
|
131
|
+
<td data-graph="bar" data-value="<%=step_data%>"></td>
|
132
|
+
</tr>
|
133
|
+
<% end %>
|
134
|
+
</table>
|
135
|
+
</div>
|
136
|
+
</div>
|
47
137
|
<% end %>
|
48
138
|
</div>
|