Guild A/B test and Achievement (#8740)
* WIP(guilds): AB test pester modal * WIP(AB-test): guild pester cont'd * fix(style): linting error * fix(AB-test): markModified and notif enum * fix(tests): update AB expectations * fix(modal): remove extra includes * feat(achievements): add Joined Guild cheevo Also removes unused achievement sprites, and properly saves counter used in A/B testing * fix(style): linting error from conflict
@@ -74,6 +74,18 @@ describe('POST /group', () => {
|
|||||||
expect(updatedUser.guilds).to.include(guild._id);
|
expect(updatedUser.guilds).to.include(guild._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('awards the Joined Guild achievement', async () => {
|
||||||
|
await user.post('/groups', {
|
||||||
|
name: 'some guild',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'public',
|
||||||
|
});
|
||||||
|
|
||||||
|
let updatedUser = await user.get('/user');
|
||||||
|
|
||||||
|
expect(updatedUser.achievements.joinedGuild).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
context('public guild', () => {
|
context('public guild', () => {
|
||||||
it('creates a group', async () => {
|
it('creates a group', async () => {
|
||||||
let groupName = 'Test Public Guild';
|
let groupName = 'Test Public Guild';
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
|
|
||||||
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
|
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('awards Joined Guild achievement', async () => {
|
||||||
|
await joiningUser.post(`/groups/${publicGuild._id}/join`);
|
||||||
|
|
||||||
|
await expect(joiningUser.get('/user')).to.eventually.have.deep.property('achievements.joinedGuild', true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Joining a private guild', () => {
|
context('Joining a private guild', () => {
|
||||||
@@ -147,8 +153,14 @@ describe('POST /group/:groupId/join', () => {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED');
|
expect(inviter.notifications[1].type).to.eql('GROUP_INVITE_ACCEPTED');
|
||||||
expect(inviter.notifications[0].data).to.eql(expectedData);
|
expect(inviter.notifications[1].data).to.eql(expectedData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('awards Joined Guild achievement', async () => {
|
||||||
|
await invitedUser.post(`/groups/${guild._id}/join`);
|
||||||
|
|
||||||
|
await expect(invitedUser.get('/user')).to.eventually.have.deep.property('achievements.joinedGuild', true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
|
|
||||||
await user.sync();
|
await user.sync();
|
||||||
await member2.sync();
|
await member2.sync();
|
||||||
expect(user.notifications.length).to.equal(1);
|
expect(user.notifications.length).to.equal(2);
|
||||||
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
expect(member2.notifications.length).to.equal(1);
|
expect(member2.notifications.length).to.equal(1);
|
||||||
expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
await user.sync();
|
await user.sync();
|
||||||
await member2.sync();
|
await member2.sync();
|
||||||
|
|
||||||
expect(user.notifications.length).to.equal(0);
|
expect(user.notifications.length).to.equal(1);
|
||||||
expect(member2.notifications.length).to.equal(0);
|
expect(member2.notifications.length).to.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
|
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
expect(user.notifications.length).to.equal(1);
|
expect(user.notifications.length).to.equal(2);
|
||||||
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', {
|
expect(user.notifications[1].data.message).to.equal(t('userHasRequestedTaskApproval', {
|
||||||
user: member.auth.local.username,
|
user: member.auth.local.username,
|
||||||
taskName: updatedTask.text,
|
taskName: updatedTask.text,
|
||||||
taskId: updatedTask._id,
|
taskId: updatedTask._id,
|
||||||
}, 'cs')); // This test only works if we have the notification translated
|
}, 'cs')); // This test only works if we have the notification translated
|
||||||
expect(user.notifications[0].data.groupId).to.equal(guild._id);
|
expect(user.notifications[1].data.groupId).to.equal(guild._id);
|
||||||
|
|
||||||
expect(updatedTask.group.approval.requested).to.equal(true);
|
expect(updatedTask.group.approval.requested).to.equal(true);
|
||||||
expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
||||||
@@ -82,14 +82,14 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
await user.sync();
|
await user.sync();
|
||||||
await member2.sync();
|
await member2.sync();
|
||||||
|
|
||||||
expect(user.notifications.length).to.equal(1);
|
expect(user.notifications.length).to.equal(2);
|
||||||
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', {
|
expect(user.notifications[1].data.message).to.equal(t('userHasRequestedTaskApproval', {
|
||||||
user: member.auth.local.username,
|
user: member.auth.local.username,
|
||||||
taskName: updatedTask.text,
|
taskName: updatedTask.text,
|
||||||
taskId: updatedTask._id,
|
taskId: updatedTask._id,
|
||||||
}));
|
}));
|
||||||
expect(user.notifications[0].data.groupId).to.equal(guild._id);
|
expect(user.notifications[1].data.groupId).to.equal(guild._id);
|
||||||
|
|
||||||
expect(member2.notifications.length).to.equal(1);
|
expect(member2.notifications.length).to.equal(1);
|
||||||
expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
expect(member2.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
|
|||||||
@@ -251,7 +251,6 @@ describe('POST /user/auth/local/register', () => {
|
|||||||
confirmPassword: password,
|
confirmPassword: password,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
|
||||||
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ describe('POST /user/auth/social', () => {
|
|||||||
network,
|
network,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ describe('POST /user/auth/social', () => {
|
|||||||
network,
|
network,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 720 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 755 B |
BIN
website/assets/sprites/spritesmith_large/scenes/scene_guilds.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -78,7 +78,7 @@ habitrpg.controller('NotificationCtrl',
|
|||||||
// Avoid showing the same notiication more than once
|
// Avoid showing the same notiication more than once
|
||||||
var lastShownNotifications = [];
|
var lastShownNotifications = [];
|
||||||
|
|
||||||
function trasnferGroupNotification(notification) {
|
function transferGroupNotification(notification) {
|
||||||
if (!User.user.groupNotifications) User.user.groupNotifications = [];
|
if (!User.user.groupNotifications) User.user.groupNotifications = [];
|
||||||
User.user.groupNotifications.push(notification);
|
User.user.groupNotifications.push(notification);
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,13 @@ habitrpg.controller('NotificationCtrl',
|
|||||||
var markAsRead = true;
|
var markAsRead = true;
|
||||||
|
|
||||||
switch (notification.type) {
|
switch (notification.type) {
|
||||||
|
case 'GUILD_PROMPT':
|
||||||
|
if (notification.data.textVariant === -1) {
|
||||||
|
$rootScope.openModal('testing');
|
||||||
|
} else {
|
||||||
|
$rootScope.openModal('testingVariant');
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'DROPS_ENABLED':
|
case 'DROPS_ENABLED':
|
||||||
$rootScope.openModal('dropsEnabled');
|
$rootScope.openModal('dropsEnabled');
|
||||||
break;
|
break;
|
||||||
@@ -129,12 +136,19 @@ habitrpg.controller('NotificationCtrl',
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ULTIMATE_GEAR_ACHIEVEMENT':
|
case 'ULTIMATE_GEAR_ACHIEVEMENT':
|
||||||
|
$rootScope.playSound('Achievement_Unlocked');
|
||||||
Achievement.displayAchievement('ultimateGear', {size: 'md'});
|
Achievement.displayAchievement('ultimateGear', {size: 'md'});
|
||||||
break;
|
break;
|
||||||
case 'REBIRTH_ACHIEVEMENT':
|
case 'REBIRTH_ACHIEVEMENT':
|
||||||
|
$rootScope.playSound('Achievement_Unlocked');
|
||||||
Achievement.displayAchievement('rebirth');
|
Achievement.displayAchievement('rebirth');
|
||||||
break;
|
break;
|
||||||
|
case 'GUILD_JOINED_ACHIEVEMENT':
|
||||||
|
$rootScope.playSound('Achievement_Unlocked');
|
||||||
|
Achievement.displayAchievement('joinedGuild', {size: 'md'});
|
||||||
|
break;
|
||||||
case 'NEW_CONTRIBUTOR_LEVEL':
|
case 'NEW_CONTRIBUTOR_LEVEL':
|
||||||
|
$rootScope.playSound('Achievement_Unlocked');
|
||||||
Achievement.displayAchievement('contributor', {size: 'md'});
|
Achievement.displayAchievement('contributor', {size: 'md'});
|
||||||
break;
|
break;
|
||||||
case 'CRON':
|
case 'CRON':
|
||||||
@@ -144,11 +158,11 @@ habitrpg.controller('NotificationCtrl',
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'GROUP_TASK_APPROVAL':
|
case 'GROUP_TASK_APPROVAL':
|
||||||
trasnferGroupNotification(notification);
|
transferGroupNotification(notification);
|
||||||
markAsRead = false;
|
markAsRead = false;
|
||||||
break;
|
break;
|
||||||
case 'GROUP_TASK_APPROVED':
|
case 'GROUP_TASK_APPROVED':
|
||||||
trasnferGroupNotification(notification);
|
transferGroupNotification(notification);
|
||||||
markAsRead = false;
|
markAsRead = false;
|
||||||
break;
|
break;
|
||||||
case 'SCORED_TASK':
|
case 'SCORED_TASK':
|
||||||
|
|||||||
@@ -282,5 +282,7 @@
|
|||||||
"userIsNotManager": "User is not manager",
|
"userIsNotManager": "User is not manager",
|
||||||
"canOnlyApproveTaskOnce": "This task has already been approved.",
|
"canOnlyApproveTaskOnce": "This task has already been approved.",
|
||||||
"leaderMarker": " - Leader",
|
"leaderMarker": " - Leader",
|
||||||
"managerMarker": " - Manager"
|
"managerMarker": " - Manager",
|
||||||
|
"joinedGuild": "Joined a Guild",
|
||||||
|
"joinedGuildText": "Ventured into the social side of Habitica by joining a Guild!"
|
||||||
}
|
}
|
||||||
|
|||||||
7
website/common/locales/en/testing.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"guildReminderTitle": "Check out Guilds!",
|
||||||
|
"guildReminderText1": "Now that you've mastered Habitica's basics, come check out Guilds, where you can chat with other people who are committed to improving their lives!",
|
||||||
|
"guildReminderText2": "Ready for more? Join a Guild to meet other people who are committed to improving their lives!",
|
||||||
|
"guildReminderCTA": "Take me there!",
|
||||||
|
"guildReminderDismiss": "Not right now."
|
||||||
|
}
|
||||||
@@ -102,6 +102,11 @@ let basicAchievs = {
|
|||||||
titleKey: 'royallyLoyal',
|
titleKey: 'royallyLoyal',
|
||||||
textKey: 'royallyLoyalText',
|
textKey: 'royallyLoyalText',
|
||||||
},
|
},
|
||||||
|
joinedGuild: {
|
||||||
|
icon: 'achievement-guild',
|
||||||
|
titleKey: 'joinedGuild',
|
||||||
|
textKey: 'joinedGuildText',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Object.assign(achievementsData, basicAchievs);
|
Object.assign(achievementsData, basicAchievs);
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ function _getBasicAchievements (user, language) {
|
|||||||
|
|
||||||
_addSimple(result, user, {path: 'partyUp', language});
|
_addSimple(result, user, {path: 'partyUp', language});
|
||||||
_addSimple(result, user, {path: 'partyOn', language});
|
_addSimple(result, user, {path: 'partyOn', language});
|
||||||
|
_addSimple(result, user, {path: 'joinedGuild', language});
|
||||||
_addSimple(result, user, {path: 'royallyLoyal', language});
|
_addSimple(result, user, {path: 'royallyLoyal', language});
|
||||||
|
|
||||||
_addSimpleWithMasterCount(result, user, {path: 'beastMaster', language});
|
_addSimpleWithMasterCount(result, user, {path: 'beastMaster', language});
|
||||||
|
|||||||
@@ -122,6 +122,14 @@ api.createGroup = {
|
|||||||
|
|
||||||
user.balance--;
|
user.balance--;
|
||||||
user.guilds.push(group._id);
|
user.guilds.push(group._id);
|
||||||
|
if (!user.achievements.joinedGuild) {
|
||||||
|
user.achievements.joinedGuild = true;
|
||||||
|
user.addNotification('GUILD_JOINED_ACHIEVEMENT');
|
||||||
|
}
|
||||||
|
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
|
||||||
|
user._ABtests.counter = -1;
|
||||||
|
user.markModified('_ABtests');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate'));
|
if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate'));
|
||||||
if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty'));
|
if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty'));
|
||||||
@@ -510,6 +518,14 @@ api.joinGroup = {
|
|||||||
throw new NotAuthorized(res.t('userAlreadyInGroup'));
|
throw new NotAuthorized(res.t('userAlreadyInGroup'));
|
||||||
}
|
}
|
||||||
user.guilds.push(group._id); // Add group to user's guilds
|
user.guilds.push(group._id); // Add group to user's guilds
|
||||||
|
if (!user.achievements.joinedGuild) {
|
||||||
|
user.achievements.joinedGuild = true;
|
||||||
|
user.addNotification('GUILD_JOINED_ACHIEVEMENT');
|
||||||
|
}
|
||||||
|
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
|
||||||
|
user._ABtests.counter = -1;
|
||||||
|
user.markModified('_ABtests');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite'));
|
if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite'));
|
||||||
|
|
||||||
|
|||||||
@@ -589,6 +589,18 @@ api.scoreTask = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user._ABtests && user._ABtests.guildReminder && user._ABtests.counter !== -1) {
|
||||||
|
user._ABtests.counter++;
|
||||||
|
if (user._ABtests.counter > 1) {
|
||||||
|
if (user._ABtests.guildReminder.indexOf('timing1') !== -1 || user._ABtests.counter > 4) {
|
||||||
|
user._ABtests.counter = -1;
|
||||||
|
let textVariant = user._ABtests.guildReminder.indexOf('text2');
|
||||||
|
user.addNotification('GUILD_PROMPT', {textVariant});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user.markModified('_ABtests');
|
||||||
|
}
|
||||||
|
|
||||||
if (task.type === 'daily') {
|
if (task.type === 'daily') {
|
||||||
task.isDue = common.shouldDo(Date.now(), task, user.preferences);
|
task.isDue = common.shouldDo(Date.now(), task, user.preferences);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ api.getBuyList = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let updatablePaths = [
|
let updatablePaths = [
|
||||||
|
'_ABtests.counter',
|
||||||
|
|
||||||
'flags.customizationsNotification',
|
'flags.customizationsNotification',
|
||||||
'flags.showTour',
|
'flags.showTour',
|
||||||
'flags.tour',
|
'flags.tour',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import schema from './schema';
|
|||||||
schema.plugin(baseModel, {
|
schema.plugin(baseModel, {
|
||||||
// noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...)
|
// noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...)
|
||||||
noSet: [],
|
noSet: [],
|
||||||
private: ['auth.local.hashed_password', 'auth.local.passwordHashMethod', 'auth.local.salt', '_cronSignature', '_ABtest', '_ABtests'],
|
private: ['auth.local.hashed_password', 'auth.local.passwordHashMethod', 'auth.local.salt', '_cronSignature', '_ABtests'],
|
||||||
toJSONTransform: function userToJSON (plainObj, originalDoc) {
|
toJSONTransform: function userToJSON (plainObj, originalDoc) {
|
||||||
plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs
|
plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs
|
||||||
delete plainObj.filters;
|
delete plainObj.filters;
|
||||||
@@ -94,13 +94,25 @@ function _setUpNewUser (user) {
|
|||||||
let taskTypes;
|
let taskTypes;
|
||||||
let iterableFlags = user.flags.toObject();
|
let iterableFlags = user.flags.toObject();
|
||||||
|
|
||||||
user._ABtest = '';
|
// A/B test 2017-05-11: Can we encourage people to join Guilds with a pester modal?
|
||||||
// A/B test 2016-12-21: Should we deliver notifications for upcoming incentives on days when users don't receive rewards?
|
let testGroup = Math.random();
|
||||||
if (Math.random() < 0.5) {
|
if (testGroup < 0.1) {
|
||||||
user._ABtests.checkInModals = '20161221_noCheckInPreviews'; // no 'preview' check-in modals
|
user._ABtests.guildReminder = '20170511_noGuildReminder'; // control group, don't pester about Guilds
|
||||||
|
user._ABtests.counter = -1;
|
||||||
|
} else if (testGroup < 0.235) {
|
||||||
|
user._ABtests.guildReminder = '20170511_text1timing1'; // first sample text, show after two clicks
|
||||||
|
user._ABtests.counter = 0;
|
||||||
|
} else if (testGroup < 0.46) {
|
||||||
|
user._ABtests.guildReminder = '20170511_text2timing1'; // second sample text, show after two clicks
|
||||||
|
user._ABtests.counter = 0;
|
||||||
|
} else if (testGroup < 0.685) {
|
||||||
|
user._ABtests.guildReminder = '20170511_text1timing2'; // first sample text, show after five clicks
|
||||||
|
user._ABtests.counter = 0;
|
||||||
} else {
|
} else {
|
||||||
user._ABtests.checkInModals = '20161221_showCheckInPreviews'; // show 'preview' check-in modals
|
user._ABtests.guildReminder = '20170511_text2timing2'; // second sample text, show after five clicks
|
||||||
|
user._ABtests.counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.items.quests.dustbunnies = 1;
|
user.items.quests.dustbunnies = 1;
|
||||||
user.purchased.background.violet = true;
|
user.purchased.background.violet = true;
|
||||||
user.preferences.background = 'violet';
|
user.preferences.background = 'violet';
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ let schema = new Schema({
|
|||||||
partyUp: Boolean,
|
partyUp: Boolean,
|
||||||
partyOn: Boolean,
|
partyOn: Boolean,
|
||||||
royallyLoyal: Boolean,
|
royallyLoyal: Boolean,
|
||||||
|
joinedGuild: Boolean,
|
||||||
},
|
},
|
||||||
|
|
||||||
backer: {
|
backer: {
|
||||||
@@ -545,7 +546,6 @@ let schema = new Schema({
|
|||||||
return {};
|
return {};
|
||||||
}},
|
}},
|
||||||
pushDevices: [PushDeviceSchema],
|
pushDevices: [PushDeviceSchema],
|
||||||
_ABtest: {type: String}, // deprecated. Superseded by _ABtests
|
|
||||||
_ABtests: {type: Schema.Types.Mixed, default: () => {
|
_ABtests: {type: Schema.Types.Mixed, default: () => {
|
||||||
return {};
|
return {};
|
||||||
}},
|
}},
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ const NOTIFICATION_TYPES = [
|
|||||||
'GROUP_INVITE_ACCEPTED',
|
'GROUP_INVITE_ACCEPTED',
|
||||||
'SCORED_TASK',
|
'SCORED_TASK',
|
||||||
'BOSS_DAMAGE', // Not used currently but kept to avoid validation errors
|
'BOSS_DAMAGE', // Not used currently but kept to avoid validation errors
|
||||||
|
'GUILD_PROMPT',
|
||||||
|
'GUILD_JOINED_ACHIEVEMENT',
|
||||||
];
|
];
|
||||||
|
|
||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ script(id='modals/achievements/contributor.html', type='text/ng-template')
|
|||||||
button.btn.btn-primary(style='margin-top:1em' ng-click='$close()')=env.t('huzzah')
|
button.btn.btn-primary(style='margin-top:1em' ng-click='$close()')=env.t('huzzah')
|
||||||
+achievementFooter
|
+achievementFooter
|
||||||
|
|
||||||
//Rebirth
|
// Rebirth
|
||||||
script(id='modals/achievements/rebirth.html', type='text/ng-template')
|
script(id='modals/achievements/rebirth.html', type='text/ng-template')
|
||||||
.modal-content(style='min-width:28em')
|
.modal-content(style='min-width:28em')
|
||||||
.modal-body.text-center
|
.modal-body.text-center
|
||||||
@@ -152,3 +152,14 @@ script(id='modals/achievements/partyOn.html', type='text/ng-template')
|
|||||||
br
|
br
|
||||||
button.btn.btn-primary(ng-click='$close()')=env.t('huzzah')
|
button.btn.btn-primary(ng-click='$close()')=env.t('huzzah')
|
||||||
+achievementFooter
|
+achievementFooter
|
||||||
|
|
||||||
|
// Joined Guild
|
||||||
|
script(id='modals/achievements/joinedGuild.html', type='text/ng-template')
|
||||||
|
.modal-content(style='min-width:28em')
|
||||||
|
.modal-body.text-center
|
||||||
|
h3(style='margin-bottom:0')=env.t('modalAchievement')
|
||||||
|
+achievementAvatar('guild',0)
|
||||||
|
p=env.t('joinedGuildText')
|
||||||
|
br
|
||||||
|
button.btn.btn-primary(ng-click='$close()')=env.t('huzzah')
|
||||||
|
+achievementFooter
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ include ./generic.jade
|
|||||||
include ./tasks-edit.jade
|
include ./tasks-edit.jade
|
||||||
include ./task-notes.jade
|
include ./task-notes.jade
|
||||||
include ./task-extra-notes.jade
|
include ./task-extra-notes.jade
|
||||||
|
include ./testing.jade
|
||||||
|
|
||||||
//- Settings
|
//- Settings
|
||||||
script(type='text/ng-template', id='modals/change-day-start.html')
|
script(type='text/ng-template', id='modals/change-day-start.html')
|
||||||
|
|||||||
31
website/views/shared/modals/testing.jade
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
script(type='text/ng-template', id='modals/testing.html')
|
||||||
|
.modal-content(style='min-width:28em')
|
||||||
|
.modal-body.text-center(style='padding-bottom:0')
|
||||||
|
h3=env.t('guildReminderTitle')
|
||||||
|
br
|
||||||
|
.scene_guilds.center-block
|
||||||
|
br
|
||||||
|
h4=env.t('guildReminderText1')
|
||||||
|
.modal-footer(style='padding-top:0')
|
||||||
|
.container-fluid
|
||||||
|
.row
|
||||||
|
.col-xs-6.text-center
|
||||||
|
button.btn-lg.btn-default(ng-click='$close()')=env.t('guildReminderDismiss')
|
||||||
|
.col-xs-6.text-center
|
||||||
|
button.btn-lg.btn-primary(ui-sref='options.social.guilds.public', href='/#/options/groups/guilds/public', ng-click='$close()')=env.t('guildReminderCTA')
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='modals/testingVariant.html')
|
||||||
|
.modal-content(style='min-width:28em')
|
||||||
|
.modal-body.text-center(style='padding-bottom:0')
|
||||||
|
h3=env.t('guildReminderTitle')
|
||||||
|
br
|
||||||
|
.scene_guilds.center-block
|
||||||
|
br
|
||||||
|
h4=env.t('guildReminderText2')
|
||||||
|
.modal-footer(style='padding-top:0')
|
||||||
|
.container-fluid
|
||||||
|
.row
|
||||||
|
.col-xs-6.text-center
|
||||||
|
button.btn-lg.btn-default(ng-click='$close()')=env.t('guildReminderDismiss')
|
||||||
|
.col-xs-6.text-center
|
||||||
|
button.btn-lg.btn-primary(ui-sref='options.social.guilds.public', href='/#/options/groups/guilds/public', ng-click='$close()')=env.t('guildReminderCTA')
|
||||||