From 50d28b2e1b973aac93098abd7a5f45b962e119fd Mon Sep 17 00:00:00 2001 From: vanilla-wave Date: Mon, 30 Sep 2024 00:54:37 +0200 Subject: [PATCH 1/3] feat(promo-manager): add repeated promos --- src/promo-manager/core/controller.ts | 20 +++++++++++------ src/promo-manager/core/types.ts | 3 ++- src/promo-manager/tests/controller.test.ts | 26 +++++++++++++++++++++- src/promo-manager/tests/options.ts | 4 ++-- src/promo-manager/tests/promoGroups.ts | 17 ++++++++++++++ 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/promo-manager/core/controller.ts b/src/promo-manager/core/controller.ts index 30c2bab..6322d7f 100644 --- a/src/promo-manager/core/controller.ts +++ b/src/promo-manager/core/controller.ts @@ -244,7 +244,15 @@ export class Controller { } if (this.isFinished(slug)) { - return 'finished'; + if (!this.helpers.promoBySlug[slug].repeatable) { + return 'finished'; + } + + if (this.checkPromoConditions(slug)) { + return 'canReRun'; + } else { + return 'forbidden'; + } } if (this.isPending(slug)) { @@ -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) => { @@ -575,11 +585,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; } diff --git a/src/promo-manager/core/types.ts b/src/promo-manager/core/types.ts index 2a8985c..7adaba2 100644 --- a/src/promo-manager/core/types.ts +++ b/src/promo-manager/core/types.ts @@ -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'; @@ -18,6 +18,7 @@ export type Promo = { priority?: Priority; meta?: T; trigger?: Trigger; + repeatable?: true; }; export type PromoGroup = { diff --git a/src/promo-manager/tests/controller.test.ts b/src/promo-manager/tests/controller.test.ts index eb3e994..b9f91f8 100644 --- a/src/promo-manager/tests/controller.test.ts +++ b/src/promo-manager/tests/controller.test.ts @@ -67,7 +67,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'); @@ -77,6 +77,30 @@ describe('active promo', () => { }); }); +describe('repeatred 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('boardPollRepeat'); + await controller.finishPromo('boardPollRepeat'); + + await controller.requestStart('boardPollRepeat'); + + expect(controller.state.base.activePromo).toBe('boardPollRepeat'); + }); +}); + describe('trigger subscribe', () => { let controller: Controller; diff --git a/src/promo-manager/tests/options.ts b/src/promo-manager/tests/options.ts index 6d80425..cb322fa 100644 --- a/src/promo-manager/tests/options.ts +++ b/src/promo-manager/tests/options.ts @@ -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, diff --git a/src/promo-manager/tests/promoGroups.ts b/src/promo-manager/tests/promoGroups.ts index 7887d58..16dff06 100644 --- a/src/promo-manager/tests/promoGroups.ts +++ b/src/promo-manager/tests/promoGroups.ts @@ -57,6 +57,23 @@ export const pollGroup2: PromoGroup = { ], }; +export const repeatedPoll: PromoGroup = { + slug: 'pollRepeat', + conditions: [], + promos: [ + { + slug: 'boardPollRepeat', + repeatable: true, + conditions: [], + }, + { + slug: 'ganttPollRepeat', + repeatable: true, + conditions: [], + }, + ], +}; + export const pollWithConditions: PromoGroup = { slug: 'ask', conditions: [ShowOnceForPeriod({months: 1})], From 247df0276f43e16085f4535d559d3f732c4b2be1 Mon Sep 17 00:00:00 2001 From: vanilla-wave Date: Mon, 30 Sep 2024 01:04:16 +0200 Subject: [PATCH 2/3] test(promo-manager): add promo status tests --- src/promo-manager/core/controller.ts | 14 ++- src/promo-manager/core/types.ts | 3 +- src/promo-manager/tests/controller.test.ts | 115 ++++++++++++++++++++- src/promo-manager/tests/promoGroups.ts | 9 +- 4 files changed, 132 insertions(+), 9 deletions(-) diff --git a/src/promo-manager/core/controller.ts b/src/promo-manager/core/controller.ts index 6322d7f..ac40ff5 100644 --- a/src/promo-manager/core/controller.ts +++ b/src/promo-manager/core/controller.ts @@ -244,7 +244,7 @@ export class Controller { } if (this.isFinished(slug)) { - if (!this.helpers.promoBySlug[slug].repeatable) { + if (!this.isPromoRepeatable(slug)) { return 'finished'; } @@ -551,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); diff --git a/src/promo-manager/core/types.ts b/src/promo-manager/core/types.ts index 7adaba2..8ae4be6 100644 --- a/src/promo-manager/core/types.ts +++ b/src/promo-manager/core/types.ts @@ -18,11 +18,12 @@ export type Promo = { priority?: Priority; meta?: T; trigger?: Trigger; - repeatable?: true; + repeatable?: boolean; }; export type PromoGroup = { slug: PromoGroupSlug; + repeatable?: boolean; conditions?: Condition[]; promos: Promo[]; }; diff --git a/src/promo-manager/tests/controller.test.ts b/src/promo-manager/tests/controller.test.ts index b9f91f8..1918ec0 100644 --- a/src/promo-manager/tests/controller.test.ts +++ b/src/promo-manager/tests/controller.test.ts @@ -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; @@ -77,7 +78,111 @@ describe('active promo', () => { }); }); -describe('repeatred runs', function () { +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); @@ -92,12 +197,12 @@ describe('repeatred runs', function () { it('repeated promo -> can rerun', async function () { const controller = new Controller(testOptions); - await controller.requestStart('boardPollRepeat'); - await controller.finishPromo('boardPollRepeat'); + await controller.requestStart('boardPollRepeatable'); + await controller.finishPromo('boardPollRepeatable'); - await controller.requestStart('boardPollRepeat'); + await controller.requestStart('boardPollRepeatable'); - expect(controller.state.base.activePromo).toBe('boardPollRepeat'); + expect(controller.state.base.activePromo).toBe('boardPollRepeatable'); }); }); diff --git a/src/promo-manager/tests/promoGroups.ts b/src/promo-manager/tests/promoGroups.ts index 16dff06..9b6de7d 100644 --- a/src/promo-manager/tests/promoGroups.ts +++ b/src/promo-manager/tests/promoGroups.ts @@ -62,15 +62,20 @@ export const repeatedPoll: PromoGroup = { conditions: [], promos: [ { - slug: 'boardPollRepeat', + slug: 'boardPollRepeatable', repeatable: true, conditions: [], }, { - slug: 'ganttPollRepeat', + slug: 'ganttPollRepeatable', repeatable: true, conditions: [], }, + { + slug: 'forbiddenRepeatablePoll', + repeatable: true, + conditions: [() => false], + }, ], }; From 9b9ff3a8243a67899db39652546968a623c39211 Mon Sep 17 00:00:00 2001 From: vanilla-wave Date: Tue, 1 Oct 2024 12:08:27 +0200 Subject: [PATCH 3/3] chore(promo-manager): increase promo-manager limit to 9Kb --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 03999e9..7bd6610 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -5,6 +5,6 @@ module.exports = [ }, { "path": "src/promo-manager.ts", - "limit": "8.5 kB" + "limit": "9 kB" } ]