Commit 7ef44a97 authored by Pavel Shutsin's avatar Pavel Shutsin Committed by Robert Speicher

Add resolution mechanism for todos

It will be used for internal research
parent c2a32118
...@@ -17,7 +17,9 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -17,7 +17,9 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end end
def destroy def destroy
TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) todo = current_user.todos.find(params[:id])
TodoService.new.resolve_todo(todo, current_user, resolved_by_action: :mark_done)
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -31,7 +33,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -31,7 +33,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end end
def destroy_all def destroy_all
updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user) updated_ids = TodoService.new.resolve_todos(@todos, current_user, resolved_by_action: :mark_all_done)
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_todos_path, status: :found, notice: _('Everything on your to-do list is marked as done.') } format.html { redirect_to dashboard_todos_path, status: :found, notice: _('Everything on your to-do list is marked as done.') }
...@@ -41,13 +43,13 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -41,13 +43,13 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end end
def restore def restore
TodoService.new.mark_todos_as_pending_by_ids(params[:id], current_user) TodoService.new.restore_todo(current_user.todos.find(params[:id]), current_user)
render json: todos_counts render json: todos_counts
end end
def bulk_restore def bulk_restore
TodoService.new.mark_todos_as_pending_by_ids(params[:ids], current_user) TodoService.new.restore_todos(current_user.todos.for_ids(params[:ids]), current_user)
render json: todos_counts render json: todos_counts
end end
......
...@@ -28,7 +28,9 @@ module Mutations ...@@ -28,7 +28,9 @@ module Mutations
def mark_all_todos_done def mark_all_todos_done
return [] unless current_user return [] unless current_user
TodoService.new.mark_all_todos_as_done_by_user(current_user) todos = TodosFinder.new(current_user).execute
TodoService.new.resolve_todos(todos, current_user, resolved_by_action: :api_all_done)
end end
end end
end end
......
...@@ -30,7 +30,7 @@ module Mutations ...@@ -30,7 +30,7 @@ module Mutations
private private
def mark_done(todo) def mark_done(todo)
TodoService.new.mark_todo_as_done(todo, current_user) TodoService.new.resolve_todo(todo, current_user, resolved_by_action: :api_done)
end end
end end
end end
......
...@@ -18,7 +18,7 @@ module Mutations ...@@ -18,7 +18,7 @@ module Mutations
def resolve(id:) def resolve(id:)
todo = authorized_find!(id: id) todo = authorized_find!(id: id)
restore(todo.id) if todo.done? restore(todo)
{ {
todo: todo.reset, todo: todo.reset,
...@@ -28,8 +28,8 @@ module Mutations ...@@ -28,8 +28,8 @@ module Mutations
private private
def restore(id) def restore(todo)
TodoService.new.mark_todos_as_pending_by_ids([id], current_user) TodoService.new.restore_todo(todo, current_user)
end end
end end
end end
......
...@@ -68,7 +68,7 @@ module Mutations ...@@ -68,7 +68,7 @@ module Mutations
end end
def restore(todos) def restore(todos)
TodoService.new.mark_todos_as_pending(todos, current_user) TodoService.new.restore_todos(todos, current_user)
end end
end end
end end
......
...@@ -66,6 +66,8 @@ class Todo < ApplicationRecord ...@@ -66,6 +66,8 @@ class Todo < ApplicationRecord
scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) } scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) }
scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) } scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
enum resolved_by_action: { system_done: 0, api_all_done: 1, api_done: 2, mark_all_done: 3, mark_done: 4 }, _prefix: :resolved_by
state_machine :state, initial: :pending do state_machine :state, initial: :pending do
event :done do event :done do
transition [:pending] => :done transition [:pending] => :done
...@@ -100,17 +102,17 @@ class Todo < ApplicationRecord ...@@ -100,17 +102,17 @@ class Todo < ApplicationRecord
state.nil? ? exists?(target: target) : exists?(target: target, state: state) state.nil? ? exists?(target: target) : exists?(target: target, state: state)
end end
# Updates the state of a relation of todos to the new state. # Updates attributes of a relation of todos to the new state.
# #
# new_state - The new state of the todos. # new_attributes - The new attributes of the todos.
# #
# Returns an `Array` containing the IDs of the updated todos. # Returns an `Array` containing the IDs of the updated todos.
def update_state(new_state) def batch_update(**new_attributes)
# Only update those that are not really on that state # Only update those that have different state
base = where.not(state: new_state).except(:order) base = where.not(state: new_attributes[:state]).except(:order)
ids = base.pluck(:id) ids = base.pluck(:id)
base.update_all(state: new_state, updated_at: Time.current) base.update_all(new_attributes.merge(updated_at: Time.current))
ids ids
end end
......
...@@ -1630,10 +1630,6 @@ class User < ApplicationRecord ...@@ -1630,10 +1630,6 @@ class User < ApplicationRecord
super.presence || build_user_detail super.presence || build_user_detail
end end
def todos_limited_to(ids)
todos.where(id: ids)
end
def pending_todo_for(target) def pending_todo_for(target)
todos.find_by(target: target, state: :pending) todos.find_by(target: target, state: :pending)
end end
......
...@@ -350,7 +350,7 @@ class IssuableBaseService < BaseService ...@@ -350,7 +350,7 @@ class IssuableBaseService < BaseService
todo_service.mark_todo(issuable, current_user) todo_service.mark_todo(issuable, current_user)
when 'done' when 'done'
todo = TodosFinder.new(current_user).find_by(target: issuable) todo = TodosFinder.new(current_user).find_by(target: issuable)
todo_service.mark_todos_as_done_by_ids(todo, current_user) if todo todo_service.resolve_todo(todo, current_user) if todo
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -32,7 +32,7 @@ module Issues ...@@ -32,7 +32,7 @@ module Issues
old_assignees = old_associations.fetch(:assignees, []) old_assignees = old_associations.fetch(:assignees, [])
if has_changes?(issue, old_labels: old_labels, old_assignees: old_assignees) if has_changes?(issue, old_labels: old_labels, old_assignees: old_assignees)
todo_service.mark_pending_todos_as_done(issue, current_user) todo_service.resolve_todos_for_target(issue, current_user)
end end
if issue.previous_changes.include?('title') || if issue.previous_changes.include?('title') ||
...@@ -68,7 +68,7 @@ module Issues ...@@ -68,7 +68,7 @@ module Issues
end end
def handle_task_changes(issuable) def handle_task_changes(issuable)
todo_service.mark_pending_todos_as_done(issuable, current_user) todo_service.resolve_todos_for_target(issuable, current_user)
todo_service.update_issue(issuable, current_user) todo_service.update_issue(issuable, current_user)
end end
......
...@@ -27,7 +27,7 @@ module MergeRequests ...@@ -27,7 +27,7 @@ module MergeRequests
old_assignees = old_associations.fetch(:assignees, []) old_assignees = old_associations.fetch(:assignees, [])
if has_changes?(merge_request, old_labels: old_labels, old_assignees: old_assignees) if has_changes?(merge_request, old_labels: old_labels, old_assignees: old_assignees)
todo_service.mark_pending_todos_as_done(merge_request, current_user) todo_service.resolve_todos_for_target(merge_request, current_user)
end end
if merge_request.previous_changes.include?('title') || if merge_request.previous_changes.include?('title') ||
...@@ -73,7 +73,7 @@ module MergeRequests ...@@ -73,7 +73,7 @@ module MergeRequests
end end
def handle_task_changes(merge_request) def handle_task_changes(merge_request)
todo_service.mark_pending_todos_as_done(merge_request, current_user) todo_service.resolve_todos_for_target(merge_request, current_user)
todo_service.update_merge_request(merge_request, current_user) todo_service.update_merge_request(merge_request, current_user)
end end
......
...@@ -30,7 +30,7 @@ class TodoService ...@@ -30,7 +30,7 @@ class TodoService
# * mark all pending todos related to the target for the current user as done # * mark all pending todos related to the target for the current user as done
# #
def close_issue(issue, current_user) def close_issue(issue, current_user)
mark_pending_todos_as_done(issue, current_user) resolve_todos_for_target(issue, current_user)
end end
# When we destroy a todo target we should: # When we destroy a todo target we should:
...@@ -79,7 +79,7 @@ class TodoService ...@@ -79,7 +79,7 @@ class TodoService
# * mark all pending todos related to the target for the current user as done # * mark all pending todos related to the target for the current user as done
# #
def close_merge_request(merge_request, current_user) def close_merge_request(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user) resolve_todos_for_target(merge_request, current_user)
end end
# When merge a merge request we should: # When merge a merge request we should:
...@@ -87,7 +87,7 @@ class TodoService ...@@ -87,7 +87,7 @@ class TodoService
# * mark all pending todos related to the target for the current user as done # * mark all pending todos related to the target for the current user as done
# #
def merge_merge_request(merge_request, current_user) def merge_merge_request(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user) resolve_todos_for_target(merge_request, current_user)
end end
# When a build fails on the HEAD of a merge request we should: # When a build fails on the HEAD of a merge request we should:
...@@ -105,7 +105,7 @@ class TodoService ...@@ -105,7 +105,7 @@ class TodoService
# * mark all pending todos related to the merge request for that user as done # * mark all pending todos related to the merge request for that user as done
# #
def merge_request_push(merge_request, current_user) def merge_request_push(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user) resolve_todos_for_target(merge_request, current_user)
end end
# When a build is retried to a merge request we should: # When a build is retried to a merge request we should:
...@@ -114,7 +114,7 @@ class TodoService ...@@ -114,7 +114,7 @@ class TodoService
# #
def merge_request_build_retried(merge_request) def merge_request_build_retried(merge_request)
merge_request.merge_participants.each do |user| merge_request.merge_participants.each do |user|
mark_pending_todos_as_done(merge_request, user) resolve_todos_for_target(merge_request, user)
end end
end end
...@@ -151,76 +151,60 @@ class TodoService ...@@ -151,76 +151,60 @@ class TodoService
# * mark all pending todos related to the awardable for the current user as done # * mark all pending todos related to the awardable for the current user as done
# #
def new_award_emoji(awardable, current_user) def new_award_emoji(awardable, current_user)
mark_pending_todos_as_done(awardable, current_user) resolve_todos_for_target(awardable, current_user)
end end
# When marking pending todos as done we should: # When user marks an issue as todo
# def mark_todo(issuable, current_user)
# * mark all pending todos related to the target for the current user as done attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
# create_todos(current_user, attributes)
def mark_pending_todos_as_done(target, user)
attributes = attributes_for_target(target)
pending_todos(user, attributes).update_all(state: :done)
user.update_todos_count_cache
end end
# When user marks some todos as done def todo_exist?(issuable, current_user)
def mark_todos_as_done(todos, current_user) TodosFinder.new(current_user).any_for_target?(issuable, :pending)
update_todos_state(todos, current_user, :done)
end end
def mark_todos_as_done_by_ids(ids, current_user) # Resolves all todos related to target
todos = todos_by_ids(ids, current_user) def resolve_todos_for_target(target, current_user)
mark_todos_as_done(todos, current_user) attributes = attributes_for_target(target)
end
def mark_all_todos_as_done_by_user(current_user) resolve_todos(pending_todos(current_user, attributes), current_user)
todos = TodosFinder.new(current_user).execute
mark_todos_as_done(todos, current_user)
end end
def mark_todo_as_done(todo, current_user) def resolve_todos(todos, current_user, resolution: :done, resolved_by_action: :system_done)
return if todo.done? todos_ids = todos.batch_update(state: resolution, resolved_by_action: resolved_by_action)
todo.update(state: :done)
current_user.update_todos_count_cache current_user.update_todos_count_cache
end
# When user marks some todos as pending todos_ids
def mark_todos_as_pending(todos, current_user)
update_todos_state(todos, current_user, :pending)
end end
def mark_todos_as_pending_by_ids(ids, current_user) def resolve_todo(todo, current_user, resolution: :done, resolved_by_action: :system_done)
todos = todos_by_ids(ids, current_user) return if todo.done?
mark_todos_as_pending(todos, current_user)
end
# When user marks an issue as todo todo.update(state: resolution, resolved_by_action: resolved_by_action)
def mark_todo(issuable, current_user)
attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
create_todos(current_user, attributes)
end
def todo_exist?(issuable, current_user) current_user.update_todos_count_cache
TodosFinder.new(current_user).any_for_target?(issuable, :pending)
end end
private def restore_todos(todos, current_user)
todos_ids = todos.batch_update(state: :pending)
current_user.update_todos_count_cache
def todos_by_ids(ids, current_user) todos_ids
current_user.todos_limited_to(Array(ids))
end end
def update_todos_state(todos, current_user, state) def restore_todo(todo, current_user)
todos_ids = todos.update_state(state) return if todo.pending?
current_user.update_todos_count_cache todo.update(state: :pending)
todos_ids current_user.update_todos_count_cache
end end
private
def create_todos(users, attributes) def create_todos(users, attributes)
Array(users).map do |user| Array(users).map do |user|
next if pending_todos(user, attributes).exists? next if pending_todos(user, attributes).exists?
...@@ -254,7 +238,7 @@ class TodoService ...@@ -254,7 +238,7 @@ class TodoService
project = note.project project = note.project
target = note.noteable target = note.noteable
mark_pending_todos_as_done(target, author) resolve_todos_for_target(target, author)
create_mention_todos(project, target, author, note, skip_users) create_mention_todos(project, target, author, note, skip_users)
end end
......
---
title: Store Todo resolution method
merge_request: 32753
author:
type: added
# frozen_string_literal: true
class AddTodoResolvedByAction < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_column :todos, :resolved_by_action, :integer, limit: 2
end
end
def down
with_lock_retries do
remove_column :todos, :resolved_by_action
end
end
end
...@@ -6522,7 +6522,8 @@ CREATE TABLE public.todos ( ...@@ -6522,7 +6522,8 @@ CREATE TABLE public.todos (
updated_at timestamp without time zone, updated_at timestamp without time zone,
note_id integer, note_id integer,
commit_id character varying, commit_id character varying,
group_id integer group_id integer,
resolved_by_action smallint
); );
CREATE SEQUENCE public.todos_id_seq CREATE SEQUENCE public.todos_id_seq
...@@ -13959,6 +13960,7 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -13959,6 +13960,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200519115908 20200519115908
20200519171058 20200519171058
20200519194042 20200519194042
20200520103514
20200525114553 20200525114553
20200525121014 20200525121014
20200526120714 20200526120714
......
...@@ -31,14 +31,14 @@ module Epics ...@@ -31,14 +31,14 @@ module Epics
old_labels = old_associations.fetch(:labels, []) old_labels = old_associations.fetch(:labels, [])
if has_changes?(epic, old_labels: old_labels) if has_changes?(epic, old_labels: old_labels)
todo_service.mark_pending_todos_as_done(epic, current_user) todo_service.resolve_todos_for_target(epic, current_user)
end end
todo_service.update_epic(epic, current_user, old_mentioned_users) todo_service.update_epic(epic, current_user, old_mentioned_users)
end end
def handle_task_changes(epic) def handle_task_changes(epic)
todo_service.mark_pending_todos_as_done(epic, current_user) todo_service.resolve_todos_for_target(epic, current_user)
todo_service.update_epic(epic, current_user) todo_service.update_epic(epic, current_user)
end end
......
...@@ -44,7 +44,7 @@ module MergeRequests ...@@ -44,7 +44,7 @@ module MergeRequests
end end
def mark_pending_todos_as_done(merge_request) def mark_pending_todos_as_done(merge_request)
todo_service.mark_pending_todos_as_done(merge_request, current_user) todo_service.resolve_todos_for_target(merge_request, current_user)
end end
def calculate_approvals_metrics(merge_request) def calculate_approvals_metrics(merge_request)
......
...@@ -91,16 +91,18 @@ module API ...@@ -91,16 +91,18 @@ module API
requires :id, type: Integer, desc: 'The ID of the todo being marked as done' requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
end end
post ':id/mark_as_done' do post ':id/mark_as_done' do
TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
todo = current_user.todos.find(params[:id]) todo = current_user.todos.find(params[:id])
TodoService.new.resolve_todo(todo, current_user, resolved_by_action: :api_done)
present todo, with: Entities::Todo, current_user: current_user present todo, with: Entities::Todo, current_user: current_user
end end
desc 'Mark all todos as done' desc 'Mark all todos as done'
post '/mark_as_done' do post '/mark_as_done' do
todos = find_todos todos = find_todos
TodoService.new.mark_todos_as_done(todos, current_user)
TodoService.new.resolve_todos(todos, current_user, resolved_by_action: :api_all_done)
no_content! no_content!
end end
......
...@@ -114,7 +114,7 @@ describe 'Dashboard Todos' do ...@@ -114,7 +114,7 @@ describe 'Dashboard Todos' do
context 'todo is stale on the page' do context 'todo is stale on the page' do
before do before do
todos = TodosFinder.new(user, state: :pending).execute todos = TodosFinder.new(user, state: :pending).execute
TodoService.new.mark_todos_as_done(todos, user) TodoService.new.resolve_todos(todos, user)
end end
it_behaves_like 'deleting the todo' it_behaves_like 'deleting the todo'
......
...@@ -393,10 +393,10 @@ describe Todo do ...@@ -393,10 +393,10 @@ describe Todo do
end end
end end
describe '.update_state' do describe '.batch_update' do
it 'updates the state of todos' do it 'updates the state of todos' do
todo = create(:todo, :pending) todo = create(:todo, :pending)
ids = described_class.update_state(:done) ids = described_class.batch_update(state: :done)
todo.reload todo.reload
...@@ -407,7 +407,7 @@ describe Todo do ...@@ -407,7 +407,7 @@ describe Todo do
it 'does not update todos that already have the given state' do it 'does not update todos that already have the given state' do
create(:todo, :pending) create(:todo, :pending)
expect(described_class.update_state(:pending)).to be_empty expect(described_class.batch_update(state: :pending)).to be_empty
end end
it 'updates updated_at' do it 'updates updated_at' do
...@@ -416,7 +416,7 @@ describe Todo do ...@@ -416,7 +416,7 @@ describe Todo do
Timecop.freeze(1.day.from_now) do Timecop.freeze(1.day.from_now) do
expected_update_date = Time.current.utc expected_update_date = Time.current.utc
ids = described_class.update_state(:done) ids = described_class.batch_update(state: :done)
expect(Todo.where(id: ids).map(&:updated_at)).to all(be_like_time(expected_update_date)) expect(Todo.where(id: ids).map(&:updated_at)).to all(be_like_time(expected_update_date))
end end
......
...@@ -274,12 +274,12 @@ describe TodoService do ...@@ -274,12 +274,12 @@ describe TodoService do
end end
end end
describe '#mark_pending_todos_as_done' do describe '#resolve_todos_for_target' do
it 'marks related pending todos to the target for the user as done' do it 'marks related pending todos to the target for the user as done' do
first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
service.mark_pending_todos_as_done(issue, john_doe) service.resolve_todos_for_target(issue, john_doe)
expect(first_todo.reload).to be_done expect(first_todo.reload).to be_done
expect(second_todo.reload).to be_done expect(second_todo.reload).to be_done
...@@ -293,7 +293,7 @@ describe TodoService do ...@@ -293,7 +293,7 @@ describe TodoService do
expect(john_doe.todos_pending_count).to eq(1) expect(john_doe.todos_pending_count).to eq(1)
expect(john_doe).to receive(:update_todos_count_cache).and_call_original expect(john_doe).to receive(:update_todos_count_cache).and_call_original
service.mark_pending_todos_as_done(issue, john_doe) service.resolve_todos_for_target(issue, john_doe)
expect(john_doe.todos_done_count).to eq(1) expect(john_doe.todos_done_count).to eq(1)
expect(john_doe.todos_pending_count).to eq(0) expect(john_doe.todos_pending_count).to eq(0)
...@@ -301,59 +301,6 @@ describe TodoService do ...@@ -301,59 +301,6 @@ describe TodoService do
end end
end end
shared_examples 'updating todos state' do |meth, state, new_state|
let!(:first_todo) { create(:todo, state, user: john_doe, project: project, target: issue, author: author) }
let!(:second_todo) { create(:todo, state, user: john_doe, project: project, target: issue, author: author) }
it 'updates related todos for the user with the new_state' do
service.send(meth, collection, john_doe)
expect(first_todo.reload.state?(new_state)).to be true
expect(second_todo.reload.state?(new_state)).to be true
end
it 'returns the updated ids' do
expect(service.send(meth, collection, john_doe)).to match_array([first_todo.id, second_todo.id])
end
describe 'cached counts' do
it 'updates when todos change' do
expect(john_doe.todos.where(state: new_state).count).to eq(0)
expect(john_doe.todos.where(state: state).count).to eq(2)
expect(john_doe).to receive(:update_todos_count_cache).and_call_original
service.send(meth, collection, john_doe)
expect(john_doe.todos.where(state: new_state).count).to eq(2)
expect(john_doe.todos.where(state: state).count).to eq(0)
end
end
end
describe '#mark_todos_as_done' do
it_behaves_like 'updating todos state', :mark_todos_as_done, :pending, :done do
let(:collection) { Todo.all }
end
end
describe '#mark_todos_as_done_by_ids' do
it_behaves_like 'updating todos state', :mark_todos_as_done_by_ids, :pending, :done do
let(:collection) { [first_todo, second_todo].map(&:id) }
end
end
describe '#mark_todos_as_pending' do
it_behaves_like 'updating todos state', :mark_todos_as_pending, :done, :pending do
let(:collection) { Todo.all }
end
end
describe '#mark_todos_as_pending_by_ids' do
it_behaves_like 'updating todos state', :mark_todos_as_pending_by_ids, :done, :pending do
let(:collection) { [first_todo, second_todo].map(&:id) }
end
end
describe '#new_note' do describe '#new_note' do
let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
...@@ -1000,123 +947,113 @@ describe TodoService do ...@@ -1000,123 +947,113 @@ describe TodoService do
expect(john_doe.todos_pending_count).to eq(1) expect(john_doe.todos_pending_count).to eq(1)
end end
describe '#mark_todos_as_done' do shared_examples 'updating todos state' do |state, new_state, new_resolved_by = nil|
let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } let!(:first_todo) { create(:todo, state, user: john_doe) }
let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } let!(:second_todo) { create(:todo, state, user: john_doe) }
let(:collection) { Todo.all }
it 'marks a relation of todos as done' do it 'updates related todos for the user with the new_state' do
create(:todo, :mentioned, user: john_doe, target: issue, project: project) method_call
todos = TodosFinder.new(john_doe, {}).execute expect(collection.all? { |todo| todo.reload.state?(new_state)}).to be_truthy
expect { described_class.new.mark_todos_as_done(todos, john_doe) }
.to change { john_doe.todos.done.count }.from(0).to(1)
end end
it 'marks an array of todos as done' do if new_resolved_by
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) it 'updates resolution mechanism' do
method_call
todos = TodosFinder.new(john_doe, {}).execute expect(collection.all? { |todo| todo.reload.resolved_by_action == new_resolved_by }).to be_truthy
expect { described_class.new.mark_todos_as_done(todos, john_doe) }
.to change { todo.reload.state }.from('pending').to('done')
end end
it 'returns the ids of updated todos' do # Needed on API
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todos = TodosFinder.new(john_doe, {}).execute
expect(described_class.new.mark_todos_as_done(todos, john_doe)).to eq([todo.id])
end end
context 'when some of the todos are done already' do it 'returns the updated ids' do
let!(:first_todo) { create(:todo, :mentioned, user: john_doe, target: issue, project: project) } expect(method_call).to match_array([first_todo.id, second_todo.id])
let!(:second_todo) { create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) }
it 'returns the ids of those still pending' do
described_class.new.mark_pending_todos_as_done(issue, john_doe)
expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([second_todo.id])
end end
it 'returns an empty array if all are done' do describe 'cached counts' do
described_class.new.mark_pending_todos_as_done(issue, john_doe) it 'updates when todos change' do
described_class.new.mark_pending_todos_as_done(another_issue, john_doe) expect(john_doe.todos.where(state: new_state).count).to eq(0)
expect(john_doe.todos.where(state: state).count).to eq(2)
expect(john_doe).to receive(:update_todos_count_cache).and_call_original
expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([]) method_call
expect(john_doe.todos.where(state: new_state).count).to eq(2)
expect(john_doe.todos.where(state: state).count).to eq(0)
end end
end end
end end
describe '#mark_todo_as_done' do describe '#resolve_todos' do
it 'marks a todo done' do it_behaves_like 'updating todos state', :pending, :done, 'mark_done' do
todo1 = create(:todo, :pending, user: john_doe) subject(:method_call) do
service.resolve_todos(collection, john_doe, resolution: :done, resolved_by_action: :mark_done)
described_class.new.mark_todo_as_done(todo1, john_doe) end
expect(todo1.reload.state).to eq('done')
end end
context 'when todo is already in state done' do
let(:todo1) { create(:todo, :done, user: john_doe) }
it 'does not update the todo' do
expect { described_class.new.mark_todo_as_done(todo1, john_doe) }.not_to change(todo1.reload, :state)
end end
it 'does not update cache count' do describe '#restore_todos' do
expect(john_doe).not_to receive(:update_todos_count_cache) it_behaves_like 'updating todos state', :done, :pending do
subject(:method_call) do
described_class.new.mark_todo_as_done(todo1, john_doe) service.restore_todos(collection, john_doe)
end end
end end
end end
describe '#mark_all_todos_as_done_by_user' do describe '#resolve_todo' do
it 'marks all todos done' do let!(:todo) { create(:todo, :assigned, user: john_doe) }
todo1 = create(:todo, user: john_doe, state: :pending)
todo2 = create(:todo, user: john_doe, state: :done)
todo3 = create(:todo, user: john_doe, state: :pending)
ids = described_class.new.mark_all_todos_as_done_by_user(john_doe) it 'marks pending todo as done' do
expect do
expect(ids).to contain_exactly(todo1.id, todo3.id) service.resolve_todo(todo, john_doe)
expect(todo1.reload.state).to eq('done') todo.reload
expect(todo2.reload.state).to eq('done') end.to change { todo.done? }.to(true)
expect(todo3.reload.state).to eq('done')
end end
it 'saves resolution mechanism' do
expect do
service.resolve_todo(todo, john_doe, resolved_by_action: :mark_done)
todo.reload
end.to change { todo.resolved_by_mark_done? }.to(true)
end end
describe '#mark_todos_as_done_by_ids' do context 'cached counts' do
let(:issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } it 'updates when todos change' do
let(:another_issue) { create(:issue, project: project, author: author, assignees: [john_doe]) } expect(john_doe.todos_done_count).to eq(0)
expect(john_doe.todos_pending_count).to eq(1)
expect(john_doe).to receive(:update_todos_count_cache).and_call_original
it 'marks an array of todo ids as done' do service.resolve_todo(todo, john_doe)
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
another_todo = create(:todo, :mentioned, user: john_doe, target: another_issue, project: project)
expect { described_class.new.mark_todos_as_done_by_ids([todo.id, another_todo.id], john_doe) } expect(john_doe.todos_done_count).to eq(1)
.to change { john_doe.todos.done.count }.from(0).to(2) expect(john_doe.todos_pending_count).to eq(0)
end
end
end end
it 'marks a single todo id as done' do describe '#restore_todo' do
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) let!(:todo) { create(:todo, :done, user: john_doe) }
expect { described_class.new.mark_todos_as_done_by_ids(todo.id, john_doe) } it 'marks resolved todo as pending' do
.to change { todo.reload.state }.from('pending').to('done') expect do
service.restore_todo(todo, john_doe)
todo.reload
end.to change { todo.pending? }.to(true)
end end
it 'caches the number of todos of a user', :use_clean_rails_memory_store_caching do context 'cached counts' do
create(:todo, :mentioned, user: john_doe, target: issue, project: project) it 'updates when todos change' do
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project) expect(john_doe.todos_done_count).to eq(1)
expect(john_doe.todos_pending_count).to eq(0)
described_class.new.mark_todos_as_done_by_ids(todo, john_doe) expect(john_doe).to receive(:update_todos_count_cache).and_call_original
# Make sure no TodosFinder is inialized to perform counting service.restore_todo(todo, john_doe)
expect(TodosFinder).not_to receive(:new)
expect(john_doe.todos_done_count).to eq(1) expect(john_doe.todos_done_count).to eq(0)
expect(john_doe.todos_pending_count).to eq(1) expect(john_doe.todos_pending_count).to eq(1)
end end
end end
end
def should_create_todo(attributes = {}) def should_create_todo(attributes = {})
attributes.reverse_merge!( attributes.reverse_merge!(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment