Commit 4d8cdb3d01b2c672c6df56d126f8e0bb4e700bac

Authored by Leandro Santos
2 parents 923baeaa 3b63b2e9

Merge branch 'task_responsible' into 'master'

Allow assignment of a task responsible to improve task management on large communities

Add responsible attribute to task and add an option to filter tasks by responsible.

See merge request !582
app/controllers/my_profile/tasks_controller.rb
... ... @@ -5,15 +5,35 @@ class TasksController < MyProfileController
5 5 def index
6 6 @filter_type = params[:filter_type].presence
7 7 @filter_text = params[:filter_text].presence
  8 + @filter_responsible = params[:filter_responsible]
8 9 @task_types = Task.pending_types_for(profile)
9   - @tasks = Task.pending_all(profile, @filter_type, @filter_text).order_by('created_at', 'asc').paginate(:per_page => Task.per_page, :page => params[:page])
  10 +
  11 + @tasks = Task.pending_all(profile, @filter_type, @filter_text).order_by('created_at', 'asc')
  12 + @tasks = @tasks.where(:responsible_id => @filter_responsible.to_i != -1 ? @filter_responsible : nil) if @filter_responsible.present?
  13 + @tasks = @tasks.paginate(:per_page => Task.per_page, :page => params[:page])
  14 +
10 15 @failed = params ? params[:failed] : {}
  16 +
  17 + @responsible_candidates = profile.members.by_role(profile.roles.reject {|r| !r.has_permission?('perform_task')}) if profile.organization?
11 18 end
12 19  
13 20 def processed
14 21 @tasks = Task.to(profile).without_spam.closed.sort_by(&:created_at)
15 22 end
16 23  
  24 + def change_responsible
  25 + task = profile.tasks.find(params[:task_id])
  26 +
  27 + if task.responsible.present? && task.responsible.id != params[:old_responsible_id].to_i
  28 + return render :json => {:notice => _('Task already assigned!'), :success => false, :current_responsible => task.responsible.id}
  29 + end
  30 +
  31 + responsible = profile.members.find(params[:responsible_id]) if params[:responsible_id].present?
  32 + task.responsible = responsible
  33 + task.save!
  34 + render :json => {:notice => _('Task responsible successfully updated!'), :success => true, :new_responsible => {:id => responsible.present? ? responsible.id : nil}}
  35 + end
  36 +
17 37 VALID_DECISIONS = [ 'finish', 'cancel', 'skip' ]
18 38  
19 39 def close
... ...
app/models/task.rb
... ... @@ -33,6 +33,7 @@ class Task < ActiveRecord::Base
33 33  
34 34 belongs_to :requestor, :class_name => 'Profile', :foreign_key => :requestor_id
35 35 belongs_to :target, :foreign_key => :target_id, :polymorphic => true
  36 + belongs_to :responsible, :class_name => 'Person', :foreign_key => :responsible_id
36 37  
37 38 validates_uniqueness_of :code, :on => :create
38 39 validates_presence_of :code
... ...
app/views/tasks/_task.html.erb
... ... @@ -2,6 +2,16 @@
2 2  
3 3 <%= render :partial => 'task_icon', :locals => {:task => task} %>
4 4  
  5 + <% if profile.organization? && @responsible_candidates.present? %>
  6 + <div class="task_responsible">
  7 + <span class="label"><%= _('Assign to:') %></span>
  8 + <span>
  9 + <% change_responsible_url = url_for :action => :change_responsible, :controller => :tasks %>
  10 + <%= select_tag "tasks[#{task.id}][responsible]", options_from_collection_for_select(@responsible_candidates, :id, :name, task.responsible.present? ? task.responsible.id : nil), :include_blank => true, :onchange => "change_task_responsible(this);", 'data-old-responsible' => task.responsible.present? ? task.responsible.id : nil, 'data-task' => task.id, 'data-url' => change_responsible_url %>
  11 + </span>
  12 + </div>
  13 + <% end %>
  14 +
5 15 <div class="task_decisions">
6 16 <%=
7 17 labelled_radio_button(_("Accept"), "tasks[#{task.id}][decision]", 'finish', task.default_decision == 'accept',
... ...
app/views/tasks/index.html.erb
... ... @@ -29,6 +29,11 @@
29 29 <p>
30 30 <%= labelled_text_field(_("Text filter")+': ', :filter_text, nil, {:id => 'filter-text',:value => @filter_text}) %>
31 31 </p>
  32 + <% if profile.organization? %>
  33 + <p>
  34 + <%= labelled_select(_('Assigned to')+': ', :filter_responsible, :id, :name, @filter_responsible, [OpenStruct.new(:name => _('All'), :id => nil), OpenStruct.new(:name => _('Unassigned'), :id => -1)] + @responsible_candidates, :class => 'filter_responsible') %>
  35 + </p>
  36 + <% end %>
32 37 <p>
33 38 <%= submit_button(:search, _('Search')) %>
34 39 </p>
... ...
db/migrate/20150525101430_add_responsible_to_task.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +class AddResponsibleToTask < ActiveRecord::Migration
  2 +
  3 + def change
  4 + add_column :tasks, :responsible_id, :integer
  5 + end
  6 +
  7 +end
... ...
public/javascripts/tasks.js
... ... @@ -47,3 +47,19 @@
47 47  
48 48 })(jQuery)
49 49  
  50 +function change_task_responsible(el) {
  51 + jQuery.post($(el).data('url'), {task_id: $(el).data('task'),
  52 + responsible_id: $(el).val(),
  53 + old_responsible_id: $(el).data('old-responsible')}, function(data) {
  54 + if (data.success) {
  55 + $(el).effect("highlight");
  56 + $(el).data('old-responsible', data.new_responsible.id);
  57 + } else {
  58 + $(el).effect("highlight", {color: 'red'});
  59 + }
  60 + if (data.notice) {
  61 + display_notice(data.notice);
  62 + }
  63 + });
  64 +}
  65 +
... ...
public/stylesheets/tasks.css
... ... @@ -49,3 +49,7 @@
49 49 text-decoration: underline;
50 50 font-weight: bold;
51 51 }
  52 +
  53 +.task_responsible {
  54 + text-align: right;
  55 +}
... ...
test/functional/tasks_controller_test.rb
... ... @@ -433,4 +433,91 @@ class TasksControllerTest &lt; ActionController::TestCase
433 433 post :index, :page => 2
434 434 assert_equal [t4], assigns(:tasks)
435 435 end
  436 +
  437 + should 'filter tasks by responsible' do
  438 + Task.stubs(:per_page).returns(3)
  439 + requestor = fast_create(Person)
  440 + responsible = fast_create(Person)
  441 + t1 = Task.create!(:requestor => requestor, :target => profile, :responsible => responsible)
  442 + t2 = Task.create!(:requestor => requestor, :target => profile, :responsible => responsible)
  443 + t3 = Task.create!(:requestor => requestor, :target => profile)
  444 +
  445 + get :index, :filter_responsible => responsible.id
  446 +
  447 + assert_includes assigns(:tasks), t1
  448 + assert_includes assigns(:tasks), t2
  449 + assert_not_includes assigns(:tasks), t3
  450 +
  451 + get :index
  452 +
  453 + assert_includes assigns(:tasks), t1
  454 + assert_includes assigns(:tasks), t2
  455 + assert_includes assigns(:tasks), t3
  456 + end
  457 +
  458 + should 'do not display responsible assignment if profile is not an organization' do
  459 + profile = create_user('personprofile').person
  460 + t1 = Task.create!(:requestor => profile, :target => profile)
  461 + @controller.stubs(:profile).returns(profile)
  462 + login_as profile.user.login
  463 + get :index
  464 +
  465 + assert_select "#task-#{t1.id}"
  466 + assert_select '.task_responsible', 0
  467 + end
  468 +
  469 + should 'do not display responsible assignment filter if profile is not an organization' do
  470 + profile = create_user('personprofile').person
  471 + @controller.stubs(:profile).returns(profile)
  472 + login_as profile.user.login
  473 + get :index
  474 +
  475 + assert_select '.filter_responsible', 0
  476 + end
  477 +
  478 + should 'display responsible assignment if profile is an organization' do
  479 + profile = fast_create(Community)
  480 + person1 = create_user('person1').person
  481 + person2 = create_user('person2').person
  482 + person3 = create_user('person3').person
  483 + profile.add_admin(person1)
  484 + profile.add_admin(person2)
  485 + profile.add_member(person3)
  486 + Task.create!(:requestor => person3, :target => profile)
  487 + @controller.stubs(:profile).returns(profile)
  488 +
  489 + login_as person1.user.login
  490 + get :index
  491 + assert_equivalent [person1, person2], assigns(:responsible_candidates)
  492 + assert_select '.task_responsible'
  493 + end
  494 +
  495 + should 'change task responsible' do
  496 + profile = fast_create(Community)
  497 + @controller.stubs(:profile).returns(profile)
  498 + person = create_user('person1').person
  499 + profile.add_admin(person)
  500 + task = Task.create!(:requestor => person, :target => profile)
  501 +
  502 + assert_equal nil, task.responsible
  503 + login_as person.user.login
  504 + post :change_responsible, :task_id => task.id, :responsible_id => person.id
  505 + assert_equal person, task.reload.responsible
  506 + end
  507 +
  508 + should 'not change task responsible if old responsible is not the current' do
  509 + profile = fast_create(Community)
  510 + @controller.stubs(:profile).returns(profile)
  511 + person1 = create_user('person1').person
  512 + person2 = create_user('person2').person
  513 + profile.add_admin(person1)
  514 + task = Task.create!(:requestor => person1, :target => profile, :responsible => person1)
  515 +
  516 + login_as person1.user.login
  517 + post :change_responsible, :task_id => task.id, :responsible_id => person2.id, :old_responsible => nil
  518 + assert_equal person1, task.reload.responsible
  519 + json_response = ActiveSupport::JSON.decode(response.body)
  520 + assert !json_response['success']
  521 + end
  522 +
436 523 end
... ...
test/unit/task_test.rb
... ... @@ -432,6 +432,14 @@ class TaskTest &lt; ActiveSupport::TestCase
432 432 assert t1.ham?
433 433 end
434 434  
  435 + should 'be able to assign a responsible to a task' do
  436 + person = fast_create(Person)
  437 + task = fast_create(Task)
  438 + task.responsible = person
  439 + task.save!
  440 + assert_equal person, task.responsible
  441 + end
  442 +
435 443 protected
436 444  
437 445 def sample_user
... ...