Import archive redmine_agile-1_6_1-light

This commit is contained in:
Alexandr Antonov 2021-05-31 12:40:41 +03:00
commit 58bb78fea5
119 changed files with 11018 additions and 0 deletions

1
Gemfile Normal file
View File

@ -0,0 +1 @@
gem "redmine_crm"

19
README.rdoc Executable file
View File

@ -0,0 +1,19 @@
= Agile plugin
== Install
* Copy redmine_agile plugin to {RAILS_APP}/plugins on your redmine path
* Run bundle install --without development test RAILS_ENV=production
* Run bundle exec rake redmine:plugins NAME=redmine_agile RAILS_ENV=production
== Uninstall
<pre>
bundle exec rake redmine:plugins NAME=redmine_agile VERSION=0 RAILS_ENV=production
rm -r plugins/redmine_agile
</pre>
== Test
bundle exec rake db:drop db:create db:migrate redmine:plugins RAILS_ENV=test
bundle exec rake test TEST="plugins/redmine_agile/test/**/*_test.rb" RAILS_ENV=test

View File

@ -0,0 +1,158 @@
# 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/>.
class AgileBoardsController < ApplicationController
unloadable
menu_item :agile
before_action :find_issue, only: [:update, :issue_tooltip, :inline_comment, :edit_issue, :update_issue, :agile_data]
before_action :find_optional_project, only: [
:index,
:create_issue,
]
before_action :authorize, except: [:index, :edit_issue, :update_issue]
accept_api_auth :agile_data
helper :issues
helper :journals
helper :projects
include ProjectsHelper
helper :custom_fields
include CustomFieldsHelper
helper :issue_relations
include IssueRelationsHelper
helper :watchers
include WatchersHelper
helper :attachments
include AttachmentsHelper
helper :queries
include QueriesHelper
helper :repositories
include RepositoriesHelper
helper :sort
include SortHelper
include IssuesHelper
helper :timelog
include RedmineAgile::AgileHelper
helper :checklists if RedmineAgile.use_checklist?
def index
retrieve_agile_query
if @query.valid?
@issues = @query.issues
@issue_board = @query.issue_board
@board_columns = @query.board_statuses
@allowed_statuses = statuses_allowed_for_create
respond_to do |format|
format.html { render :template => 'agile_boards/index', :layout => !request.xhr? }
format.js
end
else
respond_to do |format|
format.html { render(:template => 'agile_boards/index', :layout => !request.xhr?) }
format.js
end
end
rescue ActiveRecord::RecordNotFound
render_404
end
def update
(render_403; return false) unless @issue.editable?
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|
if @issue.attributes.include?(attribute.first)
total &&= @issue.attributes[attribute.first].to_i == attribute.last.to_i
else
total &&= true
end
end
call_hook(:controller_agile_boards_update_before_save, { params: params, issue: @issue})
@update = true
if saved && @issue.save
call_hook(:controller_agile_boards_update_after_save, { :params => params, :issue => @issue})
AgileData.transaction do
Issue.eager_load(:agile_data).find(params[:positions].keys).each do |issue|
issue.agile_data.position = params[:positions][issue.id.to_s]['position']
issue.agile_data.save
end
end if params[:positions]
@inline_adding = params[:issue][:notes] || nil
respond_to do |format|
format.html { render(:partial => 'issue_card', :locals => {:issue => @issue}, :status => :ok, :layout => nil) }
end
else
respond_to do |format|
messages = @issue.errors.full_messages
messages = [l(:text_agile_move_not_possible)] if messages.empty?
format.html {
render json: messages, status: :unprocessable_entity, layout: nil
}
end
end
end
def issue_tooltip
render :partial => 'issue_tooltip'
end
def inline_comment
render 'inline_comment', :layout => nil
end
def agile_data
@agile_data = @issue.agile_data
return render_404 unless @agile_data
respond_to do |format|
format.any { head :ok }
format.api { }
end
end
private
def auto_assign_on_move?
RedmineAgile.auto_assign_on_move? && @issue.assigned_to.nil? &&
!params[:issue].keys.include?('assigned_to_id') &&
@issue.status_id != params[:issue]['status_id'].to_i
end
def statuses_allowed_for_create
issue = Issue.new(project: @project)
issue.tracker = issue_tracker(issue)
issue.new_statuses_allowed_to
end
def issue_tracker(issue)
return issue.allowed_target_trackers.first if issue.respond_to?(:allowed_target_trackers)
return @project.trackers.first if @project
nil
end
end

View File

@ -0,0 +1,140 @@
# 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/>.
class AgileChartsController < ApplicationController
unloadable
menu_item :agile
before_action :find_optional_project, :only => [:show, :render_chart]
before_action :find_optional_version, :only => [:render_chart, :select_version_chart]
helper :issues
helper :journals
helper :projects
include ProjectsHelper
helper :custom_fields
include CustomFieldsHelper
helper :issue_relations
include IssueRelationsHelper
helper :watchers
include WatchersHelper
helper :attachments
include AttachmentsHelper
helper :queries
include QueriesHelper
helper :repositories
include RepositoriesHelper
helper :sort
include SortHelper
include IssuesHelper
helper :timelog
include RedmineAgile::AgileHelper
def show
retrieve_charts_query
@query.date_to ||= Date.today
@issues = @query.issues
respond_to do |format|
format.html
end
end
def render_chart
if @version
@issues = @version.fixed_issues
options = { date_from: @version.start_date,
date_to: [@version.due_date,
@issues.maximum(:due_date),
@issues.maximum(:updated_on)].compact.max,
due_date: @version.due_date || @issues.maximum(:due_date) || @issues.maximum(:updated_on),
chart_unit: params[:chart_unit] }
@chart = params[:chart]
else
retrieve_charts_query
@issues = Issue.visible
@issues = @issues.joins(:fixed_version) if @query.filters.keys.include?('version_status')
@issues = @issues.where(@query.statement)
options = { date_from: @query.date_from,
date_to: @query.date_to,
interval_size: @query.interval_size,
chart_unit: @query.chart_unit }
end
render_data(options)
end
def select_version_chart
end
private
def render_data(options = {})
agile_chart = RedmineAgile::Charts::AGILE_CHARTS[@chart]
data = agile_chart[:class].data(@issues, options) if agile_chart
if data
data[:chart] = @chart
data[:chart_unit] = options[:chart_unit]
return render json: data
end
raise ActiveRecord::RecordNotFound
end
def find_optional_version
@version = Version.find(params[:version_id]) if params[:version_id]
rescue ActiveRecord::RecordNotFound
render_404
end
def retrieve_charts_query
if params[:query_id].present?
@query = AgileChartsQuery.find(params[:query_id])
raise ::Unauthorized unless @query.visible?
@query.project = @project
elsif params[:set_filter] || session[:agile_charts_query].nil? || session[:agile_charts_query][:project_id] != (@project ? @project.id : nil)
# Give it a name, required to be valid
@query = AgileChartsQuery.new(:name => '_')
@query.project = @project
@query.build_from_params(params)
session[:agile_charts_query] = { project_id: @query.project_id,
filters: @query.filters,
group_by: @query.group_by,
column_names: @query.column_names,
date_from: @query.date_from,
date_to: @query.date_to,
interval_size: @query.interval_size,
chart: @query.chart,
chart_unit: @query.chart_unit }
else
# retrieve from session
@query = AgileChartsQuery.new(name: '_',
filters: session[:agile_charts_query][:filters] || session[:agile_query][:filters],
group_by: session[:agile_charts_query][:group_by],
column_names: session[:agile_charts_query][:column_names],
date_from: session[:agile_charts_query][:date_from],
date_to: session[:agile_charts_query][:date_to],
interval_size: session[:agile_charts_query][:interval_size],
chart: session[:agile_charts_query][:chart],
chart_unit: session[:agile_charts_query][:chart_unit])
@query.project = @project
end
@chart = params[:chart] || @query.chart
end
end

View File

@ -0,0 +1,21 @@
# 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/>.
class AgileChartsQueriesController < ApplicationController
end

View File

@ -0,0 +1,71 @@
# 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/>.
class AgileJournalDetailsController < ApplicationController
unloadable
before_action :find_issue
helper :issues
helper :agile_support
include AgileSupportHelper
def done_ratio
@done_ratios = @issue.journals.map(&:details).flatten.select {|detail| 'done_ratio' == detail.prop_key }.sort_by {|a| a.journal.created_on }
@done_ratios.unshift(JournalDetail.new(:property => 'attr', :prop_key => 'done_ratio', :value => history_initial_value(@done_ratios) || @issue.done_ratio,
:journal => Journal.new(:user => @issue.author, :created_on => @issue.created_on)))
end
def status
@statuses_collector = AgileStatusesCollector.new(@issue)
@group = params[:group_by] if params[:group_by].present?
respond_to do |format|
format.html
format.csv { send_data(issue_statuses_to_csv(@statuses_collector), type: 'text/csv; header=present', filename: "issue_#{@issue.id}_statuses.csv") }
end
end
def assignee
@assignees = @issue.journals.map(&:details).flatten.select {|detail| 'assigned_to_id' == detail.prop_key }.sort_by {|a| a.journal.created_on }
@assignees.unshift(JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id', :value => history_initial_value(@assignees) || @issue.assigned_to_id,
:journal => Journal.new(:user => @issue.author, :created_on => @issue.created_on)))
end
def edit
end
def new
end
private
def find_issue
@issue = Issue.eager_load(:journals => :details).find(params[:issue_id])
raise Unauthorized unless @issue.visible?
@project = @issue.project
rescue ActiveRecord::RecordNotFound
render_404
end
def history_initial_value(journals)
return nil unless journals.present?
journals.first.old_value
end
end

View File

@ -0,0 +1,159 @@
# encoding: utf-8
#
# 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 AgileBoardsHelper
def agile_color_class(issue, options={})
''
end
def agile_user_color(user, options={})
end
def header_th(name, rowspan = 1, colspan = 1, leaf = nil)
th_attributes = {}
if leaf
# th_attributes[:style] = ""
th_attributes[:"data-column-id"] = leaf.id
issue_count = leaf.instance_variable_get("@issue_count") || 0
count_tag = " (#{content_tag(:span, issue_count.to_i, :class => 'count')})".html_safe
# estimated hours total
story_points_count = leaf.instance_variable_get("@story_points") || 0
hours_count = leaf.instance_variable_get("@estimated_hours_sum") || 0
values = []
values << '%.2fh' % hours_count.to_f if hours_count > 0
values << "#{story_points_count}sp" if story_points_count > 0
if values.present?
hours_tag = content_tag(:span, values.join('/').html_safe, class: 'hours', title: l(:field_estimated_hours))
end
end
content_tag :th, h(name) + count_tag + hours_tag, th_attributes
end
def render_board_headers(columns)
"<tr>#{columns.map{|column| header_th(column.name, 1, 1, column)}.join}</tr>".html_safe
end
def color_by_name(name)
"##{"%06x" % (name.unpack('H*').first.hex % 0xffffff)}"
end
def render_board_fields_selection(query)
query.available_inline_columns.reject(&:frozen?).reject{ |c| c.name == :story_points && !RedmineAgile.use_story_points? }.map do |column|
label_tag('', check_box_tag('c[]', column.name, query.columns.include?(column)) + column.caption, :class => "floating" )
end.join(" ").html_safe
end
def render_board_fields_status(query)
available_statuses = Redmine::VERSION.to_s >= '3.4' && @project ? @project.rolled_up_statuses : IssueStatus.sorted
current_statuses = query.options[:f_status] || IssueStatus.where(:is_closed => false).pluck(:id).map(&:to_s)
wp = query.options[:wp] || {}
status_tags = available_statuses.map do |status|
label_tag('', check_box_tag('f_status[]', status.id, current_statuses.include?(status.id.to_s)
) + status.to_s, :class => 'floating')
end.join(' ').html_safe
hidden_field_tag('f[]', 'status_id').html_safe +
hidden_field_tag('op[status_id]', "=").html_safe +
status_tags
end
def render_issue_card_hours(query, issue)
hours = []
hours << "%.2f" % issue.total_spent_hours.to_f if query.has_column_name?(:spent_hours) && issue.total_spent_hours > 0
hours << "%.2f" % issue.estimated_hours.to_f if query.has_column_name?(:estimated_hours) && issue.estimated_hours
hours = [hours.join('/') + "h"] unless hours.blank?
hours << "#{issue.story_points}sp" if RedmineAgile.use_story_points? && query.has_column_name?(:story_points) && issue.story_points
content_tag(:span, "(#{hours.join('/')})", :class => 'hours') unless hours.blank?
end
def agile_progress_bar(pcts, options={})
pcts = [pcts, pcts] unless pcts.is_a?(Array)
pcts = pcts.collect(&:round)
pcts[1] = pcts[1] - pcts[0]
pcts << (100 - pcts[1] - pcts[0])
width = options[:width] || '100px;'
legend = options[:legend] || ''
content_tag('table',
content_tag('tr',
(pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
(pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
(pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) +
(legend ? content_tag('td', content_tag('p', legend, :class => 'percent'), :class => 'legend') : ''.html_safe)
), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe
end
def issue_children(issue)
return unless issue.children.any?
content_tag :ul do
issue.children.select{ |x| x.visible? }.each do |child|
id = if @query.has_column_name?(:tracker) || @query.has_column_name?(:id) then "##{child.id}:&nbsp;" else '' end
concat "<li class='#{'task-closed' if child.closed?}'><a href='#{issue_path(child)}'>#{id}#{child.subject}</a></li>#{issue_children(child)}".html_safe
end
end
end
def time_in_state(distance=nil)
return "" if !distance || !(distance.is_a? Time)
distance = Time.now - distance
hours = distance/(3600)
return "#{I18n.t('datetime.distance_in_words.x_hours', :count => hours.to_i)}" if hours < 24
"#{I18n.t('datetime.distance_in_words.x_days', :count => (hours/24).to_i)}"
end
def class_for_closed_issue(issue, is_version_board)
return '' if !RedmineAgile.hide_closed_issues_data? && !is_version_board
return 'closed-issue' if issue.closed?
''
end
def init_agile_tooltip_info(options={})
js_code = "function callGetToolTipInfo()
{
var url = '#{issue_tooltip_url}';
agileBoard.getToolTipInfo(this, url);
}
$('.tooltip').mouseenter(callGetToolTipInfo);
"
return js_code.html_safe if options[:only_code]
javascript_tag(js_code)
end
def estimated_value(issue)
return (issue.story_points || 0) if RedmineAgile.use_story_points?
issue.estimated_hours.to_f || 0
end
def estimated_time_value(query, issue)
issue.estimated_hours.to_f if query.has_column_name?(:estimated_hours)
end
def story_points_value(query, issue)
issue.story_points.to_f if query.has_column_name?(:story_points) && RedmineAgile.use_story_points?
end
def show_checklist?(issue)
RedmineAgile.use_checklist? && issue.checklists.any? && User.current.allowed_to?(:view_checklists, issue.project)
rescue
false
end
end

View File

@ -0,0 +1,32 @@
# encoding: utf-8
#
# 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 AgileChartsHelper
def render_agile_charts_breadcrumb
links = []
links << link_to(l(:label_project_all), project_id: nil, issue_id: nil)
links << link_to(h(@project), project_id: @project, issue_id: nil) if @project
if @version
links << @version.visible? ? link_to(@version.name, version_path(@version)) : @version.name
end
breadcrumb links
end
end

View File

@ -0,0 +1,70 @@
# encoding: utf-8
#
# 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 AgileSupportHelper
include ActionView::Helpers::DateHelper
# Returns a h2 tag and sets the html title with the given arguments
def title(*args)
strings = args.map do |arg|
if arg.is_a?(Array) && arg.size >= 2
link_to(*arg)
else
h(arg.to_s)
end
end
html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
content_tag('h2', strings.join(' &#187; ').html_safe)
end
def event_duration(event, next_event)
end_time = next_event ? next_event.journal.created_on : Time.now
distance_of_time_in_words(end_time, event.journal.created_on).html_safe
end
def issue_statuses_to_csv(collector)
decimal_separator = l(:general_csv_decimal_separator)
encoding = 'utf-8'
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
headers = [ "#",
l(:field_created_on, locale: :en),
l(:field_status, locale: :en),
l(:field_duration, locale: :en),
l(:field_author, locale: :en),
l(:field_assigned_to, locale: :en)
]
csv << headers.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) }
collector.data.each_with_index do |data, index|
issue_status = IssueStatus.where(id: data.status_id).first
fields = [index + 1,
format_time(data.journal.created_on),
issue_status.name,
distance_of_time_in_words(data.end_time, data.start_time),
data.journal.user.name,
Principal.where(id: data.assigned_to_id).first.try(:name)
]
csv << fields.collect { |c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) }
end
end
export
end
end

View File

@ -0,0 +1,167 @@
# 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/>.
class AgileChartsQuery < AgileQuery
unloadable
validate :validate_query_dates
attr_writer :date_from, :date_to
def initialize(attributes = nil, *args)
super attributes
self.filters.delete('status_id')
self.filters['chart_period'] = { operator: 'm', values: [''] } unless has_filter?('chart_period')
end
self.operators_by_filter_type[:chart_period] = ['><', 'w', 'lw', 'l2w', 'm', 'lm', 'y']
def initialize_available_filters
super
add_available_filter 'chart_period', type: :date_past, name: l(:label_date)
end
def sprint_values
return [] unless project
project.shared_agile_sprints.available.map { |s| [s.to_s, s.id.to_s] }
end
def default_columns_names
@default_columns_names = [:id, :subject, :estimated_hours, :spent_hours, :done_ratio, :assigned_to]
end
def sql_for_chart_period_field(_field, _operator, _value)
'1=1'
end
def chart
@chart ||= RedmineAgile::Charts.valid_chart_name_by(options[:chart])
end
def chart=(arg)
options[:chart] = arg
end
def date_from
@date_from ||= chart_period[:from]
end
def date_to
@date_to ||= chart_period[:to]
end
def interval_size
if RedmineAgile::AgileChart::TIME_INTERVALS.include?(options[:interval_size])
options[:interval_size]
else
RedmineAgile::AgileChart::DAY_INTERVAL
end
end
def interval_size=(value)
options[:interval_size] = value
end
def build_from_params(params)
if params[:fields] || params[:f]
self.filters = {}.merge(chart_period_filter(params))
add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
else
available_filters.keys.each do |field|
add_short_filter(field, params[field]) if params[field]
end
end
self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
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
end
def condition_for_status
'1=1'
end
private
def chart_period_filter(params)
return {} if (params[:fields] || params[:f]).include?('chart_period')
{ 'chart_period' => { operator: 'm', values: [''] } }
end
def validate_query_dates
if (self.date_from && self.date_to && self.date_from >= self.date_to)
errors.add(:base, l(:label_agile_chart_dates) + ' ' + l(:invalid, scope: 'activerecord.errors.messages'))
end
end
def db_timestamp_regex
/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:.\d*))/
end
def chart_period
@chart_period ||= {
from: chart_period_statement.match("chart_period > '#{db_timestamp_regex}") { |m| Time.zone.parse(m[1]) },
to: chart_period_statement.match("chart_period <= '#{db_timestamp_regex}") { |m| Time.zone.parse(m[1]) }
}
end
def chart_period_statement
@chart_period_statement ||= build_chart_period_statement
end
def build_chart_period_statement
field = 'chart_period'
operator = filters[field][:operator]
values = filters[field][:values]
date = User.current.today
case operator
when 'w'
first_day_of_week = (Setting.start_of_week || l(:general_first_day_of_week)).to_i
day_of_week = date.cwday
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
sql_for_field(field, '><t-', [days_ago], Issue.table_name, field)
when 'm'
days_ago = date - date.beginning_of_month
sql_for_field(field, '><t-', [days_ago], Issue.table_name, field)
when 'y'
days_ago = date - date.beginning_of_year
sql_for_field(field, '><t-', [days_ago], Issue.table_name, field)
when '><'
sql_for_field(field, '><', adjusted_values(values), Issue.table_name, field)
else
sql_for_field(field, operator, values, Issue.table_name, field)
end
end
def adjusted_values(values)
return values unless values.is_a?(Array)
from = values[0].present? ? Date.parse(values[0]) : Date.today
to = values[1].present? ? Date.parse(values[1]) : Date.today
[from.to_s, (to < from ? from : to).to_s]
end
end

25
app/models/agile_data.rb Executable file
View File

@ -0,0 +1,25 @@
# 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/>.
class AgileData < ActiveRecord::Base
unloadable
belongs_to :issue
validates :story_points, :numericality => {:only_integer => true, :greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
end

679
app/models/agile_query.rb Normal file
View File

@ -0,0 +1,679 @@
# 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/>.
class AgileQuery < Query
include Redmine::SafeAttributes
attr_reader :truncated
self.queried_class = Issue
self.view_permission = :view_issues if Redmine::VERSION.to_s >= '3.4'
self.available_columns = [
QueryColumn.new(:id, sortable: "#{Issue.table_name}.id", default_order: 'desc', caption: :label_agile_issue_id),
QueryColumn.new(:project, groupable: "#{Issue.table_name}.project_id", sortable: "#{Project.table_name}.id"),
QueryColumn.new(:tracker, sortable: "#{Tracker.table_name}.position", groupable: true),
QueryColumn.new(:estimated_hours, sortable: "#{Issue.table_name}.estimated_hours"),
QueryColumn.new(:done_ratio, sortable: "#{Issue.table_name}.done_ratio"),
QueryColumn.new(:day_in_state, caption: :label_agile_day_in_state),
QueryColumn.new(:parent, groupable: "#{Issue.table_name}.parent_id", sortable: "#{AgileData.table_name}.position", caption: :field_parent_issue),
QueryColumn.new(:assigned_to, sortable: lambda { User.fields_for_order_statement }, groupable: "#{Issue.table_name}.assigned_to_id"),
QueryColumn.new(:relations, caption: :label_related_issues),
QueryColumn.new(:last_comment, caption: :label_agile_last_comment),
QueryColumn.new(:story_points, caption: :label_agile_story_points)
]
self.available_columns << QueryColumn.new(:checklists, caption: :label_checklist_plural) if RedmineAgile.use_checklist?
def self.build_from_params(params, attributes = {})
new(attributes).build_from_params(params)
end
scope :visible, lambda { |*args|
user = args.shift || User.current
base = Project.allowed_to_condition(user, :view_issues, *args)
scope = eager_load(:project).where("#{table_name}.project_id IS NULL OR (#{base})")
if user.admin?
scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
elsif user.memberships.any?
scope.where("#{table_name}.visibility = ?" +
" OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
"SELECT DISTINCT q.id FROM #{table_name} q" +
" INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
" INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
" INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
" WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
" OR #{table_name}.user_id = ?",
VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
elsif user.logged?
scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
else
scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
end
}
def initialize(attributes = nil, *args)
super attributes
unless Redmine::VERSION.to_s > '2.4'
self.filters ||= { 'status_id' => { operator: '*', values: [''] } }
end
self.filters ||= {}
@truncated = false
end
def card_columns
self.inline_columns.select { |c| !%w(day_in_state tracker thumbnails description assigned_to done_ratio spent_hours estimated_hours project id sub_issues checklists last_comment story_points).include?(c.name.to_s) }
end
def visible?(user=User.current)
return true if user.admin?
return false unless project.nil? || user.allowed_to?(:view_issues, project)
case visibility
when VISIBILITY_PUBLIC
true
when VISIBILITY_ROLES
if project
(user.roles_for_project(project) & roles).any?
else
Member.where(user_id: user.id).joins(:roles).where(member_roles: {role_id: roles.map(&:id)}).any?
end
else
user == self.user
end
end
def is_private?
visibility == VISIBILITY_PRIVATE
end
def is_public?
!is_private?
end
def color_base
end
def color_base=(value)
end
def default_chart
end
def default_chart=(value)
end
def chart_unit
@chart_unit ||= RedmineAgile::Charts.valid_chart_unit_by(options[:chart], options[:chart_unit])
end
def chart_unit=(value)
options[:chart_unit] = value
end
def draw_relations
r = options[:draw_relations]
r.nil? || r == '1'
end
def draw_relations=(arg)
options[:draw_relations] = (arg == '0' ? '0' : nil)
end
def with_totals?
end
def build_from_params(params)
if params[:fields] || params[:f]
self.filters = {}
add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
else
available_filters.keys.each do |field|
add_short_filter(field, params[field]) if params[field]
end
end
self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
self.color_base = params[:color_base] || (params[:query] && params[:query][:color_base])
self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations])
if params[:f_status] || params[:wp]
self.options = options.merge({ :f_status => params[:f_status], :wp => params[:wp] })
end
self
end
def initialize_available_filters
principals = []
subprojects = []
versions = []
categories = []
issue_custom_fields = []
if project
principals += project.principals.sort
unless project.leaf?
subprojects = project.descendants.visible.all
principals += Principal.member_of(subprojects)
end
versions = project.shared_versions.all
categories = project.issue_categories.all
issue_custom_fields = project.all_issue_custom_fields
else
if all_projects.any?
principals += Principal.member_of(all_projects)
end
versions = Version.visible.where(sharing: 'system').all
issue_custom_fields = IssueCustomField.where(is_for_all: true)
end
principals.uniq!
principals.sort!
users = principals.select { |p| p.is_a?(User) }
unless Redmine::VERSION.to_s > '2.4'
add_available_filter 'status_id',
type: :list_status, values: IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
end
if project.nil?
project_values = []
if User.current.logged? && User.current.memberships.any?
project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
end
project_values += all_projects_values
add_available_filter("project_id",
type: :list, values: project_values
) unless project_values.empty?
end
add_available_filter "tracker_id",
type: :list, values: trackers.collect{|s| [s.name, s.id.to_s] }
add_available_filter "priority_id",
type: :list, values: IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
author_values = []
author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
author_values += users.collect{|s| [s.name, s.id.to_s] }
add_available_filter("author_id",
type: :list, values: author_values
) unless author_values.empty?
assigned_to_values = []
assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
assigned_to_values += (Setting.issue_group_assignment? ?
principals : users).collect{|s| [s.name, s.id.to_s] }
add_available_filter("assigned_to_id",
type: :list_optional, values: assigned_to_values
) unless assigned_to_values.empty?
group_values = Group.visible.all.collect {|g| [g.name, g.id.to_s] }
add_available_filter("member_of_group",
type: :list_optional, values: group_values
) unless group_values.empty?
role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
add_available_filter("assigned_to_role",
type: :list_optional, values: role_values
) unless role_values.empty?
if versions.any?
fixed_versions = []
fixed_versions << ["<< #{l(:label_current_version)} >>", 'current_version']
versions.sort.each{ |s| fixed_versions << ["#{s.project.name} - #{s.name}", s.id.to_s] }
add_available_filter "fixed_version_id",
type: :list_optional,
values: fixed_versions
end
if categories.any?
add_available_filter "category_id",
type: :list_optional,
values: categories.collect{|s| [s.name, s.id.to_s] }
end
add_available_filter "subject", type: :text
add_available_filter "created_on", type: :date_past
add_available_filter "updated_on", type: :date_past
add_available_filter "closed_on", type: :date_past
add_available_filter "start_date", type: :date
add_available_filter "due_date", type: :date
add_available_filter "estimated_hours", type: :float
add_available_filter "done_ratio", type: :integer
add_available_filter "parent_issue_id", type: :relation, values: all_projects_values
add_available_filter "has_sub_issues", type: :list,
values: [l(:general_text_yes), l(:general_text_no)],
label: :label_agile_has_sub_issues
add_available_filter "version_status", type: :list,
name: l("label_attribute_of_fixed_version", name: 'status'),
values: Version::VERSION_STATUSES.collect {|s| [l("version_status_#{s}"), s]}
add_available_filter "parent_issue_tracker_id", type: :list,
label: :label_agile_parent_issue_tracker_id,
values: Tracker.pluck(:name)
if subprojects.any?
add_available_filter "subproject_id",
type: :list_subprojects,
values: subprojects.collect{|s| [s.name, s.id.to_s] }
end
add_custom_fields_filters(issue_custom_fields)
add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
IssueRelation::TYPES.each do |relation_type, options|
add_available_filter relation_type, type: :relation, label: options[:name], values: all_projects_values
end
Tracker.disabled_core_fields(trackers).each { |field|
delete_available_filter field
}
add_available_filter "issue_id", type: :integer, label: :label_issue
if User.current.allowed_to?(:set_issues_private, nil, global: true) ||
User.current.allowed_to?(:set_own_issues_private, nil, global: true)
add_available_filter 'is_private', type: :list,
values: [[l(:general_text_yes), '1'], [l(:general_text_no), '0']]
end
if User.current.logged?
add_available_filter 'watcher_id', type: :list, values: author_values
end
end
def available_columns
return @available_columns if @available_columns
@available_columns = self.class.available_columns.dup
@available_columns += (project ? project.all_issue_custom_fields : IssueCustomField).visible.collect { |cf| QueryCustomFieldColumn.new(cf) }
if User.current.allowed_to?(:view_time_entries, project, global: true)
index = nil
@available_columns.each_with_index { |column, i| index = i if column.name == :estimated_hours}
index = (index ? index + 1 : -1)
# insert the column after estimated_hours or at the end
@available_columns.insert index, QueryColumn.new(:spent_hours,
sortable: "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
default_order: 'desc',
caption: :label_spent_time
)
end
if User.current.allowed_to?(:set_issues_private, nil, global: true) ||
User.current.allowed_to?(:set_own_issues_private, nil, global: true)
@available_columns << QueryColumn.new(:is_private, sortable: "#{Issue.table_name}.is_private")
end
disabled_fields = Tracker.disabled_core_fields(trackers).map { |field| field.sub(/_id$/, '')}
@available_columns.reject! { |column|
disabled_fields.include?(column.name.to_s)
}
@available_columns.reject! { |column| column.name == :done_ratio} unless Issue.use_field_for_done_ratio?
@available_columns
end
def editable_by?(user)
return false unless user
# Admin can edit them all and regular users can edit their private queries
return true if user.admin? || (is_private? && user_id == user.id)
# Members can not edit public queries that are for all project (only admin is allowed to)
is_public? && !@is_for_all && user.allowed_to?(:manage_public_agile_queries, project, global: true)
end
def default_columns_names
@default_columns_names = RedmineAgile.default_columns.map(&:to_sym)
end
def has_column_name?(name)
columns.detect { |c| c.name == name}
end
def groupable_columns
groupable_method = Redmine::VERSION.to_s > '4.2' ? :groupable? : :groupable
available_columns.select { |c| c.public_send(groupable_method) && !c.is_a?(QueryCustomFieldColumn) }
end
def sql_for_issue_id_field(field, operator, value)
if operator == "="
# accepts a comma separated list of ids
ids = value.first.to_s.scan(/\d+/).map(&:to_i)
if ids.present?
"#{Issue.table_name}.id IN (#{ids.join(",")})"
else
"1=0"
end
else
sql_for_field("id", operator, value, Issue.table_name, "id")
end
end
def sql_for_watcher_id_field(field, operator, value)
db_table = Watcher.table_name
"#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
sql_for_field(field, '=', value, db_table, 'user_id') + ')'
end
def sql_for_version_status_field(field, operator, value)
sql_for_field(field, operator, value, Version.table_name, "status")
end
def sql_for_has_sub_issues_field(field, operator, value)
cond = ''
cond = 'NOT' if operator == '=' && value.include?(I18n.t(:general_text_no))
cond = 'NOT' if operator == '!' && value.include?(I18n.t(:general_text_yes))
"( #{cond} EXISTS ( SELECT * FROM #{Issue.table_name} AS subissues WHERE subissues.parent_id = issues.id ) )"
end
def sql_for_parent_issue_id_field(field, operator, value, options={})
value = value.first.split(',') if value.is_a? Array
value = value.split(',') if value.is_a? String
sql = case operator
when '*', '!*', '=', '!'
sql_for_field(field, operator, value, queried_table_name, 'parent_id')
when '=p', '=!p', '!p'
op = (operator == '!p' ? 'NOT IN' : 'IN')
comp = (operator == '=!p' ? '<>' : '=')
"#{Issue.table_name}.parent_id #{op} (SELECT DISTINCT #{Issue.table_name}.id FROM #{Issue.table_name} WHERE #{Issue.table_name}.project_id #{comp} #{value.first.to_i})"
when '*o', '!o'
op = (operator == '!o' ? 'NOT IN' : 'IN')
"#{Issue.table_name}.parent_id #{op} (SELECT DISTINCT #{Issue.table_name}.id FROM #{Issue.table_name} WHERE #{Issue.table_name}.status_id IN (SELECT DISTINCT #{IssueStatus.table_name}.id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false}))"
end
"(#{sql})"
end
def sql_for_parent_issue_tracker_id_field(field, operator, value)
cond = if operator == '=' then '' else 'NOT' end
selected_trackers_ids = Tracker.where(name: value).pluck(:id).join(',')
"( EXISTS (SELECT * FROM #{Issue.table_name} AS parents WHERE parents.tracker_id #{cond} IN (#{selected_trackers_ids}) AND parents.id = issues.parent_id ) )"
end
def sql_for_member_of_group_field(field, operator, value)
if operator == '*' # Any group
groups = Group.all
operator = '=' # Override the operator since we want to find by assigned_to
elsif operator == "!*"
groups = Group.all
operator = '!' # Override the operator since we want to find by assigned_to
else
groups = Group.where(:id => value).all
end
groups ||= []
members_of_groups = groups.inject([]) {|user_ids, group|
user_ids + group.user_ids + [group.id]
}.uniq.compact.sort.collect(&:to_s)
'(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
end
def sql_for_assigned_to_role_field(field, operator, value)
case operator
when "*", "!*" # Member / Not member
sw = operator == "!*" ? 'NOT' : ''
nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
"(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
" WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
when "=", "!"
role_cond = value.any? ?
"#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" :
"1=0"
sw = operator == "!" ? 'NOT' : ''
nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
"(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
" WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
end
end
def sql_for_relations(field, operator, value, options={})
relation_options = IssueRelation::TYPES[field]
return relation_options unless relation_options
relation_type = field
join_column, target_join_column = "issue_from_id", "issue_to_id"
if relation_options[:reverse] || options[:reverse]
relation_type = relation_options[:reverse] || relation_type
join_column, target_join_column = target_join_column, join_column
end
sql = case operator
when "*", "!*"
op = (operator == "*" ? 'IN' : 'NOT IN')
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}')"
when "=", "!"
op = (operator == "=" ? 'IN' : 'NOT IN')
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
when "=p", "=!p", "!p"
op = (operator == "!p" ? 'NOT IN' : 'IN')
comp = (operator == "=!p" ? '<>' : '=')
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
when "*o", "!o"
op = (operator == "!o" ? 'NOT IN' : 'IN')
"#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false}))"
end
if relation_options[:sym] == field && !options[:reverse]
sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
end
"(#{sql})"
end
IssueRelation::TYPES.keys.each do |relation_type|
alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
end
def condition_for_status
if Redmine::VERSION.to_s > '2.4'
return { status_id: options[:f_status] || IssueStatus.where(is_closed: false) }
end
'1=1'
end
def issues(options={})
@issues_cache ||= {}
return @issues_cache[options.to_s] if @issues_cache.has_key?(options.to_s)
order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
scope = options[:scope] ? options[:scope] : issue_scope
scope = scope.
joins(:status).
eager_load((options[:include] || []).uniq).
where(options[:conditions]).
order(order_option).
joins(joins_for_order_statement(order_option.join(','))).
limit(options[:limit]).
offset(options[:offset])
scope = scope.preload(:custom_values)
scope = scope.preload(:author) if has_column?(:author)
if has_column_name?(:checklists)
scope = scope.preload(:checklists)
end
if order_option.detect {|x| x.match("agile_data.position")}
scope = scope.sorted_by_rank
end
if has_column_name?(:last_comment)
journal_comment = Journal.joins(:issue).where("#{Journal.table_name}.notes <> ''").
where(:issues => {:id => issues_ids(scope)}).order("#{Journal.table_name}.id ASC")
@last_comments = {}
journal_comment.each do |lc|
@last_comments[lc.journalized_id] = lc
end
end
if has_column_name?(:day_in_state)
@journals_for_state = Journal.joins(:details).where(
:journals => {
:journalized_id => issues_ids(scope),
:journalized_type => "Issue"
},
:journal_details => {:prop_key => 'status_id'}).order("created_on DESC")
end
@issues_cache[options.to_s] = scope
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
end
def issues_ids(scope)
@issues_ids ||= scope.map(&:id)
end
def issues_paginator(issues, page = nil)
Redmine::Pagination::Paginator.new(issues.count, 20, page)
end
def journals_for_state
@journals_for_state
end
def issue_last_comment(issue, options = {})
return unless has_column_name?(:last_comment) || options[:inline_adding]
return issue.last_comment unless @last_comments
@last_comments[issue.id]
end
def board_statuses
if Redmine::VERSION.to_s > '2.4'
statuses =
if Redmine::VERSION.to_s >= '3.4' && project
project.rolled_up_statuses
else
IssueStatus.where(id: Tracker.eager_load(issues: [:status, :project, :fixed_version]).where(statement).map(&:issue_statuses).flatten.uniq.map(&:id))
end
status_filter_values = (options[:f_status] if options)
if status_filter_values
result_statuses = statuses.where(id: status_filter_values)
else
result_statuses = statuses.where(is_closed: false)
end
result_statuses.sorted.map do |s|
s.instance_variable_set "@issue_count", self.issue_count_by_status[s.id].to_i
if has_column_name?(:estimated_hours)
s.instance_variable_set "@estimated_hours_sum", self.issue_count_by_estimated_hours[s.id].to_f
end
if RedmineAgile.use_story_points? && has_column_name?(:story_points)
s.instance_variable_set "@story_points", self.issue_count_by_story_points[s.id].to_i
end
s
end
else
status_filter_operator = filters.fetch("status_id", {}).fetch(:operator, nil)
status_filter_values = filters.fetch("status_id", {}).fetch(:values, [])
statuses = IssueStatus.where(id: Tracker.eager_load(issues: [:status, :project, :fixed_version]).where(statement).map(&:issue_statuses).flatten.uniq.map(&:id))
result_statuses =
case status_filter_operator
when "o"
statuses.where(is_closed: false).sorted
when "c"
statuses.where(is_closed: true).sorted
when "="
statuses.where(id: status_filter_values).sorted
when "!"
statuses.where("#{IssueStatus.table_name}.id NOT IN (" + status_filter_values.map{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")").sorted
else
statuses.sorted
end
result_statuses.map do |s|
s.instance_variable_set "@issue_count", self.issue_count_by_status[s.id].to_i
if has_column_name?(:estimated_hours)
s.instance_variable_set "@estimated_hours_sum", self.issue_count_by_estimated_hours[s.id].to_f
end
s
end
s
end
end
def issue_count_by_status
@issue_count_by_status ||= issue_scope.group("#{Issue.table_name}.status_id").count
end
def issue_count_by_estimated_hours
@issue_count_by_estimated_hours ||= issue_scope.group("#{Issue.table_name}.status_id").sum("estimated_hours")
end
def issue_count_by_story_points
@issue_count_by_story_points ||= issue_scope.group("#{Issue.table_name}.status_id").sum("#{AgileData.table_name}.story_points")
end
def issue_board
@truncated = RedmineAgile.board_items_limit <= issue_scope.count
all_issues = self.issues.limit(RedmineAgile.board_items_limit).sorted_by_rank
all_issues.group_by{|i| [i.status_id]}
end
def statement
if values_for('fixed_version_id') == ['current_version'] && project
version = current_version
# substitute id for current version
version ? filters['fixed_version_id'][:values] = [version.id.to_s] : filters.delete('fixed_version_id')
end
clauses = super
if version
# return string for correct value in a select on a form
filters['fixed_version_id'][:values] = ['current_version']
end
clauses
end
private
def base_agile_query_scope
Issue.visible
.eager_load(:status, :project, :assigned_to, :tracker, :priority, :category, :fixed_version, :agile_data)
.where(agile_projects)
.where(statement)
.where(condition_for_status)
end
def agile_projects
return '1=1' unless project
p_ids = [project.id]
p_ids += project.descendants.select { |sub| sub.module_enabled?('agile') }.map(&:id) if Setting.display_subprojects_issues?
"#{Project.table_name}.id IN (#{p_ids.join(',')})"
end
def issue_scope
return @agile_scope if @agile_scope
@agile_scope = base_agile_query_scope
@agile_scope
end
def project_statement
return super
end
def current_version
return @current_version if @current_version
versions = project.shared_versions.open.where("LOWER(#{Version.table_name}.name) NOT LIKE LOWER(?)", 'backlog')
versions -= versions.select(&:completed?).reverse
@current_version = versions.to_a.uniq.sort.first
end
end

1
app/models/agile_query/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
.gitattributes export-ignore

View File

@ -0,0 +1,104 @@
# 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/>.
class AgileStatusesCollector
def initialize(issue, options = {})
@issue = issue
@data = []
fill_data
end
def data
@data
end
def grouped_by(field)
return unless field
@data.group_by { |detail| detail.status_id } if field == 'status'
end
def object_for(field)
return unless field
return IssueStatus if field == 'status'
end
def issue_status_for(field, group_id)
return IssueStatus.where(id: group_id).first if field == 'status'
end
def group_total_for(field, group_data)
return unless field
(group_data.map(&:duration).sum / 1.days).to_i if field == 'status'
end
private
def fill_data
assignee_id = initial_assignee_id
@data << detail_object(initial_detail, initial_assignee_id, @issue.created_on)
issue_details.each_with_index do |detail, idx|
next if detail.prop_key != 'status_id'
assignee_id = assignee_for(detail) || assignee_id
@data << detail_object(detail, assignee_id, detail.journal.created_on)
end
@data.each_with_index do |detail, idx|
detail.end_time = @data[idx + 1] ? @data[idx + 1].journal.created_on : Time.now
detail.duration = detail.end_time - detail.start_time
end
end
def detail_object(detail, assignee_id, start_time)
OpenStruct.new(journal: detail.journal,
status_id: detail.value,
assigned_to_id: assignee_id,
start_time: start_time,
end_time: nil,
duration: nil)
end
def assignee_for(detail)
detail.journal.details.detect { |detail| 'assigned_to_id' == detail.prop_key }.try(:value)
end
def issue_details
return @issue_details if @issue_details
@issue_details = @issue.journals.map(&:details).flatten.sort_by { |a| a.journal.created_on }
@issue_details.unshift()
end
def first_status_detail
issue_details.detect { |d| d.prop_key == 'status_id' }
end
def initial_assignee_id
issue_details.detect { |detail| 'assigned_to_id' == detail.prop_key }.try(:old_value) || @issue.assigned_to_id
end
def initial_detail
JournalDetail.new(property: 'attr',
prop_key: 'status_id',
value: first_status_detail.try(:old_value) || @issue.status.id,
journal: Journal.new(user: @issue.author, created_on: @issue.created_on))
end
end

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,15 @@
<td class="issue-backlog-col" rowspan="<%= rowspan %>">
<div class="issue-backlog-search">
<%= text_field_tag('search', '', placeholder: l(:label_agile_board_search_backlog_issues)) %>
<%= javascript_tag("observeIssueSearchfield('search', '#{backlog_autocomplete_agile_boards_path(project_id: query.project.try(:id))}');") %>
</div>
<div class="backlog-issues issue-status-col <%= 'empty' if @backlog_issues.empty? %>" data-id="">
<% paginator = query.issues_paginator(@backlog_issues, params[:page]) %>
<%= render partial: 'issues_list', locals: { column_id: nil,
project: query.project,
issues: @backlog_issues.offset(paginator.offset).limit(paginator.per_page).all,
more_url: paginator.next_page ? backlog_load_more_agile_boards_path(project_id: query.project.try(:id),
q: params[:q],
page: paginator.next_page) : nil } %>
</div>
</td>

View File

@ -0,0 +1,26 @@
<%= form_tag({}) do -%>
<%= hidden_field_tag 'back_url', url_for(params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params) %>
<%= hidden_field_tag 'project_id', @project.id if @project %>
<div class="agile-board autoscroll">
<div class="flash error" style="display: none;" id="agile-board-errors">
</div>
<table class="list issues-board <%= 'status-colors' if RedmineAgile.status_colors? %> <%= 'minimize-closed' if RedmineAgile.minimize_closed? %>">
<div class='lock'> </div>
<thead>
<%= render_board_headers(@board_columns) %>
</thead>
<tr style="text-align: center;white-space: nowrap;" class="issue <%= cycle('odd', 'even') %>">
<% @board_columns.each do |column| %>
<% column_issues = @issue_board[[column.id]] || [] %>
<td class="issue-status-col <%= 'closed' if column.is_closed? %> <%= 'collapse' if RedmineAgile.hide_closed_issues_data? %> <%= column.wp_class if column.respond_to?(:wp_class) %> <%= 'empty' if column_issues.empty? %>" data-id="<%= column.id %>">
<% column_issues.each do |issue| %>
<%= render :partial => 'issue_card', :locals => {:issue => issue} %>
<% end if @issue_board[[column.id]] %>
<%= render(:partial => 'add_issue_card') if @allowed_statuses.include?(column) && !column.is_closed? %>
</td>
<% end %>
</tr>
</table>
</div>
<% end %>

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,98 @@
<div class="contextual">
<%= link_to l(:label_agile_charts), @project ? project_agile_charts_path(project_id: @project) : agile_charts_path, class: 'icon icon-stats agile_charts_link', onclick: 'chartLinkGenerator();' %>
<% if !@query.new_record? && @query.editable_by?(User.current) %>
<%= link_to l(:button_edit), edit_agile_query_path(@query), class: 'icon icon-edit' %>
<%= delete_link agile_query_path(@query) %>
<% end %>
</div>
<% html_title(@query.new_record? ? l(:label_agile_board) : @query.name) %>
<%= form_tag({ controller: 'agile_boards', action: 'index', project_id: @project }, method: :get, id: 'query_form', onsubmit: 'DisableNullFields()') do %>
<h2>
<%= @query.new_record? ? l(:label_agile_board) : h(@query.name) %>
<span class="live_search">
<%= text_field_tag(:search, '', :id => 'agile_live_search', :class => 'live_search_field', :placeholder => l(:label_cards_search)) %>
</span>
</h2>
<div id="query_form_with_buttons" class="hide-when-print">
<%= hidden_field_tag 'set_filter', '1' %>
<div id="query_form_content">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend class="icon icon-<%= @query.new_record? ? 'expended' : 'collapsed' %>" onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>">
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div>
</fieldset>
<fieldset id="options" class="collapsible collapsed">
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend>
<div style="display: none;">
<table class="options agile_options">
<tr>
<td colspan="2">
<fieldset class="card-fields">
<legend><%= l(:label_agile_board_columns) %></legend>
<%= render_board_fields_status(@query) %>
</fieldset>
</tr>
<tr>
<td colspan="2">
<fieldset class="card-fields">
<legend><%= l(:label_agile_fields) %></legend>
<%= render_board_fields_selection(@query) %>
</fieldset>
</td>
</tr>
</table>
</div>
</fieldset>
</div>
<p class="buttons">
<%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
<%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
</p>
</div>
<% end %>
<%= error_messages_for 'query' %>
<% if @query.valid? %>
<% empty_data = @issues.empty? || @board_columns.empty? %>
<% if empty_data %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<% if @query.truncated %>
<p class="warning"><%= l(:label_agile_board_truncated, :max => RedmineAgile.board_items_limit) %></p>
<% end %>
<%= render :partial => 'board' %>
<% end %>
<% end %>
<% content_for :sidebar do %>
<%= render :partial => 'upgrade_to_pro' %>
<%= render :partial => 'issues_links' %>
<% if @project && @project.assignable_users.any? %>
<%= render :partial => 'members' %>
<% end %>
<%= render :partial => 'agile_charts/agile_charts' %>
<% end %>
<% html_title l(:label_agile_board) %>
<% content_for :header_tags do %>
<%= javascript_include_tag "redmine_agile", plugin: 'redmine_agile' %>
<%= javascript_include_tag "jquery.ui.touch-punch.js", plugin: 'redmine_agile' %>
<%= stylesheet_link_tag 'context_menu' %>
<%= stylesheet_link_tag "redmine_agile.css", plugin: "redmine_agile", media: "print" %>
<% end %>
<% if User.current.allowed_to?(:edit_issues, @project, global: true) %>
<script type="text/javascript">
var agileBoard = new AgileBoard({
project_id: '<%= @project && @project.id %>',
update_agile_board_path: '<%= escape_javascript update_agile_board_path %>',
issues_path: '<%= escape_javascript issues_path %>',
create_issue_path: '<%= escape_javascript(agile_create_issue_path(project_id: @project)) if @project %>'
});
</script>
<% end %>

View File

@ -0,0 +1,149 @@
<div class="issue-card hascontextmenu <%= agile_color_class(issue, :color_base => @query.respond_to?(:color_base) && @query.color_base) %> <%= class_for_closed_issue(issue, @version_board) %>"
data-id="<%= issue.id %>"
data-estimated-hours="<%= estimated_time_value(@query, issue).to_f %>"
data-story-points="<%= story_points_value(@query, issue).to_f %>"
style="<%= agile_user_color(issue.assigned_to, :color_base => @query.respond_to?(:color_base) && @query.color_base) if issue.assigned_to %>">
<% if issue.closed? && RedmineAgile.hide_closed_issues_data? && !@version_board %>
<span class="fields">
<div class="tooltip">
<p class="issue-id <%= 'without-tracker' if @query.has_column_name?(:tracker).blank? %>">
<strong><%= link_to "#{'#' + issue.id.to_s}", issue_path(issue) %></strong>
</p>
<span class="tip">
</span>
</div>
</span>
<% else %>
<span class="fields">
<% if User.current.allowed_to?(:edit_issues, @project, :global => true) && !@version_board %>
<div class="quick-edit-card">
<%= link_to image_tag("/images/comment.png", alt: 'Comment'), '#', onclick: "showInlineComment(this, '#{j(agile_inline_comment_path(:id => issue))}'); return false;", title: l(:label_comment_add), class: 'add-comment' if RedmineAgile.allow_inline_comments? %>
</div>
<% end %>
<% if @query.has_column_name?(:project) %>
<p class="project">
<%= issue.project.name %>
</p>
<% end %>
<p class="issue-id <%= 'without-tracker' if @query.has_column_name?(:tracker).blank? %>">
<%= check_box_tag("ids[]", issue.id, false, :id => nil, :class => 'checkbox') %>
<% if @query.has_column_name?(:tracker) %>
<strong><%= issue_heading(issue) %></strong>
<% end %>
<%= render_issue_card_hours(@query, issue) %>
</p>
<p class="name" ><%= link_to "#{'#' + issue.id.to_s + ': ' if @query.has_column_name?(:id) && @query.has_column_name?(:tracker).blank?}#{issue.subject.truncate(100)}", issue_path(issue) %></p>
<p class="attributes">
<% @query.card_columns.select{|c| !c.value(issue).blank? }.each do |column| %>
<b><%= column.caption %></b>: <%= column_content(column, issue) %> <br>
<% end %>
</p>
<% if @query.has_column_name?(:day_in_state) %>
<p class="attributes">
<b><%= " #{I18n.t('label_agile_day_in_state')}: " %></b>
<% find_change_state = false %>
<% if @query.journals_for_state %>
<% @query.journals_for_state.each do |journal| %>
<% if journal.journalized_id == issue.id %>
<%= time_in_state(journal.created_on) %>
<% find_change_state = true %>
<% break %>
<% end %>
<% end %>
<% end %>
<% if !find_change_state %>
<%= time_in_state(issue.day_in_state) %>
<% end %>
</p>
<% end %>
<% if @query.has_column_name?(:description) && !issue.description.blank? %>
<em class="info description">
<%= issue.description.truncate(200) %>
</em>
<% end %>
<% if @query.has_column_name?(:sub_issues) && issue.sub_issues.any? %>
<div class='sub-issues'>
<%= issue_children(issue) %>
</div>
<% end %>
<% if @query.has_column_name?(:thumbnails) %>
<% image = issue.attachments.select(&:thumbnailable?).last %>
<% if image %>
<div class="thumbnail" style="background-image: url('<%= thumbnail_path(image, :size => 250) %>')">
</div>
<% end %>
<% end %>
<!-- Check list -->
<% if @query.has_column_name?(:checklists) && show_checklist?(issue) %>
<div class="checklist" id="checklist_<%= issue.id %>">
<ul id="checklist_items">
<% issue.checklists.each do |checklist_item| %>
<%= render :partial => 'checklists/checklist_item', :object => checklist_item %>
<% end %>
</ul>
<script type="text/javascript">
$("#checklist_<%= issue.id %>").checklist();
</script>
</div>
<% end %>
<% if @query.has_column_name?(:assigned_to) %>
<p class="info assigned-user" style="<%= 'display: none;' unless issue.assigned_to %>">
<span class="user"><%= avatar(issue.assigned_to, :size => "14").to_s.html_safe + " " + link_to_user(issue.assigned_to) if issue.assigned_to %></span>
</p>
<% end %>
<% if @query.has_column_name?(:done_ratio) %>
<%= progress_bar(issue.done_ratio, :width => '100%') %>
<% end %>
<% if (last_comment = @query.issue_last_comment(issue, :inline_adding => @inline_adding)) %>
<em class="info description last_comment" title='<%= last_comment.user.to_s + " " + format_date(last_comment.created_on) %>'>
<span class="icon icon-comment last-comment">
<%= last_comment.notes.truncate(100) %>
</span>
</em>
<% end %>
<% if User.current.allowed_to?(:edit_issues, @project, :global => true) && RedmineAgile.allow_inline_comments? %>
<div class="quick-comment">
</div>
<% end %>
</span>
<% end %>
</div>
<% if User.current.allowed_to?(:edit_issues, @project, :global => true) && @update %>
<script type="text/javascript">
document.onkeydown = function(evt) {
evt = evt || window.event;
if (evt.keyCode == 27) {
$('html.agile-board-fullscreen').removeClass('agile-board-fullscreen');
$(".issue-card").addClass("hascontextmenu");
saveFullScreenState();
}
};
$("table.issues-board thead").html("<%=escape_javascript render_board_headers(@query.board_statuses) %>");
</script>
<% if @error_msg %>
<script type="text/javascript">
setErrorMessage("<%= @error_msg%>", 'warning');
</script>
<% end %>
<% if @update && !@version_board %>
<script type="text/javascript">
$("table.issues-board thead").html("<%=escape_javascript render_board_headers(@query.board_statuses) %>");
</script>
<% if Redmine::VERSION.to_s > '2.4' %>
<script type="text/javascript">
agileBoard.initDroppable();
</script>
<% end %>
<% end %>
<%= init_agile_tooltip_info %>
<% end %>

View File

@ -0,0 +1 @@
<%= render_issue_tooltip @issue %>

View File

@ -0,0 +1,17 @@
<h3><%= l(:label_issue_plural) %></h3>
<ul>
<li><%= link_to l(:label_issue_view_all), _project_issues_path(@project, :set_filter => 1) %></li>
<% if @project %>
<li><%= link_to l(:field_summary), project_issues_report_path(@project) %></li>
<% end %>
<% if User.current.allowed_to?(:view_calendar, @project, :global => true) %>
<li><%= link_to l(:label_calendar), _project_calendar_path(@project) %></li>
<% end %>
<% if User.current.allowed_to?(:view_gantt, @project, :global => true) %>
<li><%= link_to l(:label_gantt), _project_gantt_path(@project) %></li>
<% end %>
<% if User.current.allowed_to?(:view_agile_queries, @project) %>
<li><%= link_to l(:label_agile_board), {:controller => "agile_boards", :action => "index", :project_id => @project} %></li>
<% end %>
</ul>

View File

@ -0,0 +1,15 @@
<% if issues.any? %>
<% issues.each do |issue| %>
<%= render partial: 'issue_card', locals: { issue: issue } %>
<% end %>
<% if more_url %>
<div class="load-more">
<div class="pagination-wrapper">
<span class="pagination">
<%= link_to l(:label_agile_planning_board_more), more_url, remote: true %>
</span>
</div>
</div>
<% end %>
<% end %>

View File

@ -0,0 +1,8 @@
<div class="project-members">
<h3><%=l(:label_member_plural)%></h3>
<% @project.assignable_users.each do |user| %>
<span class="assignable-user" data-id="<%= user.id %>"><%= avatar(user.is_a?(User) ? user : '<gravatar>', :size => "14", :style => agile_user_color(user, :color_base => @query.respond_to?(:color_base) && @query.color_base) ).to_s.html_safe + " " + link_to_user(user) %>
<br>
</span>
<% end %>
</div>

View File

@ -0,0 +1,9 @@
<h3><%= l(:label_agile_light_free_version) %></h3>
<ul>
<li><%= link_to l(:label_agile_link_to_pro), "https://www.redmineup.com/pages/plugins/agile#pricing?from=light_upgrade" %>
</li>
<li><%= link_to l(:label_agile_link_to_pro_demo), "http://demo.redmineup.com/projects/agile/agile/board?query_id=4?from=light_live_preview" %>
</li>
<li><%= link_to l(:label_agile_link_to_more_plugins), "https://www.redmineup.com/pages/plugins?from=light_more_plugins" %>
</li>
</ul>

View File

@ -0,0 +1,7 @@
api.agile_data do
api.id @agile_data.id
api.issue_id @agile_data.issue_id
api.position @agile_data.position
api.story_points @agile_data.story_points
api.agile_sprint_id @agile_data.agile_sprint_id
end

View File

@ -0,0 +1 @@
$(".issues-board div[data-id='<%= @column_id %>']").html('<%= escape_javascript(render partial: "issues_list", locals: { project: @project, issues: @issues, more_url: @more_url }) %>')

View File

@ -0,0 +1,3 @@
<%= render 'index' %>
<%= init_agile_tooltip_info %>
<%= call_hook(:view_agile_board_bottom, { :issues => @issues, :project => @project, :query => @query }) %>

View File

@ -0,0 +1,3 @@
$('#content').html('<%= j(render('index')) %>')
$('table.issues-board').StickyHeader();
<%= init_agile_tooltip_info(:only_code => true) %>

View File

@ -0,0 +1,3 @@
<%= text_area_tag "issue[notes]", "", :cols => 60, :rows => 5, :class => 'wiki-edit', :no_label => true %>
<button type="button" onclick="saveInlineComment(this, '<%= update_agile_board_path(:id => @issue) %>'); return false;"> <%= l(:button_submit) %> </button>
<%= link_to l(:button_cancel), "#", :onclick => "cancelInlineComment(this);return false;" %>

View File

@ -0,0 +1 @@
$(".issues-board div[data-id='<%= @column_id %>'] .load-more").replaceWith('<%= escape_javascript(render partial: "issues_list", locals: { project: @project, issues: @issues, more_url: @more_url }) %>')

View File

@ -0,0 +1,6 @@
decHtmlNumber('th[data-column-id="<%= params[:old_status_id] %>"] span.count');
incHtmlNumber('th[data-column-id="<%= params[:new_status_id] %>"] span.count');
decHtmlNumber('tr.group.swimlane[data-id="<%= params[:old_swimlane_id] %>"] td span.count');
incHtmlNumber('tr.group.swimlane[data-id="<%= params[:new_swimlane_id] %>"] td span.count');
//$('.issue-card[data-id="<%= @issue.id %>"]').html("<%= render :partial => 'issue_card', :locals => {:issue => @issue} %>");

View File

@ -0,0 +1,5 @@
<% if User.current.allowed_to?(:view_agile_charts, @project, :global => true) %>
<ul class=agile-chart-queries>
</ul>
<% end %>

View File

@ -0,0 +1,99 @@
<% if issues_scope.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% elsif %w(work_burndown cumulative_flow hours_velocity).include?(chart) && issues_scope.count > RedmineAgile.time_reports_items_limit %>
<p class="nodata"><%= l(:label_agile_too_many_items, :max => RedmineAgile.time_reports_items_limit) %></p>
<% else %>
<div class="agile-chart-container">
<canvas id="agile-chart"></canvas>
<div style="clear: both;"></div>
</div>
<% end %>
<%= javascript_tag do %>
$(document).ready(function(){
Chart.plugins.register({
beforeDraw: function(chartInstance, easing) {
if (chartInstance.config.options.tooltips.onlyShowForDatasetIndex) {
var tooltipsToDisplay = chartInstance.config.options.tooltips.onlyShowForDatasetIndex;
var active = chartInstance.tooltip._active || [];
if (active.length > 0) {
if (tooltipsToDisplay.indexOf(active[0]._datasetIndex) === -1) {
chartInstance.tooltip._model.opacity = 0;
}
}
}
}
});
if (document.getElementById("agile-chart")) {
$.getJSON(<%= raw url_for(controller: 'agile_charts', action: 'render_chart', project_id: @project,
chart: chart, version_id: @version, query_id: @query.try(:id), chart_unit: chart_unit).to_json %>, function(data){
Chart.defaults.global.defaultFontColor = 'black';
Chart.defaults.global.defaultFontFamily = '"Arial", sans-serif';
Chart.defaults.global.defaultFontStyle = 'normal';
var chartData = {
labels: data['labels'],
datasets: data['datasets'],
stacked: data['stacked']
};
new Chart(document.getElementById("agile-chart").getContext("2d"), {
type: data['type'] || 'bar',
data: chartData,
options: {
tooltips: {
onlyShowForDatasetIndex: data['show_tooltips'],
callbacks: chartTooltipCallbacks(data['type'])
},
maintainAspectRatio: false,
legend: {
position: 'right',
labels: {
filter: function(legendItem, chartData) {
if (legendItem.text) { return true }
}
}
},
title: {
display: true,
fontSize: 16,
fontStyle: 'normal',
text: data['title']
},
elements: {
line: {
tension: 0, // disables bezier curves
}
},
scales: {
yAxes: [{
stacked: data['stacked'],
scaleLabel: {
display: true,
fontColor: 'rgba(255, 0, 0 ,1)',
fontSize: 14,
labelString: data['y_title']
}
}],
xAxes: [{
ticks: {
autoSkip: true,
maxRotation: 0,
userCallback: function(value, index, values) {
if (data['type'] == 'scatter') {
return data['labels'][value]
} else {
return value
}
}
}
}]
},
}
});
});
}
});
<% end %>

View File

@ -0,0 +1,31 @@
<% if @issues.any? && User.current.allowed_to?(:view_agile_charts, @project) %>
<fieldset>
<legend>
<%= l(:label_agile_chart) %>
<%= select_tag('chart', options_charts_for_select(params[:chart] || RedmineAgile.default_chart),
id: 'chart_by_select',
onchange: "toggleChartUnit($(this).val(), 'chart-unit-row'); updateVersionAgileChart('#{agile_charts_select_version_chart_path(version_id: @version)}');") %>
<span id="chart-unit-row">
<label for='chart_unit'><%= l(:label_agile_chart_units) %></label>
<%= select_tag 'chart_unit', options_chart_units_for_select,
onchange: "updateVersionAgileChart('#{agile_charts_select_version_chart_path(version_id: @version)}');" %>
</span>
</legend>
<div id='agile_chart'>
<%= render_agile_chart(RedmineAgile::Charts.valid_chart_name_by(params[:chart] || RedmineAgile.default_chart), @version.fixed_issues) %>
</div>
</fieldset>
<% end %>
<% content_for :header_tags do %>
<%= chartjs_assets %>
<%= javascript_include_tag 'redmine_agile', plugin: 'redmine_agile' %>
<% end %>
<%= javascript_tag do %>
var chartsWithUnits = <%= raw RedmineAgile::Charts::CHARTS_WITH_UNITS.to_json %>
$(document).ready(function() {
toggleChartUnit($('#chart_by_select').val(), 'chart-unit-row');
});
<% end %>

View File

@ -0,0 +1,5 @@
$('#agile_chart').html('<%= escape_javascript(render_agile_chart(params[:chart], @version.fixed_issues)) %>');
if ("replaceState" in window.history) {
window.history.replaceState(null, document.title, "?chart=<%= params[:chart] %>");
}

View File

@ -0,0 +1,67 @@
<%= render_agile_charts_breadcrumb %>
<h2><%= @query.new_record? ? l(:label_agile_chart_plural) : h(@query.name) %></h2>
<% html_title(@query.new_record? ? l(:label_agile_chart_plural) : @query.name) %>
<%= form_tag({ :controller => 'agile_charts', :action => 'show', :project_id => @project },
:method => :get, :id => 'query_form') do %>
<div id="query_form_with_buttons" class="hide-when-print">
<%= hidden_field_tag 'set_filter', '1' %>
<div id="query_form_content">
<fieldset id="filters" class="collapsible">
<legend class="icon icon-expended" onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div>
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div>
</fieldset>
<fieldset class="collapsible">
<legend class="icon icon-expended" onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
<div>
<table>
<tr>
<td><label for='chart'><%= l(:label_agile_chart) %></label></td>
<td><%= select_tag 'chart', options_charts_for_select(@chart), onchange: "toggleChartUnit($(this).val(), 'chart-unit-row');" %></td>
<td id="chart-unit-row">
<label for='chart_unit'><%= l(:label_agile_chart_units) %></label>
<%= select_tag 'chart_unit', options_chart_units_for_select(@query.chart_unit) %>
</td>
</tr>
<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) %>
</td>
</tr>
</table>
</div>
</fieldset>
</div>
<p class="buttons">
<%= link_to_function l(:button_apply), '$("#query_form").submit()', :class => 'icon icon-checked' %>
<%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
</p>
</div>
<% end %>
<%= error_messages_for 'query' %>
<%= render_agile_chart(@chart, @issues) if @query.valid? %>
<% content_for :header_tags do %>
<%= chartjs_assets %>
<%= javascript_include_tag 'redmine_agile', plugin: 'redmine_agile' %>
<% end %>
<% content_for :sidebar do %>
<%= render :partial => 'agile_boards/issues_links' %>
<%= render :partial => 'agile_charts/agile_charts' %>
<% end %>
<%= javascript_tag do %>
var chartsWithUnits = <%= raw RedmineAgile::Charts::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 */
hideChartPeriodCheckbox();
});
<% end %>

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,8 @@
<!-- </PRO> -->
<h2><%= l(:label_agile_chart_new) %></h2>
<%= form_tag(@project ? project_agile_charts_queries_path(@project) : agile_charts_queries_path) do %>
<%= render partial: 'form', locals: { query: @query } %>
<%= submit_tag l(:button_create) %>
<% end %>
<!-- </PRO> -->

View File

@ -0,0 +1,9 @@
<% assignee = Principal.where(:id => data.assigned_to_id).first %>
<tr class="<%= cycle('odd', 'even') %>">
<td class="index"><%= index + 1 %></td>
<td class="name"><%= format_time(data.journal.created_on) %></td>
<td><%= issue_status.name %></td>
<td><%= distance_of_time_in_words(data.end_time, data.start_time).html_safe %></td>
<td><%= data.journal.user.name %></td>
<td><%= assignee.try(:name) %></td>
</tr>

View File

@ -0,0 +1,27 @@
<%= title [issue_heading(@issue) , issue_path(@issue)], l(:field_assigned_to) %>
<% html_title(l(:field_assigned_to)) %>
<% if @assignees.any? %>
<table class="list"><thead>
<tr>
<th>#</th>
<th><%= l(:field_created_on) %></th>
<th><%= l(:field_assigned_to) %></th>
<th><%= l(:field_duration) %></th>
<th><%= l(:field_author) %></th>
</tr></thead>
<% @assignees.each_with_index do |status, index| %>
<% assignee = User.where(:id => status.value).first %>
<tr class="<%= cycle('odd', 'even') %>">
<td class="index"><%= index + 1 %></td>
<td class="name"><%= format_time(status.journal.created_on) %></td>
<td class="name"><%= avatar(assignee, :size => "14").to_s.html_safe + " " + link_to_user(assignee) %></td>
<td><%= event_duration(status, @assignees[index + 1]) %></td>
<td><%= link_to_user status.journal.user %></td>
</tr>
<% end %>
</table>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>

View File

@ -0,0 +1,28 @@
<%= title [issue_heading(@issue) , issue_path(@issue)], l(:field_done_ratio) %>
<% html_title(l(:field_done_ratio)) %>
<% if @done_ratios.any? %>
<table class="list"><thead>
<tr>
<th>#</th>
<th><%= l(:field_created_on) %></th>
<th><%= l(:field_done_ratio) %></th>
<th><%= l(:label_agile_charts_number_of_hours) %></th>
<th><%= l(:field_duration) %></th>
<th><%= l(:field_author) %></th>
</tr></thead>
<% @done_ratios.each_with_index do |done_ratio, index| %>
<tr class="<%= cycle('odd', 'even') %>">
<td class="index"><%= index + 1 %></td>
<td class="name"><%= format_time(done_ratio.journal.created_on) %></td>
<td><%= progress_bar(done_ratio.value.to_f, :width => '80px', :legend => "#{"%.2f" %done_ratio.value.to_f}%") %></td>
<td><%= "%.2f" % (@issue.estimated_hours.to_f * (100 - done_ratio.value.to_f) / 100.0) %></td>
<td><%= event_duration(done_ratio, @done_ratios[index + 1]) %></td>
<td><%= done_ratio.journal.user.name %></td>
</tr>
<% end %>
</table>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>

View File

@ -0,0 +1 @@
<h2>AgileDoneRatioFlowsController#edit</h2>

View File

@ -0,0 +1 @@
<h2>AgileDoneRatioFlowsController#new</h2>

View File

@ -0,0 +1,70 @@
<%= title [issue_heading(@issue) , issue_path(@issue)], l(:label_issue_status) %>
<% html_title(l(:label_issue_status_plural)) %>
<%= form_tag({ controller: 'agile_journal_details', action: 'status' }, method: :get, id: 'query_form') do %>
<div id="query_form_with_buttons" class="hide-when-print">
<div id="query_form_content">
<fieldset id="options" class="collapsible collapsed">
<legend onclick="toggleFieldset(this);" class="icon icon-expended"><%= l(:label_options) %></legend>
<div style="display: none;">
<table>
<tr>
<td><label for='group_by'><%= l(:field_group_by) %></label></td>
<td><%= select_tag('group_by', options_for_select([[]] + [[l(:field_status), 'status']], params[:group_by])) %></td>
</tr>
</table>
</div>
</fieldset>
</div>
</div>
<p class="buttons hide-when-print">
<%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %>
<%= link_to l(:button_clear), { }, :class => 'icon icon-reload' %>
</p>
<% end %>
<% if @statuses_collector.data.any? %>
<table class="list"><thead>
<tr>
<th>#</th>
<th><%= l(:field_created_on) %></th>
<th><%= l(:field_status) %></th>
<th><%= l(:field_duration) %></th>
<th><%= l(:field_author) %></th>
<th><%= l(:field_assigned_to) %></th>
</tr></thead>
<% if @group %>
<% @statuses_collector.grouped_by(@group).each do |group_id, group_data| %>
<% group_object = @statuses_collector.object_for(@group).where(:id => group_id).first %>
<tr class="group open">
<td colspan="6">
<span class="expander icon icon-expended" onclick="toggleRowGroup(this);">&nbsp;</span>
<span class="name"><%= group_object.name %></span>
<span class="badge badge-count count"><%= group_data.count %></span>
<span class="totals">
<%= l('datetime.distance_in_words.x_days', count: @statuses_collector.group_total_for(@group, group_data)) %>
</span>
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}",
"toggleAllRowGroups(this)", :class => 'toggle-all') %>
</td>
</tr>
<% group_data.each_with_index do |data, index| %>
<%= render partial: 'status_detail', locals: { issue_status: @statuses_collector.issue_status_for(@group, group_id), data: data, index: index } %>
<% end %>
<% end %>
<% else %>
<% @statuses_collector.data.each_with_index do |data, index| %>
<% issue_status = IssueStatus.where(:id => data.status_id).first %>
<%= render partial: 'status_detail', locals: { issue_status: issue_status, data: data, index: index } %>
<% end %>
<% end %>
</table>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<% other_formats_links do |f| %>
<%= f.link_to 'CSV', :url => params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params %>
<% end %>

View File

@ -0,0 +1,9 @@
<div class="attributes">
<div class="splitcontent">
<%= render :partial => 'issue_story_points_form', :locals => { :form => form } %>
</div>
</div>
<% content_for :header_tags do %>
<%= javascript_include_tag "redmine_agile", :plugin => 'redmine_agile' %>
<% end %>

View File

@ -0,0 +1 @@
<%= render :partial => 'issue_story_points' %>

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,7 @@
<% if @issue.project.module_enabled?('agile') && RedmineAgile.use_story_points? && RedmineAgile.use_story_points_for?(@issue.tracker) %>
<%= issue_fields_rows do |rows|
rows.left l(:label_agile_story_points), @issue.story_points
end if @issue.story_points %>
<% end %>
<%= javascript_tag "linkableAttributeFields();" %>

View File

@ -0,0 +1,9 @@
<% if @issue.project.module_enabled?('agile') && RedmineAgile.use_story_points? && RedmineAgile.use_story_points_for?(@issue.tracker) %>
<div class="splitcontentright">
<%= form.fields_for :agile_data do |f| %>
<p>
<%= f.text_field :story_points, :size => 3, :required => @issue.required_attribute?('agile_data_attributes') %>
</p>
<% end %>
</div>
<% end %>

1
app/views/projects/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
.gitattributes export-ignore

View File

@ -0,0 +1,54 @@
<p>
<label><%= l(:label_agile_board_items_limit) %></label>
<%= text_field_tag 'settings[board_items_limit]', RedmineAgile.board_items_limit, :size => 3 %>
</p>
<p>
<label><%= l(:label_agile_board_default_fields) %></label>
<%= select_tag 'settings[default_columns]', options_for_select(AgileQuery.new(:column_names => RedmineAgile.default_columns).available_columns.collect{|column| [column.caption, column.name]}, RedmineAgile.default_columns), :multiple => true, :include_blank => true, :size => 10, :style => "width:150px" %>
</p>
<p>
<label><%= l(:label_agile_story_points) %></label>
<%= hidden_field_tag 'settings[story_points_on]', 0, id: nil %>
<%= check_box_tag 'settings[story_points_on]', 1, RedmineAgile.use_story_points?, onchange: "$('#trackers_for_sp').toggle(this.checked);" %>
</p>
<div id="trackers_for_sp" style="display:<%= RedmineAgile.use_story_points? ? 'block' : 'none' %>">
<p>
<label><%= l(:label_agile_trackers_for_sp) %></label>
<%= select_tag 'settings[trackers_for_sp]', options_for_select(Tracker.all.map{|tr| [tr.name, tr.id]}, RedmineAgile.trackers_for_sp), :prompt => "All", :style => "width:150px" %>
</p>
</div>
<p>
<label><%= l(:label_agile_default_chart) %></label>
<%= select_tag 'settings[default_chart]', grouped_options_charts_for_select(RedmineAgile.default_chart) %>
</p>
<p>
<label for="settings_exclude_weekends"><%= l(:label_agile_exclude_weekends) %></label>
<%= hidden_field_tag 'settings[exclude_weekends]', 0, :id => nil %>
<%= check_box_tag 'settings[exclude_weekends]', 1, RedmineAgile.exclude_weekends? %>
</p>
<p>
<label><%= l(:label_agile_time_reports_items_limit) %></label>
<%= text_field_tag 'settings[time_reports_items_limit]', RedmineAgile.time_reports_items_limit, :size => 3 %>
</p>
<p>
<label for="settings_hide_closed_issues_data"><%= l(:label_agile_hide_closed_issues_data) %></label>
<%= check_box_tag 'settings[hide_closed_issues_data]', 1, RedmineAgile.hide_closed_issues_data? %>
</p>
<p>
<label for="settings_auto_assign_on_move"><%= l(:label_agile_auto_assign_on_move) %></label>
<%= hidden_field_tag 'settings[auto_assign_on_move]', 0, :id => nil %>
<%= check_box_tag 'settings[auto_assign_on_move]', 1, RedmineAgile.auto_assign_on_move? %>
</p>
<p>
<label for="settings_allow_inline_comments"><%= l(:label_agile_inline_comment) %></label>
<%= hidden_field_tag 'settings[allow_inline_comments]', 0, :id => nil %>
<%= check_box_tag 'settings[allow_inline_comments]', 1, RedmineAgile.allow_inline_comments? %>
</p>

1
app/views/users/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
.gitattributes export-ignore

BIN
assets/images/agile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

View File

@ -0,0 +1,235 @@
/*
* Very simple jQuery Color Picker
* https://github.com/tkrotoff/jquery-simplecolorpicker
*
* Copyright (C) 2012-2013 Tanguy Krotoff <tkrotoff@gmail.com>
*
* Licensed under the MIT license
*/
(function($) {
'use strict';
/**
* Constructor.
*/
var SimpleColorPicker = function(select, options) {
this.init('simplecolorpicker', select, options);
};
/**
* SimpleColorPicker class.
*/
SimpleColorPicker.prototype = {
constructor: SimpleColorPicker,
init: function(type, select, options) {
var self = this;
self.type = type;
self.$select = $(select);
self.$select.hide();
self.options = $.extend({}, $.fn.simplecolorpicker.defaults, options);
self.$colorList = null;
if (self.options.picker === true) {
var selectText = self.$select.find('> option:selected').text();
self.$icon = $('<span class="simplecolorpicker button"'
+ ' title="' + selectText + '"'
+ ' style="background-color: ' + self.$select.val() + ';"'
+ ' role="button" tabindex="0">'
+ '</span>').insertAfter(self.$select);
self.$icon.on('click.' + self.type, $.proxy(self.showPicker, self));
self.$picker = $('<span class="simplecolorpicker picker ' + self.options.theme + '"></span>').appendTo(document.body);
self.$colorList = self.$picker;
// Hide picker when clicking outside
$(document).on('mousedown.' + self.type, $.proxy(self.hidePicker, self));
self.$picker.on('mousedown.' + self.type, $.proxy(self.mousedown, self));
} else {
self.$inline = $('<span class="simplecolorpicker inline ' + self.options.theme + '"></span>').insertAfter(self.$select);
self.$colorList = self.$inline;
}
// Build the list of colors
// <span class="color selected" title="Green" style="background-color: #7bd148;" role="button"></span>
self.$select.find('> option').each(function() {
var $option = $(this);
var color = $option.val();
var isSelected = $option.is(':selected');
var isDisabled = $option.is(':disabled');
var selected = '';
if (isSelected === true) {
selected = ' data-selected';
}
var disabled = '';
if (isDisabled === true) {
disabled = ' data-disabled';
}
var title = '';
if (isDisabled === false) {
title = ' title="' + $option.text() + '"';
}
var role = '';
if (isDisabled === false) {
role = ' role="button" tabindex="0"';
}
var $colorSpan = $('<span class="color"'
+ title
+ ' style="background-color: ' + color + ';"'
+ ' data-color="' + color + '"'
+ selected
+ disabled
+ role + '>'
+ '</span>');
self.$colorList.append($colorSpan);
$colorSpan.on('click.' + self.type, $.proxy(self.colorSpanClicked, self));
var $next = $option.next();
if ($next.is('optgroup') === true) {
// Vertical break, like hr
self.$colorList.append('<span class="vr"></span>');
}
});
},
/**
* Changes the selected color.
*
* @param color the hexadecimal color to select, ex: '#fbd75b'
*/
selectColor: function(color) {
var self = this;
var $colorSpan = self.$colorList.find('> span.color').filter(function() {
return $(this).data('color').toLowerCase() === color.toLowerCase();
});
if ($colorSpan.length > 0) {
self.selectColorSpan($colorSpan);
} else {
console.error("The given color '" + color + "' could not be found");
}
},
showPicker: function() {
var pos = this.$icon.offset();
this.$picker.css({
// Remove some pixels to align the picker icon with the icons inside the dropdown
left: pos.left - 1,
top: pos.top - 4//+ this.$icon.outerHeight()
});
this.$picker.show(this.options.pickerDelay);
},
hidePicker: function() {
this.$picker.hide(this.options.pickerDelay);
},
/**
* Selects the given span inside $colorList.
*
* The given span becomes the selected one.
* It also changes the HTML select value, this will emit the 'change' event.
*/
selectColorSpan: function($colorSpan) {
var color = $colorSpan.data('color');
var title = $colorSpan.prop('title');
// Mark this span as the selected one
$colorSpan.siblings().removeAttr('data-selected');
$colorSpan.attr('data-selected', '');
if (this.options.picker === true) {
this.$icon.css('background-color', color);
this.$icon.prop('title', title);
this.hidePicker();
}
// Change HTML select value
this.$select.val(color);
},
/**
* The user clicked on a color inside $colorList.
*/
colorSpanClicked: function(e) {
// When a color is clicked, make it the new selected one (unless disabled)
if ($(e.target).is('[data-disabled]') === false) {
this.selectColorSpan($(e.target));
this.$select.trigger('change');
}
},
/**
* Prevents the mousedown event from "eating" the click event.
*/
mousedown: function(e) {
e.stopPropagation();
e.preventDefault();
},
destroy: function() {
if (this.options.picker === true) {
this.$icon.off('.' + this.type);
this.$icon.remove();
$(document).off('.' + this.type);
}
this.$colorList.off('.' + this.type);
this.$colorList.remove();
this.$select.removeData(this.type);
this.$select.show();
}
};
/**
* Plugin definition.
* How to use: $('#id').simplecolorpicker()
*/
$.fn.simplecolorpicker = function(option) {
var args = $.makeArray(arguments);
args.shift();
// For HTML element passed to the plugin
return this.each(function() {
var $this = $(this),
data = $this.data('simplecolorpicker'),
options = typeof option === 'object' && option;
if (data === undefined) {
$this.data('simplecolorpicker', (data = new SimpleColorPicker(this, options)));
}
if (typeof option === 'string') {
data[option].apply(data, args);
}
});
};
/**
* Default options.
*/
$.fn.simplecolorpicker.defaults = {
// No theme by default
theme: '',
// Show the picker or make it inline
picker: false,
// Animation delay in milliseconds
pickerDelay: 0
};
})(jQuery);

View File

@ -0,0 +1,180 @@
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 20112014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
(function ($) {
// Detect touch support
$.support.touch = 'ontouchend' in document;
// Ignore browsers without touch support
if (!$.support.touch) {
return;
}
var mouseProto = $.ui.mouse.prototype,
_mouseInit = mouseProto._mouseInit,
_mouseDestroy = mouseProto._mouseDestroy,
touchHandled;
/**
* Simulate a mouse event based on a corresponding touch event
* @param {Object} event A touch event
* @param {String} simulatedType The corresponding mouse event
*/
function simulateMouseEvent (event, simulatedType) {
// Ignore multi-touch events
if (event.originalEvent.touches.length > 1) {
return;
}
event.preventDefault();
var touch = event.originalEvent.changedTouches[0],
simulatedEvent = document.createEvent('MouseEvents');
// Initialize the simulated mouse event using the touch event's coordinates
simulatedEvent.initMouseEvent(
simulatedType, // type
true, // bubbles
true, // cancelable
window, // view
1, // detail
touch.screenX, // screenX
touch.screenY, // screenY
touch.clientX, // clientX
touch.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false, // metaKey
0, // button
null // relatedTarget
);
// Dispatch the simulated event to the target element
event.target.dispatchEvent(simulatedEvent);
}
/**
* Handle the jQuery UI widget's touchstart events
* @param {Object} event The widget element's touchstart event
*/
mouseProto._touchStart = function (event) {
var self = this;
// Ignore the event if another widget is already being handled
if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
return;
}
// Set the flag to prevent other widgets from inheriting the touch event
touchHandled = true;
// Track movement to determine if interaction was a click
self._touchMoved = false;
// Simulate the mouseover event
simulateMouseEvent(event, 'mouseover');
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
// Simulate the mousedown event
simulateMouseEvent(event, 'mousedown');
};
/**
* Handle the jQuery UI widget's touchmove events
* @param {Object} event The document's touchmove event
*/
mouseProto._touchMove = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Interaction was not a click
this._touchMoved = true;
// Simulate the mousemove event
simulateMouseEvent(event, 'mousemove');
};
/**
* Handle the jQuery UI widget's touchend events
* @param {Object} event The document's touchend event
*/
mouseProto._touchEnd = function (event) {
// Ignore event if not handled
if (!touchHandled) {
return;
}
// Simulate the mouseup event
simulateMouseEvent(event, 'mouseup');
// Simulate the mouseout event
simulateMouseEvent(event, 'mouseout');
// If the touch interaction did not move, it should trigger a click
if (!this._touchMoved) {
// Simulate the click event
simulateMouseEvent(event, 'click');
}
// Unset the flag to allow other widgets to inherit the touch event
touchHandled = false;
};
/**
* A duck punch of the $.ui.mouse _mouseInit method to support touch events.
* This method extends the widget with bound touch event handlers that
* translate touch events to mouse events and pass them to the widget's
* original mouse event handling methods.
*/
mouseProto._mouseInit = function () {
var self = this;
// Delegate the touch handlers to the widget's element
self.element.bind({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse init method
_mouseInit.call(self);
};
/**
* Remove the touch event handlers
*/
mouseProto._mouseDestroy = function () {
var self = this;
// Delegate the touch handlers to the widget's element
self.element.unbind({
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse destroy method
_mouseDestroy.call(self);
};
})(jQuery);

View File

@ -0,0 +1,787 @@
(function() {
// var AgileBoard = function() {};
var PlanningBoard = function() {};
PlanningBoard.prototype = {
init: function(routes) {
var self = this;
self.routes = routes;
$(function() {
self.initSortable();
});
},
// If there are no changes
backSortable: function($oldColumn) {
$oldColumn.sortable('cancel');
},
successSortable: function($oldColumn, $column) {
clearErrorMessage();
var r = new RegExp(/\d+/)
var ids = [];
ids.push({
column: $column,
id: $column.data('id'),
to: true
});
ids.push({
column: $oldColumn,
id: $oldColumn.data('id'),
from: true
});
for (var i = 0; i < ids.length; i++) {
var current = ids[i];
var headerSelector = '.version-planning-board thead tr th[data-column-id="' + current.id + '"]';
var $columnHeader = $(headerSelector);
var columnText = $columnHeader.text();
var currentIssuesAmount = ~~columnText.match(r);
currentIssuesAmount = (current.from) ? currentIssuesAmount - 1 : currentIssuesAmount + 1;
$columnHeader.text(columnText.replace(r, currentIssuesAmount));
}
},
errorSortable: function(responseText) {
var alertMessage = parseErrorResponse(responseText);
if (alertMessage) {
setErrorMessage(alertMessage);
};
},
initSortable: function() {
var self = this;
var $issuesCols = $(".column-issues");
$issuesCols.sortable({
connectWith: ".column-issues",
start: function(event, ui) {
var $item = $(ui.item);
$item.attr('oldColumnId', $item.parent().data('version-id'));
$item.attr('oldSprintId', $item.parent().data('sprint-id'));
$item.attr('oldPosition', $item.index());
},
stop: function(event, ui) {
var $item = $(ui.item);
var $column = $item.parents('.column-issues');
var issue_id = $item.data('id');
var version_id = $column.attr('data-version-id');
var sprint_id = $column.attr('data-sprint-id');
var positions = {};
var oldId = $item.attr('oldColumnId');
var $oldColumn = $('.ui-sortable[data-version-id="' + oldId + '"]');
if(!self.hasChange($item)){
self.backSortable($column);
return;
}
$column.find('.issue-card').each(function(i, e) {
var $e = $(e);
positions[$e.data('id')] = { position: $e.index() };
});
var issueParams = {};
if (version_id != undefined) issueParams['fixed_version_id'] = version_id || "";
if (sprint_id != undefined) issueParams['sprint_id'] = sprint_id || "";
$.ajax({
url: self.routes.update_agile_board_path,
type: 'PUT',
data: {
issue: issueParams,
positions: positions,
id: issue_id
},
success: function(data, status, xhr) {
self.successSortable($oldColumn, $column);
},
error: function(xhr, status, error) {
self.errorSortable(xhr.responseText);
self.backSortable($oldColumn);
}
});
}
}).disableSelection();
$issuesCols.sortable('option', 'cancel', 'div.pagination-wrapper');
},
hasChange: function($item){
var column = $item.parents('.column-issues');
return $item.attr('oldColumnId') != column.data('version-id') || // Checks a version change
$item.attr('oldSprintId') != column.data('sprint-id') || // Checks a sprint change;
$item.attr('oldPosition') != $item.index()
},
}
function AgileBoard(routes){
// ----- estimated hours ------
this.recalculateEstimateHours = function(oldStatusId, newStatusId, value){
oldStatusElement = $('th[data-column-id="' + oldStatusId + '"]');
newStatusElement = $('th[data-column-id="' + newStatusId + '"]');
oldStatusElement.each(function(i, elem){
changeHtmlNumber(elem, -value);
});
newStatusElement.each(function(i, elem){
changeHtmlNumber(elem, value);
});
};
this.successSortable = function(oldStatusId, newStatusId, oldSwimLaneId, newSwimLaneId) {
clearErrorMessage();
};
// If there are no changes
this.backSortable = function($oldColumn) {
$oldColumn.sortable('cancel');
};
this.errorSortable = function($oldColumn, responseText) {
var alertMessage = parseErrorResponse(responseText);
if (alertMessage) {
setErrorMessage(alertMessage);
}
};
this.initSortable = function() {
var self = this;
var $issuesCols = $(".issue-status-col");
$issuesCols.sortable({
items: '.issue-card',
connectWith: ".issue-status-col",
start: function(event, ui) {
var $item = $(ui.item);
$item.attr('oldColumnId', $item.parent().data('id'));
$item.attr('oldSwimLaneId', $item.parents('tr.swimlane').data('id'));
$item.attr('oldSwimLaneField', $item.parents('tr.swimlane').attr('data-field'));
$item.attr('oldPosition', $item.index());
},
stop: function(event, ui) {
var that = this;
var $item = $(ui.item);
var sender = ui.sender;
var $column = $item.parents('.issue-status-col');
var $swimlane = $item.parents('tr.swimlane');
var issue_id = $item.data('id');
var newStatusId = $column.data("id");
var order = $column.sortable('serialize');
var swimLaneId = $swimlane.data('id')
var swimLaneField = $swimlane.attr('data-field');
var positions = {};
var oldStatusId = $item.attr('oldColumnId');
var oldSwimLaneId = $item.attr('oldSwimLaneId');
var oldSwimLaneField = $item.attr('oldSwimLaneField');
var $oldColumn = $('.ui-sortable[data-id="' + oldStatusId + '"]');
var $sprintField = $('#sprint_id');
if(!self.hasChange($item)){
self.backSortable($column);
return;
}
$('.lock').show();
if ($column.hasClass("closed")){
$item.addClass("float-left")
}
else{
$item.removeClass("closed-issue");
$item.removeClass("float-left")
}
$column.find('.issue-card').each(function(i, e) {
var $e = $(e);
positions[$e.data('id')] = { position: $e.index() };
});
var params = {
issue: {
status_id: newStatusId
},
positions: positions,
id: issue_id
}
params['issue'][swimLaneField] = swimLaneId;
if ($sprintField) {
if (oldStatusId == '' && newStatusId != '') {
params['issue'].sprint_id = $sprintField.val();
}
if (oldStatusId != '' && newStatusId == '') {
delete(params['issue'].status_id)
params['issue'].sprint_id = '';
}
}
$.ajax({
url: self.routes.update_agile_board_path,
type: 'PUT',
data: params,
success: function(data, status, xhr) {
self.successSortable(oldStatusId, newStatusId, oldSwimLaneId, swimLaneId);
$($item).replaceWith(data);
estimatedHours = $($item).find("span.hours");
if(estimatedHours.length > 0){
hours = $(estimatedHours).html().replace(/(\(|\)|h)?/g, '');
// self.recalculateEstimateHours(oldStatusId, newStatusId, hours);
}
},
error: function(xhr, status, error) {
self.errorSortable($oldColumn, xhr.responseText);
$(that).sortable( "cancel" );
},
complete: function(){
$('.lock').hide();
}
});
}
});
};
this.initDraggable = function() {
if ($("#group_by").val() != "assigned_to"){
$(".assignable-user").draggable({
helper: "clone",
start: function startDraggable(event, ui) {
$(ui.helper).addClass("draggable-active")
}
});
}
};
this.hasChange = function($item){
var column = $item.parents('.issue-status-col');
var swimlane = $item.parents('tr.swimlane');
return $item.attr('oldColumnId') != column.data('id') || // Checks the status change
$item.attr('oldSwimLaneId') != swimlane.data('id') ||
$item.attr('oldPosition') != $item.index();
};
this.initDroppable = function() {
var self = this;
$(".issue-card").droppable({
activeClass: 'droppable-active',
hoverClass: 'droppable-hover',
accept: '.assignable-user',
tolerance: 'pointer',
drop: function(event, ui) {
var $self = $(this);
$('.lock').show();
$.ajax({
url: self.routes.update_agile_board_path,
type: "PUT",
dataType: "html",
data: {
issue: {
assigned_to_id: ui.draggable.data("id")
},
id: $self.data("id")
},
success: function(data, status, xhr){
$self.replaceWith(data);
},
error:function(xhr, status, error) {
var alertMessage = parseErrorResponse(xhr.responseText);
if (alertMessage) {
setErrorMessage(alertMessage);
$self.find("p.assigned-user").remove();
}
},
complete: function(){
$('.lock').hide();
}
});
$self.find("p.info").show();
$self.find("p.info").html(ui.draggable.clone());
}
});
};
this.getToolTipInfo = function(node, url){
var issue_id = $(node).parents(".issue-card").data("id");
var tip = $(node).children(".tip");
if( $(tip).html() && $.trim($(tip).html()) != "")
return;
$.ajax({
url: url,
type: "get",
dataType: "html",
data: {
id: issue_id
},
success: function(data, status, xhr){
$(tip).html(data);
},
error:function(xhr, status, error) {
$(tip).html(error);
}
});
}
this.createIssue = function (url) {
$(".add-issue").click(function () {
$(this).children(".new-card__input").focus()
})
$(".new-card__input").keyup(function (evt) {
var node = this
var $sprintField = $("#sprint_id")
evt = evt || window.event
subject = $.trim($(node).val())
sprint_id = $sprintField ? $sprintField.val() : ""
if (evt.keyCode == 13 && subject.length != 0) {
$.ajax({
url: url,
type: "POST",
data: {
subject: subject,
status_id: $(node).parents("td").data("id"),
sprint_id: sprint_id
},
dataType: "html",
success: function (data, status, xhr) {
$(node).parent().before(data)
$(node).val("")
},
error: function (xhr, status, error) {
var alertMessage = parseErrorResponse(xhr.responseText)
if (alertMessage) {
setErrorMessage(alertMessage)
}
},
})
}
})
}
this.routes = routes;
this.initSortable();
this.initDraggable();
this.initDroppable();
this.createIssue(routes.create_issue_path);
}
window.AgileBoard = AgileBoard;
window.PlanningBoard = PlanningBoard;
$.fn.StickyHeader = function() {
return this.each(function() {
var
$this = $(this),
$body = $('body'),
$html = $body.parent(),
$hideButton = $body.find('#hideSidebarButton'),
$fullScreenButton = $body.find('.icon-fullscreen'),
$containerFixed,
$tableFixed,
$tableRows,
$tableFixedRows,
containerWidth,
offset,
tableHeight,
tableHeadHeight,
tableOffsetTop,
tableOffsetBottom,
tmp;
function init() {
$this.wrap('<div class="container-fixed" />');
$tableFixed = $this.clone();
$containerFixed = $this.parents('.container-fixed');
$tableFixed
.find('tbody')
.remove()
.end()
.addClass('sticky')
.insertBefore($this)
.hide();
}
function resizeFixed() {
containerWidth = $containerFixed.width();
tableHeadHeight = $this.find("thead").height() + 3;
$tableRows = $this.find('thead th');
$tableFixedRows = $tableFixed.find('th');
$tableFixed.css({'width': containerWidth});
$tableRows.each(function(i) {
tmp = jQuery(this).width();
jQuery($tableFixedRows[i]).css('width', tmp);
});
}
function scrollFixed() {
tableHeight = $this.height();
tableHeadHeight = $this.find("thead").height();
offset = $(window).scrollTop();
tableOffsetTop = $this.offset().top;
tableOffsetBottom = tableOffsetTop + tableHeight - tableHeadHeight;
resizeFixed();
// The first breakpoint to add responsiveness is 899px
var headerHeight = $(window).width() < 900 ? $('#header').height() : 0;
var tablePositionTop= tableOffsetTop - headerHeight;
var tablePositionBottom= tableOffsetBottom - headerHeight;
if (offset < tablePositionTop|| offset > tablePositionBottom) {
$tableFixed.css('display', 'none');
} else if (offset >= tablePositionTop && offset <= tablePositionBottom) {
$tableFixed.css('display', 'table');
// Fix for chrome not redrawing header
$tableFixed.css('z-index', '100');
}
}
function bindScroll() {
if ($html.hasClass('agile-board-fullscreen')) {
scrollFixed();
$('div.agile-board.autoscroll').scroll(scrollFixed);
$(window).unbind('scroll');
} else {
$(window).scroll(scrollFixed);
$('div.agile-board.autoscroll').unbind('scroll');
$tableFixed.hide();
}
}
$hideButton.click(function() {
resizeFixed();
});
$fullScreenButton.click(function() {
bindScroll();
});
$(window).resize(resizeFixed);
$(window).keyup(function(evt){
if (evt.keyCode == 27) {
$('html.agile-board-fullscreen').removeClass('agile-board-fullscreen');
$(".issue-card").addClass("hascontextmenu");
bindScroll();
saveFullScreenState();
}
}
);
init();
bindScroll();
});
};
})();
function parseErrorResponse(responseText){
try {
var errors = JSON.parse(responseText);
} catch(e) {
};
var alertMessage = '';
if (errors && errors.length > 0) {
for (var i = 0; i < errors.length; i++) {
alertMessage += errors[i] + '\n';
}
}
return alertMessage;
}
function setErrorMessage(message, flashClass) {
flashClass = flashClass || "error"
$('div#agile-board-errors').addClass("flash " + flashClass);
$('div#agile-board-errors').html(message).show();
setTimeout(clearErrorMessage,3000);
}
function clearErrorMessage() {
$('div#agile-board-errors').removeClass();
$('div#agile-board-errors').html('').hide();
}
function incHtmlNumber(element) {
$(element).html(~~$(element).html() + 1);
}
function decHtmlNumber(element) {
$(element).html(~~$(element).html() - 1);
}
function changeHtmlNumber(element, number){
elementWithHours = $(element).find("span.hours");
if (elementWithHours.size() > 0){
old_value = $(elementWithHours).html().replace(/(\(|\)|h)/);
new_value = parseFloat(old_value)+ parseFloat(number);
if (new_value > 0)
$(elementWithHours).html(new_value.toFixed(2) + "h");
else
$(elementWithHours).remove();
}
else{
new_value = number;
$(element).append("<span class='hours'>" + new_value + "h</span>");
}
}
function observeIssueSearchfield(fieldId, url) {
$('#'+fieldId).each(function() {
var $this = $(this);
$this.addClass('autocomplete');
$this.attr('data-value-was', $this.val());
var check = function() {
var val = $this.val();
if ($this.attr('data-value-was') != val){
var request_data = {}
$.map($('#query_form').serializeArray(), function(n, i){
if (request_data[n['name']]) {
if ($.isArray(request_data[n['name']])) {
request_data[n['name']].push(n['value'])
} else {
request_data[n['name']] = [request_data[n['name']], n['value']];
}
} else {
request_data[n['name']] = n['value'];
}
});
request_data['q'] = val
$this.attr('data-value-was', val);
$.ajax({
url: url,
type: 'get',
data: request_data,
beforeSend: function(){ $this.addClass('ajax-loading'); },
complete: function(){ $this.removeClass('ajax-loading'); }
});
}
};
var reset = function(e) {
if (timer) {
clearInterval(timer);
timer = setInterval(check, 300);
}
};
var skipSpecialKeys = function(e) {
if (e.keyCode === 13) { e.preventDefault() }
}
var timer = setInterval(check, 300);
var skipSubmit = function(e) {
if (e.which == 13 || e.keyCode == 13) {
e.preventDefault();
return false
}
}
$this.bind('keydown', skipSubmit);
$this.bind('keyup click mousemove', reset);
$this.bind('keydown', skipSpecialKeys);
});
}
function recalculateHours() {
$('.version-column').each(function (i, elem) {
var estimatedHours = 0;
var storyPoints = 0;
$(elem).find('.issue-card').each(function (j, issue) {
estimatedHours += parseFloat($(issue).data('estimated-hours'));
storyPoints += parseFloat($(issue).data('story-points'));
});
var values = [];
if (estimatedHours > 0) {
values.push(estimatedHours.toFixed(2) + 'h');
}
if (storyPoints > 0) {
values.push(storyPoints.toFixed(2) + 'sp');
}
if (values.length > 0) {
$(elem).find('.version-estimate').text('(' + values.join('/') + ')');
}
});
}
function recalculateSprintHours() {
var unit = $(".planning-board").data('estimated-unit');
var dataAttr = unit == 'sp' ? 'story-points' : 'estimated-hours';
$('.sprint-column').each(function(i, elem){
var versionEstimationSum = 0;
$(elem).find('.issue-card').each(function(j, issue){
hours = parseFloat($(issue).data(dataAttr));
versionEstimationSum += hours;
});
$(elem).find('.sprint-estimate').text('(' + versionEstimationSum.toFixed(2) + unit + ')');
});
}
function showInlineCommentNode(quick_comment){
if(quick_comment){
$(quick_comment).siblings(".last_comment").hide();
$(quick_comment).show();
$(quick_comment).children("textarea").focus();
}
}
function showInlineComment(node, url){
$(node).parent().toggleClass('hidden');
var quick_comment = $(node).parents(".fields").children(".quick-comment");
if ( $.trim($(quick_comment).html()) != '' ){
showInlineCommentNode(quick_comment);
}
else{
$.ajax({
url: url,
type: "get",
dataType: "html",
success: function(data, status, xhr){
$(quick_comment).html(data);
showInlineCommentNode(quick_comment);
},
error:function(xhr, status, error) {
var alertMessage = parseErrorResponse(xhr.responseText);
if (alertMessage) {
setErrorMessage(alertMessage);
}
}
})
};
}
function saveInlineComment(node, url){
var node = node;
var comment = $(node).siblings("textarea").val();
if ($.trim(comment) === "") return false;
$(node).prop('disabled', true);
$('.lock').show();
var card = $(node).parents(".issue-card");
var version_board = $('.planning-board').length;
$.ajax({
url: url,
type: "PUT",
dataType: "html",
data: { issue: { notes: comment }, version_board: version_board },
success: function(data, status, xhr){
$(card).replaceWith(data);
},
error: function(xhr, status, error){
var alertMessage = parseErrorResponse(xhr.responseText);
if (alertMessage) {
setErrorMessage(alertMessage);
}
},
complete: function(xhr, status){
$(node).prop('disabled', false);
$('.lock').hide();
}
});
}
function cancelInlineComment(node){
$(node).parent().hide();
$(node).parent().siblings(".last_comment").show();
$(node).parent().siblings('.quick-edit-card').toggleClass('hidden');
$(node).parent().html('');
return false;
}
function saveFullScreenState() {
state = $('html').hasClass('agile-board-fullscreen');
localStorage.setItem('full-screen-board', state);
};
$(document).ready(function(){
$('table.issues-board').StickyHeader();
$('div#agile-board-errors').click(function(){
$(this).animate({top: -$(this).outerHeight()}, 500);
});
$("#agile_live_search").keyup(function() {
var cards = $(".issues-board").find(".issue-card");
var searchTerm = this.value;
cards.removeClass("filtered");
cards.filter(function() {
return $(this).find(".name").text().toLowerCase().indexOf(searchTerm.toLowerCase()) === -1;
}).addClass("filtered");
});
});
function DisableNullFields() {
$('input').each(function(i) {
var $input = $(this);
if ($input.val() == '')
$input.attr('disabled', 'disabled');
}
);
};
function linkGenerator(path, text) {
return '<a href="' + window.location.origin + window.location.pathname + path + ' ">' + text + '</a>'
};
function linkableAttributeFields() {
var status_label = $('.status.attribute .label')
status_label.html(linkGenerator('/status', status_label.html()));
var assigned_label = $('.assigned-to.attribute .label')
assigned_label.html(linkGenerator('/assignee', assigned_label.html()));
var progress_label = $('.progress.attribute .label')
progress_label.html(linkGenerator('/done_ratio', progress_label.html()));
};
function chartLinkGenerator() {
var filter_values = $("#query_form").serialize();
event.preventDefault();
window.location.href = $('.agile_charts_link').prop('href') + '?' + filter_values;
}
function hideChartPeriodCheckbox() {
$("#cb_chart_period").hide();
$("label[for=cb_chart_period]").removeAttr("for");
};
function toggleChartUnit(chart, target) {
var showTarget = chartsWithUnits.indexOf(chart) > -1;
$('#' + target).toggle(showTarget);
};
function updateVersionAgileChart(url) {
$.ajax(url + '&chart=' + $('#chart_by_select').val() + '&chart_unit=' + $('#chart_unit').val());
};
function chartTooltipCallbacks(chartType) {
if (chartType === 'scatter') {
return scatterChartTooltipCallbacks()
} else {
return {}
}
};
function scatterChartTooltipCallbacks() {
return {
title: function (tooltipItem, data) {
return data.labels[tooltipItem[0].xLabel] || '';
},
label: function (tooltipItem, data) {
var label = data.datasets[tooltipItem.datasetIndex].label || '';
if (label) { label += ': ' }
label += tooltipItem.yLabel;
return label;
}
}
};

View File

@ -0,0 +1,14 @@
function setAgileSprintEndDate(){
var start_date = new Date($("#agile_sprint_start_date").val());
var end_date = new Date(start_date);
var duration = this.value;
if (start_date && duration >= 1) {
end_date.setDate(start_date.getDate() + duration * 7);
$("#agile_sprint_end_date").val(end_date.getFullYear() + '-' + ('0' + (end_date.getMonth() + 1)).slice(-2) + '-' + ('0' + end_date.getDate()).slice(-2));
$("#agile_sprint_duration").val(null);
}
}
$(document).ready(function(){
$('#content').on('change', '#agile_sprint_duration', setAgileSprintEndDate);
});

View File

@ -0,0 +1,88 @@
/*
* Very simple jQuery Color Picker
* https://github.com/tkrotoff/jquery-simplecolorpicker
*
* Copyright (C) 2012-2013 Tanguy Krotoff <tkrotoff@gmail.com>
*
* Licensed under the MIT license
*/
/**
* Inspired by Bootstrap Twitter.
* See https://github.com/twbs/bootstrap/blob/master/less/navbar.less
* See https://github.com/twbs/bootstrap/blob/master/less/dropdowns.less
*/
.simplecolorpicker.picker {
position: absolute;
top: 100%;
left: 0;
z-index: 1051; /* Above Bootstrap modal (@zindex-modal = 1050) */
display: none;
float: left;
min-width: 160px;
max-width: 283px; /* @popover-max-width = 276px + 7 */
padding: 5px 0 0 5px;
margin: 2px 0 0;
list-style: none;
background-color: #fff; /* @dropdown-bg */
border: 1px solid #ccc;
}
.simplecolorpicker.inline {
display: inline-block;
}
.simplecolorpicker span {
margin: 0 5px 5px 0;
}
.simplecolorpicker.button,
.simplecolorpicker span.color {
display: inline-block;
outline: none;
cursor: pointer;
border: 1px solid transparent;
}
.simplecolorpicker.button {
border: 1px solid #DDD;
}
.simplecolorpicker.button:after,
.simplecolorpicker span.color:after {
content: '\00a0\00a0\00a0\00a0'; /* Spaces */
}
.simplecolorpicker span.color[data-disabled]:hover {
cursor: not-allowed;
border: 1px solid transparent;
}
.simplecolorpicker span.color:hover,
.simplecolorpicker span.color[data-selected],
.simplecolorpicker span.color[data-selected]:hover {
border: 1px solid #222; /* @gray-dark */
}
.simplecolorpicker span.color[data-selected]:after {
color: #fff;
}
/* Vertical separator, replaces optgroup. */
.simplecolorpicker span.vr {
border-left: 1px solid #222; /* @gray-dark */
}
.simplecolorpicker span.color[data-selected]:after {
/*font-family: 'FontAwesome';*/
-webkit-font-smoothing: antialiased;
content: '\2714'; /* Ok/check mark */
margin-right: 2px;
margin-left: 2px;
}

View File

@ -0,0 +1,610 @@
/**********************************************************************/
/* ICONS
/**********************************************************************/
#admin-menu a.agile { background-image: url(../images/agile.png);}
.icon-fullscreen { background-image: url(../images/fullscreen.png); }
/**********************************************************************/
/* FULLSCREEN
/**********************************************************************/
html.agile-board-fullscreen {
overflow: hidden;
}
html.agile-board-fullscreen div.agile-board {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 20;
background: white;
overflow-y:scroll;
}
html.agile-board-fullscreen table.list.issues-board {
min-height: 100%;
top: 0px;
left: 0px;
right: 0px;
}
html.agile-board-fullscreen table.list.issues-board.sticky { min-height: auto; }
html.agile-board-fullscreen .icon-fullscreen {
position: fixed;
right: 5px;
top: 5px;
z-index: 21;
text-indent: -9999px;
white-space: nowrap;
overflow: hidden;
}
div.agile-board.autoscroll {
overflow: visible;
position: relative;
}
/**********************************************************************/
/* BOARD SETTINGS
/**********************************************************************/
table.options tr > td {
white-space: nowrap;
}
.agile_options_field {
display: inline-block;
min-width: 30%;
}
.agile_options_label {
display: inline-block;
min-width: 100px;
}
.selected_sprint {
font-size: 14px;
}
.selected_sprint > select {
font-size: 14px;
}
.card-fields .floating {
text-align: left;
width: 200px;
float: left;
overflow: hidden;
word-wrap: break-word;
}
.card-fields .floating label span {
width: 110px;
text-overflow: ellipsis;
display: inline-block;
white-space: nowrap;
overflow: hidden;
vertical-align: middle;
}
.card-fields .floating .wp_input{
width: 50px;
padding: 1px 5px !important;
}
.card-fields .floating .wp_input:not(:focus) {
background-color: transparent;
border-color: transparent;
font-size: 90%;
}
/**********************************************************************/
/* ISSUES SIDEBAR
/**********************************************************************/
#sidebar ul {
margin: 0;
padding: 0;
}
#sidebar ul li {
list-style-type: none;
margin: 0px 2px 0px 0px;
padding: 0px 0px 0px 0px;
}
/**********************************************************************/
/* PLANNING BOARD
/**********************************************************************/
table.versions-planning-board {
border-spacing: 10px;
width: 100%;
}
table.versions-planning-board td.issue-version-col {
vertical-align: top;
height: 400px;
}
table.list.versions-planning-board input#search.autocomplete {width: 80%;}
table.list.versions-planning-board tbody tr,
table.list.versions-planning-board tbody tr:hover {background-color: white;}
table.list.versions-planning-board .header-hours {
float: right;
vertical-align: baseline;
height: 26px;
line-height: 26px;
}
/**********************************************************************/
/* AGILE BOARD
/**********************************************************************/
table.list.issues-board {table-layout: fixed;}
table.list.issues-board th {overflow: hidden; text-overflow: ellipsis;}
.agile-board table.list.issues-board tbody tr,
.agile-board table.list.issues-board tbody tr:hover {background-color: white;}
.assignable-user.draggable-active {
padding: 5px;
border: 1px solid #D5D5D5;
background-color: #ffffdd;
}
table.issues-board .issue-status-col.empty { padding-bottom: 30px }
table.issues-board td.issue-status-col.closed {background-color: #FAFAFA;}
table.issues-board tr.group.swimlane {height: 30px;}
table.issues-board tr.group.swimlane td {border-top: 0px;border-bottom: 1px solid #ccc;}
table.issues-board tbody td,
table.issues-board tbody tr.issue:hover td {border: 0px; vertical-align: top;}
table.issues-board.minimize-closed td.issue-status-col.closed .issue-card {
width: 10px;
float: left;
}
table.issues-board.minimize-closed td.issue-status-col.closed .issue-card span.fields {display: none;}
/**********************************************************************/
/* ISSUE CARD
/**********************************************************************/
.issue-card {
padding: 5px;
border: solid 1px #d5d5d5;
background-color: #ffffdd;
margin: 5px;
word-wrap: break-word;
text-align: left;
white-space: normal;
cursor: pointer;
position: relative;
}
.issues-board td.issue-status-col.closed .issue-card.closed-issue {float: left;}
.issues-board td.issue-status-col.closed.collapse .issue-card.float-left {float: left; position: relative;}
.issue-card.closed-issue {white-space: nowrap; display: inline-block;}
.issue-card .tip{ position: fixed; white-space: normal; }
.issue-card:not(.context-menu-selection) .attributes,
.issue-card:not(.context-menu-selection) span.hours {color: #888;}
.issue-card .attributes,
.issue-card span.hours {font-size: 90%;}
.issue-card span.hours {float: right;}
.issue-card .thumbnail {
height: 145px;
background-size: auto;
background-position: center;
background-repeat: no-repeat;
margin: 5px;
}
.issue-card .checkbox {display: none;}
.issue-card .avatar {float: left; margin-right: 5px;}
.issue-card p.name {font-weight: bold;}
.issue-card p.project {
border: 1px solid #d5d5d5;
padding: 5px;
margin-bottom: 5px;
text-align: center;
background-color: white;
}
.issue-card .info {
border-top: 1px solid #d5d5d5;
padding-top: 5px;
margin-top: 5px;
}
.issue-card table.progress {
float: none;
margin-top: 5px;
width: 100%;
}
.issue-card table.progress td {
height: 5px;
padding: 0px;
}
.issue-card li.task-closed {
text-decoration: line-through;
color: #999;
}
.issue-card div.sub-issues {
border-top: 1px solid #d5d5d5;
padding-top: 5px;
margin-top: 5px;
font-size: 90%;
}
.issue-card div.sub-issues ul {
margin: 1px 0px 5px 0px;
padding-left: 30px;
}
.issue-card .issue-id.without-tracker {
float: right;
}
.issue-card.context-menu-selection p.project {
color: black;
}
.issue-card.context-menu-selection .attributes,
.issue-card.context-menu-selection em.info {
color: white;
}
.issues-board td.issue-status-col.closed .issue-card {background-color: #EDEDED;}
.issue-card.ui-sortable-helper {
-moz-transform: rotate(5deg); /* Для Firefox */
-ms-transform: rotate(5deg); /* Для IE */
-webkit-transform: rotate(5deg); /* Для Safari, Chrome, iOS */
-o-transform: rotate(5deg); /* Для Opera */
transform: rotate(5deg);
box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.05);
-moz-box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.05);
-webkit-box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.05);
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
cursor: grabbing;
}
.issue-card.br-red {border-left: 5px solid red;}
.issue-card.br-green {border-left: 5px solid green;}
.issue-card.br-blue {border-left: 5px solid blue;}
.issue-card.br-turquoise {border-left: 5px solid turquoise;}
.issue-card.br-lightgreen {border-left: 5px solid lightgreen;}
.issue-card.br-yellow {border-left: 5px solid yellow;}
.issue-card.br-orange {border-left: 5px solid orange;}
.issue-card.br-purple {border-left: 5px solid purple;}
.issue-card.br-gray {border-left: 5px solid gray;}
table.list.issues tr.issue.br-red, .issue-card.br-red {border-left: 5px solid red;}
table.list.issues tr.issue.br-green, .issue-card.br-green {border-left: 5px solid green;}
table.list.issues tr.issue.br-blue, .issue-card.br-blue {border-left: 5px solid blue;}
table.list.issues tr.issue.br-turquoise, .issue-card.br-turquoise {border-left: 5px solid turquoise;}
table.list.issues tr.issue.br-lightgreen, .issue-card.br-lightgreen {border-left: 5px solid lightgreen;}
table.list.issues tr.issue.br-yellow, .issue-card.br-yellow {border-left: 5px solid yellow;}
table.list.issues tr.issue.br-orange, .issue-card.br-orange {border-left: 5px solid orange;}
table.list.issues tr.issue.br-purple, .issue-card.br-purple {border-left: 5px solid purple;}
table.list.issues tr.issue.br-gray, .issue-card.br-gray {border-left: 5px solid gray;}
.issue-card.bk-red {background-color: #FFE2E3; border-color: rgb(255, 201, 201);}
.issue-card.bk-green {background-color: #DFFFCC; border-color: rgb(190, 239, 190);}
.issue-card.bk-blue {background-color: #DCE7FF; border-color: rgb(189, 189, 233);}
.issue-card.bk-turquoise {background-color: #C4FDFF; border-color: rgb(151, 222, 214);}
.issue-card.bk-lightgreen {background-color: #D2FFEF; border-color: rgb(184, 228, 226);}
.issue-card.bk-yellow {background-color: #FFFD9C; border-color: rgb(234, 234, 94);}
.issue-card.bk-orange {background-color: #FFDBBA; border-color: rgb(255, 202, 105);}
.issue-card.bk-purple {background-color: #EFDFFC; border-color: rgb(233, 186, 233);}
.issue-card.bk-gray {background-color: #e1e1e1; border-color: rgb(198, 198, 198);}
div.issue.details.br-red div.subject div > h3::before, div.issue.details.bk-red div.subject div > h3::before, a.issue.bk-red::before, a.issue.br-red::before {content: "\25CF "; color: red;}
div.issue.details.br-green div.subject div > h3::before, div.issue.details.bk-green div.subject div > h3::before, a.issue.bk-green::before, a.issue.br-green::before {content: "\25CF "; color: green;}
div.issue.details.br-blue div.subject div > h3::before, div.issue.details.bk-blue div.subject div > h3::before, a.issue.bk-blue::before, a.issue.br-blue::before {content: "\25CF "; color: blue;}
div.issue.details.br-turquoise div.subject div > h3::before, div.issue.details.bk-turquoise div.subject div > h3::before, a.issue.bk-turquoise::before, a.issue.br-turquoise::before {content: "\25CF "; color: turquoise;}
div.issue.details.br-lightgreen div.subject div > h3::before, div.issue.details.bk-lightgreen div.subject div > h3::before, a.issue.bk-lightgreen::before, a.issue.br-lightgreen::before {content: "\25CF "; color: lightgreen;}
div.issue.details.br-yellow div.subject div > h3::before, div.issue.details.bk-yellow div.subject div > h3::before, a.issue.bk-yellow::before, a.issue.br-yellow::before {content: "\25CF "; color: yellow;}
div.issue.details.br-orange div.subject div > h3::before, div.issue.details.bk-orange div.subject div > h3::before, a.issue.bk-orange::before, a.issue.br-orange::before {content: "\25CF "; color: orange;}
div.issue.details.br-purple div.subject div > h3::before, div.issue.details.bk-purple div.subject div > h3::before, a.issue.bk-purple::before, a.issue.br-purple::before {content: "\25CF "; color: purple;}
div.issue.details.br-gray div.subject div > h3::before, div.issue.details.bk-gray div.subject div > h3::before, a.issue.bk-gray::before, a.issue.br-gray::before {content: "\25CF "; color: gray;}
table.list.issues tr.issue:not(.context-menu-selection).bk-red td.id a {background-color: #FFE2E3; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-green td.id a {background-color: #DFFFCC; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-blue td.id a {background-color: #DCE7FF; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-turquoise td.id a {background-color: #C4FDFF; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-lightgreen td.id a {background-color: #D2FFEF; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-yellow td.id a {background-color: #FFFD9C; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-orange td.id a {background-color: #FFDBBA; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-purple td.id a {background-color: #EFDFFC; padding: 2px; border: 1px solid #DDD;}
table.list.issues tr.issue:not(.context-menu-selection).bk-gray td.id a {background-color: #e1e1e1; padding: 2px; border: 1px solid #DDD;}
/**********************************************************************/
/* ISSUE CARD CHECKLIST
/**********************************************************************/
.issue-card div.checklist {
border-top: 1px solid #d5d5d5;
padding-top: 5px;
margin-top: 5px;
font-size: 90%;
}
.issue-card div.checklist ul {
list-style: none;
padding: 0px;
margin: 0px;
}
.issue-card div.checklist input[type=checkbox] {
margin: 0px;
}
/**********************************************************************/
/* ISSUE CARD QUICKEDIT
/**********************************************************************/
.issue-card div.quick-edit-card {
position: absolute;
right: 2px;
bottom: 2px;
border: 1px solid #ddd;
background-color: white;
height: 17px;
padding: 1px 1px 0px 1px;
margin: 0px;
display: none;
}
.issue-card div.quick-edit-card a {
float: left;
}
.issue-card:hover div.quick-edit-card.hidden{
display: none;
}
.issue-card:hover div.quick-edit-card {
display: block;
cursor: default;
opacity: 0.5;
}
.issue-card:hover div.quick-edit-card:hover { opacity: 1;}
.issue-card .quick-comment {
display: none;
margin-top: 5px;
padding-right: 5px;
}
.issue-card .quick-comment textarea{
width: 100%;
}
.issue-card .quick-edit {
margin-right: 3px;
}
.issue-card .last-comment{
font-style: italic;
}
.issues-board .add-issue{
background: transparent;
border: 1px dashed rgba(48, 59, 77, 0.3);
padding: 5px;
margin: 5px;
}
.issues-board .add-issue .new-card__input{
outline: none;
width: 100%;
border: 0px;
background: transparent;
position: relative;
text-align: center;
}
/**********************************************************************/
/* ISSUES BOARD
/**********************************************************************/
.issues-board td.issue-status-col.droppable-hover, .issues-board td.issue-status-col .issue-card.droppable-hover {
border-style: dotted;
background-color: #E9F8FD;
}
table.list thead tr th span.hours {color: #888; float: right; font-size: 90%; font-weight: normal;}
table.issues-board thead th {white-space: normal;}
/*
tr.issue.br-red td.id::before,
tr.issue.br-green td.id::before,
tr.issue.br-blue td.id::before,
tr.issue.br-turquoise td.id::before,
tr.issue.br-lightgreen td.id::before,
tr.issue.br-yellow td.id::before,
tr.issue.br-orange td.id::before,
tr.issue.br-purple td.id::before,
tr.issue.br-gray td.id::before,
*/
.wp_input {
margin-left: 2px;
}
table.issues-board thead th span.count span.under_wp_limit {color: #008000;}
table.issues-board thead th span.count span.over_wp_limit {color: #A50000;}
table.issues-board thead th.under_wp_limit {background-color: #D4F1D4;}
table.issues-board thead th.over_wp_limit {background-color: #FFE9E9;}
table.issues-board tbody td.under_wp_limit {background-color: #EFFFEF;}
table.issues-board tbody td.over_wp_limit {background-color: #FFF6F6;}
.add-issue{
background: transparent;
border: 1px dashed rgba(48, 59, 77, 0.3);
margin: 5px;
}
.add-issue .new-card__input{
outline: none;
width: 100%;
border: 0px;
background: transparent;
position: relative;
text-align: center;
}
table.issues-board tbody td.over_wp_limit {background-color: #FFF6F6;}
.lock {
display: none;
position: absolute;
width: 100%;
height: 100%;
z-index: 9999;
opacity: 0.9;
}
.agile-board-fullscreen .lock{
position: fixed;
}
.agile-chart-container {
margin: auto;
min-height: 400px;
max-width: 1200px;
}
.issue-card.filtered {
opacity: 0.2;
}
table.list.issues-board.sticky {
display: table;
top: 0px;
position: fixed;
}
@media screen and (max-width: 899px) {
html.agile-board-fullscreen div.agile-board {
padding: 64px 0 0; /* padding-top equals header height */
}
html.agile-board-fullscreen .icon-fullscreen {
top: 64px; /* top equals header height */
}
html.agile-board-fullscreen table.list.issues-board.sticky { top: 64px; }
table.list.issues-board.sticky { top: 64px; }
html.agile-board-fullscreen #wrapper > div.flyout-menu.js-flyout-menu { z-index: 22; }
}
.planning-board {
display: flex;
overflow-x: auto;
}
.planning-board p {
margin: 0;
}
.planning-board .column-content {
width: 272px;
min-width: 272px;
margin: 0 4px 4px 4px;
background-color: #f6f7f8;
border: 1px solid #d7d7d7;
}
.planning-board .column-header {
display: block;
justify-content: space-between;
align-items: center;
min-height: 28px;
padding: 5px;
background-color: #eeeeee;
border-bottom: 1px solid #d7d7d7;
}
.planning-board .column-header .version-name {
color: #555;
font-size: 90%;
font-weight: bold;
}
.planning-board .column-issues {
padding-bottom: 30px;
}
.planning-board .closed-issue {
color: #999;
display: block;
white-space: inherit;
background-color: #ededed;
border-color: #d7d7d7;
}
.planning-board .closed-issue p.name a, .planning-board .closed-issue p.issue-id {
color: #999;
text-decoration: line-through;
}
.issue-edit-modal {
min-height: initial !important;
}
.modal-action-button {
height: 28px;
}
.agile_sprint_list .spring_column { text-align: center; }
.controller-agile_boards .query-totals {
margin-top: -2.3em;
}
.agile-board-fullscreen .controller-agile_boards .query-totals {
margin: 0px;
position: fixed;
top: 0px;
background: inherit;
left: 0;
right: 0;
padding-right: 32px;
min-height: 20px;
line-height: 20px;
}
.backlog-column-header {
min-width: 15%;
border-right: solid 3px #d7d7d7;
}
.issue-backlog-search { padding: 5px 5px 0px 5px; }
.issue-backlog-search input#search {
max-width: 100%;
}
.issues-board .backlog-column-header { background-color: #e1f1f8; }
.issues-board .issue-backlog-col {
background: #e1f1f8;
border-right: solid 3px #d7d7d7 !important;
}
html {
overflow-y: inherit !important;
}

233
config/locales/de.yml Normal file
View File

@ -0,0 +1,233 @@
# German strings go here for Rails i18n
de:
label_agile: Agiles Projektmanagement
label_agile_board: Agiles Taskboard
label_agile_board_plural: Agile Taskboards
label_agile_board_thumbnails: Vorschaubilder
label_agile_board_more_issues: Mehr Aufgaben
error_agile_status_transition: Aufgabenstatus konnte nicht geändert werden.
label_agile_board_new: Neues Taskboard
label_agile_board_edit: Taskboard bearbeiten
label_agile_my_boards: Meine Taskboards
label_agile_issue_id: Aufgaben-Nummer
permission_manage_public_agile_queries: Taskboards verwalten
permission_add_agile_queries: Taskboards hinzufügen
permission_view_agile_queries: Taskboards ansehen
#1.1.0
label_agile_board_default_fields: Standardfelder für Taskboard-Karten
label_agile_charts_issues_burndown: Burndown nach Aufgaben
label_agile_charts_work_burndown: Burndown nach erfassten Zeiten
label_agile_charts_work_burndown_sp: Burndown nach Story points
label_agile_charts_work_burndown_hours: Burndown nach Stunden
label_agile_charts_number_of_hours: Stunden
label_agile_charts_number_of_issues: Anzahl Aufgaben
label_agile_charts_cumulative_flow: Kumulativer Fluss
label_agile_charts_trackers_cumulative_flow: Kumulativer Fluss nach Tracker
label_agile_charts_issues_velocity: Velocity
label_agile_charts_cycle_time: Zyklus
label_agile_charts_lead_time: Durchlaufzeit
label_agile_charts_average_lead_time: Durchschnittliche Durchlaufzeit
label_agile_chart_plural: Agile Diagramme
permission_view_agile_charts: Agile Diagramme ansehen
label_agile_ideal_work_remaining: Soll
label_agile_actual_work_remaining: Ist
label_agile_chart: Diagramm
label_agile_date_from: Von
label_agile_date_to: Bis
label_agile_chart_dates: Intervall für das Diagramm
label_agile_weighed_ideal_work_remaining: Gewichtetes Soll
label_agile_status_colors: Status-Farben
label_agile_charts_burnup: Burnup nach Aufgaben
label_agile_charts_number_of_days: Anzahl Tage
label_agile_too_many_items: "Das Diagramm konnte nicht generiert werden, da es die maximale Anzahl an darstellbaren Aufgaben (%{max}) überschreiten würde."
label_agile_time_reports_items_limit: Maximale Anzahl an Aufgaben in Diagrammen
label_agile_total_work_remaining: Gesamt
label_agile_default_chart: Standarddiagramm für Versions
#1.1.1
label_agile_charts_average_velocity: Durchschnittliche Velocity
label_agile_charts_avarate_number_of_issues: Durchschnittliche Aufgabenanzahl
label_agile_charts_avarate_number_of_hours: Durchschnittliche Stundenanzahl
#1.2.0
label_agile_color: Farben
permission_manage_agile_verions: Versionplanung verwalten
label_agile_version_planning: Versionplanung
error_agile_version_transition: Version der Aufgabe konnte nicht verändert werden.
label_agile_tracker_colors: Farben für Tracker
label_agile_issue_priority_colors: Farben für Aufgabenprioritäten
label_agile_color_based_on: Taskboard-Karten einfärben basierend auf
label_agile_color_no_colors: Keine Farben
label_agile_manage_colors: Farben verwalten
label_agile_fullscreen: Vollbild
label_agile_no_version_issues: Aufgaben ohne Version
label_agile_charts_work_burnup: Burnup nach erfassten Zeiten
label_agile_completed: Abgeschlossen
label_agile_exclude_weekends: Wochenenden von Soll-Zeiten ausnehmen
label_agile_board_truncated: "Das Taskboard wurde abgeschnitten, da es die maximale Anzahl an darstellbaren Aufgaben (%{max}) überschreitet."
label_agile_board_items_limit: Aufgabenlimit für Taskboards
label_agile_swimlanes: Swimlanes
label_agile_minimize_closed: Geschlossene Aufgaben minimieren
label_agile_fields: Felder für Taskboard-Karten
label_agile_default_board: Standard-Taskboard
#1.3.6
text_agile_move_not_possible: Diese Änderung ist nicht möglich
#1.3.8
label_agile_parent_issue_tracker_id: Übergeordneter Tracker
label_agile_sub_issues: Unteraufgaben
label_agile_color_green: Grün
label_agile_color_blue: Blau
label_agile_color_turquoise: Türkis
label_agile_color_lightgreen: Hellgrün
label_agile_color_yellow: Gelb
label_agile_color_orange: Orange
label_agile_color_red: Rot
label_agile_color_purple: Lila
label_agile_color_gray: Grau
label_agile_has_sub_issues: Hat Unteraufgaben
label_agile_light_free_version: Agile Light freie Version
label_agile_link_to_pro: Upgrade auf PRO
label_agile_link_to_pro_demo: PRO version live demo
label_agile_link_to_more_plugins: Entdecken Sie mehr RedmineUP plugins
label_agile_button_agree: Einverstanden
label_agile_license: RedmineUP Lizenz
label_agile_saving_boards: Boards speichern
label_agile_horizontal_swim_lines: Horizontale Swimlanes
label_agile_board_sub_columns: Board-Unterspalten
label_agile_additional_agile_charts: Weitere Agile-Charts
label_agile_coloured_issue_cards: Farbige Karten
label_agile_4_more_features: 4 weitere Funktionen...
label_agile_upgrade_to_pro: Upgrade auf die PRO-Version um die Funktionen zu nutzen
label_agile_day_in_state: Zeit seit letzter Status-Änderung
#1.3.10
label_agile_hide_closed_issues_data: Daten von geschlossenen Aufgaben ausblenden
project_module_agile: Agile
#1.3.13
label_agile_last_comment: Letzter Kommentar
label_agile_esitmate_units: Einheit für Schätzungen
label_agile_trackers_for_sp: Tracker für Story Points
label_agile_story_points: Story Points
field_story_points: Story Points
label_agile_charts_number_of_story_points: Anzahl an Story Points
label_agile_board_columns: Taskboard Spalten
lable_agile_wip_limit_exceeded: Work-in-progress Grenze überschritten
label_agile_wip_limit: Work-in-progress Grenze
label_agile_add_new_issue: + NEUE AUFGABE
label_agile_allow_create_cards: Karte für neue Aufgabe
label_agile_auto_assign_on_move: Selbst zuweisen bei Taskboard-Karten-Bewegung
text_agile_create_issue_error: Die Aufgabe konnte nicht erstellt werden
label_agile_inline_comment: Inline-Kommentar
label_agile_hours: Stunden
field_color: Farbe
#1.4.0
label_agile_esitmate_units: Einheit für Schätzung
label_agile_trackers_for_sp: Tracker für Story points
label_agile_story_points: Story points
field_story_points: Story points
label_agile_charts_number_of_story_points: Anzahl Story points
label_agile_board_columns: Board Spalten
lable_agile_wip_limit_exceeded: Work-in-progress Limit überschritten
label_agile_wip_limit: Work-in-progress Limit
label_agile_add_new_issue: '+ NEUES TICKET'
label_agile_allow_create_cards: Ticket aus Taskboard erstellen
label_agile_auto_assign_on_move: Automatische Zuweisung bei
text_agile_create_issue_error: Beim Erstellen der Aufgabe ist ein Fehler aufgetreten
label_agile_inline_comment: Inline Kommentar
label_agile_hours: Stunden
field_color: Farbe
field_duration: Dauer
field_closed_on_trendline: Closed trendline
field_created_on_trendline: Created trendline
label_agile_sp_values: Story points Werte
label_agile_interval_size: Interval Größe
label_agile_day: Tag
label_agile_week: Woche
label_agile_month: Monat
label_agile_quarter: Quartal
label_agile_year: Jahr
label_cards_search: Suche nach Thema
field_agile_sprint: Sprint
field_end_date: Enddatum
label_agile_sprints_on: Aktiviere Sprints standardmäßig
label_agile_sprint: Sprint
label_agile_sprint_plural: Sprints
label_agile_sprint_name: Name
label_agile_sprint_description: Beschreibung
label_agile_sprint_status: Status
label_agile_sprint_start_date: Startdatum
label_agile_sprint_end_date: Enddatum
label_agile_sprint_new: Neuer Sprint
label_agile_sprint_status_open: Offen
label_agile_sprint_status_active: Aktiv
label_agile_sprint_status_closed: Geschlossen
label_agile_sprint_duration: Dauer
label_agile_sprint_duration_select: <<Wähle Dauer>>
label_agile_sprint_duration_week_1: 1 Woche
label_agile_sprint_duration_week_2: 2 Wochen
label_agile_sprint_duration_week_3: 3 Wochen
label_agile_sprint_duration_week_4: 4 Wochen
label_agile_sprint_errors_crossed: Sprint Daten überschneiden sich mit einem anderen existierenden Sprint
label_agile_sprint_errors_end_more_start: Das Enddatum sollte nach dem Startdatum liegen
label_agile_sprint_default_chart: Diagramm
label_agile_sprint_chart_units: Einheit für Schätzung
label_agile_chart_for_sprint: "Für Sprint: %{sprint}"
label_agile_charts: Diagramme
label_agile_chart_burndown: Burndown Diagramm
label_agile_chart_burnup: Burnup Diagramm
label_agile_chart_units: Einheiten
label_agile_planning_board_more: Lade mehr...
label_agile_version_query_new: Neue agile Abfrage
label_agile_version_my_boards: Meine agilen boards
label_agile_version_board_plural: Agile boards
label_agile_version_board_edit: Bearbeite agile Board version
label_agile_edit_issue: Bearbeite Ticket
label_agile_board_type: Art des Taskboards
label_agile_board_type_kanban: Kanban board
label_agile_board_type_scrum: Scrum board
label_agile_board_totals: Gesamt
label_agile_board_totals_story_points: Story points
label_agile_board_totals_hours: Stunden
label_agile_board_totals_spent_time: Aufgewendete Zeit
label_agile_board_totals_percent_done: Prozent erledigt
label_agile_board_totals_velocity: Velocity
label_agile_board_totals_interval: Intervall
label_agile_board_totals_remaining: Verbleibend
label_agile_sprint_list_active: Aktive Sprints
label_agile_sprint_list_future: Zukünftige Sprints
label_agile_mixed_trackers: Verschiedene Tracker
label_agile_moving_average: Gleitender Durchschnitt
label_agile_board_backlog: Backlog
label_agile_board_backlog_column: Backlog Spalten
label_agile_board_search_backlog_issues: Durchsuche Backlog Tickets
label_agile_version_plural: Versionen
label_agile_sprint_add: Sprint hinzufügen
label_agile_version_add: Version hinzufügen
label_agile_sprint_query_new: Neue agile Sprint Abfragen
label_agile_sprint_my_boards: Meine agilen Sprint boards
label_agile_sprint_board_plural: Agile Sprint boards
label_agile_sprint_board_edit: Bearbeite agiles Sprint board
field_sprint: Sprint
label_agile_no_sprint_issues: Tickets ohne Sprint
field_closed_sprints: Geschlossenen Sprints
field_closed_versions: Geschlossene Versionen

231
config/locales/en.yml Executable file
View File

@ -0,0 +1,231 @@
# English strings go here for Rails i18n
en:
label_agile: Agile
label_agile_board: Agile board
label_agile_board_plural: Agile boards
label_agile_board_thumbnails: Thumbnails
label_agile_board_more_issues: More issues
error_agile_status_transition: Cant change issue status
label_agile_board_new: New agile board
label_agile_board_edit: Edit agile board
label_agile_my_boards: My agile boards
label_agile_issue_id: Issue ID
permission_manage_public_agile_queries: Manage public agile boards
permission_add_agile_queries: Add agile boards
permission_view_agile_queries: View agile boards
#1.1.0
label_agile_board_default_fields: Default card fields
label_agile_charts_issues_burndown: Issues burndown
label_agile_charts_work_burndown_hours: Hours burndown
label_agile_charts_work_burndown_sp: Story points burndown
label_agile_charts_number_of_hours: Number of hours
label_agile_charts_number_of_issues: Number of issues
label_agile_charts_cumulative_flow: Cumulative flow
label_agile_charts_trackers_cumulative_flow: Trackers cumulative flow
label_agile_charts_issues_velocity: Velocity
label_agile_charts_cycle_time: Cycle time
label_agile_charts_lead_time: Lead time
label_agile_charts_average_lead_time: Average lead time
label_agile_chart_plural: Agile charts
label_agile_chart_new: New agile chart
label_agile_chart_edit: Edit agile chart
permission_view_agile_charts: View agile charts
label_agile_ideal_work_remaining: Ideal
label_agile_actual_work_remaining: Actual
label_agile_chart: Chart
label_agile_date_from: From
label_agile_date_to: To
label_agile_chart_dates: Chart intervals
label_agile_weighed_ideal_work_remaining: Weighted ideal
label_agile_status_colors: Status colors
label_agile_charts_burnup: Issues burnup
label_agile_charts_number_of_days: Number of days
label_agile_too_many_items: "The chart can't be created because it exceeds the maximum number of items that can be displayed (%{max})"
label_agile_time_reports_items_limit: Time entries based charts issues limit
label_agile_total_work_remaining: Total
label_agile_default_chart: Default chart
#1.1.1
label_agile_charts_average_velocity: Average velocity
label_agile_charts_avarate_number_of_issues: Average number of issues
label_agile_charts_avarate_number_of_hours: Average number of hours
#1.2.0
label_agile_color: Color
permission_manage_agile_verions: Manage version planning
label_agile_version_planning: Version planning
error_agile_version_transition: Cant change issue version
label_agile_tracker_colors: Tracker colors
label_agile_issue_priority_colors: Issue priority colors
label_agile_color_based_on: Colored by
label_agile_color_no_colors: No colors
label_agile_manage_colors: Manage colors
label_agile_fullscreen: Full screen
label_agile_no_version_issues: Issues without version
label_agile_charts_work_burnup_hours: Hours burnup
label_agile_charts_work_burnup_sp: Story points burnup
label_agile_completed: Completed
label_agile_exclude_weekends: Exclude weekends from ideal effort
label_agile_board_truncated: "The board was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
label_agile_board_items_limit: Agile board items limit
label_agile_swimlanes: Swim lanes
label_agile_minimize_closed: Minimize closed issues
label_agile_fields: Card fields
label_agile_default_board: Default board
#1.3.6
text_agile_move_not_possible: That move is not possible
#1.3.8
label_agile_parent_issue_tracker_id: Parent tracker
label_agile_sub_issues: Sub issues
label_agile_color_green: Green
label_agile_color_blue: Blue
label_agile_color_turquoise: Turquoise
label_agile_color_lightgreen: Light green
label_agile_color_yellow: Yellow
label_agile_color_orange: Orange
label_agile_color_red: Red
label_agile_color_purple: Purple
label_agile_color_gray: Gray
label_agile_has_sub_issues: Has sub issues
label_agile_light_free_version: Agile Light free version
label_agile_link_to_pro: Upgrade to PRO
label_agile_link_to_pro_demo: PRO version live demo
label_agile_link_to_more_plugins: Find more RedmineUP plugins
label_agile_button_agree: Agree
label_agile_license: RedmineUP License
label_agile_saving_boards: Saving board
label_agile_horizontal_swim_lines: Horizontal swim lines
label_agile_board_sub_columns: Board sub-columns
label_agile_additional_agile_charts: Additional agile charts
label_agile_coloured_issue_cards: Coloured issue cards
label_agile_4_more_features: 4 more features...
label_agile_upgrade_to_pro: Upgrade to PRO version to use this features
label_agile_day_in_state: In status
#1.3.10
label_agile_hide_closed_issues_data: Hide closed issues data
project_module_agile: Agile
#1.3.13
label_agile_last_comment: Last comment
#1.4.0
label_agile_esitmate_units: Estimate units
label_agile_trackers_for_sp: Trackers for story points
label_agile_story_points: Story points
field_story_points: Story points
label_agile_charts_number_of_story_points: Number of story points
label_agile_board_columns: Board columns
lable_agile_wip_limit_exceeded: Work-in-progress limit exceeded
label_agile_wip_limit: Work-in-progress limit
label_agile_add_new_issue: '+ ADD NEW ISSUE'
label_agile_allow_create_cards: Card creation
label_agile_auto_assign_on_move: Auto assign on move
text_agile_create_issue_error: An error occurred while creating the task
label_agile_inline_comment: Inline comment
label_agile_hours: Hours
field_color: Color
field_duration: Duration
field_closed_on_trendline: Closed trendline
field_created_on_trendline: Created trendline
label_agile_sp_values: Story points values
label_agile_interval_size: Interval size
label_agile_day: Day
label_agile_week: Week
label_agile_month: Month
label_agile_quarter: Quarter
label_agile_year: Year
label_cards_search: Search by subject
field_agile_sprint: Sprint
field_end_date: End date
label_agile_sprints_on: Activate sprints by default
label_agile_sprint: Sprint
label_agile_sprint_plural: Sprints
label_agile_sprint_name: Name
label_agile_sprint_description: Description
label_agile_sprint_status: Status
label_agile_sprint_sharing: Sharing
label_agile_sprint_start_date: Start date
label_agile_sprint_end_date: End date
label_agile_sprint_new: New sprint
label_agile_sprint_status_open: Open
label_agile_sprint_status_active: Active
label_agile_sprint_status_closed: Closed
label_agile_sprint_duration: Duration
label_agile_sprint_duration_select: <<Select duration>>
label_agile_sprint_duration_week_1: 1 week
label_agile_sprint_duration_week_2: 2 weeks
label_agile_sprint_duration_week_3: 3 weeks
label_agile_sprint_duration_week_4: 4 weeks
label_agile_sprint_errors_crossed: Sprint dates are cross another existed sprint
label_agile_sprint_errors_end_more_start: End date should be more than start date
label_agile_sprint_errors_open_issues: Sprint with open issues can be closed
label_agile_sprint_default_chart: Сhart
label_agile_sprint_chart_units: Estimation
label_agile_chart_for_sprint: "For sprint: %{sprint}"
label_agile_charts: Charts
label_agile_chart_burndown: Burndown chart
label_agile_chart_burnup: Burnup chart
label_agile_chart_units: Units
label_agile_planning_board_more: Load more...
label_agile_version_query_new: New Agile versions query
label_agile_version_my_boards: My agile version boards
label_agile_version_board_plural: Agile version boards
label_agile_version_board_edit: Edit agile version board
label_agile_edit_issue: Edit issue
label_agile_board_type: Board type
label_agile_board_type_kanban: Kanban board
label_agile_board_type_scrum: Scrum board
label_agile_board_totals: Totals
label_agile_board_totals_story_points: Story points
label_agile_board_totals_hours: Hours
label_agile_board_totals_spent_time: Spent time
label_agile_board_totals_percent_done: Percent done
label_agile_board_totals_velocity: Velocity
label_agile_board_totals_interval: Interval
label_agile_board_totals_remaining: Remaining
label_agile_sprint_list_active: Active sprints
label_agile_sprint_list_future: Future sprints
label_agile_sprint_list_old: Old sprints
label_agile_mixed_trackers: Mixed trackers
label_agile_moving_average: Moving average
label_agile_board_backlog: Backlog
label_agile_board_backlog_column: Backlog column
label_agile_board_search_backlog_issues: Search backlog issues
label_agile_version_plural: Versions
label_agile_sprint_add: Add sprint
label_agile_version_add: Add version
label_agile_sprint_query_new: New agile sprints query
label_agile_sprint_my_boards: My agile sprint boards
label_agile_sprint_board_plural: Agile sprint boards
label_agile_sprint_board_edit: Edit agile sprint board
field_sprint: Sprint
label_agile_no_sprint_issues: Issues without sprint
field_closed_sprints: Closed sprints
field_closed_versions: Closed versions
label_agile_sprint_sharing_none: Not shared
label_agile_sprint_sharing_descendants: With subprojects
label_agile_sprint_sharing_hierarchy: With project hierarchy
label_agile_sprint_sharing_tree: With project tree
label_agile_sprint_sharing_system: With all projects

221
config/locales/es.yml Normal file
View File

@ -0,0 +1,221 @@
# Spanish strings go here for Rails i18n
es:
label_agile: Ágil
label_agile_board: Tablero ágil
label_agile_board_plural: Tableros ágiles
label_agile_board_thumbnails: Thumbnails
label_agile_board_more_issues: Más peticiones
error_agile_status_transition: No se puede cambiar el estado de la petición
label_agile_board_new: Nuevo tablero ágil
label_agile_board_edit: Editar tablero ágil
label_agile_my_boards: Mis tableros ágiles
label_agile_issue_id: ID de petición
permission_manage_public_agile_queries: Administrar tableros ágiles públicos
permission_add_agile_queries: Agregar tableros ágiles
permission_view_agile_queries: Ver tableros ágiles
#1.1.0
label_agile_board_default_fields: Columnas por defecto
label_agile_charts_issues_burndown: Peticiones burndown
label_agile_charts_work_burndown: Horas burndown
label_agile_charts_work_burndown_sp: Story points burndown
label_agile_charts_number_of_hours: Número de horas
label_agile_charts_number_of_issues: Número de peticiones
label_agile_charts_cumulative_flow: Flujo acumulado
label_agile_charts_trackers_cumulative_flow: Seguimiento de flujo acumulado
label_agile_charts_issues_velocity: Velocidad
label_agile_charts_cycle_time: Ciclo de tiempo
label_agile_charts_lead_time: Tiempo de ejecución
label_agile_charts_average_lead_time: Tiempo medio de entrega
label_agile_chart_plural: Gráficas Agile
label_agile_chart_new: Nueva gráfica Agile
label_agile_chart_edit: Editar gráfica Agile
permission_view_agile_charts: Ver gráfica Agile
label_agile_ideal_work_remaining: Ideal
label_agile_actual_work_remaining: Actual
label_agile_chart: Gráfica
label_agile_date_from: Desde
label_agile_date_to: a
label_agile_chart_dates: Gráfica de interválos
label_agile_weighed_ideal_work_remaining: Ideal ponderado
label_agile_status_colors: Estado de colores
label_agile_charts_burnup: Peticiones burnup
label_agile_charts_number_of_days: Número de días
label_agile_too_many_items: El gráfico no puede ser creado por exceder el número máximo de items que pueden ser mostrados (%{max})
label_agile_time_reports_items_limit: Tiempo de entradas basado en gráfico de peticiones límite
label_agile_total_work_remaining: Total
label_agile_default_chart: Versión gráfica por defecto
#1.1.1
label_agile_charts_average_velocity: Velocidad promedio
label_agile_charts_avarate_number_of_issues: Número de peticiones promedio
label_agile_charts_avarate_number_of_hours: Número de horas promedio
#1.2.0
label_agile_color: Color
permission_manage_agile_verions: Administrar planificación de versiones
label_agile_version_planning: planificación de versiones
error_agile_version_transition: No se puede cambiar la versión de la petición
label_agile_tracker_colors: Colores de tipos
label_agile_issue_priority_colors: Colores de prioridades de la petición
label_agile_color_based_on: Color por
label_agile_color_no_colors: Sin colores
label_agile_manage_colors: Administrar colores
label_agile_fullscreen: Pantalla completa
label_agile_no_version_issues: Petición sin version
label_agile_charts_work_burnup_hours: burnup de horas
label_agile_charts_work_burnup_sp: burnup de Story points
label_agile_completed: Completado
label_agile_exclude_weekends: Excluir los fines de semana del esfuerzo ideal
label_agile_board_truncated: El tablero se truncó porque excede el número máximo de elementos que se pueden mostrar (%{max})
label_agile_board_items_limit: Límite de elementos del tablero ágil
label_agile_swimlanes: Swimlanes
label_agile_minimize_closed: minimizar las peticiones cerradas
label_agile_fields: Campos de la tarjeta
label_agile_default_board: tablero por defecto
#1.3.6
text_agile_move_not_possible: Esté movimiento no es posible ()
#1.3.8
label_agile_parent_issue_tracker_id: Tipo petición padre
label_agile_sub_issues: SubTareas
label_agile_color_green: Verde
label_agile_color_blue: Azul
label_agile_color_turquoise: Turquesa
label_agile_color_lightgreen: Verde claro
label_agile_color_yellow: Amarillo
label_agile_color_orange: Naranja
label_agile_color_red: Rojo
label_agile_color_purple: Morado
label_agile_color_gray: Gris
label_agile_has_sub_issues: Tiene SubTareas
label_agile_light_free_version: Versión gratuita Agile
label_agile_link_to_pro: Actualizar a PRO
label_agile_link_to_pro_demo: demo version PRO
label_agile_link_to_more_plugins: Encontrar más extensiones de RedmineUP
label_agile_button_agree: Acuerdo
label_agile_license: Licencia RedmineUP
label_agile_saving_boards: Guardar tablero
label_agile_horizontal_swim_lines: swimlines horizontales
label_agile_board_sub_columns: sub-columns del tablero
label_agile_additional_agile_charts: Additional agile charts
label_agile_coloured_issue_cards: Tarjetas de colores
label_agile_4_more_features: 4 funcionalides más...
label_agile_upgrade_to_pro: Actualice a la version PRO apra usar estas funcionalidades
label_agile_day_in_state: En estado
#1.3.10
label_agile_hide_closed_issues_data: Ocultar datos de las peticiones cerradas
project_module_agile: Agile
#1.3.13
label_agile_last_comment: Último comentario
#1.4.0
label_agile_esitmate_units: Unidades estimadas
label_agile_trackers_for_sp: Tipos para story points
label_agile_story_points: Story points
field_story_points: Story points
label_agile_charts_number_of_story_points: Número de story points
label_agile_board_columns: Columnas del tablero
lable_agile_wip_limit_exceeded: Excedido límite de dedicación
label_agile_wip_limit: límite de dedicación
label_agile_add_new_issue: "+ AÑADIR NUEVA PETICION"
label_agile_allow_create_cards: Crear una tarjeta
label_agile_auto_assign_on_move: Auto-Asignar al mover
text_agile_create_issue_error: Se produjo un error al crear la petición
label_agile_inline_comment: Comentario en linea
label_agile_hours: Horas
field_color: Color
field_duration: Duración
field_closed_on_trendline: Trendline cerrada
field_created_on_trendline: Trendline creada
label_agile_sp_values: Valores Story points
label_agile_interval_size: Tamaño intervalo
label_agile_day: Día
label_agile_week: Semana
label_agile_month: Mes
label_agile_quarter: Trimestre
label_agile_year: Año
label_cards_search: Búsqueda por asunto
field_agile_sprint: Sprint
field_end_date: Fecha fin
label_agile_sprints_on: Activar sprints por defecto
label_agile_sprint: Sprint
label_agile_sprint_plural: Sprints
label_agile_sprint_name: Nombre
label_agile_sprint_description: Descripción
label_agile_sprint_status: Estado
label_agile_sprint_start_date: Fecha inicio
label_agile_sprint_end_date: Fecha fin
label_agile_sprint_new: nuevo sprint
label_agile_sprint_status_open: Abierto
label_agile_sprint_status_active: Activo
label_agile_sprint_status_closed: Cerrado
label_agile_sprint_duration: Duraccion
label_agile_sprint_duration_select: <<Seleccionar duración>>
label_agile_sprint_duration_week_1: 1 semana
label_agile_sprint_duration_week_2: 2 semanas
label_agile_sprint_duration_week_3: 3 semanas
label_agile_sprint_duration_week_4: 4 semanas
label_agile_sprint_errors_crossed: Las fechas del Sprint se cruza con otro Sprint
label_agile_sprint_errors_end_more_start: La fecha fin debe ser mayor que la fecha de inicio
label_agile_sprint_errors_open_issues: las tareas abiertas del Sprint se puede cerrar
label_agile_sprint_default_chart: Gráfica
label_agile_sprint_chart_units: Estimación
label_agile_chart_for_sprint: "Para el sprint: %{sprint}"
label_agile_charts: Gráficas
label_agile_chart_burndown: Gráficas Burndown
label_agile_chart_burnup: Gráfica Burnup
label_agile_chart_units: Unidades
label_agile_planning_board_more: cargar más...
label_agile_version_query_new: Nueva consulta de version Agile
label_agile_version_my_boards: Mis tableros Ágiles
label_agile_version_board_plural: Tablero versión Agil
label_agile_version_board_edit: Editar tablero versión Agil
label_agile_edit_issue: Editar petición
label_agile_board_type: Tipo de tablero
label_agile_board_type_kanban: Tablero Kanban
label_agile_board_type_scrum: Tablero Scrum
label_agile_board_totals: Totales
label_agile_board_totals_story_points: Story points
label_agile_board_totals_hours: Horas
label_agile_board_totals_spent_time: Tiempo dedicado
label_agile_board_totals_percent_done: "% Realizado"
label_agile_board_totals_velocity: Velocidad
label_agile_board_totals_interval: Intervalo
label_agile_board_totals_remaining: Restante
label_agile_sprint_list_active: Sprints activos
label_agile_sprint_list_future: Sprints futuros
label_agile_mixed_trackers: Tipos mixtos
label_agile_moving_average: Moving Average
label_agile_board_backlog: Backlog
label_agile_board_backlog_column: columna Backlog
label_agile_board_search_backlog_issues: Buscar peticiones en backlog
label_agile_version_plural: Versiones
label_agile_sprint_add: Añadir sprint
label_agile_version_add: Añadir versión
label_agile_sprint_query_new: Nueva consulta Sprint ágil
label_agile_sprint_my_boards: Mis tableros Sprint ágil
label_agile_sprint_board_plural: Tablero Sprint ágil
label_agile_sprint_board_edit: Editar tablero Sprint ágil
field_sprint: Sprint
label_agile_no_sprint_issues: Peticiones sin sprint
field_closed_sprints: sprints cerrados
field_closed_versions: Versiones cerradas

106
config/locales/fr.yml Normal file
View File

@ -0,0 +1,106 @@
# French strings go here for Rails i18n
fr:
label_agile: Agile
label_agile_board: Tableau Agile
label_agile_board_plural: Tableaux Agile
label_agile_board_thumbnails: Miniatures
label_agile_board_more_issues: Plus de demandes
error_agile_status_transition: Impossible de modifier les statuts des demandes
label_agile_board_new: Nouveau tableau Agile
label_agile_board_edit: Modifier le tableau Agile
label_agile_my_boards: Mes tableaux Agiles
label_agile_issue_id: ID de demande
permission_manage_public_agile_queries: Gérer les tableaux Agile publics
permission_add_agile_queries: Ajouter des tableaux Agile
permission_view_agile_queries: Afficher des tableaux Agile
#1.1.0
label_agile_board_default_fields: Champs par défaut des cartes
label_agile_charts_issues_burndown: Burndown des demandes
label_agile_charts_work_burndown: Burndown du temps de travail
label_agile_charts_number_of_hours: Nombre d'heures
label_agile_charts_number_of_issues: Nombre de demandes
label_agile_charts_cumulative_flow: Flux cumulé
label_agile_charts_trackers_cumulative_flow: Flux cumulé des trackers
label_agile_charts_issues_velocity: Vélocité
label_agile_charts_lead_time: Délais
label_agile_charts_average_lead_time: Délai moyen de livraison
label_agile_chart_plural: Graphiques Agiles
permission_view_agile_charts: Afficher les graphique Agiles
label_agile_ideal_work_remaining: Idéal
label_agile_actual_work_remaining: Réel
label_agile_chart: Graphique
label_agile_date_from: De
label_agile_date_to: À
label_agile_chart_dates: Intervalles du graphique
label_agile_weighed_ideal_work_remaining: Idéal pondéré
label_agile_status_colors: Couleurs des statuts
label_agile_charts_burnup: Burnup des demandes
label_agile_charts_number_of_days: Nombre de jours
label_agile_too_many_items: "Le graphique ne peut pas être créé car il dépasse le nombre maximal d'éléments pouvant être affichés (%{max})"
label_agile_time_reports_items_limit: Limite du nombre de demandes pour les graphiques temporels
label_agile_total_work_remaining: Total
label_agile_default_chart: Graphique de versions
#1.1.1
label_agile_charts_average_velocity: Vélocité moyenne
label_agile_charts_avarate_number_of_issues: Nombre moyen de demandes
label_agile_charts_avarate_number_of_hours: nombre moyen d'heures
#1.2.0
label_agile_color: Couleur
permission_manage_agile_verions: Gérer la planification de versions
label_agile_version_planning: Planification de versions
error_agile_version_transition: Impossible de modifier la version de la demande
label_agile_tracker_colors: Couleurs des trackers
label_agile_issue_priority_colors: Couleurs des priorités de demandes
label_agile_color_based_on: Coloré par
label_agile_color_no_colors: Pas de couleurs
label_agile_manage_colors: Gérer les couleurs
label_agile_fullscreen: Plein écran
label_agile_no_version_issues: Demandes sans version
label_agile_charts_work_burnup: Burnup du temps de travail
label_agile_completed: Terminé
label_agile_exclude_weekends: Exclure les week-ends de l'effort idéal
label_agile_board_truncated: "Le tableau ne peut pas être créé car il dépasse le nombre maximal d'éléments pouvant être affichés (%{max})"
label_agile_board_items_limit: Limite du nombre d'élément du tableau Agile
label_agile_swimlanes: Lignes
label_agile_minimize_closed: Réduire les demandes fermées
label_agile_fields: Champs des cartes
label_agile_default_board: Tableau par défaut
#1.3.6
text_agile_move_not_possible: Ce déplacement n'est pas possible
#1.3.8
label_agile_parent_issue_tracker_id: Tracker parent
label_agile_sub_issues: Sous-demandes
label_agile_color_green: Vert
label_agile_color_blue: Bleu
label_agile_color_turquoise: Turquoise
label_agile_color_lightgreen: Vert clair
label_agile_color_yellow: Jaune
label_agile_color_orange: Orange
label_agile_color_red: Rouge
label_agile_color_purple: Violet
label_agile_color_gray: Gris
label_agile_has_sub_issues: A des sous-demandes
label_agile_light_free_version: Agile Version gratuite
label_agile_link_to_pro: Mettre à jour en version PRO
label_agile_link_to_pro_demo: Démo live de la version PRO
label_agile_link_to_more_plugins: Voir plus de plugins RedmineUP
label_agile_button_agree: D'accord
label_agile_license: Licence RedmineUP
label_agile_saving_boards: Enregistrement des tableaux
label_agile_horizontal_swim_lines: Lignes en horizontal
label_agile_board_sub_columns: Sous-colonnes du tableau
label_agile_additional_agile_charts: Graphiques Agiles supplémentaires
label_agile_coloured_issue_cards: Cartes colorées
label_agile_4_more_features: 4 nouveautés supplémentaires...
label_agile_upgrade_to_pro: Passez en version PRO pour bénéficier de ces nouveautés
label_agile_day_in_state: Dans le statut
#1.3.10
project_module_agile: Agile

221
config/locales/it.yml Normal file
View File

@ -0,0 +1,221 @@
# Italian strings go here for Rails i18n
it:
label_agile: Agile
label_agile_board: Tabella Agile
label_agile_board_plural: Tabelle Agile
label_agile_board_thumbnails: Thumbnails
label_agile_board_more_issues: Altre segnalazioni
error_agile_status_transition: Lo stato della segnalazione non può essere cambiato
label_agile_board_new: Nuova tabella Agile
label_agile_board_edit: Modifica tabella Agile
label_agile_my_boards: Le mie tabelle Agile
label_agile_issue_id: ID segnalazione
permission_manage_public_agile_queries: Gestione tabelle pubbliche Agile
permission_add_agile_queries: Aggiungi tabella Agile
permission_view_agile_queries: Visualizza tabelle Agile
#1.1.0
label_agile_board_default_fields: Colonne di Default
label_agile_charts_issues_burndown: Segnalazioni burndown
label_agile_charts_work_burndown_hours: Ore di burndown
label_agile_charts_work_burndown_sp: Story points burndown
label_agile_charts_number_of_hours: Numero di ore
label_agile_charts_number_of_issues: Numero di segnalazioni
label_agile_charts_cumulative_flow: Flusso cumulativo
label_agile_charts_trackers_cumulative_flow: Flusso cumulativo di trackers
label_agile_charts_issues_velocity: Velocità
label_agile_charts_cycle_time: Tempo di ciclo
label_agile_charts_lead_time: Tempo di esecuzione
label_agile_charts_average_lead_time: Media del tempo di esecuziona
label_agile_chart_plural: Grafici Agile
label_agile_chart_new: Nuovo grafico Agile
label_agile_chart_edit: Modifica Grafico Agile
permission_view_agile_charts: Visuaolizza grafici Agile
label_agile_ideal_work_remaining: Ideale
label_agile_actual_work_remaining: Attuale
label_agile_chart: Grafico
label_agile_date_from: Da
label_agile_date_to: A
label_agile_chart_dates: Intervalli dei grafici
label_agile_weighed_ideal_work_remaining: Ideali pesati
label_agile_status_colors: Colori di status
label_agile_charts_burnup: Burnup segnalazioni
label_agile_charts_number_of_days: Numero di giorni
label_agile_too_many_items: "Il grafico non può essere generato perchè ecccede il numero massimo di elementi che possono essere visualizzati (%{max})"
label_agile_time_reports_items_limit: Grafici basati su limiti temporali delle segnalazioni
label_agile_total_work_remaining: Totale
label_agile_default_chart: Grafico di default
#1.1.1
label_agile_charts_average_velocity: Media velocità
label_agile_charts_avarate_number_of_issues: Media del numero di segnalazioni
label_agile_charts_avarate_number_of_hours: Media del numero di ore
#1.2.0
label_agile_color: Colore
permission_manage_agile_verions: Gestione Pianificazione di versione
label_agile_version_planning: Pianificazione di versione
error_agile_version_transition: Cambio versione di segnalazione non possibile
label_agile_tracker_colors: Colori Tracker
label_agile_issue_priority_colors: Colori priorità di segnalazione
label_agile_color_based_on: Colorato da
label_agile_color_no_colors: Nessun colore
label_agile_manage_colors: Gestione colori
label_agile_fullscreen: Full screen
label_agile_no_version_issues: Segnalazioni senza versione
label_agile_charts_work_burnup_hours: Burnup ore di lavoro
label_agile_charts_work_burnup_sp: Burnup degli Story points
label_agile_completed: Completato
label_agile_exclude_weekends: Escludi weekends dall'effort ideale
label_agile_board_truncated: "La tabella è stata troncata perchè eccede il massimo numero di elementi che possono essere visualizzati (%{max})"
label_agile_board_items_limit: Limite elementi tabella Agile
label_agile_swimlanes: Swim lanes
label_agile_minimize_closed: Minimizza le segnalazioni chiuse
label_agile_fields: Colonne tabella
label_agile_default_board: Tabella di default
#1.3.6
text_agile_move_not_possible: That move is not possible
#1.3.8
label_agile_parent_issue_tracker_id: Tracker principale
label_agile_sub_issues: Richieste secondarie
label_agile_color_green: Verde
label_agile_color_blue: Blu
label_agile_color_turquoise: Turchese
label_agile_color_lightgreen: Verde chiaro
label_agile_color_yellow: Giallo
label_agile_color_orange: Arancione
label_agile_color_red: Rosso
label_agile_color_purple: Viola
label_agile_color_gray: Grigio
label_agile_has_sub_issues: Ha segnalazioni secondarie
label_agile_light_free_version: Versione Agile Free
label_agile_link_to_pro: Aggiorna alla versione PRO
label_agile_link_to_pro_demo: Demo live della versione PRO
label_agile_link_to_more_plugins: Visualizza altri plug-in RedmineUP
label_agile_button_agree: Okay
label_agile_license: Licenza RedmineUP
label_agile_saving_boards: Salvataggio delle tabelle
label_agile_horizontal_swim_lines: Swim lanes
label_agile_board_sub_columns: Sottocolonne della tabella
label_agile_additional_agile_charts: Grafico aggiuntivo Agile
label_agile_coloured_issue_cards: Schede segnalazioni colorate
label_agile_4_more_features: 4 nuove funzionalità aggiuntive ...
label_agile_upgrade_to_pro: Passa alla versione PRO per beneficiare di queste nuove funzionalità
label_agile_day_in_state: In status
#1.3.10
label_agile_hide_closed_issues_data: Nascondi i dati relativi ai problemi chiusi
project_module_agile: Agile
#1.3.13
label_agile_last_comment: Ultimo commento
#1.4.0
label_agile_esitmate_units: Stimare le unità
label_agile_trackers_for_sp: Trackers per gli Story points
label_agile_story_points: Story points
field_story_points: Story points
label_agile_charts_number_of_story_points: Numero di Story points
label_agile_board_columns: Colonne della tabella
lable_agile_wip_limit_exceeded: Limite Work-in-progress superato
label_agile_wip_limit: Limite Work-in-progress
label_agile_add_new_issue: "+ aggiungi nuova segnalazione"
label_agile_allow_create_cards: Creazione schede
label_agile_auto_assign_on_move: Assegnazione automatica sulla disposizione
text_agile_create_issue_error: Si è verificato un errore durante la creazione dell'atticità
label_agile_inline_comment: Commento in linea
label_agile_hours: Ore
field_color: Colore
field_duration: Durata
field_closed_on_trendline: Trendline chiusa
field_created_on_trendline: Trendline creata
label_agile_sp_values: Valore degli Story points
label_agile_interval_size: Dimensione intervallo
label_agile_day: Giorno
label_agile_week: Settimana
label_agile_month: Mese
label_agile_quarter: Trimestre
label_agile_year: Anno
label_cards_search: Cerca per argomento
field_agile_sprint: Sprint
field_end_date: Data di fine
label_agile_sprints_on: Attiva gli sprint per impostazione predefinita
label_agile_sprint: Sprint
label_agile_sprint_plural: Sprints
label_agile_sprint_name: Nome
label_agile_sprint_description: Descrizione
label_agile_sprint_status: Stato
label_agile_sprint_start_date: Data d'inizio
label_agile_sprint_end_date: Data di fine
label_agile_sprint_new: Nuovo sprint
label_agile_sprint_status_open: Aperto
label_agile_sprint_status_active: Attivo
label_agile_sprint_status_closed: Chiuso
label_agile_sprint_duration: Durata
label_agile_sprint_duration_select: << Seleziona durata >>
label_agile_sprint_duration_week_1: 1 settimana
label_agile_sprint_duration_week_2: 2 settimane
label_agile_sprint_duration_week_3: 3 settimane
label_agile_sprint_duration_week_4: 4 settimane
label_agile_sprint_errors_crossed: Le date dello sprint si incrociano con un altro sprint esistente
label_agile_sprint_errors_end_more_start: La data di fine dovrebbe essere superiore alla data di inizio
label_agile_sprint_errors_open_issues: Lo sprint con problemi aperti non può essere chiuso
label_agile_sprint_default_chart: Grafico
label_agile_sprint_chart_units: Stima
label_agile_chart_for_sprint: "Per lo sprint: %{sprint}"
label_agile_charts: Grafici
label_agile_chart_burndown: Grafico di burndown
label_agile_chart_burnup: Grafico di burnup
label_agile_chart_units: unità
label_agile_planning_board_more: Carica altri ...
label_agile_version_query_new: Nuova query sulle versioni Agile
label_agile_version_my_boards: Le mie schede di versioni Agile
label_agile_version_board_plural: Schede versioni Agile
label_agile_version_board_edit: Modifica la scheda della versione Agile
label_agile_edit_issue: Modifica segnbalazione
label_agile_board_type: Tipo di tabella
label_agile_board_type_kanban: Tabella Kanban
label_agile_board_type_scrum: Tabella Scrum
label_agile_board_totals: Totali
label_agile_board_totals_story_points: Story points
label_agile_board_totals_hours: Ore
label_agile_board_totals_spent_time: Tempo trascorso
label_agile_board_totals_percent_done: Percentuale fatta
label_agile_board_totals_velocity: Velocità
label_agile_board_totals_interval: Intervallo
label_agile_board_totals_remaining: Residuo
label_agile_sprint_list_active: Sprint attivi
label_agile_sprint_list_future: Sprint futuri
label_agile_mixed_trackers: Trackers misti
label_agile_moving_average: Media scostamenti
label_agile_board_backlog: Backlog
label_agile_board_backlog_column: Colonna Backlog
label_agile_board_search_backlog_issues: Cerca segnalazioni backlog
label_agile_version_plural: Versioni
label_agile_sprint_add: Aggiungi Sprint
label_agile_version_add: Aggiungi versione
label_agile_sprint_query_new: Nuova query sugli sprint Agile
label_agile_sprint_my_boards: Le mie tabelle Sprint Agile
label_agile_sprint_board_plural: Tabelle Sprint Agile
label_agile_sprint_board_edit: Modifica la tabella Sprint Agile
field_sprint: Sprint
label_agile_no_sprint_issues: Segnalazioni senza Sprint
field_closed_sprints: Sprint chiusi
field_closed_versions: Versioni chiuse

70
config/locales/ko.yml Normal file
View File

@ -0,0 +1,70 @@
# Translation by Ki Won Kim (http://x10.iptime.org/redmine, http://xyz37.blog.me, xyz37@naver.com)
ko:
label_agile: "애자일"
label_agile_board: "애자일 보드"
label_agile_board_plural: "애자일 보드"
label_agile_board_thumbnails: "축소판 그림"
label_agile_board_more_issues: "더 많은 일감"
error_agile_status_transition: "일감 상태를 변경할 수 없습니다."
label_agile_board_new: "신규 애자일 보드"
label_agile_board_edit: "애자일 보드 수정"
label_agile_my_boards: "내 애자일 보드"
label_agile_issue_id: "일감 번호"
permission_manage_public_agile_queries: "공용 애자일 보드 관리"
permission_add_agile_queries: "애자일 보드 추가"
permission_view_agile_queries: "애자일 보드 보기"
#1.1.0
label_agile_board_default_fields: "기본 카드 필드"
label_agile_charts_issues_burndown: "일감 Burn Down"
label_agile_charts_work_burndown: "작업 Burn Down"
label_agile_charts_number_of_hours: "시간 수"
label_agile_charts_number_of_issues: "일감 수"
label_agile_charts_cumulative_flow: "누적 흐름"
label_agile_charts_trackers_cumulative_flow: "일감 유형 누적 흐름"
label_agile_charts_issues_velocity: "속도"
label_agile_charts_lead_time: "리드 타임"
label_agile_charts_average_lead_time: "평균 리드 타임"
label_agile_chart_plural: "애자일 차트"
permission_view_agile_charts: "애자일 차트 보기"
label_agile_ideal_work_remaining: "이상적인"
label_agile_actual_work_remaining: "실제"
label_agile_chart: "차트"
label_agile_date_from: "부터"
label_agile_date_to: "까지"
label_agile_chart_dates: "차트 간격"
label_agile_weighed_ideal_work_remaining: "무게 이상"
label_agile_status_colors: "상태 색상"
label_agile_charts_burnup: "일감 Burn Up"
label_agile_charts_number_of_days: "일 수"
label_agile_too_many_items: "차트가 표시 (% {max}) 될 수 있는 항목의 최대 수를 초과 하므로 만들 수 없습니다."
label_agile_time_reports_items_limit: "시간 항목에 기반한 차트 일감 제한"
label_agile_total_work_remaining: "전체"
label_agile_default_chart: "버전 기본 차트"
#1.1.1
label_agile_charts_average_velocity: "평균 속도"
label_agile_charts_avarate_number_of_issues: "평균 일감 수"
label_agile_charts_avarate_number_of_hours: "평균 시간 수"
#1.2.0
label_agile_color: "색상"
permission_manage_agile_verions: "버전 계획 관리"
label_agile_version_planning: "버전 계획"
error_agile_version_transition: "일감 버전을 변경할 수 없습니다."
label_agile_tracker_colors: "일감 유형 색상"
label_agile_issue_priority_colors: "일감 우선 순위 색상"
label_agile_color_based_on: "색상의"
label_agile_color_no_colors: "색상 없음"
label_agile_manage_colors: "색상 관리"
label_agile_fullscreen: "전체 화면"
label_agile_no_version_issues: "버전없는 일감들"
label_agile_charts_work_burnup: "작업 Burn Up"
label_agile_completed: "완료됨"
label_agile_exclude_weekends: "이상적인 노력에서 주말 제외"
label_agile_board_truncated: "보드가 표시 (% {max}) 될 수 있는 항목의 최대 수를 초과 하므로 잘렸습니다."
label_agile_board_items_limit: "애자일 보드 항목 제한"
label_agile_swimlanes: "Swim lanes"
label_agile_minimize_closed: "완료된 일감 최소화"
label_agile_fields: "카드 필드"

222
config/locales/pt-BR.yml Normal file
View File

@ -0,0 +1,222 @@
# Portugues - Brazil strings go here for Rails i18n
pt-BR:
label_agile: Ágil
label_agile_board: Quadro Ágil
label_agile_board_plural: Quadros ágeis
label_agile_board_thumbnails: Thumbnails
label_agile_board_more_issues: Mais tarefas
error_agile_status_transition: Não é possível alterar o status do problema
label_agile_board_new: Novo quadro ágil
label_agile_board_edit: Editar quadro ágil
label_agile_my_boards: Meus quadros ágeis
label_agile_issue_id: Id. Tarefa
permission_manage_public_agile_queries: Gerenciar quadros ágeis publicos
permission_add_agile_queries: Adicionar quadro ágil
permission_view_agile_queries: Visualizar quadros ágeis
#1.1.0
label_agile_board_default_fields: Campos padrão no post-it
label_agile_charts_issues_burndown: Burndow de tarefas
label_agile_charts_work_burndown_hours: Burndow de horas
label_agile_charts_work_burndown_sp: Burndow por pontos por histórias
label_agile_charts_number_of_hours: Número de horas
label_agile_charts_number_of_issues: Número de tarefas
label_agile_charts_cumulative_flow: Fluxo cumulativo
label_agile_charts_trackers_cumulative_flow: Fluxo comulativo por tipo
label_agile_charts_issues_velocity: Velocidade
label_agile_charts_cycle_time: Tempo de ciclo
label_agile_charts_lead_time: Tempo de espera
label_agile_charts_average_lead_time: Tempo de espera médio
label_agile_chart_plural: Gráficos ágeis
label_agile_chart_new: Novo gráfico ágil
label_agile_chart_edit: Editar gráfico ágil
permission_view_agile_charts: Visualizar gráficos ágeis
label_agile_ideal_work_remaining: Ideal
label_agile_actual_work_remaining: Atual
label_agile_chart: Gráfico
label_agile_date_from: De
label_agile_date_to: Para
label_agile_chart_dates: Intervalo de gráficos
label_agile_weighed_ideal_work_remaining: Ideal ponderado
label_agile_status_colors: Cor dos status
label_agile_charts_burnup: Burnup tarefas
label_agile_charts_number_of_days: Número de dias
label_agile_too_many_items: "O gráfico não pode ser criado porque excede o número máximo de itens que podem ser exibidos (%{max})"
label_agile_time_reports_items_limit: Limite de problemas de gráficos baseados em entradas de tempo
label_agile_total_work_remaining: Total
label_agile_default_chart: Gráfico padrão
#1.1.1
label_agile_charts_average_velocity: Velocidade média
label_agile_charts_avarate_number_of_issues: Média de número de tarefas
label_agile_charts_avarate_number_of_hours: Média número de horas
#1.2.0
label_agile_color: Cor
permission_manage_agile_verions: Gerencia planejamento da versão
label_agile_version_planning: Planejamento da versão
error_agile_version_transition: Não é possível alterar a versão do problema
label_agile_tracker_colors: Cores por tipo
label_agile_issue_priority_colors: Cores pela prioridade das tarefas
label_agile_color_based_on: Colorir por
label_agile_color_no_colors: Sem cores
label_agile_manage_colors: Gerenciar cores
label_agile_fullscreen: Tela cheia
label_agile_no_version_issues: Tarefas sem versão
label_agile_charts_work_burnup_hours: Burnup de horas
label_agile_charts_work_burnup_sp: Burnup de pontos por histórias
label_agile_completed: Completado
label_agile_exclude_weekends: Exclua fins de semana do esforço
label_agile_board_truncated: "O tabuleiro foi truncado porque excede o número máximo de itens que podem ser exibidos (%{max})"
label_agile_board_items_limit: Limites de tarefas no quadro ágil
label_agile_swimlanes: Rais do quadro
label_agile_minimize_closed: Minimizar itens concluídos
label_agile_fields: Post-its
label_agile_default_board: Quadro padrão
#1.3.6
text_agile_move_not_possible: Esse movimento não é possível
#1.3.8
label_agile_parent_issue_tracker_id: Tipo parental
label_agile_sub_issues: Sub-tarefas
label_agile_color_green: Verde
label_agile_color_blue: Azul
label_agile_color_turquoise: Turquesa
label_agile_color_lightgreen: Verde claro
label_agile_color_yellow: Amarelo
label_agile_color_orange: Laranja
label_agile_color_red: Vermelho
label_agile_color_purple: Roxo
label_agile_color_gray: Cinza
label_agile_has_sub_issues: Teve Sub-tarefas
label_agile_light_free_version: Versão gratuita Agile Light
label_agile_link_to_pro: Atualize para PRO
label_agile_link_to_pro_demo: Demo da versão PRO
label_agile_link_to_more_plugins: Procurar mais plugins no RedmineUP
label_agile_button_agree: Aceito
label_agile_license: RedmineUP Licensa
label_agile_saving_boards: Salvando quadro
label_agile_horizontal_swim_lines: Rais horizontais
label_agile_board_sub_columns: Quadro sub-colunas
label_agile_additional_agile_charts: Quadro ágil adicional
label_agile_coloured_issue_cards: Colorir post-its das tarefas
label_agile_4_more_features: 4 mais funcionalidades...
label_agile_upgrade_to_pro: Atualizar para versão PRO version para usar estas funcionalidades
label_agile_day_in_state: Em status
#1.3.10
label_agile_hide_closed_issues_data: Ocultar dados de tarefas fechadas
project_module_agile: Agile
#1.3.13
label_agile_last_comment: Último comentário
#1.4.0
label_agile_esitmate_units: Unidades estimadas
label_agile_trackers_for_sp: Tipos por pontos de histórias
label_agile_story_points: Pontos por histórias
field_story_points: Pontos por histórias
label_agile_charts_number_of_story_points: Número de pontos por história
label_agile_board_columns: Colunas do quadro
lable_agile_wip_limit_exceeded: Limite de trabalho excedido
label_agile_wip_limit: Limite trabalho WIP
label_agile_add_new_issue: '+ Adicionar nova tarefa'
label_agile_allow_create_cards: Criação do post-it
label_agile_auto_assign_on_move: Adicionar o responsável ao mover
text_agile_create_issue_error: Ocorreu um erro na criação da tarefa
label_agile_inline_comment: Comentários em linha
label_agile_hours: Horas
field_color: Cores
field_duration: Duração
field_closed_on_trendline: Linha de tendência fechada
field_created_on_trendline: Criar linha de tendência
label_agile_sp_values: Valor dos pontos por história
label_agile_interval_size: Tamanho do intervalo
label_agile_day: Dia
label_agile_week: Semana
label_agile_month: Mês
label_agile_quarter: Trimestre
label_agile_year: Ano
label_cards_search: Pesquisar por título
field_agile_sprint: Sprint
field_end_date: Encerramento
label_agile_sprints_on: Ativar sprints por padrão
label_agile_sprint: Sprint
label_agile_sprint_plural: Sprints
label_agile_sprint_name: Nome
label_agile_sprint_description: Descrição
label_agile_sprint_status: Status
label_agile_sprint_start_date: Início em
label_agile_sprint_end_date: Término em
label_agile_sprint_new: Nova sprint
label_agile_sprint_status_open: Aberta
label_agile_sprint_status_active: Ativa
label_agile_sprint_status_closed: Fechada
label_agile_sprint_duration: Duração
label_agile_sprint_duration_select: <<Select duration>>
label_agile_sprint_duration_week_1: 1 semana
label_agile_sprint_duration_week_2: 2 semanas
label_agile_sprint_duration_week_3: 3 semanas
label_agile_sprint_duration_week_4: 4 semanas
label_agile_sprint_errors_crossed: As datas da sprint são cruzadas com outra sprint existente
label_agile_sprint_errors_end_more_start: A data de término deve ser posterior à data de início
label_agile_sprint_errors_open_issues: Sprint com problemas abertos pode ser fechado
label_agile_sprint_default_chart: Gráfico
label_agile_sprint_chart_units: Estimativa
label_agile_chart_for_sprint: "Para sprint: %{sprint}"
label_agile_charts: Gráficos
label_agile_chart_burndown: Gráfico burndown
label_agile_chart_burnup: Gráfico burnup
label_agile_chart_units: Unidades
label_agile_planning_board_more: Carregar mais...
label_agile_version_query_new: Consulta de novas versões do Agile
label_agile_version_my_boards: Meus quadros ágeis
label_agile_version_board_plural: Versões dos quados ágeis
label_agile_version_board_edit: Editar versão do quadro ágil
label_agile_edit_issue: Editar tarefa
label_agile_board_type: Tipo do quadro
label_agile_board_type_kanban: Quadro Kanban
label_agile_board_type_scrum: Quadro Scrum
label_agile_board_totals: Totais
label_agile_board_totals_story_points: Pontos por história
label_agile_board_totals_hours: Horas
label_agile_board_totals_spent_time: Tempo gasto
label_agile_board_totals_percent_done: Percentual de pronto
label_agile_board_totals_velocity: Velocidade
label_agile_board_totals_interval: Intervalo
label_agile_board_totals_remaining: Remanecente
label_agile_sprint_list_active: Sprints ativos
label_agile_sprint_list_future: Sprints futuros
label_agile_sprint_list_old: Sprints antigos
label_agile_mixed_trackers: Tipos mistos
label_agile_moving_average: Média móvel
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_sprint_add: Adicionar novo Sprint
label_agile_version_add: Adicionar Versão
label_agile_sprint_query_new: Nova consulta sprint ágil
label_agile_sprint_my_boards: Meus quadros de sprint
label_agile_sprint_board_plural: Quadros de sprint ágeis
label_agile_sprint_board_edit: Editar quadros de sprint
field_sprint: Sprint
label_agile_no_sprint_issues: Tarefas sem sprint
field_closed_sprints: Sprints fechadas
field_closed_versions: Versões fechadas

222
config/locales/pt.yml Normal file
View File

@ -0,0 +1,222 @@
# Portugues - Portugal strings go here for Rails i18n
pt:
label_agile: Ágil
label_agile_board: Quadro Ágil
label_agile_board_plural: Quadros ágeis
label_agile_board_thumbnails: Thumbnails
label_agile_board_more_issues: Mais tarefas
error_agile_status_transition: Não é possível alterar o status do problema
label_agile_board_new: Novo quadro ágil
label_agile_board_edit: Editar quadro ágil
label_agile_my_boards: Meus quadros ágeis
label_agile_issue_id: Id. Tarefa
permission_manage_public_agile_queries: Gerenciar quadros ágeis publicos
permission_add_agile_queries: Adicionar quadro ágil
permission_view_agile_queries: Visualizar quadros ágeis
#1.1.0
label_agile_board_default_fields: Campos padrão no post-it
label_agile_charts_issues_burndown: Burndow de tarefas
label_agile_charts_work_burndown_hours: Burndow de horas
label_agile_charts_work_burndown_sp: Burndow por pontos por histórias
label_agile_charts_number_of_hours: Número de horas
label_agile_charts_number_of_issues: Número de tarefas
label_agile_charts_cumulative_flow: Fluxo cumulativo
label_agile_charts_trackers_cumulative_flow: Fluxo comulativo por tipo
label_agile_charts_issues_velocity: Velocidade
label_agile_charts_cycle_time: Tempo de ciclo
label_agile_charts_lead_time: Tempo de espera
label_agile_charts_average_lead_time: Tempo de espera médio
label_agile_chart_plural: Gráficos ágeis
label_agile_chart_new: Novo gráfico ágil
label_agile_chart_edit: Editar gráfico ágil
permission_view_agile_charts: Visualizar gráficos ágeis
label_agile_ideal_work_remaining: Ideal
label_agile_actual_work_remaining: Atual
label_agile_chart: Gráfico
label_agile_date_from: De
label_agile_date_to: Para
label_agile_chart_dates: Intervalo de gráficos
label_agile_weighed_ideal_work_remaining: Ideal ponderado
label_agile_status_colors: Cor dos status
label_agile_charts_burnup: Burnup tarefas
label_agile_charts_number_of_days: Número de dias
label_agile_too_many_items: "O gráfico não pode ser criado porque excede o número máximo de itens que podem ser exibidos (%{max})"
label_agile_time_reports_items_limit: Limite de problemas de gráficos baseados em entradas de tempo
label_agile_total_work_remaining: Total
label_agile_default_chart: Gráfico padrão
#1.1.1
label_agile_charts_average_velocity: Velocidade média
label_agile_charts_avarate_number_of_issues: Média de número de tarefas
label_agile_charts_avarate_number_of_hours: Média número de horas
#1.2.0
label_agile_color: Cor
permission_manage_agile_verions: Gerencia planejamento da versão
label_agile_version_planning: Planejamento da versão
error_agile_version_transition: Não é possível alterar a versão do problema
label_agile_tracker_colors: Cores por tipo
label_agile_issue_priority_colors: Cores pela prioridade das tarefas
label_agile_color_based_on: Colorir por
label_agile_color_no_colors: Sem cores
label_agile_manage_colors: Gerenciar cores
label_agile_fullscreen: Tela cheia
label_agile_no_version_issues: Tarefas sem versão
label_agile_charts_work_burnup_hours: Burnup de horas
label_agile_charts_work_burnup_sp: Burnup de pontos por histórias
label_agile_completed: Completado
label_agile_exclude_weekends: Exclua fins de semana do esforço
label_agile_board_truncated: "O tabuleiro foi truncado porque excede o número máximo de itens que podem ser exibidos (%{max})"
label_agile_board_items_limit: Limites de tarefas no quadro ágil
label_agile_swimlanes: Rais do quadro
label_agile_minimize_closed: Minimizar itens concluídos
label_agile_fields: Post-its
label_agile_default_board: Quadro padrão
#1.3.6
text_agile_move_not_possible: Esse movimento não é possível
#1.3.8
label_agile_parent_issue_tracker_id: Tipo parental
label_agile_sub_issues: Sub-tarefas
label_agile_color_green: Verde
label_agile_color_blue: Azul
label_agile_color_turquoise: Turquesa
label_agile_color_lightgreen: Verde claro
label_agile_color_yellow: Amarelo
label_agile_color_orange: Laranja
label_agile_color_red: Vermelho
label_agile_color_purple: Roxo
label_agile_color_gray: Cinza
label_agile_has_sub_issues: Teve Sub-tarefas
label_agile_light_free_version: Versão gratuita Agile Light
label_agile_link_to_pro: Atualize para PRO
label_agile_link_to_pro_demo: Demo da versão PRO
label_agile_link_to_more_plugins: Procurar mais plugins no RedmineUP
label_agile_button_agree: Aceito
label_agile_license: RedmineUP Licensa
label_agile_saving_boards: Salvando quadro
label_agile_horizontal_swim_lines: Rais horizontais
label_agile_board_sub_columns: Quadro sub-colunas
label_agile_additional_agile_charts: Quadro ágil adicional
label_agile_coloured_issue_cards: Colorir post-its das tarefas
label_agile_4_more_features: 4 mais funcionalidades...
label_agile_upgrade_to_pro: Atualizar para versão PRO version para usar estas funcionalidades
label_agile_day_in_state: Em status
#1.3.10
label_agile_hide_closed_issues_data: Ocultar dados de tarefas fechadas
project_module_agile: Agile
#1.3.13
label_agile_last_comment: Último comentário
#1.4.0
label_agile_esitmate_units: Unidades estimadas
label_agile_trackers_for_sp: Tipos por pontos de histórias
label_agile_story_points: Pontos por histórias
field_story_points: Pontos por histórias
label_agile_charts_number_of_story_points: Número de pontos por história
label_agile_board_columns: Colunas do quadro
lable_agile_wip_limit_exceeded: Limite de trabalho excedido
label_agile_wip_limit: Limite trabalho WIP
label_agile_add_new_issue: '+ Adicionar nova tarefa'
label_agile_allow_create_cards: Criação do post-it
label_agile_auto_assign_on_move: Adicionar o responsável ao mover
text_agile_create_issue_error: Ocorreu um erro na criação da tarefa
label_agile_inline_comment: Comentários em linha
label_agile_hours: Horas
field_color: Cores
field_duration: Duração
field_closed_on_trendline: Linha de tendência fechada
field_created_on_trendline: Criar linha de tendência
label_agile_sp_values: Valor dos pontos por história
label_agile_interval_size: Tamanho do intervalo
label_agile_day: Dia
label_agile_week: Semana
label_agile_month: Mês
label_agile_quarter: Trimestre
label_agile_year: Ano
label_cards_search: Pesquisar por título
field_agile_sprint: Sprint
field_end_date: Encerramento
label_agile_sprints_on: Ativar sprints por padrão
label_agile_sprint: Sprint
label_agile_sprint_plural: Sprints
label_agile_sprint_name: Nome
label_agile_sprint_description: Descrição
label_agile_sprint_status: Status
label_agile_sprint_start_date: Início em
label_agile_sprint_end_date: Término em
label_agile_sprint_new: Nova sprint
label_agile_sprint_status_open: Aberta
label_agile_sprint_status_active: Ativa
label_agile_sprint_status_closed: Fechada
label_agile_sprint_duration: Duração
label_agile_sprint_duration_select: <<Select duration>>
label_agile_sprint_duration_week_1: 1 semana
label_agile_sprint_duration_week_2: 2 semanas
label_agile_sprint_duration_week_3: 3 semanas
label_agile_sprint_duration_week_4: 4 semanas
label_agile_sprint_errors_crossed: As datas da sprint são cruzadas com outra sprint existente
label_agile_sprint_errors_end_more_start: A data de término deve ser posterior à data de início
label_agile_sprint_errors_open_issues: Sprint com problemas abertos pode ser fechado
label_agile_sprint_default_chart: Gráfico
label_agile_sprint_chart_units: Estimativa
label_agile_chart_for_sprint: "Para sprint: %{sprint}"
label_agile_charts: Gráficos
label_agile_chart_burndown: Gráfico burndown
label_agile_chart_burnup: Gráfico burnup
label_agile_chart_units: Unidades
label_agile_planning_board_more: Carregar mais...
label_agile_version_query_new: Consulta de novas versões do Agile
label_agile_version_my_boards: Meus quadros ágeis
label_agile_version_board_plural: Versões dos quados ágeis
label_agile_version_board_edit: Editar versão do quadro ágil
label_agile_edit_issue: Editar tarefa
label_agile_board_type: Tipo do quadro
label_agile_board_type_kanban: Quadro Kanban
label_agile_board_type_scrum: Quadro Scrum
label_agile_board_totals: Totais
label_agile_board_totals_story_points: Pontos por história
label_agile_board_totals_hours: Horas
label_agile_board_totals_spent_time: Tempo gasto
label_agile_board_totals_percent_done: Percentual de pronto
label_agile_board_totals_velocity: Velocidade
label_agile_board_totals_interval: Intervalo
label_agile_board_totals_remaining: Remanecente
label_agile_sprint_list_active: Sprints ativos
label_agile_sprint_list_future: Sprints futuros
label_agile_sprint_list_old: Sprints antigos
label_agile_mixed_trackers: Tipos mistos
label_agile_moving_average: Média móvel
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_sprint_add: Adicionar novo Sprint
label_agile_version_add: Adicionar Versão
label_agile_sprint_query_new: Nova consulta sprint ágil
label_agile_sprint_my_boards: Meus quadros de sprint
label_agile_sprint_board_plural: Quadros de sprint ágeis
label_agile_sprint_board_edit: Editar quadros de sprint
field_sprint: Sprint
label_agile_no_sprint_issues: Tarefas sem sprint
field_closed_sprints: Sprints fechadas
field_closed_versions: Versões fechadas

220
config/locales/ru.yml Executable file
View File

@ -0,0 +1,220 @@
# Russian strings go here for Rails i18n
ru:
label_agile: Agile
label_agile_board: Доска задач
label_agile_board_plural: Доски задач
label_agile_board_thumbnails: Превью
label_agile_board_issues_per_column: Кол-во задач в колонке
label_agile_board_more_issues: Еще задачи
error_agile_status_transition: Невозможно изменить статус
label_agile_board_new: Новая доска
label_agile_board_edit: Редактировать доску
label_agile_my_boards: Мои доски
label_agile_issue_id: Номер задачи
permission_manage_public_agile_queries: Управление публичными досками задач
permission_add_agile_queries: Добавление досок задач
permission_view_agile_queries: Просмотр досок задач
#1.1.0
label_agile_board_default_fields: Поля по умолчанию
label_agile_charts_issues_burndown: Сгорание задач
label_agile_charts_work_burndown_hours: Сгорание времени задач (burndown)
label_agile_charts_work_burndown_sp: Сгорание story points (burndown)
label_agile_charts_number_of_hours: Кол-во часов
label_agile_charts_number_of_issues: Кол-во задач
label_agile_charts_cumulative_flow: Накопительный поток
label_agile_charts_trackers_cumulative_flow: Накопительный поток трекеров
label_agile_charts_issues_velocity: Скорость закрытия задач
label_agile_charts_cycle_time: Цикл задачи
label_agile_charts_lead_time: Цикл задачи
label_agile_charts_average_lead_time: Средний цикл задачи
label_agile_chart_plural: Диаграммы
label_agile_chart_new: Новая диаграмма
label_agile_chart_edit: Редактировать диаграмму
permission_view_agile_charts: Просмотр диаграмм
label_agile_ideal_work_remaining: Идеально
label_agile_actual_work_remaining: Действительно
label_agile_chart: Диаграмма
label_agile_date_from: С
label_agile_date_to: По
label_agile_chart_dates: Интервалы диаграммы
label_agile_weighed_ideal_work_remaining: Взвещенно
label_agile_status_colors: Цвета статусов
label_agile_charts_burnup: Выполнение задач
label_agile_charts_number_of_days: Кол-во дней
label_agile_too_many_items: "Диаграмма не может быть создана потому что превышено максимальное кол-во задач (%{max})"
label_agile_time_reports_items_limit: Максимальное кол-во задач отображаемых на диаграммах
label_agile_total_work_remaining: Всего
label_agile_default_chart: Диаграмма по умолчанию
#1.1.1
label_agile_charts_average_velocity: Средняя скорость закрытия задач
label_agile_charts_avarate_number_of_issues: Среднее кол-во задач
label_agile_charts_avarate_number_of_hours: Среднее кол-во часов
#1.2.0
label_agile_color: Цвет
permission_manage_agile_verions: Планирование версий
label_agile_version_planning: Планирование версий
error_agile_version_transition: Невозможно изменить версию задачи
label_agile_tracker_colors: Цвета трекеров
label_agile_issue_priority_colors: Цвета приоритетов задачи
label_agile_color_based_on: Цвета на основании
label_agile_color_no_colors: Без цвета
label_agile_manage_colors: Настроить цвета
label_agile_fullscreen: Во весь экран
label_agile_no_version_issues: Задачи без версии
label_agile_charts_work_burnup_hours: Выполнение времени задач (burnup)
label_agile_charts_work_burnup_sp: Выполнение story points (burnup)
label_agile_completed: Выполнено
label_agile_exclude_weekends: Исключать выходные для диаграммы сгорания
label_agile_board_truncated: "Доска была усечена, поскольку превышено максимальное кол-во элементов, которые могут отображаться (%{max})"
label_agile_board_items_limit: Максимальное кол-во задач отображаемых на Доске
label_agile_swimlanes: Группировать по
label_agile_minimize_closed: Минимизировать закрытые задачи
label_agile_fields: Поля
label_agile_default_board: По умолчанию
#1.3.6
text_agile_move_not_possible: Данное передвижение невозможно
#1.3.8
label_agile_parent_issue_tracker_id: Трекер родителя
label_agile_sub_issues: Подзадачи
label_agile_color_green: Зеленый
label_agile_color_blue: Синий
label_agile_color_turquoise: Бирюзовый
label_agile_color_lightgreen: Светло зеленый
label_agile_color_yellow: Желтый
label_agile_color_orange: Оранжевый
label_agile_color_red: Красный
label_agile_color_purple: Фиолетовый
label_agile_color_gray: Серый
label_agile_has_sub_issues: Подзадачи
label_agile_light_free_version: Agile бесплатная версия
label_agile_link_to_pro: Обновление до PRO
label_agile_link_to_pro_demo: Онлайн демо PRO
label_agile_link_to_more_plugins: Другие плагины RedmineUP
label_agile_button_agree: Согласен
label_agile_license: Лицензионное соглашение RedmineUP
label_agile_saving_boards: Сохранить настройку доски
label_agile_horizontal_swim_lines: Группировки карточек
label_agile_board_sub_columns: Доп. колонки
label_agile_additional_agile_charts: Доп. диаграммы
label_agile_coloured_issue_cards: Цветовая индикация карточек
label_agile_4_more_features: И еще другие 4 функции...
label_agile_upgrade_to_pro: 'Обновитесь до PRO версии чтобы:'
#1.3.10
label_agile_hide_closed_issues_data: Скрывать данные закрытых задач
label_agile_day_in_state: В статусе
project_module_agile: Agile
#1.3.13
label_agile_last_comment: Последний комментарий
#1.4.0
label_agile_esitmate_units: Использовать для оценки
label_agile_trackers_for_sp: Трекеры для Story points
label_agile_story_points: Story points
field_story_points: Story points
label_agile_charts_number_of_story_points: Кол-во story points
label_agile_board_columns: Столбцы доски
lable_agile_wip_limit_exceeded: Превышен лимит по задачам в данном столбце
label_agile_wip_limit: Ограничение по кол-ву задач в столбце
label_agile_add_new_issue: '+ НОВАЯ ЗАДАЧА'
label_agile_allow_create_cards: Создавать новые задачи на доске
label_agile_auto_assign_on_move: Автоназначение при перетаскивании
text_agile_create_issue_error: Произошла ошибка при создании задачи
label_agile_inline_comment: Комментирование с доски
label_agile_hours: Часы
field_color: Цвет
field_duration: Продолжительность
label_cards_search: Поиск по теме
field_agile_sprint: Спринт
field_end_date: Дата окончания
label_agile_sprints_on: Активировать спринты по умолчанию
label_agile_sprint: Спринт
label_agile_sprint_plural: Спринты
label_agile_sprint_name: Название
label_agile_sprint_description: Описание
label_agile_sprint_status: Статус
label_agile_sprint_sharing: Совместное использование
label_agile_sprint_start_date: Дата начала
label_agile_sprint_end_date: Дата окончания
label_agile_sprint_new: Новый спринт
label_agile_sprint_status_open: Открытый
label_agile_sprint_status_active: Активный
label_agile_sprint_status_closed: Закрытый
label_agile_sprint_duration: Продолжительность
label_agile_sprint_duration_select: <<Выберите>>
label_agile_sprint_duration_week_1: 1 неделя
label_agile_sprint_duration_week_2: 2 недели
label_agile_sprint_duration_week_3: 3 недели
label_agile_sprint_duration_week_4: 4 недели
label_agile_sprint_errors_crossed: Выбранные даты пересекают другой спринт
label_agile_sprint_errors_end_more_start: Дата окончания должна быть больше даты начала
label_agile_sprint_errors_open_issues: Нельзя закрыть спринт содержащий открытые задачи
label_agile_sprint_default_chart: График
label_agile_sprint_chart_units: Единицы
label_agile_chart_for_sprint: "Для спринта: %{sprint}"
label_agile_interval_size: Размер интервала
label_agile_day: День
label_agile_week: Неделя
label_agile_month: Месяц
label_agile_quarter: Квартал
label_agile_year: Год
label_agile_charts: Диаграммы
label_agile_chart_units: Тип
label_agile_planning_board_more: Загрузить еще...
label_agile_version_query_new: Новый запрос версий
label_agile_version_my_boards: Мои доски версий
label_agile_version_board_plural: Доски версий
label_agile_version_board_edit: Редактировать доску версий
label_agile_edit_issue: Изменить задачу
label_agile_board_type: Тип доски
label_agile_board_type_kanban: Kanban доска
label_agile_board_type_scrum: Scrum доска
label_agile_board_totals: Итоги
label_agile_board_totals_story_points: Story points
label_agile_board_totals_hours: Часы
label_agile_board_totals_spent_time: Потраченное время
label_agile_board_totals_percent_done: Готовность (%)
label_agile_board_totals_velocity: Скорость закрытия
label_agile_sprint_list_active: Активные спринты
label_agile_sprint_list_future: Следующие спринты
label_agile_sprint_list_old: Прошлые спринты
label_agile_moving_average: Скользящая средняя
label_agile_board_backlog: Backlog
label_agile_board_backlog_column: Backlog column
label_agile_board_search_backlog_issues: Поиск backlog задач
label_agile_version_plural: Версии
label_agile_sprint_add: Добавить спринт
label_agile_version_add: Добавить версию
label_agile_sprint_query_new: Новый запрос спринтов
label_agile_sprint_my_boards: Мои доски спринтов
label_agile_sprint_board_plural: Доски спринтов
label_agile_sprint_board_edit: Редактировать доску спринтов
field_sprint: Спринт
label_agile_no_sprint_issues: Задачи без спринта
field_closed_sprints: Закрытые спринты
field_closed_versions: Закрытые версии
label_agile_sprint_sharing_none: Без совместного использования
label_agile_sprint_sharing_descendants: С подпроектами
label_agile_sprint_sharing_hierarchy: С иерархией проектов
label_agile_sprint_sharing_tree: С деревом проектов
label_agile_sprint_sharing_system: Со всеми проектами

92
config/locales/zh-TW.yml Normal file
View File

@ -0,0 +1,92 @@
# encoding: utf-8
# Simplified Chinese strings go here for Rails i18n
# Author: zhoutt
# Based on file: en.yml
zh-TW:
label_agile: 敏捷
label_agile_board: 敏捷看板
label_agile_board_plural: 敏捷看板
label_agile_board_thumbnails: 縮略圖
label_agile_board_more_issues: 更多問題
error_agile_status_transition: 不能更改問題狀態
label_agile_board_new: 新敏捷看板
label_agile_board_edit: 編輯敏捷看板
label_agile_my_boards: 我的敏捷看板
label_agile_issue_id: 問題ID
permission_manage_public_agile_queries: 管理公共敏捷看板
permission_add_agile_queries: 新建敏捷看板
permission_view_agile_queries: 瀏覽敏捷看板
#1.1.0
label_agile_board_default_fields: 默認卡片區域
label_agile_charts_issues_burndown: 問題燃燒圖
label_agile_charts_work_burndown: 工作燃燒圖
label_agile_charts_number_of_hours: 時長數
label_agile_charts_number_of_issues: 問題數
label_agile_charts_cumulative_flow: 累積流量
label_agile_charts_trackers_cumulative_flow: 追蹤累流量
label_agile_charts_issues_velocity: 速度
label_agile_charts_lead_time: 交付周期
label_agile_charts_average_lead_time: 平均交付周期
label_agile_chart_plural: 敏捷圖標
permission_view_agile_charts: 瀏覽敏捷圖標
label_agile_ideal_work_remaining: 理想
label_agile_actual_work_remaining: 實際
label_agile_chart: 圖表
label_agile_date_from:
label_agile_date_to:
label_agile_chart_dates: 圖標間隔
label_agile_weighed_ideal_work_remaining: 理想權衡
label_agile_status_colors: 狀態顏色
label_agile_charts_burnup: 問題燃燒圖
label_agile_charts_number_of_days: 天數
label_agile_too_many_items: "該圖表因超出可顯示的最大項目數而不能被創建(%{max})"
label_agile_time_reports_items_limit: 時間條目基于圖表的問題限制
label_agile_total_work_remaining: 總計
label_agile_default_chart: 版本默認圖表
#1.1.1
label_agile_charts_average_velocity: 平均速率
label_agile_charts_avarate_number_of_issues: 平均問題數
label_agile_charts_avarate_number_of_hours: 平均時長
#1.2.0
label_agile_color: 顏色
permission_manage_agile_verions: 管理版本規劃
label_agile_version_planning: 版本規劃
error_agile_version_transition: 不能更改問題版本
label_agile_tracker_colors: 跟蹤器顏色
label_agile_issue_priority_colors: 問題優先級顏色
label_agile_color_based_on: 顏色
label_agile_color_no_colors: 無顏色
label_agile_manage_colors: 管理顏色
label_agile_fullscreen: 全屏
label_agile_no_version_issues: 無版本項目
label_agile_charts_work_burnup: 工作燃燒
label_agile_completed: 完成
label_agile_exclude_weekends: 從理想工作中排除周末時間
label_agile_board_truncated: "看板被縮短是由于超出最大顯示項目數 (%{max})"
label_agile_board_items_limit: 敏捷看板項目限制
label_agile_swimlanes: 泳道
label_agile_minimize_closed: 最小化關閉的問題
label_agile_fields: 卡片區域
label_agile_default_board: 默認看板
#1.3.6
text_agile_move_not_possible: 該步驟不可行
#1.3.8
label_agile_parent_issue_tracker_id: 本追蹤
label_agile_sub_issues: 子問題
label_agile_color_green: 綠色
label_agile_color_blue: 藍色
label_agile_color_turquoise: 藍綠色
label_agile_color_lightgreen: 淺綠色
label_agile_color_yellow: 黃色
label_agile_color_orange: 橙色
label_agile_color_red: 紅色
label_agile_color_purple: 紫色
label_agile_color_gray: 灰色
label_agile_has_sub_issues: 子問題

133
config/locales/zh.yml Normal file
View File

@ -0,0 +1,133 @@
# encoding: utf-8
# Simplified Chinese strings go here for Rails i18n
# Author: zhoutt
# Based on file: en.yml
zh:
label_agile: 敏捷
label_agile_board: 敏捷看板
label_agile_board_plural: 敏捷看板
label_agile_board_thumbnails: 缩略图
label_agile_board_more_issues: 更多问题
error_agile_status_transition: 不能更改问题状态
label_agile_board_new: 新敏捷看板
label_agile_board_edit: 编辑敏捷看板
label_agile_my_boards: 我的敏捷看板
label_agile_issue_id: 问题ID
permission_manage_public_agile_queries: 管理公共敏捷看板
permission_add_agile_queries: 新建敏捷看板
permission_view_agile_queries: 浏览敏捷看板
#1.1.0
label_agile_board_default_fields: 默认卡片区域
label_agile_charts_issues_burndown: 问题燃烧图
label_agile_charts_work_burndown_hours: 小时燃尽图
label_agile_charts_work_burndown_sp: 故事点燃烧图
label_agile_charts_number_of_hours: 时长数
label_agile_charts_number_of_issues: 问题数
label_agile_charts_cumulative_flow: 累积流量
label_agile_charts_trackers_cumulative_flow: 追踪累流量
label_agile_charts_issues_velocity: 速度
label_agile_charts_lead_time: 交付周期
label_agile_charts_average_lead_time: 平均交付周期
label_agile_chart_plural: 敏捷图标
permission_view_agile_charts: 浏览敏捷图标
label_agile_ideal_work_remaining: 理想
label_agile_actual_work_remaining: 实际
label_agile_chart: 图表
label_agile_date_from:
label_agile_date_to:
label_agile_chart_dates: 图标间隔
label_agile_weighed_ideal_work_remaining: 理想权衡
label_agile_status_colors: 状态颜色
label_agile_charts_burnup: 问题燃烧图
label_agile_charts_number_of_days: 天数
label_agile_too_many_items: "该图表因超出可显示的最大项目数而不能被创建(%{max})"
label_agile_time_reports_items_limit: 时间条目基于图表的问题限制
label_agile_total_work_remaining: 总计
label_agile_default_chart: 版本默认图表
#1.1.1
label_agile_charts_average_velocity: 平均速率
label_agile_charts_avarate_number_of_issues: 平均问题数
label_agile_charts_avarate_number_of_hours: 平均时长
#1.2.0
label_agile_color: 颜色
permission_manage_agile_verions: 管理版本规划
label_agile_version_planning: 版本规划
error_agile_version_transition: 不能更改问题版本
label_agile_tracker_colors: 跟踪器颜色
label_agile_issue_priority_colors: 问题优先级颜色
label_agile_color_based_on: 颜色
label_agile_color_no_colors: 无颜色
label_agile_manage_colors: 管理颜色
label_agile_fullscreen: 全屏
label_agile_no_version_issues: 无版本项目
label_agile_charts_work_burnup_hours: 小时燃耗
label_agile_charts_work_burnup_sp: 故事点燃耗
label_agile_completed: 完成
label_agile_exclude_weekends: 从理想工作中排除周末时间
label_agile_board_truncated: "看板被缩短是由于超出最大显示项目数 (%{max})"
label_agile_board_items_limit: 敏捷看板项目限制
label_agile_swimlanes: 泳道
label_agile_minimize_closed: 最小化关闭的问题
label_agile_fields: 卡片区域
label_agile_default_board: 默认看板
#1.3.6
text_agile_move_not_possible: 该步骤不可行
#1.3.8
label_agile_parent_issue_tracker_id: 本追踪
label_agile_sub_issues: 子问题
label_agile_color_green: 绿色
label_agile_color_blue: 蓝色
label_agile_color_turquoise: 蓝绿色
label_agile_color_lightgreen: 浅绿色
label_agile_color_yellow: 黄色
label_agile_color_orange: 橙色
label_agile_color_red: 红色
label_agile_color_purple: 紫色
label_agile_color_gray: 灰色
label_agile_has_sub_issues: 子问题
label_agile_light_free_version: 敏捷免费版
label_agile_link_to_pro: 升级到PRO版
label_agile_link_to_pro_demo: PRO版在线演示
label_agile_link_to_more_plugins: 查找更多RedmineUP插件
label_agile_button_agree: 同意
label_agile_license: RedmineUP 协议
label_agile_saving_boards: 保存白板
label_agile_horizontal_swim_lines: 水平泳道线
label_agile_board_sub_columns: 白板子列
label_agile_additional_agile_charts: 额外的敏捷图表
label_agile_coloured_issue_cards: 彩色的问题卡
label_agile_4_more_features: 敏捷4更多功能...
label_agile_upgrade_to_pro: 更新到PRO版使用这个功能
label_agile_day_in_state: 状态
#1.3.10
label_agile_hide_closed_issues_data: 隐藏关闭的问题数据
project_module_agile: 敏捷
#1.3.13
label_agile_last_comment: 最后的评论
#1.4.0
label_agile_esitmate_units: 单元评估
label_agile_trackers_for_sp: 追踪故事点
label_agile_story_points: 故事要点
field_story_points: 故事要点
label_agile_charts_number_of_story_points: 故事要点数量
label_agile_board_columns: 白板列
lable_agile_wip_limit_exceeded: 进展中的工作超出限制
label_agile_wip_limit: 进展中的工作限制
label_agile_add_new_issue: '+ 添加新问题'
label_agile_allow_create_cards: 卡片创建
label_agile_auto_assign_on_move: 移动自动分配
text_agile_create_issue_error: 在创建任务时发生了一个错误
label_agile_inline_comment: 内联注释
label_agile_hours: 小时
field_color: 颜色

47
config/routes.rb Executable file
View File

@ -0,0 +1,47 @@
# 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/>.
# Plugin's routes
# See: http://guides.rubyonrails.org/routing.html
resources :projects do
resources :agile_queries, only: [:new, :create]
end
resources :issues do
get "done_ratio", :to => "agile_journal_details#done_ratio"
get "status", :to => "agile_journal_details#status"
get "assignee", :to => "agile_journal_details#assignee"
member do
get "agile_data", :to => "agile_boards#agile_data"
end
end
resources :agile_queries
get '/projects/:project_id/agile/charts', :to => "agile_charts#show", :as => "project_agile_charts"
get '/agile/charts/', :to => "agile_charts#show", :as => "agile_charts"
get '/agile/charts/render_chart', :to => "agile_charts#render_chart"
get '/agile/charts/select_version_chart', :to => "agile_charts#select_version_chart"
get '/projects/:project_id/agile/board', :to => 'agile_boards#index'
get '/agile/board', :to => 'agile_boards#index'
put '/agile/board', :to => 'agile_boards#update', :as => 'update_agile_board'
get '/agile/issue_tooltip', :to => 'agile_boards#issue_tooltip', :as => 'issue_tooltip'
get '/agile/inline_comment', :to => 'agile_boards#inline_comment', :as => 'agile_inline_comment'
post 'projects/:project_id/agile/create_issue', :to => 'agile_boards#create_issue', :as => 'agile_create_issue'

View File

@ -0,0 +1,30 @@
# 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/>.
class CreateIssueStatusOrders < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def change
create_table :issue_status_orders do |t|
t.integer :issue_id
t.integer :position
end
add_index :issue_status_orders, :issue_id
add_index :issue_status_orders, :position
end
end

View File

@ -0,0 +1,30 @@
# 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/>.
class CreateAgileColors < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def change
create_table :agile_colors do |t|
t.references :container, :polymorphic => true
t.string :color
end
add_index :agile_colors, :container_id
add_index :agile_colors, :container_type
end
end

View File

@ -0,0 +1,40 @@
# 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/>.
class RenameIssueStatusOrders < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def up
remove_index :issue_status_orders, :issue_id
remove_index :issue_status_orders, :position
rename_table :issue_status_orders, :agile_ranks
add_index :agile_ranks, :issue_id
add_index :agile_ranks, :position
end
def down
remove_index :agile_ranks, :issue_id
remove_index :agile_ranks, :position
rename_table :agile_ranks, :issue_status_orders
add_index :issue_status_orders, :issue_id
add_index :issue_status_orders, :position
end
end

View File

@ -0,0 +1,40 @@
# 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/>.
class RenameAgileRanks < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def up
remove_index :agile_ranks, :issue_id if index_exists? :agile_ranks, :issue_id
remove_index :agile_ranks, :position if index_exists? :agile_ranks, :position
rename_table :agile_ranks, :agile_data
add_index :agile_data, :issue_id
add_index :agile_data, :position
end
def down
remove_index :agile_data, :issue_id
remove_index :agile_data, :position
rename_table :agile_data, :agile_ranks
add_index :agile_ranks, :issue_id
add_index :agile_ranks, :position
end
end

View File

@ -0,0 +1,24 @@
# 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/>.
class AddStoryPointsToAgileRanks < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def change
add_column :agile_data, :story_points, :integer
end
end

View File

@ -0,0 +1,33 @@
# 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/>.
class CreateAgileSprints < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def change
create_table :agile_sprints do |t|
t.references :project
t.string :name, null: false
t.text :description
t.integer :status, null: false, default: 0
t.date :start_date, null: false
t.date :end_date, null: false
t.timestamps null: false
end
end
end

View File

@ -0,0 +1,24 @@
# 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/>.
class AddSprintIdToAgileData < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def change
add_column :agile_data, :agile_sprint_id, :integer, index: true
end
end

View File

@ -0,0 +1,24 @@
# 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/>.
class AddSharingToAgileSprint < Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
def change
add_column :agile_sprints, :sharing, :integer, default: 0, null: false, index: true
end
end

346
doc/CHANGELOG Executable file
View File

@ -0,0 +1,346 @@
== Redmine Agile plugin changelog
Redmine Agile plugin - Agile board plugin for redmine
Copyright (C) 2011-2021 RedmineUP
http://www.redmineup.com/
== 2021-05-04 v1.6.1
* Redmine 4.2 support
* Added assignee group board filter
* Added past Sprints
* Added reset sprint on project change
* Fixed missied table SQL error
* Fixed empty data error
* Fixed FCSV warning
* Dropped Redmine <3.0 support
== 2021-02-15 v1.6.0
* Added Agile Sprint sharing
* Fixed sprint/story points copy
* Fixed sprint for on-board created sprints
* Fixed initial install error
* Fixed Sprint query save bug
* Fixed chart dates interval
* Fixed for all-projects query bug
* Fixed chart empty dates bug
* Fixed chart version bug
* Fixed Backlog load-more bug
* Fixed Agile board remarks
* Fixed selected sprint bug
* Fixed Agile board issues display bug
* Fixed missed filters for old Redmine versions
* Fixed empty watcher filter bug
* Fixed workflow issue creation bug
* Updated BR locale (Adriano J. Baptistella)
* Updated pt-BR locale (Adriano J. Baptistella)
== 2020-07-24 v1.5.4
* Added None for Sprint in issue context menu
* Updated zh-tw locale (Hsiao Chung Chiang)
* Updated es locale (Manuel Alba Ortega)
* Fixed agile sprint remaining bug
* Fixed issue copy error
* Fixed submit error for issue search field
* Fixed future data in the charts
* Fixed error with all closed issues
* Fixed full screen board update bug
* Fixed first day for Agile chart
* Fixed chart query visibility bug
* Fixed no version column bug
* Fixed sub project sprint assignment
* Fixed backlog column saving
* Fixed "For all projects" chart bug
== 2020-03-18 v1.5.3
* Added grouping for agine status changes view
* Added issue list sprint filter
* German translation updated (Peter Schossig)
* Added Italian translation (Marco Congia)
* Fixed bug with context menu story points
* Added validation for sprint open issues
* Backlog tab default menu
== 2020-02-05 v1.5.2
* Agile Backlog as a different module
* Fixed between dates chart bug
* Fixed context menu edit bug
* Fixed sprint cards load bug
== 2019-12-10 v1.5.1
* Added Sprint view permissions
* Redmine 4.1 styles support
* New Story points board setting
* Hide Sprint field without available sprints
* Modal edit styles cleanup
* Fixed sprint charts bug
* Fixed MySQL order by bug
== 2019-10-22 v1.5.0
* Agile sprints
* New Backlog tab
* Board types: Scrum and Kanban
* New lead time chart
* Totals for board and swimlanes
* Added board units
* Added board default chart
* Fixed chart caclulation bug
* Fixed custom field js error
== 2019-06-14 v1.4.12
* Fixed light version Redmine 4.0.3+ bug
* Fixed swilanes sorting
* Fixed version planing Load more bug
== 2019-05-15 v1.4.11
* Fixed compatibility issues
== 2019-04-25 v1.4.10
* Fixed light version compatibility bug
== 2019-04-15 v1.4.9
* Redmine 4.0.3 support
* Agile chart units setting
* Charts end date fixes
* Fixed expand all board bug
* Fixed interval size for old user sessions
* Fixed charts with current interval
== 2019-02-08 v1.4.8
* Added new Version planner
* Added saving for Agile charts
* Fixed "Related to" filter
* Fixed "Parent task" filter
* Fixed search sensitive bug
* Fixed empty current version bug
== 2018-09-25 v1.4.7
* Added missing filters: Issue, Description, Private, Watcher
* Added preloading of colors for issues
* Fixed tags render on a card
* Fixed public board permission bug
* Fixed agile board adaptability
* Fixed fullscreen link displaying
* Fixed filtering by Company custom field
== 2018-03-20 v1.4.6
* Redmine 4 support
* Quick seach on the board
* Issue tags field on cards
* Story Points available values setting
* Fixed story point showing bug
* Fixed makeup on issue edit form
* Fixed light version dropable bug
* Fixed custom fields bug with charts
== 2017-09-06 v1.4.5
* Agile charts moved to Chartjs
* Fixed current version filter value
* Fixed issue sorting bug with active story points
== 2017-07-06 v1.4.4
* Redmine 3.4 support
* Color attribute for users
* Agile board <Current version> query filter
* Added initional state for status and assignee history
* Chinese translation update
* Fixed checklist items order
== 2017-03-20 v1.4.3
* Added assignee, status and % done history urls
* Created and Updated dates card fields
* Fixed version from future bug
* Fixed Agile board z-index bug
* Fixed blocked query filter error
* Fixed bug with zero divide for spent time color
* Added trendline first point
== 2016-11-16 v1.4.2
* Trendlines for lead time and velocity charts
* Current active version filter
* Chinese translation (Zuofeng Zhang)
* Issue order for swimlanes
* Fixed bug with session storage overload
* Story points chart data calculation bug fixed
* Firefox and IE bug with on board comments fixed
== 2016-06-20 v1.4.1
* Agile board header height fixed for IE
* Auto assign user on move card
* Burndown/burnup charts fixes for story points and hours
* Context menu for fullscreen board
* Separate charts for SP and hours
* Lock board on autorefresh
== 2016-02-09 v1.4.0
* Story points estimation
* Card assignment error messages
* Cleanup WIP limits option styles
* Highligh inline created cards
* Inline comments board lock
* Fixed version planning access right
* Fixed unicode letters in locale files
* Fixed performance issues with board rendering
== 2016-01-20 v1.3.13
* Inline issue creation
* Work-in-progress limits
* Colored by project
* Adding notes inline
* Checklist on card view
* Last comment card field
* French locale by Olivier Houdas
* Fixed bug with sorting cards in redmine < 3
== 2015-11-03 v1.3.12
* Estimated time sum in column header
* Issue relations filter
* Fixed bug with Mysql migration
* Fixed bug with global board cards view
* Fixed issue context menu bug
== 2015-08-18 v1.3.11
* Setting for closed cards view
* Colorized by assignee
== 2015-08-06 v1.3.10
* Fixed bug with sticky headers after fullscreen auto update
* Colored by spent time
* Fixed bug with version planner unassigned issues
* Added translation for module
* Days in state card field
== 2015-05-20 v1.3.9
* Chinese translation (zhoutt)
* Fixed print media styles for board
* Parent task filter allowed commas
* Version planner filters
== 2015-03-06 v1.3.8
* Redmine 3.0 support fixes
* Autoupdate board on fullscreen view
* N+1 agile color fixes
* New filter Parent issue tracker
* Total hours on version planning columns
* Sub issues card field
* Issue history for color changes
== 2015-02-10 v1.3.6
* Sticky headers in fullscreen mode (Dariusz Kowalski)
* Ajax error messages
* Fixed bug in version planner with wrong column ID
* Fixed 404 when enter hit in version planning form
* Portuguese (Brazil) translation (Marcelo de A. Fernandes)
== 2014-10-28 v1.3.5
* Default boards for projects roles and users
* Sub-projects issues in version planner
== 2014-10-16 v1.3.4
* Added filters for Assignee's role and Assignee's group
* Shared versions support
* Assignee history log
== 2014-09-28 v1.3.3
* German translation (Jan Schulz-Hofen)
* Assignable users on agile board sidebar
* Fixed bug with saving boards for non admin roles for Redmine 2.3
* Fixed bug with authors swimlanes
* Fixed bug in swimlanes with empty versions list
== 2014-06-27 v1.3.2
* Default desc sorting for priority swimlanes
* Rank sorting for parent task swimlanes
* Koren translation (김기원)
* Fixed XSS Vulnerability (Felix Schäfer)
* Fixed Issue status count starting with empty not updated (Felix Schäfer)
* Fixed bug with "Is not" status filter
== 2014-05-05 v1.3.1
* Status Sub-Columns with colon delimiter
* Fixes for SQL Server
== 2014-04-24 v1.3.0
* Swim lanes
* Weekends for burnup and burndown charts
* Burnup charts cleanup
* New board card fields setting
== 2014-04-18 v1.2.0
* Colors
* Version planning
* Touch devices support
* Fullscreen mode
* Spanish translation by (Leandro Russo)
* Created/Closed chart renamed to Issues burnup
* Work burnup chart
* First period effort for budrndown charst
* Fixes in cumulative flow chart
* Fixes with load_more duplications
== 2014-04-09 v1.1.2
* Compatibility fixes
== 2014-04-09 v1.1.1
* New scale divisions for charts
* Fixes for work burndown with subtasks
* Fixed Gantt and Caledar issues
== 2014-04-07 v1.1.0
* Save and manage agile boards
* Select issue card fields
* Permissions for agile boards
* Default issue card columns setting
* Issue card context menu
* Project column
* Issue done ratio and status flows
* Charts (Work burndown chart, Issues burndown chart, Cumulative flow chart, Trackers cumulative flow chart, Velocity chart, Lead time chart, Average lead time chart, Created/Closed chart)
* Fixed bug with load more filters
== 2014-03-18 v1.0.1
* Sorting issues inside status
* Issues lazy loading
== 2014-02-28 v1.0.0
* Initial release
* Draggable issues
* Draggable assignees
* Issue description and attached image preview

339
doc/COPYING Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

26
doc/LICENSE Normal file
View File

@ -0,0 +1,26 @@
LICENSING
RedmineUP Licencing
This End User License Agreement is a binding legal agreement between you and RedmineUP. Purchase, installation or use of RedmineUP Extensions provided on redmineup.com signifies that you have read, understood, and agreed to be bound by the terms outlined below.
RedmineUP GPL Licencing
All Redmine Extensions produced by RedmineUP are released under the GNU General Public License, version 2 (http://www.gnu.org/licenses/gpl-2.0.html). Specifically, the Ruby code portions are distributed under the GPL license. If not otherwise stated, all images, manuals, cascading style sheets, and included JavaScript are NOT GPL, and are released under the RedmineUP Proprietary Use License v1.0 (See below) unless specifically authorized by RedmineUP. Elements of the extensions released under this proprietary license may not be redistributed or repackaged for use other than those allowed by the Terms of Service.
RedmineUP Proprietary Use License (v1.0)
The RedmineUP Proprietary Use License covers any images, cascading stylesheets, manuals and JavaScript files in any extensions produced and/or distributed by redmineup.com. These files are copyrighted by redmineup.com (RedmineUP) and cannot be redistributed in any form without prior consent from redmineup.com (RedmineUP)
Usage Terms
You are allowed to use the Extensions on one or many "production" domains, depending on the type of your license
You are allowed to make any changes to the code, however modified code will not be supported by us.
Modification Of Extensions Produced By RedmineUP.
You are authorized to make any modification(s) to RedmineUP extension Ruby code. However, if you change any Ruby code and it breaks functionality, support may not be available to you.
In accordance with the RedmineUP Proprietary Use License v1.0, you may not release any proprietary files (modified or otherwise) under the GPL license. The terms of this license and the GPL v2 prohibit the removal of the copyright information from any file.
Please contact us if you have any requirements that are not covered by these terms.

71
init.rb Executable file
View File

@ -0,0 +1,71 @@
# 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/>.
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"
require 'redmine'
AGILE_VERSION_NUMBER = '1.6.1'
AGILE_VERSION_TYPE = "Light version"
if ActiveRecord::VERSION::MAJOR >= 4 && !defined?(FCSV)
require 'csv'
FCSV = CSV
end
Redmine::Plugin.register :redmine_agile do
name "Redmine Agile plugin (#{AGILE_VERSION_TYPE})"
author 'RedmineUP'
description 'Scrum and Agile project management plugin for redmine'
version AGILE_VERSION_NUMBER
url 'http://redmineup.com/pages/plugins/agile'
author_url 'mailto:support@redmineup.com'
requires_redmine version_or_higher: '3.0'
settings default: { 'default_columns' => %w(tracker assigned_to) },
partial: 'settings/agile/general'
menu :application_menu, :agile,
{ controller: 'agile_boards', action: 'index' },
caption: :label_agile,
if: Proc.new { User.current.allowed_to?(:view_agile_queries, nil, global: true) }
menu :project_menu, :agile, { controller: 'agile_boards', action: 'index' }, caption: :label_agile,
after: :gantt,
param: :project_id
menu :admin_menu, :agile, { controller: 'settings', action: 'plugin', id: 'redmine_agile' }, caption: :label_agile, html: { class: 'icon' }
project_module :agile do
permission :manage_public_agile_queries, { agile_queries: [:new, :create, :edit, :update, :destroy] }, require: :member
permission :add_agile_queries, { agile_queries: [:new, :create, :edit, :update, :destroy] }, require: :loggedin
permission :view_agile_queries, { agile_boards: [:index,
:update,
:create_issue,
:issue_tooltip,
:inline_comment,
:agile_data,
:backlog_load_more,
:backlog_autocomplete],
agile_queries: :index }, read: true
permission :view_agile_charts, { agile_charts: [:show, :render_chart, :select_version_chart] }, read: true
end
end
require 'redmine_agile'

133
lib/redmine_agile.rb Executable file
View File

@ -0,0 +1,133 @@
# 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/>.
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
TIME_REPORTS_ITEMS = 1000
BOARD_ITEMS = 500
ESTIMATE_HOURS = 'hours'.freeze
ESTIMATE_STORY_POINTS = 'story_points'.freeze
ESTIMATE_UNITS = [ESTIMATE_HOURS, ESTIMATE_STORY_POINTS].freeze
class << self
def time_reports_items_limit
by_settigns = Setting.plugin_redmine_agile['time_reports_items_limit'].to_i
by_settigns > 0 ? by_settigns : TIME_REPORTS_ITEMS
end
def board_items_limit
by_settigns = Setting.plugin_redmine_agile['board_items_limit'].to_i
by_settigns > 0 ? by_settigns : BOARD_ITEMS
end
def issues_per_column
by_settigns = Setting.plugin_redmine_agile['issues_per_column'].to_i
by_settigns > 0 ? by_settigns : ISSUES_PER_COLUMN
end
def default_columns
Setting.plugin_redmine_agile['default_columns'].to_a
end
def default_chart
Setting.plugin_redmine_agile['default_chart'] || Charts::BURNDOWN_CHART
end
def estimate_units
Setting.plugin_redmine_agile['estimate_units'] || 'hours'
end
def use_story_points?
if Setting.plugin_redmine_agile.key?('story_points_on')
Setting.plugin_redmine_agile['story_points_on'] == '1'
else
estimate_units == ESTIMATE_STORY_POINTS
end
end
def trackers_for_sp
Setting.plugin_redmine_agile['trackers_for_sp']
end
def use_story_points_for?(tracker)
return true if trackers_for_sp.blank? && use_story_points?
tracker = tracker.is_a?(Tracker) ? tracker.id.to_s : tracker
trackers_for_sp == tracker && use_story_points?
end
def use_colors?
false
end
def color_base
"none"
end
def minimize_closed?
Setting.plugin_redmine_agile['minimize_closed'].to_i > 0
end
def exclude_weekends?
Setting.plugin_redmine_agile['exclude_weekends'].to_i > 0
end
def auto_assign_on_move?
Setting.plugin_redmine_agile['auto_assign_on_move'].to_i > 0
end
def status_colors?
false
end
def hide_closed_issues_data?
Setting.plugin_redmine_agile['hide_closed_issues_data'].to_i > 0
end
def use_checklist?
@@chcklist_plugin_installed ||= (Redmine::Plugin.installed?(:redmine_checklists))
end
def allow_create_card?
false
end
def allow_inline_comments?
Setting.plugin_redmine_agile['allow_inline_comments'].to_i > 0
end
end
end

View File

@ -0,0 +1,278 @@
# 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
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
TIME_INTERVALS = [DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, YEAR_INTERVAL].freeze
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 data
{ title: '', y_title: '', labels: [], datasets: [] }
end
def self.data(data_scope, options = {})
new(data_scope, options).data
end
protected
def current_date_period
return @current_date_period if @current_date_period
date_period = (@date_to <= Date.today || @options[:date_to].present? ? @period_count : (@period_count - (@date_to - Date.today).to_i / @scale_division) + 1).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) + 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_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
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
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
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
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

View File

@ -0,0 +1,112 @@
# 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
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
@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]
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)
end
{
:title => @graph_title,
:y_title => @y_title,
:labels => @fields,
:datasets => datasets,
:show_tooltips => [0, 2]
}
end
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
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
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)
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

View File

@ -0,0 +1,98 @@
# 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,69 @@
# 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
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
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'
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
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
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

@ -0,0 +1,153 @@
# encoding: utf-8
#
# 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 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]
end
else
container << [l(value[:name]), chart]
end
end
grouped_options_for_select(grouped_options, selected) + options_for_select(container, selected)
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
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
ActionView::Base.send :include, RedmineAgile::AgileHelper

View File

@ -0,0 +1,50 @@
# 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 Hooks
class ControllerIssueHook < Redmine::Hook::ViewListener
def controller_issues_edit_before_save(context={})
add_agile_journal_details(context)
end
def controller_issues_bulk_edit_before_save(context={})
add_agile_journal_details(context)
end
private
def add_agile_journal_details(context)
return false unless context[:issue].project.module_enabled?(:agile)
# return false unless context[:issue].color
old_value = Issue.where(id: context[:issue].id).first || context[:issue]
# save changes for story points to journal
old_sp = old_value.story_points
new_sp = context[:issue].story_points
if !((new_sp == old_sp) || context[:issue].current_journal.blank?)
context[:issue].current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => 'story_points',
:old_value => old_sp,
:value => new_sp)
end
end
end
end
end

View File

@ -0,0 +1,31 @@
# 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 Hooks
class ViewsIssuesHook < Redmine::Hook::ViewListener
def view_issues_sidebar_issues_bottom(context = {})
context[:controller].send(:render_to_string, partial: 'agile_charts/agile_charts', locals: context)
end
render_on :view_issues_form_details_bottom, :partial => 'issues/agile_data_fields'
render_on :view_issues_show_details_bottom, :partial => 'issues/agile_data_labels'
end
end
end

View File

@ -0,0 +1,28 @@
# 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 Hooks
class ViewsLayoutsHook < Redmine::Hook::ViewListener
def view_layouts_base_html_head(context={})
return stylesheet_link_tag(:redmine_agile, :plugin => 'redmine_agile')
end
end
end
end

View File

@ -0,0 +1,26 @@
# 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 Hooks
class ViewsVersionsHook < Redmine::Hook::ViewListener
render_on :view_versions_show_bottom, :partial => "agile_charts/versions_show"
end
end
end

View File

@ -0,0 +1,44 @@
# 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 Patches
module IssueDropPatch
def self.included(base)
base.class_eval do
base.send(:include, InstanceMethods)
end
end
module InstanceMethods
def time_in_statuses
statuses_data = AgileStatusesCollector.new(@issue).grouped_by('status')
statuses = Hash[IssueStatus.where(id: statuses_data.keys).map { |s| [s.id.to_s, s.name] }]
Hash[statuses_data.map { |sid, data| [statuses[sid], (data.map(&:duration).sum / 1.days).round] }]
end
end
end
end
end
unless RedmineCrm::Liquid::IssueDrop.included_modules.include?(RedmineAgile::Patches::IssueDropPatch)
RedmineCrm::Liquid::IssueDrop.send(:include, RedmineAgile::Patches::IssueDropPatch)
end

View File

@ -0,0 +1,75 @@
# 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/>.
require_dependency 'issue'
require_dependency 'agile_data'
module RedmineAgile
module Patches
module IssuePatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
has_one :agile_data, :dependent => :destroy
delegate :position, :to => :agile_data, :allow_nil => true
scope :sorted_by_rank, lambda { eager_load(:agile_data).
order(Arel.sql("COALESCE(#{AgileData.table_name}.position, 999999 )")) }
safe_attributes 'agile_data_attributes', :if => lambda { |issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
accepts_nested_attributes_for :agile_data, :allow_destroy => true
alias_method :agile_data_without_default, :agile_data
alias_method :agile_data, :agile_data_with_default
end
end
module InstanceMethods
def agile_data_with_default
agile_data_without_default || build_agile_data
end
def day_in_state
change_time = journals.joins(:details).where(:journals => { :journalized_id => id, :journalized_type => 'Issue' },
:journal_details => { :prop_key => 'status_id' }).order('created_on DESC').first
change_time.created_on
rescue
created_on
end
def last_comment
journals.where("notes <> ''").order("#{Journal.table_name}.id ASC").last
end
def story_points
@story_points ||= agile_data.story_points
end
def sub_issues
descendants
end
end
end
end
end
unless Issue.included_modules.include?(RedmineAgile::Patches::IssuePatch)
Issue.send(:include, RedmineAgile::Patches::IssuePatch)
end

View File

@ -0,0 +1,33 @@
# 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 Patches
module IssuesControllerPatch
def self.included(base) # :nodoc:
base.class_eval do
end
end
end
end
end
unless IssuesController.included_modules.include?(RedmineAgile::Patches::IssuesControllerPatch)
IssuesController.send(:include, RedmineAgile::Patches::IssuesControllerPatch)
end

View File

@ -0,0 +1,41 @@
# 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 Patches
module ProjectPatch
def self.included(base)
base.class_eval do
base.send(:include, InstanceMethods)
safe_attributes 'agile_color_attributes',
if: lambda { |project, user| user.allowed_to?(:edit_project, project) && user.allowed_to?(:view_agile_queries, project) && RedmineAgile.use_colors? }
end
end
module InstanceMethods
end
end
end
end
unless Project.included_modules.include?(RedmineAgile::Patches::ProjectPatch)
Project.send(:include, RedmineAgile::Patches::ProjectPatch)
end

Some files were not shown because too many files have changed in this diff Show More