Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7578e485a5 | ||
|
|
a35b81b9fa | ||
|
|
fd450fd2da | ||
|
|
2c0ba78f70 | ||
|
|
f0f01d370e | ||
|
|
4c330a1241 | ||
|
|
baa4ebd05f | ||
|
|
59f14478ed | ||
|
|
8fefb7c05b | ||
|
|
1feb373c89 | ||
|
|
32fd503cbb | ||
|
|
cf31aeaf81 | ||
|
|
83ea66fd2c | ||
|
|
ef2c5cab2d | ||
|
|
dee6f6b138 | ||
|
|
a4c0c18e3d | ||
|
|
4c82fbb6f8 | ||
|
|
68ded50edc | ||
|
|
72ecb80dc7 | ||
|
|
86ee285eb4 | ||
|
|
c229ea6386 | ||
|
|
687fca170e | ||
|
|
26564b06f7 | ||
|
|
34016c38bd | ||
|
|
15ff361894 | ||
|
|
b45b5f4322 | ||
|
|
8addbc537a | ||
|
|
87eeacba80 | ||
|
|
a7250c41e2 | ||
|
|
7c45396d92 | ||
|
|
c1f98c835c |
23
Gemfile
23
Gemfile
@@ -2,7 +2,7 @@ source :rubygems
|
||||
|
||||
gem "rails", "2.3.14"
|
||||
gem "i18n", "~> 0.4.2"
|
||||
gem "coderay", "~> 1.0.0"
|
||||
gem "coderay", "~> 1.0.6"
|
||||
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
|
||||
gem "tzinfo", "~> 0.3.31"
|
||||
|
||||
@@ -16,18 +16,20 @@ group :openid do
|
||||
gem "ruby-openid", "~> 2.1.4", :require => "openid"
|
||||
end
|
||||
|
||||
# Optional gem for exporting the gantt to a PNG file
|
||||
group :rmagick do
|
||||
# RMagick 2 supports ruby 1.9
|
||||
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
|
||||
# different requirements for the same gem on different platforms
|
||||
gem "rmagick", ">= 2.0.0"
|
||||
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
|
||||
platforms :mri, :mingw do
|
||||
group :rmagick do
|
||||
# RMagick 2 supports ruby 1.9
|
||||
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
|
||||
# different requirements for the same gem on different platforms
|
||||
gem "rmagick", ">= 2.0.0"
|
||||
end
|
||||
end
|
||||
|
||||
# Database gems
|
||||
platforms :mri, :mingw do
|
||||
group :postgresql do
|
||||
gem "pg", "~> 0.9.0"
|
||||
gem "pg", ">= 0.11.0"
|
||||
end
|
||||
|
||||
group :sqlite do
|
||||
@@ -73,6 +75,11 @@ group :test do
|
||||
gem "mocha"
|
||||
end
|
||||
|
||||
if File.exists?('Gemfile.local')
|
||||
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||
instance_eval File.read('Gemfile.local')
|
||||
end
|
||||
|
||||
# Load plugins' Gemfiles
|
||||
Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file|
|
||||
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||
|
||||
@@ -75,9 +75,7 @@ class AdminController < ApplicationController
|
||||
def info
|
||||
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
|
||||
@checklist = [
|
||||
[:text_default_administrator_account_changed,
|
||||
User.find(:first,
|
||||
:conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
|
||||
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
|
||||
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
|
||||
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
|
||||
[:text_rmagick_available, Object.const_defined?(:Magick)]
|
||||
|
||||
@@ -364,18 +364,6 @@ class ApplicationController < ActionController::Base
|
||||
:content_type => 'application/atom+xml'
|
||||
end
|
||||
|
||||
# TODO: remove in Redmine 1.4
|
||||
def self.accept_key_auth(*actions)
|
||||
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
|
||||
accept_rss_auth(*actions)
|
||||
end
|
||||
|
||||
# TODO: remove in Redmine 1.4
|
||||
def accept_key_auth_actions
|
||||
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
|
||||
self.class.accept_rss_auth
|
||||
end
|
||||
|
||||
def self.accept_rss_auth(*actions)
|
||||
if actions.any?
|
||||
write_inheritable_attribute('accept_rss_auth_actions', actions)
|
||||
|
||||
@@ -47,7 +47,7 @@ class BoardsController < ApplicationController
|
||||
:include => [:author, {:last_reply => :author}],
|
||||
:limit => @topic_pages.items_per_page,
|
||||
:offset => @topic_pages.current.offset
|
||||
@message = Message.new
|
||||
@message = Message.new(:board => @board)
|
||||
render :action => 'show', :layout => !request.xhr?
|
||||
}
|
||||
format.atom {
|
||||
|
||||
@@ -42,7 +42,7 @@ class IssueRelationsController < ApplicationController
|
||||
def create
|
||||
@relation = IssueRelation.new(params[:relation])
|
||||
@relation.issue_from = @issue
|
||||
if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
|
||||
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/)
|
||||
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
|
||||
end
|
||||
saved = @relation.save
|
||||
|
||||
@@ -225,12 +225,19 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
target_projects ||= @projects
|
||||
|
||||
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
if @copy
|
||||
@available_statuses = [IssueStatus.default]
|
||||
else
|
||||
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
|
||||
end
|
||||
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
|
||||
@assignables = target_projects.map(&:assignable_users).reduce(:&)
|
||||
@trackers = target_projects.map(&:trackers).reduce(:&)
|
||||
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
|
||||
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
|
||||
if @copy
|
||||
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
|
||||
end
|
||||
|
||||
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
|
||||
render :layout => false if request.xhr?
|
||||
@@ -246,7 +253,7 @@ class IssuesController < ApplicationController
|
||||
@issues.each do |issue|
|
||||
issue.reload
|
||||
if @copy
|
||||
issue = issue.copy
|
||||
issue = issue.copy({}, :attachments => params[:copy_attachments].present?)
|
||||
end
|
||||
journal = issue.init_journal(User.current, params[:notes])
|
||||
issue.safe_attributes = attributes
|
||||
@@ -348,8 +355,6 @@ private
|
||||
# from the params
|
||||
# TODO: Refactor, not everything in here is needed by #edit
|
||||
def update_issue_from_params
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@priorities = IssuePriority.active
|
||||
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
|
||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||
@time_entry.attributes = params[:time_entry]
|
||||
@@ -371,6 +376,8 @@ private
|
||||
end
|
||||
end
|
||||
@issue.safe_attributes = issue_attributes
|
||||
@priorities = IssuePriority.active
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
true
|
||||
end
|
||||
|
||||
@@ -420,7 +427,16 @@ private
|
||||
def parse_params_for_bulk_issue_attributes(params)
|
||||
attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
|
||||
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
|
||||
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
|
||||
if custom = attributes[:custom_field_values]
|
||||
custom.reject! {|k,v| v.blank?}
|
||||
custom.keys.each do |k|
|
||||
if custom[k].is_a?(Array)
|
||||
custom[k] << '' if custom[k].delete('__none__')
|
||||
else
|
||||
custom[k] = '' if custom[k] == '__none__'
|
||||
end
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
end
|
||||
|
||||
@@ -100,6 +100,7 @@ module CustomFieldsHelper
|
||||
when "list"
|
||||
options = []
|
||||
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
|
||||
options << [l(:label_none), '__none__'] unless custom_field.is_required?
|
||||
options += custom_field.possible_values_options(projects)
|
||||
select_tag(field_name, options_for_select(options),
|
||||
:id => field_id, :multiple => custom_field.multiple?)
|
||||
|
||||
@@ -332,7 +332,7 @@ module IssuesHelper
|
||||
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
|
||||
show_value(cv)
|
||||
else
|
||||
value = issue.send(column.name)
|
||||
value = column.value(issue)
|
||||
if value.is_a?(Date)
|
||||
format_date(value)
|
||||
elsif value.is_a?(Time)
|
||||
|
||||
@@ -145,8 +145,8 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
# Returns an unsaved copy of the issue
|
||||
def copy(attributes=nil)
|
||||
copy = self.class.new.copy_from(self)
|
||||
def copy(attributes=nil, copy_options={})
|
||||
copy = self.class.new.copy_from(self, copy_options)
|
||||
copy.attributes = attributes if attributes
|
||||
copy
|
||||
end
|
||||
@@ -509,18 +509,30 @@ class Issue < ActiveRecord::Base
|
||||
!relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
|
||||
end
|
||||
|
||||
# Returns an array of status that user is able to apply
|
||||
# Returns an array of statuses that user is able to apply
|
||||
def new_statuses_allowed_to(user=User.current, include_default=false)
|
||||
statuses = status.find_new_statuses_allowed_to(
|
||||
user.admin ? Role.all : user.roles_for_project(project),
|
||||
tracker,
|
||||
author == user,
|
||||
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
|
||||
)
|
||||
statuses << status unless statuses.empty?
|
||||
statuses << IssueStatus.default if include_default
|
||||
statuses = statuses.uniq.sort
|
||||
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
|
||||
if new_record? && @copied_from
|
||||
[IssueStatus.default, @copied_from.status].compact.uniq.sort
|
||||
else
|
||||
initial_status = nil
|
||||
if new_record?
|
||||
initial_status = IssueStatus.default
|
||||
elsif status_id_was
|
||||
initial_status = IssueStatus.find_by_id(status_id_was)
|
||||
end
|
||||
initial_status ||= status
|
||||
|
||||
statuses = initial_status.find_new_statuses_allowed_to(
|
||||
user.admin ? Role.all : user.roles_for_project(project),
|
||||
tracker,
|
||||
author == user,
|
||||
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
|
||||
)
|
||||
statuses << initial_status unless statuses.empty?
|
||||
statuses << IssueStatus.default if include_default
|
||||
statuses = statuses.compact.uniq.sort
|
||||
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_to_was
|
||||
|
||||
@@ -42,6 +42,12 @@ class MailHandler < ActionMailer::Base
|
||||
super email
|
||||
end
|
||||
|
||||
cattr_accessor :ignored_emails_headers
|
||||
@@ignored_emails_headers = {
|
||||
'X-Auto-Response-Suppress' => 'OOF',
|
||||
'Auto-Submitted' => 'auto-replied'
|
||||
}
|
||||
|
||||
# Processes incoming emails
|
||||
# Returns the created object (eg. an issue, a message) or false
|
||||
def receive(email)
|
||||
@@ -54,6 +60,16 @@ class MailHandler < ActionMailer::Base
|
||||
end
|
||||
return false
|
||||
end
|
||||
# Ignore auto generated emails
|
||||
self.class.ignored_emails_headers.each do |key, ignored_value|
|
||||
value = email.header_string(key)
|
||||
if value && value.to_s.downcase == ignored_value.downcase
|
||||
if logger && logger.info
|
||||
logger.info "MailHandler: ignoring email with #{key}:#{value} header"
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
@user = User.find_by_mail(sender_email) if sender_email.present?
|
||||
if @user && !@user.active?
|
||||
if logger && logger.info
|
||||
|
||||
@@ -359,7 +359,7 @@ class Mailer < ActionMailer::Base
|
||||
|
||||
issues_by_assignee = scope.all(:include => [:status, :assigned_to, :project, :tracker]).group_by(&:assigned_to)
|
||||
issues_by_assignee.each do |assignee, issues|
|
||||
deliver_reminder(assignee, issues, days) if assignee && assignee.active?
|
||||
deliver_reminder(assignee, issues, days) if assignee.is_a?(User) && assignee.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -372,6 +372,17 @@ class Mailer < ActionMailer::Base
|
||||
ActionMailer::Base.perform_deliveries = was_enabled
|
||||
end
|
||||
|
||||
# Sends emails synchronously in the given block
|
||||
def self.with_synched_deliveries(&block)
|
||||
saved_method = ActionMailer::Base.delivery_method
|
||||
if m = saved_method.to_s.match(%r{^async_(.+)$})
|
||||
ActionMailer::Base.delivery_method = m[1].to_sym
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
ActionMailer::Base.delivery_method = saved_method
|
||||
end
|
||||
|
||||
private
|
||||
def initialize_defaults(method_name)
|
||||
super
|
||||
|
||||
@@ -37,7 +37,6 @@ class Message < ActiveRecord::Base
|
||||
:author_key => :author_id
|
||||
acts_as_watchable
|
||||
|
||||
attr_protected :locked, :sticky
|
||||
validates_presence_of :board, :subject, :content
|
||||
validates_length_of :subject, :maximum => 255
|
||||
validate :cannot_reply_to_locked_topic, :on => :create
|
||||
@@ -50,7 +49,7 @@ class Message < ActiveRecord::Base
|
||||
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
|
||||
|
||||
safe_attributes 'subject', 'content'
|
||||
safe_attributes 'locked', 'sticky',
|
||||
safe_attributes 'locked', 'sticky', 'board_id',
|
||||
:if => lambda {|message, user|
|
||||
user.allowed_to?(:edit_messages, message.project)
|
||||
}
|
||||
|
||||
@@ -348,6 +348,11 @@ class User < Principal
|
||||
find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
|
||||
end
|
||||
|
||||
# Returns true if the default admin account can no longer be used
|
||||
def self.default_admin_account_changed?
|
||||
!User.active.find_by_login("admin").try(:check_password?, "admin")
|
||||
end
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
@@ -60,6 +60,13 @@
|
||||
<p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %></p>
|
||||
<% end %>
|
||||
|
||||
<% if @copy && @attachments_present %>
|
||||
<p>
|
||||
<label for='copy_attachments'><%= l(:label_copy_attachments) %></label>
|
||||
<%= check_box_tag 'copy_attachments', '1', true %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<title><%=h html_title %></title>
|
||||
<meta name="description" content="<%= Redmine::Info.app_name %>" />
|
||||
<meta name="keywords" content="issue,bug,tracker" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE" />
|
||||
<%= csrf_meta_tag %>
|
||||
<%= favicon %>
|
||||
<%= stylesheet_link_tag 'application', :media => 'all' %>
|
||||
|
||||
@@ -6,13 +6,17 @@
|
||||
<p><label for="message_subject"><%= l(:field_subject) %></label><br />
|
||||
<%= f.text_field :subject, :size => 120, :id => "message_subject" %>
|
||||
|
||||
<% if !replying && User.current.allowed_to?(:edit_messages, @project) %>
|
||||
<% unless replying %>
|
||||
<% if @message.safe_attribute? 'sticky' %>
|
||||
<label><%= f.check_box :sticky %><%= l(:label_board_sticky) %></label>
|
||||
<% end %>
|
||||
<% if @message.safe_attribute? 'locked' %>
|
||||
<label><%= f.check_box :locked %><%= l(:label_board_locked) %></label>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<% if !replying && !@message.new_record? && User.current.allowed_to?(:edit_messages, @project) %>
|
||||
<% if !replying && !@message.new_record? && @message.safe_attribute?('board_id') %>
|
||||
<p><label><%= l(:label_board) %></label><br />
|
||||
<%= f.select :board_id, @project.boards.collect {|b| [b.name, b.id]} %></p>
|
||||
<% end %>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# MySQL (default setup).
|
||||
# Default setup is given for MySQL with ruby1.8. If you're running Redmine
|
||||
# with MySQL and ruby1.9, replace the adapter name with `mysql2`.
|
||||
# Examples for PostgreSQL and SQLite3 can be found at the end.
|
||||
|
||||
production:
|
||||
adapter: mysql
|
||||
|
||||
@@ -1039,4 +1039,4 @@ fr:
|
||||
label_child_revision: Enfant
|
||||
error_scm_annotate_big_text_file: Cette entrée ne peut pas être annotée car elle excède la taille maximale.
|
||||
setting_repositories_encodings: Encodages des fichiers et des dépôts
|
||||
label_search_for_watchers: Search for watchers to add
|
||||
label_search_for_watchers: Rechercher des observateurs
|
||||
|
||||
@@ -1001,12 +1001,12 @@ zh:
|
||||
label_child_revision: 子修订
|
||||
error_scm_annotate_big_text_file: 输入文本内容超长,无法输入。
|
||||
setting_default_issue_start_date_to_creation_date: 使用当前日期作为新问题的开始日期
|
||||
button_edit_section: Edit this section
|
||||
setting_repositories_encodings: Attachments and repositories encodings
|
||||
description_all_columns: All Columns
|
||||
button_export: Export
|
||||
label_export_options: "%{export_format} export options"
|
||||
error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})
|
||||
button_edit_section: 编辑此区域
|
||||
setting_repositories_encodings: 附件和版本库编码
|
||||
description_all_columns: 所有列
|
||||
button_export: 导出
|
||||
label_export_options: "%{export_format} 导出选项"
|
||||
error_attachment_too_big: 该文件无法上传。超过文件大小限制 (%{max_size})
|
||||
notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
|
||||
label_x_issues:
|
||||
zero: 0 问题
|
||||
|
||||
@@ -339,6 +339,7 @@ ActionController::Routing::Routes.draw do |map|
|
||||
map.resources :roles, :except => :show, :collection => {:permissions => [:get, :post]}
|
||||
map.resources :enumerations, :except => :show
|
||||
|
||||
map.connect 'projects/:id/search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
|
||||
map.connect 'search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
|
||||
|
||||
map.connect 'mail_handler', :controller => 'mail_handler',
|
||||
|
||||
@@ -4,13 +4,15 @@ Redmine - project management software
|
||||
Copyright (C) 2006-2012 Jean-Philippe Lang
|
||||
http://www.redmine.org/
|
||||
|
||||
== TBD v1.4.0
|
||||
== 2012-04-14 v1.4.0
|
||||
|
||||
* Defect #2719: Increase username length limit from 30 to 60
|
||||
* Defect #3087: Revision referring to issues across all projects
|
||||
* Defect #4824: Unable to connect (can't convert Net::LDAP::LdapError into String)
|
||||
* Defect #5058: reminder mails are not sent when delivery_method is :async_smtp
|
||||
* Defect #6859: Moving issues to a tracker with different custom fields should let fill these fields
|
||||
* Defect #7398: Error when trying to quick create a version with required custom field
|
||||
* Defect #7495: Python multiline comments highlighting problem in Repository browser
|
||||
* Defect #7826: bigdecimal-segfault-fix.rb must be removed for Oracle
|
||||
* Defect #7920: Attempted to update a stale object when copying a project
|
||||
* Defect #8857: Git: Too long in fetching repositories after upgrade from 1.1 or new branch at first time
|
||||
@@ -20,6 +22,7 @@ http://www.redmine.org/
|
||||
* Defect #9978: Japanese "permission_add_issue_watchers" is wrong
|
||||
* Defect #10006: Email reminders are sent for closed issues
|
||||
* Defect #10150: CSV export and spent time: rounding issue
|
||||
* Defect #10168: CSV export breaks custom columns
|
||||
* Defect #10181: Issue context menu and bulk edit form show irrelevant statuses
|
||||
* Defect #10198: message_id regex in pop3.rb only recognizes Message-ID header (not Message-Id)
|
||||
* Defect #10251: Description diff link in note details is relative when received by email
|
||||
@@ -29,14 +32,12 @@ http://www.redmine.org/
|
||||
* Defect #10410: [Localization] Grammar issue of Simplified Chinese in zh.yml
|
||||
* Defect #10442: Ruby 1.9.3 Time Zone setting Internal error.
|
||||
* Defect #10467: Confusing behavior while moving issue to a project with disabled Issues module
|
||||
* Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass)
|
||||
* Defect #10554: Defect symbols when exporting tasks in pdf
|
||||
* Defect #10575: Uploading of attachments which filename contains non-ASCII chars fails with Ruby 1.9
|
||||
* Defect #10590: WikiContent::Version#text return string with #<Encoding:ASCII-8BIT> when uncompressed
|
||||
* Defect #10591: Dutch "label_file_added" translation is wrong
|
||||
* Defect #10593: Error: 'incompatible character encodings: UTF-8 and ASCII-8BIT' (old annoing issue) on ruby-1.9.3
|
||||
* Defect #10600: Watchers search generates an Internal error
|
||||
* Defect #10602: Attachment link has get parameter :class
|
||||
* Defect #10605: Bulk edit selected issues does not allow selection of blank values for custom fields
|
||||
* Defect #10619: When changing status before tracker, it shows improper status
|
||||
* Feature #779: Multiple SCM per project
|
||||
* Feature #971: Add "Spent time" column to query
|
||||
* Feature #1060: Add a LDAP-filter using external auth sources
|
||||
@@ -63,6 +64,7 @@ http://www.redmine.org/
|
||||
* Feature #6296: Bulk-edit custom fields through context menu
|
||||
* Feature #6386: Issue mail should render the HTML version of the issue details
|
||||
* Feature #6449: Edit a wiki page's parent on the edit page
|
||||
* Feature #6555: Double-click on "Submit" and "Save" buttons should not send two requests to server
|
||||
* Feature #7361: Highlight active query in the side bar
|
||||
* Feature #7420: Rest API for projects members
|
||||
* Feature #7603: Please make editing issues more obvious than "Change properties (More)"
|
||||
@@ -74,20 +76,32 @@ http://www.redmine.org/
|
||||
* Feature #9995: Time entries insertion, "Create and continue" button
|
||||
* Feature #10020: Enable global time logging at /time_entries/new
|
||||
* Feature #10042: Bulk change private flag
|
||||
* Feature #10046: Update installation doc on release
|
||||
* Feature #10126: Add members of subprojects in the assignee and author filters
|
||||
* Feature #10131: Include custom fiels in time entries API responses
|
||||
* Feature #10207: Git: use default branch from HEAD
|
||||
* Feature #10208: Estonian translation
|
||||
* Feature #10253: Better handling of attachments when validation fails
|
||||
* Feature #10350: Bulk copy should allow for changing the target version
|
||||
* Feature #10607: Ignore out-of-office incoming emails
|
||||
* Feature #10635: Adding time like "123 Min" is invalid
|
||||
* Patch #9998: Make attachement "Optional Description" less wide
|
||||
* Patch #10066: i18n not working with russian gem
|
||||
* Patch #10128: Disable IE 8 compatibility mode to fix wrong div.autoscroll scroll bar behaviour
|
||||
* Patch #10155: Russian translation changed
|
||||
* Patch #10464: Enhanced PDF output for Issues list
|
||||
* Patch #10470: Efficiently process new git revisions in a single batch
|
||||
* Patch #10513: Dutch translation improvement
|
||||
|
||||
== 2012-04-14 v1.3.3
|
||||
|
||||
* Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass)
|
||||
* Defect #10554: Defect symbols when exporting tasks in pdf
|
||||
* Defect #10564: Unable to change locked, sticky flags and board when editing a message
|
||||
* Defect #10591: Dutch "label_file_added" translation is wrong
|
||||
* Defect #10622: "Default administrator account changed" is always true
|
||||
* Patch #10555: rake redmine:send_reminders aborted if issue assigned to group
|
||||
* Patch #10611: Simplified Chinese translations for 1.3-stable
|
||||
|
||||
== 2012-03-11 v1.3.2
|
||||
|
||||
* Defect #8194: {{toc}} uses identical anchors for subsections with the same name
|
||||
|
||||
@@ -31,6 +31,10 @@ Optional:
|
||||
of the rmagick gem using:
|
||||
bundle install --without development test rmagick
|
||||
|
||||
If you need to load some gems that are not required by Redmine core (eg. fcgi),
|
||||
you can create a file named Gemfile.local at the root of your redmine directory.
|
||||
It will be loaded automatically when running `bundle install`.
|
||||
|
||||
3. Create an empty utf8 encoded database: "redmine" for example
|
||||
|
||||
4. Configure the database parameters in config/database.yml
|
||||
|
||||
@@ -30,7 +30,7 @@ module Redmine #:nodoc:
|
||||
# 2:30 => 2.5
|
||||
s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 }
|
||||
# 2h30, 2h, 30m => 2.5, 2, 0.5
|
||||
s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] }
|
||||
s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}i) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] }
|
||||
end
|
||||
# 2,5 => 2.5
|
||||
s.gsub!(',', '.')
|
||||
|
||||
@@ -40,7 +40,7 @@ module Redmine
|
||||
# Should not return line numbers nor outer pre tag
|
||||
def highlight_by_filename(text, filename)
|
||||
language = ::CodeRay::FileType[filename]
|
||||
language ? ::CodeRay.scan(text, language).html : ERB::Util.h(text)
|
||||
language ? ::CodeRay.scan(text, language).html(:break_lines => true) : ERB::Util.h(text)
|
||||
end
|
||||
|
||||
# Highlights +text+ using +language+ syntax
|
||||
|
||||
@@ -10,7 +10,7 @@ module Redmine
|
||||
# * official release: nil
|
||||
# * stable branch: stable
|
||||
# * trunk: devel
|
||||
BRANCH = 'devel'
|
||||
BRANCH = 'stable'
|
||||
|
||||
def self.revision
|
||||
revision = nil
|
||||
|
||||
@@ -23,7 +23,7 @@ module Redmine
|
||||
end
|
||||
|
||||
def link_to(name, options={})
|
||||
url = { :format => name.to_s.downcase }.merge(options.delete(:url) || {})
|
||||
url = { :format => name.to_s.downcase }.merge(options.delete(:url) || {}).except('page')
|
||||
caption = options.delete(:caption) || name
|
||||
html_options = { :class => name.to_s.downcase, :rel => 'nofollow' }.merge(options)
|
||||
@view.content_tag('span', @view.link_to(caption, url, html_options))
|
||||
|
||||
@@ -48,8 +48,8 @@ namespace :ci do
|
||||
test_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' }
|
||||
when 'postgresql'
|
||||
raise "Error creating databases" unless
|
||||
system(%|psql -U jenkins -d postgres -c "create database #{dev_db_name} owner jenkins encoding 'UTF8';|) &&
|
||||
system(%|psql -U jenkins -d postgres -c "create database #{test_db_name} owner jenkins encoding 'UTF8';|)
|
||||
system(%|psql -U jenkins -d postgres -c "create database #{dev_db_name} owner jenkins encoding 'UTF8';"|) &&
|
||||
system(%|psql -U jenkins -d postgres -c "create database #{test_db_name} owner jenkins encoding 'UTF8';"|)
|
||||
dev_conf = { 'adapter' => 'postgresql', 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' }
|
||||
test_conf = { 'adapter' => 'postgresql', 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' }
|
||||
when 'sqlite3'
|
||||
|
||||
@@ -35,7 +35,9 @@ namespace :redmine do
|
||||
options[:project] = ENV['project'] if ENV['project']
|
||||
options[:tracker] = ENV['tracker'].to_i if ENV['tracker']
|
||||
options[:users] = (ENV['users'] || '').split(',').each(&:strip!)
|
||||
|
||||
Mailer.reminders(options)
|
||||
|
||||
Mailer.with_synched_deliveries do
|
||||
Mailer.reminders(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -520,4 +520,16 @@ function hideOnLoad() {
|
||||
});
|
||||
}
|
||||
|
||||
function addFormObserversForDoubleSubmit() {
|
||||
$$('form[method=post]').each(function(el) {
|
||||
Event.observe(el, 'submit', function(e) {
|
||||
var form = Event.element(e);
|
||||
form.select('input[type=submit]').each(function(btn) {
|
||||
btn.disable();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Event.observe(window, 'load', hideOnLoad);
|
||||
Event.observe(window, 'load', addFormObserversForDoubleSubmit);
|
||||
|
||||
@@ -110,92 +110,89 @@ div.action_A { background: #bfb }
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.syntaxhl .code pre { overflow: auto }
|
||||
.syntaxhl .debug { color:white ! important; background:blue ! important; }
|
||||
.syntaxhl .debug { color: white !important; background: blue !important; }
|
||||
|
||||
.syntaxhl .attribute-name { color:#b48 }
|
||||
.syntaxhl .annotation { color:#007 }
|
||||
.syntaxhl .attribute-name { color:#b48 }
|
||||
.syntaxhl .attribute-value { color:#700 }
|
||||
.syntaxhl .binary { color:#509 }
|
||||
|
||||
.syntaxhl .char .content { color:#D20 }
|
||||
.syntaxhl .char .delimiter { color:#710 }
|
||||
.syntaxhl .char { color:#D20 }
|
||||
.syntaxhl .class { color:#B06; font-weight:bold }
|
||||
.syntaxhl .class-variable { color:#369 }
|
||||
.syntaxhl .color { color:#0A0 }
|
||||
.syntaxhl .comment { color:#777 }
|
||||
.syntaxhl .comment .char { color:#444 }
|
||||
.syntaxhl .comment .delimiter { color:#444 }
|
||||
|
||||
.syntaxhl .char { color:#D20 }
|
||||
.syntaxhl .char .content { color:#D20 }
|
||||
.syntaxhl .char .delimiter { color:#710 }
|
||||
|
||||
.syntaxhl .class { color:#B06; font-weight:bold }
|
||||
.syntaxhl .complex { color:#A08 }
|
||||
.syntaxhl .constant { color:#036; font-weight:bold }
|
||||
.syntaxhl .color { color:#0A0 }
|
||||
.syntaxhl .class-variable { color:#369 }
|
||||
.syntaxhl .decorator { color:#B0B }
|
||||
.syntaxhl .definition { color:#099; font-weight:bold }
|
||||
.syntaxhl .directive { color:#088; font-weight:bold }
|
||||
.syntaxhl .delimiter { color:black }
|
||||
.syntaxhl .directive { color:#088; font-weight:bold }
|
||||
.syntaxhl .doc { color:#970 }
|
||||
.syntaxhl .doctype { color:#34b }
|
||||
.syntaxhl .doc-string { color:#D42; font-weight:bold }
|
||||
.syntaxhl .escape { color:#666 }
|
||||
.syntaxhl .doctype { color:#34b }
|
||||
.syntaxhl .entity { color:#800; font-weight:bold }
|
||||
.syntaxhl .error { color:#F00; background-color:#FAA }
|
||||
.syntaxhl .escape { color:#666 }
|
||||
.syntaxhl .exception { color:#C00; font-weight:bold }
|
||||
.syntaxhl .float { color:#60E }
|
||||
.syntaxhl .function { color:#06B; font-weight:bold }
|
||||
.syntaxhl .global-variable { color:#d70 }
|
||||
.syntaxhl .hex { color:#02b }
|
||||
.syntaxhl .integer { color:#00D }
|
||||
.syntaxhl .include { color:#B44; font-weight:bold }
|
||||
.syntaxhl .imaginary { color:#f00 }
|
||||
|
||||
.syntaxhl .include { color:#B44; font-weight:bold }
|
||||
.syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
|
||||
.syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
|
||||
|
||||
.syntaxhl .instance-variable { color:#33B }
|
||||
.syntaxhl .integer { color:#00D }
|
||||
.syntaxhl .key .char { color: #60f }
|
||||
.syntaxhl .key .delimiter { color: #404 }
|
||||
.syntaxhl .key { color: #606 }
|
||||
.syntaxhl .keyword { color:#080; font-weight:bold }
|
||||
.syntaxhl .label { color:#970; font-weight:bold }
|
||||
.syntaxhl .local-variable { color:#963 }
|
||||
.syntaxhl .namespace { color:#707; font-weight:bold }
|
||||
.syntaxhl .octal { color:#40E }
|
||||
.syntaxhl .operator { }
|
||||
.syntaxhl .predefined-constant { color:#069 }
|
||||
.syntaxhl .predefined { color:#369; font-weight:bold }
|
||||
.syntaxhl .predefined-constant { color:#069 }
|
||||
.syntaxhl .predefined-type { color:#0a5; font-weight:bold }
|
||||
.syntaxhl .preprocessor { color:#579 }
|
||||
.syntaxhl .pseudo-class { color:#00C; font-weight:bold }
|
||||
.syntaxhl .reserved { color:#080; font-weight:bold }
|
||||
|
||||
.syntaxhl .key .char { color: #60f }
|
||||
.syntaxhl .key .delimiter { color: #404 }
|
||||
.syntaxhl .key { color: #606 }
|
||||
.syntaxhl .keyword { color:#080; font-weight:bold }
|
||||
|
||||
.syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
|
||||
.syntaxhl .regexp .content { color:#808 }
|
||||
.syntaxhl .regexp .delimiter { color:#404 }
|
||||
.syntaxhl .regexp .modifier { color:#C2C }
|
||||
|
||||
.syntaxhl .string { background-color:hsla(0,100%,50%,0.05); }
|
||||
.syntaxhl .string .content { color: #D20 }
|
||||
.syntaxhl .string .char { color: #b0b }
|
||||
.syntaxhl .string .delimiter { color: #710 }
|
||||
.syntaxhl .string .modifier { color: #E40 }
|
||||
|
||||
.syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
|
||||
.syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
|
||||
.syntaxhl .reserved { color:#080; font-weight:bold }
|
||||
.syntaxhl .shell .content { color:#2B2 }
|
||||
.syntaxhl .shell .delimiter { color:#161 }
|
||||
|
||||
.syntaxhl .symbol { color:#A60 }
|
||||
.syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
|
||||
.syntaxhl .string .char { color: #b0b }
|
||||
.syntaxhl .string .content { color: #D20 }
|
||||
.syntaxhl .string .delimiter { color: #710 }
|
||||
.syntaxhl .string .modifier { color: #E40 }
|
||||
.syntaxhl .string { background-color:hsla(0,100%,50%,0.05); }
|
||||
.syntaxhl .symbol .content { color:#A60 }
|
||||
.syntaxhl .symbol .delimiter { color:#630 }
|
||||
|
||||
.syntaxhl .symbol { color:#A60 }
|
||||
.syntaxhl .tag { color:#070 }
|
||||
.syntaxhl .type { color:#339; font-weight:bold }
|
||||
.syntaxhl .value { color: #088; }
|
||||
.syntaxhl .variable { color:#037 }
|
||||
|
||||
|
||||
.syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
|
||||
.syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
|
||||
.syntaxhl .change { color: #bbf; background: #007; }
|
||||
.syntaxhl .head { color: #f8f; background: #505 }
|
||||
.syntaxhl .head .filename { color: white; }
|
||||
|
||||
.syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
|
||||
.syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
|
||||
|
||||
.syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
|
||||
.syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
|
||||
.syntaxhl .change .change { color: #88f }
|
||||
.syntaxhl .head .head { color: #f4f }
|
||||
|
||||
@@ -48,6 +48,10 @@ class IssueRelationsControllerTest < ActionController::TestCase
|
||||
post :create, :issue_id => 1,
|
||||
:relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''}
|
||||
end
|
||||
relation = IssueRelation.first(:order => 'id DESC')
|
||||
assert_equal 1, relation.issue_from_id
|
||||
assert_equal 2, relation.issue_to_id
|
||||
assert_equal 'relates', relation.relation_type
|
||||
end
|
||||
|
||||
def test_create_xhr
|
||||
@@ -61,6 +65,9 @@ class IssueRelationsControllerTest < ActionController::TestCase
|
||||
assert_select 'tr', 2 # relations
|
||||
end
|
||||
end
|
||||
relation = IssueRelation.first(:order => 'id DESC')
|
||||
assert_equal 3, relation.issue_from_id
|
||||
assert_equal 1, relation.issue_to_id
|
||||
end
|
||||
|
||||
def test_create_should_accept_id_with_hash
|
||||
@@ -69,6 +76,18 @@ class IssueRelationsControllerTest < ActionController::TestCase
|
||||
post :create, :issue_id => 1,
|
||||
:relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''}
|
||||
end
|
||||
relation = IssueRelation.first(:order => 'id DESC')
|
||||
assert_equal 2, relation.issue_to_id
|
||||
end
|
||||
|
||||
def test_create_should_strip_id
|
||||
assert_difference 'IssueRelation.count' do
|
||||
@request.session[:user_id] = 3
|
||||
post :create, :issue_id => 1,
|
||||
:relation => {:issue_to_id => ' 2 ', :relation_type => 'relates', :delay => ''}
|
||||
end
|
||||
relation = IssueRelation.first(:order => 'id DESC')
|
||||
assert_equal 2, relation.issue_to_id
|
||||
end
|
||||
|
||||
def test_create_should_not_break_with_non_numerical_id
|
||||
|
||||
@@ -305,6 +305,15 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_index_should_omit_page_param_in_export_links
|
||||
get :index, :page => 2
|
||||
assert_response :success
|
||||
assert_select 'a.atom[href=/issues.atom]'
|
||||
assert_select 'a.csv[href=/issues.csv]'
|
||||
assert_select 'a.pdf[href=/issues.pdf]'
|
||||
assert_select 'form#csv-export-form[action=/issues.csv]'
|
||||
end
|
||||
|
||||
def test_index_csv
|
||||
get :index, :format => 'csv'
|
||||
assert_response :success
|
||||
@@ -1363,6 +1372,22 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal 'This is the test_new issue', issue.subject
|
||||
end
|
||||
|
||||
def test_update_new_form_should_propose_transitions_based_on_initial_status
|
||||
@request.session[:user_id] = 2
|
||||
Workflow.delete_all
|
||||
Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
|
||||
Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
|
||||
Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
|
||||
|
||||
xhr :post, :new, :project_id => 1,
|
||||
:issue => {:tracker_id => 1,
|
||||
:status_id => 5,
|
||||
:subject => 'This is an issue'}
|
||||
|
||||
assert_equal 5, assigns(:issue).status_id
|
||||
assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
|
||||
end
|
||||
|
||||
def test_post_create
|
||||
@request.session[:user_id] = 2
|
||||
assert_difference 'Issue.count' do
|
||||
@@ -2171,6 +2196,23 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal 'This is the test_new issue', issue.subject
|
||||
end
|
||||
|
||||
def test_update_edit_form_should_propose_transitions_based_on_initial_status
|
||||
@request.session[:user_id] = 2
|
||||
Workflow.delete_all
|
||||
Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
|
||||
Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
|
||||
Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
|
||||
|
||||
xhr :put, :new, :project_id => 1,
|
||||
:id => 2,
|
||||
:issue => {:tracker_id => 2,
|
||||
:status_id => 5,
|
||||
:subject => 'This is an issue'}
|
||||
|
||||
assert_equal 5, assigns(:issue).status_id
|
||||
assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
|
||||
end
|
||||
|
||||
def test_update_edit_form_with_project_change
|
||||
@request.session[:user_id] = 2
|
||||
xhr :put, :new, :project_id => 1,
|
||||
@@ -2665,7 +2707,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:attributes => {:name => "issue[custom_field_values][#{field.id}]"},
|
||||
:children => {
|
||||
:only => {:tag => 'option'},
|
||||
:count => Project.find(1).users.count + 1
|
||||
:count => Project.find(1).users.count + 2 # "no change" + "none" options
|
||||
}
|
||||
end
|
||||
|
||||
@@ -2681,7 +2723,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:attributes => {:name => "issue[custom_field_values][#{field.id}]"},
|
||||
:children => {
|
||||
:only => {:tag => 'option'},
|
||||
:count => Project.find(1).shared_versions.count + 1
|
||||
:count => Project.find(1).shared_versions.count + 2 # "no change" + "none" options
|
||||
}
|
||||
end
|
||||
|
||||
@@ -2698,7 +2740,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:attributes => {:name => "issue[custom_field_values][1][]"},
|
||||
:children => {
|
||||
:only => {:tag => 'option'},
|
||||
:count => 3
|
||||
:count => field.possible_values.size + 1 # "none" options
|
||||
}
|
||||
end
|
||||
|
||||
@@ -2924,6 +2966,17 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal '777', journal.details.first.value
|
||||
end
|
||||
|
||||
def test_bulk_update_custom_field_to_blank
|
||||
@request.session[:user_id] = 2
|
||||
post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
|
||||
:issue => {:priority_id => '',
|
||||
:assigned_to_id => '',
|
||||
:custom_field_values => {'1' => '__none__'}}
|
||||
assert_response 302
|
||||
assert_equal '', Issue.find(1).custom_field_value(1)
|
||||
assert_equal '', Issue.find(3).custom_field_value(1)
|
||||
end
|
||||
|
||||
def test_bulk_update_multi_custom_field
|
||||
field = CustomField.find(1)
|
||||
field.update_attribute :multiple, true
|
||||
@@ -2942,6 +2995,20 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_nil Issue.find(2).custom_field_value(1)
|
||||
end
|
||||
|
||||
def test_bulk_update_multi_custom_field_to_blank
|
||||
field = CustomField.find(1)
|
||||
field.update_attribute :multiple, true
|
||||
|
||||
@request.session[:user_id] = 2
|
||||
post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
|
||||
:issue => {:priority_id => '',
|
||||
:assigned_to_id => '',
|
||||
:custom_field_values => {'1' => ['__none__']}}
|
||||
assert_response 302
|
||||
assert_equal [''], Issue.find(1).custom_field_value(1)
|
||||
assert_equal [''], Issue.find(3).custom_field_value(1)
|
||||
end
|
||||
|
||||
def test_bulk_update_unassign
|
||||
assert_not_nil Issue.find(2).assigned_to
|
||||
@request.session[:user_id] = 2
|
||||
@@ -2990,6 +3057,19 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
|
||||
end
|
||||
|
||||
def test_get_bulk_copy
|
||||
@request.session[:user_id] = 2
|
||||
get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
|
||||
assert_response :success
|
||||
assert_template 'bulk_edit'
|
||||
|
||||
issues = assigns(:issues)
|
||||
assert_not_nil issues
|
||||
assert_equal [1, 2, 3], issues.map(&:id).sort
|
||||
|
||||
assert_select 'input[name=copy_attachments]'
|
||||
end
|
||||
|
||||
def test_bulk_copy_to_another_project
|
||||
@request.session[:user_id] = 2
|
||||
assert_difference 'Issue.count', 2 do
|
||||
@@ -2998,27 +3078,41 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
end
|
||||
end
|
||||
assert_redirected_to '/projects/ecookbook/issues'
|
||||
|
||||
copies = Issue.all(:order => 'id DESC', :limit => issues.size)
|
||||
copies.each do |copy|
|
||||
assert_equal 2, copy.project_id
|
||||
end
|
||||
end
|
||||
|
||||
def test_bulk_copy_should_allow_not_changing_the_issue_attributes
|
||||
@request.session[:user_id] = 2
|
||||
issue_before_move = Issue.find(1)
|
||||
assert_difference 'Issue.count', 1 do
|
||||
assert_no_difference 'Project.find(1).issues.count' do
|
||||
post :bulk_update, :ids => [1], :copy => '1',
|
||||
:issue => {
|
||||
:project_id => '2', :tracker_id => '', :assigned_to_id => '',
|
||||
:status_id => '', :start_date => '', :due_date => ''
|
||||
}
|
||||
end
|
||||
issues = [
|
||||
Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil),
|
||||
Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3)
|
||||
]
|
||||
|
||||
assert_difference 'Issue.count', issues.size do
|
||||
post :bulk_update, :ids => issues.map(&:id), :copy => '1',
|
||||
:issue => {
|
||||
:project_id => '', :tracker_id => '', :assigned_to_id => '',
|
||||
:status_id => '', :start_date => '', :due_date => ''
|
||||
}
|
||||
end
|
||||
|
||||
copies = Issue.all(:order => 'id DESC', :limit => issues.size)
|
||||
issues.each do |orig|
|
||||
copy = copies.detect {|c| c.subject == orig.subject}
|
||||
assert_not_nil copy
|
||||
assert_equal orig.project_id, copy.project_id
|
||||
assert_equal orig.tracker_id, copy.tracker_id
|
||||
assert_equal orig.status_id, copy.status_id
|
||||
assert_equal orig.assigned_to_id, copy.assigned_to_id
|
||||
assert_equal orig.priority_id, copy.priority_id
|
||||
end
|
||||
issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
|
||||
assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
|
||||
assert_equal issue_before_move.status_id, issue_after_move.status_id
|
||||
assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
|
||||
end
|
||||
|
||||
def test_bulk_copy_should_allow_changing_the_issue_attributes
|
||||
def test_bulk_copy_should_allow_changing_the_issue_attributes
|
||||
# Fixes random test failure with Mysql
|
||||
# where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
|
||||
# doesn't return the expected results
|
||||
@@ -3030,7 +3124,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
post :bulk_update, :ids => [1, 2], :copy => '1',
|
||||
:issue => {
|
||||
:project_id => '2', :tracker_id => '', :assigned_to_id => '4',
|
||||
:status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
|
||||
:status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -3040,7 +3134,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
copied_issues.each do |issue|
|
||||
assert_equal 2, issue.project_id, "Project is incorrect"
|
||||
assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
|
||||
assert_equal 3, issue.status_id, "Status is incorrect"
|
||||
assert_equal 1, issue.status_id, "Status is incorrect"
|
||||
assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
|
||||
assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
|
||||
end
|
||||
@@ -3064,6 +3158,36 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal 'Copying one issue', journal.notes
|
||||
end
|
||||
|
||||
def test_bulk_copy_should_allow_not_copying_the_attachments
|
||||
attachment_count = Issue.find(3).attachments.size
|
||||
assert attachment_count > 0
|
||||
@request.session[:user_id] = 2
|
||||
|
||||
assert_difference 'Issue.count', 1 do
|
||||
assert_no_difference 'Attachment.count' do
|
||||
post :bulk_update, :ids => [3], :copy => '1',
|
||||
:issue => {
|
||||
:project_id => ''
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_bulk_copy_should_allow_copying_the_attachments
|
||||
attachment_count = Issue.find(3).attachments.size
|
||||
assert attachment_count > 0
|
||||
@request.session[:user_id] = 2
|
||||
|
||||
assert_difference 'Issue.count', 1 do
|
||||
assert_difference 'Attachment.count', attachment_count do
|
||||
post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
|
||||
:issue => {
|
||||
:project_id => ''
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_bulk_copy_to_another_project_should_follow_when_needed
|
||||
@request.session[:user_id] = 2
|
||||
post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
|
||||
|
||||
@@ -131,6 +131,30 @@ class MessagesControllerTest < ActionController::TestCase
|
||||
assert_equal 'New body', message.content
|
||||
end
|
||||
|
||||
def test_post_edit_sticky_and_locked
|
||||
@request.session[:user_id] = 2
|
||||
post :edit, :board_id => 1, :id => 1,
|
||||
:message => { :subject => 'New subject',
|
||||
:content => 'New body',
|
||||
:locked => '1',
|
||||
:sticky => '1'}
|
||||
assert_redirected_to '/boards/1/topics/1'
|
||||
message = Message.find(1)
|
||||
assert_equal true, message.sticky?
|
||||
assert_equal true, message.locked?
|
||||
end
|
||||
|
||||
def test_post_edit_should_allow_to_change_board
|
||||
@request.session[:user_id] = 2
|
||||
post :edit, :board_id => 1, :id => 1,
|
||||
:message => { :subject => 'New subject',
|
||||
:content => 'New body',
|
||||
:board_id => 2}
|
||||
assert_redirected_to '/boards/2/topics/1'
|
||||
message = Message.find(1)
|
||||
assert_equal Board.find(2), message.board
|
||||
end
|
||||
|
||||
def test_reply
|
||||
@request.session[:user_id] = 2
|
||||
post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' }
|
||||
|
||||
@@ -53,4 +53,14 @@ class LayoutTest < ActionController::IntegrationTest
|
||||
:attributes => {:src => %r{^/javascripts/jstoolbar/textile.js}},
|
||||
:parent => {:tag => 'head'}
|
||||
end
|
||||
|
||||
def test_search_field_outside_project_should_link_to_global_search
|
||||
get '/'
|
||||
assert_select 'div#quick-search form[action=/search]'
|
||||
end
|
||||
|
||||
def test_search_field_inside_project_should_link_to_project_search
|
||||
get '/projects/ecookbook'
|
||||
assert_select 'div#quick-search form[action=/projects/ecookbook/search]'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,5 +23,9 @@ class RoutingSearchTest < ActionController::IntegrationTest
|
||||
{ :method => 'get', :path => "/search" },
|
||||
{ :controller => 'search', :action => 'index' }
|
||||
)
|
||||
assert_routing(
|
||||
{ :method => 'get', :path => "/projects/foo/search" },
|
||||
{ :controller => 'search', :action => 'index', :id => 'foo' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -395,6 +395,14 @@ class IssueTest < ActiveSupport::TestCase
|
||||
assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
|
||||
end
|
||||
|
||||
def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
|
||||
issue = Issue.find(1).copy
|
||||
assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
|
||||
|
||||
issue = Issue.find(2).copy
|
||||
assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
|
||||
end
|
||||
|
||||
def test_copy
|
||||
issue = Issue.new.copy_from(1)
|
||||
assert issue.copy?
|
||||
|
||||
@@ -359,6 +359,21 @@ class MailHandlerTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_ignore_auto_replied_emails
|
||||
[
|
||||
"X-Auto-Response-Suppress: OOF",
|
||||
"Auto-Submitted: auto-replied",
|
||||
"Auto-Submitted: Auto-Replied"
|
||||
].each do |header|
|
||||
raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
|
||||
raw = header + "\n" + raw
|
||||
|
||||
assert_no_difference 'Issue.count' do
|
||||
assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_issue_should_send_email_notification
|
||||
Setting.notified_events = ['issue_added']
|
||||
ActionMailer::Base.deliveries.clear
|
||||
|
||||
@@ -45,6 +45,7 @@ class TimeEntryTest < ActiveSupport::TestCase
|
||||
"3 h 15 m" => 3.25,
|
||||
"3 hours" => 3.0,
|
||||
"12min" => 0.2,
|
||||
"12 Min" => 0.2,
|
||||
}
|
||||
|
||||
assertions.each do |k, v|
|
||||
|
||||
@@ -630,6 +630,38 @@ class UserTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
|
||||
user = User.find_by_login("admin")
|
||||
user.password = "admin"
|
||||
user.save!
|
||||
|
||||
assert_equal false, User.default_admin_account_changed?
|
||||
end
|
||||
|
||||
def test_default_admin_account_changed_should_return_true_if_password_was_changed
|
||||
user = User.find_by_login("admin")
|
||||
user.password = "newpassword"
|
||||
user.save!
|
||||
|
||||
assert_equal true, User.default_admin_account_changed?
|
||||
end
|
||||
|
||||
def test_default_admin_account_changed_should_return_true_if_account_is_disabled
|
||||
user = User.find_by_login("admin")
|
||||
user.password = "admin"
|
||||
user.status = User::STATUS_LOCKED
|
||||
user.save!
|
||||
|
||||
assert_equal true, User.default_admin_account_changed?
|
||||
end
|
||||
|
||||
def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
|
||||
user = User.find_by_login("admin")
|
||||
user.destroy
|
||||
|
||||
assert_equal true, User.default_admin_account_changed?
|
||||
end
|
||||
|
||||
def test_roles_for_project
|
||||
# user with a role
|
||||
roles = @jsmith.roles_for_project(Project.find(1))
|
||||
|
||||
Reference in New Issue
Block a user