Skip to content

Commit

Permalink
Update frequency capping conditions on ContentType, Repeatability (#3576
Browse files Browse the repository at this point in the history
)

* account for repeatable completions for globalfreq

* include completions of same prompt in globalfreq

* add tests

---------

Co-authored-by: justinchou-google <justinchou-google@users.noreply.github.com>
  • Loading branch information
justinchou-google and justinchou-google authored Sep 27, 2024
1 parent 2205cf6 commit 6d49d1b
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 20 deletions.
133 changes: 126 additions & 7 deletions src/runtime/auto-prompt-manager-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,45 @@ describes.realWin('AutoPromptManager', (env) => {

await autoPromptManager.storeCompletion('TYPE_REWARDED_SURVEY');
});

[
{eventType: AnalyticsEvent.IMPRESSION_CONTRIBUTION_OFFERS},
{eventType: AnalyticsEvent.IMPRESSION_NEWSLETTER_OPT_IN},
{eventType: AnalyticsEvent.IMPRESSION_BYOP_NEWSLETTER_OPT_IN},
{eventType: AnalyticsEvent.IMPRESSION_REGWALL_OPT_IN},
{eventType: AnalyticsEvent.IMPRESSION_SURVEY},
{eventType: AnalyticsEvent.IMPRESSION_REWARDED_AD},
{eventType: AnalyticsEvent.IMPRESSION_OFFERS},
{eventType: AnalyticsEvent.ACTION_CONTRIBUTION_OFFERS_CLOSED},
{eventType: AnalyticsEvent.ACTION_NEWSLETTER_OPT_IN_CLOSE},
{eventType: AnalyticsEvent.ACTION_BYOP_NEWSLETTER_OPT_IN_CLOSE},
{eventType: AnalyticsEvent.ACTION_REGWALL_OPT_IN_CLOSE},
{eventType: AnalyticsEvent.ACTION_SURVEY_CLOSED},
{eventType: AnalyticsEvent.ACTION_REWARDED_AD_CLOSE},
{eventType: AnalyticsEvent.ACTION_SUBSCRIPTION_OFFERS_CLOSED},
{eventType: AnalyticsEvent.EVENT_CONTRIBUTION_PAYMENT_COMPLETE},
{eventType: AnalyticsEvent.ACTION_NEWSLETTER_OPT_IN_BUTTON_CLICK},
{eventType: AnalyticsEvent.ACTION_BYOP_NEWSLETTER_OPT_IN_SUBMIT},
{eventType: AnalyticsEvent.ACTION_REGWALL_OPT_IN_BUTTON_CLICK},
{eventType: AnalyticsEvent.ACTION_SURVEY_SUBMIT_CLICK},
{eventType: AnalyticsEvent.ACTION_REWARDED_AD_VIEW},
{eventType: AnalyticsEvent.EVENT_SUBSCRIPTION_PAYMENT_COMPLETE},
].forEach(({eventType}) => {
it(`with actionOrhcestrationExperiment enabled, should not store impression timestamps for event ${eventType} for Closed contentType`, async () => {
autoPromptManager.actionOrchestrationExperiment_ = true;
autoPromptManager.contentType_ = ContentType.CLOSED;
autoPromptManager.isClosable_ = true;
storageMock.expects('get').never();
storageMock.expects('set').never();

await eventManagerCallback({
eventType,
eventOriginator: EventOriginator.UNKNOWN_CLIENT,
isFromUserAction: null,
additionalParameters: null,
});
});
});
});

describe('Miniprompt', () => {
Expand Down Expand Up @@ -1128,13 +1167,14 @@ describes.realWin('AutoPromptManager', (env) => {

// Dismissal Events
[
{eventType: AnalyticsEvent.ACTION_CONTRIBUTION_OFFERS_CLOSED},
{eventType: AnalyticsEvent.ACTION_NEWSLETTER_OPT_IN_CLOSE},
{eventType: AnalyticsEvent.ACTION_BYOP_NEWSLETTER_OPT_IN_CLOSE},
{eventType: AnalyticsEvent.ACTION_REGWALL_OPT_IN_CLOSE},
{eventType: AnalyticsEvent.ACTION_SURVEY_CLOSED},
{eventType: AnalyticsEvent.ACTION_REWARDED_AD_CLOSE},
{eventType: AnalyticsEvent.ACTION_SUBSCRIPTION_OFFERS_CLOSED},
{
eventType: AnalyticsEvent.ACTION_SWG_CONTRIBUTION_MINI_PROMPT_CLOSE,
action: 'TYPE_CONTRIBUTION',
},
{
eventType: AnalyticsEvent.ACTION_SWG_SUBSCRIPTION_MINI_PROMPT_CLOSE,
action: 'TYPE_SUBSCRIPTION',
},
].forEach(({eventType}) => {
it(`should not store dismissal timestamps for miniprompt event ${eventType} for nondismissible prompts`, async () => {
autoPromptManager.isClosable_ = false;
Expand Down Expand Up @@ -1175,6 +1215,32 @@ describes.realWin('AutoPromptManager', (env) => {
});
});

[
{eventType: AnalyticsEvent.IMPRESSION_SWG_CONTRIBUTION_MINI_PROMPT},
{eventType: AnalyticsEvent.IMPRESSION_SWG_SUBSCRIPTION_MINI_PROMPT},
{
eventType: AnalyticsEvent.ACTION_SWG_CONTRIBUTION_MINI_PROMPT_CLOSE,
},
{
eventType: AnalyticsEvent.ACTION_SWG_SUBSCRIPTION_MINI_PROMPT_CLOSE,
},
].forEach(({eventType}) => {
it(`with actionOrhcestrationExperiment enabled, should not store impression timestamps for event ${eventType} for Closed contentType`, async () => {
autoPromptManager.actionOrchestrationExperiment_ = true;
autoPromptManager.contentType_ = ContentType.CLOSED;
autoPromptManager.isClosable_ = true;
storageMock.expects('get').never();
storageMock.expects('set').never();

await eventManagerCallback({
eventType,
eventOriginator: EventOriginator.UNKNOWN_CLIENT,
isFromUserAction: null,
additionalParameters: null,
});
});
});

it('should log events when a large prompt overrides the miniprompt', async () => {
win./*OK*/ innerWidth = 500;
const expectedEvent = {
Expand Down Expand Up @@ -3121,6 +3187,59 @@ describes.realWin('AutoPromptManager', (env) => {
});
});

it('should not show a repeatable intervention due to past completion within the global frequency cap', async () => {
getArticleExpectation
.resolves({
audienceActions: {
actions: [
{
type: 'TYPE_BYO_CTA',
configurationId: 'byocta_config_id',
numberOfCompletions: 10,
},
],
engineId: '123',
},
actionOrchestration: {
interventionFunnel: {
globalFrequencyCap: {
duration: {
seconds: 500,
},
},
interventions: [
{
configId: 'byocta_config_id',
type: 'TYPE_BYO_CTA',
duration: {
seconds: 100,
},
closability: 'DISMISSIBLE',
repeatability: {
type: 'INFINITE',
},
},
],
},
},
experimentConfig: {
experimentFlags: ['action_orchestration_experiment'],
},
})
.once();
expectFrequencyCappingTimestamps(storageMock, {
'TYPE_BYO_CTA': {
completions: [CURRENT_TIME - 200],
},
});

await autoPromptManager.showAutoPrompt({contentType: ContentType.OPEN});
await tick(20);

expect(startSpy).to.not.have.been.called;
expect(actionFlowSpy).to.not.have.been.called;
});

it('preview should show a dismissble prompt when ContentType OPEN', async () => {
getArticleExpectation
.resolves({
Expand Down
38 changes: 25 additions & 13 deletions src/runtime/auto-prompt-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export class AutoPromptManager {
private lastAudienceActionFlow_: AudienceActionFlow | null = null;
private isClosable_: boolean | undefined;
private autoPromptType_: AutoPromptType | undefined;
private contentType_: ContentType | undefined;
private shouldRenderOnsitePreview_: boolean = false;
private actionOrchestrationExperiment_: boolean = false;

Expand Down Expand Up @@ -191,13 +192,15 @@ export class AutoPromptManager {
return;
}

this.contentType_ = params.contentType;

// Manual override of display rules, mainly for demo purposes. Requires
// contribution or subscription to be set as autoPromptType in snippet.
if (params.alwaysShow) {
this.autoPromptType_ = this.getPromptTypeToDisplay_(
params.autoPromptType
);
this.isClosable_ = params.contentType != ContentType.CLOSED;
this.isClosable_ = this.contentType_ != ContentType.CLOSED;
const promptFn = this.getMonetizationPromptFn_();
promptFn();
return;
Expand Down Expand Up @@ -257,7 +260,7 @@ export class AutoPromptManager {

// For FPA M0.5 - default to the contentType.
// TODO(b/364344782): Determine closability for FPA M1+.
this.isClosable_ = params.contentType != ContentType.CLOSED;
this.isClosable_ = this.contentType_ != ContentType.CLOSED;

const previewAction = actions[0];

Expand Down Expand Up @@ -303,8 +306,7 @@ export class AutoPromptManager {
// FPA M0.5 Flow: get next Intervention of the Targeted Funnel.
const nextOrchestration = await this.getInterventionOrchestration_(
clientConfig,
article,
params.contentType
article
);

if (!!nextOrchestration) {
Expand All @@ -316,7 +318,7 @@ export class AutoPromptManager {
this.isClosable_ = true;
break;
default:
this.isClosable_ = params.contentType != ContentType.CLOSED;
this.isClosable_ = this.contentType_ != ContentType.CLOSED;
}
potentialAction = article.audienceActions?.actions?.find(
(action) => action.configurationId === nextOrchestration.configId
Expand Down Expand Up @@ -502,8 +504,7 @@ export class AutoPromptManager {

private async getInterventionOrchestration_(
clientConfig: ClientConfig,
article: Article,
contentType: ContentType
article: Article
): Promise<InterventionOrchestration | void> {
const eligibleActions = article.audienceActions?.actions;
let interventionOrchestration =
Expand Down Expand Up @@ -544,7 +545,7 @@ export class AutoPromptManager {
return;
}

if (contentType === ContentType.CLOSED) {
if (this.contentType_ === ContentType.CLOSED) {
return interventionOrchestration[0];
}

Expand Down Expand Up @@ -598,9 +599,11 @@ export class AutoPromptManager {
if (this.isValidFrequencyCapDuration_(globalFrequencyCapDuration)) {
const globalTimestamps = Array.prototype.concat.apply(
[],
Object.entries(actionsTimestamps!)
.filter(([type, _]) => type !== nextOrchestration!.type)
.map(([_, timestamps]) => timestamps.impressions)
Object.entries(actionsTimestamps!).map(([type, timestamps]) =>
type === nextOrchestration!.type
? timestamps.completions // Completed repeatable actions count towards global frequency
: timestamps.impressions
)
);
if (
this.isFrequencyCapped_(globalFrequencyCapDuration!, globalTimestamps)
Expand Down Expand Up @@ -782,8 +785,17 @@ export class AutoPromptManager {
private async handleFrequencyCappingLocalStorage_(
analyticsEvent: AnalyticsEvent
): Promise<void> {
if (!this.isClosable_) {
return;
// For FPA M0.5, do not log frequency capping event for closed contentType. Blocking
// interventions on Open content will still log impression & completion timestamps
// (but not dismissal).
if (this.actionOrchestrationExperiment_) {
if (this.contentType_ === ContentType.CLOSED) {
return;
}
} else {
if (!this.isClosable_) {
return;
}
}

if (
Expand Down

0 comments on commit 6d49d1b

Please sign in to comment.