Track success of DEV Shop ads (#3589)
* Add basic display ad event tracking * Finalize client-side eventlistening functionality
This commit is contained in:
parent
3071ac8c76
commit
5d380f93cb
16 changed files with 246 additions and 4 deletions
|
|
@ -52,6 +52,7 @@ function callInitalizers(){
|
|||
nextPage = 0;
|
||||
fetching = false;
|
||||
done = false;
|
||||
adClicked = false;
|
||||
setTimeout(function(){
|
||||
done = false;
|
||||
},300);
|
||||
|
|
|
|||
|
|
@ -141,7 +141,20 @@ function trackCustomImpressions() {
|
|||
}
|
||||
}, 15000)
|
||||
}
|
||||
}, 1500)
|
||||
|
||||
// display add
|
||||
var adBox = document.getElementById('sponsorship-arbitrary-display-widget')
|
||||
if (adBox
|
||||
&& tokenMeta
|
||||
&& !isBot
|
||||
&& windowBigEnough
|
||||
&& checkUserLoggedIn()) {
|
||||
var csrfToken = tokenMeta.getAttribute('content');
|
||||
trackAdImpression(csrfToken, adBox)
|
||||
adBox.removeEventListener('click', trackAdClick, false );
|
||||
adBox.addEventListener('click', function() { trackAdClick(csrfToken, adBox) });
|
||||
}
|
||||
}, 1800)
|
||||
}
|
||||
|
||||
function trackHTMLVariantTrial(dataBody, csrfToken) {
|
||||
|
|
@ -192,5 +205,45 @@ function trackFifteenSecondsOnPage(articleId, csrfToken) {
|
|||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
}
|
||||
|
||||
function trackAdImpression(token, adBox) {
|
||||
var dataBody = {
|
||||
display_ad_event: {
|
||||
display_ad_id: adBox.dataset.id,
|
||||
context_type: "home",
|
||||
category: "impression",
|
||||
}
|
||||
}
|
||||
window.fetch('/display_ad_events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': token,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(dataBody),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
}
|
||||
|
||||
function trackAdClick(token, adBox) {
|
||||
if (!adClicked) {
|
||||
var dataBody = {
|
||||
display_ad_event: {
|
||||
display_ad_id: adBox.dataset.id,
|
||||
context_type: "home",
|
||||
category: "click",
|
||||
}
|
||||
}
|
||||
window.fetch('/display_ad_events', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': token,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(dataBody),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
}
|
||||
adClicked = true;
|
||||
}
|
||||
29
app/controllers/display_ad_events_controller.rb
Normal file
29
app/controllers/display_ad_events_controller.rb
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
class DisplayAdEventsController < ApplicationController
|
||||
# No policy needed. All views are for all users
|
||||
def create
|
||||
# Only tracking for logged in users at the moment
|
||||
display_ad_event_create_params = display_ad_event_params.merge(user_id: current_user.id)
|
||||
@display_ad_event = DisplayAdEvent.create(display_ad_event_create_params)
|
||||
|
||||
update_display_ads_data
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_display_ads_data
|
||||
return if Rails.env.production? && rand(20) != 1 # We need to do this operation only once in a while.
|
||||
@display_ad = DisplayAd.find(display_ad_event_params[:display_ad_id])
|
||||
num_impressions = @display_ad.display_ad_events.where(category: "impression").size
|
||||
num_clicks = @display_ad.display_ad_events.where(category: "click").size
|
||||
rate = num_clicks.to_f / num_impressions.to_f
|
||||
|
||||
@display_ad.
|
||||
update_columns(success_rate: rate, clicks_count: num_clicks, impressions_count: num_impressions)
|
||||
end
|
||||
|
||||
def display_ad_event_params
|
||||
params.require(:display_ad_event).permit(%i[context_type category display_ad_id])
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +1,24 @@
|
|||
class DisplayAd < ApplicationRecord
|
||||
belongs_to :organization
|
||||
has_many :display_ad_events
|
||||
|
||||
validates :organization_id, presence: true
|
||||
validates :placement_area, presence: true,
|
||||
inclusion: { in: %w[sidebar_left sidebar_right] }
|
||||
validates :body_markdown, presence: true
|
||||
before_save :process_markdown
|
||||
|
||||
scope :approved_and_published, -> { where(approved: true, published: true) }
|
||||
|
||||
|
||||
def self.for_display(area)
|
||||
if rand(8) == 1
|
||||
approved_and_published.where(placement_area: area).order("success_rate DESC").sample
|
||||
else
|
||||
approved_and_published.where(placement_area: area).order("success_rate DESC").limit(rand(1..15)).sample
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_markdown
|
||||
|
|
|
|||
8
app/models/display_ad_event.rb
Normal file
8
app/models/display_ad_event.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
class DisplayAdEvent < ApplicationRecord
|
||||
|
||||
belongs_to :display_ad
|
||||
belongs_to :user
|
||||
|
||||
validates :category, inclusion: {in: %w[impression click]}
|
||||
validates :context_type, inclusion: {in: %w[home]}
|
||||
end
|
||||
|
|
@ -45,6 +45,7 @@ class User < ApplicationRecord
|
|||
has_many :poll_votes
|
||||
has_many :poll_skips
|
||||
has_many :backup_data, foreign_key: "instance_user_id", inverse_of: :instance_user, class_name: "BackupData"
|
||||
has_many :display_ad_events
|
||||
has_many :access_grants, class_name: "Doorkeeper::AccessGrant", foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :delete_all
|
||||
has_many :access_tokens, class_name: "Doorkeeper::AccessToken", foreign_key: :resource_owner_id, inverse_of: :resource_owner, dependent: :delete_all
|
||||
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@
|
|||
</div>
|
||||
<% end %>
|
||||
<% cache("display-area-left-#{rand(5)}", expires_in: 5.minutes) do %>
|
||||
<% @left_sidebar_ad = DisplayAd.where(approved: true, published: true, placement_area: "sidebar_left").order(Arel.sql("RANDOM()")).first %>
|
||||
<% @left_sidebar_ad = DisplayAd.for_display("sidebar_left") %>
|
||||
<% if @left_sidebar_ad %>
|
||||
<div class="widget">
|
||||
<div class="widget" id="sponsorship-arbitrary-display-widget" data-id="<%= @left_sidebar_ad.id %>">
|
||||
<div class="widget-body" style="margin-top:-6px">
|
||||
<%= @left_sidebar_ad.processed_html.html_safe %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
<% cache("main-article-right-sidebar-discussions-#{params[:timeframe]}", expires_in: (params[:timeframe].blank? ? 120 : 360).seconds) do %>
|
||||
<% @sidebar_ad = DisplayAd.where(approved: true, published: true, placement_area: "sidebar_right").order(Arel.sql("RANDOM()")).first %>
|
||||
<% @sidebar_ad = DisplayAd.for_display("sidebar_right") %>
|
||||
<% if @sidebar_ad %>
|
||||
<div class="widget">
|
||||
<div class="widget-body" style="margin-top:-6px">
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ Rails.application.routes.draw do
|
|||
resources :poll_skips, only: [:create]
|
||||
resources :profile_pins, only: %i[create update]
|
||||
resources :partnerships, only: %i[index create show], param: :option
|
||||
resources :display_ad_events, only: [:create]
|
||||
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id" => "chat_channel_memberships#find_by_chat_channel_id"
|
||||
get "/listings/dashboard" => "classified_listings#dashboard"
|
||||
|
|
|
|||
16
db/migrate/20190713213412_create_display_ad_events.rb
Normal file
16
db/migrate/20190713213412_create_display_ad_events.rb
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
class CreateDisplayAdEvents < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :display_ad_events do |t|
|
||||
t.integer :display_ad_id
|
||||
t.integer :user_id
|
||||
t.string :category
|
||||
t.string :context_type
|
||||
t.bigint :context_id
|
||||
t.integer :counts_for, default: 1
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
add_index("display_ad_events", "display_ad_id")
|
||||
add_index("display_ad_events", "user_id")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class AddSuccessRateToDisplayAds < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :display_ads, :success_rate, :float, default: 0.0
|
||||
end
|
||||
end
|
||||
14
db/schema.rb
14
db/schema.rb
|
|
@ -338,6 +338,19 @@ ActiveRecord::Schema.define(version: 2019_07_23_094834) do
|
|||
t.index ["priority", "run_at"], name: "delayed_jobs_priority"
|
||||
end
|
||||
|
||||
create_table "display_ad_events", force: :cascade do |t|
|
||||
t.string "category"
|
||||
t.bigint "context_id"
|
||||
t.string "context_type"
|
||||
t.integer "counts_for", default: 1
|
||||
t.datetime "created_at", null: false
|
||||
t.integer "display_ad_id"
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id"
|
||||
t.index ["display_ad_id"], name: "index_display_ad_events_on_display_ad_id"
|
||||
t.index ["user_id"], name: "index_display_ad_events_on_user_id"
|
||||
end
|
||||
|
||||
create_table "display_ads", force: :cascade do |t|
|
||||
t.boolean "approved", default: false
|
||||
t.text "body_markdown"
|
||||
|
|
@ -350,6 +363,7 @@ ActiveRecord::Schema.define(version: 2019_07_23_094834) do
|
|||
t.string "placement_area"
|
||||
t.text "processed_html"
|
||||
t.boolean "published", default: false
|
||||
t.float "success_rate", default: 0.0
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
|
|
|
|||
6
spec/factories/display_ad_events.rb
Normal file
6
spec/factories/display_ad_events.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
FactoryBot.define do
|
||||
factory :display_ad_event do
|
||||
category { "impression" }
|
||||
context_type { "home" }
|
||||
end
|
||||
end
|
||||
21
spec/models/display_ad_event_spec.rb
Normal file
21
spec/models/display_ad_event_spec.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe DisplayAdEvent, type: :model do
|
||||
let(:user) { create(:user) }
|
||||
let(:organization) { create(:organization) }
|
||||
let(:display_ad) { create(:display_ad, organization_id: organization.id) }
|
||||
|
||||
it "creates a click event" do
|
||||
event = build(:display_ad_event, category: "click", user_id: user.id, display_ad_id: display_ad.id)
|
||||
expect(event).to be_valid
|
||||
end
|
||||
it "creates an impression event" do
|
||||
event = build(:display_ad_event, category: "impression", user_id: user.id, display_ad_id: display_ad.id)
|
||||
expect(event).to be_valid
|
||||
end
|
||||
|
||||
it "does not create an invalid event" do
|
||||
event = build(:display_ad_event, category: "wazoo", user_id: user.id, display_ad_id: display_ad.id)
|
||||
expect(event).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
|
@ -23,4 +23,13 @@ RSpec.describe DisplayAd, type: :model do
|
|||
display_ad.placement_area = "sidebar_left"
|
||||
expect(display_ad).to be_valid
|
||||
end
|
||||
|
||||
it "displays published and approved posts" do
|
||||
create(:display_ad, organization_id: organization.id, published: true, approved: true)
|
||||
create(:display_ad, organization_id: organization.id, published: true, approved: true)
|
||||
create(:display_ad, organization_id: organization.id, published: false, approved: true)
|
||||
create(:display_ad, organization_id: organization.id, published: true, approved: false)
|
||||
expect(DisplayAd.for_display(DisplayAd.last.placement_area).published).to eq(true)
|
||||
expect(DisplayAd.for_display(DisplayAd.last.placement_area).approved).to eq(true)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
65
spec/requests/display_ad_events_spec.rb
Normal file
65
spec/requests/display_ad_events_spec.rb
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "DisplayAdEvents", type: :request do
|
||||
let(:user) { create(:user, :trusted) }
|
||||
let(:organization) { create(:organization) }
|
||||
let(:display_ad) { create(:display_ad, organization_id: organization.id) }
|
||||
|
||||
describe "POST /display_ad_events" do
|
||||
context "when user signed in" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "creates a display ad click event" do
|
||||
post "/display_ad_events", params: {
|
||||
display_ad_event: {
|
||||
display_ad_id: display_ad.id,
|
||||
context_type: "home",
|
||||
category: "click",
|
||||
}
|
||||
}
|
||||
expect(display_ad.reload.clicks_count).to eq(1)
|
||||
end
|
||||
it "creates a display ad impression event" do
|
||||
post "/display_ad_events", params: {
|
||||
display_ad_event: {
|
||||
display_ad_id: display_ad.id,
|
||||
context_type: "home",
|
||||
category: "impression",
|
||||
}
|
||||
}
|
||||
expect(display_ad.reload.impressions_count).to eq(1)
|
||||
end
|
||||
it "creates a display ad success rate" do
|
||||
4.times do
|
||||
post "/display_ad_events", params: {
|
||||
display_ad_event: {
|
||||
display_ad_id: display_ad.id,
|
||||
context_type: "home",
|
||||
category: "impression",
|
||||
}
|
||||
}
|
||||
end
|
||||
post "/display_ad_events", params: {
|
||||
display_ad_event: {
|
||||
display_ad_id: display_ad.id,
|
||||
context_type: "home",
|
||||
category: "click",
|
||||
}
|
||||
}
|
||||
expect(display_ad.reload.success_rate).to eq(0.25)
|
||||
end
|
||||
it "assigns event to current user" do
|
||||
post "/display_ad_events", params: {
|
||||
display_ad_event: {
|
||||
display_ad_id: display_ad.id,
|
||||
context_type: "home",
|
||||
category: "impression",
|
||||
}
|
||||
}
|
||||
expect(DisplayAdEvent.last.user_id).to eq(user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue