Import archive redmine_agile-1_6_3-light

This commit is contained in:
Alexandr Antonov 2022-04-01 17:28:41 +03:00
parent 82b5e37e67
commit 7c5d7b4292
60 changed files with 705 additions and 655 deletions

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -51,7 +51,7 @@ class AgileBoardsController < ApplicationController
include SortHelper
include IssuesHelper
helper :timelog
include RedmineAgile::AgileHelper
include RedmineAgile::Helpers::AgileHelper
helper :checklists if RedmineAgile.use_checklist?
def index
@ -81,10 +81,10 @@ class AgileBoardsController < ApplicationController
retrieve_agile_query_from_session
old_status = @issue.status
@issue.init_journal(User.current)
@issue.safe_attributes = auto_assign_on_move? ? params[:issue].merge(:assigned_to_id => User.current.id) : params[:issue]
checking_params = params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params
saved = checking_params['issue'] && checking_params['issue'].inject(true) do |total, attribute|
@issue.safe_attributes = configured_params['issue']
saved = configured_params['issue'] && configured_params['issue'].inject(true) do |total, attribute|
if @issue.attributes.include?(attribute.first)
total &&= @issue.attributes[attribute.first].to_i == attribute.last.to_i
else
@ -138,6 +138,16 @@ class AgileBoardsController < ApplicationController
private
def configured_params
return @configured_params if @configured_params
issue_params = params[:issue]
issue_params[:parent_issue_id] = issue_params[:parent_id] && issue_params.delete(:parent_id) if issue_params[:parent_id]
issue_params[:assigned_to_id] = User.current.id if auto_assign_on_move?
@configured_params = params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params
end
def auto_assign_on_move?
RedmineAgile.auto_assign_on_move? && @issue.assigned_to.nil? &&
!params[:issue].keys.include?('assigned_to_id') &&

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -45,7 +45,7 @@ class AgileChartsController < ApplicationController
include SortHelper
include IssuesHelper
helper :timelog
include RedmineAgile::AgileHelper
include RedmineAgile::Helpers::AgileHelper
def show
retrieve_charts_query
@ -85,7 +85,7 @@ class AgileChartsController < ApplicationController
private
def render_data(options = {})
agile_chart = RedmineAgile::Charts::AGILE_CHARTS[@chart]
agile_chart = RedmineAgile::Charts::Helper::AGILE_CHARTS[@chart]
data = agile_chart[:class].data(@issues, options) if agile_chart
if data

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -53,7 +53,7 @@ class AgileChartsQuery < AgileQuery
end
def chart
@chart ||= RedmineAgile::Charts.valid_chart_name_by(options[:chart])
@chart ||= RedmineAgile::Charts::Helper.valid_chart_name_by(options[:chart])
end
def chart=(arg)
@ -69,10 +69,10 @@ class AgileChartsQuery < AgileQuery
end
def interval_size
if RedmineAgile::AgileChart::TIME_INTERVALS.include?(options[:interval_size])
if RedmineAgile::Charts::AgileChart::TIME_INTERVALS.include?(options[:interval_size])
options[:interval_size]
else
RedmineAgile::AgileChart::DAY_INTERVAL
RedmineAgile::Charts::AgileChart::DAY_INTERVAL
end
end
@ -94,8 +94,8 @@ class AgileChartsQuery < AgileQuery
self.date_from = params[:date_from] || (params[:query] && params[:query][:date_from])
self.date_to = params[:date_to] || (params[:query] && params[:query][:date_to])
self.chart = params[:chart] || (params[:query] && params[:query][:chart]) || params[:default_chart] || RedmineAgile.default_chart
self.interval_size = params[:interval_size] || (params[:query] && params[:query][:interval_size]) || RedmineAgile::AgileChart::DAY_INTERVAL
self.chart_unit = params[:chart_unit] || (params[:query] && params[:query][:chart_unit]) || RedmineAgile::Charts::UNIT_ISSUES
self.interval_size = params[:interval_size] || (params[:query] && params[:query][:interval_size]) || RedmineAgile::Charts::AgileChart::DAY_INTERVAL
self.chart_unit = params[:chart_unit] || (params[:query] && params[:query][:chart_unit]) || RedmineAgile::Charts::Helper::UNIT_ISSUES
self
end

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -120,7 +120,7 @@ class AgileQuery < Query
end
def chart_unit
@chart_unit ||= RedmineAgile::Charts.valid_chart_unit_by(options[:chart], options[:chart_unit])
@chart_unit ||= RedmineAgile::Charts::Helper.valid_chart_unit_by(options[:chart], options[:chart_unit])
end
def chart_unit=(value)

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -13,7 +13,7 @@
</span>
</legend>
<div id='agile_chart'>
<%= render_agile_chart(RedmineAgile::Charts.valid_chart_name_by(params[:chart] || RedmineAgile.default_chart), @version.fixed_issues) %>
<%= render_agile_chart(RedmineAgile::Charts::Helper.valid_chart_name_by(params[:chart] || RedmineAgile.default_chart), @version.fixed_issues) %>
</div>
</fieldset>
<% end %>
@ -24,7 +24,7 @@
<% end %>
<%= javascript_tag do %>
var chartsWithUnits = <%= raw RedmineAgile::Charts::CHARTS_WITH_UNITS.to_json %>
var chartsWithUnits = <%= raw RedmineAgile::Charts::Helper::CHARTS_WITH_UNITS.to_json %>
$(document).ready(function() {
toggleChartUnit($('#chart_by_select').val(), 'chart-unit-row');
});

View File

@ -29,7 +29,7 @@
<tr id="interval-size">
<td><%= l(:label_agile_interval_size) %></td>
<td>
<%= select_tag 'interval_size', options_for_select(RedmineAgile::AgileChart::TIME_INTERVALS.map { |i| [l(:"label_agile_#{i}"), i] }, @query.interval_size) %>
<%= select_tag 'interval_size', options_for_select(RedmineAgile::Charts::AgileChart::TIME_INTERVALS.map { |i| [l(:"label_agile_#{i}"), i] }, @query.interval_size) %>
</td>
</tr>
</table>
@ -58,7 +58,7 @@
<% end %>
<%= javascript_tag do %>
var chartsWithUnits = <%= raw RedmineAgile::Charts::CHARTS_WITH_UNITS.to_json %>
var chartsWithUnits = <%= raw RedmineAgile::Charts::Helper::CHARTS_WITH_UNITS.to_json %>
$(document).ready(function() {
toggleChartUnit($('#chart').val(), 'chart-unit-row');
/* Hide chart_period checkbox so that it couldn't be unchecked */

View File

@ -630,7 +630,9 @@ function recalculateSprintHours() {
hours = parseFloat($(issue).data(dataAttr));
versionEstimationSum += hours;
});
$(elem).find('.sprint-estimate').text('(' + versionEstimationSum.toFixed(2) + unit + ')');
if (versionEstimationSum > 0) {
$(elem).find('.sprint-estimate').text('(' + versionEstimationSum.toFixed(2) + unit + ')');
}
});
}

View File

@ -576,6 +576,16 @@ table.list.issues-board.sticky {
margin-top: -2.3em;
}
.controller-agile_boards .observed-desc {
margin-top: 0;
display: flex;
justify-content: end;
}
.agile-sprint-description {
margin-right: auto;
}
.agile-board-fullscreen .controller-agile_boards .query-totals {
margin: 0px;
position: fixed;

View File

@ -198,6 +198,8 @@ en:
label_agile_board_totals_velocity: Velocity
label_agile_board_totals_interval: Interval
label_agile_board_totals_remaining: Remaining
label_agile_board_totals_description: Sprint goal
label_agile_sprint_list_active: Active sprints
label_agile_sprint_list_future: Future sprints

View File

@ -208,7 +208,7 @@ pt-BR:
label_agile_board_backlog: Backlog
label_agile_board_backlog_column: Colunas do Backlog
label_agile_board_search_backlog_issues: Pesquisar tarefas do backlog
label_agile_version_plural: Vesões
label_agile_version_plural: Versões
label_agile_sprint_add: Adicionar novo Sprint
label_agile_version_add: Adicionar Versão

View File

@ -189,6 +189,7 @@ ru:
label_agile_board_totals_spent_time: Потраченное время
label_agile_board_totals_percent_done: Готовность (%)
label_agile_board_totals_velocity: Скорость закрытия
label_agile_board_totals_description: Цель спринта
label_agile_sprint_list_active: Активные спринты
label_agile_sprint_list_future: Следующие спринты

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,9 +1,21 @@
== Redmine Agile plugin changelog
Redmine Agile plugin - Agile board plugin for redmine
Copyright (C) 2011-2021 RedmineUP
Copyright (C) 2011-2022 RedmineUP
http://www.redmineup.com/
== 2022-03-30 v1.6.3
* Redmine 5.0 compatibility
* Added sprint description to a board view
* Changed Manage board permissions behavior
* Fixed default board setting
* Fixed card load error
* Fixed empty sprint for default board
* Fixed saved query bug for agile board
* Fixed story points are not being added up in "Sprints" subtab of "Backlog" tab
* Fixed error with empty sprint_id in AgileQuery
== 2021-09-07 v1.6.2
* Added notification message for not available operations on Backlog board

11
init.rb
View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -17,11 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
requires_redmine_crm version_or_higher: '0.0.43' rescue raise "\n\033[31mRedmine requires newer redmine_crm gem version.\nPlease update with 'bundle update redmine_crm'.\033[0m"
requires_redmine_crm version_or_higher: '0.0.56' rescue raise "\n\033[31mRedmine requires newer redmine_crm gem version.\nPlease update with 'bundle update redmine_crm'.\033[0m"
require 'redmine'
AGILE_VERSION_NUMBER = '1.6.2'
AGILE_VERSION_NUMBER = '1.6.3'
AGILE_VERSION_TYPE = "Light version"
if ActiveRecord::VERSION::MAJOR >= 4 && !defined?(FCSV)
@ -68,4 +68,7 @@ Redmine::Plugin.register :redmine_agile do
end
end
require 'redmine_agile'
if Rails.configuration.respond_to?(:autoloader) && Rails.configuration.autoloader == :zeitwerk
Rails.autoloaders.each { |loader| loader.ignore(File.dirname(__FILE__) + '/lib') }
end
require File.dirname(__FILE__) + '/lib/redmine_agile'

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -17,22 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
require 'redmine_agile/hooks/views_layouts_hook'
require 'redmine_agile/hooks/views_issues_hook'
require 'redmine_agile/hooks/views_versions_hook'
require 'redmine_agile/hooks/controller_issue_hook'
require 'redmine_agile/patches/issue_patch'
require 'redmine_agile/helpers/agile_helper'
require 'redmine_agile/charts/agile_chart'
require 'redmine_agile/charts/burndown_chart'
require 'redmine_agile/charts/work_burndown_chart'
require 'redmine_agile/charts/charts'
require 'redmine_agile/patches/issue_drop_patch'
module RedmineAgile
ISSUES_PER_COLUMN = 10
@ -64,7 +48,7 @@ module RedmineAgile
end
def default_chart
Setting.plugin_redmine_agile['default_chart'] || Charts::BURNDOWN_CHART
Setting.plugin_redmine_agile['default_chart'] || Charts::Helper::BURNDOWN_CHART
end
def estimate_units
@ -135,3 +119,22 @@ module RedmineAgile
end
end
REDMINE_AGILE_REQUIRED_FILES = [
'redmine_agile/hooks/views_layouts_hook',
'redmine_agile/hooks/views_issues_hook',
'redmine_agile/hooks/views_versions_hook',
'redmine_agile/hooks/controller_issue_hook',
'redmine_agile/patches/issue_patch',
'redmine_agile/helpers/agile_helper',
'redmine_agile/charts/helper',
'redmine_agile/charts/agile_chart',
'redmine_agile/charts/burndown_chart',
'redmine_agile/charts/work_burndown_chart',
'redmine_agile/patches/issue_drop_patch'
]
Rails.application.config.after_initialize do
base_url = File.dirname(__FILE__)
REDMINE_AGILE_REQUIRED_FILES.each { |file| require(base_url + '/' + file) }
end

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -18,262 +18,264 @@
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
module RedmineAgile
class AgileChart
include Redmine::I18n
include Redmine::Utils::DateCalculation
module Charts
class AgileChart
include Redmine::I18n
include Redmine::Utils::DateCalculation
DAY_INTERVAL = 'day'.freeze
WEEK_INTERVAL = 'week'.freeze
MONTH_INTERVAL = 'month'.freeze
QUARTER_INTERVAL = 'quarter'.freeze
YEAR_INTERVAL = 'year'.freeze
DAY_INTERVAL = 'day'.freeze
WEEK_INTERVAL = 'week'.freeze
MONTH_INTERVAL = 'month'.freeze
QUARTER_INTERVAL = 'quarter'.freeze
YEAR_INTERVAL = 'year'.freeze
TIME_INTERVALS = [DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, YEAR_INTERVAL].freeze
TIME_INTERVALS = [DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, YEAR_INTERVAL].freeze
attr_reader :line_colors
attr_reader :line_colors
def initialize(data_scope, options = {})
@options = options
@data_scope = data_scope
@data_from ||= options[:data_from]
@data_to ||= options[:data_to]
@interval_size = options[:interval_size] || DAY_INTERVAL
initialize_chart_periods
@step_x_labels = @period_count > 18 ? @period_count / 12 + 1 : 1
@fields = chart_fields_by_period
@weekend_periods = weekend_periods
@estimated_unit = options[:estimated_unit] || ESTIMATE_HOURS
@line_colors = {}
end
def initialize(data_scope, options = {})
@options = options
@data_scope = data_scope
@data_from ||= options[:data_from]
@data_to ||= options[:data_to]
@interval_size = options[:interval_size] || DAY_INTERVAL
initialize_chart_periods
@step_x_labels = @period_count > 18 ? @period_count / 12 + 1 : 1
@fields = chart_fields_by_period
@weekend_periods = weekend_periods
@estimated_unit = options[:estimated_unit] || ESTIMATE_HOURS
@line_colors = {}
end
def data
{ title: '', y_title: '', labels: [], datasets: [] }
end
def data
{ title: '', y_title: '', labels: [], datasets: [] }
end
def self.data(data_scope, options = {})
new(data_scope, options).data
end
def self.data(data_scope, options = {})
new(data_scope, options).data
end
protected
protected
def current_date_period
return @current_date_period if @current_date_period
def current_date_period
return @current_date_period if @current_date_period
for_future = RedmineAgile.chart_future_data? ? @options[:date_to].present? : false
date_period = (@date_to <= Date.today || for_future ? @period_count : (@period_count - (@date_to - Date.today).to_i / @scale_division)).round
@current_date_period ||= date_period > 0 ? date_period : 0
end
for_future = RedmineAgile.chart_future_data? ? @options[:date_to].present? : false
date_period = (@date_to <= Date.today || for_future ? @period_count : (@period_count - (@date_to - Date.today).to_i / @scale_division)).round
@current_date_period ||= date_period > 0 ? date_period : 0
end
def due_date_period
@date_from = @date_from.to_date
@date_to = @date_to.to_date
due_date = (@due_date && @due_date > @date_from) ? @due_date : @date_from
@due_date_period ||= (@due_date ? @period_count - (@date_to - due_date.to_date).to_i : @period_count - 1)
@due_date_period = @due_date_period > 0 ? @due_date_period : 1
end
def due_date_period
@date_from = @date_from.to_date
@date_to = @date_to.to_date
due_date = (@due_date && @due_date > @date_from) ? @due_date : @date_from
@due_date_period ||= (@due_date ? @period_count - (@date_to - due_date.to_date).to_i : @period_count - 1)
@due_date_period = @due_date_period > 0 ? @due_date_period : 1
end
def date_short_period?
(@date_to.to_date - @date_from.to_date).to_i <= 31
end
def date_short_period?
(@date_to.to_date - @date_from.to_date).to_i <= 31
end
def date_effort(issues, effort_date)
cumulative_left = 0
total_left = 0
total_done = 0
issues.each do |issue|
done_ratio_details = issue.journals.map(&:details).flatten.select { |detail| 'done_ratio' == detail.prop_key }
details_today_or_earlier = done_ratio_details.select { |a| a.journal.created_on.localtime.to_date <= effort_date }
def date_effort(issues, effort_date)
cumulative_left = 0
total_left = 0
total_done = 0
issues.each do |issue|
done_ratio_details = issue.journals.map(&:details).flatten.select { |detail| 'done_ratio' == detail.prop_key }
details_today_or_earlier = done_ratio_details.select { |a| a.journal.created_on.localtime.to_date <= effort_date }
last_done_ratio_change = details_today_or_earlier.sort_by { |a| a.journal.created_on }.last
ratio = if issue.closed? && issue.closed_on.localtime.to_date <= effort_date
100
elsif last_done_ratio_change
last_done_ratio_change.value
elsif (done_ratio_details.size > 0) || (issue.closed? && issue.closed_on > effort_date)
0
else
issue.done_ratio.to_i
end
last_done_ratio_change = details_today_or_earlier.sort_by { |a| a.journal.created_on }.last
ratio = if issue.closed? && issue.closed_on.localtime.to_date <= effort_date
100
elsif last_done_ratio_change
last_done_ratio_change.value
elsif (done_ratio_details.size > 0) || (issue.closed? && issue.closed_on > effort_date)
0
else
issue.done_ratio.to_i
end
if @estimated_unit == 'hours'
cumulative_left += (issue.estimated_hours.to_f * ratio.to_f / 100.0)
total_left += (issue.estimated_hours.to_f * (100 - ratio.to_f) / 100.0)
total_done += (issue.estimated_hours.to_f * ratio.to_f / 100.0)
if @estimated_unit == 'hours'
cumulative_left += (issue.estimated_hours.to_f * ratio.to_f / 100.0)
total_left += (issue.estimated_hours.to_f * (100 - ratio.to_f) / 100.0)
total_done += (issue.estimated_hours.to_f * ratio.to_f / 100.0)
else
cumulative_left += (issue.story_points.to_f * ratio.to_f / 100.0)
total_left += (issue.story_points.to_f * (100 - ratio.to_f) / 100.0)
total_done += (issue.story_points.to_f * ratio.to_f / 100.0)
end
end
[total_left, cumulative_left, total_done]
end
def use_subissue_done_ratio
!Setting.respond_to?(:parent_issue_done_ratio) || Setting.parent_issue_done_ratio == 'derived' || Setting.parent_issue_done_ratio.nil?
end
private
def scope_by_created_date
@data_scope.
where("#{Issue.table_name}.created_on >= ?", @date_from).
where("#{Issue.table_name}.created_on < ?", @date_to.to_date + 1).
where("#{Issue.table_name}.created_on IS NOT NULL").
group("#{Issue.table_name}.created_on").
count
end
def scope_by_closed_date
@data_scope.
open(false).
where("#{Issue.table_name}.closed_on >= ?", @date_from).
where("#{Issue.table_name}.closed_on < ?", @date_to.to_date + 1).
where("#{Issue.table_name}.closed_on IS NOT NULL").
group("#{Issue.table_name}.closed_on").
count
end
# options
# color - Line color in RGB format (e.g '255,255,255') (random)
# fill - Fille background under line (false)
# dashed - Draw dached line (solid)
# nopoints - Doesn't show points on line (false)
def dataset(dataset_data, label, options = {})
color = options[:color] || [rand(255), rand(255), rand(255)].join(',')
dataset_color = "rgba(#{color}, 1)"
{
type: (options[:type] || 'line'),
data: dataset_data,
label: label,
fill: (options[:fill] || false),
backgroundColor: "rgba(#{color}, 0.2)",
borderColor: dataset_color,
borderDash: (options[:dashed] ? [5, 5] : []),
borderWidth: (options[:dashed] ? 1.5 : 2),
pointRadius: (options[:nopoints] ? 0 : 3),
pointBackgroundColor: dataset_color,
tooltips: { enable: false }
}
end
def initialize_chart_periods
raise Exception "Dates can't be blank" if [@date_to, @date_from].any?(&:blank?)
period_count
scale_division
end
def issues_count_by_period(issues_scope)
data = [0] * @period_count
issues_scope.each do |c|
next if c.first.localtime.to_date > @date_to.to_date
period_num = ((@date_to.to_date - c.first.localtime.to_date).to_i / @scale_division).to_i
data[period_num] += c.last unless data[period_num].blank?
end
data.reverse
end
def issues_avg_count_by_period(issues_scope)
count_by_date = {}
issues_scope.each {|x, y| count_by_date[x.localtime.to_date] = count_by_date[x.localtime.to_date].to_i + y }
data = [0] * @period_count
count_by_date.each do |x, y|
next if x.to_date > @date_to.to_date
period_num = ((@date_to.to_date - x.to_date).to_i / @scale_division).to_i
if data[period_num]
data[period_num] = y unless data[period_num].to_i > 0
data[period_num] = (data[period_num] + y) / 2.0
end
end
data.reverse
end
def chart_fields_by_period
chart_dates_by_period.map { |d| chart_field_by_date(d) }
end
def chart_field_by_date(date)
case @interval_size
when YEAR_INTERVAL
date.year
when QUARTER_INTERVAL, MONTH_INTERVAL
month_abbr_name(date.month) + ' ' + date.year.to_s
else
cumulative_left += (issue.story_points.to_f * ratio.to_f / 100.0)
total_left += (issue.story_points.to_f * (100 - ratio.to_f) / 100.0)
total_done += (issue.story_points.to_f * ratio.to_f / 100.0)
date.day.to_s + ' ' + month_name(date.month)
end
end
[total_left, cumulative_left, total_done]
end
def use_subissue_done_ratio
!Setting.respond_to?(:parent_issue_done_ratio) || Setting.parent_issue_done_ratio == 'derived' || Setting.parent_issue_done_ratio.nil?
end
private
def scope_by_created_date
@data_scope.
where("#{Issue.table_name}.created_on >= ?", @date_from).
where("#{Issue.table_name}.created_on < ?", @date_to.to_date + 1).
where("#{Issue.table_name}.created_on IS NOT NULL").
group("#{Issue.table_name}.created_on").
count
end
def scope_by_closed_date
@data_scope.
open(false).
where("#{Issue.table_name}.closed_on >= ?", @date_from).
where("#{Issue.table_name}.closed_on < ?", @date_to.to_date + 1).
where("#{Issue.table_name}.closed_on IS NOT NULL").
group("#{Issue.table_name}.closed_on").
count
end
# options
# color - Line color in RGB format (e.g '255,255,255') (random)
# fill - Fille background under line (false)
# dashed - Draw dached line (solid)
# nopoints - Doesn't show points on line (false)
def dataset(dataset_data, label, options = {})
color = options[:color] || [rand(255), rand(255), rand(255)].join(',')
dataset_color = "rgba(#{color}, 1)"
{
type: (options[:type] || 'line'),
data: dataset_data,
label: label,
fill: (options[:fill] || false),
backgroundColor: "rgba(#{color}, 0.2)",
borderColor: dataset_color,
borderDash: (options[:dashed] ? [5, 5] : []),
borderWidth: (options[:dashed] ? 1.5 : 2),
pointRadius: (options[:nopoints] ? 0 : 3),
pointBackgroundColor: dataset_color,
tooltips: { enable: false }
}
end
def initialize_chart_periods
raise Exception "Dates can't be blank" if [@date_to, @date_from].any?(&:blank?)
period_count
scale_division
end
def issues_count_by_period(issues_scope)
data = [0] * @period_count
issues_scope.each do |c|
next if c.first.localtime.to_date > @date_to.to_date
period_num = ((@date_to.to_date - c.first.localtime.to_date).to_i / @scale_division).to_i
data[period_num] += c.last unless data[period_num].blank?
end
data.reverse
end
def issues_avg_count_by_period(issues_scope)
count_by_date = {}
issues_scope.each {|x, y| count_by_date[x.localtime.to_date] = count_by_date[x.localtime.to_date].to_i + y }
data = [0] * @period_count
count_by_date.each do |x, y|
next if x.to_date > @date_to.to_date
period_num = ((@date_to.to_date - x.to_date).to_i / @scale_division).to_i
if data[period_num]
data[period_num] = y unless data[period_num].to_i > 0
data[period_num] = (data[period_num] + y) / 2.0
def weekend_periods
periods = []
@period_count.times do |m|
period_date = ((@date_to.to_date - 1 - m * @scale_division) + 1)
periods << @period_count - m - 1 if non_working_week_days.include?(period_date.cwday)
end
end
data.reverse
end
def chart_fields_by_period
chart_dates_by_period.map { |d| chart_field_by_date(d) }
end
def chart_field_by_date(date)
case @interval_size
when YEAR_INTERVAL
date.year
when QUARTER_INTERVAL, MONTH_INTERVAL
month_abbr_name(date.month) + ' ' + date.year.to_s
else
date.day.to_s + ' ' + month_name(date.month)
end
end
def weekend_periods
periods = []
@period_count.times do |m|
period_date = ((@date_to.to_date - 1 - m * @scale_division) + 1)
periods << @period_count - m - 1 if non_working_week_days.include?(period_date.cwday)
end
periods.compact
end
def chart_data_pairs(chart_data)
chart_data.inject([]) { |accum, value| accum << value }
data_pairs = []
for i in 0..chart_data.count - 1
data_pairs << [chart_dates_by_period[i], chart_data[i]]
end
data_pairs
end
def chart_dates_by_period
return @chart_dates_by_period if @chart_dates_by_period
period = period_count > 1 ? period_count - 1 : period_count
@chart_dates_by_period ||= period.times.inject([]) do |accum, m|
period_date = ((@date_to.to_date - 1 - m * @scale_division) + 1)
accum << if @interval_size == WEEK_INTERVAL
period_date.at_beginning_of_week.to_date
else
period_date.to_date
end
end.reverse
end
def month_abbr_name(month)
l('date.abbr_month_names')[month]
end
def trendline(y_values)
size = y_values.size
x_values = (1..size).to_a
sum_x = 0
sum_y = 0
sum_xx = 0
sum_xy = 0
y_values.zip(x_values).each do |y, x|
sum_xy += x * y
sum_xx += x * x
sum_x += x
sum_y += y
periods.compact
end
slope = 1.0 * ((size * sum_xy) - (sum_x * sum_y)) / ((size * sum_xx) - (sum_x * sum_x))
intercept = 1.0 * (sum_y - (slope * sum_x)) / size
def chart_data_pairs(chart_data)
chart_data.inject([]) { |accum, value| accum << value }
data_pairs = []
for i in 0..chart_data.count - 1
data_pairs << [chart_dates_by_period[i], chart_data[i]]
end
data_pairs
end
line_values = x_values.map { |x| predict(x, slope, intercept) }
line_values.select { |val| val >= 0 }
end
def chart_dates_by_period
return @chart_dates_by_period if @chart_dates_by_period
def predict(x, slope, intercept)
slope * x + intercept
end
period = period_count > 1 ? period_count - 1 : period_count
@chart_dates_by_period ||= period.times.inject([]) do |accum, m|
period_date = ((@date_to.to_date - 1 - m * @scale_division) + 1)
accum << if @interval_size == WEEK_INTERVAL
period_date.at_beginning_of_week.to_date
else
period_date.to_date
end
end.reverse
end
def period_count
@period_count ||= ((@date_to.to_time - @date_from.to_time) / time_divider).round + 1
end
def month_abbr_name(month)
l('date.abbr_month_names')[month]
end
def scale_division
@scale_division ||= time_divider / 1.day
end
def trendline(y_values)
size = y_values.size
x_values = (1..size).to_a
sum_x = 0
sum_y = 0
sum_xx = 0
sum_xy = 0
y_values.zip(x_values).each do |y, x|
sum_xy += x * y
sum_xx += x * x
sum_x += x
sum_y += y
end
def time_divider
@interval_size == QUARTER_INTERVAL ? 3.months : 1.send(@interval_size)
slope = 1.0 * ((size * sum_xy) - (sum_x * sum_y)) / ((size * sum_xx) - (sum_x * sum_x))
intercept = 1.0 * (sum_y - (slope * sum_x)) / size
line_values = x_values.map { |x| predict(x, slope, intercept) }
line_values.select { |val| val >= 0 }
end
def predict(x, slope, intercept)
slope * x + intercept
end
def period_count
@period_count ||= ((@date_to.to_time - @date_from.to_time) / time_divider).round + 1
end
def scale_division
@scale_division ||= time_divider / 1.day
end
def time_divider
@interval_size == QUARTER_INTERVAL ? 3.months : 1.send(@interval_size)
end
end
end
end

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -18,95 +18,97 @@
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
module RedmineAgile
class BurndownChart < AgileChart
attr_accessor :burndown_data, :cumulative_burndown_data
module Charts
class BurndownChart < AgileChart
attr_accessor :burndown_data, :cumulative_burndown_data
def initialize(data_scope, options = {})
@date_from = options[:date_from] && options[:date_from].to_date ||
[data_scope.minimum("#{Issue.table_name}.created_on"),
data_scope.minimum("#{Issue.table_name}.start_date")].compact.map(&:to_date).min
def initialize(data_scope, options = {})
@date_from = options[:date_from] && options[:date_from].to_date ||
[data_scope.minimum("#{Issue.table_name}.created_on"),
data_scope.minimum("#{Issue.table_name}.start_date")].compact.map(&:to_date).min
@date_to = options[:date_to] && options[:date_to].to_date ||
[options[:due_date],
data_scope.maximum("#{Issue.table_name}.updated_on")].compact.map(&:to_date).max
@date_to = options[:date_to] && options[:date_to].to_date ||
[options[:due_date],
data_scope.maximum("#{Issue.table_name}.updated_on")].compact.map(&:to_date).max
@due_date = options[:due_date].to_date if options[:due_date]
@show_ideal_effort = options[:date_from] && options[:date_to]
@due_date = options[:due_date].to_date if options[:due_date]
@show_ideal_effort = options[:date_from] && options[:date_to]
super data_scope, options
super data_scope, options
@fields = [''] + @fields
@y_title = l(:label_agile_charts_number_of_issues)
@graph_title = l(:label_agile_charts_issues_burndown)
@line_colors = { :work => '80,122,170', :ideal => '102,102,102', :total => '80,122,170' }
end
def data
return false unless calculate_burndown_data.any?
datasets = [dataset(@burndown_data, l(:label_agile_actual_work_remaining), :fill => true, :color => line_colors[:work])]
if @show_ideal_effort
datasets << dataset(ideal_effort(@cumulative_burndown_data.first), l(:label_agile_ideal_work_remaining),
:color => line_colors[:ideal], :dashed => true, :nopoints => true)
end
if @show_ideal_effort && (@cumulative_burndown_data != @burndown_data)
datasets << dataset(@cumulative_burndown_data, l(:label_agile_total_work_remaining),
:color => line_colors[:total], :dashed => true)
@fields = [''] + @fields
@y_title = l(:label_agile_charts_number_of_issues)
@graph_title = l(:label_agile_charts_issues_burndown)
@line_colors = { :work => '80,122,170', :ideal => '102,102,102', :total => '80,122,170' }
end
{
:title => @graph_title,
:y_title => @y_title,
:labels => @fields,
:datasets => datasets,
:show_tooltips => [0, 2]
}
end
def data
return false unless calculate_burndown_data.any?
def self.data(data_scope, options = {})
if options[:chart_unit] == Charts::UNIT_HOURS
WorkBurndownChart.new(data_scope, options.merge(estimated_unit: ESTIMATE_HOURS)).data
elsif options[:chart_unit] == Charts::UNIT_STORY_POINTS
WorkBurndownChart.new(data_scope, options.merge(estimated_unit: ESTIMATE_STORY_POINTS)).data
else
super
datasets = [dataset(@burndown_data, l(:label_agile_actual_work_remaining), :fill => true, :color => line_colors[:work])]
if @show_ideal_effort
datasets << dataset(ideal_effort(@cumulative_burndown_data.first), l(:label_agile_ideal_work_remaining),
:color => line_colors[:ideal], :dashed => true, :nopoints => true)
end
if @show_ideal_effort && (@cumulative_burndown_data != @burndown_data)
datasets << dataset(@cumulative_burndown_data, l(:label_agile_total_work_remaining),
:color => line_colors[:total], :dashed => true)
end
{
:title => @graph_title,
:y_title => @y_title,
:labels => @fields,
:datasets => datasets,
:show_tooltips => [0, 2]
}
end
end
protected
def ideal_effort(start_remaining)
data = [0] * (due_date_period - 1)
active_periods = (RedmineAgile.exclude_weekends? && date_short_period?) ? due_date_period - @weekend_periods.select { |p| p < due_date_period }.count : due_date_period
avg_remaining_velocity = start_remaining.to_f / active_periods.to_f
sum = start_remaining.to_f
data[0] = sum
(1..due_date_period - 1).each do |i|
sum -= avg_remaining_velocity unless (RedmineAgile.exclude_weekends? && date_short_period?) && @weekend_periods.include?(i - 1)
data[i] = (sum * 100).round / 100.0
def self.data(data_scope, options = {})
if options[:chart_unit] == Helper::UNIT_HOURS
WorkBurndownChart.new(data_scope, options.merge(estimated_unit: ESTIMATE_HOURS)).data
elsif options[:chart_unit] == Helper::UNIT_STORY_POINTS
WorkBurndownChart.new(data_scope, options.merge(estimated_unit: ESTIMATE_STORY_POINTS)).data
else
super
end
end
data[due_date_period] = 0
data
end
def calculate_burndown_data
created_by_period = issues_count_by_period(scope_by_created_date)
closed_by_period = issues_count_by_period(scope_by_closed_date)
protected
total_issues = @data_scope.count
total_issues_before = @data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).count
total_closed_before = @data_scope.open(false).where("#{Issue.table_name}.closed_on < ?", @date_from).count
def ideal_effort(start_remaining)
data = [0] * (due_date_period - 1)
active_periods = (RedmineAgile.exclude_weekends? && date_short_period?) ? due_date_period - @weekend_periods.select { |p| p < due_date_period }.count : due_date_period
avg_remaining_velocity = start_remaining.to_f / active_periods.to_f
sum = start_remaining.to_f
data[0] = sum
(1..due_date_period - 1).each do |i|
sum -= avg_remaining_velocity unless (RedmineAgile.exclude_weekends? && date_short_period?) && @weekend_periods.include?(i - 1)
data[i] = (sum * 100).round / 100.0
end
data[due_date_period] = 0
data
end
sum = total_issues_before
cumulative_created_by_period = created_by_period.first(current_date_period).map { |x| sum += x }
sum = total_closed_before
cumulative_closed_by_period = closed_by_period.first(current_date_period).map { |x| sum += x }
def calculate_burndown_data
created_by_period = issues_count_by_period(scope_by_created_date)
closed_by_period = issues_count_by_period(scope_by_closed_date)
burndown_by_period = [0] * (current_date_period)
cumulative_created_by_period.each_with_index { |e, i| burndown_by_period[i] = e - cumulative_closed_by_period[i] }
first_day_open_issues = @data_scope.where("#{Issue.table_name}.created_on < ?", @date_from + 1).count - total_closed_before
@cumulative_burndown_data = [total_issues - total_closed_before] + cumulative_closed_by_period.map { |c| total_issues - c }
@burndown_data = [first_day_open_issues] + burndown_by_period
total_issues = @data_scope.count
total_issues_before = @data_scope.where("#{Issue.table_name}.created_on < ?", @date_from).count
total_closed_before = @data_scope.open(false).where("#{Issue.table_name}.closed_on < ?", @date_from).count
sum = total_issues_before
cumulative_created_by_period = created_by_period.first(current_date_period).map { |x| sum += x }
sum = total_closed_before
cumulative_closed_by_period = closed_by_period.first(current_date_period).map { |x| sum += x }
burndown_by_period = [0] * (current_date_period)
cumulative_created_by_period.each_with_index { |e, i| burndown_by_period[i] = e - cumulative_closed_by_period[i] }
first_day_open_issues = @data_scope.where("#{Issue.table_name}.created_on < ?", @date_from + 1).count - total_closed_before
@cumulative_burndown_data = [total_issues - total_closed_before] + cumulative_closed_by_period.map { |c| total_issues - c }
@burndown_data = [first_day_open_issues] + burndown_by_period
end
end
end
end

View File

@ -1,98 +0,0 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_agile is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
module RedmineAgile
module Charts
BURNDOWN_CHART = 'burndown_chart'.freeze
ISSUES_BURNDOWN_CHART = 'issues_burndown'.freeze
WORK_BURNDOWN_SP_CHART = 'work_burndown_sp'.freeze
WORK_BURNDOWN_HOURS_CHART = 'work_burndown_hours'.freeze
AGILE_CHARTS = {
BURNDOWN_CHART => { name: :label_agile_chart_burndown, class: BurndownChart,
aliases: [ISSUES_BURNDOWN_CHART, WORK_BURNDOWN_HOURS_CHART, WORK_BURNDOWN_SP_CHART] },
}.freeze
CHARTS_WITH_UNITS = [
BURNDOWN_CHART,
].freeze
UNIT_ISSUES = 'issues'.freeze
UNIT_STORY_POINTS = 'story_points'.freeze
UNIT_HOURS = 'hours'.freeze
CHART_UNITS = {
UNIT_ISSUES => :label_issue_plural,
UNIT_STORY_POINTS => :label_agile_story_points,
UNIT_HOURS => :label_agile_hours
}.freeze
CHART_UNIT_BY_ALIAS = {
ISSUES_BURNDOWN_CHART => UNIT_ISSUES,
WORK_BURNDOWN_HOURS_CHART => UNIT_HOURS,
WORK_BURNDOWN_SP_CHART => UNIT_STORY_POINTS,
}
def self.valid_chart?(name) !!AGILE_CHARTS[name] end
def self.chart_by_alias(alias_name)
chart_name = nil
AGILE_CHARTS.each do |chart, value|
if value[:aliases] && value[:aliases].include?(alias_name)
chart_name = chart
break
end
end
chart_name
end
def self.valid_chart_name_by(old_chart_name)
if valid_chart?(old_chart_name)
old_chart_name
elsif (chart_by_alias = chart_by_alias(old_chart_name))
chart_by_alias
else
BURNDOWN_CHART
end
end
def self.valid_chart_unit?(name) !!CHART_UNITS[name] end
def self.chart_with_units?(old_chart_name)
CHARTS_WITH_UNITS.include? valid_chart_name_by(old_chart_name)
end
def self.valid_chart_unit_by(old_chart_name, chart_unit)
if chart_with_units?(old_chart_name)
if chart_by_alias(old_chart_name)
CHART_UNIT_BY_ALIAS[old_chart_name]
elsif valid_chart_unit?(chart_unit)
chart_unit
else
UNIT_ISSUES
end
end
end
def self.chart_unit_label_by(alias_name)
chart_unit = CHART_UNIT_BY_ALIAS[alias_name]
CHART_UNITS[chart_unit]
end
end
end

View File

@ -0,0 +1,100 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_agile is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
module RedmineAgile
module Charts
class Helper
BURNDOWN_CHART = 'burndown_chart'.freeze
ISSUES_BURNDOWN_CHART = 'issues_burndown'.freeze
WORK_BURNDOWN_SP_CHART = 'work_burndown_sp'.freeze
WORK_BURNDOWN_HOURS_CHART = 'work_burndown_hours'.freeze
AGILE_CHARTS = {
BURNDOWN_CHART => { name: :label_agile_chart_burndown, class: BurndownChart,
aliases: [ISSUES_BURNDOWN_CHART, WORK_BURNDOWN_HOURS_CHART, WORK_BURNDOWN_SP_CHART] },
}.freeze
CHARTS_WITH_UNITS = [
BURNDOWN_CHART,
].freeze
UNIT_ISSUES = 'issues'.freeze
UNIT_STORY_POINTS = 'story_points'.freeze
UNIT_HOURS = 'hours'.freeze
CHART_UNITS = {
UNIT_ISSUES => :label_issue_plural,
UNIT_STORY_POINTS => :label_agile_story_points,
UNIT_HOURS => :label_agile_hours
}.freeze
CHART_UNIT_BY_ALIAS = {
ISSUES_BURNDOWN_CHART => UNIT_ISSUES,
WORK_BURNDOWN_HOURS_CHART => UNIT_HOURS,
WORK_BURNDOWN_SP_CHART => UNIT_STORY_POINTS,
}
def self.valid_chart?(name) !!AGILE_CHARTS[name] end
def self.chart_by_alias(alias_name)
chart_name = nil
AGILE_CHARTS.each do |chart, value|
if value[:aliases] && value[:aliases].include?(alias_name)
chart_name = chart
break
end
end
chart_name
end
def self.valid_chart_name_by(old_chart_name)
if valid_chart?(old_chart_name)
old_chart_name
elsif (chart_by_alias = chart_by_alias(old_chart_name))
chart_by_alias
else
BURNDOWN_CHART
end
end
def self.valid_chart_unit?(name) !!CHART_UNITS[name] end
def self.chart_with_units?(old_chart_name)
CHARTS_WITH_UNITS.include? valid_chart_name_by(old_chart_name)
end
def self.valid_chart_unit_by(old_chart_name, chart_unit)
if chart_with_units?(old_chart_name)
if chart_by_alias(old_chart_name)
CHART_UNIT_BY_ALIAS[old_chart_name]
elsif valid_chart_unit?(chart_unit)
chart_unit
else
UNIT_ISSUES
end
end
end
def self.chart_unit_label_by(alias_name)
chart_unit = CHART_UNIT_BY_ALIAS[alias_name]
CHART_UNITS[chart_unit]
end
end
end
end

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -18,52 +18,54 @@
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
module RedmineAgile
class WorkBurndownChart < BurndownChart
def initialize(data_scope, options = {})
super data_scope, options
if @estimated_unit == 'hours'
@y_title = l(:label_agile_charts_number_of_hours)
@graph_title = l(:label_agile_charts_work_burndown_hours)
else
@y_title = l(:label_agile_charts_number_of_story_points)
@graph_title = l(:label_agile_charts_work_burndown_sp)
module Charts
class WorkBurndownChart < BurndownChart
def initialize(data_scope, options = {})
super data_scope, options
if @estimated_unit == 'hours'
@y_title = l(:label_agile_charts_number_of_hours)
@graph_title = l(:label_agile_charts_work_burndown_hours)
else
@y_title = l(:label_agile_charts_number_of_story_points)
@graph_title = l(:label_agile_charts_work_burndown_sp)
end
@line_colors = { work: '0,153,0', ideal: '102,102,102', total: '0,153,0' }
end
@line_colors = { work: '0,153,0', ideal: '102,102,102', total: '0,153,0' }
end
protected
protected
def calculate_burndown_data
data_scope = @data_scope
data_scope = data_scope.where("#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1") if use_subissue_done_ratio && @estimated_unit == 'hours'
def calculate_burndown_data
data_scope = @data_scope
data_scope = data_scope.where("#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1") if use_subissue_done_ratio && @estimated_unit == 'hours'
if @estimated_unit == 'hours'
all_issues = data_scope.where("#{Issue.table_name}.estimated_hours IS NOT NULL").
eager_load([:journals, :status, { journals: { details: :journal } }])
cumulative_total_hours = data_scope.sum("#{Issue.table_name}.estimated_hours").to_f
else
all_issues = data_scope.where("#{AgileData.table_name}.story_points IS NOT NULL").
joins(:agile_data).eager_load([:journals, :status, { journals: { details: :journal } }])
cumulative_total_hours = data_scope.joins(:agile_data).sum("#{AgileData.table_name}.story_points").to_f
end
if @estimated_unit == 'hours'
all_issues = data_scope.where("#{Issue.table_name}.estimated_hours IS NOT NULL").
eager_load([:journals, :status, { journals: { details: :journal } }])
cumulative_total_hours = data_scope.sum("#{Issue.table_name}.estimated_hours").to_f
else
all_issues = data_scope.where("#{AgileData.table_name}.story_points IS NOT NULL").
joins(:agile_data).eager_load([:journals, :status, { journals: { details: :journal } }])
cumulative_total_hours = data_scope.joins(:agile_data).sum("#{AgileData.table_name}.story_points").to_f
data = chart_dates_by_period.select { |d| d <= Date.today }.map do |date|
issues = all_issues.select { |issue| issue.created_on.localtime.to_date <= date }
total_hours_left, cumulative_total_hours_left = date_effort(issues, date)
[total_hours_left, cumulative_total_hours - cumulative_total_hours_left]
end
tail_values = data.last ? [data.last] * (current_date_period - data.size) : []
data = first_period_effort(all_issues, chart_dates_by_period.first, cumulative_total_hours) + data + tail_values
@burndown_data, @cumulative_burndown_data = data.transpose
end
data = chart_dates_by_period.select { |d| d <= Date.today }.map do |date|
issues = all_issues.select { |issue| issue.created_on.localtime.to_date <= date }
total_hours_left, cumulative_total_hours_left = date_effort(issues, date)
[total_hours_left, cumulative_total_hours - cumulative_total_hours_left]
private
def first_period_effort(issues_scope, start_date, cumulative_total_hours)
issues = issues_scope.select { |issue| issue.created_on.localtime.to_date <= start_date }
total_left, cumulative_left = date_effort(issues, start_date - 1)
[[total_left, cumulative_total_hours - cumulative_left]]
end
tail_values = data.last ? [data.last] * (current_date_period - data.size) : []
data = first_period_effort(all_issues, chart_dates_by_period.first, cumulative_total_hours) + data + tail_values
@burndown_data, @cumulative_burndown_data = data.transpose
end
private
def first_period_effort(issues_scope, start_date, cumulative_total_hours)
issues = issues_scope.select { |issue| issue.created_on.localtime.to_date <= start_date }
total_left, cumulative_left = date_effort(issues, start_date - 1)
[[total_left, cumulative_total_hours - cumulative_left]]
end
end
end

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -20,134 +20,136 @@
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
module RedmineAgile
module AgileHelper
module Helpers
module AgileHelper
def retrieve_agile_query_from_session
if session[:agile_query]
if session[:agile_query][:id]
@query = AgileQuery.find_by_id(session[:agile_query][:id])
return unless @query
else
@query = AgileQuery.new(get_query_attributes_from_session)
end
if session[:agile_query].has_key?(:project_id)
@query.project_id = session[:agile_query][:project_id]
else
@query.project = @project
end
@query
else
@query = AgileQuery.new(:name => "_")
end
end
def retrieve_agile_query
if !params[:query_id].blank?
cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project
@query = AgileQuery.where(cond).find(params[:query_id])
raise ::Unauthorized unless @query.visible?
@query.project = @project
session[:agile_query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear
elsif api_request? || params[:set_filter] || session[:agile_query].nil? || session[:agile_query][:project_id] != (@project ? @project.id : nil)
unless @query
@query = AgileQuery.new(:name => "_", :project => @project)
@query.build_from_params(params)
else
@query.project = @project if @project
end
save_query_attribures_to_session(@query)
else
# retrieve from session
@query = nil
if session[:agile_query] && !session[:agile_query][:id] && !params[:project_id]
@query = AgileQuery.new(get_query_attributes_from_session)
end
@query ||= AgileQuery.find_by_id(session[:agile_query][:id]) if session[:agile_query][:id]
@query ||= AgileQuery.new(get_query_attributes_from_session)
@query.project = @project
save_query_attribures_to_session(@query)
end
end
def options_card_colors_for_select(selected, options={})
color_base = [[l(:label_agile_color_no_colors), "none"],
[l(:label_issue), "issue"],
[l(:label_tracker), "tracker"],
[l(:field_priority), "priority"],
[l(:label_spent_time), "spent_time"],
[l(:field_assigned_to), "user"]]
color_base << [l(:field_project), 'project'] if (@project && @project.children.any?) || !@project
options_for_select(color_base.compact, selected)
end
def options_charts_for_select(selected)
container = []
RedmineAgile::Charts::AGILE_CHARTS.each { |k, v| container << [l(v[:name]), k] }
selected_chart = RedmineAgile::Charts.chart_by_alias(selected) || selected
options_for_select(container, selected_chart)
end
def grouped_options_charts_for_select(selected)
grouped_options = {}
container = []
RedmineAgile::Charts::AGILE_CHARTS.each do |chart, value|
if RedmineAgile::Charts::CHARTS_WITH_UNITS.include?(chart)
group = l(value[:name])
grouped_options[group] = []
value[:aliases].each do |alias_name|
grouped_options[group] << ["#{group} (#{l(RedmineAgile::Charts.chart_unit_label_by(alias_name))})", alias_name]
def retrieve_agile_query_from_session
if session[:agile_query]
if session[:agile_query][:id]
@query = AgileQuery.find_by_id(session[:agile_query][:id])
return unless @query
else
@query = AgileQuery.new(get_query_attributes_from_session)
end
if session[:agile_query].has_key?(:project_id)
@query.project_id = session[:agile_query][:project_id]
else
@query.project = @project
end
@query
else
container << [l(value[:name]), chart]
@query = AgileQuery.new(:name => "_")
end
end
grouped_options_for_select(grouped_options, selected) + options_for_select(container, selected)
end
def retrieve_agile_query
if !params[:query_id].blank?
cond = "project_id IS NULL"
cond << " OR project_id = #{@project.id}" if @project
@query = AgileQuery.where(cond).find(params[:query_id])
raise ::Unauthorized unless @query.visible?
@query.project = @project
session[:agile_query] = {:id => @query.id, :project_id => @query.project_id}
sort_clear
elsif api_request? || params[:set_filter] || session[:agile_query].nil? || session[:agile_query][:project_id] != (@project ? @project.id : nil)
unless @query
@query = AgileQuery.new(:name => "_", :project => @project)
@query.build_from_params(params)
else
@query.project = @project if @project
end
save_query_attribures_to_session(@query)
else
# retrieve from session
@query = nil
if session[:agile_query] && !session[:agile_query][:id] && !params[:project_id]
@query = AgileQuery.new(get_query_attributes_from_session)
end
def options_chart_units_for_select(selected = nil)
container = []
RedmineAgile::Charts::CHART_UNITS.each { |k, v| container << [l(v), k] }
selected_unit = RedmineAgile::Charts::CHART_UNIT_BY_ALIAS[selected] || selected
options_for_select(container, selected_unit)
end
@query ||= AgileQuery.find_by_id(session[:agile_query][:id]) if session[:agile_query][:id]
@query ||= AgileQuery.new(get_query_attributes_from_session)
@query.project = @project
save_query_attribures_to_session(@query)
end
end
def render_agile_chart(chart_name, issues_scope)
render partial: "agile_charts/chart",
locals: { chart: chart_name, issues_scope: issues_scope, chart_unit: params[:chart_unit] }
end
def options_card_colors_for_select(selected, options={})
color_base = [[l(:label_agile_color_no_colors), "none"],
[l(:label_issue), "issue"],
[l(:label_tracker), "tracker"],
[l(:field_priority), "priority"],
[l(:label_spent_time), "spent_time"],
[l(:field_assigned_to), "user"]]
color_base << [l(:field_project), 'project'] if (@project && @project.children.any?) || !@project
options_for_select(color_base.compact, selected)
end
def upgrade_to_pro_agile_chart_link(chart_name, query = @query, current_chart = @chart)
link_to l("label_agile_charts_#{chart_name}"), '#',
onclick: "showModal('upgrade-to-pro', '557px');",
class: ('selected' if query.is_a?(AgileChartsQuery) && query.new_record? && current_chart == chart_name)
end
def options_charts_for_select(selected)
container = []
RedmineAgile::Charts::Helper::AGILE_CHARTS.each { |k, v| container << [l(v[:name]), k] }
selected_chart = RedmineAgile::Charts::Helper.chart_by_alias(selected) || selected
options_for_select(container, selected_chart)
end
private
def grouped_options_charts_for_select(selected)
grouped_options = {}
container = []
def get_query_attributes_from_session
attributes = { name: '_',
filters: session[:agile_query][:filters],
group_by: session[:agile_query][:group_by],
column_names: session[:agile_query][:column_names],
color_base: session[:agile_query][:color_base] }
(attributes[:options] = session[:agile_query][:options] || {}) if Redmine::VERSION.to_s > '2.4'
attributes
end
RedmineAgile::Charts::Helper::AGILE_CHARTS.each do |chart, value|
if RedmineAgile::Charts::Helper::CHARTS_WITH_UNITS.include?(chart)
group = l(value[:name])
grouped_options[group] = []
value[:aliases].each do |alias_name|
grouped_options[group] << ["#{group} (#{l(RedmineAgile::Charts::Helper.chart_unit_label_by(alias_name))})", alias_name]
end
else
container << [l(value[:name]), chart]
end
end
def save_query_attribures_to_session(query)
session[:agile_query] = { project_id: query.project_id,
filters: query.filters,
group_by: query.group_by,
color_base: (query.respond_to?(:color_base) && query.color_base),
column_names: query.column_names }
(session[:agile_query][:options] = query.options) if Redmine::VERSION.to_s > '2.4'
grouped_options_for_select(grouped_options, selected) + options_for_select(container, selected)
end
def options_chart_units_for_select(selected = nil)
container = []
RedmineAgile::Charts::Helper::CHART_UNITS.each { |k, v| container << [l(v), k] }
selected_unit = RedmineAgile::Charts::Helper::CHART_UNIT_BY_ALIAS[selected] || selected
options_for_select(container, selected_unit)
end
def render_agile_chart(chart_name, issues_scope)
render partial: "agile_charts/chart",
locals: { chart: chart_name, issues_scope: issues_scope, chart_unit: params[:chart_unit] }
end
def upgrade_to_pro_agile_chart_link(chart_name, query = @query, current_chart = @chart)
link_to l("label_agile_charts_#{chart_name}"), '#',
onclick: "showModal('upgrade-to-pro', '557px');",
class: ('selected' if query.is_a?(AgileChartsQuery) && query.new_record? && current_chart == chart_name)
end
private
def get_query_attributes_from_session
attributes = { name: '_',
filters: session[:agile_query][:filters],
group_by: session[:agile_query][:group_by],
column_names: session[:agile_query][:column_names],
color_base: session[:agile_query][:color_base] }
(attributes[:options] = session[:agile_query][:options] || {}) if Redmine::VERSION.to_s > '2.4'
attributes
end
def save_query_attribures_to_session(query)
session[:agile_query] = { project_id: query.project_id,
filters: query.filters,
group_by: query.group_by,
color_base: (query.respond_to?(:color_base) && query.color_base),
column_names: query.column_names }
(session[:agile_query][:options] = query.options) if Redmine::VERSION.to_s > '2.4'
end
end
end
end
ActionView::Base.send :include, RedmineAgile::AgileHelper
ActionView::Base.send :include, RedmineAgile::Helpers::AgileHelper

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -17,9 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with redmine_agile. If not, see <http://www.gnu.org/licenses/>.
require_dependency 'issue'
require_dependency 'agile_data'
module RedmineAgile
module Patches

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -53,8 +53,8 @@ class AgileChartsControllerTest < ActionController::TestCase
EnabledModule.create(project: @project, name: 'agile')
@charts = RedmineAgile::Charts::AGILE_CHARTS.keys
@charts_with_units = RedmineAgile::Charts::CHARTS_WITH_UNITS
@charts = RedmineAgile::Charts::Helper::AGILE_CHARTS.keys
@charts_with_units = RedmineAgile::Charts::Helper::CHARTS_WITH_UNITS
end
def test_get_show
@ -73,7 +73,7 @@ class AgileChartsControllerTest < ActionController::TestCase
def test_charts_with_chart_unit
@charts_with_units.each do |chart|
RedmineAgile::Charts::CHART_UNITS.each do |chart_unit, label|
RedmineAgile::Charts::Helper::CHART_UNITS.each do |chart_unit, label|
check_chart chart: chart, project_id: @project.identifier, chart_unit: chart_unit
end
end
@ -81,7 +81,7 @@ class AgileChartsControllerTest < ActionController::TestCase
def test_charts_by_different_time_intervals
@charts.each do |chart|
RedmineAgile::AgileChart::TIME_INTERVALS.each do |interval|
RedmineAgile::Charts::AgileChart::TIME_INTERVALS.each do |interval|
check_chart chart: chart, project_id: @project.identifier, interval_size: interval
end
end
@ -89,7 +89,7 @@ class AgileChartsControllerTest < ActionController::TestCase
def test_charts_by_different_periods_and_time_intervals
@charts.each do |chart|
RedmineAgile::AgileChart::TIME_INTERVALS.each do |interval|
RedmineAgile::Charts::AgileChart::TIME_INTERVALS.each do |interval|
params = {
chart: chart,
project_id: @project.identifier,
@ -135,7 +135,7 @@ class AgileChartsControllerTest < ActionController::TestCase
def test_charts_with_version_and_chart_unit
@charts_with_units.each do |chart|
RedmineAgile::Charts::CHART_UNITS.each do |chart_unit, label|
RedmineAgile::Charts::Helper::CHART_UNITS.each do |chart_unit, label|
should_get_render_chart chart: chart, version_id: 2, chart_unit: chart_unit
end
end
@ -152,7 +152,7 @@ class AgileChartsControllerTest < ActionController::TestCase
)
new_version.fixed_issues << issue.reload
should_get_render_chart chart: RedmineAgile::Charts::BURNDOWN_CHART, project_id: @project.identifier, version_id: new_version.id
should_get_render_chart chart: RedmineAgile::Charts::Helper::BURNDOWN_CHART, project_id: @project.identifier, version_id: new_version.id
end
def test_get_show_chart_with_open_target_version

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify
@ -38,7 +38,7 @@ class BurndownChartTest < ActiveSupport::TestCase
test_case_issues = test_case[:issues].call
test_case[:inerval_data].each do |case_interval|
puts "BurndownChartTest case - #{case_interval[:name]}"
chart_data = RedmineAgile::BurndownChart.data(test_case_issues, case_interval[:options])
chart_data = RedmineAgile::Charts::BurndownChart.data(test_case_issues, case_interval[:options])
assert_equal case_interval[:result], extract_values(chart_data)
end
test_case_issues.destroy_all

View File

@ -3,7 +3,7 @@
# This file is a part of Redmin Agile (redmine_agile) plugin,
# Agile board plugin for redmine
#
# Copyright (C) 2011-2021 RedmineUP
# Copyright (C) 2011-2022 RedmineUP
# http://www.redmineup.com/
#
# redmine_agile is free software: you can redistribute it and/or modify