You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
407 lines
13 KiB
Ruby
407 lines
13 KiB
Ruby
# Copyright © Emilio González Montaña
|
|
# Licence: Attribution & no derivatives
|
|
# * Attribution to the plugin web page URL should be done if you want to use it.
|
|
# https://redmine.ociotec.com/projects/redmine-plugin-scrum
|
|
# * No derivatives of this plugin (or partial) are allowed.
|
|
# Take a look to licence.txt file at plugin root folder for further details.
|
|
|
|
class ScrumController < ApplicationController
|
|
|
|
menu_item :product_backlog, :except => [:stats]
|
|
menu_item :overview, :only => [:stats]
|
|
|
|
before_action :find_issue,
|
|
:only => [:change_story_points, :change_remaining_story_points,
|
|
:change_pending_effort,
|
|
:change_assigned_to, :new_time_entry,
|
|
:create_time_entry, :edit_task, :update_task,
|
|
:change_pending_efforts]
|
|
before_action :find_sprint,
|
|
:only => [:new_pbi, :create_pbi,
|
|
:move_not_closed_pbis_to_last_sprint]
|
|
before_action :find_pbi,
|
|
:only => [:new_task, :create_task, :edit_pbi, :update_pbi,
|
|
:move_pbi, :move_to_last_sprint,
|
|
:move_to_product_backlog]
|
|
before_action :find_project_by_project_id,
|
|
:only => [:stats]
|
|
|
|
before_action :authorize,
|
|
:except => [:new_pbi, :create_pbi, :new_task, :create_task,
|
|
:move_to_last_sprint,
|
|
:move_not_closed_pbis_to_last_sprint,
|
|
:move_to_product_backlog,
|
|
:new_time_entry, :create_time_entry]
|
|
before_action :authorize_add_issues,
|
|
:only => [:new_pbi, :create_pbi, :new_task, :create_task]
|
|
before_action :authorize_edit_issues,
|
|
:only => [:move_to_last_sprint,
|
|
:move_not_closed_pbis_to_last_sprint,
|
|
:move_to_product_backlog]
|
|
before_action :authorize_log_time,
|
|
:only => [:new_time_entry, :create_time_entry]
|
|
|
|
helper :custom_fields
|
|
helper :projects
|
|
helper :scrum
|
|
helper :timelog
|
|
|
|
def change_story_points
|
|
begin
|
|
@issue.story_points = params[:value]
|
|
status = 200
|
|
rescue
|
|
status = 503
|
|
end
|
|
render :body => nil, :status => status
|
|
end
|
|
|
|
def change_remaining_story_points
|
|
@issue.remaining_story_points = params[:value]
|
|
render :body => nil, :status => status
|
|
end
|
|
|
|
def change_pending_effort
|
|
@issue.pending_effort = params[:value]
|
|
render :body => nil, :status => 200
|
|
end
|
|
|
|
def change_pending_efforts
|
|
params['pending_efforts'].each_pair do |id, value|
|
|
pending_effort = PendingEffort.find(id)
|
|
raise "Invalid pending effort ID #{id}" if pending_effort.nil?
|
|
raise "Pending effort ID #{id} is not owned by this issue" if pending_effort.issue_id != @issue.id
|
|
if value.blank?
|
|
pending_effort.delete
|
|
else
|
|
pending_effort.effort = value.to_f
|
|
pending_effort.save!
|
|
end
|
|
end
|
|
redirect_to issue_path(@issue)
|
|
end
|
|
|
|
def change_assigned_to
|
|
@issue.init_journal(User.current)
|
|
@issue.assigned_to = params[:value].blank? ? nil : User.find(params[:value].to_i)
|
|
@issue.save!
|
|
render_task(@project, @issue, params)
|
|
end
|
|
|
|
def new_time_entry
|
|
@pbi_status_id = params[:pbi_status_id]
|
|
@other_pbi_status_ids = params[:other_pbi_status_ids]
|
|
@issue_id = params[:issue_id]
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def create_time_entry
|
|
begin
|
|
time_entry = TimeEntry.new(params.require(:time_entry).permit(:hours, :spent_on, :comments, :activity_id, :user_id))
|
|
time_entry.project_id = @project.id
|
|
time_entry.issue_id = @issue.id
|
|
time_entry.user_id = params[:time_entry][:user_id]
|
|
call_hook(:controller_timelog_edit_before_save, {:params => params, :time_entry => time_entry})
|
|
time_entry.save!
|
|
rescue Exception => @exception
|
|
logger.error("Exception: #{@exception.inspect}")
|
|
end
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def new_pbi
|
|
@pbi = Issue.new
|
|
@pbi.project = @project
|
|
@pbi.tracker = @project.trackers.find(params[:tracker_id])
|
|
@pbi.status = @pbi.default_status
|
|
@pbi.author = User.current
|
|
@pbi.sprint = @sprint
|
|
@top = true unless params[:top].nil? or (params[:top] == 'false')
|
|
respond_to do |format|
|
|
format.html
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def create_pbi
|
|
begin
|
|
@continue = !(params[:create_and_continue].nil?)
|
|
@top = !(params[:top].nil?)
|
|
@pbi = Issue.new
|
|
if params[:issue][:project_id]
|
|
@pbi.project_id = params[:issue][:project_id]
|
|
else
|
|
@pbi.project = @project
|
|
end
|
|
@pbi.author = User.current
|
|
@pbi.tracker_id = params[:issue][:tracker_id]
|
|
@pbi.set_on_top if @top
|
|
@pbi.sprint = @sprint
|
|
update_attributes(@pbi, params)
|
|
@pbi.save!
|
|
rescue Exception => @exception
|
|
logger.error("Exception: #{@exception.inspect}")
|
|
end
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def edit_pbi
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def update_pbi
|
|
begin
|
|
@pbi.init_journal(User.current, params[:issue][:notes])
|
|
update_attributes(@pbi, params)
|
|
@pbi.save!
|
|
rescue Exception => @exception
|
|
logger.error("Exception: #{@exception.inspect}")
|
|
end
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def move_pbi
|
|
begin
|
|
@position = params[:position]
|
|
case params[:position]
|
|
when 'top', 'bottom'
|
|
@pbi.move_pbi_to(@position)
|
|
when 'before'
|
|
@other_pbi = params[:before_other_pbi]
|
|
@pbi.move_pbi_to(@position, @other_pbi)
|
|
when 'after'
|
|
@other_pbi = params[:after_other_pbi]
|
|
@pbi.move_pbi_to(@position, @other_pbi)
|
|
else
|
|
raise "Invalid position: #{@position.inspect}"
|
|
end
|
|
rescue Exception => @exception
|
|
logger.error("Exception: #{@exception.inspect}")
|
|
end
|
|
end
|
|
|
|
def move_to_last_sprint
|
|
begin
|
|
raise "The project hasn't defined any Sprint yet" unless @project.last_sprint
|
|
@previous_sprint = @pbi.sprint
|
|
move_issue_to_sprint(@pbi, @project.last_sprint)
|
|
rescue Exception => @exception
|
|
logger.error("Exception: #{@exception.inspect}")
|
|
end
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def move_not_closed_pbis_to_last_sprint
|
|
begin
|
|
last_sprint = @project.last_sprint
|
|
raise "The project hasn't defined any Sprint yet" unless last_sprint
|
|
not_closed_pbis = @sprint.not_closed_pbis
|
|
if not_closed_pbis.empty?
|
|
flash[:notice] = l(:label_nothing_to_move)
|
|
else
|
|
not_closed_pbis_links = []
|
|
not_closed_pbis.each do |pbi|
|
|
link = view_context.link_to_issue(pbi,
|
|
:project => pbi.project != @project,
|
|
:tracker => true)
|
|
not_closed_pbis_links << link
|
|
move_issue_to_sprint(pbi, last_sprint)
|
|
end
|
|
flash[:notice] = l(:label_pbis_moved,
|
|
:pbis => not_closed_pbis_links.join(', '))
|
|
end
|
|
rescue Exception => exception
|
|
logger.error("Exception: #{exception.inspect}")
|
|
flash[:error] = exception
|
|
end
|
|
redirect_to sprint_path(@sprint)
|
|
end
|
|
|
|
def move_to_product_backlog
|
|
begin
|
|
product_backlog = @project.product_backlogs.find(params[:id])
|
|
move_issue_to_sprint(@pbi, product_backlog)
|
|
rescue Exception => @exception
|
|
logger.error("Exception: #{@exception.inspect}")
|
|
end
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def new_task
|
|
@task = Issue.new
|
|
@task.project = @pbi.project
|
|
@task.tracker = Tracker.find(params[:tracker_id])
|
|
@task.status = @task.default_status
|
|
@task.parent = @pbi
|
|
@task.author = User.current
|
|
@task.sprint = @sprint
|
|
if Scrum::Setting.inherit_pbi_attributes
|
|
@task.inherit_from_issue(@pbi)
|
|
end
|
|
respond_to do |format|
|
|
format.html
|
|
format.js
|
|
end
|
|
rescue Exception => e
|
|
logger.error("Exception: #{e.inspect}")
|
|
render_404
|
|
end
|
|
|
|
def create_task
|
|
begin
|
|
@continue = !(params[:create_and_continue].nil?)
|
|
@task = Issue.new
|
|
if params[:issue][:project_id]
|
|
@task.project_id = params[:issue][:project_id]
|
|
else
|
|
@task.project = @pbi.project
|
|
end
|
|
@task.parent_issue_id = @pbi.id
|
|
@task.author = User.current
|
|
@task.sprint = @sprint
|
|
@task.tracker_id = params[:issue][:tracker_id]
|
|
update_attributes(@task, params)
|
|
@task.save!
|
|
@task.pending_effort = params[:issue][:pending_effort]
|
|
rescue Exception => @exception
|
|
end
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def edit_task
|
|
respond_to do |format|
|
|
format.js
|
|
end
|
|
end
|
|
|
|
def update_task
|
|
begin
|
|
@issue.init_journal(User.current, params[:issue][:notes])
|
|
@old_status = @issue.status
|
|
update_attributes(@issue, params)
|
|
@issue.save!
|
|
@issue.pending_effort = params[:issue][:pending_effort]
|
|
rescue Exception => @exception
|
|
logger.error("Exception: #{@exception.inspect}")
|
|
end
|
|
respond_to do |format|
|
|
format.js do
|
|
render "scrum/update_issue"
|
|
end
|
|
end
|
|
end
|
|
|
|
def stats
|
|
if User.current.allowed_to?(:view_time_entries, @project)
|
|
cond = @project.project_condition(Setting.display_subprojects_issues?)
|
|
@total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f
|
|
end
|
|
|
|
@closed_story_points_per_sprint = @project.closed_story_points_per_sprint
|
|
@closed_story_points_per_sprint_chart = {:id => 'closed_story_points_per_sprint', :height => 400}
|
|
|
|
@hours_per_story_point = @project.hours_per_story_point
|
|
@hours_per_story_point_chart = {:id => 'hours_per_story_point', :height => 400}
|
|
|
|
@sps_by_pbi_category, @sps_by_pbi_category_total = @project.sps_by_category
|
|
@sps_by_pbi_type, @sps_by_pbi_type_total = @project.sps_by_pbi_type
|
|
@effort_by_activity, @effort_by_activity_total = @project.effort_by_activity
|
|
end
|
|
|
|
private
|
|
|
|
def render_task(project, task, params)
|
|
render :partial => "post_its/sprint_board/task",
|
|
:status => 200,
|
|
:locals => {:project => project,
|
|
:task => task,
|
|
:pbi_status_id => params[:pbi_status_id],
|
|
:other_pbi_status_ids => params[:other_pbi_status_ids].split(","),
|
|
:task_id => params[:task_id],
|
|
:read_only => false}
|
|
end
|
|
|
|
def find_sprint
|
|
@sprint = Sprint.find(params[:sprint_id])
|
|
@project = @sprint.project
|
|
rescue
|
|
logger.error("Sprint #{params[:sprint_id]} not found")
|
|
render_404
|
|
end
|
|
|
|
def find_pbi
|
|
@pbi = Issue.find(params[:pbi_id])
|
|
@sprint = @pbi.sprint
|
|
@project = @sprint.project
|
|
rescue
|
|
logger.error("PBI #{params[:pbi_id]} not found")
|
|
render_404
|
|
end
|
|
|
|
def authorize_action_on_current_project(action)
|
|
if User.current.allowed_to?(action, @project)
|
|
return true
|
|
else
|
|
render_403
|
|
return false
|
|
end
|
|
end
|
|
|
|
def authorize_add_issues
|
|
authorize_action_on_current_project(:add_issues)
|
|
end
|
|
|
|
def authorize_log_time
|
|
authorize_action_on_current_project(:log_time)
|
|
end
|
|
|
|
def authorize_edit_issues
|
|
authorize_action_on_current_project(:edit_issues)
|
|
end
|
|
|
|
def update_attributes(issue, params)
|
|
issue.status_id = params[:issue][:status_id] unless params[:issue][:status_id].nil?
|
|
raise 'New status is not allowed' unless issue.new_statuses_allowed_to.include?(issue.status)
|
|
issue.project_id = params[:issue][:project_id] unless params[:issue][:project_id].nil?
|
|
issue.assigned_to_id = params[:issue][:assigned_to_id] unless params[:issue][:assigned_to_id].nil?
|
|
issue.subject = params[:issue][:subject] unless params[:issue][:subject].nil?
|
|
issue.priority_id = params[:issue][:priority_id] unless params[:issue][:priority_id].nil?
|
|
issue.estimated_hours = params[:issue][:estimated_hours].gsub(',', '.') if issue.safe_attribute?(:estimated_hours) and (!(params[:issue][:estimated_hours].nil?))
|
|
issue.done_ratio = params[:issue][:done_ratio] unless params[:issue][:done_ratio].nil?
|
|
issue.description = params[:issue][:description] unless params[:issue][:description].nil?
|
|
issue.category_id = params[:issue][:category_id] if issue.safe_attribute?(:category_id) and (!(params[:issue][:category_id].nil?))
|
|
issue.fixed_version_id = params[:issue][:fixed_version_id] if issue.safe_attribute?(:fixed_version_id) and (!(params[:issue][:fixed_version_id].nil?))
|
|
issue.start_date = params[:issue][:start_date] if issue.safe_attribute?(:start_date) and (!(params[:issue][:start_date].nil?))
|
|
issue.due_date = params[:issue][:due_date] if issue.safe_attribute?(:due_date) and (!(params[:issue][:due_date].nil?))
|
|
issue.custom_field_values = params[:issue][:custom_field_values] unless params[:issue][:custom_field_values].nil?
|
|
end
|
|
|
|
def move_issue_to_sprint(issue, sprint)
|
|
issue.init_journal(User.current)
|
|
issue.sprint = sprint
|
|
issue.save!
|
|
issue.children.each do |child|
|
|
unless child.closed?
|
|
move_issue_to_sprint(child, sprint)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|