Add HTML variants for cta optimization and other nav bar adjustments (#970)
* Add HTML variants for cta optimization and other nav bar adjustments * Fix 1==1 non-random mistake * Spruce up org call-to-action * Change default text color * Add html variant trial and success request specs * Fix article sidebar caching issue * One line adjustment * Modify schema * Add include ActionView::Helpers::TagHelper to user_tag_spec * Modify follow_button to remove session context * Dummy commit * Change Edit Article to Edit Post * Dummy commit * Fix rubocop concerns * Fix rubocop style
This commit is contained in:
parent
7399e022da
commit
302e8f202d
46 changed files with 924 additions and 131 deletions
|
|
@ -65,7 +65,7 @@ guard :rspec, rspec_options do
|
|||
[
|
||||
rspec.spec.call("routing/#{m[1]}_routing"),
|
||||
rspec.spec.call("controllers/#{m[1]}_controller"),
|
||||
rspec.spec.call("acceptance/#{m[1]}"),
|
||||
rspec.spec.call("acceptance/#{m[1]}")
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ function initializeBaseTracking() {
|
|||
wait++;
|
||||
if (window.ga && ga.create) {
|
||||
ga('create', '<%= ApplicationConfig["GA_TRACKING_ID"] %>', 'auto');
|
||||
if (!checkUserLoggedIn() && Math.random() < 0.35) {
|
||||
ga('require', '<%= ApplicationConfig["GA_OPTIMIZE_ID"] %>');
|
||||
}
|
||||
ga('send', 'pageview', location.pathname + location.search);
|
||||
if (document.location.search.indexOf('newly-registered-user=true') > -1) {
|
||||
ga('send', 'event', 'registration', 'New user registration', location.pathname, null);
|
||||
|
|
@ -29,6 +26,7 @@ function initializeBaseTracking() {
|
|||
}, 25);
|
||||
facebookTrackingScript();
|
||||
eventListening();
|
||||
trackCustomImpressions();
|
||||
}
|
||||
|
||||
function fallbackActivityRecording() {
|
||||
|
|
@ -107,4 +105,50 @@ function logImpressions() {
|
|||
var data = el.dataset.featuredArticle;
|
||||
ga('send', 'event', 'view', 'featured-feed-impression', data, null);
|
||||
}
|
||||
}
|
||||
|
||||
function trackCustomImpressions() {
|
||||
setTimeout(function(){
|
||||
var stickyNav = document.getElementById('article-show-primary-sticky-nav');
|
||||
var sidebarHTMLVariant = document.getElementById('html-variant-article-show-sidebar');
|
||||
var ArticleElement = document.getElementById('article-body');
|
||||
var tokenMeta = document.querySelector("meta[name='csrf-token']")
|
||||
var randomNumber = Math.floor(Math.random() * 10); // 1 in 10; Only track 1 in 10 impressions
|
||||
var isBot = /bot|google|baidu|bing|msn|duckduckbot|teoma|slurp|yandex/i.test(navigator.userAgent) // is crawler
|
||||
if (sidebarHTMLVariant && ArticleElement && tokenMeta && !isBot) {
|
||||
var dataBody = {
|
||||
html_variant_id: sidebarHTMLVariant.dataset.variantId,
|
||||
article_id: ArticleElement.dataset.articleId,
|
||||
};
|
||||
var csrfToken = tokenMeta.getAttribute('content');
|
||||
if (randomNumber === 1) {
|
||||
window.fetch('/html_variant_trials', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(dataBody),
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
}
|
||||
var successLinks = stickyNav.querySelectorAll('a,button'); //track all links and button clicks within nav
|
||||
for(var i = 0; i < successLinks.length; i++)
|
||||
{
|
||||
successLinks[i].addEventListener('click', function() { trackHtmlVariantSuccess(dataBody, csrfToken) });
|
||||
}
|
||||
}
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
function trackHtmlVariantSuccess(dataBody, csrfToken) {
|
||||
window.fetch('/html_variant_successes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(dataBody),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
}
|
||||
|
|
@ -663,7 +663,7 @@ header{
|
|||
button{
|
||||
background:transparent;
|
||||
margin: 0px 0.22vw;
|
||||
padding:calc(0.05vw + 3px) calc(0.88vw + 3px);
|
||||
padding: 3px calc(0.88vw + 3px);
|
||||
border-radius:100px;
|
||||
border:2px solid darken($light-gray, 7%);
|
||||
font-family: $helvetica-condensed;
|
||||
|
|
@ -702,7 +702,6 @@ header{
|
|||
}
|
||||
&.user-activated {
|
||||
border:2px solid darken($bold-blue, 40%);
|
||||
padding:calc(0.05vw + 3px) calc(0.88vw + 3px);
|
||||
background: linear-gradient(60deg, #f4d2d2, #efb6b6);
|
||||
img{
|
||||
filter: none;
|
||||
|
|
@ -1004,13 +1003,15 @@ header{
|
|||
|
||||
@keyframes reaction {
|
||||
0% {
|
||||
padding:calc(0.05vw + 3px) calc(0.88vw + 3px);
|
||||
padding-left: calc(0.88vw + 3px);
|
||||
padding-right: calc(0.88vw + 3px);
|
||||
}
|
||||
50% {
|
||||
padding:calc(0.05vw + 3px) calc(1.1vw + 3px);
|
||||
margin: 0px 0.00vw;
|
||||
padding-left: calc(1.1vw + 3px);
|
||||
padding-left: calc(1.1vw + 3px);
|
||||
}
|
||||
100% {
|
||||
padding:calc(0.05vw + 3px) calc(0.88vw + 3px);
|
||||
padding-left: calc(0.88vw + 3px);
|
||||
padding-right: calc(0.88vw + 3px);
|
||||
}
|
||||
}
|
||||
|
|
@ -108,7 +108,6 @@
|
|||
}
|
||||
}
|
||||
label {
|
||||
width: 160px;
|
||||
display:inline-block;
|
||||
vertical-align:top;
|
||||
padding:14px 0px 8px;
|
||||
|
|
|
|||
|
|
@ -39,13 +39,12 @@
|
|||
.primary-sticky-nav-author-follow{
|
||||
padding-top:8px;
|
||||
button{
|
||||
width:136px;
|
||||
font-size:1.1em;
|
||||
width:100%;
|
||||
font-size:1.25em;
|
||||
border:0px;
|
||||
border-radius:3px;
|
||||
padding: 3px;
|
||||
height: 29px;
|
||||
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,38 +111,67 @@
|
|||
box-shadow: 3px 4px 0px darken($light-medium-gray, 17%);
|
||||
}
|
||||
}
|
||||
.user-metadata-details {
|
||||
margin-top: 10px;
|
||||
margin-left: 2px;
|
||||
font-family: $monospace;
|
||||
.row{
|
||||
padding: calc(5px + 0.2vw) 0px;
|
||||
display: inline-block;
|
||||
width: 96%;
|
||||
}
|
||||
.key{
|
||||
font-size: 0.8em;
|
||||
font-weight: 800;
|
||||
margin-bottom: 2px;
|
||||
color: $medium-gray;
|
||||
}
|
||||
.value{
|
||||
display: inline-block;
|
||||
font-size: 0.92em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
.html-variant-wrapper{
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.primary-sticky-nav-org{
|
||||
position:relative;
|
||||
.sticky-box-connector{
|
||||
position:absolute;
|
||||
top: - 10px;
|
||||
height: 10px;
|
||||
width: 5px;
|
||||
background: $outline-color;
|
||||
left: 15px;
|
||||
&.sticky-box-right-connector{
|
||||
left: auto;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.primary-sticky-nav-org-summary{
|
||||
font-weight: 400;
|
||||
color: $medium-gray;
|
||||
padding: 0px 0px 5px;
|
||||
font-size:0.88em;
|
||||
color: $black;
|
||||
padding: 4px 0px;
|
||||
font-size:0.95em;
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid $light-medium-gray;
|
||||
p{
|
||||
margin:5px 0px;
|
||||
margin:12px 0px;
|
||||
}
|
||||
a.primary-sticky-nav-org-cta-link{
|
||||
width:100%;
|
||||
display: block;
|
||||
font-size:1.25em;
|
||||
border:0px;
|
||||
border-radius:3px;
|
||||
padding: 8px 0px 3px;
|
||||
margin-top: 20px;
|
||||
background: white;
|
||||
color: $black;
|
||||
font-family: $helvetica-condensed;
|
||||
border: 1px solid darken($light-medium-gray, 7%);
|
||||
text-align: center;
|
||||
.primary-sticky-nav-org-cta-link-domain-small{
|
||||
display: block;
|
||||
padding-top: 2px;
|
||||
font-size:0.8em;
|
||||
color: darken($light-medium-gray, 4%);
|
||||
font-family: $monospace;
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid darken($light-medium-gray, 12%);
|
||||
box-shadow: $shadow;
|
||||
transition: all 0.1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
button{
|
||||
min-width:135px;
|
||||
font-size:1.1em;
|
||||
border:0px;
|
||||
border-radius:3px;
|
||||
padding: 3px;
|
||||
display: block;
|
||||
margin-top:18px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,12 +61,10 @@ class GoogleAnalytics
|
|||
metrics: [Metric.new(expression: metrics_string)],
|
||||
dimensions: [Dimension.new(name: "ga:segment")],
|
||||
segments: [Segment.new(segment_id: "gaid::-1"),
|
||||
Segment.new(segment_id: "gaid::-2"),
|
||||
Segment.new(segment_id: "gaid::-19"),
|
||||
Segment.new(segment_id: "gaid::-7")],
|
||||
date_ranges: [
|
||||
DateRange.new(start_date: @start_date, end_date: "2020-01-01"),
|
||||
],
|
||||
Segment.new(segment_id: "gaid::-2"),
|
||||
Segment.new(segment_id: "gaid::-19"),
|
||||
Segment.new(segment_id: "gaid::-7")],
|
||||
date_ranges: [DateRange.new(start_date: @start_date, end_date: "2020-01-01")],
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
21
app/controllers/admin/html_variants_controller.rb
Normal file
21
app/controllers/admin/html_variants_controller.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
module Admin
|
||||
class HtmlVariantsController < Admin::ApplicationController
|
||||
# To customize the behavior of this controller,
|
||||
# you can overwrite any of the RESTful actions. For example:
|
||||
#
|
||||
# def index
|
||||
# super
|
||||
# @resources = HtmlVariant.
|
||||
# page(params[:page]).
|
||||
# per(10)
|
||||
# end
|
||||
|
||||
# Define a custom finder by overriding the `find_resource` method:
|
||||
# def find_resource(param)
|
||||
# HtmlVariant.find_by!(slug: param)
|
||||
# end
|
||||
|
||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
6
app/controllers/html_variant_successes_controller.rb
Normal file
6
app/controllers/html_variant_successes_controller.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
class HtmlVariantSuccessesController < ApplicationController
|
||||
def create
|
||||
HtmlVariantSuccess.delay.create(html_variant_id: params[:html_variant_id], article_id: params[:article_id])
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
6
app/controllers/html_variant_trials_controller.rb
Normal file
6
app/controllers/html_variant_trials_controller.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
class HtmlVariantTrialsController < ApplicationController
|
||||
def create
|
||||
HtmlVariantTrial.delay.create(html_variant_id: params[:html_variant_id], article_id: params[:article_id])
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
53
app/controllers/html_variants_controller.rb
Normal file
53
app/controllers/html_variants_controller.rb
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
class HtmlVariantsController < ApplicationController
|
||||
after_action :verify_authorized
|
||||
|
||||
def index
|
||||
authorize HtmlVariant
|
||||
@user_variants = current_user.html_variants.order("created_at DESC")
|
||||
@all_published_variants = HtmlVariant.where(published: true).order("created_at DESC")
|
||||
@preview_path = Article.where(featured: true, published: true).order("published_at DESC").first&.path.to_s
|
||||
end
|
||||
|
||||
def new
|
||||
authorize HtmlVariant
|
||||
@html_variant = HtmlVariant.new
|
||||
if params[:fork_id]
|
||||
@fork = HtmlVariant.find(params[:fork_id])
|
||||
@html_variant.name = @fork.name + " FORK"
|
||||
@html_variant.html = @fork.html
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@html_variant = HtmlVariant.find(params[:id])
|
||||
authorize @html_variant
|
||||
end
|
||||
|
||||
def create
|
||||
authorize HtmlVariant
|
||||
@html_variant = HtmlVariant.new(html_variant_params)
|
||||
@html_variant.group = "article_show_sidebar_cta"
|
||||
@html_variant.user_id = current_user.id
|
||||
if @html_variant.save
|
||||
redirect_to "/html_variants"
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@html_variant = HtmlVariant.find(params[:id])
|
||||
authorize @html_variant
|
||||
if @html_variant.update(html_variant_params)
|
||||
redirect_to "/html_variants"
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def html_variant_params
|
||||
params.require(:html_variant).permit(policy(HtmlVariant).permitted_attributes)
|
||||
end
|
||||
end
|
||||
|
|
@ -25,6 +25,7 @@ class DashboardManifest
|
|||
display_ads
|
||||
badges
|
||||
badge_achievements
|
||||
html_variants
|
||||
].freeze
|
||||
# DASHBOARDS = [
|
||||
# :users,
|
||||
|
|
|
|||
78
app/dashboards/html_variant_dashboard.rb
Normal file
78
app/dashboards/html_variant_dashboard.rb
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class HtmlVariantDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
user: Field::BelongsTo,
|
||||
html_variant_trials: Field::HasMany,
|
||||
html_variant_successes: Field::HasMany,
|
||||
id: Field::Number,
|
||||
group: Field::String,
|
||||
name: Field::String,
|
||||
html: Field::Text,
|
||||
target_tag: Field::String,
|
||||
success_rate: Field::Number.with_options(decimals: 2),
|
||||
published: Field::Boolean,
|
||||
approved: Field::Boolean,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = [
|
||||
:user,
|
||||
:html_variant_trials,
|
||||
:html_variant_successes,
|
||||
:id,
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = [
|
||||
:user,
|
||||
:html_variant_trials,
|
||||
:html_variant_successes,
|
||||
:id,
|
||||
:group,
|
||||
:name,
|
||||
:html,
|
||||
:target_tag,
|
||||
:success_rate,
|
||||
:published,
|
||||
:approved,
|
||||
:created_at,
|
||||
:updated_at,
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = [
|
||||
:user,
|
||||
:html_variant_trials,
|
||||
:html_variant_successes,
|
||||
:group,
|
||||
:name,
|
||||
:html,
|
||||
:target_tag,
|
||||
:success_rate,
|
||||
:published,
|
||||
:approved,
|
||||
].freeze
|
||||
|
||||
# Overwrite this method to customize how html variants are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(html_variant)
|
||||
# "HtmlVariant ##{html_variant.id}"
|
||||
# end
|
||||
end
|
||||
|
|
@ -44,8 +44,8 @@ class UserDecorator < ApplicationDecorator
|
|||
text: "#ffffff"
|
||||
},
|
||||
{
|
||||
bg: "#edebf6",
|
||||
text: " #070126"
|
||||
bg: "#2e0338",
|
||||
text: " #ffffff"
|
||||
},
|
||||
{
|
||||
bg: "#080E3B",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ module ApplicationHelper
|
|||
comments
|
||||
notifications
|
||||
reading_list_items
|
||||
html_variants
|
||||
).include?(controller_name)
|
||||
end
|
||||
|
||||
|
|
@ -153,7 +154,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def follow_button(followable, style = "full")
|
||||
tag :button,
|
||||
tag :button, #Yikes
|
||||
class: "cta follow-action-button",
|
||||
data: {
|
||||
info: { id: followable.id, className: followable.class.name, style: style }.to_json,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
class UserTag < LiquidTagBase
|
||||
include ApplicationHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
attr_reader :user
|
||||
|
||||
def initialize(_tag_name, user, _tokens)
|
||||
|
|
|
|||
40
app/models/html_variant.rb
Normal file
40
app/models/html_variant.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
class HtmlVariant < ApplicationRecord
|
||||
validates :html, presence: true
|
||||
validates :name, uniqueness: true
|
||||
validates :group, inclusion: { in: %w(article_show_sidebar_cta) }
|
||||
validates :success_rate, presence: true
|
||||
validate :no_edits
|
||||
belongs_to :user, optional: true
|
||||
has_many :html_variant_trials
|
||||
has_many :html_variant_successes
|
||||
|
||||
def calculate_success_rate!
|
||||
self.success_rate = html_variant_successes.size.to_f / (html_variant_trials.size * 10.0) # x10 because we only capture every 10th
|
||||
save!
|
||||
end
|
||||
|
||||
def self.find_for_test(tags = [])
|
||||
tags_array = tags + ["", nil]
|
||||
if rand(10) == 1 # 10% return completely random
|
||||
find_random_for_test(tags_array)
|
||||
else # 90% chance return one in top 10
|
||||
find_top_for_test(tags_array)
|
||||
end
|
||||
end
|
||||
|
||||
def self.find_top_for_test(tags_array)
|
||||
where(group: "article_show_sidebar_cta", approved: true, published: true, target_tag: tags_array).order("success_rate DESC").limit(rand(1..10)).sample
|
||||
end
|
||||
|
||||
def self.find_random_for_test(tags_array)
|
||||
where(group: "article_show_sidebar_cta", approved: true, published: true, target_tag: tags_array).order("RANDOM()").first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def no_edits
|
||||
if (approved && html_changed? || name_changed? || group_changed?) && persisted?
|
||||
errors.add(:base, "cannot change once published and approved")
|
||||
end
|
||||
end
|
||||
end
|
||||
5
app/models/html_variant_success.rb
Normal file
5
app/models/html_variant_success.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class HtmlVariantSuccess < ApplicationRecord
|
||||
validates :html_variant_id, presence: true
|
||||
belongs_to :html_variant
|
||||
belongs_to :article, optional: true
|
||||
end
|
||||
5
app/models/html_variant_trial.rb
Normal file
5
app/models/html_variant_trial.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class HtmlVariantTrial < ApplicationRecord
|
||||
validates :html_variant_id, presence: true
|
||||
belongs_to :html_variant
|
||||
belongs_to :article, optional: true
|
||||
end
|
||||
|
|
@ -36,7 +36,7 @@ class Organization < ApplicationRecord
|
|||
validates :cta_button_url,
|
||||
url: { allow_blank: true, no_local: true, schemes: ["https", "http"] }, if: :approved
|
||||
validates :cta_button_text, length: { maximum: 12 }
|
||||
validates :cta_body_markdown, length: { maximum: 140 }
|
||||
validates :cta_body_markdown, length: { maximum: 256 }
|
||||
before_save :remove_at_from_usernames
|
||||
after_save :bust_cache
|
||||
before_save :generate_secret
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class User < ApplicationRecord
|
|||
has_many :chat_channels, through: :chat_channel_memberships
|
||||
has_many :push_notification_subscriptions, dependent: :destroy
|
||||
has_many :feedback_messages
|
||||
has_many :html_variants, dependent: :destroy
|
||||
has_many :mentor_relationships_as_mentee,
|
||||
class_name: "MentorRelationship", foreign_key: "mentee_id"
|
||||
has_many :mentor_relationships_as_mentor,
|
||||
|
|
|
|||
33
app/policies/html_variant_policy.rb
Normal file
33
app/policies/html_variant_policy.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
class HtmlVariantPolicy < ApplicationPolicy
|
||||
def index?
|
||||
user_admin?
|
||||
end
|
||||
|
||||
def show?
|
||||
user_admin?
|
||||
end
|
||||
|
||||
def edit?
|
||||
user_admin?
|
||||
end
|
||||
|
||||
def update?
|
||||
user_admin?
|
||||
end
|
||||
|
||||
def new?
|
||||
user_admin?
|
||||
end
|
||||
|
||||
def create?
|
||||
user_admin?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user_admin?
|
||||
end
|
||||
|
||||
def permitted_attributes
|
||||
%i[html name published approved target_tag]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
<% @sticky_collection = StickyArticleCollection.new(@article, @organization || @user) %>
|
||||
<% @sticky_articles = @sticky_collection.suggested_stickies %>
|
||||
<% @user_stickies = @sticky_collection.user_stickies %>
|
||||
<% if user_signed_in? %>
|
||||
<% @sticky_collection = StickyArticleCollection.new(@article, @organization || @user) %>
|
||||
<% @sticky_articles = @sticky_collection.suggested_stickies %>
|
||||
<% @user_stickies = @sticky_collection.user_stickies %>
|
||||
<% end %>
|
||||
|
||||
<% @actor = @article.organization || @article.user %>
|
||||
<style>
|
||||
|
|
@ -27,7 +29,7 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<div class="primary-sticky-nav">
|
||||
<div class="primary-sticky-nav" id="article-show-primary-sticky-nav">
|
||||
<div class="primary-sticky-nav-element primary-sticky-nav-author">
|
||||
<a href="<%= @actor.path %>"><img src="<%= ProfileImage.new(@actor).get(90) %>" class="primary-sticky-nav-author-top-profile-image" /></a>
|
||||
<span class="primary-sticky-nav-author-name">
|
||||
|
|
@ -38,61 +40,76 @@
|
|||
<div class="primary-sticky-nav-author-summary">
|
||||
<% if @actor.tag_line.present? %>
|
||||
<%= truncate (@actor.tag_line || @actor.summary || "Posts in this tag"), length: 200 %>
|
||||
<% else %>
|
||||
Member since <%= @actor.created_at.strftime("%b %e, %Y") %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="primary-sticky-nav-author-follow">
|
||||
<%= follow_button(@actor) %>
|
||||
</div>
|
||||
</div>
|
||||
<% if @actor.class.name == "Organization" && @actor.approved_and_filled_out_cta? %>
|
||||
<div class="primary-sticky-nav-element primary-sticky-nav-author primary-sticky-nav-org">
|
||||
<div class="sticky-box-connector"></div>
|
||||
<div class="sticky-box-connector sticky-box-right-connector"></div>
|
||||
<% if @actor.class.name == "User" %>
|
||||
<%= render "articles/user_metadata" %>
|
||||
<% elsif @actor.class.name == "Organization" && @actor.approved_and_filled_out_cta? %>
|
||||
<div class="primary-sticky-nav-org-summary">
|
||||
<%= @actor.cta_processed_html.html_safe %>
|
||||
<button class="cta primary-sticky-nav-org-button">
|
||||
<%= link_to @actor.cta_button_text.upcase || "LEARN MORE", @actor.cta_button_url %>
|
||||
</button>
|
||||
<a href="<%= @actor.cta_button_url || "LEARN MORE" %>" class="primary-sticky-nav-org-cta-link">
|
||||
<%= @actor.cta_button_text.upcase %>
|
||||
<span class="primary-sticky-nav-org-cta-link-domain-small"><%= Addressable::URI.parse(@actor.cta_button_url).domain %></span>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if params[:html_variant_id] %>
|
||||
<% @html_variant = HtmlVariant.where(group: "article_show_sidebar_cta", id: params[:html_variant_id].to_i ).first %>
|
||||
<% else %>
|
||||
<% @html_variant = HtmlVariant.find_for_test(@article.cached_tag_list_array) %>
|
||||
<% end %>
|
||||
<% if ( params[:html_variant_id] || !user_signed_in? ) && @html_variant %>
|
||||
<div class="html-variant-wrapper" id="html-variant-article-show-sidebar" data-variant-id="<%= @html_variant.id %>" >
|
||||
<%= @html_variant.html.html_safe %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @user_stickies && @user_stickies.any? %>
|
||||
<div class="primary-sticky-nav-title">
|
||||
<% if @actor.username.size < 18 %>
|
||||
More from <a href="<%= @actor.path %>">@<%= @actor.username %></a>
|
||||
<% else %>
|
||||
More from<br/><a href="<%= @actor.path %>">@<%= @actor.username %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% @user_stickies.each do |article| %>
|
||||
<a class="primary-sticky-nav-element primary-sticky-nav-author-element" href="<%= article.path %>">
|
||||
<img src="<%= ProfileImage.new(article.user).get(90) %>" class="primary-sticky-nav-profile-image" />
|
||||
<%= article.title %>
|
||||
<div class="primary-sticky-nav-element-details">
|
||||
<% article.decorate.cached_tag_list_array.each do |tag| %>
|
||||
<span>#<%= tag %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</a>
|
||||
<% if user_signed_in? && @user_stickies && @user_stickies.any? %>
|
||||
<% @user_stickies.each_with_index do |article, index| %>
|
||||
<div class="primary-sticky-nav-element-wrapper">
|
||||
<% if index == 0 %>
|
||||
<div class="primary-sticky-nav-title">
|
||||
<% if @actor.username.size < 18 %>
|
||||
More from <a href="<%= @actor.path %>">@<%= @actor.username %></a>
|
||||
<% else %>
|
||||
More from<br/><a href="<%= @actor.path %>">@<%= @actor.username %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<a class="primary-sticky-nav-element primary-sticky-nav-author-element" href="<%= article.path %>">
|
||||
<img src="<%= ProfileImage.new(article.user).get(90) %>" class="primary-sticky-nav-profile-image" />
|
||||
<%= article.title %>
|
||||
<div class="primary-sticky-nav-element-details">
|
||||
<% article.decorate.cached_tag_list_array.each do |tag| %>
|
||||
<span>#<%= tag %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @sticky_articles %>
|
||||
<div class="primary-sticky-nav-title">
|
||||
<img src="<%= asset_path "emoji/apple-fire.png"%>"/> Trending on <a href="https://dev.to">dev.to</a>
|
||||
</div>
|
||||
<% @sticky_articles.each do |article| %>
|
||||
<a class="primary-sticky-nav-element" href="<%= article.path %>">
|
||||
<img src="<%= ProfileImage.new(article.user).get(90) %>" class="primary-sticky-nav-profile-image" />
|
||||
<%= article.title %>
|
||||
<div class="primary-sticky-nav-element-details">
|
||||
<% article.decorate.cached_tag_list_array.each do |tag| %>
|
||||
<span>#<%= tag %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</a>
|
||||
<% if user_signed_in? && @sticky_articles %>
|
||||
<% @sticky_articles.each_with_index do |article, index| %>
|
||||
<div class="primary-sticky-nav-element-wrapper">
|
||||
<% if index == 0 %>
|
||||
<div class="primary-sticky-nav-title">
|
||||
<img src="<%= asset_path "emoji/apple-fire.png"%>"/> Trending on <a href="https://dev.to">dev.to</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<a class="primary-sticky-nav-element" href="<%= article.path %>">
|
||||
<img src="<%= ProfileImage.new(article.user).get(90) %>" class="primary-sticky-nav-profile-image" />
|
||||
<%= article.title %>
|
||||
<div class="primary-sticky-nav-element-details">
|
||||
<% article.decorate.cached_tag_list_array.each do |tag| %>
|
||||
<span>#<%= tag %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<% title "Edit Article - DEV" %>
|
||||
<% title "Edit Post" %>
|
||||
|
||||
<% if @article.video.present? %>
|
||||
<div style="text-align:center;padding-top:60px;margin:auto;max-width:880px;margin-bottom:-50px;">
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<div style="max-width:500px;margin:18px auto 0px;font-size:0.8em;">
|
||||
<p>
|
||||
<strong>You'll get an email when this process finishes.</strong>
|
||||
</p>
|
||||
</p>
|
||||
<p>
|
||||
<em>This is an ordinary unpublished post. Add title/tags/body/etc, and then mark as published after the video is done processing.</em>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<% cache("sticky-sidebar-#{@article.id}-#{@article.edited_at}", :expires_in => 8.hours) do %>
|
||||
<% cache("sticky-sidebar-#{@article.id}-#{@article.edited_at}-#{(@organization || @user).updated_at}", :expires_in => 8.hours) do %>
|
||||
<%= render "articles/sticky_nav" %>
|
||||
<% end %>
|
||||
|
||||
|
|
@ -238,4 +238,4 @@
|
|||
<%= GistTag.script.html_safe %>
|
||||
<%= RunkitTag.script.html_safe %>
|
||||
</script>
|
||||
<% end %>
|
||||
<% end %>
|
||||
26
app/views/html_variants/_form.html.erb
Normal file
26
app/views/html_variants/_form.html.erb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<% if @html_variant.errors.any? %>
|
||||
<div id="error_explanation">
|
||||
<h2><%= pluralize(@html_variant.errors.count, "error") %> prohibited this block from being saved:</h2>
|
||||
|
||||
<ul>
|
||||
<% @html_variant.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= form_for(@html_variant) do |f| %>
|
||||
<% if @html_variant.new_record? %>
|
||||
<%= f.label :name %>
|
||||
<%= f.text_field :name, placeholder: 'Unique, descriptive name' %>
|
||||
<% end %>
|
||||
<%= f.label :html %>
|
||||
<%= f.text_area :html, placeholder: 'HTML to be shown. Make sure all CSS is properly scoped, and all HTML tags are closed, etc. Manditory field.' %>
|
||||
<%= f.label :published %>
|
||||
<%= f.check_box :published %>
|
||||
<%= f.label :target_tag %>
|
||||
<%= f.text_field :target_tag, placeholder: 'One tag, e.g. javascript. Optional targeting.' %>
|
||||
<%= f.submit %>
|
||||
<% end %>
|
||||
71
app/views/html_variants/_page_styles.html.erb
Normal file
71
app/views/html_variants/_page_styles.html.erb
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<style>
|
||||
.html-variants-page {
|
||||
margin: 100px auto;
|
||||
width: 800px;
|
||||
max-width: 96%;
|
||||
}
|
||||
h1 a {
|
||||
border: 1px solid black;
|
||||
padding: 10px 40px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
input[type="submit"] {
|
||||
background: blue;
|
||||
color: white;
|
||||
font-size: 30px;
|
||||
padding: 15px 0px;
|
||||
border: 0px;
|
||||
}
|
||||
textarea {
|
||||
height: calc(100vh - 500px);
|
||||
}
|
||||
#error_explanation {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.html-variants-page-single-variant {
|
||||
border: 1px solid black;
|
||||
padding: 15px 15px 5px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.html-variants-page-single-variant h3 {
|
||||
margin-top: 0px;
|
||||
|
||||
}
|
||||
.html-variants-page-single-variant-details {
|
||||
margin: 5px 0px 0px;
|
||||
padding: 10px 0px;
|
||||
border-top: 1px solid gray;
|
||||
}
|
||||
.pill {
|
||||
padding: 3px 10px;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
border-radius: 100px;
|
||||
background: gray;
|
||||
color: white;
|
||||
}
|
||||
.published {
|
||||
background: blue;
|
||||
}
|
||||
.approved {
|
||||
background: green;
|
||||
}
|
||||
.html-variants-page-single-variant-details input {
|
||||
background: green;
|
||||
padding: 20px;
|
||||
font-size:35px;
|
||||
border-radius: 3px;
|
||||
border: 0px;
|
||||
margin-top: 15px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
23
app/views/html_variants/_single_html_variant.html.erb
Normal file
23
app/views/html_variants/_single_html_variant.html.erb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<div class="html-variants-page-single-variant">
|
||||
<h3>
|
||||
<%= html_variant.name %>
|
||||
</h3>
|
||||
<%= html_variant.html %>
|
||||
<div class="html-variants-page-single-variant-details">
|
||||
<% if html_variant.published %>
|
||||
<span class='pill published'>published</span>
|
||||
<a class='pill' href="/html_variants/new?fork_id=<%= html_variant.id %>">fork</a>
|
||||
<% else %>
|
||||
<a class='pill' href="/html_variants/<%= html_variant.id %>/edit">edit</a>
|
||||
<% end %>
|
||||
<a class='pill' href="<%= @preview_path %>?html_variant_id=<%= html_variant.id %>&state=<%= html_variant.updated_at.to_i %>" target="_blank">preview</a>
|
||||
<% if html_variant.approved %>
|
||||
<span class='pill approved'>approved</span>
|
||||
<% elsif admin %>
|
||||
<%= form_for(html_variant) do |f| %>
|
||||
<%= f.hidden_field :approved, value: "true" %>
|
||||
<%= f.submit "APPROVE" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
6
app/views/html_variants/edit.html.erb
Normal file
6
app/views/html_variants/edit.html.erb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<%= render "page_styles" %>
|
||||
|
||||
<div class="html-variants-page" >
|
||||
<h1><%= @html_variant.name %></h1>
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
15
app/views/html_variants/index.html.erb
Normal file
15
app/views/html_variants/index.html.erb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<%= render "page_styles" %>
|
||||
|
||||
<div class="html-variants-page" >
|
||||
<h1>
|
||||
HTML Variants <a href="/html_variants/new">New</a>
|
||||
</h1>
|
||||
<h2>Your HTML Variants</h2>
|
||||
<% @user_variants.each do |html_variant| %>
|
||||
<%= render "single_html_variant", html_variant: html_variant, admin: false %>
|
||||
<% end %>
|
||||
<h2>All Published</h2>
|
||||
<% @all_published_variants.each do |html_variant| %>
|
||||
<%= render "single_html_variant", html_variant: html_variant, admin: true %>
|
||||
<% end %>
|
||||
</div>
|
||||
5
app/views/html_variants/new.html.erb
Normal file
5
app/views/html_variants/new.html.erb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<%= render "page_styles" %>
|
||||
|
||||
<div class="html-variants-page" >
|
||||
<%= render "form" %>
|
||||
</div>
|
||||
|
|
@ -113,38 +113,19 @@
|
|||
<%= f.text_area :tech_stack, maxlength: 640, placeholder: "Limit of 640 characters" %>
|
||||
</div>
|
||||
<% if @organization.approved %>
|
||||
<h2>Call-to-action box</h2>
|
||||
<h2>Post Call-to-action box</h2>
|
||||
<div class="field">
|
||||
<%= f.label "Button Text" %>
|
||||
<%= f.label "CTA Link Text" %>
|
||||
<%= f.text_field :cta_button_text, maxlength: 12, placeholder: "Limit of 12 characters" %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label "Button Link" %>
|
||||
<%= f.label "CTA Link URL" %>
|
||||
<%= f.text_field :cta_button_url %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label "Body text" %>
|
||||
<%= f.text_area :cta_body_markdown, maxlength: 140, placeholder: "Limit of 140 characters" %>
|
||||
<%= f.label "Body text (markdown inline only)" %>
|
||||
<%= f.text_area :cta_body_markdown, maxlength: 256, placeholder: "Limit of 256 characters" %>
|
||||
</div>
|
||||
<% if @organization.approved_and_filled_out_cta? %>
|
||||
<div class="field">
|
||||
Preview of Widget:
|
||||
<div class="org-widget-preview">
|
||||
<div class="primary-sticky-nav">
|
||||
<div class="primary-sticky-nav-element primary-sticky-nav-author primary-sticky-nav-org">
|
||||
<div class="primary-sticky-nav-org-summary">
|
||||
<!-- Markdown -->
|
||||
<%# @organization.cta_body_markdown %>
|
||||
<%= @organization.cta_processed_html.html_safe %>
|
||||
<button class="cta primary-sticky-nav-org-button">
|
||||
<%= @organization.cta_button_text.upcase || "LEARN MORE" %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="field">
|
||||
<label></label>
|
||||
|
|
|
|||
|
|
@ -113,6 +113,9 @@ Rails.application.routes.draw do
|
|||
resources :videos, only: %i[create new]
|
||||
resources :video_states, only: [:create]
|
||||
resources :twilio_tokens, only: [:show]
|
||||
resources :html_variants
|
||||
resources :html_variant_trials, only: [:create]
|
||||
resources :html_variant_successes, only: [:create]
|
||||
resources :push_notification_subscriptions, only: [:create]
|
||||
|
||||
get "/notifications/:username" => "notifications#index"
|
||||
|
|
|
|||
15
db/migrate/20181019195746_create_html_variants.rb
Normal file
15
db/migrate/20181019195746_create_html_variants.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
class CreateHtmlVariants < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :html_variants do |t|
|
||||
t.integer :user_id
|
||||
t.string :group
|
||||
t.string :name
|
||||
t.text :html
|
||||
t.string :target_tag
|
||||
t.float :success_rate, default: 0.0
|
||||
t.boolean :published, default: false
|
||||
t.boolean :approved, default: false
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
10
db/migrate/20181020195949_create_html_variant_trials.rb
Normal file
10
db/migrate/20181020195949_create_html_variant_trials.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
class CreateHtmlVariantTrials < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :html_variant_trials do |t|
|
||||
t.integer :html_variant_id
|
||||
t.integer :article_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :html_variant_trials, [:html_variant_id, :article_id]
|
||||
end
|
||||
end
|
||||
10
db/migrate/20181020195954_create_html_variant_successes.rb
Normal file
10
db/migrate/20181020195954_create_html_variant_successes.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
class CreateHtmlVariantSuccesses < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :html_variant_successes do |t|
|
||||
t.integer :html_variant_id
|
||||
t.integer :article_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :html_variant_successes, [:html_variant_id, :article_id]
|
||||
end
|
||||
end
|
||||
33
db/schema.rb
33
db/schema.rb
|
|
@ -1,5 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
|
|
@ -12,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20181016181008) do
|
||||
ActiveRecord::Schema.define(version: 20181020195954) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
|
@ -358,6 +356,35 @@ ActiveRecord::Schema.define(version: 20181016181008) do
|
|||
t.integer "watchers_count"
|
||||
end
|
||||
|
||||
create_table "html_variant_successes", force: :cascade do |t|
|
||||
t.integer "article_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "html_variant_id"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["html_variant_id", "article_id"], name: "index_html_variant_successes_on_html_variant_id_and_article_id"
|
||||
end
|
||||
|
||||
create_table "html_variant_trials", force: :cascade do |t|
|
||||
t.integer "article_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "html_variant_id"
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["html_variant_id", "article_id"], name: "index_html_variant_trials_on_html_variant_id_and_article_id"
|
||||
end
|
||||
|
||||
create_table "html_variants", force: :cascade do |t|
|
||||
t.boolean "approved", default: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "group"
|
||||
t.text "html"
|
||||
t.string "name"
|
||||
t.boolean "published", default: false
|
||||
t.float "success_rate", default: 0.0
|
||||
t.string "target_tag"
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id"
|
||||
end
|
||||
|
||||
create_table "identities", id: :serial, force: :cascade do |t|
|
||||
t.text "auth_data_dump"
|
||||
t.datetime "created_at", null: false
|
||||
|
|
|
|||
|
|
@ -91,3 +91,12 @@ task :award_contributor_badges, [:arg1] => :environment do |_t, args|
|
|||
BadgeRewarder.award_contributor_badges(usernames)
|
||||
puts "Done!"
|
||||
end
|
||||
|
||||
task remove_old_html_variant_data: :environment do
|
||||
HtmlVariantTrial.where("created_at < ?", 1.week.ago).destroy_all
|
||||
HtmlVariantSuccess.where("created_at < ?", 1.week.ago).destroy_all
|
||||
HtmlVariant.find_each do |html_variant|
|
||||
return if html_variant.html_variant_successes.size < 20
|
||||
html_variant.calculate_success_rate!
|
||||
end
|
||||
end
|
||||
|
|
|
|||
9
spec/factories/html_variants.rb
Normal file
9
spec/factories/html_variants.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
FactoryBot.define do
|
||||
factory :html_variant do
|
||||
user
|
||||
name { Faker::Hipster.paragraph(1) }
|
||||
html { "<div>#{rand(10000000000)}</div><h1>HEllo</h1>" }
|
||||
success_rate { 0.3 }
|
||||
group { "article_show_sidebar_cta" }
|
||||
end
|
||||
end
|
||||
38
spec/models/html_variant_spec.rb
Normal file
38
spec/models/html_variant_spec.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe HtmlVariant, type: :model do
|
||||
it { is_expected.to validate_uniqueness_of(:name) }
|
||||
it { is_expected.to validate_presence_of(:html) }
|
||||
it { is_expected.to belong_to(:user) }
|
||||
|
||||
let(:html_variant) { create(:html_variant, approved: true, published: true) }
|
||||
|
||||
it "calculates success rate" do
|
||||
HtmlVariantTrial.create!(html_variant_id: html_variant.id)
|
||||
HtmlVariantTrial.create!(html_variant_id: html_variant.id)
|
||||
HtmlVariantTrial.create!(html_variant_id: html_variant.id)
|
||||
HtmlVariantTrial.create!(html_variant_id: html_variant.id)
|
||||
HtmlVariantSuccess.create!(html_variant_id: html_variant.id)
|
||||
html_variant.calculate_success_rate!
|
||||
expect(html_variant.success_rate).to eq(0.025)
|
||||
end
|
||||
|
||||
it "finds for test without tag" do
|
||||
html_variant.save!
|
||||
expect(HtmlVariant.find_for_test.id).to eq(html_variant.id)
|
||||
end
|
||||
it "finds for test with tag given" do
|
||||
html_variant.target_tag = "hello"
|
||||
html_variant.save!
|
||||
expect(HtmlVariant.find_for_test(["hello"]).id).to eq(html_variant.id)
|
||||
end
|
||||
it "does not find if different tag targeted" do
|
||||
html_variant.target_tag = "different_tag_yolo"
|
||||
html_variant.save!
|
||||
expect(HtmlVariant.find_for_test(["hello"])).to eq(nil)
|
||||
end
|
||||
it "finds if no tag targeted and tag given" do
|
||||
html_variant.save!
|
||||
expect(HtmlVariant.find_for_test(["hello"]).id).to eq(html_variant.id)
|
||||
end
|
||||
end
|
||||
5
spec/models/html_variant_success_spec.rb
Normal file
5
spec/models/html_variant_success_spec.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe HtmlVariantSuccess, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
5
spec/models/html_variant_trial_spec.rb
Normal file
5
spec/models/html_variant_trial_spec.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe HtmlVariantTrial, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
18
spec/policies/html_variant_policy_spec.rb
Normal file
18
spec/policies/html_variant_policy_spec.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe HtmlVariantPolicy do
|
||||
subject { described_class.new(user, html_variant) }
|
||||
|
||||
context "when user is not an admin" do
|
||||
let(:user) { build(:user) }
|
||||
let(:html_variant) { build(:html_variant) }
|
||||
|
||||
it { is_expected.to forbid_actions(%i[index show edit update create]) }
|
||||
end
|
||||
|
||||
context "when user is an admin" do
|
||||
let(:user) { build(:user, :super_admin) }
|
||||
let(:html_variant) { build(:html_variant) }
|
||||
it { is_expected.to permit_actions(%i[index show edit update create]) }
|
||||
end
|
||||
end
|
||||
25
spec/requests/html_variant_successes_spec.rb
Normal file
25
spec/requests/html_variant_successes_spec.rb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "HtmlVariantSuccesses", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:article) { create(:article, user_id: user.id, approved: true) }
|
||||
let(:html_variant) { create(:html_variant) }
|
||||
|
||||
before do
|
||||
Delayed::Worker.delay_jobs = false
|
||||
end
|
||||
|
||||
after do
|
||||
Delayed::Worker.delay_jobs = true
|
||||
end
|
||||
|
||||
describe "POST /html_variant_successes" do
|
||||
it "rejects non-permissioned user" do
|
||||
post "/html_variant_successes", params: {
|
||||
article_id: article.id,
|
||||
html_variant_id: html_variant.id,
|
||||
}
|
||||
expect(HtmlVariantSuccess.all.size).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
25
spec/requests/html_variant_trials_spec.rb
Normal file
25
spec/requests/html_variant_trials_spec.rb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "HtmlVariantTrials", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:article) { create(:article, user_id: user.id, approved: true) }
|
||||
let(:html_variant) { create(:html_variant) }
|
||||
|
||||
before do
|
||||
Delayed::Worker.delay_jobs = false
|
||||
end
|
||||
|
||||
after do
|
||||
Delayed::Worker.delay_jobs = true
|
||||
end
|
||||
|
||||
describe "POST /html_variant_trials" do
|
||||
it "rejects non-permissioned user" do
|
||||
post "/html_variant_trials", params: {
|
||||
article_id: article.id,
|
||||
html_variant_id: html_variant.id,
|
||||
}
|
||||
expect(HtmlVariantTrial.all.size).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
110
spec/requests/html_variants_spec.rb
Normal file
110
spec/requests/html_variants_spec.rb
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "HtmlVariants", type: :request do
|
||||
let(:user) { create(:user)}
|
||||
let(:article) { create(:article, user_id: user.id, approved: true) }
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
describe "GET /html_variants" do
|
||||
it "rejects non-permissioned user" do
|
||||
expect { get "/html_variants" }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "accepts permissioned" do
|
||||
user.add_role(:super_admin)
|
||||
get "/html_variants"
|
||||
expect(response.body).to include("HTML Variants")
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /html_variants/new" do
|
||||
it "rejects non-permissioned user" do
|
||||
expect { get "/html_variants/new" }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "accepts permissioned" do
|
||||
user.add_role(:super_admin)
|
||||
get "/html_variants/new"
|
||||
expect(response.body).to include("<form")
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /html_variants/:id/edit" do
|
||||
it "rejects non-permissioned user" do
|
||||
html_variant = create(:html_variant)
|
||||
expect { get "/html_variants/#{html_variant.id}/edit" }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "accepts permissioned" do
|
||||
user.add_role(:super_admin)
|
||||
html_variant = create(:html_variant)
|
||||
get "/html_variants/#{html_variant.id}/edit"
|
||||
expect(response.body).to include("<form")
|
||||
end
|
||||
end
|
||||
|
||||
describe "Post /html_variants" do
|
||||
it "rejects non-permissioned user" do
|
||||
expect { post "/html_variants" }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "creates" do
|
||||
user.add_role(:super_admin)
|
||||
post "/html_variants", params: {
|
||||
html_variant: {
|
||||
name: "New post",
|
||||
html: "Yo ho ho#{rand(100)}", tag_list: "yoyo",
|
||||
published: true
|
||||
}
|
||||
}
|
||||
expect(HtmlVariant.all.size).to eq(1)
|
||||
end
|
||||
|
||||
it "does not create with invalid params" do
|
||||
user.add_role(:super_admin)
|
||||
post "/html_variants", params: {
|
||||
html_variant: {
|
||||
# name: NOTHING HERE
|
||||
html: "Yo ho ho#{rand(100)}", tag_list: "yoyo",
|
||||
published: true
|
||||
}
|
||||
}
|
||||
expect(HtmlVariant.all.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Put /html_variants" do
|
||||
it "rejects non-permissioned user" do
|
||||
expect { post "/html_variants" }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "updates when appropriate" do
|
||||
user.add_role(:super_admin)
|
||||
html_variant = create(:html_variant)
|
||||
new_html = "Yo ho ho#{rand(100)}"
|
||||
put "/html_variants/#{html_variant.id}", params: {
|
||||
html_variant: {
|
||||
html: new_html
|
||||
}
|
||||
}
|
||||
expect(html_variant.reload.html).to eq(new_html)
|
||||
end
|
||||
|
||||
it "does not create with invalid params" do
|
||||
user.add_role(:super_admin)
|
||||
html_variant = create(:html_variant, approved: true, published: true)
|
||||
new_html = "Yo ho ho#{rand(100)}"
|
||||
put "/html_variants/#{html_variant.id}", params: {
|
||||
html_variant: {
|
||||
html: new_html
|
||||
}
|
||||
}
|
||||
expect(html_variant.reload.html).not_to eq(new_html)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -4,7 +4,7 @@ RSpec.describe "StoriesShow", type: :request do
|
|||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:article) { FactoryBot.create(:article, user_id: user.id) }
|
||||
|
||||
describe "GET /user" do
|
||||
describe "GET /:username/:slug" do
|
||||
it "renders to appropriate page" do
|
||||
get article.path
|
||||
expect(response.body).to include CGI.escapeHTML(article.title)
|
||||
|
|
@ -66,4 +66,23 @@ RSpec.describe "StoriesShow", type: :request do
|
|||
end
|
||||
# rubocop:enable RSpec/ExampleLength
|
||||
end
|
||||
|
||||
it "renders html variant" do
|
||||
html_variant = create(:html_variant, published: true, approved: true)
|
||||
get article.path
|
||||
expect(response.body).to include html_variant.html
|
||||
end
|
||||
|
||||
it "Does not render variant when no variants published" do
|
||||
html_variant = create(:html_variant, published: false, approved: true)
|
||||
get article.path
|
||||
expect(response.body).not_to include html_variant.html
|
||||
end
|
||||
|
||||
it "does not render html variant when user logged in" do
|
||||
html_variant = create(:html_variant, published: true, approved: true)
|
||||
sign_in user
|
||||
get article.path
|
||||
expect(response.body).not_to include html_variant.html
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue