Award mystery items at cron (#11519)

* feat(subscriber): award mystery items at cron

* fix(mystery): backfill skipped months

* fix(mystery): adjust subscriber text

* fix(mystery): correct moment logic
and remove irrelevant tests

* fix(mystery): build set one month in advance

* fix(mystery): don't add empty set to Time Travelers

* refactor(mystery): readability
This commit is contained in:
Sabe Jones
2019-11-27 12:10:56 -06:00
committed by GitHub
parent d0033322f7
commit 6395070eb6
7 changed files with 59 additions and 310 deletions

View File

@@ -88,6 +88,28 @@ describe('cron', () => {
user.purchased.plan.dateUpdated = moment().subtract(1, 'months').toDate();
});
it('awards current mystery items to subscriber', () => {
user.purchased.plan.dateUpdated = new Date('2018-12-11');
clock = sinon.useFakeTimers(new Date('2019-01-29'));
cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.mysteryItems.length).to.eql(2);
expect(user.notifications.length).to.eql(3);
expect(user.notifications[0].type).to.eql('NEW_MYSTERY_ITEMS');
});
it('awards multiple mystery item sets if user skipped months between logins', () => {
user.purchased.plan.dateUpdated = new Date('2018-11-11');
clock = sinon.useFakeTimers(new Date('2019-01-29'));
cron({
user, tasksByType, daysMissed, analytics,
});
expect(user.purchased.plan.mysteryItems.length).to.eql(4);
expect(user.notifications.length).to.eql(3);
expect(user.notifications[0].type).to.eql('NEW_MYSTERY_ITEMS');
});
it('resets plan.gemsBought on a new month', () => {
user.purchased.plan.gemsBought = 10;
cron({

View File

@@ -439,31 +439,6 @@ describe('payments/index', () => {
fakeClock.restore();
});
it('does not awards mystery items when not within the timeframe for a mystery item', async () => {
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
const fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
await api.createSubscription(data);
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(0);
fakeClock.restore();
});
it('does not add a notification for mystery items if none was awarded', async () => {
const noMysteryItemTimeframe = 1462183920000; // May 2nd 2016
const fakeClock = sinon.useFakeTimers(noMysteryItemTimeframe);
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
await api.createSubscription(data);
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(0);
expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.be.undefined;
fakeClock.restore();
});
it('does not award mystery item when user already owns the item', async () => {
const mayMysteryItemTimeframe = 1464725113000; // May 31st 2016
const fakeClock = sinon.useFakeTimers(mayMysteryItemTimeframe);

View File

@@ -67,7 +67,7 @@
"newGroupTitle": "New Group",
"subscriberItem": "Mystery Item",
"newSubscriberItem": "You have new <span class=\"notification-bold-blue\">Mystery Items</span>",
"subscriberItemText": "Each month, subscribers will receive a mystery item. This is usually released about one week before the end of the month. See the wiki's 'Mystery Item' page for more information.",
"subscriberItemText": "Each month, subscribers will receive a mystery item. It becomes available at the beginning of the month. See the wiki's 'Mystery Item' page for more information.",
"all": "All",
"none": "None",
"more": "<%= count %> more",

View File

@@ -1,287 +1,8 @@
import each from 'lodash/each';
import moment from 'moment';
import t from './translation';
const mysterySets = {
201402: {
start: '2014-02-22',
end: '2014-02-28',
},
201403: {
start: '2014-03-24',
end: '2014-04-02',
},
201404: {
start: '2014-04-24',
end: '2014-05-02',
},
201405: {
start: '2014-05-21',
end: '2014-06-02',
},
201406: {
start: '2014-06-23',
end: '2014-07-02',
},
201407: {
start: '2014-07-23',
end: '2014-08-02',
},
201408: {
start: '2014-08-23',
end: '2014-09-02',
},
201409: {
start: '2014-09-24',
end: '2014-10-02',
},
201410: {
start: '2014-10-24',
end: '2014-11-02',
},
201411: {
start: '2014-11-24',
end: '2014-12-02',
},
201412: {
start: '2014-12-25',
end: '2015-01-02',
},
201501: {
start: '2015-01-26',
end: '2015-02-02',
},
201502: {
start: '2015-02-24',
end: '2015-03-02',
},
201503: {
start: '2015-03-25',
end: '2015-04-02',
},
201504: {
start: '2015-04-24',
end: '2015-05-02',
},
201505: {
start: '2015-05-25',
end: '2015-06-02',
},
201506: {
start: '2015-06-25',
end: '2015-07-02',
},
201507: {
start: '2015-07-24',
end: '2015-08-02',
},
201508: {
start: '2015-08-23',
end: '2015-09-02',
},
201509: {
start: '2015-09-24',
end: '2015-10-02',
},
201510: {
start: '2015-10-26',
end: '2015-11-02',
},
201511: {
start: '2015-11-25',
end: '2015-12-02',
},
201512: {
start: '2015-12-23',
end: '2016-01-02',
},
201601: {
start: '2016-01-26',
end: '2016-02-02',
},
201602: {
start: '2016-02-24',
end: '2016-03-02',
},
201603: {
start: '2016-03-24',
end: '2016-04-02',
},
201604: {
start: '2016-04-25',
end: '2016-05-02',
},
201605: {
start: '2016-05-25',
end: '2016-06-02',
},
201606: {
start: '2016-06-23',
end: '2016-07-02',
},
201607: {
start: '2016-07-26',
end: '2016-08-02',
},
201608: {
start: '2016-08-23',
end: '2016-09-02',
},
201609: {
start: '2016-09-22',
end: '2016-10-02',
},
201610: {
start: '2016-10-25',
end: '2016-11-02',
},
201611: {
start: '2016-11-22',
end: '2016-12-02',
},
201612: {
start: '2016-12-21',
end: '2017-01-02',
},
201701: {
start: '2017-01-24',
end: '2017-02-02',
},
201702: {
start: '2017-02-21',
end: '2017-03-02',
},
201703: {
start: '2017-03-23',
end: '2017-04-02',
},
201704: {
start: '2017-04-25',
end: '2017-05-02',
},
201705: {
start: '2017-05-23',
end: '2017-06-02',
},
201706: {
start: '2017-06-22',
end: '2017-07-02',
},
201707: {
start: '2017-07-25',
end: '2017-08-02',
},
201708: {
start: '2017-08-17',
end: '2017-09-02',
},
201709: {
start: '2017-09-19',
end: '2017-10-07',
},
201710: {
start: '2017-10-24',
end: '2017-11-02',
},
201711: {
start: '2017-11-17',
end: '2017-12-02',
},
201712: {
start: '2017-12-21',
end: '2018-01-02',
},
201801: {
start: '2018-01-23',
end: '2018-02-02',
},
201802: {
start: '2018-02-22',
end: '2018-03-02',
},
201803: {
start: '2018-03-22',
end: '2018-04-02',
},
201804: {
start: '2018-04-24',
end: '2018-05-02',
},
201805: {
start: '2018-05-24',
end: '2018-06-02',
},
201806: {
start: '2018-06-21',
end: '2018-07-02',
},
201807: {
start: '2018-07-24',
end: '2018-08-02',
},
201808: {
start: '2018-08-23',
end: '2018-09-02',
},
201809: {
start: '2018-09-25',
end: '2018-10-02',
},
201810: {
start: '2018-10-25',
end: '2018-11-02',
},
201811: {
start: '2018-11-28',
end: '2018-12-4',
},
201812: {
start: '2018-12-19',
end: '2019-01-02',
},
201901: {
start: '2019-01-28',
end: '2019-02-02',
},
201902: {
start: '2019-02-25',
end: '2019-03-02',
},
201903: {
start: '2019-03-26',
end: '2019-04-02',
},
201904: {
start: '2019-04-25',
end: '2019-05-02',
},
201905: {
start: '2019-05-28',
end: '2019-06-02',
},
201906: {
start: '2019-06-25',
end: '2019-07-03',
},
201907: {
start: '2019-07-25',
end: '2019-08-02',
},
201908: {
start: '2019-08-27',
end: '2019-09-02',
},
201909: {
start: '2019-09-25',
end: '2019-10-02',
},
201910: {
start: '2019-10-24',
end: '2019-11-02',
},
201911: {
start: '2019-11-21',
end: '2019-12-02',
},
301404: {
start: '3014-03-24',
end: '3014-04-02',
@@ -304,6 +25,19 @@ const mysterySets = {
end: '2014-04-01',
},
};
const FIRST_MYSTERY_SET = moment('2014-02-01');
for (
let mysteryMonth = FIRST_MYSTERY_SET;
moment(mysteryMonth).startOf('month').isSameOrBefore(moment().add(1, 'months'));
mysteryMonth = moment(mysteryMonth).add(1, 'months')
) {
const setKey = moment(mysteryMonth).format('YYYYMM');
mysterySets[setKey] = {
start: moment(mysteryMonth).startOf('month').format('YYYY-MM-DD'),
end: moment(mysteryMonth).endOf('month').format('YYYY-MM-DD'),
};
}
each(mysterySets, (value, key) => {
value.key = key;

View File

@@ -13,6 +13,7 @@ each(mystery, (v, k) => {
v.items = filter(gear.flat, {
mystery: k,
});
if (v.items.length === 0) delete mystery[k];
});
const timeTravelerStore = user => {

View File

@@ -6,6 +6,7 @@ import { model as User } from '../models/user';
import common from '../../common';
import { preenUserHistory } from './preening';
import sleep from './sleep';
import { revealMysteryItems } from './payments/subscriptions';
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
@@ -75,6 +76,9 @@ function grantEndOfTheMonthPerks (user, now) {
count: 0, offset: 0, trinkets: 0, gemCapExtra: 0,
});
// Award mystery items
revealMysteryItems(user, elapsedMonths);
// 1 for one-month recurring or gift subscriptions; later set to 3 for 3-month recurring, etc.
let planMonthsLength = 1;

View File

@@ -21,14 +21,13 @@ import { sendNotification as sendPushNotification } from '../pushNotifications';
// @TODO: Abstract to shared/constant
const JOINED_GROUP_PLAN = 'joined group plan';
function revealMysteryItems (user) {
function _findMysteryItems (user, dateMoment) {
const pushedItems = [];
_.each(shared.content.gear.flat, item => {
if (
item.klass === 'mystery'
&& moment().isAfter(shared.content.mystery[item.mystery].start)
&& moment().isBefore(shared.content.mystery[item.mystery].end)
&& dateMoment.isSameOrAfter(shared.content.mystery[item.mystery].start)
&& dateMoment.isSameOrBefore(moment(shared.content.mystery[item.mystery].end).endOf('day'))
&& !user.items.gear.owned[item.key]
&& user.purchased.plan.mysteryItems.indexOf(item.key) === -1
) {
@@ -36,6 +35,19 @@ function revealMysteryItems (user) {
pushedItems.push(item.key);
}
});
return pushedItems;
}
function revealMysteryItems (user, elapsedMonths = 1) {
let monthsToCheck = elapsedMonths;
let pushedItems = [];
do {
monthsToCheck -= 1;
pushedItems = pushedItems.concat(_findMysteryItems(user, moment().subtract(monthsToCheck, 'months')));
}
while (monthsToCheck > 0);
if (pushedItems.length > 0) {
user.addNotification('NEW_MYSTERY_ITEMS', { items: pushedItems });
}
@@ -324,4 +336,5 @@ async function cancelSubscription (data) {
export {
createSubscription,
cancelSubscription,
revealMysteryItems,
};