Use an ad server to select promos for spotlight.

Replaces client side weighted random selection.
This commit is contained in:
bsimpson63
2013-05-21 14:50:37 -04:00
committed by Brian Simpson
parent 7423c17b39
commit 5967acfa83
11 changed files with 124 additions and 164 deletions

View File

@@ -316,6 +316,7 @@ sponsors =
selfserve_support_email = selfservesupport@mydomain.com
MAX_CAMPAIGNS_PER_LINK = 100
cpm_selfserve = 1.00
adserver_click_domain =
# authorize.net credentials (blank authorizenetapi to disable)
authorizenetapi =

View File

@@ -3158,21 +3158,6 @@ class ApiController(RedditController, OAuth2ResourceController):
Trophy.by_account(recipient, _update=True)
Trophy.by_award(award, _update=True)
@validate(link=nop('link'),
campaign=nop('campaign'))
def GET_fetch_promo(self, link, campaign):
promo_tuples = [promote.PromoTuple(link, 1., campaign)]
builder = CampaignBuilder(promo_tuples,
wrap=default_thing_wrapper(),
keep_fn=promote.is_promoted)
promoted_links = builder.get_items()[0]
if promoted_links:
s = SpotlightListing(promoted_links=promoted_links).listing()
item = s.things[0]
return spaceCompress(item.render())
@noresponse(VUser(),
ui_elem = VOneOf('id', ('organic',)))
def POST_disable_ui(self, ui_elem):
@@ -3522,3 +3507,15 @@ class ApiController(RedditController, OAuth2ResourceController):
to_omit=to_omit.values())
sr_names = [sr.name for sr in rec_srs]
return json.dumps(sr_names)
def POST_request_promo(self):
promo_tuples = promote.lottery_promoted_links(c.user, c.site, n=10)
builder = CampaignBuilder(promo_tuples,
wrap=default_thing_wrapper(),
keep_fn=promote.is_promoted,
num=1,
skip=True)
listing = LinkListing(builder, nextprev=False).listing()
if listing.things:
w = listing.things[0]
return spaceCompress(w.render())

View File

@@ -246,13 +246,8 @@ class HotController(FixListing, ListingController):
return res
def make_single_ad(self):
promo_tuples = promote.lottery_promoted_links(c.user, c.site, n=10)
b = CampaignBuilder(promo_tuples, wrap=self.builder_wrapper,
keep_fn=organic.keep_fresh_links, num=1, skip=True)
res = LinkListing(b, nextprev=False).listing()
res.parent_name = "promoted"
if res.things:
return res
if promote.srids_with_live_promos(c.user, c.site):
return SpotlightListing(show_promo=True, navigable=False).listing()
def make_spotlight(self):
"""Build the Spotlight.
@@ -282,35 +277,16 @@ class HotController(FixListing, ListingController):
promoted_links = []
# If prefs allow it, mix in promoted links and sr discovery content
if ((c.user.pref_show_sponsors or not c.user.gold)
and g.live_config['sr_discovery_links']):
organic_fullnames.extend(g.live_config['sr_discovery_links'])
show_promo = False
if c.user.pref_show_sponsors or not c.user.gold:
if g.live_config['sr_discovery_links']:
organic_fullnames.extend(g.live_config['sr_discovery_links'])
n_promoted = 100
n_build = 1 if c.user_is_loggedin else 10
picker = (promote.lottery_promoted_links if c.user_is_loggedin else
promote.sample_promoted_links)
promo_tuples = picker(c.user, c.site, n=n_promoted)
if not c.user_is_loggedin:
promo_tuples.sort(key=lambda t: t.weight, reverse=True)
b = CampaignBuilder(
promo_tuples,
wrap=self.builder_wrapper,
keep_fn=organic.keep_fresh_links,
skip=True,
)
promoted_links, first, last, before, after = b.get_items()
if promoted_links:
stubs = promoted_links[n_build:]
stubs = [promote.PromoTuple(item._fullname, item.weight,
item.campaign)
for item in stubs]
promoted_links = promoted_links[:n_build] + stubs
if not (organic_fullnames or promoted_links):
return None
if promote.srids_with_live_promos(c.user, c.site):
if ((c.user_is_loggedin and random.random() > 0.5) or
not c.user_is_loggedin):
show_promo = True
random.shuffle(organic_fullnames)
organic_fullnames = organic_fullnames[:10]
@@ -325,16 +301,13 @@ class HotController(FixListing, ListingController):
if has_subscribed else
'spotlight_interest_nosub_p']
interestbar = InterestBar(has_subscribed)
promotion_prob = 0.5 if c.user_is_loggedin else 1.
s = SpotlightListing(organic_links=organic_links,
promoted_links=promoted_links,
interestbar=interestbar,
interestbar_prob=interestbar_prob,
promotion_prob=promotion_prob,
show_promo=show_promo,
max_num = self.listing_obj.max_num,
max_score = self.listing_obj.max_score,
predetermined_winner=c.user_is_loggedin).listing()
max_score = self.listing_obj.max_score).listing()
return s
def query(self):

View File

@@ -198,6 +198,7 @@ class Globals(object):
'wiki_page_registration_info',
'wiki_page_privacy_policy',
'wiki_page_user_agreement',
'adserver_click_domain',
],
ConfigValue.choice: {

View File

@@ -616,6 +616,24 @@ def get_live_promotions(srids):
return weights
def srids_from_site(user, site):
if not isinstance(site, FakeSubreddit):
srids = {site._id}
elif isinstance(site, MultiReddit):
srids = set(site.sr_ids)
elif user and not isinstance(user, FakeAccount):
srids = set(Subreddit.user_subreddits(user, ids=True) + [""])
else:
srids = set(Subreddit.user_subreddits(None, ids=True) + [""])
return srids
def srids_with_live_promos(user, site):
srids = srids_from_site(user, site)
weights = get_live_promotions(srids)
return [srid for srid, adweights in weights.iteritems() if adweights]
def set_live_promotions(weights):
start = time.time()
# First, figure out which subreddits have had ads recently
@@ -783,15 +801,7 @@ PromoTuple = namedtuple('PromoTuple', ['link', 'weight', 'campaign'])
def get_promotion_list(user, site):
if not isinstance(site, FakeSubreddit):
srids = set([site._id])
elif isinstance(site, MultiReddit):
srids = set(site.sr_ids)
elif user and not isinstance(user, FakeAccount):
srids = set(Subreddit.reverse_subscriber_ids(user) + [""])
else:
srids = set(Subreddit.user_subreddits(None, ids=True) + [""])
srids = srids_from_site(user, site)
tuples = get_promotion_list_cached(srids)
return [PromoTuple(*t) for t in tuples]

View File

@@ -143,43 +143,9 @@ class SpotlightListing(Listing):
self._parent_max_score = kw.get('max_score', 0)
self.interestbar = kw.get('interestbar')
self.interestbar_prob = kw.get('interestbar_prob', 0.)
self.promotion_prob = kw.get('promotion_prob', 0.5)
promoted_links = kw.get('promoted_links', [])
organic_links = kw.get('organic_links', [])
predetermined_winner = kw.get('predetermined_winner', False)
self.links = []
for l in organic_links:
self.links.append(
SpotlightTuple(
link=l._fullname,
is_promo=False,
campaign=None,
weight=None,
)
)
total = sum(float(l.weight) for l in promoted_links)
for i, l in enumerate(promoted_links):
link = l._fullname if isinstance(l, Wrapped) else l.link
if predetermined_winner:
weight = 1 if i == 0 else 0
else:
weight = l.weight / total
self.links.append(
SpotlightTuple(
link=link,
is_promo=True,
campaign=l.campaign,
weight=weight,
)
)
self.things = organic_links
self.things.extend(l for l in promoted_links
if isinstance(l, Wrapped))
self.show_promo = kw.get('show_promo', False)
self.navigable = kw.get('navigable', True)
self.things = kw.get('organic_links', [])
def get_items(self):
from r2.lib.template_helpers import replace_render

View File

@@ -80,6 +80,14 @@ r.analytics = {
'r': Math.round(Math.random() * 2147483647) // cachebuster
})
var adServerPixel = new Image(),
adServerImpPixel = $el.data('adserverImpPixel'),
adServerClickUrl = $el.data('adserverClickUrl')
if (adServerImpPixel) {
adServerPixel.src = adServerImpPixel
}
// If IE7/8 thinks the text of a link looks like an email address
// (e.g. it has an @ in it), then setting the href replaces the
// text as well. We'll store the original text and replace it to
@@ -87,11 +95,17 @@ r.analytics = {
var link = $el.find('a.title'),
old_html = link.html(),
dest = link.attr('href'),
click_url = r.config.clicktracker_url + '?' + $.param({
'id': trackingName,
'hash': hash,
'url': dest
})
click_params = {
'id': trackingName,
'hash': hash,
'url': dest
},
click_url
if (adServerClickUrl) {
click_params['adserverclick'] = adServerClickUrl
}
click_url = r.config.clicktracker_url + '?' + $.param(click_params)
save_href(link)
link.attr('href', click_url)

View File

@@ -29,42 +29,45 @@ r.spotlight.init = function() {
r.spotlight._advance(0)
}
r.spotlight.setup = function(links, interest_prob, promotion_prob) {
this.link_by_camp = {},
this.weights = {},
r.spotlight.setup = function(organic_links, interest_prob, show_promo) {
this.organics = []
this.lineup = []
for (var index in links) {
var link = links[index][0],
is_promo = links[index][1],
campaign = links[index][2],
weight = links[index][3]
_.each(organic_links, function (name) {
this.organics.push(name)
this.lineup.push({fullname: name})
}, this)
if (is_promo) {
this.link_by_camp[campaign] = link
this.weights[campaign] = weight
this.lineup.push({fullname: link, campaign: campaign})
} else {
this.organics.push(link)
this.lineup.push({fullname: link})
}
if (interest_prob) {
this.lineup.push('.interestbar')
}
this.lineup.push('.interestbar')
this.interest_prob = interest_prob
this.promotion_prob = promotion_prob
this.show_promo = show_promo
this.init()
}
r.spotlight.requestPromo = function() {
return $.ajax({
type: "POST",
url: '/api/request_promo',
data: {'r': reddit.post_site}
}).pipe(function(promo) {
if (promo) {
$item = $(promo)
$item.hide().appendTo($('.organic-listing'))
return $item
} else {
return false
}
})
}
r.spotlight.chooseRandom = function() {
var listing = $('.organic-listing')
if (!_.isEmpty(this.weights)
&& Math.random() < this.promotion_prob) {
var campaign_name = this.weighted_lottery(this.weights),
link_name = this.link_by_camp[campaign_name]
return {fullname: link_name, campaign: campaign_name}
if (this.show_promo) {
return this.requestPromo()
} else if (Math.random() < this.interest_prob) {
return '.interestbar'
} else {
@@ -83,27 +86,15 @@ r.spotlight._materialize = function(item) {
if (_.isString(item)) {
itemSel = item
} else if (item.campaign) {
itemSel = '[data-cid="' + item.campaign + '"]'
} else {
itemSel = '[data-fullname="' + item.fullname + '"]'
if (item.campaign) {
itemSel += '[data-cid="' + item.campaign + '"]'
}
}
var $item = listing.find(itemSel)
if ($item.length) {
return $item
} else if (item.campaign) {
r.debug('fetching promo %s from campaign %s', item.fullname, item.campaign)
return $.get('/api/fetch_promo', {
link: item.fullname,
campaign: item.campaign
}).pipe(function (data) {
$item = $(data)
$item.hide().appendTo(listing)
return $item
})
} else {
r.error('unable to locate spotlight item', itemSel, item)
}
@@ -137,6 +128,16 @@ r.spotlight._advance = function(dir) {
return
}
if (!$next) {
if (this.lineup.length > 1) {
this._advance(dir || 1)
return
} else {
listing.hide()
return
}
}
$nextprev.removeClass('working')
listing.removeClass('loading')
@@ -184,17 +185,3 @@ r.spotlight.help = function(thing) {
})
})
}
r.spotlight.weighted_lottery = function(weights) {
var seed_rand = Math.random(),
t = 0
for (var name in weights) {
weight = weights[name]
t += weight
if (t > seed_rand) {
return name
}
}
r.warn('weighted_lottery fell through!')
}

View File

@@ -87,6 +87,14 @@ ${self.RenderPrintable()}
% if hasattr(what, 'campaign'):
data-cid="${what.campaign}"
% endif
% if hasattr(what, 'adserver_imp_pixel'):
data-adserver-imp-pixel="${what.adserver_imp_pixel}"
% endif
% if hasattr(what, 'adserver_click_url'):
data-adserver-click-url="${what.adserver_click_url}"
% endif
</%def>
<%def name="RenderPrintable()">

View File

@@ -41,11 +41,13 @@
</div>
%endif
<div class="nextprev">
<div class="throbber"></div>
<button class="arrow prev">${_("prev")}</button>
<button class="arrow next">${_("next")}</button>
</div>
%if thing.navigable:
<div class="nextprev">
<div class="throbber"></div>
<button class="arrow prev">${_("prev")}</button>
<button class="arrow next">${_("next")}</button>
</div>
%endif
<div class="help help-hoverable">
${_("what's this?")}
@@ -92,9 +94,9 @@
</div>
<script>
r.spotlight.setup(
${unsafe(json.dumps(thing.links))},
${unsafe(json.dumps([link._fullname for link in thing.things]))},
${unsafe(json.dumps(thing.interestbar_prob))},
${unsafe(json.dumps(thing.promotion_prob))}
${unsafe(json.dumps(thing.show_promo))}
)
</script>

View File

@@ -95,6 +95,7 @@ setup(
"lxml",
"kazoo",
"stripe",
"requests",
],
dependency_links=[
"https://github.com/reddit/snudown/archive/v1.1.3.tar.gz#egg=snudown-1.1.3",