Commit c2b57574362fc332baa605a26e3d51cb98ac1e94

Authored by Austin Smith
1 parent 2c8364f931
Exists in master

adding 'my taskboard' feature; requires rake redmine:plugins:migrate to install …

…assignee weight model

Showing 12 changed files with 266 additions and 2 deletions Side-by-side Diff

app/controllers/my_taskboard_controller.rb View file @ c2b5757
  1 +class MyTaskboardController < ApplicationController
  2 + unloadable
  3 +
  4 + before_filter :my_account_or_admin
  5 +
  6 + def my_index
  7 + index
  8 + render 'index'
  9 + end
  10 +
  11 + def index
  12 + issues = Issue.select( \
  13 + "issues.id,
  14 + issues.subject,
  15 + issues.status_id,
  16 + projects.name as project_name,
  17 + trackers.name as tracker_name,
  18 + issue_priority.name as priority_name,
  19 + issue_priority.id as priority_id,
  20 + projects.id as project_id,
  21 + COALESCE(tba.weight, 0) as weight,
  22 + issue_statuses.name as status_name,
  23 + tba.issue_id"
  24 + ) \
  25 + .joins('LEFT OUTER JOIN task_board_assignees AS tba ON tba.issue_id = issues.id AND tba.assignee_id = issues.assigned_to_id') \
  26 + .joins('INNER JOIN issue_statuses ON issues.status_id = issue_statuses.id') \
  27 + .joins('INNER JOIN trackers ON trackers.id = issues.tracker_id') \
  28 + .joins('INNER JOIN projects ON projects.id = issues.project_id') \
  29 + .joins('INNER JOIN enumerations issue_priority ON issues.priority_id = issue_priority.id') \
  30 + .where("assigned_to_id = ? AND issue_statuses.is_closed = 0 AND projects.status = 1", @user.id) \
  31 + .order("weight ASC, issue_priority.position DESC")
  32 + @not_prioritized = Array.new
  33 + @prioritized = Array.new
  34 +
  35 + issues.each do |issue|
  36 + if issue.weight == nil or issue.weight == 0
  37 + @not_prioritized << issue
  38 + else
  39 + @prioritized << issue
  40 + end
  41 + end
  42 + end
  43 +
  44 + def save
  45 + TaskBoardAssignee.destroy_all(:assignee_id => @user.id)
  46 + weight = 1;
  47 + params[:sort].each do |issue_id|
  48 + TaskBoardAssignee.create(:issue_id => issue_id, :assignee_id => @user.id, :weight => weight)
  49 + weight += 1
  50 + end
  51 + respond_to do |format|
  52 + format.js{ head :ok }
  53 + end
  54 + end
  55 +
  56 + def my_account_or_admin
  57 + find_user
  58 + if @user.id != User.current.id
  59 + require_admin
  60 + end
  61 + true
  62 + end
  63 +
  64 + def find_user
  65 + if params[:id] == nil
  66 + params[:id] = User.current.id
  67 + end
  68 +
  69 + if params[:id] == 'current'
  70 + require_login || return
  71 + @user = User.current
  72 + else
  73 + @user = User.find(params[:id])
  74 + end
  75 + rescue ActiveRecord::RecordNotFound
  76 + render_404
  77 + end
  78 +
  79 +end
app/controllers/taskboard_controller.rb View file @ c2b5757
... ... @@ -72,7 +72,7 @@
72 72 status_id = status_id.to_i
73 73 column_id = column_id.to_i
74 74 unless column_id == 0
75   - @column = TaskBoardColumn.find(column_id)
  75 + @columnsn = TaskBoardColumn.find(column_id)
76 76 @column.issue_statuses << IssueStatus.find(status_id)
77 77 end
78 78 end
app/models/task_board_assignee.rb View file @ c2b5757
  1 +class TaskBoardAssignee < ActiveRecord::Base
  2 + unloadable
  3 +end
app/views/my_taskboard/index.html.erb View file @ c2b5757
  1 +<%= javascript_include_tag('task_board', :plugin => 'redmine_task_board') %>
  2 +
  3 +<div id="taskboard-buttons">
  4 + <input type="button" id="edit-issues" value="<%= translate :task_board_issue_bulk_edit %>" />
  5 +</div>
  6 +
  7 +<div class="my-taskboard-wrapper" id="sortable-root">
  8 + <div id="prioritized-wrapper" class="taskboard-pane">
  9 + <h4><%= translate :task_board_prioritized %></h4>
  10 + <ul id="prioritized">
  11 + <% @prioritized.each do|issue| %>
  12 + <li class="card priority-<%= issue.priority_id.to_s %> <%= issue.tracker_name.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '') %>" id="issue_<%= issue.id %>" data-issue-id="<%= issue.id %>" data-status-id="<%= issue.status_id %>">
  13 + <div class="issue">
  14 + <div class="issue-heading">
  15 + <p class="issue-number meta">
  16 + <input type="checkbox" name="ids[]" value="<%= issue.id.to_s %>" />
  17 + <%= link_to "#{issue.tracker_name} ##{issue.id.to_s}", :controller => :issues, :action => :show, :id => issue.id %>
  18 + </p>
  19 + <p class="issue-priority">
  20 + <%= issue.priority_name %>
  21 + </p>
  22 + </div>
  23 + <h3><%= link_to issue.subject, :controller => :issues, :action => :show, :id => issue.id %></h3>
  24 + <p class="meta">
  25 + <%= link_to issue.project_name, :controller => :projects, :action => :show, :id => issue.project_id %> / <%= issue.status_name %>
  26 + </p>
  27 + </div>
  28 + </li>
  29 + <% end %>
  30 + </ul>
  31 + </div>
  32 + <div id="not-prioritized-wrapper" class="taskboard-pane">
  33 + <h4><%= translate :task_board_not_prioritized %></h4>
  34 + <ul id="not-prioritized">
  35 + <% @not_prioritized.each do|issue| %>
  36 + <li class="card priority-<%= issue.priority_id.to_s %> <%= issue.tracker_name.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '') %>" id="issue_<%= issue.id %>" data-issue-id="<%= issue.id %>" data-status-id="<%= issue.status_id %>">
  37 + <div class="issue">
  38 + <div class="issue-heading">
  39 + <p class="issue-number meta">
  40 + <input type="checkbox" name="ids[]" value="<%= issue.id.to_s %>" />
  41 + <%= link_to "#{issue.tracker_name} ##{issue.id.to_s}", :controller => :issues, :action => :show, :id => issue.id %>
  42 + </p>
  43 + <p class="issue-priority">
  44 + <%= issue.priority_name %>
  45 + </p>
  46 + </div>
  47 + <h3><%= link_to issue.subject, :controller => :issues, :action => :show, :id => issue.id %></h3>
  48 + <p class="meta">
  49 + <%= link_to issue.project_name, :controller => :projects, :action => :show, :id => issue.project_id %> / <%= issue.status_name %>
  50 + </p>
  51 + </div>
  52 + </li>
  53 + <% end %>
  54 + </ul>
  55 + </div>
  56 +</div>
  57 +
  58 +<script type="text/javascript">
  59 + var project_save_url = '/users/<%= @user.id %>/taskboard/save';
  60 + var sections = [];
  61 + $('#sortable-root .taskboard-pane ul').each(function() {
  62 + sections.push($(this).attr('id'));
  63 + });
  64 + for (var i in sections) {
  65 + new MyTaskBoardPane(sections[i], {connectWith: '#sortable-root .taskboard-pane ul', constraint: false, scroll: true, dropOnEmpty: true, items: '> .card'});
  66 + }
  67 + TaskBoardUtils.checkboxListener();
  68 + // Sortable.create('sortable-root', {tree: true, dropOnEmpty: true, constraint: false, overlap: 'vertical'});
  69 +</script>
  70 +
  71 +<% content_for :header_tags do %>
  72 + <%= stylesheet_link_tag 'taskboard', :plugin => 'redmine_task_board' %>
  73 +<% end %>
assets/javascripts/task_board.js View file @ c2b5757
... ... @@ -143,6 +143,42 @@
143 143  
144 144 });
145 145  
  146 +var MyTaskBoardPane = TaskBoardSortable.extend({
  147 +
  148 + init: function(id, options) {
  149 + this._super(id, options);
  150 + this.root.data('card-count', this.getNumberOfCards());
  151 + },
  152 +
  153 + getNumberOfCards: function() {
  154 + return this.root.find('.card').length;
  155 + },
  156 +
  157 + onUpdate: function(e, ui) {
  158 + // Add or remove 'empty' class
  159 + var list = ui.item.parent();
  160 + if (list.hasClass('empty') && list.find('.card').length > 0) {
  161 + list.removeClass('empty');
  162 + }
  163 + else if (list.find('.card').length == 0) {
  164 + list.addClass('empty');
  165 + }
  166 +
  167 + var priority_list = [];
  168 + $('#prioritized').find('li.card').each(function() {
  169 + priority_list.push('sort[]=' + $(this).data('issue-id'));
  170 + });
  171 + TaskBoardUtils.save(priority_list);
  172 +
  173 + // We don't handle (this.getNumberOfCards() < $(this.id).readAttribute('data-card-count'))
  174 + // because the gaining column handles re-weighting for the losing column for AJAX efficiency.
  175 +
  176 +
  177 + list.data('card-count', this.getNumberOfCards());
  178 + },
  179 +
  180 +});
  181 +
146 182 var TaskBoardUtils = {
147 183  
148 184 column_serialize: function(list) {
assets/stylesheets/taskboard.css View file @ c2b5757
... ... @@ -122,5 +122,43 @@
122 122 float: right;
123 123 padding: 2px;
124 124 font-size: 9px;
  125 + margin: 0;
  126 +}
  127 +
  128 +#prioritized-wrapper {
  129 + width: 30%;
  130 + float: left;
  131 +}
  132 +
  133 +#not-prioritized-wrapper {
  134 + width: 65%;
  135 + float: left;
  136 +}
  137 +
  138 +ul#not-prioritized {
  139 + overflow: hidden;
  140 +}
  141 +
  142 +#not-prioritized-wrapper ul li {
  143 + float: left;
  144 + width: 200px;
  145 + height: 70px;
  146 + margin: 0 10px 10px 0;
  147 +}
  148 +
  149 +#not-prioritized-wrapper ul li .issue {
  150 + height: 60px;
  151 +}
  152 +
  153 +#not-prioritized-wrapper ul li .issue .issue-heading {
  154 + height: 16px;
  155 +}
  156 +
  157 +#not-prioritized-wrapper ul li .issue h3 {
  158 + height: 24px;
  159 + line-height: 12px;
  160 + font-size: 12px;
  161 + margin-bottom: 0;
  162 + overflow: hidden;
125 163 }
config/locales/en.yml View file @ c2b5757
... ... @@ -15,4 +15,6 @@
15 15 task_board_issue_unarchive: Unarchive Issue
16 16 task_board_issue_bulk_archive: Archive Selected Issues
17 17 task_board_issue_bulk_edit: Edit Selected Issues
  18 + task_board_not_prioritized: Other Issues
  19 + task_board_prioritized: Prioritized Issues
config/routes.rb View file @ c2b5757
1 1 # Plugin's routes
2 2 # See: http://guides.rubyonrails.org/routing.html
3 3  
  4 +get 'users/:id/taskboard', :to => 'my_taskboard#index'
  5 +post 'users/:id/taskboard/save', :to => 'my_taskboard#save'
  6 +get 'my/taskboard', :to => 'my_taskboard#my_index'
4 7 get 'projects/:project_id/taskboard', :to => 'taskboard#index'
5 8 post 'projects/:project_id/taskboard/save', :to => 'taskboard#save'
6 9 post 'projects/:project_id/taskboard/archive-issues', :to => 'taskboard#archive_issues'
db/migrate/003_create_task_board_assignees.rb View file @ c2b5757
  1 +class CreateTaskBoardAssignees < ActiveRecord::Migration
  2 + def change
  3 + create_table :task_board_assignees do |t|
  4 + t.integer :issue_id
  5 + t.integer :assignee_id
  6 + t.integer :weight
  7 + end
  8 + add_index :task_board_assignees, :issue_id
  9 + add_index :task_board_assignees, :assignee_id
  10 + end
  11 +end
... ... @@ -18,6 +18,7 @@
18 18 permission :edit_taskboard, {:projects => :settings, :taskboard => [:create_column, :delete_column, :update_columns]}, :require => :member
19 19 permission :view_taskboard, {:taskboard => [:index, :save, :archive_issues, :unarchive_issue]}, :require => :member
20 20 end
  21 + menu :top_menu, :taskboard, { :controller => 'my_taskboard', :action => 'my_index' }, :caption => 'My Task Board', :before => :projects
21 22 menu :project_menu, :taskboard, { :controller => 'taskboard', :action => 'index' }, :caption => 'Task Board', :before => :issues, :param => :project_id
22 23 end
test/unit/task_board_assignee_test.rb View file @ c2b5757
  1 +require File.expand_path('../../test_helper', __FILE__)
  2 +
  3 +class TaskBoardAssigneeTest < ActiveSupport::TestCase
  4 +
  5 + # Replace this with your real tests.
  6 + def test_truth
  7 + assert true
  8 + end
  9 +end
test/unit/task_board_assignees_test.rb View file @ c2b5757
  1 +require File.expand_path('../../test_helper', __FILE__)
  2 +
  3 +class TaskBoardAssigneesTest < ActiveSupport::TestCase
  4 +
  5 + # Replace this with your real tests.
  6 + def test_truth
  7 + assert true
  8 + end
  9 +end