Skip to content

Commit

Permalink
Add Segment Assembler feature
Browse files Browse the repository at this point in the history
Segment Assembler combines SendGrid segments and optionally takes a
random sample of the segments.
  • Loading branch information
markbao committed May 24, 2018
1 parent d21c7b4 commit e500c88
Show file tree
Hide file tree
Showing 11 changed files with 619 additions and 0 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ gem "active_model_serializers", "~> 0.9.3"
gem 'bitmask_attributes', '~> 1.0'
gem 'bourbon', '~> 4'
gem 'neat', '~> 1'
gem 'chosen-rails'

gem 'markerb'
gem 'redcarpet', '~> 3.4'
Expand Down
12 changes: 12 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,19 @@ GEM
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (>= 2.0, < 4.0)
chosen-rails (1.8.3)
coffee-rails (>= 3.2)
railties (>= 3.0)
sass-rails (>= 3.2)
climate_control (0.2.0)
coderay (1.1.2)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
railties (>= 4.0.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
commonjs (0.2.7)
concurrent-ruby (1.0.5)
coveralls (0.8.21)
Expand Down Expand Up @@ -411,6 +422,7 @@ DEPENDENCIES
bourbon (~> 4)
cancancan (~> 2.1)
capybara
chosen-rails
coveralls
database_cleaner
delayed_job (~> 4.1)
Expand Down
40 changes: 40 additions & 0 deletions app/assets/javascripts/admin.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
function adminReady() {
// Attach refresh page handler
$('#segment-form-refresh-page').off('click').on('click', function(e) {
e.preventDefault();
location.reload();
});

// Attach refresh recipients handler
$('#segment-form-info-total-recipients-refresh').off('click').on('click', function(e) {
e.preventDefault();

// Collect segments
params = {
'segments[]': $('#segment_segments').val()
};

// Hide refresh, show spinner
$('#segment-form-info-total-recipients-refresh').hide();
$('#segment-form-info-total-recipients-spinner').show();

$.post('/admin/segment_count', params, function(data) {
// Hide spinner, show count
$('#segment-form-info-total-recipients-spinner').hide();
$('#segment-form-info-total-recipients-count')
.text(data)
.show();
});
});

// Attach chosen change handler
$('#segment_segments').chosen().change(function (e) {
// Hide count, show refresh
$('#segment-form-info-total-recipients-refresh').show();
$('#segment-form-info-total-recipients-count').hide();
})
}

// $(document).ready(adminReady)
// $(document).on('page:load', adminReady)
$(document).on('turbolinks:load', adminReady)
1 change: 1 addition & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//= require jquery_ujs
//= require turbolinks
//= require jquery-ui/dialog
//= require chosen-jquery
//= require_tree .

function viewportWidth() { return (window.innerWidth > 0) ? window.innerWidth : screen.width; }
Expand Down
2 changes: 2 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*
*= require normalize
*= require jquery-ui/dialog
*= require chosen
*= require_self
*/

Expand Down Expand Up @@ -48,6 +49,7 @@
@import "components/dashboard-tasks";
@import "components/notifications";
@import "components/ckeditor-override";
@import "components/admin";

// Block Components
@import "components/cover-photo";
Expand Down
252 changes: 252 additions & 0 deletions app/assets/stylesheets/components/_admin.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
.segment-help {
width: 600px;
margin-top: 20px;
font-size: 0.8em;
}

.segment-form-field {
margin-top: 30px;
border: 1px solid #E6E6E6;
border-radius: 3px;
background-color: #fff;
width: 600px;

.segment-form-field-header {
padding: 10px 20px;
border-bottom: 1px solid #E6E6E6;

.segment-form-field-name {
}

.segment-form-field-header-meta {
float: right;
a {
padding-left: 20px;
}
}
}

.segment-form-field-content {
input {
border: 0;
margin: 0;
padding: 20px;
}

.chosen-container, .chosen-container-multi {
.chosen-choices {
border: 0;
background: none;
padding: 10px 20px;

li.search-field input[type="text"] {
height: 35px;
font-size: 15px;
}

li.search-choice {
background-image: none;
background-color: #eee;
font-size: 15px;
padding: 8px 25px 8px 12px;
border: 1px solid #ccc;

.search-choice-close {
top: 9px;
transition: none;
}
}
}

.chosen-drop {
border: 0;
}

.chosen-results {
font-size: 15px;
}
}

.chosen-container-active {
border: 0;
box-shadow: none;

.chosen-choices {
box-shadow: none;
}
}
}
}

/*
Copyright (c) 2018 by Brent Miller (https://codepen.io/BrentWMiller/pen/XKGyBb)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

.segment-form-info {
display: flex;
position: relative;
flex-direction: row;
justify-content: space-between;
padding: 0;
width: 600px;
margin-top: 30px;

.segment-form-info-item:first-child {
margin-left: 0;
}
.segment-form-info-item:last-child {
margin-right: 0;
}

.segment-form-info-item {
position: relative;
width: 100%;
margin: 0 2px;
z-index: 2;
border-radius: 2px;

.segment-form-info-item-content {
position: relative;
margin: 0;
z-index: 2;
text-align: center;

&.segment-form-info-item-content-centered {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
}
}
}

.segment-form-info-item-wrap {
padding: 10px;
position: relative;
height: 100%;
&:before, &:after {
position: absolute;
left: 0;
content: ' ';
width: 100%;
height: 50.5%;
z-index: 1;
background-color: #F8D68F;
}

// Top of arrow
&:before {
top: 0;
transform: skew(20deg);
border-radius: .1em .1em 0 0;
}

// Bottom of arrow
&:after {
bottom: 0;
transform: skew(-20deg);
border-radius: 0 0 .1em .1em;
}
}

.segment-form-info-item:first-child .segment-form-info-item-wrap,
.segment-form-info-item:last-child .segment-form-info-item-wrap {
width: 100%;
border-radius: .2em;
&:before, &:after {
width: 50%;
}
}

.segment-form-info-item:first-child .segment-form-info-item-wrap {
background: linear-gradient(to right, #F7C661 95%, transparent 5%);
&:before, &:after {
background-color: #F7C661;
left: 50%;
}
}

.segment-form-info-item:last-child .segment-form-info-item-wrap {
background: linear-gradient(to left, #F9E6BC 95%, transparent 5%);
&:before, &:after {
background-color: #F9E6BC;
right: 50%;
}
}

.segment-form-info-total-recipients {
height: 40px;
#segment-form-info-total-recipients-count {
display: none;
font-size: 25px;
}

a#segment-form-info-total-recipients-refresh {
font-size: 25px;
padding: 10px;
}

#segment-form-info-total-recipients-spinner {
display: none;
}
}

.segment-form-info-sample {
height: 37px;
margin-top: 3px;

input {
padding: 2px;
font-size: 25px;
text-align: center;
margin: auto;
width: 100px;
}
}

.segment-form-info-content-subtitle {
color: #7C6331;
line-height: 1em;
margin-bottom: 8px;
}

.segment-form-info-submit {
input {
padding: 8px 12px;
border-radius: 3px;
width: auto;
margin: 0;
}
}
}

/*
https://stephanwagner.me/only-css-loading-spinner
MIT licensed
*/

@keyframes spinner {
to {transform: rotate(360deg);}
}

.spinner:before {
content: '';
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin-top: -20px;
margin-left: -10px;
border-radius: 50%;
border: 2px solid transparent;
border-top-color: #EF6236;
animation: spinner .6s linear infinite;
}
35 changes: 35 additions & 0 deletions app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,41 @@ def ghost
end
end

def segment
@segments = SendGridList.get_segments
end

def segment_create
name = params[:segment][:name]
segments = params[:segment][:segments]
sample = params[:segment][:sample]

if sample.blank?
sample = nil
else
sample = sample.delete(',').to_i
end

if name.blank?
name = 'List'
end

@list_id = SendGridList.create_list_from_segments(name, segments, sample)

if @list_id === false
flash[:error] = 'Error creating list. Did you already use that list name?'
redirect_to segment_path
else
flash[:success] = 'Successfully created list. Note: the SendGrid UI may not immediately display all contacts in the list.'
@list_url = 'https://sendgrid.com/marketing_campaigns/ui/lists/%d' % [@list_id]
end
end

def segment_count
segments = params[:segments]
render text: SendGridList.get_recipients(segments).length
end

private
def authorized?
unless current_user.admin?
Expand Down

0 comments on commit e500c88

Please sign in to comment.