Merge branch '27114-add-undo-to-todos-in-the-done-tab' into 'master'

Add 'Undo' to Todos in the Done tab

Closes #27114

See merge request !8782
This commit is contained in:
Sean McGivern 2017-03-13 15:02:40 +00:00
commit cc64eda987
4 changed files with 174 additions and 132 deletions

View File

@ -1,146 +1,146 @@
/* eslint-disable class-methods-use-this, no-new, func-names, no-unneeded-ternary, object-shorthand, quote-props, no-param-reassign, max-len */ /* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */
/* global UsersSelect */ /* global UsersSelect */
((global) => { class Todos {
class Todos { constructor() {
constructor() { this.initFilters();
this.initFilters(); this.bindEvents();
this.bindEvents();
this.cleanupWrapper = this.cleanup.bind(this); this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('beforeunload', this.cleanupWrapper); document.addEventListener('beforeunload', this.cleanupWrapper);
} }
cleanup() { cleanup() {
this.unbindEvents(); this.unbindEvents();
document.removeEventListener('beforeunload', this.cleanupWrapper); document.removeEventListener('beforeunload', this.cleanupWrapper);
} }
unbindEvents() { unbindEvents() {
$('.js-done-todo, .js-undo-todo').off('click', this.updateStateClickedWrapper); $('.js-done-todo, .js-undo-todo, .js-add-todo').off('click', this.updateRowStateClickedWrapper);
$('.js-todos-mark-all').off('click', this.allDoneClickedWrapper); $('.js-todos-mark-all').off('click', this.allDoneClickedWrapper);
$('.todo').off('click', this.goToTodoUrl); $('.todo').off('click', this.goToTodoUrl);
} }
bindEvents() { bindEvents() {
this.updateStateClickedWrapper = this.updateStateClicked.bind(this); this.updateRowStateClickedWrapper = this.updateRowStateClicked.bind(this);
this.allDoneClickedWrapper = this.allDoneClicked.bind(this); this.allDoneClickedWrapper = this.allDoneClicked.bind(this);
$('.js-done-todo, .js-undo-todo').on('click', this.updateStateClickedWrapper); $('.js-done-todo, .js-undo-todo, .js-add-todo').on('click', this.updateRowStateClickedWrapper);
$('.js-todos-mark-all').on('click', this.allDoneClickedWrapper); $('.js-todos-mark-all').on('click', this.allDoneClickedWrapper);
$('.todo').on('click', this.goToTodoUrl); $('.todo').on('click', this.goToTodoUrl);
} }
initFilters() { initFilters() {
new UsersSelect(); this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); this.initFilterDropdown($('.js-type-search'), 'type');
this.initFilterDropdown($('.js-type-search'), 'type'); this.initFilterDropdown($('.js-action-search'), 'action_id');
this.initFilterDropdown($('.js-action-search'), 'action_id');
$('form.filter-form').on('submit', function (event) { $('form.filter-form').on('submit', function applyFilters(event) {
event.preventDefault(); event.preventDefault();
gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`); gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`);
}); });
} return new UsersSelect();
}
initFilterDropdown($dropdown, fieldName, searchFields) { initFilterDropdown($dropdown, fieldName, searchFields) {
$dropdown.glDropdown({ $dropdown.glDropdown({
fieldName, fieldName,
selectable: true, selectable: true,
filterable: searchFields ? true : false, filterable: searchFields ? true : false,
search: { fields: searchFields }, search: { fields: searchFields },
data: $dropdown.data('data'), data: $dropdown.data('data'),
clicked: function () { clicked: () => $dropdown.closest('form.filter-form').submit(),
return $dropdown.closest('form.filter-form').submit(); });
}, }
});
}
updateStateClicked(e) { updateRowStateClicked(e) {
e.preventDefault(); e.preventDefault();
const target = e.target;
target.setAttribute('disabled', '');
target.classList.add('disabled');
$.ajax({
type: 'POST',
url: target.getAttribute('href'),
dataType: 'json',
data: {
'_method': target.getAttribute('data-method'),
},
success: (data) => {
this.updateState(target);
this.updateBadges(data);
},
});
}
allDoneClicked(e) { const target = e.target;
e.preventDefault(); target.setAttribute('disabled', '');
const $target = $(e.currentTarget); target.classList.add('disabled');
$target.disable(); $.ajax({
$.ajax({ type: 'POST',
type: 'POST', url: target.getAttribute('href'),
url: $target.attr('href'), dataType: 'json',
dataType: 'json', data: {
data: { '_method': target.getAttribute('data-method'),
'_method': 'delete', },
}, success: (data) => {
success: (data) => { this.updateRowState(target);
$target.remove(); return this.updateBadges(data);
$('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>'); },
this.updateBadges(data); });
}, }
});
}
updateState(target) { allDoneClicked(e) {
const row = target.closest('li'); e.preventDefault();
const restoreBtn = row.querySelector('.js-undo-todo'); const $target = $(e.currentTarget);
const doneBtn = row.querySelector('.js-done-todo'); $target.disable();
$.ajax({
type: 'POST',
url: $target.attr('href'),
dataType: 'json',
data: {
'_method': 'delete',
},
success: (data) => {
$target.remove();
$('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
this.updateBadges(data);
},
});
}
target.removeAttribute('disabled'); updateRowState(target) {
target.classList.remove('disabled'); const row = target.closest('li');
target.classList.add('hidden'); const restoreBtn = row.querySelector('.js-undo-todo');
const doneBtn = row.querySelector('.js-done-todo');
if (target === doneBtn) { target.classList.add('hidden');
row.classList.add('done-reversible'); target.removeAttribute('disabled');
restoreBtn.classList.remove('hidden'); target.classList.remove('disabled');
} else {
row.classList.remove('done-reversible');
doneBtn.classList.remove('hidden');
}
}
updateBadges(data) { if (target === doneBtn) {
$(document).trigger('todo:toggle', data.count); row.classList.add('done-reversible');
$('.todos-pending .badge').text(data.count); restoreBtn.classList.remove('hidden');
$('.todos-done .badge').text(data.done_count); } else if (target === restoreBtn) {
} row.classList.remove('done-reversible');
doneBtn.classList.remove('hidden');
goToTodoUrl(e) { } else {
const todoLink = this.dataset.url; row.parentNode.removeChild(row);
if (!todoLink) {
return;
}
if (gl.utils.isMetaClick(e)) {
const windowTarget = '_blank';
const selected = e.target;
e.preventDefault();
if (selected.tagName === 'IMG') {
const avatarUrl = selected.parentElement.getAttribute('href');
window.open(avatarUrl, windowTarget);
} else {
window.open(todoLink, windowTarget);
}
} else {
gl.utils.visitUrl(todoLink);
}
} }
} }
global.Todos = Todos; updateBadges(data) {
})(window.gl || (window.gl = {})); $(document).trigger('todo:toggle', data.count);
document.querySelector('.todos-pending .badge').innerHTML = data.count;
document.querySelector('.todos-done .badge').innerHTML = data.done_count;
}
goToTodoUrl(e) {
const todoLink = this.dataset.url;
if (!todoLink) {
return;
}
if (gl.utils.isMetaClick(e)) {
const windowTarget = '_blank';
const selected = e.target;
e.preventDefault();
if (selected.tagName === 'IMG') {
const avatarUrl = selected.parentElement.getAttribute('href');
window.open(avatarUrl, windowTarget);
} else {
window.open(todoLink, windowTarget);
}
} else {
gl.utils.visitUrl(todoLink);
}
}
}
window.gl = window.gl || {};
gl.Todos = Todos;

View File

@ -42,3 +42,8 @@
= link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-undo-todo hidden' do = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-undo-todo hidden' do
Undo Undo
= icon('spinner spin') = icon('spinner spin')
- else
.todo-actions
= link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-add-todo' do
Add todo
= icon('spinner spin')

View File

@ -0,0 +1,4 @@
---
title: Add Undo to Todos in the Done tab
merge_request: 8782
author: Jacopo Beschi @jacopo-beschi

View File

@ -38,7 +38,9 @@ describe 'Dashboard Todos', feature: true do
shared_examples 'deleting the todo' do shared_examples 'deleting the todo' do
before do before do
first('.js-done-todo').click within first('.todo') do
click_link 'Done'
end
end end
it 'is marked as done-reversible in the list' do it 'is marked as done-reversible in the list' do
@ -62,9 +64,11 @@ describe 'Dashboard Todos', feature: true do
shared_examples 'deleting and restoring the todo' do shared_examples 'deleting and restoring the todo' do
before do before do
first('.js-done-todo').click within first('.todo') do
wait_for_ajax click_link 'Done'
first('.js-undo-todo').click wait_for_ajax
click_link 'Undo'
end
end end
it 'is marked back as pending in the list' do it 'is marked back as pending in the list' do
@ -97,6 +101,35 @@ describe 'Dashboard Todos', feature: true do
end end
end end
context 'User has done todos', js: true do
before do
create(:todo, :mentioned, :done, user: user, project: project, target: issue, author: author)
login_as(user)
visit dashboard_todos_path(state: :done)
end
it 'has the done todo present' do
expect(page).to have_selector('.todos-list .todo.todo-done', count: 1)
end
describe 'restoring the todo' do
before do
within first('.todo') do
click_link 'Add todo'
end
end
it 'is removed from the list' do
expect(page).not_to have_selector('.todos-list .todo.todo-done')
end
it 'updates todo count' do
expect(page).to have_content 'To do 1'
expect(page).to have_content 'Done 0'
end
end
end
context 'User has Todos with labels spanning multiple projects' do context 'User has Todos with labels spanning multiple projects' do
before do before do
label1 = create(:label, project: project) label1 = create(:label, project: project)
@ -143,7 +176,7 @@ describe 'Dashboard Todos', feature: true do
describe 'mark all as done', js: true do describe 'mark all as done', js: true do
before do before do
visit dashboard_todos_path visit dashboard_todos_path
click_link('Mark all as done') click_link 'Mark all as done'
end end
it 'shows "All done" message!' do it 'shows "All done" message!' do