Refactoring & Fix: editing group and challenges from user dashboard (#11418)

* Refactoring & Feature: edit/delete group and challenge tasks

- Remove showOption from tasks props
- Pass all needed data to task for understand in mapGetter function which controls we should show
- Improve current solution with edit and delete logic

* Fix: this in template

* Fix & Test: extend tests, fix can Edit/Delete functions

* Fix: allow user edit challenge tasks on dashboard

* Fix: test case after code change

* fix import path

* Fix:

- Extend canEdit and canDelete functions with admin role
- Clarify canEdit and canDelete conditions
- Extend test cases
This commit is contained in:
Aleksey
2019-11-16 20:36:27 +03:00
committed by Matteo Pagliazzi
parent a859dbd646
commit eaad244181
7 changed files with 236 additions and 30 deletions

View File

@@ -123,7 +123,7 @@
class="col-12 col-sm-6" class="col-12 col-sm-6"
:type="column" :type="column"
:task-list-override="tasksByType[column]" :task-list-override="tasksByType[column]"
:show-options="showOptions" :challenge="challenge"
@editTask="editTask" @editTask="editTask"
@taskDestroyed="taskDestroyed" @taskDestroyed="taskDestroyed"
/> />
@@ -386,9 +386,6 @@ export default {
canJoin () { canJoin () {
return !this.isMember; return !this.isMember;
}, },
showOptions () {
return this.isLeader;
},
}, },
mounted () { mounted () {
if (!this.searchId) this.searchId = this.challengeId; if (!this.searchId) this.searchId = this.challengeId;

View File

@@ -75,7 +75,6 @@
class="col-12 col-md-3" class="col-12 col-md-3"
:type="column" :type="column"
:task-list-override="tasksByType[column]" :task-list-override="tasksByType[column]"
:show-options="showOptions"
:group="group" :group="group"
:search-text="searchText" :search-text="searchText"
@editTask="editTask" @editTask="editTask"
@@ -199,9 +198,6 @@ export default {
return (this.group.leader && this.group.leader._id === this.user._id) return (this.group.leader && this.group.leader._id === this.user._id)
|| (this.group.managers && Boolean(this.group.managers[this.user._id])); || (this.group.managers && Boolean(this.group.managers[this.user._id]));
}, },
showOptions () {
return this.canCreateTasks;
},
}, },
watch: { watch: {
// call again the method if the route changes (when this route is already active) // call again the method if the route changes (when this route is already active)

View File

@@ -90,8 +90,8 @@
:key="task.id" :key="task.id"
:task="task" :task="task"
:is-user="isUser" :is-user="isUser"
:show-options="showOptions"
:group="group" :group="group"
:challenge="challenge"
@editTask="editTask" @editTask="editTask"
@moveTo="moveTo" @moveTo="moveTo"
@taskDestroyed="taskDestroyed" @taskDestroyed="taskDestroyed"
@@ -372,10 +372,7 @@ export default {
selectedTags: {}, selectedTags: {},
taskListOverride: {}, taskListOverride: {},
group: {}, group: {},
showOptions: { challenge: {},
type: Boolean,
default: true,
},
}, // @TODO: maybe we should store the group on state? }, // @TODO: maybe we should store the group on state?
data () { data () {
const icons = Object.freeze({ const icons = Object.freeze({

View File

@@ -99,6 +99,7 @@
</div> </div>
<div slot="dropdown-content"> <div slot="dropdown-content">
<div <div
v-if="showEdit"
ref="editTaskItem" ref="editTaskItem"
class="dropdown-item edit-task-item" class="dropdown-item edit-task-item"
> >
@@ -137,7 +138,7 @@
</span> </span>
</div> </div>
<div <div
v-if="canDelete(task)" v-if="showDelete"
class="dropdown-item" class="dropdown-item"
@click="destroy" @click="destroy"
> >
@@ -789,6 +790,7 @@ import moment from 'moment';
import axios from 'axios'; import axios from 'axios';
import Vue from 'vue'; import Vue from 'vue';
import uuid from 'uuid'; import uuid from 'uuid';
import isEmpty from 'lodash/isEmpty';
import { mapState, mapGetters, mapActions } from '@/libs/store'; import { mapState, mapGetters, mapActions } from '@/libs/store';
import scoreTask from '@/../../common/script/ops/scoreTask'; import scoreTask from '@/../../common/script/ops/scoreTask';
import * as Analytics from '@/libs/analytics'; import * as Analytics from '@/libs/analytics';
@@ -825,7 +827,7 @@ export default {
markdown: markdownDirective, markdown: markdownDirective,
}, },
mixins: [notifications], mixins: [notifications],
props: ['task', 'isUser', 'group', 'dueDate', 'showOptions'], // @TODO: maybe we should store the group on state? props: ['task', 'isUser', 'group', 'challenge', 'dueDate'], // @TODO: maybe we should store the group on state?
data () { data () {
return { return {
random: uuid.v4(), // used to avoid conflicts between checkboxes ids random: uuid.v4(), // used to avoid conflicts between checkboxes ids
@@ -859,6 +861,7 @@ export default {
getTagsFor: 'tasks:getTagsFor', getTagsFor: 'tasks:getTagsFor',
getTaskClasses: 'tasks:getTaskClasses', getTaskClasses: 'tasks:getTaskClasses',
canDelete: 'tasks:canDelete', canDelete: 'tasks:canDelete',
canEdit: 'tasks:canEdit',
}), }),
hasChecklist () { hasChecklist () {
return this.task.checklist && this.task.checklist.length > 0; return this.task.checklist && this.task.checklist.length > 0;
@@ -937,6 +940,27 @@ export default {
return this.task.challenge.shortName ? this.task.challenge.shortName.toString() : ''; return this.task.challenge.shortName ? this.task.challenge.shortName.toString() : '';
}, },
isChallangeTask () {
return !isEmpty(this.task.challenge);
},
isGroupTask () {
return this.task.group.taskId || this.task.group.id;
},
taskCategory () {
let taskCategory = 'default';
if (this.isGroupTask) taskCategory = 'group';
else if (this.isChallangeTask) taskCategory = 'challenge';
return taskCategory;
},
showDelete () {
return this.canDelete(this.task, this.taskCategory, this.isUser, this.group, this.challenge);
},
showEdit () {
return this.canEdit(this.task, this.taskCategory, this.isUser, this.group, this.challenge);
},
showOptions () {
return this.showEdit || this.showDelete || this.isUser;
},
}, },
methods: { methods: {
...mapActions({ ...mapActions({
@@ -950,7 +974,7 @@ export default {
this.scoreChecklistItem({ taskId: this.task._id, itemId: item.id }); this.scoreChecklistItem({ taskId: this.task._id, itemId: item.id });
}, },
edit (e, task) { edit (e, task) {
if (this.isRunningYesterdailies) return; if (this.isRunningYesterdailies || !this.showEdit) return;
// Prevent clicking on a link from opening the edit modal // Prevent clicking on a link from opening the edit modal
const target = e.target || e.srcElement; const target = e.target || e.srcElement;

View File

@@ -34,12 +34,75 @@ function getTaskColor (task) {
return 'best'; return 'best';
} }
export function canDelete () { export function canDelete (store) {
return task => { return (task, taskCategory, onUserDashboard, group, challenge) => {
const isUserChallenge = Boolean(task.userId); const user = store.state.user.data;
const activeChallenge = isUserChallenge const userId = user.id || user._id;
&& task.challenge && task.challenge.id && !task.challenge.broken;
return !activeChallenge; const isUserAdmin = user.contributor && !!user.contributor.admin;
const isUserGroupLeader = group && (group.leader
&& group.leader._id === userId);
const isUserGroupManager = group && (group.managers
&& Boolean(group.managers[userId]));
const isUserChallenge = userId === (challenge && challenge.leader.id);
let isUserCanDeleteTask = onUserDashboard;
switch (taskCategory) {
case 'challenge':
if (!onUserDashboard) {
isUserCanDeleteTask = isUserChallenge || isUserAdmin;
} else {
isUserCanDeleteTask = isUserAdmin;
}
break;
case 'group':
if (!onUserDashboard) {
isUserCanDeleteTask = isUserGroupLeader || isUserGroupManager || isUserAdmin;
} else {
isUserCanDeleteTask = isUserAdmin;
}
break;
default:
break;
}
return Boolean(isUserCanDeleteTask);
};
}
export function canEdit (store) {
return (task, taskCategory, onUserDashboard, group, challenge) => {
let isUserCanEditTask = onUserDashboard;
const user = store.state.user.data;
const userId = user.id || user._id;
const isUserAdmin = user.contributor && !!user.contributor.admin;
const isUserGroupLeader = group && (group.leader
&& group.leader._id === userId);
const isUserGroupManager = group && (group.managers
&& Boolean(group.managers[userId]));
const isUserChallenge = userId === (challenge && challenge.leader.id);
switch (taskCategory) {
case 'challenge':
if (!onUserDashboard) {
isUserCanEditTask = isUserChallenge || isUserAdmin;
} else {
isUserCanEditTask = true;
}
break;
case 'group':
if (!onUserDashboard) {
isUserCanEditTask = isUserGroupLeader || isUserGroupManager || isUserAdmin;
} else {
isUserCanEditTask = true;
}
break;
default:
break;
}
return Boolean(isUserCanEditTask);
}; };
} }

View File

@@ -1,19 +1,73 @@
import generateStore from '@/store'; import generateStore from '@/store';
describe('canDelete getter', () => { describe('canDelete getter', () => {
it('cannot delete active challenge task', () => { let store;
const store = generateStore(); let group;
let challenge;
let task;
beforeEach(() => {
store = generateStore();
const task = { userId: 1, challenge: { id: 2 } }; store.state.user.data = {
expect(store.getters['tasks:canDelete'](task)).to.equal(false); id: 10,
contributor: {
admin: false,
},
};
group = {
leader: {
_id: 123,
},
managers: {
123984: {},
},
};
challenge = {
leader: {
id: 123,
},
};
task = { userId: 1, challenge: { id: 2 } };
});
it('cannot Delete challenge or group task in own dashboard', () => {
expect(store.getters['tasks:canDelete'](task, 'challenge', true, null, challenge)).to.equal(false);
expect(store.getters['tasks:canDelete'](task, 'group', true, group, null)).to.equal(false);
}); });
it('can delete broken challenge task', () => { it('can Delete any challenge task as admin', () => {
const store = generateStore(); store.state.user.data.contributor.admin = true;
expect(store.getters['tasks:canDelete'](task, 'challenge', true, null, challenge)).to.equal(true);
});
const task = { userId: 1, challenge: { id: 2, broken: true } }; it('can Delete own challenge task if leader', () => {
expect(store.getters['tasks:canDelete'](task)).to.equal(true); store.state.user.data.id = 123;
expect(store.getters['tasks:canDelete'](task, 'challenge', false, null, challenge)).to.equal(true);
});
it('cannot Delete challenge task if non leader on challenge page', () => {
expect(store.getters['tasks:canDelete'](task, 'challenge', false, null, challenge)).to.equal(false);
});
it('can Delete group task as leader on group page', () => {
store.state.user.data.id = 123;
expect(store.getters['tasks:canDelete'](task, 'group', false, group)).to.equal(true);
});
it('can Delete group task if manager on group page', () => {
store.state.user.data.id = 123984;
expect(store.getters['tasks:canDelete'](task, 'group', false, group)).to.equal(true);
});
it('cannot Delete group task if not a leader on group page', () => {
expect(store.getters['tasks:canDelete'](task, 'group', false, group)).to.equal(false);
}); });
}); });

View File

@@ -0,0 +1,75 @@
import generateStore from '@/store';
describe('canEdit getter', () => {
let store;
let group;
let challenge;
let task;
beforeEach(() => {
store = generateStore();
store.state.user.data = {
id: 10,
contributor: {
admin: false,
},
};
group = {
leader: {
_id: 123,
},
managers: {
123984: {},
},
};
challenge = {
leader: {
id: 123,
},
};
task = { userId: 1, challenge: { id: 2 } };
});
it('can Edit task in own dashboard', () => {
expect(store.getters['tasks:canEdit'](task, 'challenge', true, null, challenge)).to.equal(true);
expect(store.getters['tasks:canEdit'](task, 'group', true, group, null)).to.equal(true);
});
it('can Edit any challenge task if admin', () => {
store.state.user.data.contributor.admin = true;
expect(store.getters['tasks:canEdit'](task, 'challenge', true, null, challenge)).to.equal(true);
expect(store.getters['tasks:canEdit'](task, 'challenge', false, null, challenge)).to.equal(true);
});
it('can Edit own challenge task if leader', () => {
store.state.user.data.id = 123;
expect(store.getters['tasks:canEdit'](task, 'challenge', true, null, challenge)).to.equal(true);
expect(store.getters['tasks:canEdit'](task, 'challenge', false, null, challenge)).to.equal(true);
});
it('cannot Edit challenge task if not leader on challenge page', () => {
expect(store.getters['tasks:canEdit'](task, 'challenge', false, null, challenge)).to.equal(false);
});
it('can Edit group task as leader on group page', () => {
store.state.user.data.id = 123;
expect(store.getters['tasks:canEdit'](task, 'group', false, group)).to.equal(true);
});
it('can Edit group task if manager on group page', () => {
store.state.user.data.id = 123984;
expect(store.getters['tasks:canEdit'](task, 'group', false, group)).to.equal(true);
});
it('cannot Edit group task if not leader on group page', () => {
expect(store.getters['tasks:canEdit'](task, 'group', false, group)).to.equal(false);
});
});