feat(api): Stop alerting about new messages for large groups

closes #6706
closes #6762
This commit is contained in:
Blade Barringer
2016-06-19 20:54:30 -05:00
parent a4dba82d14
commit e920734648
6 changed files with 175 additions and 25 deletions

View File

@@ -163,6 +163,7 @@
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
"partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
"largeGroupNote": "Note: This Guild is now too large to support notifications! Be sure to check back every day to see new messages.",
"groupIdRequired": "\"groupId\" must be a valid UUID",
"groupNotFound": "Group not found or you don't have access.",
"groupTypesRequired": "You must supply a valid \"type\" query string.",

View File

@@ -3,4 +3,5 @@ export const MAX_LEVEL = 100;
export const MAX_STAT_POINTS = MAX_LEVEL;
export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
export const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = 5000;

View File

@@ -17,13 +17,18 @@ import { shouldDo, daysSince } from './cron';
api.shouldDo = shouldDo;
api.daysSince = daysSince;
// TODO under api.constants? and capitalize exported names too
import {
MAX_HEALTH,
MAX_LEVEL,
MAX_STAT_POINTS,
TAVERN_ID,
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
} from './constants';
api.constants = {
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
};
// TODO Move these under api.constants
api.maxLevel = MAX_LEVEL;
api.maxHealth = MAX_HEALTH;
api.maxStatPoints = MAX_STAT_POINTS;

View File

@@ -3,6 +3,8 @@ import { model as Group } from '../../../../../website/server/models/group';
import { model as User } from '../../../../../website/server/models/user';
import { quests as questScrolls } from '../../../../../common/script/content';
import * as email from '../../../../../website/server/libs/api-v3/email';
import validator from 'validator';
import { TAVERN_ID } from '../../../../../common/script/';
describe('Group Model', () => {
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
@@ -395,6 +397,147 @@ describe('Group Model', () => {
});
context('Instance Methods', () => {
describe('#sendChat', () => {
beforeEach(() => {
sandbox.spy(User, 'update');
});
it('puts message at top of chat array', () => {
let oldMessage = {
text: 'a message',
};
party.chat.push(oldMessage, oldMessage, oldMessage);
party.sendChat('a new message', {_id: 'user-id', profile: { name: 'user name' }});
expect(party.chat).to.have.a.lengthOf(4);
expect(party.chat[0].text).to.eql('a new message');
expect(party.chat[0].uuid).to.eql('user-id');
});
it('formats message', () => {
party.sendChat('a new message', {
_id: 'user-id',
profile: { name: 'user name' },
contributor: { toObject () { return 'contributor object'; } },
backer: { toObject () { return 'backer object'; } },
});
let chat = party.chat[0];
expect(chat.text).to.eql('a new message');
expect(validator.isUUID(chat.id)).to.eql(true);
expect(chat.timestamp).to.be.a('number');
expect(chat.likes).to.eql({});
expect(chat.flags).to.eql({});
expect(chat.flagCount).to.eql(0);
expect(chat.uuid).to.eql('user-id');
expect(chat.contributor).to.eql('contributor object');
expect(chat.backer).to.eql('backer object');
expect(chat.user).to.eql('user name');
});
it('formats message as system if no user is passed in', () => {
party.sendChat('a system message');
let chat = party.chat[0];
expect(chat.text).to.eql('a system message');
expect(validator.isUUID(chat.id)).to.eql(true);
expect(chat.timestamp).to.be.a('number');
expect(chat.likes).to.eql({});
expect(chat.flags).to.eql({});
expect(chat.flagCount).to.eql(0);
expect(chat.uuid).to.eql('system');
expect(chat.contributor).to.not.exist;;
expect(chat.backer).to.not.exist;;
expect(chat.user).to.not.exist;;
});
it('cuts down chat to 200 messages', () => {
for (var i = 0; i < 220; i++) {
party.chat.push({ text: 'a message' });
};
expect(party.chat).to.have.a.lengthOf(220);
party.sendChat('message');
expect(party.chat).to.have.a.lengthOf(200);
});
it('updates users about new messages in party', () => {
party.sendChat('message');
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
'party._id': party._id,
_id: { $ne: '' },
}, {
$set: {
[`newMessages.${party._id}`]: {
name: party.name,
value: true,
},
},
});
});
it('updates users about new messages in group', () => {
let group = new Group({
type: 'guild',
});
group.sendChat('message');
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
'guilds': group._id,
_id: { $ne: '' },
}, {
$set: {
[`newMessages.${group._id}`]: {
name: group.name,
value: true,
},
},
});
});
it('does not send update to user that sent the message', () => {
party.sendChat('message', {_id: 'user-id', profile: { name: 'user' }});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
'party._id': party._id,
_id: { $ne: 'user-id' },
}, {
$set: {
[`newMessages.${party._id}`]: {
name: party.name,
value: true,
},
},
});
});
it('skips sending new message notification for guilds with > 5000 members', () => {
party.memberCount = 5001;
party.sendChat('message');
expect(User.update).to.not.be.called;
});
it('skips sending messages to the tavern', () => {
party._id = TAVERN_ID;
party.sendChat('message');
expect(User.update).to.not.be.called;
});
});
describe('#startQuest', () => {
context('Failure Conditions', () => {
it('throws an error if group is not a party', async () => {

View File

@@ -25,6 +25,9 @@ const Schema = mongoose.Schema;
export const INVITES_LIMIT = 100;
export const TAVERN_ID = shared.TAVERN_ID;
const NO_CHAT_NOTIFICATIONS = [TAVERN_ID];
const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = shared.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF;
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
@@ -300,34 +303,30 @@ export function chatDefaults (msg, user) {
return message;
}
const NO_CHAT_NOTIFICATIONS = [TAVERN_ID];
schema.methods.sendChat = function sendChat (message, user) {
this.chat.unshift(chatDefaults(message, user));
this.chat.splice(200);
// Kick off chat notifications in the background.
let lastSeenUpdate = {$set: {}};
lastSeenUpdate.$set[`newMessages.${this._id}`] = {name: this.name, value: true};
// do not send notifications for guilds with more than 5000 users and for the tavern
if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > 5000) {
// TODO For Tavern, only notify them if their name was mentioned
// var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names?
// User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec();
} else {
let query = {};
if (this.type === 'party') {
query['party._id'] = this._id;
} else {
query.guilds = this._id;
}
query._id = { $ne: user ? user._id : ''};
User.update(query, lastSeenUpdate, {multi: true}).exec();
if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > LARGE_GROUP_COUNT_MESSAGE_CUTOFF) {
return;
}
// Kick off chat notifications in the background.
let lastSeenUpdate = {$set: {
[`newMessages.${this._id}`]: {name: this.name, value: true},
}};
let query = {};
if (this.type === 'party') {
query['party._id'] = this._id;
} else {
query.guilds = this._id;
}
query._id = { $ne: user ? user._id : ''};
User.update(query, lastSeenUpdate, {multi: true}).exec();
};
schema.methods.startQuest = async function startQuest (user) {

View File

@@ -82,7 +82,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
span.glyphicon.glyphicon-ban-circle(tooltip=env.t('banTip'))
a.media-body
span(ng-click='clickMember(member._id, true)')
| {{member.profile.name}}
| {{member.profile.name}}
span(ng-if='group.type === "party"')
| (#[strong {{member.stats.hp.toFixed(1)}}] #{env.t('hp')})
tr(ng-if='::group.memberCount > group.members.length')
@@ -145,6 +145,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
.popover-content
markdown(text='group._editing ? groupCopy.leaderMessage : group.leaderMessage')
div(ng-controller='ChatCtrl')
.alert.alert-info.alert-sm(ng-if='group.memberCount > Shared.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF')=env.t('largeGroupNote')
h3=env.t('chat')
include ./chat-box