Add below-article html variant (#1669)
* Add below-article html variant * Fix tests for variant number * Fix stories show tests for html_variants
This commit is contained in:
parent
1cdbfe3af1
commit
ea63374408
15 changed files with 149 additions and 95 deletions
|
|
@ -79,38 +79,61 @@ function logImpressions() {
|
|||
|
||||
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 windowBigEnough = window.innerWidth > 1250
|
||||
|
||||
// Sidebar HTML variant tracking
|
||||
var stickyNav = document.getElementById('article-show-primary-sticky-nav');
|
||||
var sidebarHTMLVariant = document.getElementById('html-variant-article-show-sidebar');
|
||||
if (sidebarHTMLVariant && ArticleElement && tokenMeta && !isBot && windowBigEnough) {
|
||||
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',
|
||||
});
|
||||
}
|
||||
trackHTMLVariantTrial(dataBody, csrfToken)
|
||||
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) });
|
||||
}
|
||||
}
|
||||
|
||||
// Below article HTML variant tracking
|
||||
var belowArticleHTMLVariant = document.getElementById('html-variant-article-show-below-article');
|
||||
if (belowArticleHTMLVariant && ArticleElement && tokenMeta && !isBot && windowBigEnough) {
|
||||
var dataBody = {
|
||||
html_variant_id: belowArticleHTMLVariant.dataset.variantId,
|
||||
article_id: ArticleElement.dataset.articleId,
|
||||
};
|
||||
var csrfToken = tokenMeta.getAttribute('content');
|
||||
trackHTMLVariantTrial(dataBody, csrfToken)
|
||||
var successLinks = belowArticleHTMLVariant.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 trackHTMLVariantTrial(dataBody, csrfToken) {
|
||||
var randomNumber = Math.floor(Math.random() * 10); // 1 in 10; Only track 1 in 10 impressions
|
||||
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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function trackHtmlVariantSuccess(dataBody, csrfToken) {
|
||||
window.fetch('/html_variant_successes', {
|
||||
method: 'POST',
|
||||
|
|
|
|||
|
|
@ -918,6 +918,13 @@ article{
|
|||
|
||||
}
|
||||
}
|
||||
.html-variant-wrapper {
|
||||
width: 88%;
|
||||
margin: 0px auto 25px;
|
||||
@media screen and ( min-width: 500px ){
|
||||
width: 82%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dev-ios-native-body {
|
||||
|
|
@ -955,41 +962,6 @@ article{
|
|||
border-top: 27px solid $green;
|
||||
}
|
||||
}
|
||||
.showpage-signin-cta{
|
||||
background: lighten($light-gray, 1%);
|
||||
border: 1px solid darken($light-medium-gray, 15%);
|
||||
box-shadow: $bold-shadow;
|
||||
width: 680px;
|
||||
max-width:82%;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
padding: 40px 5%;
|
||||
font-size: 1.1em;
|
||||
line-height: 1.33em;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
a{
|
||||
color: $black;
|
||||
text-decoration: underline;
|
||||
display: inline-block;
|
||||
&.primary-cta-link{
|
||||
font-size: calc(1.3em + 0.2vw);
|
||||
background: $bold-blue;
|
||||
border-radius: 100px;
|
||||
text-decoration: none;
|
||||
padding: 16px 0px;
|
||||
color: white;
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
.showpage-signin-cta-subtext {
|
||||
font-family: $monospace;
|
||||
font-size:calc(0.62em + 0.13vw);
|
||||
color: lighten($medium-gray, 10%);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.show-comments-header{
|
||||
width:800px;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ class HtmlVariantsController < ApplicationController
|
|||
def index
|
||||
authorize HtmlVariant
|
||||
@html_variants = if params[:state] == "mine"
|
||||
current_user.html_variants.order("created_at DESC").includes(:user)
|
||||
current_user.html_variants.order("created_at DESC").includes(:user).page(params[:page]).per(15)
|
||||
elsif params[:state] == "admin"
|
||||
HtmlVariant.where(published: true).order("created_at DESC").includes(:user)
|
||||
HtmlVariant.where(published: true, approved: false).order("created_at DESC").includes(:user).page(params[:page]).per(15)
|
||||
else
|
||||
HtmlVariant.where(published: true, approved: true).order("success_rate DESC").includes(:user)
|
||||
HtmlVariant.where(published: true, approved: true).order("success_rate DESC").includes(:user).page(params[:page]).per(15)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -38,7 +38,6 @@ class HtmlVariantsController < ApplicationController
|
|||
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
|
||||
flash[:success] = "HTML Variant Created"
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ class StoriesController < ApplicationController
|
|||
|
||||
def handle_article_show
|
||||
@article_show = true
|
||||
@variant_number = params[:variant_version] || rand(2)
|
||||
@comment = Comment.new
|
||||
assign_user_and_org
|
||||
@comments_to_show_count = @article.cached_tag_list_array.include?("discuss") ? 50 : 30
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class HtmlVariant < ApplicationRecord
|
||||
validates :html, presence: true
|
||||
validates :name, uniqueness: true
|
||||
validates :group, inclusion: { in: %w(article_show_sidebar_cta) }
|
||||
validates :group, inclusion: { in: %w(article_show_sidebar_cta article_show_below_article_cta) }
|
||||
validates :success_rate, presence: true
|
||||
validate :no_edits
|
||||
belongs_to :user, optional: true
|
||||
|
|
@ -14,27 +14,27 @@ class HtmlVariant < ApplicationRecord
|
|||
save!
|
||||
end
|
||||
|
||||
def self.find_for_test(tags = [])
|
||||
def self.find_for_test(tags = [], group = "article_show_sidebar_cta")
|
||||
tags_array = tags + ["", nil]
|
||||
if rand(10) == 1 # 10% return completely random
|
||||
find_random_for_test(tags_array)
|
||||
find_random_for_test(tags_array, group)
|
||||
else # 90% chance return one in top 10
|
||||
find_top_for_test(tags_array)
|
||||
find_top_for_test(tags_array, group)
|
||||
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..15)).sample
|
||||
def self.find_top_for_test(tags_array, group)
|
||||
where(group: group, approved: true, published: true, target_tag: tags_array).order("success_rate DESC").limit(rand(1..15)).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
|
||||
def self.find_random_for_test(tags_array, group)
|
||||
where(group: group, 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?
|
||||
if (approved && (html_changed? || name_changed? || group_changed?)) && persisted?
|
||||
errors.add(:base, "cannot change once published and approved")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
class HtmlVariantPolicy < ApplicationPolicy
|
||||
def index?
|
||||
user_admin?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def show?
|
||||
user_admin?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def edit?
|
||||
user_admin?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def update?
|
||||
user_admin?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def new?
|
||||
user_admin?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def create?
|
||||
user_admin?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user_admin?
|
||||
minimal_admin?
|
||||
end
|
||||
|
||||
def permitted_attributes
|
||||
%i[html name published approved target_tag]
|
||||
%i[html name published approved target_tag group]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,11 +40,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% unless user_signed_in? %>
|
||||
<div class="showpage-signin-cta cta" id="showpage-signin-cta">
|
||||
<a href="/">dev.to</a> is where software developers read, write, and level up.
|
||||
<span style="display:block;margin-top:30px;"><a href="/enter" id="primary-cta-link" class="primary-cta-link">Sign Up Now</a></span>
|
||||
<br/>
|
||||
<span class="showpage-signin-cta-subtext">(open source and free forever ❤️)</span>
|
||||
</div>
|
||||
<% if @variant_number.to_i == 0 && !user_signed_in? && @article.body_markdown.size > 900 %>
|
||||
<% cache("below-article-html-variant-#{rand(20)}", :expires_in => 8.hours) do %>
|
||||
<% @html_variant = HtmlVariant.find_for_test(@article.cached_tag_list_array, "article_show_below_article_cta") %>
|
||||
<% if @html_variant %>
|
||||
<div class="html-variant-wrapper" id="html-variant-article-show-below-article" data-variant-id="<%= @html_variant.id %>" >
|
||||
<%= @html_variant.html.html_safe %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if !user_signed_in? %>
|
||||
<% if @variant_number.to_i == 1 && !user_signed_in? %>
|
||||
<% cache("html-variant-sidebar#{rand(20)}", :expires_in => 8.hours) do %>
|
||||
<% @html_variant = HtmlVariant.find_for_test(@article.cached_tag_list_array) %>
|
||||
<% if @html_variant %>
|
||||
|
|
|
|||
|
|
@ -26,14 +26,20 @@
|
|||
<div class="form-sub-details">
|
||||
<%= 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.label :group %>
|
||||
<%= f.select(:group, options_for_select(["article_show_sidebar_cta", "article_show_below_article_cta"], @html_variant.group || "article_show_sidebar_cta")) %>
|
||||
</div>
|
||||
<%= f.submit %>
|
||||
<% end %>
|
||||
<% if @html_variant.html.present? %>
|
||||
<h1 style="text-align:center;">Preview:</h1>
|
||||
<div class="html-variant-wrapper" style="width: 310px;margin:20px auto;">
|
||||
<div class="html-variant-wrapper" style="width: 360px;margin:20px auto;">
|
||||
<%= @html_variant.html.html_safe %>
|
||||
</div>
|
||||
<% if @html_variant.group == "article_show_below_article_cta" %>
|
||||
<div class="html-variant-wrapper" style="width: 880px;margin:20px auto;">
|
||||
<%= @html_variant.html.html_safe %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
||||
|
|
@ -68,11 +68,14 @@
|
|||
border-radius: 3px;
|
||||
margin: 15px;
|
||||
width: 450px;
|
||||
height: 550px;
|
||||
height: 620px;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.html-variants-page-single-big {
|
||||
width: 962px;
|
||||
}
|
||||
.html-variants-page-single-variant h3 {
|
||||
margin-top: 0px;
|
||||
font-size:1.3em;
|
||||
|
|
@ -82,6 +85,10 @@
|
|||
border: 0px;
|
||||
width: 310px;
|
||||
height: 350px;
|
||||
border: 1px solid #888888;
|
||||
}
|
||||
.html-variants-page-single-big iframe {
|
||||
width: 800px;
|
||||
}
|
||||
.html-variants-page-single-variant-details {
|
||||
padding: 10px;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="html-variants-page-single-variant">
|
||||
@<%= html_variant.user.username %>:
|
||||
<div class="html-variants-page-single-variant <%= "html-variants-page-single-big" if html_variant.group == "article_show_below_article_cta" %>">
|
||||
<%= html_variant.group %> | @<%= html_variant.user.username %>:
|
||||
<h3>
|
||||
<%= html_variant.name %>
|
||||
<a href="/html_variants/<%= html_variant.id %>"><%= html_variant.name %></a>
|
||||
</h3>
|
||||
<iframe src="/html_variants/<%= html_variant.id %>"></iframe>
|
||||
<div class="html-variants-page-single-variant-details">
|
||||
|
|
|
|||
|
|
@ -20,3 +20,4 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= paginate @html_variants %>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
<style>
|
||||
body {
|
||||
all: unset;
|
||||
}
|
||||
</style>
|
||||
<%= render "layouts/styles" %>
|
||||
<style>
|
||||
body{padding-top: 20px !important; }
|
||||
</style>
|
||||
<div class="primary-sticky-nav">
|
||||
<div class="html-variant-wrapper">
|
||||
<%= @html_variant.html.html_safe %>
|
||||
<% if @html_variant.group == "article_show_below_article_cta" %>
|
||||
<div class="home">
|
||||
<div class="container">
|
||||
<%= @html_variant.html.html_safe %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="primary-sticky-nav">
|
||||
<div class="html-variant-wrapper">
|
||||
<%= @html_variant.html.html_safe %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -57,7 +57,8 @@ RSpec.describe "HtmlVariants", type: :request do
|
|||
html_variant: {
|
||||
name: "New post",
|
||||
html: "Yo ho ho#{rand(100)}", tag_list: "yoyo",
|
||||
published: true
|
||||
published: true,
|
||||
group: "article_show_sidebar_cta"
|
||||
}
|
||||
}
|
||||
expect(HtmlVariant.all.size).to eq(1)
|
||||
|
|
@ -72,7 +73,7 @@ RSpec.describe "HtmlVariants", type: :request do
|
|||
published: true
|
||||
}
|
||||
}
|
||||
expect(HtmlVariant.all.size).to eq(1)
|
||||
expect(HtmlVariant.all.size).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -31,22 +31,51 @@ RSpec.describe "StoriesShow", type: :request do
|
|||
expect(response.body).to include "<em>with <b><a href=\"#{user2.path}\">"
|
||||
end
|
||||
|
||||
# sidebar HTML variant
|
||||
it "renders html variant" do
|
||||
html_variant = create(:html_variant, published: true, approved: true)
|
||||
get article.path
|
||||
get article.path + "?variant_version=1"
|
||||
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
|
||||
get article.path + "?variant_version=1"
|
||||
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
|
||||
get article.path + "?variant_version=1"
|
||||
expect(response.body).not_to include html_variant.html
|
||||
end
|
||||
|
||||
# Below article HTML variant
|
||||
it "renders below article html variant" do
|
||||
html_variant = create(:html_variant, published: true, approved: true, group: "article_show_below_article_cta")
|
||||
article.update_column(:body_markdown, rand(36**1000).to_s(36).to_s) # min length for article
|
||||
get article.path + "?variant_version=0"
|
||||
expect(response.body).to include html_variant.html
|
||||
end
|
||||
|
||||
it "Does not render below article html variant for short article" do
|
||||
html_variant = create(:html_variant, published: true, approved: true, group: "article_show_below_article_cta")
|
||||
article.update_column(:body_markdown, rand(36**100).to_s(36).to_s) # ensure too short
|
||||
get article.path + "?variant_version=0"
|
||||
expect(response.body).not_to include html_variant.html
|
||||
end
|
||||
|
||||
it "Does not render below article variant when no variants published" do
|
||||
html_variant = create(:html_variant, published: false, approved: true, group: "article_show_below_article_cta")
|
||||
get article.path + "?variant_version=0"
|
||||
expect(response.body).not_to include html_variant.html
|
||||
end
|
||||
|
||||
it "does not render below article html variant when user logged in" do
|
||||
html_variant = create(:html_variant, published: true, approved: true, group: "article_show_below_article_cta")
|
||||
sign_in user
|
||||
get article.path + "?variant_version=0"
|
||||
expect(response.body).not_to include html_variant.html
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue