Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(promo-manager): add repeated promos #118

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ module.exports = [
},
{
"path": "src/promo-manager.ts",
"limit": "8.5 kB"
"limit": "9 kB"
}
]
32 changes: 25 additions & 7 deletions src/promo-manager/core/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,15 @@ export class Controller {
}

if (this.isFinished(slug)) {
return 'finished';
if (!this.isPromoRepeatable(slug)) {
return 'finished';
}

if (this.checkPromoConditions(slug)) {
return 'canReRun';
} else {
return 'forbidden';
}
}

if (this.isPending(slug)) {
Expand Down Expand Up @@ -524,7 +532,9 @@ export class Controller {
}

private isAbleToRun = (slug: PromoSlug) => {
return this.getPromoStatus(slug) === 'canRun';
const status = this.getPromoStatus(slug);

return status === 'canRun' || status === 'canReRun';
};

private isActive = (slug: PromoSlug) => {
Expand All @@ -541,6 +551,18 @@ export class Controller {
return this.state.base.activeQueue.includes(slug);
};

private isPromoRepeatable = (slug: PromoSlug) => {
const isPromoRepeatable = this.helpers.promoBySlug[slug].repeatable;

const groupSlug = this.getGroupBySlug(slug);
const group = this.options.config.promoGroups.find(
(currentGroup) => currentGroup.slug === groupSlug,
);
const isGroupRepeatable = Boolean(group?.repeatable);

return isPromoRepeatable || isGroupRepeatable;
};

private activatePromo = (slug: PromoSlug) => {
this.logger.debug('Activate promo', slug);
this.stateActions.setActivePromo(slug);
Expand Down Expand Up @@ -575,11 +597,7 @@ export class Controller {
this.logger.debug('Add promo to the queue', slug);
this.assertProgressLoaded();

if (
this.state.progress.finishedPromos.includes(slug) ||
this.state.base.activeQueue.includes(slug) ||
!this.checkPromoConditions(slug)
) {
if (this.state.base.activeQueue.includes(slug) || !this.checkPromoConditions(slug)) {
return;
}

Expand Down
4 changes: 3 additions & 1 deletion src/promo-manager/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {LoggerOptions} from '../../logger';
import {Controller as OnboardingController} from '../../controller';
import {Controller} from './controller';

export type PromoStatus = 'forbidden' | 'canRun' | 'active' | 'finished' | 'pending';
export type PromoStatus = 'forbidden' | 'canRun' | 'canReRun' | 'active' | 'finished' | 'pending';
export type Priority = 'high';
export type PromoManagerStatus = 'idle' | 'initialized';

Expand All @@ -18,10 +18,12 @@ export type Promo<T = PromoMeta> = {
priority?: Priority;
meta?: T;
trigger?: Trigger;
repeatable?: boolean;
};

export type PromoGroup<Config = PromoMeta> = {
slug: PromoGroupSlug;
repeatable?: boolean;
conditions?: Condition[];
promos: Promo<Config>[];
};
Expand Down
131 changes: 130 additions & 1 deletion src/promo-manager/tests/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Controller} from '../core/controller';
import {testOptions} from './options';
import {testMetaInfo} from './promoGroups';
import {waitForNextTick} from './utils';
import {PromoGroup} from '../core/types';

describe('active promo', () => {
let controller: Controller;
Expand Down Expand Up @@ -67,7 +68,7 @@ describe('active promo', () => {
expect(controller.state.base.activePromo).toBe('ganttPoll');
});

it('run 2 duplicates -> finish promo -> not trigger next', async () => {
it('2 request and finish promo -> not trigger next', async () => {
await controller.requestStart('boardPoll');
await controller.requestStart('boardPoll');

Expand All @@ -77,6 +78,134 @@ describe('active promo', () => {
});
});

describe('promo status', function () {
it('no progress -> forbidden', function () {
const controller = new Controller({...testOptions, progressState: undefined});

const status = controller.getPromoStatus('boardPoll');

expect(status).toBe('forbidden');
});

it('active promo -> active', async function () {
const controller = new Controller(testOptions);
await controller.requestStart('boardPoll');

const status = controller.getPromoStatus('boardPoll');

expect(status).toBe('active');
});

it('common finished promo -> finished', async function () {
const controller = new Controller(testOptions);
await controller.requestStart('boardPoll');
await controller.finishPromo('boardPoll');

const status = controller.getPromoStatus('boardPoll');

expect(status).toBe('finished');
});

it('promo in queue -> pending', async function () {
const controller = new Controller(testOptions);
await controller.requestStart('ganttPoll');
await controller.requestStart('boardPoll');

const status = controller.getPromoStatus('boardPoll');

expect(status).toBe('pending');
});

it('pass conditions -> canRun', async function () {
const controller = new Controller(testOptions);

const status = controller.getPromoStatus('boardPoll');

expect(status).toBe('canRun');
});

it('not pass conditions -> forbidden', async function () {
const controller = new Controller(testOptions);

const status = controller.getPromoStatus('pastDayPoll');

expect(status).toBe('forbidden');
});

describe('repeatable promos', function () {
it('repeatable finished promo -> canReRun', async function () {
const controller = new Controller(testOptions);
await controller.requestStart('boardPollRepeatable');
await controller.finishPromo('boardPollRepeatable');

const status = controller.getPromoStatus('boardPollRepeatable');

expect(status).toBe('canReRun');
});

it('repeatable promo not pass conditions -> forbidden', async function () {
const controller = new Controller(testOptions);
await controller.requestStart('forbiddenRepeatablePoll');
await controller.finishPromo('forbiddenRepeatablePoll');

const status = controller.getPromoStatus('forbiddenRepeatablePoll');

expect(status).toBe('forbidden');
});

it('finished promo in repeatable group', async function () {
const repeatableGroup: PromoGroup = {
slug: 'pollRepeat',
repeatable: true,
conditions: [],
promos: [
{
slug: 'boardPollRepeatable',
conditions: [],
},
],
};

const controller = new Controller({
...testOptions,
config: {
promoGroups: [repeatableGroup],
},
});
await controller.requestStart('boardPollRepeatable');
await controller.finishPromo('boardPollRepeatable');

const status = controller.getPromoStatus('boardPollRepeatable');

expect(status).toBe('canReRun');
});
});
});

describe('repeated runs', function () {
it('common promo -> cannot run', async function () {
const controller = new Controller(testOptions);

await controller.requestStart('boardPoll');
await controller.finishPromo('boardPoll');

await controller.requestStart('boardPoll');

expect(controller.state.base.activePromo).toBe(null);
});

it('repeated promo -> can rerun', async function () {
const controller = new Controller(testOptions);

await controller.requestStart('boardPollRepeatable');
await controller.finishPromo('boardPollRepeatable');

await controller.requestStart('boardPollRepeatable');

expect(controller.state.base.activePromo).toBe('boardPollRepeatable');
});
});

describe('trigger subscribe', () => {
let controller: Controller;

Expand Down
4 changes: 2 additions & 2 deletions src/promo-manager/tests/options.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {PromoProgressState} from '../core/types';

import {pollGroup, pollGroup2, pollWithConditions} from './promoGroups';
import {pollGroup, pollGroup2, pollWithConditions, repeatedPoll} from './promoGroups';

export const testOptions = {
config: {
promoGroups: [pollGroup, pollGroup2, pollWithConditions],
promoGroups: [pollGroup, pollGroup2, pollWithConditions, repeatedPoll],
init: {
initType: 'timeout' as const,
timeout: 0,
Expand Down
22 changes: 22 additions & 0 deletions src/promo-manager/tests/promoGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ export const pollGroup2: PromoGroup = {
],
};

export const repeatedPoll: PromoGroup = {
slug: 'pollRepeat',
conditions: [],
promos: [
{
slug: 'boardPollRepeatable',
repeatable: true,
conditions: [],
},
{
slug: 'ganttPollRepeatable',
repeatable: true,
conditions: [],
},
{
slug: 'forbiddenRepeatablePoll',
repeatable: true,
conditions: [() => false],
},
],
};

export const pollWithConditions: PromoGroup = {
slug: 'ask',
conditions: [ShowOnceForPeriod({months: 1})],
Expand Down
Loading