mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-07-16 11:11:43 +00:00
Tasks score notes (#8507)
* Added setting and modal for score notes * Added persistent score notes * Fixed linting issues and documented new field * Added max length to task score notes * Added check for score notes existence * Combined tasks perferences
This commit is contained in:
parent
68a042cdb9
commit
93befcebcc
12 changed files with 102 additions and 9 deletions
|
|
@ -315,6 +315,30 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||
|
||||
expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
|
||||
});
|
||||
|
||||
it('adds score notes to task', async () => {
|
||||
let scoreNotesString = 'test-notes';
|
||||
|
||||
await user.post(`/tasks/${habit._id}/score/up`, {
|
||||
scoreNotes: scoreNotesString,
|
||||
});
|
||||
let updatedTask = await user.get(`/tasks/${habit._id}`);
|
||||
|
||||
expect(updatedTask.history[0].scoreNotes).to.eql(scoreNotesString);
|
||||
});
|
||||
|
||||
it('errors when score notes are too large', async () => {
|
||||
let scoreNotesString = new Array(258).join('a');
|
||||
|
||||
await expect(user.post(`/tasks/${habit._id}/score/up`, {
|
||||
scoreNotes: scoreNotesString,
|
||||
}))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('taskScoreNotesTooLong'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('reward', () => {
|
||||
|
|
|
|||
|
|
@ -212,6 +212,15 @@ describe('shared.ops.scoreTask', () => {
|
|||
expect(ref.afterUser.stats.gp).to.be.greaterThan(ref.beforeUser.stats.gp);
|
||||
});
|
||||
|
||||
it('adds score notes', () => {
|
||||
let scoreNotesString = 'scoreNotes';
|
||||
habit.scoreNotes = scoreNotesString;
|
||||
options = { user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false };
|
||||
scoreTask(options);
|
||||
|
||||
expect(habit.history[0].scoreNotes).to.eql(scoreNotesString);
|
||||
});
|
||||
|
||||
it('down', () => {
|
||||
scoreTask({user: ref.afterUser, task: habit, direction: 'down', times: 5, cron: false}, {});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Content', 'Shared', 'Guide', 'Tasks', 'Analytics',
|
||||
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Content, Shared, Guide, Tasks, Analytics) {
|
||||
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Content', 'Shared', 'Guide', 'Tasks', 'Analytics', '$modal',
|
||||
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Content, Shared, Guide, Tasks, Analytics, $modal) {
|
||||
$scope.obj = User.user; // used for task-lists
|
||||
$scope.user = User.user;
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
|||
return Shared.count.remainingGearInSet(gear, 'armoire');
|
||||
};
|
||||
|
||||
$scope.score = function(task, direction) {
|
||||
function scoreTask (task, direction) {
|
||||
switch (task.type) {
|
||||
case 'reward':
|
||||
playRewardSound(task);
|
||||
|
|
@ -26,8 +26,36 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
|
|||
if (direction === 'down') $rootScope.playSound('Minus_Habit');
|
||||
else if (direction === 'up') $rootScope.playSound('Plus_Habit');
|
||||
}
|
||||
User.score({params:{task: task, direction:direction}});
|
||||
User.score({
|
||||
params: {
|
||||
task: task,
|
||||
direction:direction
|
||||
},
|
||||
body: {
|
||||
scoreNotes: task.scoreNotes,
|
||||
},
|
||||
});
|
||||
Analytics.updateUser();
|
||||
}
|
||||
|
||||
$scope.score = function(task, direction) {
|
||||
if (!User.user.preferences.tasks.confirmScoreNotes) return scoreTask(task, direction);
|
||||
|
||||
$modal.open({
|
||||
templateUrl: 'modals/task-notes.html',
|
||||
controller: function ($scope, task) {
|
||||
$scope.task = task;
|
||||
},
|
||||
resolve: {
|
||||
task: function() {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
}).result.then(function(result) {
|
||||
task = result;
|
||||
if (!task) return;
|
||||
scoreTask(task, direction);
|
||||
});
|
||||
};
|
||||
|
||||
function addUserTasks(listDef, tasks) {
|
||||
|
|
|
|||
|
|
@ -144,10 +144,13 @@ angular.module('habitrpg')
|
|||
});
|
||||
};
|
||||
|
||||
function scoreTask (taskId, direction) {
|
||||
function scoreTask (taskId, direction, body) {
|
||||
if (!body) body = {};
|
||||
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/v3/tasks/' + taskId + '/score/' + direction,
|
||||
data: body,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ angular.module('habitrpg')
|
|||
},
|
||||
|
||||
allocate: function (data) {
|
||||
callOpsFunctionAndRequest('allocate', 'allocate', "POST",'', data);
|
||||
callOpsFunctionAndRequest('allocate', 'allocate', "POST", '', data);
|
||||
},
|
||||
|
||||
allocateNow: function () {
|
||||
|
|
@ -243,7 +243,8 @@ angular.module('habitrpg')
|
|||
return;
|
||||
}
|
||||
|
||||
Tasks.scoreTask(data.params.task._id, data.params.direction).then(function (res) {
|
||||
Tasks.scoreTask(data.params.task._id, data.params.direction, data.body)
|
||||
.then(function (res) {
|
||||
var tmp = res.data.data._tmp || {}; // used to notify drops, critical hits and other bonuses
|
||||
var crit = tmp.crit;
|
||||
var drop = tmp.drop;
|
||||
|
|
|
|||
|
|
@ -147,5 +147,7 @@
|
|||
"taskApprovalHasBeenRequested": "Approval has been requested",
|
||||
"approvals": "Approvals",
|
||||
"approvalRequired": "Approval Required",
|
||||
"confirmScoreNotes": "Confirm task scoring with notes",
|
||||
"taskScoreNotesTooLong": "Task score notes must be less than 256 characters",
|
||||
"groupTasksByChallenge": "Group tasks by challenge title"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,11 +210,15 @@ module.exports = function scoreTask (options = {}, req = {}) {
|
|||
_gainMP(user, _.max([0.25, 0.0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
|
||||
|
||||
task.history = task.history || [];
|
||||
|
||||
// Add history entry, even more than 1 per day
|
||||
task.history.push({
|
||||
let historyEntry = {
|
||||
date: Number(new Date()),
|
||||
value: task.value,
|
||||
});
|
||||
};
|
||||
if (task.scoreNotes) historyEntry.scoreNotes = task.scoreNotes;
|
||||
|
||||
task.history.push(historyEntry);
|
||||
|
||||
_updateCounter(task, direction, times);
|
||||
} else if (task.type === 'daily') {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import Bluebird from 'bluebird';
|
|||
import _ from 'lodash';
|
||||
import logger from '../../libs/logger';
|
||||
|
||||
const MAX_SCORE_NOTES_LENGTH = 256;
|
||||
|
||||
/**
|
||||
* @apiDefine TaskNotFound
|
||||
* @apiError (404) {NotFound} TaskNotFound The specified task could not be found.
|
||||
|
|
@ -482,6 +484,7 @@ api.updateTask = {
|
|||
*
|
||||
* @apiParam {String} taskId The task _id or alias
|
||||
* @apiParam {String="up","down"} direction The direction for scoring the task
|
||||
* @apiParam {String} scoreNotes Notes explaining the scoring
|
||||
*
|
||||
* @apiExample {json} Example call:
|
||||
* curl -X "POST" https://habitica.com/api/v3/tasks/test-api-params/score/up
|
||||
|
|
@ -509,11 +512,15 @@ api.scoreTask = {
|
|||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let user = res.locals.user;
|
||||
let scoreNotes = req.body.scoreNotes;
|
||||
if (scoreNotes && scoreNotes.length > MAX_SCORE_NOTES_LENGTH) throw new NotAuthorized(res.t('taskScoreNotesTooLong'));
|
||||
let {taskId} = req.params;
|
||||
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id, {userId: user._id});
|
||||
let direction = req.params.direction;
|
||||
|
||||
if (scoreNotes) task.scoreNotes = scoreNotes;
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
|
||||
if (task.group.approval.required && !task.group.approval.approved) {
|
||||
|
|
|
|||
|
|
@ -469,6 +469,7 @@ let schema = new Schema({
|
|||
},
|
||||
tasks: {
|
||||
groupByChallenge: {type: Boolean, default: false},
|
||||
confirmScoreNotes: {type: Boolean, default: false},
|
||||
},
|
||||
improvementCategories: {
|
||||
type: Array,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
|||
.checkbox
|
||||
label=env.t('suppressStreakModal')
|
||||
input(type='checkbox', ng-model='user.preferences.suppressModals.streak', ng-change='set({"preferences.suppressModals.streak": user.preferences.suppressModals.streak?true: false})')
|
||||
.checkbox
|
||||
label=env.t('confirmScoreNotes')
|
||||
input(type='checkbox', ng-model='user.preferences.tasks.confirmScoreNotes', ng-change='set({"preferences.tasks.confirmScoreNotes": user.preferences.tasks.confirmScoreNotes ? true: false})')
|
||||
|
||||
.checkbox
|
||||
label=env.t('groupTasksByChallenge')
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ include ./login-incentives.jade
|
|||
include ./login-incentives-reward-unlocked.jade
|
||||
include ./generic.jade
|
||||
include ./tasks-edit.jade
|
||||
include ./task-notes.jade
|
||||
|
||||
//- Settings
|
||||
script(type='text/ng-template', id='modals/change-day-start.html')
|
||||
|
|
|
|||
10
website/views/shared/modals/task-notes.jade
Normal file
10
website/views/shared/modals/task-notes.jade
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
script(id='modals/task-notes.html', type='text/ng-template')
|
||||
.modal-header
|
||||
h4 Task Notes
|
||||
|
||||
.modal-body
|
||||
textarea.form-control(ng-model="task.scoreNotes", row="10")
|
||||
|
||||
.modal-footer
|
||||
.btn.btn-default(ng-click='$close()')=env.t('cancel')
|
||||
.btn.btn-primary(ng-click="$close(task)")=env.t('save')
|
||||
Loading…
Reference in a new issue