mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-07-21 21:34:15 +00:00
Merge branch 'develop' into release
This commit is contained in:
commit
1390c4eae5
23 changed files with 3226 additions and 2403 deletions
18
.travis.yml
18
.travis.yml
|
|
@ -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
5321
package-lock.json
generated
File diff suppressed because it is too large
Load diff
31
package.json
31
package.json
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
33
website/client/components/tasks/clearCompletedTodos.vue
Normal file
33
website/client/components/tasks/clearCompletedTodos.vue
Normal 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>
|
||||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ div
|
|||
}
|
||||
|
||||
.photo img {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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.\"",
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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, {});
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue