Skip to content

Commit

Permalink
Brave Today: send ad service notifications when promoted content is v…
Browse files Browse the repository at this point in the history
…iewed or clicked
  • Loading branch information
petemill committed Jan 21, 2021
1 parent 976e813 commit 0343ea4
Show file tree
Hide file tree
Showing 15 changed files with 119 additions and 17 deletions.
37 changes: 34 additions & 3 deletions browser/ui/webui/new_tab_page/brave_new_tab_message_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,18 @@ void BraveNewTabMessageHandler::RegisterMessages() {
&BraveNewTabMessageHandler::HandleTodayInteractionBegin,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"todayOnCardVisits",
base::BindRepeating(&BraveNewTabMessageHandler::HandleTodayOnCardVisits,
"todayOnCardVisit",
base::BindRepeating(&BraveNewTabMessageHandler::HandleTodayOnCardVisit,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"todayOnCardViews",
base::BindRepeating(&BraveNewTabMessageHandler::HandleTodayOnCardViews,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"todayOnPromotedCardView",
base::BindRepeating(
&BraveNewTabMessageHandler::HandleTodayOnPromotedCardView,
base::Unretained(this)));
}

void BraveNewTabMessageHandler::OnJavascriptAllowed() {
Expand Down Expand Up @@ -565,7 +570,7 @@ void BraveNewTabMessageHandler::HandleTodayInteractionBegin(
base::size(kSessionCountBuckets) + 1);
}

void BraveNewTabMessageHandler::HandleTodayOnCardVisits(
void BraveNewTabMessageHandler::HandleTodayOnCardVisit(
const base::ListValue* args) {
// Argument should be how many cards visited in this session.
// We need the front-end to give us this since this class
Expand All @@ -587,6 +592,19 @@ void BraveNewTabMessageHandler::HandleTodayOnCardVisits(
int answer = it_count - kBuckets;
UMA_HISTOGRAM_EXACT_LINEAR("Brave.Today.WeeklyMaxCardVisitsCount", answer,
base::size(kBuckets) + 1);
// Record ad click if a promoted card was read.
if (args->GetSize() < 4) {
return;
}
std::string item_id = args->GetList()[1].GetString();
std::string creative_instance_id = args->GetList()[2].GetString();
bool is_promoted = args->GetList()[3].GetBool();
if (is_promoted && !item_id.empty() && !creative_instance_id.empty()) {
auto* ads_service_ = brave_ads::AdsServiceFactory::GetForProfile(profile_);
ads_service_->OnPromotedContentAdEvent(
item_id, creative_instance_id,
ads::mojom::BraveAdsPromotedContentAdEventType::kClicked);
}
}

void BraveNewTabMessageHandler::HandleTodayOnCardViews(
Expand All @@ -609,6 +627,19 @@ void BraveNewTabMessageHandler::HandleTodayOnCardViews(
base::size(kBuckets) + 1);
}

void BraveNewTabMessageHandler::HandleTodayOnPromotedCardView(
const base::ListValue* args) {
// Argument should be how many cards viewed in this session.
std::string creative_instance_id = args->GetList()[0].GetString();
std::string item_id = args->GetList()[1].GetString();
if (!item_id.empty() && !creative_instance_id.empty()) {
auto* ads_service_ = brave_ads::AdsServiceFactory::GetForProfile(profile_);
ads_service_->OnPromotedContentAdEvent(
item_id, creative_instance_id,
ads::mojom::BraveAdsPromotedContentAdEventType::kViewed);
}
}

void BraveNewTabMessageHandler::OnPrivatePropertiesChanged() {
PrefService* prefs = profile_->GetPrefs();
auto data = GetPrivatePropertiesDictionary(prefs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ class BraveNewTabMessageHandler : public content::WebUIMessageHandler,
// TODO(petemill): Today should get it's own message handler
// or service.
void HandleTodayInteractionBegin(const base::ListValue* args);
void HandleTodayOnCardVisits(const base::ListValue* args);
void HandleTodayOnCardVisit(const base::ListValue* args);
void HandleTodayOnCardViews(const base::ListValue* args);
void HandleTodayOnPromotedCardView(const base::ListValue* args);

void OnStatsChanged();
void OnPreferencesChanged();
Expand Down
3 changes: 3 additions & 0 deletions components/brave_new_tab_ui/actions/today_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ export const errorGettingDataFromBackground = createAction<BackgroundErrorPayloa
*/
export type ReadFeedItemPayload = {
item: BraveToday.FeedItem,
isPromoted?: boolean,
openInNewTab?: boolean
}
export const readFeedItem = createAction<ReadFeedItemPayload>('readFeedItem')

export const feedItemViewedCountChanged = createAction<number>('feedItemViewedCountChanged')

export const promotedItemViewed = createAction<BraveToday.PromotedArticle>('promotedItemViewed')

export type SetPublisherPrefPayload = {
publisherId: string
enabled: boolean | null
Expand Down
19 changes: 18 additions & 1 deletion components/brave_new_tab_ui/async/today.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,17 @@ handler.on(Actions.ensureSettingsData.getType(), async (store) => {
handler.on<Actions.ReadFeedItemPayload>(Actions.readFeedItem.getType(), async (store, payload) => {
const state = store.getState() as ApplicationState
const todayPageIndex = state.today.currentPageIndex
chrome.send('todayOnCardVisits', [state.today.cardsVisited])
const backendArgs: any[] = [
state.today.cardsVisited
]
if (payload.isPromoted) {
backendArgs.push(
payload.item.url_hash,
(payload.item as BraveToday.PromotedArticle).creative_instance_id,
payload.isPromoted
)
}
chrome.send('todayOnCardVisit', backendArgs)
if (!payload.openInNewTab) {
// remember article so we can scroll to it on "back" navigation
// TODO(petemill): Type this history.state data and put in an API module
Expand All @@ -75,6 +85,13 @@ handler.on<Actions.ReadFeedItemPayload>(Actions.readFeedItem.getType(), async (s
}
})

handler.on<BraveToday.PromotedArticle>(Actions.promotedItemViewed.getType(), async (store, item) => {
chrome.send('todayOnPromotedCardView', [
item.creative_instance_id,
item.url_hash
])
})

handler.on<number>(Actions.feedItemViewedCountChanged.getType(), async (store, payload) => {
const state = store.getState() as ApplicationState
chrome.send('todayOnCardViews', [state.today.cardsViewed])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import PublisherMeta from '../PublisherMeta'
import useScrollIntoView from '../../useScrollIntoView'
import useReadArticleClickHandler from '../../useReadArticleClickHandler'
import { OnReadFeedItem, OnSetPublisherPref } from '../../'
import VisibilityTimer from '../../../../../helpers/visibilityTimer'
// TODO(petemill): Large and Medium article should be combined to 1 component.

interface Props {
Expand All @@ -18,6 +19,7 @@ interface Props {
articleToScrollTo?: BraveToday.FeedItem
onReadFeedItem: OnReadFeedItem
onSetPublisherPref: OnSetPublisherPref
onItemViewed?: (item: BraveToday.FeedItem) => any
isPromoted?: boolean
}

Expand All @@ -27,6 +29,7 @@ type ArticleProps = {
shouldScrollIntoView?: boolean
onReadFeedItem: OnReadFeedItem
onSetPublisherPref: OnSetPublisherPref
onItemViewed?: (item: BraveToday.FeedItem) => any
isPromoted?: boolean
}

Expand All @@ -42,16 +45,46 @@ function onClickPromoted (e: React.MouseEvent) {
e.preventDefault()
}

const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props: ArticleProps, ref) {
const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props: ArticleProps, forwardedRef) {
const { publisher, item } = props
const [cardRef] = useScrollIntoView(props.shouldScrollIntoView || false)

const onClick = useReadArticleClickHandler(props.onReadFeedItem, item)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, { item, isPromoted: props.isPromoted })

const innerRef = React.useRef<HTMLElement>(null)

React.useEffect(() => {
if (!innerRef.current) {
return
}
// Pass ref to parent
if (forwardedRef) {
if (typeof forwardedRef === 'function') {
forwardedRef(innerRef.current)
} else {
// @ts-ignore
// Ref.current is meant to be readonly, but we can ignore that.
ref.current = newRef
}
}
// If asked, detect when card is viewed, and send an action.
if (!props.onItemViewed) {
return
}
let onItemViewed = props.onItemViewed
const observer = new VisibilityTimer(() => {
onItemViewed(item)
}, 100, innerRef.current)
observer.startTracking()
return () => {
observer.stopTracking()
}
}, [innerRef.current, props.onItemViewed])

// TODO(petemill): Avoid nested links
// `ref as any` due to https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884
return (
<Card.Large innerRef={ref as any}>
<Card.Large innerRef={innerRef}>
<a onClick={onClick} href={item.url} ref={cardRef}>
<CardImage
imageUrl={item.img}
Expand Down Expand Up @@ -121,6 +154,7 @@ const CardSingleArticleLarge = React.forwardRef<HTMLElement, Props>(function (pr
shouldScrollIntoView={shouldScrollIntoView}
onReadFeedItem={props.onReadFeedItem}
onSetPublisherPref={props.onSetPublisherPref}
onItemViewed={props.onItemViewed}
isPromoted={props.isPromoted}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ type ArticleProps = {
function MediumArticle (props: ArticleProps) {
const { publisher, item } = props
const [cardRef] = useScrollIntoView(props.shouldScrollIntoView || false)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, item)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, {
item
})
return (
<Card.Small>
<a onClick={onClick} href={item.url} ref={cardRef}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ type ListItemProps = {

function ListItem (props: ListItemProps) {
const [cardRef] = useScrollIntoView(props.shouldScrollIntoView)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, props.item)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, {
item: props.item
})
return (
<Card.DealItem innerRef={cardRef} onClick={onClick} href={props.item.url}>
<CardImage imageUrl={props.item.padded_img} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ type ListItemProps = {

function ListItem (props: ListItemProps) {
const [cardRef] = useScrollIntoView(props.shouldScrollIntoView)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, props.item)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, {
item: props.item
})
return (
<Card.ListItem>
<a onClick={onClick} href={props.item.url} ref={cardRef}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ type ListItemProps = {

function ListItem (props: ListItemProps) {
const [cardRef] = useScrollIntoView(props.shouldScrollIntoView)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, props.item)
const onClick = useReadArticleClickHandler(props.onReadFeedItem, {
item: props.item
})
return (
<Card.ListItem>
<a onClick={onClick} href={props.item.url} ref={cardRef}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import CardSmall from './cards/_articles/cardArticleMedium'
import CategoryGroup from './cards/categoryGroup'
import PublisherGroup from './cards/publisherGroup'
import CardDeals from './cards/cardDeals'
import { attributeNameCardCount, OnReadFeedItem, OnSetPublisherPref } from './'
import { attributeNameCardCount, OnPromotedItemViewed, OnReadFeedItem, OnSetPublisherPref } from './'

// Disabled rules because we have a function
// which returns elements in a switch.
Expand Down Expand Up @@ -58,6 +58,7 @@ type Props = {
onReadFeedItem: OnReadFeedItem
onSetPublisherPref: OnSetPublisherPref
onPeriodicCardViews: (element: HTMLElement | null) => void
onPromotedItemViewed: OnPromotedItemViewed
}

type CardProps = Props & {
Expand Down Expand Up @@ -97,6 +98,7 @@ function Card (props: CardProps) {
articleToScrollTo={props.articleToScrollTo}
onReadFeedItem={props.onReadFeedItem}
onSetPublisherPref={props.onSetPublisherPref}
onItemViewed={props.onPromotedItemViewed}
/>
case CardType.CategoryGroup:
if (!props.content.itemsByCategory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export default function BraveTodayContent (props: Props) {
onReadFeedItem={props.onReadFeedItem}
onPeriodicCardViews={registerCardCountTriggerElement}
onSetPublisherPref={props.onSetPublisherPref}
onPromotedItemViewed={props.onPromotedItemViewed}
/>
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type State = {

export type OnReadFeedItem = (args: ReadFeedItemPayload) => any
export type OnSetPublisherPref = (publisherId: string, enabled: boolean) => any
export type OnPromotedItemViewed = (item: BraveToday.FeedItem) => any

export type Props = {
isFetching: boolean
Expand All @@ -28,6 +29,7 @@ export type Props = {
displayedPageCount: number
onInteracting: (interacting: boolean) => any
onReadFeedItem: OnReadFeedItem
onPromotedItemViewed: OnPromotedItemViewed
onFeedItemViewedCountChanged: (feedItemsViewed: number) => any
onSetPublisherPref: OnSetPublisherPref
onAnotherPageNeeded: () => any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
// you can obtain one at http://mozilla.org/MPL/2.0/.

import * as React from 'react'
import { ReadFeedItemPayload } from '../../../actions/today_actions'
import { OnReadFeedItem } from './'

export default function useReadArticleClickHandler (action: OnReadFeedItem, item: BraveToday.FeedItem) {
export default function useReadArticleClickHandler (action: OnReadFeedItem, payloadData: ReadFeedItemPayload) {
return React.useCallback((e: React.MouseEvent) => {
const openInNewTab = e.ctrlKey || e.metaKey
action({ item, openInNewTab })
action({ ...payloadData, openInNewTab })
e.preventDefault()
}, [action, item])
}, [action, payloadData.item, payloadData.isPromoted])
}
1 change: 1 addition & 0 deletions components/brave_new_tab_ui/containers/newTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@ class NewTabPage extends React.Component<Props, State> {
// tslint:disable-next-line:jsx-no-lambda
onCustomizeBraveToday={() => { this.openSettings(SettingsTabType.BraveToday) }}
onReadFeedItem={this.props.actions.today.readFeedItem}
onPromotedItemViewed={this.props.actions.today.promotedItemViewed}
onSetPublisherPref={this.props.actions.today.setPublisherPref}
onCheckForUpdate={this.props.actions.today.checkForUpdate}
onReadCardIntro={this.props.onReadBraveTodayIntroCard}
Expand Down
3 changes: 2 additions & 1 deletion components/definitions/today.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ declare namespace BraveToday {
}

export type PromotedArticle = BaseFeedItem & {
content_type: 'brave_partner'
content_type: 'brave_partner',
creative_instance_id: string // (Possibly but not guaranteed unique) Id for this specific item
}

export type Deal = BaseFeedItem & {
Expand Down

0 comments on commit 0343ea4

Please sign in to comment.