Import archive redmine_agile-1_6_1-light
This commit is contained in:
commit
58bb78fea5
19
README.rdoc
Executable file
19
README.rdoc
Executable 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
|
158
app/controllers/agile_boards_controller.rb
Executable file
158
app/controllers/agile_boards_controller.rb
Executable 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
|
140
app/controllers/agile_charts_controller.rb
Normal file
140
app/controllers/agile_charts_controller.rb
Normal 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
|
21
app/controllers/agile_charts_queries_controller.rb
Normal file
21
app/controllers/agile_charts_queries_controller.rb
Normal 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
|
71
app/controllers/agile_journal_details_controller.rb
Normal file
71
app/controllers/agile_journal_details_controller.rb
Normal 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
|
159
app/helpers/agile_boards_helper.rb
Normal file
159
app/helpers/agile_boards_helper.rb
Normal 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}: " 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
|
32
app/helpers/agile_charts_helper.rb
Normal file
32
app/helpers/agile_charts_helper.rb
Normal 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
|
70
app/helpers/agile_support_helper.rb
Normal file
70
app/helpers/agile_support_helper.rb
Normal 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(' » ').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
|
167
app/models/agile_charts_query.rb
Normal file
167
app/models/agile_charts_query.rb
Normal 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
25
app/models/agile_data.rb
Executable 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
679
app/models/agile_query.rb
Normal 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
1
app/models/agile_query/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
.gitattributes export-ignore
|
104
app/models/agile_statuses_collector.rb
Normal file
104
app/models/agile_statuses_collector.rb
Normal 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
|
1
app/views/agile_boards/_add_issue_card.html.erb
Normal file
1
app/views/agile_boards/_add_issue_card.html.erb
Normal file
@ -0,0 +1 @@
|
||||
|
15
app/views/agile_boards/_backlog_column.html.erb
Normal file
15
app/views/agile_boards/_backlog_column.html.erb
Normal 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>
|
26
app/views/agile_boards/_board.html.erb
Normal file
26
app/views/agile_boards/_board.html.erb
Normal 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 %>
|
1
app/views/agile_boards/_form.html.erb
Normal file
1
app/views/agile_boards/_form.html.erb
Normal file
@ -0,0 +1 @@
|
||||
|
98
app/views/agile_boards/_index.html.erb
Normal file
98
app/views/agile_boards/_index.html.erb
Normal 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 %>
|
149
app/views/agile_boards/_issue_card.html.erb
Normal file
149
app/views/agile_boards/_issue_card.html.erb
Normal 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 %>
|
1
app/views/agile_boards/_issue_tooltip.html.erb
Normal file
1
app/views/agile_boards/_issue_tooltip.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= render_issue_tooltip @issue %>
|
17
app/views/agile_boards/_issues_links.html.erb
Normal file
17
app/views/agile_boards/_issues_links.html.erb
Normal 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>
|
15
app/views/agile_boards/_issues_list.html.erb
Normal file
15
app/views/agile_boards/_issues_list.html.erb
Normal 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 %>
|
8
app/views/agile_boards/_members.html.erb
Normal file
8
app/views/agile_boards/_members.html.erb
Normal 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>
|
9
app/views/agile_boards/_upgrade_to_pro.html.erb
Normal file
9
app/views/agile_boards/_upgrade_to_pro.html.erb
Normal 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>
|
7
app/views/agile_boards/agile_data.api.rsb
Normal file
7
app/views/agile_boards/agile_data.api.rsb
Normal 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
|
1
app/views/agile_boards/autocomplete.js.erb
Normal file
1
app/views/agile_boards/autocomplete.js.erb
Normal 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 }) %>')
|
3
app/views/agile_boards/index.html.erb
Executable file
3
app/views/agile_boards/index.html.erb
Executable file
@ -0,0 +1,3 @@
|
||||
<%= render 'index' %>
|
||||
<%= init_agile_tooltip_info %>
|
||||
<%= call_hook(:view_agile_board_bottom, { :issues => @issues, :project => @project, :query => @query }) %>
|
3
app/views/agile_boards/index.js.erb
Normal file
3
app/views/agile_boards/index.js.erb
Normal file
@ -0,0 +1,3 @@
|
||||
$('#content').html('<%= j(render('index')) %>')
|
||||
$('table.issues-board').StickyHeader();
|
||||
<%= init_agile_tooltip_info(:only_code => true) %>
|
3
app/views/agile_boards/inline_comment.html.erb
Normal file
3
app/views/agile_boards/inline_comment.html.erb
Normal 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;" %>
|
1
app/views/agile_boards/load_more.js.erb
Normal file
1
app/views/agile_boards/load_more.js.erb
Normal 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 }) %>')
|
6
app/views/agile_boards/update.js.erb
Normal file
6
app/views/agile_boards/update.js.erb
Normal 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} %>");
|
5
app/views/agile_charts/_agile_charts.html.erb
Normal file
5
app/views/agile_charts/_agile_charts.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<% if User.current.allowed_to?(:view_agile_charts, @project, :global => true) %>
|
||||
|
||||
<ul class=agile-chart-queries>
|
||||
</ul>
|
||||
<% end %>
|
99
app/views/agile_charts/_chart.html.erb
Normal file
99
app/views/agile_charts/_chart.html.erb
Normal 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 %>
|
31
app/views/agile_charts/_versions_show.html.erb
Normal file
31
app/views/agile_charts/_versions_show.html.erb
Normal 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 %>
|
5
app/views/agile_charts/select_version_chart.js.erb
Normal file
5
app/views/agile_charts/select_version_chart.js.erb
Normal 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] %>");
|
||||
}
|
67
app/views/agile_charts/show.html.erb
Normal file
67
app/views/agile_charts/show.html.erb
Normal 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 %>
|
1
app/views/agile_charts_queries/_form.html.erb
Normal file
1
app/views/agile_charts_queries/_form.html.erb
Normal file
@ -0,0 +1 @@
|
||||
|
1
app/views/agile_charts_queries/edit.html.erb
Normal file
1
app/views/agile_charts_queries/edit.html.erb
Normal file
@ -0,0 +1 @@
|
||||
|
8
app/views/agile_charts_queries/new.html.erb
Normal file
8
app/views/agile_charts_queries/new.html.erb
Normal 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> -->
|
9
app/views/agile_journal_details/_status_detail.html.erb
Normal file
9
app/views/agile_journal_details/_status_detail.html.erb
Normal 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>
|
27
app/views/agile_journal_details/assignee.html.erb
Normal file
27
app/views/agile_journal_details/assignee.html.erb
Normal 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 %>
|
28
app/views/agile_journal_details/done_ratio.html.erb
Normal file
28
app/views/agile_journal_details/done_ratio.html.erb
Normal 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 %>
|
1
app/views/agile_journal_details/edit.html.erb
Normal file
1
app/views/agile_journal_details/edit.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<h2>AgileDoneRatioFlowsController#edit</h2>
|
1
app/views/agile_journal_details/new.html.erb
Normal file
1
app/views/agile_journal_details/new.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<h2>AgileDoneRatioFlowsController#new</h2>
|
70
app/views/agile_journal_details/status.html.erb
Normal file
70
app/views/agile_journal_details/status.html.erb
Normal 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);"> </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 %>
|
9
app/views/issues/_agile_data_fields.html.erb
Normal file
9
app/views/issues/_agile_data_fields.html.erb
Normal 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 %>
|
1
app/views/issues/_agile_data_labels.html.erb
Normal file
1
app/views/issues/_agile_data_labels.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= render :partial => 'issue_story_points' %>
|
1
app/views/issues/_issue_spint_form.html.erb
Normal file
1
app/views/issues/_issue_spint_form.html.erb
Normal file
@ -0,0 +1 @@
|
||||
|
7
app/views/issues/_issue_story_points.html.erb
Normal file
7
app/views/issues/_issue_story_points.html.erb
Normal 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();" %>
|
9
app/views/issues/_issue_story_points_form.html.erb
Normal file
9
app/views/issues/_issue_story_points_form.html.erb
Normal 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
1
app/views/projects/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
.gitattributes export-ignore
|
54
app/views/settings/agile/_general.html.erb
Executable file
54
app/views/settings/agile/_general.html.erb
Executable 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
1
app/views/users/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
.gitattributes export-ignore
|
BIN
assets/images/agile.png
Normal file
BIN
assets/images/agile.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 857 B |
BIN
assets/images/fullscreen.png
Normal file
BIN
assets/images/fullscreen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 594 B |
235
assets/javascripts/jquery.simplecolorpicker.js
Executable file
235
assets/javascripts/jquery.simplecolorpicker.js
Executable 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);
|
180
assets/javascripts/jquery.ui.touch-punch.js
vendored
Normal file
180
assets/javascripts/jquery.ui.touch-punch.js
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, 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);
|
787
assets/javascripts/redmine_agile.js
Executable file
787
assets/javascripts/redmine_agile.js
Executable 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;
|
||||
}
|
||||
}
|
||||
};
|
14
assets/javascripts/redmine_agile_sprint.js
Normal file
14
assets/javascripts/redmine_agile_sprint.js
Normal 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);
|
||||
});
|
88
assets/stylesheets/jquery.simplecolorpicker.css
Executable file
88
assets/stylesheets/jquery.simplecolorpicker.css
Executable 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;
|
||||
}
|
||||
|
610
assets/stylesheets/redmine_agile.css
Executable file
610
assets/stylesheets/redmine_agile.css
Executable 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
233
config/locales/de.yml
Normal 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
231
config/locales/en.yml
Executable 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: Can’t 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: Can’t 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
221
config/locales/es.yml
Normal 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
106
config/locales/fr.yml
Normal 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
221
config/locales/it.yml
Normal 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
70
config/locales/ko.yml
Normal 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
222
config/locales/pt-BR.yml
Normal 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
222
config/locales/pt.yml
Normal 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
220
config/locales/ru.yml
Executable 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
92
config/locales/zh-TW.yml
Normal 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
133
config/locales/zh.yml
Normal 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
47
config/routes.rb
Executable 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'
|
30
db/migrate/001_create_issue_status_orders.rb
Executable file
30
db/migrate/001_create_issue_status_orders.rb
Executable 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
|
30
db/migrate/002_create_agile_colors.rb
Normal file
30
db/migrate/002_create_agile_colors.rb
Normal 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
|
40
db/migrate/003_rename_issue_status_orders.rb
Normal file
40
db/migrate/003_rename_issue_status_orders.rb
Normal 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
|
40
db/migrate/004_rename_agile_ranks.rb
Normal file
40
db/migrate/004_rename_agile_ranks.rb
Normal 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
|
24
db/migrate/005_add_story_points_to_agile_ranks.rb
Normal file
24
db/migrate/005_add_story_points_to_agile_ranks.rb
Normal 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
|
33
db/migrate/006_create_agile_sprints.rb
Normal file
33
db/migrate/006_create_agile_sprints.rb
Normal 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
|
24
db/migrate/007_add_sprint_id_to_agile_data.rb
Normal file
24
db/migrate/007_add_sprint_id_to_agile_data.rb
Normal 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
|
24
db/migrate/008_add_sharing_to_agile_sprint.rb
Normal file
24
db/migrate/008_add_sharing_to_agile_sprint.rb
Normal 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
346
doc/CHANGELOG
Executable 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
339
doc/COPYING
Normal 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
26
doc/LICENSE
Normal 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
71
init.rb
Executable 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
133
lib/redmine_agile.rb
Executable 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
|
278
lib/redmine_agile/charts/agile_chart.rb
Normal file
278
lib/redmine_agile/charts/agile_chart.rb
Normal 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
|
112
lib/redmine_agile/charts/burndown_chart.rb
Normal file
112
lib/redmine_agile/charts/burndown_chart.rb
Normal 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
|
98
lib/redmine_agile/charts/charts.rb
Normal file
98
lib/redmine_agile/charts/charts.rb
Normal 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
|
69
lib/redmine_agile/charts/work_burndown_chart.rb
Normal file
69
lib/redmine_agile/charts/work_burndown_chart.rb
Normal 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
|
153
lib/redmine_agile/helpers/agile_helper.rb
Normal file
153
lib/redmine_agile/helpers/agile_helper.rb
Normal 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
|
50
lib/redmine_agile/hooks/controller_issue_hook.rb
Normal file
50
lib/redmine_agile/hooks/controller_issue_hook.rb
Normal 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
|
31
lib/redmine_agile/hooks/views_issues_hook.rb
Executable file
31
lib/redmine_agile/hooks/views_issues_hook.rb
Executable 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
|
28
lib/redmine_agile/hooks/views_layouts_hook.rb
Normal file
28
lib/redmine_agile/hooks/views_layouts_hook.rb
Normal 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
|
26
lib/redmine_agile/hooks/views_versions_hook.rb
Normal file
26
lib/redmine_agile/hooks/views_versions_hook.rb
Normal 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
|
44
lib/redmine_agile/patches/issue_drop_patch.rb
Normal file
44
lib/redmine_agile/patches/issue_drop_patch.rb
Normal 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
|
75
lib/redmine_agile/patches/issue_patch.rb
Executable file
75
lib/redmine_agile/patches/issue_patch.rb
Executable 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
|
33
lib/redmine_agile/patches/issues_controller_patch.rb
Normal file
33
lib/redmine_agile/patches/issues_controller_patch.rb
Normal 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
|
41
lib/redmine_agile/patches/project_patch.rb
Normal file
41
lib/redmine_agile/patches/project_patch.rb
Normal 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
Loading…
Reference in New Issue
Block a user