Merge branch 'develop' into release

This commit is contained in:
SabreCat 2018-01-25 23:20:01 +00:00
commit 1390c4eae5
23 changed files with 3226 additions and 2403 deletions

View file

@ -1,32 +1,22 @@
language: node_js
node_js:
- '6'
sudo: required
dist: precise
services:
- mongodb
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
cache:
directories:
- 'node_modules'
before_install:
- $CXX --version
- npm install -g npm@5
- if [ $REQUIRES_SERVER ]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10; echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list; sudo apt-get update; sudo apt-get install mongodb-org-server; fi
install:
- npm install &> npm.install.log || (cat npm.install.log; false)
before_script:
- npm run test:build
- cp config.json.example config.json
- sleep 15
- sleep 5
script:
- npm run $TEST
- if [ $COVERAGE ]; then ./node_modules/.bin/lcov-result-merger 'coverage/**/*.info' | ./node_modules/coveralls/bin/coveralls.js; fi
env:
global:
- CXX=g++-4.8
- DISABLE_REQUEST_LOGGING=true
matrix:
- TEST="lint"

5321
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -27,15 +27,12 @@
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"babelify": "^7.2.0",
"bcrypt": "^1.0.2",
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "^4.0.0-beta.3",
"bootstrap-vue": "^1.0.2",
"browserify": "~12.0.1",
"bootstrap": "^4.0.0",
"bootstrap-vue": "^1.5.0",
"compression": "^1.6.1",
"connect-ratelimit": "0.0.7",
"cookie-session": "^1.2.0",
"coupon-code": "^0.4.5",
"cross-env": "^4.0.0",
@ -43,13 +40,10 @@
"csv-stringify": "^1.0.2",
"cwait": "~1.0.1",
"domain-middleware": "~0.1.0",
"estraverse": "^4.1.1",
"express": "~4.14.0",
"express-basic-auth": "^1.0.1",
"express-csv": "~0.6.0",
"express-validator": "^2.18.0",
"extract-text-webpack-plugin": "^2.0.0-rc.3",
"file-loader": "^0.10.0",
"glob": "^4.3.5",
"got": "^6.1.1",
"gulp": "^3.9.0",
@ -77,21 +71,18 @@
"mongoose-id-autoinc": "~2013.7.14-4",
"morgan": "^1.7.0",
"nconf": "~0.8.2",
"nib": "^1.1.0",
"node-gcm": "^0.14.4",
"node-sass": "^4.5.0",
"nodemailer": "^2.3.2",
"object-path": "^0.9.2",
"ora": "^1.1.0",
"pageres": "^4.1.1",
"passport": "^0.3.2",
"passport-facebook": "^2.0.0",
"passport-google-oauth20": "1.0.0",
"paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.2.1",
"paypal-rest-sdk": "^1.8.1",
"popper.js": "^1.13.0",
"postcss-easy-import": "^2.0.0",
"pretty-data": "^0.40.0",
"ps-tree": "^1.0.0",
"pug": "^2.0.0-beta.12",
"push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
@ -99,11 +90,8 @@
"request": "~2.74.0",
"rimraf": "^2.4.3",
"run-sequence": "^1.1.4",
"s3-upload-stream": "^1.0.6",
"sass-loader": "^6.0.2",
"serve-favicon": "^2.3.0",
"shelljs": "^0.7.6",
"sortablejs": "^1.6.1",
"stripe": "^4.2.0",
"superagent": "^3.4.3",
"svg-inline-loader": "^0.7.1",
@ -114,8 +102,6 @@
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^4.9.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vue": "^2.5.2",
"vue-loader": "^13.3.0",
"vue-mugen-scroll": "^0.2.1",
@ -123,7 +109,7 @@
"vue-style-loader": "^3.0.0",
"vue-template-compiler": "^2.5.2",
"vuedraggable": "^2.15.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#af96f357a746463dc0461ddf5c2c13b7a4e1f484",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^2.2.1",
"webpack-merge": "^4.0.0",
"winston": "^2.1.0",
@ -169,18 +155,15 @@
"coveralls": "^2.11.2",
"cross-spawn": "^5.0.1",
"csv": "~0.3.6",
"deep-diff": "~0.1.4",
"eslint": "^3.0.0",
"eslint-config-habitrpg": "^3.0.0",
"eslint-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.3.0",
"eslint-plugin-html": "^2.0.0",
"eslint-plugin-mocha": "^4.7.0",
"event-stream": "^3.2.2",
"eventsource-polyfill": "^0.9.6",
"expect.js": "~0.2.0",
"http-proxy-middleware": "^0.17.0",
"inject-loader": "^3.0.0-beta4",
"istanbul": "^1.1.0-alpha.1",
"karma": "^1.3.0",
"karma-babel-preprocessor": "^6.0.1",
@ -195,23 +178,17 @@
"karma-spec-reporter": "0.0.24",
"karma-webpack": "^2.0.2",
"lcov-result-merger": "^1.0.2",
"lolex": "^1.4.0",
"mocha": "^3.2.0",
"mongodb": "^2.2.33",
"mongoskin": "~2.1.0",
"monk": "^4.0.0",
"nightwatch": "^0.9.12",
"phantomjs-prebuilt": "^2.1.12",
"protractor": "^3.1.1",
"raw-loader": "^0.5.1",
"require-again": "^2.0.0",
"rewire": "^2.3.3",
"selenium-server": "^3.0.1",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0",
"sinon-stub-promise": "^4.0.0",
"superagent-defaults": "^0.1.13",
"vinyl-transform": "^1.0.0",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.6.1"

View file

@ -95,13 +95,23 @@ describe('GET /challenges/:challengeId/members/:memberId', () => {
expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id);
});
it('returns the tasks without the tags', async () => {
it('returns the tasks without the tags and checklist', async () => {
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
let challenge = await generateChallenge(user, group);
let taskText = 'Test Text';
await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
await user.post(`/tasks/challenge/${challenge._id}`, [{
type: 'todo',
text: taskText,
checklist: [
{
_id: 123,
text: 'test',
},
],
}]);
let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
expect(memberProgress.tasks[0]).not.to.have.key('tags');
expect(memberProgress.tasks[0].checklist).to.eql([]);
});
});

View file

@ -74,14 +74,15 @@ describe('Challenge Model', () => {
it('adds tasks to challenge and challenge members', async () => {
await challenge.addTasks([task]);
let updatedLeader = await User.findOne({_id: leader._id});
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
const updatedLeader = await User.findOne({_id: leader._id});
const updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
const syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text;
});
expect(syncedTask).to.exist;
expect(syncedTask.notes).to.eql(task.notes);
expect(syncedTask.tags[0]).to.eql(challenge._id);
});
it('syncs a challenge to a user', async () => {

View file

@ -17,3 +17,6 @@ let sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(global.sinon);
global.sandbox = sinon.sandbox.create();
global.Promise = Bluebird;
import setupNconf from '../../website/server/libs/setupNconf';
setupNconf('./config.json.example');

View file

@ -1,29 +1,29 @@
<template lang="pug">
.standard-page
.row.tasks-navigation
.col-4
.col-12.col-md-4
h1 Group's Tasks
// @TODO: Abstract to component!
.col-4
.col-12.col-md-4
.input-group
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
.filter-panel(v-if="isFilterPanelOpen")
.tags-category.d-flex(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key")
.tags-header
.tags-category(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key")
.tags-header.col-12
strong(v-once) {{ $t(tagsType.key) }}
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
.tags-list.container
.tags-list.container.col-12
.row(:class="{'no-gutters': !editingTags}")
template(v-if="editingTags && tagsType.key === 'tags'")
.col-6(v-for="(tag, tagIndex) in tagsSnap")
.col-12.col-md-6(v-for="(tag, tagIndex) in tagsSnap")
.inline-edit-input-group.tag-edit-item.input-group
input.tag-edit-input.inline-edit-input.form-control(type="text", :value="tag.name")
.input-group-append(@click="removeTag(tagIndex)")
.svg-icon.destroy-icon(v-html="icons.destroy")
.col-6
.col-12.col-md-6
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event)", v-model="newTag")
template(v-else)
.col-6(v-for="(tag, tagIndex) in tagsType.tags")
.col-12.col-md-6(v-for="(tag, tagIndex) in tagsType.tags")
.custom-control.custom-checkbox
input.custom-control-input(
type="checkbox",
@ -53,7 +53,7 @@
.d-flex.align-items-center
span(v-once) {{ $t('filter') }}
.svg-icon.filter-icon(v-html="icons.filter")
#create-dropdown.col-1.offset-3
#create-dropdown.col-12.col-md-1.offset-md-3
b-dropdown(:right="true", :variant="'success'")
div(slot="button-content")
.svg-icon.positive(v-html="icons.positive")
@ -138,6 +138,7 @@
padding-left: 24px;
padding-right: 24px;
max-width: 40vw;
width: 100%;
z-index: 9999;
background: $white;
border-radius: 2px;

View file

@ -36,7 +36,7 @@ div
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.messageIcon")
span.text {{$t('sendMessage')}}
b-dropdown-item(@click='promoteToLeader(member)', v-if='isLeader || isAdmin')
b-dropdown-item(@click='promoteToLeader(member)', v-if='shouldShowPromoteToLeader')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('promoteToLeader')}}
@ -286,6 +286,9 @@ export default {
},
computed: {
...mapState({user: 'user.data'}),
shouldShowPromoteToLeader () {
return !this.challengeId && (this.isLeader || this.isAdmin);
},
isLeader () {
if (!this.group || !this.group.leader) return false;
return this.user._id === this.group.leader || this.user._id === this.group.leader._id;

View file

@ -331,6 +331,7 @@ export default {
this.isUserDropdownOpen = !this.isUserDropdownOpen;
},
sync () {
this.$root.$emit('habitica::resync-requested');
return Promise.all([
this.$store.dispatch('user:fetch', {forceLoad: true}),
this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}),

View file

@ -1,4 +1,4 @@
<template lang='pug'>
<template lang="pug">
b-modal#broken-task-modal(title="Broken Challenge", size='sm', :hide-footer="true")
.modal-body(v-if='brokenChallengeTask && brokenChallengeTask.challenge')
div(v-if='brokenChallengeTask.challenge.broken === "TASK_DELETED" || brokenChallengeTask.challenge.broken === "CHALLENGE_TASK_NOT_FOUND"')
@ -59,7 +59,7 @@ export default {
}),
async unlink (keepOption) {
if (keepOption.indexOf('-all') !== -1) {
this.unlinkAllTasks({
await this.unlinkAllTasks({
challengeId: this.brokenChallengeTask.challenge.id,
keep: keepOption,
});

View file

@ -0,0 +1,33 @@
<template lang="pug">
.text-center.delete-completed
.help-text {{ $t('clearCompletedDescription') }}
.btn.btn-danger(@click='clearTodos()') {{ $t('clearCompleted') }}
</template>
<style lang="scss">
.delete-completed {
padding: 1em;
.help-text {
color: #878190;
font-size: 12px;
}
.btn-danger {
font-size: 12px;
padding: .4em .6em;
margin-top: .5em;
}
}
</style>
<script>
export default {
methods: {
clearTodos () {
if (!confirm(this.$t('clearCompletedConfirm'))) return;
this.$store.dispatch('tasks:clearCompletedTodos');
},
},
};
</script>

View file

@ -26,6 +26,7 @@
)
transition(name="quick-add-tip-slide")
.quick-add-tip.small-text(v-show="quickAddFocused", v-html="$t('addMultipleTip')")
clear-completed-todos(v-if="activeFilters[type].label === 'complete2'")
.column-background(
v-if="isUser === true",
:class="{'initial-description': initialColumnDescription}",
@ -243,6 +244,7 @@
<script>
import Task from './task';
import ClearCompletedTodos from './clearCompletedTodos';
import sortBy from 'lodash/sortBy';
import throttle from 'lodash/throttle';
import buyMixin from 'client/mixins/buy';
@ -267,6 +269,7 @@ export default {
mixins: [buyMixin, notifications],
components: {
Task,
ClearCompletedTodos,
BuyQuestModal,
shopItem,
draggable,
@ -467,6 +470,17 @@ export default {
this.$root.$on('buyModal::boughtItem', () => {
this.forceRefresh = new Date();
});
if (this.type !== 'todo') return;
this.$root.$on('habitica::resync-requested', () => {
if (this.activeFilters.todo.label !== 'complete2') return;
this.loadCompletedTodos(true);
});
},
destroyed () {
this.$root.$off('buyModal::boughtItem');
if (this.type !== 'todo') return;
this.$root.$off('habitica::resync-requested');
},
methods: {
...mapActions({

View file

@ -27,7 +27,7 @@
.form-group
label(v-once) {{ $t('cost') }}
.input-group
.input-group-prepend.input-group-icon
.input-group-prepend.input-group-icon.align-items-center
.svg-icon.gold(v-html="icons.gold")
input.form-control(type="number", v-model="task.value", required, placeholder="1.0", step="0.01", min="0")

View file

@ -357,7 +357,7 @@ div
}
.photo img {
width: 100%;
max-width: 100%;
}
.header {

View file

@ -203,14 +203,18 @@ export default {
queryParams.groupId = group._id;
}
let cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
await axios.get(cancelUrl);
try {
const cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
await axios.get(cancelUrl);
this.loading = false;
alert(this.$t('paypalCanceled'));
// @TODO: We should probably update the api to return the new sub data eventually.
await this.$store.dispatch('user:fetch', {forceLoad: true});
// Success
alert(this.$t('paypalCanceled'));
this.$router.push('/');
this.loading = false;
} catch (e) {
alert(e.response.data.message);
}
},
},
};

View file

@ -50,6 +50,14 @@ export async function fetchCompletedTodos (store, forceLoad = false) {
}
}
export async function clearCompletedTodos (store) {
await axios.post('/api/v3/tasks/clearCompletedTodos');
store.state.tasks.data.todos = store.state.tasks.data.todos.filter(task => {
return !task.completed;
});
}
export function order (store, [rawTasks, tasksOrder]) {
const tasks = {
habits: [],

View file

@ -1,5 +1,7 @@
{
"clearCompleted": "Delete Completed",
"clearCompletedDescription": "Completed To-Dos are deleted after 30 days for non-subscribers and 90 days for subscribers.",
"clearCompletedConfirm": "Are you sure you want to clear your completed todos?",
"lotOfToDos": "Your most recent 30 completed To-Dos are shown here. You can see older completed To-Dos from Data > Data Display Tool or Data > Export Data > User Data.",
"deleteToDosExplanation": "If you click the button below, all of your completed To-Dos and archived To-Dos will be permanently deleted, except for To-Dos from active challenges and Group Plans. Export them first if you want to keep a record of them.",
"addMultipleTip": "<strong>Tip:</strong> To add multiple Tasks, separate each one using a line break (Shift + Enter) and then press \"Enter.\"",

View file

@ -25,7 +25,7 @@ function trueRandom () {
return Math.random();
}
module.exports = function randomDrop (user, options, req = {}) {
module.exports = function randomDrop (user, options, req = {}, analytics) {
let acceptableDrops;
let drop;
let dropMultiplier;
@ -112,6 +112,16 @@ module.exports = function randomDrop (user, options, req = {}) {
}, req.language);
}
if (analytics) {
analytics.track('dropped item', {
uuid: user._id,
itemKey: drop.key,
acquireMethod: 'Drop',
category: 'behavior',
headers: req.headers,
});
}
user._tmp.drop = drop;
user.items.lastDrop.date = Number(new Date());
user.items.lastDrop.count++;

View file

@ -257,6 +257,16 @@ api.createChallenge = {
privacy: group.privacy,
};
res.analytics.track('challenge create', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
challengeID: response._id,
groupID: group._id,
groupName: group.privacy === 'private' ? null : group.name,
groupType: group._id === TAVERN_ID ? 'tavern' : group.type,
});
res.respond(201, response);
},
};
@ -315,6 +325,16 @@ api.joinChallenge = {
let chalLeader = await User.findById(response.leader).select(nameFields).exec();
response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
res.analytics.track('challenge join', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
challengeID: challenge._id,
groupID: group._id,
groupName: group.privacy === 'private' ? null : group.name,
groupType: group._id === TAVERN_ID ? 'tavern' : group.type,
});
res.respond(200, response);
},
};
@ -351,6 +371,17 @@ api.leaveChallenge = {
// Unlink challenge's tasks from user's tasks and save the challenge
await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]);
res.analytics.track('challenge leave', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
challengeID: challenge._id,
groupID: challenge.group._id,
groupName: challenge.group.privacy === 'private' ? null : challenge.group.name,
groupType: challenge.group._id === TAVERN_ID ? 'tavern' : challenge.group.type,
});
res.respond(200, {});
},
};
@ -679,6 +710,17 @@ api.deleteChallenge = {
// Close channel in background, some ops are run in the background without `await`ing
await challenge.closeChal({broken: 'CHALLENGE_DELETED'});
res.analytics.track('challenge delete', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
challengeID: challenge._id,
groupID: challenge.group._id,
groupName: challenge.group.privacy === 'private' ? null : challenge.group.name,
groupType: challenge.group._id === TAVERN_ID ? 'tavern' : challenge.group.type,
});
res.respond(200, {});
},
};
@ -717,6 +759,18 @@ api.selectChallengeWinner = {
// Close channel in background, some ops are run in the background without `await`ing
await challenge.closeChal({broken: 'CHALLENGE_CLOSED', winner});
res.analytics.track('challenge close', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
challengeID: challenge._id,
challengeWinnerID: winner._id,
groupID: challenge.group._id,
groupName: challenge.group.privacy === 'private' ? null : challenge.group.name,
groupType: challenge.group._id === TAVERN_ID ? 'tavern' : challenge.group.type,
});
res.respond(200, {});
},
};

View file

@ -413,7 +413,10 @@ api.getChallengeMemberProgress = {
// manually call toJSON with minimize: true so empty paths aren't returned
let response = member.toJSON({minimize: true});
delete response.challenges;
response.tasks = chalTasks.map(chalTask => chalTask.toJSON({minimize: true}));
response.tasks = chalTasks.map(chalTask => {
chalTask.checklist = []; // Clear checklists as they are private
return chalTask.toJSON({minimize: true});
});
res.respond(200, response);
},
};

View file

@ -23,6 +23,7 @@ import common from '../../../common';
import Bluebird from 'bluebird';
import _ from 'lodash';
import logger from '../../libs/logger';
import moment from 'moment';
const MAX_SCORE_NOTES_LENGTH = 256;
@ -166,6 +167,16 @@ api.createUserTasks = {
res.respond(201, tasks.length === 1 ? tasks[0] : tasks);
tasks.forEach((task) => {
// Track when new users (first 7 days) create tasks
if (moment().diff(user.auth.timestamps.created, 'days') < 7) {
res.analytics.track('task create', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
taskType: task.type,
});
}
taskActivityWebhook.send(user.webhooks, {
type: 'created',
task,
@ -241,6 +252,16 @@ api.createChallengeTasks = {
// If adding tasks to a challenge -> sync users
if (challenge) challenge.addTasks(tasks);
tasks.forEach((task) => {
res.analytics.track('task create', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
taskType: task.type,
challengeID: challenge._id,
});
});
},
};
@ -584,7 +605,7 @@ api.scoreTask = {
let [delta] = common.ops.scoreTask({task, user, direction}, req);
// Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results)
if (direction === 'up') user.fns.randomDrop({task, delta}, req);
if (direction === 'up') user.fns.randomDrop({task, delta}, req, res.analytics);
// If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list
// TODO move to common code?
@ -654,16 +675,16 @@ api.scoreTask = {
}
}
/*
* TODO: enable score task analytics if desired
res.analytics.track('score task', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
taskType: task.type,
direction
});
*/
// Track when new users (first 7 days) score tasks
if (moment().diff(user.auth.timestamps.created, 'days') < 7) {
res.analytics.track('task score', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
taskType: task.type,
direction,
});
}
},
};

View file

@ -58,6 +58,16 @@ api.createGroupTasks = {
let tasks = await createTasks(req, res, {user, group});
res.respond(201, tasks.length === 1 ? tasks[0] : tasks);
tasks.forEach((task) => {
res.analytics.track('task create', {
uuid: user._id,
hitType: 'event',
category: 'behavior',
taskType: task.type,
groupID: group._id,
});
});
},
};

View file

@ -182,7 +182,10 @@ async function _addTaskFn (challenge, tasks, memberId) {
let userTask = new Tasks[chalTask.type](Tasks.Task.sanitize(syncableAttrs(chalTask)));
userTask.challenge = {taskId: chalTask._id, id: challenge._id, shortName: challenge.shortName};
userTask.userId = memberId;
userTask.notes = chalTask.notes; // We want to sync the notes when the task is first added to the challenge
// We want to sync the notes and tags when the task is first added to the challenge
userTask.notes = chalTask.notes;
userTask.tags.push(challenge._id);
let tasksOrderList = updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`];
if (!tasksOrderList) {