Group plans misc fixes (#8388)

* Added notification for approval request in the group leaders language

* Added test for group task meta actions. Added sync when user claims

* Added tests for group task actions. Ensured assigned members are synce when added or removed

* Fixed approval required toggle

* Added support for users with comma in their name

* Fixed sync issue when user is approved and reloads the website

* Added advance options for group rewards

* Added back ticks to group claim message

* Fixed disappearing tasks that need approval

* Up chat limit to 400 for subbed groups

* Fixed line endings

* Updated activie subscription check

* Added group isSubscribed function

* Changed to isAfter
This commit is contained in:
Keith Holliday
2017-01-18 07:54:49 -07:00
committed by GitHub
parent e4bb82768c
commit 28fec237fe
16 changed files with 169 additions and 10 deletions

View File

@@ -34,6 +34,10 @@ describe('POST /tasks/:id/score/:direction', () => {
});
it('prevents user from scoring a task that needs to be approved', async () => {
await user.update({
'preferences.language': 'cs',
});
let memberTasks = await member.get('/tasks/user');
let syncedTask = find(memberTasks, findAssignedTask);
@@ -52,7 +56,7 @@ describe('POST /tasks/:id/score/:direction', () => {
expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', {
user: member.auth.local.username,
taskName: updatedTask.text,
}));
}, 'cs')); // This test only works if we have the notification translated
expect(user.notifications[0].data.groupId).to.equal(guild._id);
expect(updatedTask.group.approval.requested).to.equal(true);

View File

@@ -809,6 +809,20 @@ describe('Group Model', () => {
expect(party.chat).to.have.a.lengthOf(200);
});
it('cuts down chat to 400 messages when group is subcribed', () => {
party.purchased.plan.customerId = 'test-customer-id';
for (let i = 0; i < 420; i++) {
party.chat.push({ text: 'a message' });
}
expect(party.chat).to.have.a.lengthOf(420);
party.sendChat('message');
expect(party.chat).to.have.a.lengthOf(400);
});
it('updates users about new messages in party', () => {
party.sendChat('message');

View File

@@ -0,0 +1,63 @@
describe('Group Tasks Meta Actions Controller', () => {
let rootScope, scope, user, userSerivce;
beforeEach(() => {
module(function($provide) {
$provide.value('User', {});
});
inject(($rootScope, $controller) => {
rootScope = $rootScope;
user = specHelper.newUser();
user._id = "unique-user-id";
userSerivce = {user: user};
scope = $rootScope.$new();
scope.task = {
group: {
assignedUsers: [],
approval: {
required: false,
}
},
};
scope.task._edit = angular.copy(scope.task);
$controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce});
});
});
describe('toggleTaskRequiresApproval', function () {
it('toggles task approval required field from false to true', function () {
scope.toggleTaskRequiresApproval();
expect(scope.task._edit.group.approval.required).to.be.true;
});
it('toggles task approval required field from true to false', function () {
scope.task._edit.group.approval.required = true;
scope.toggleTaskRequiresApproval();
expect(scope.task._edit.group.approval.required).to.be.false;
});
});
describe('assign events', function () {
it('adds a group member to assigned users on "addedGroupMember" event ', () => {
var testId = 'test-id';
rootScope.$broadcast('addedGroupMember', testId);
expect(scope.task.group.assignedUsers).to.contain(testId);
expect(scope.task._edit.group.assignedUsers).to.contain(testId);
});
it('removes a group member to assigned users on "addedGroupMember" event ', () => {
var testId = 'test-id';
scope.task.group.assignedUsers.push(testId);
scope.task._edit.group.assignedUsers.push(testId);
rootScope.$broadcast('removedGroupMember', testId);
expect(scope.task.group.assignedUsers).to.not.contain(testId);
expect(scope.task._edit.group.assignedUsers).to.not.contain(testId);
});
});
});

View File

@@ -0,0 +1,42 @@
describe('Group Task Actions Controller', () => {
let scope, user, userSerivce;
beforeEach(() => {
module(function($provide) {
$provide.value('User', {});
});
inject(($rootScope, $controller) => {
user = specHelper.newUser();
user._id = "unique-user-id";
userSerivce = {user: user};
userSerivce.sync = sandbox.stub();
scope = $rootScope.$new();
$controller('GroupTaskMetaActionsCtrl', {$scope: scope, User: userSerivce});
scope.task = {
group: {
assignedUsers: [],
},
};
});
});
describe('claim', () => {
beforeEach(() => {
sandbox.stub(window, 'confirm').returns(true);
});
it('adds user to assigned users of scope task ', () => {
scope.claim();
expect(scope.task.group.assignedUsers).to.contain(user._id);
});
it('syncs user tasks ', () => {
scope.claim();
expect(userSerivce.sync).to.be.calledOnce;
});
});
});

View File

@@ -44,6 +44,7 @@ module.exports = function karmaConfig (config) {
'../../../website/client-old/js/filters/**/*.js',
'../../../website/client-old/js/directives/**/*.js',
'../../../website/client-old/js/controllers/**/*.js',
'../../../website/client-old/js/components/**/*.js',
'../../../test/client-old/spec/specHelper.js',
'../../../test/client-old/spec/**/*.js',

View File

@@ -37,6 +37,7 @@
allowedTags: allowedTags,
allowDuplicates: false,
preserveCase: true,
delimeter: '|',
placeholder: window.env.t('assignFieldPlaceholder'),
onBeforeTagAdd: function(event, tag) {
return confirm(window.env.t('confirmAddTag', {tag: tag}));

View File

@@ -3,16 +3,30 @@ habitrpg.controller('GroupTaskActionsCtrl', ['$scope', 'Shared', 'Tasks', 'User'
$scope.assignedMembers = [];
$scope.user = User.user;
// We must use a separate field here, because task.group is private. So, instead, we send this tmp field to alter the approval.
$scope.task._edit.requiresApproval = false;
if ($scope.task.group.approval.required) {
$scope.task._edit.requiresApproval = $scope.task.group.approval.required;
}
$scope.toggleTaskRequiresApproval = function () {
$scope.task._edit.group.approval.required = !$scope.task._edit.group.approval.required;
$scope.task._edit.requiresApproval = $scope.task._edit.group.approval.required;
}
$scope.$on('addedGroupMember', function(evt, userId) {
$scope.task.group.assignedUsers.push(userId);
if ($scope.task._edit) $scope.task._edit.group.assignedUsers.push(userId);
Tasks.assignTask($scope.task.id, userId);
});
$scope.$on('removedGroupMember', function(evt, userId) {
var index = $scope.task.group.assignedUsers.indexOf(userId);
$scope.task.group.assignedUsers.splice(index, 1);
if ($scope.task._edit) {
var index = $scope.task._edit.group.assignedUsers.indexOf(userId);
$scope.task._edit.group.assignedUsers.splice(index, 1);
}
Tasks.unAssignTask($scope.task.id, userId);
});
}]);

View File

@@ -7,6 +7,7 @@ habitrpg.controller('GroupTaskMetaActionsCtrl', ['$scope', 'Shared', 'Tasks', 'U
if (!confirm("Are you sure you want to claim this task?")) return;
Tasks.assignTask($scope.task.id, $scope.user._id);
$scope.task.group.assignedUsers.push($scope.user._id);
User.sync();
};
$scope.userIsAssigned = function () {

View File

@@ -177,7 +177,7 @@ habitrpg.controller('GroupTasksCtrl', ['$scope', 'Shared', 'Tasks', 'User', func
var claimingUsers = [];
task.group.assignedUsers.forEach(function (userId) {
claimingUsers.push(memberIdToProfileNameMap[userId]);
claimingUsers.push('"' + memberIdToProfileNameMap[userId] + '"');
})
if (claimingUsers.length > 0) content += window.env.t('claimedBy', {claimingUsers: claimingUsers.join(', ')});

View File

@@ -171,6 +171,7 @@ habitrpg.controller('NotificationCtrl',
if (scoreTaskNotification) {
Notification.markdown(scoreTaskNotification.data.message);
User.score({params:{task: scoreTaskNotification.data.scoreTask, direction: "up"}});
User.sync();
}
});
}

View File

@@ -109,6 +109,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
} else {
$scope.score(task, "down");
}
if (task.group && task.group.approval && task.group.approval.required && !task.group.approval.approved) task.completed = false;
};
$scope.saveTask = function(task, stayOpen, isSaveAndClose) {

View File

@@ -247,7 +247,7 @@
"approvalsTitle": "Tasks Awaiting Approval",
"upgradeTitle": "Upgrade",
"blankApprovalsDescription": "When your group completes tasks that need your approval, they'll appear here! Adjust approval requirement settings under task editing.",
"userIsClamingTask": "<%= username %> has claimed \"<%= task %>\"",
"userIsClamingTask": "`<%= username %> has claimed \"<%= task %>\"`",
"approvalRequested": "Approval Requested",
"refreshApprovals": "Refresh Approvals",
"refreshGroupTasks": "Refresh Group Tasks",

View File

@@ -354,7 +354,7 @@ api.scoreTask = {
message: res.t('userHasRequestedTaskApproval', {
user: user.profile.name,
taskName: task.text,
}),
}, groupLeader.preferences.language),
groupId: group._id,
});

View File

@@ -1,3 +1,4 @@
import moment from 'moment';
import mongoose from 'mongoose';
import {
model as User,
@@ -392,7 +393,17 @@ schema.methods.sendChat = function sendChat (message, user) {
let newMessage = chatDefaults(message, user);
this.chat.unshift(newMessage);
this.chat.splice(200);
const MAX_CHAT_COUNT = 200;
const MAX_SUBBED_GROUP_CHAT_COUNT = 400;
let maxCount = MAX_CHAT_COUNT;
if (this.isSubscribed()) {
maxCount = MAX_SUBBED_GROUP_CHAT_COUNT;
}
this.chat.splice(maxCount);
// 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 > LARGE_GROUP_COUNT_MESSAGE_CUTOFF) {
@@ -882,8 +893,7 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') {
let group = this;
let update = {};
let plan = group.purchased.plan;
if (group.memberCount <= 1 && group.privacy === 'private' && plan && plan.customerId && !plan.dateTerminated) {
if (group.memberCount <= 1 && group.privacy === 'private' && group.isSubscribed()) {
throw new NotAuthorized(shared.i18n.t('cannotDeleteActiveGroup'));
}
@@ -1136,6 +1146,12 @@ schema.methods.removeTask = async function groupRemoveTask (task) {
}, {multi: true}).exec();
};
schema.methods.isSubscribed = function isSubscribed () {
let now = new Date();
let plan = this.purchased.plan;
return plan && plan.customerId && (!plan.dateTerminated || moment(plan.dateTerminated).isAfter(now));
};
export let model = mongoose.model('Group', schema);
// initialize tavern if !exists (fresh installs)

View File

@@ -5,6 +5,6 @@ script(type='text/ng-template', id='partials/groups.tasks.actions.html')
ul.priority-multiplier
li {{requiresApproval}}
button(type='button', ng-class='{active: task._edit.requiresApproval==true}',
ng-click='task._edit.requiresApproval = !task._edit.requiresApproval')
button(type='button', ng-class='{active: task._edit.group.approval.required == true}',
ng-click='toggleTaskRequiresApproval()')
=env.t('approvalRequired')

View File

@@ -1,4 +1,4 @@
div(ng-if='::task.type!="reward"')
div(ng-if='(task.type !== "reward") || (!obj.auth && obj.purchased && obj.purchased.active)')
button.advanced-options-toggle.option-title.mega(type='button',
ng-class='{active: task._edit._advanced}',
ng-click='task._edit._advanced = !task._edit._advanced', tooltip=env.t('expandCollapse'))