mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-04-27 03:00:12 -04:00
Use an ad server to select promos for spotlight.
Replaces client side weighted random selection.
This commit is contained in:
committed by
Brian Simpson
parent
7423c17b39
commit
5967acfa83
@@ -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 =
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -198,6 +198,7 @@ class Globals(object):
|
||||
'wiki_page_registration_info',
|
||||
'wiki_page_privacy_policy',
|
||||
'wiki_page_user_agreement',
|
||||
'adserver_click_domain',
|
||||
],
|
||||
|
||||
ConfigValue.choice: {
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!')
|
||||
}
|
||||
|
||||
@@ -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()">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user