Skip to content

Commit

Permalink
Merge pull request #118 from gravity-ui/repeated-promo
Browse files Browse the repository at this point in the history
feat(promo-manager): add repeated promos
  • Loading branch information
vanilla-wave authored Oct 1, 2024
2 parents 0966d01 + 9b9ff3a commit f90a9bd
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 12 deletions.
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

0 comments on commit f90a9bd

Please sign in to comment.