diff --git a/app/assets/javascripts/initializers/initializeBaseTracking.js.erb b/app/assets/javascripts/initializers/initializeBaseTracking.js.erb index b281ebe62..154a472ea 100644 --- a/app/assets/javascripts/initializers/initializeBaseTracking.js.erb +++ b/app/assets/javascripts/initializers/initializeBaseTracking.js.erb @@ -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', diff --git a/app/assets/stylesheets/article-show.scss b/app/assets/stylesheets/article-show.scss index 027f2dd8d..b0ab97f28 100644 --- a/app/assets/stylesheets/article-show.scss +++ b/app/assets/stylesheets/article-show.scss @@ -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; diff --git a/app/controllers/html_variants_controller.rb b/app/controllers/html_variants_controller.rb index 01806bd23..220c32e04 100644 --- a/app/controllers/html_variants_controller.rb +++ b/app/controllers/html_variants_controller.rb @@ -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" diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 210c2093c..f13038c9f 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -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 diff --git a/app/models/html_variant.rb b/app/models/html_variant.rb index 791252287..7d98329ed 100644 --- a/app/models/html_variant.rb +++ b/app/models/html_variant.rb @@ -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 diff --git a/app/policies/html_variant_policy.rb b/app/policies/html_variant_policy.rb index d7d705087..dc7935d66 100644 --- a/app/policies/html_variant_policy.rb +++ b/app/policies/html_variant_policy.rb @@ -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 diff --git a/app/views/articles/_actions.html.erb b/app/views/articles/_actions.html.erb index d0b250a45..d5ed7ea5f 100644 --- a/app/views/articles/_actions.html.erb +++ b/app/views/articles/_actions.html.erb @@ -40,11 +40,13 @@ -<% unless user_signed_in? %> -
- dev.to is where software developers read, write, and level up. - Sign Up Now -
- (open source and free forever ❤️) -
+<% 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 %> +
+ <%= @html_variant.html.html_safe %> +
+ <% end %> + <% end %> <% end %> diff --git a/app/views/articles/_sticky_nav.html.erb b/app/views/articles/_sticky_nav.html.erb index 4a914f798..1d7b9490f 100644 --- a/app/views/articles/_sticky_nav.html.erb +++ b/app/views/articles/_sticky_nav.html.erb @@ -61,7 +61,7 @@ <% end %> -<% 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 %> diff --git a/app/views/html_variants/_form.html.erb b/app/views/html_variants/_form.html.erb index b2468601d..1774a77a8 100644 --- a/app/views/html_variants/_form.html.erb +++ b/app/views/html_variants/_form.html.erb @@ -26,14 +26,20 @@
<%= 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")) %>
<%= f.submit %> <% end %> <% if @html_variant.html.present? %>

Preview:

-
+
<%= @html_variant.html.html_safe %>
+ <% if @html_variant.group == "article_show_below_article_cta" %> +
+ <%= @html_variant.html.html_safe %> +
+ <% end %> + <% end %> \ No newline at end of file diff --git a/app/views/html_variants/_page_styles.html.erb b/app/views/html_variants/_page_styles.html.erb index 542a9096a..fe2fa3c09 100644 --- a/app/views/html_variants/_page_styles.html.erb +++ b/app/views/html_variants/_page_styles.html.erb @@ -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; diff --git a/app/views/html_variants/_single_html_variant.html.erb b/app/views/html_variants/_single_html_variant.html.erb index df961d9be..0611d6f31 100644 --- a/app/views/html_variants/_single_html_variant.html.erb +++ b/app/views/html_variants/_single_html_variant.html.erb @@ -1,7 +1,7 @@ -
- @<%= html_variant.user.username %>: +
"> + <%= html_variant.group %> | @<%= html_variant.user.username %>:

- <%= html_variant.name %> + <%= html_variant.name %>

diff --git a/app/views/html_variants/index.html.erb b/app/views/html_variants/index.html.erb index 1adc36b47..6c439b93a 100644 --- a/app/views/html_variants/index.html.erb +++ b/app/views/html_variants/index.html.erb @@ -20,3 +20,4 @@ <% end %>
+ <%= paginate @html_variants %> diff --git a/app/views/html_variants/show.html.erb b/app/views/html_variants/show.html.erb index 3da7cc719..91f4ec2ed 100644 --- a/app/views/html_variants/show.html.erb +++ b/app/views/html_variants/show.html.erb @@ -1,9 +1,22 @@ + <%= render "layouts/styles" %> -
-
- <%= @html_variant.html.html_safe %> +<% if @html_variant.group == "article_show_below_article_cta" %> +
+
+ <%= @html_variant.html.html_safe %> +
-
\ No newline at end of file +<% else %> +
+
+ <%= @html_variant.html.html_safe %> +
+
+<% end %> \ No newline at end of file diff --git a/spec/requests/html_variants_spec.rb b/spec/requests/html_variants_spec.rb index b37e728a2..4f28f61fe 100644 --- a/spec/requests/html_variants_spec.rb +++ b/spec/requests/html_variants_spec.rb @@ -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 diff --git a/spec/requests/stories_show_spec.rb b/spec/requests/stories_show_spec.rb index d64520795..6de7deccf 100644 --- a/spec/requests/stories_show_spec.rb +++ b/spec/requests/stories_show_spec.rb @@ -31,22 +31,51 @@ RSpec.describe "StoriesShow", type: :request do expect(response.body).to include "with " 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