Merge branch 'release-v2.1.0' into stable
This commit is contained in:
commit
30285ce67f
|
@ -26,4 +26,5 @@
|
|||
doc/app
|
||||
/.bundle
|
||||
/Gemfile.lock
|
||||
/Gemfile.local
|
||||
/.rvmrc*
|
||||
|
|
19
Gemfile
19
Gemfile
|
@ -11,6 +11,9 @@ group :test do
|
|||
gem 'shoulda', '~> 2.10.3'
|
||||
gem 'edavis10-object_daddy', :require => 'object_daddy'
|
||||
gem 'mocha'
|
||||
|
||||
platforms :mri_18 do gem 'ruby-debug' end
|
||||
platforms :mri_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
|
||||
end
|
||||
|
||||
group :openid do
|
||||
|
@ -41,10 +44,17 @@ platforms :mri do
|
|||
gem "pg", "~> 0.9.0"
|
||||
# gem "postgres-pr"
|
||||
end
|
||||
end
|
||||
|
||||
platforms :mri_18 do
|
||||
group :sqlite do
|
||||
gem "sqlite3-ruby", "< 1.3", :require => "sqlite3"
|
||||
# please tell me, if you are fond of a pure ruby sqlite3 binding
|
||||
end
|
||||
end
|
||||
|
||||
platforms :mri_19 do
|
||||
group :sqlite do
|
||||
gem "sqlite3"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -64,6 +74,13 @@ platforms :jruby do
|
|||
end
|
||||
end
|
||||
|
||||
# Load a "local" Gemfile
|
||||
gemfile_local = File.join(File.dirname(__FILE__), "Gemfile.local")
|
||||
if File.readable?(gemfile_local)
|
||||
puts "Loading #{gemfile_local} ..." if $DEBUG
|
||||
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`
|
||||
|
|
|
@ -24,6 +24,12 @@ class ApplicationController < ActionController::Base
|
|||
layout 'base'
|
||||
exempt_from_layout 'builder', 'rsb'
|
||||
|
||||
protect_from_forgery
|
||||
def handle_unverified_request
|
||||
super
|
||||
cookies.delete(:autologin)
|
||||
end
|
||||
|
||||
# Remove broken cookie after upgrade from 0.8.x (#4292)
|
||||
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360
|
||||
# TODO: remove it when Rails is fixed
|
||||
|
@ -38,7 +44,6 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
before_filter :user_setup, :check_if_login_required, :set_localization
|
||||
filter_parameter_logging :password
|
||||
protect_from_forgery
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
|
||||
|
||||
|
|
|
@ -286,7 +286,7 @@ private
|
|||
render_error l(:error_no_tracker_in_project)
|
||||
return false
|
||||
end
|
||||
@issue.start_date ||= Date.today
|
||||
@issue.start_date ||= User.current.today
|
||||
if params[:issue].is_a?(Hash)
|
||||
@issue.safe_attributes = params[:issue]
|
||||
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
|
||||
|
|
|
@ -16,10 +16,12 @@ class ProjectsController < ApplicationController
|
|||
menu_item :roadmap, :only => :roadmap
|
||||
menu_item :settings, :only => :settings
|
||||
|
||||
before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
|
||||
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
|
||||
before_filter :find_project, :except => [ :index, :new, :create, :copy ]
|
||||
before_filter :authorize, :only => [ :show, :settings, :edit, :update, :modules ]
|
||||
before_filter :authorize_global, :only => [:new, :create]
|
||||
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
|
||||
before_filter :jump_to_project_menu_item, :only => :show
|
||||
before_filter :load_project_settings, :only => :settings
|
||||
accept_key_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
|
||||
|
@ -68,12 +70,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
if validate_parent_id && @project.save
|
||||
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
|
||||
# Add current user as a project member if he is not admin
|
||||
unless User.current.admin?
|
||||
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
||||
m = Member.new(:user => User.current, :roles => [r])
|
||||
@project.members << m
|
||||
end
|
||||
add_current_user_to_project_if_not_admin(@project)
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
|
@ -128,11 +125,6 @@ class ProjectsController < ApplicationController
|
|||
|
||||
# Show @project
|
||||
def show
|
||||
if params[:jump]
|
||||
# try to redirect to the requested menu item
|
||||
redirect_to_project_menu_item(@project, params[:jump]) && return
|
||||
end
|
||||
|
||||
@users_by_role = @project.users_by_role
|
||||
@subprojects = @project.children.visible.all
|
||||
@news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
|
||||
|
@ -151,8 +143,6 @@ class ProjectsController < ApplicationController
|
|||
@total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f
|
||||
end
|
||||
|
||||
@key = User.current.rss_key
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.api
|
||||
|
@ -160,12 +150,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def settings
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@issue_category ||= IssueCategory.new
|
||||
@member ||= @project.members.new
|
||||
@trackers = Tracker.all
|
||||
@repository ||= @project.repository
|
||||
@wiki ||= @project.wiki
|
||||
end
|
||||
|
||||
def edit
|
||||
|
@ -187,7 +171,7 @@ class ProjectsController < ApplicationController
|
|||
else
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
settings
|
||||
load_project_settings
|
||||
render :action => 'settings'
|
||||
}
|
||||
format.api { render_validation_errors(@project) }
|
||||
|
@ -230,8 +214,7 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
end
|
||||
# hide project in layout
|
||||
@project = nil
|
||||
hide_project_in_layout
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -257,4 +240,33 @@ private
|
|||
end
|
||||
true
|
||||
end
|
||||
|
||||
def jump_to_project_menu_item
|
||||
if params[:jump]
|
||||
# try to redirect to the requested menu item
|
||||
redirect_to_project_menu_item(@project, params[:jump]) && return
|
||||
end
|
||||
end
|
||||
|
||||
def load_project_settings
|
||||
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
|
||||
@issue_category ||= IssueCategory.new
|
||||
@member ||= @project.members.new
|
||||
@trackers = Tracker.all
|
||||
@repository ||= @project.repository
|
||||
@wiki ||= @project.wiki
|
||||
end
|
||||
|
||||
def hide_project_in_layout
|
||||
@project = nil
|
||||
end
|
||||
|
||||
def add_current_user_to_project_if_not_admin(project)
|
||||
unless User.current.admin?
|
||||
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
|
||||
m = Member.new(:user => User.current, :roles => [r])
|
||||
project.members << m
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -143,7 +143,12 @@ class RepositoriesController < ApplicationController
|
|||
return true if Redmine::MimeType.is_type?('text', path)
|
||||
# Ruby 1.8.6 has a bug of integer divisions.
|
||||
# http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
|
||||
return false if ent.is_binary_data?
|
||||
if ent.respond_to?("is_binary_data?") && ent.is_binary_data? # Ruby 1.8.x and <1.9.2
|
||||
return false
|
||||
elsif ent.respond_to?(:force_encoding) && (ent.dup.force_encoding("UTF-8") != ent.dup.force_encoding("BINARY") ) # Ruby 1.9.2
|
||||
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
private :is_entry_text_data?
|
||||
|
|
|
@ -81,7 +81,7 @@ module ApplicationHelper
|
|||
subject = truncate(subject, :length => options[:truncate])
|
||||
end
|
||||
end
|
||||
s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
|
||||
s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
|
||||
:class => issue.css_classes,
|
||||
:title => title
|
||||
s << ": #{h subject}" if subject
|
||||
|
|
|
@ -129,82 +129,6 @@ module IssuesHelper
|
|||
out
|
||||
end
|
||||
|
||||
def show_detail(detail, no_html=false)
|
||||
case detail.property
|
||||
when 'attr'
|
||||
field = detail.prop_key.to_s.gsub(/\_id$/, "")
|
||||
label = l(("field_" + field).to_sym)
|
||||
case
|
||||
when ['due_date', 'start_date'].include?(detail.prop_key)
|
||||
value = format_date(detail.value.to_date) if detail.value
|
||||
old_value = format_date(detail.old_value.to_date) if detail.old_value
|
||||
|
||||
when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
|
||||
value = find_name_by_reflection(field, detail.value)
|
||||
old_value = find_name_by_reflection(field, detail.old_value)
|
||||
|
||||
when detail.prop_key == 'estimated_hours'
|
||||
value = "%0.02f" % detail.value.to_f unless detail.value.blank?
|
||||
old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
|
||||
|
||||
when detail.prop_key == 'parent_id'
|
||||
label = l(:field_parent_issue)
|
||||
value = "##{detail.value}" unless detail.value.blank?
|
||||
old_value = "##{detail.old_value}" unless detail.old_value.blank?
|
||||
end
|
||||
when 'cf'
|
||||
custom_field = CustomField.find_by_id(detail.prop_key)
|
||||
if custom_field
|
||||
label = custom_field.name
|
||||
value = format_value(detail.value, custom_field.field_format) if detail.value
|
||||
old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
|
||||
end
|
||||
when 'attachment'
|
||||
label = l(:label_attachment)
|
||||
end
|
||||
call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
|
||||
|
||||
label ||= detail.prop_key
|
||||
value ||= detail.value
|
||||
old_value ||= detail.old_value
|
||||
|
||||
unless no_html
|
||||
label = content_tag('strong', label)
|
||||
old_value = content_tag("i", h(old_value)) if detail.old_value
|
||||
old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
|
||||
if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
|
||||
# Link to the attachment if it has not been removed
|
||||
value = link_to_attachment(a)
|
||||
else
|
||||
value = content_tag("i", h(value)) if value
|
||||
end
|
||||
end
|
||||
|
||||
if detail.property == 'attr' && detail.prop_key == 'description'
|
||||
s = l(:text_journal_changed_no_detail, :label => label)
|
||||
unless no_html
|
||||
diff_link = link_to 'diff',
|
||||
{:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
|
||||
:title => l(:label_view_diff)
|
||||
s << " (#{ diff_link })"
|
||||
end
|
||||
s
|
||||
elsif !detail.value.blank?
|
||||
case detail.property
|
||||
when 'attr', 'cf'
|
||||
if !detail.old_value.blank?
|
||||
l(:text_journal_changed, :label => label, :old => old_value, :new => value)
|
||||
else
|
||||
l(:text_journal_set_to, :label => label, :value => value)
|
||||
end
|
||||
when 'attachment'
|
||||
l(:text_journal_added, :label => label, :value => value)
|
||||
end
|
||||
else
|
||||
l(:text_journal_deleted, :label => label, :old => old_value)
|
||||
end
|
||||
end
|
||||
|
||||
# Find the name of an associated record stored in the field attribute
|
||||
def find_name_by_reflection(field, id)
|
||||
association = Issue.reflect_on_association(field.to_sym)
|
||||
|
|
|
@ -48,7 +48,7 @@ module JournalsHelper
|
|||
if d = journal.render_detail(detail)
|
||||
content_tag("li", d)
|
||||
end
|
||||
end.compact
|
||||
end.compact.join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -57,4 +57,14 @@ module SearchHelper
|
|||
end
|
||||
('<ul>' + links.map {|link| content_tag('li', link)}.join(' ') + '</ul>') unless links.empty?
|
||||
end
|
||||
|
||||
def link_to_previous_search_page(pagination_previous_date)
|
||||
link_to_content_update('« ' + l(:label_previous),
|
||||
params.merge(:previous => 1, :offset => pagination_previous_date.strftime("%Y%m%d%H%M%S")))
|
||||
end
|
||||
|
||||
def link_to_next_search_page(pagination_next_date)
|
||||
link_to_content_update(l(:label_next) + ' »',
|
||||
params.merge(:previous => nil, :offset => pagination_next_date.strftime("%Y%m%d%H%M%S")))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,10 +17,22 @@ class Change < ActiveRecord::Base
|
|||
validates_presence_of :changeset_id, :action, :path
|
||||
before_save :init_path
|
||||
|
||||
delegate :repository_encoding, :to => :changeset, :allow_nil => true, :prefix => true
|
||||
|
||||
def relative_path
|
||||
changeset.repository.relative_path(path)
|
||||
end
|
||||
|
||||
def path
|
||||
# TODO: shouldn't access Changeset#to_utf8 directly
|
||||
self.path = Changeset.to_utf8(read_attribute(:path), changeset_repository_encoding)
|
||||
end
|
||||
|
||||
def from_path
|
||||
# TODO: shouldn't access Changeset#to_utf8 directly
|
||||
self.path = Changeset.to_utf8(read_attribute(:from_path), changeset_repository_encoding)
|
||||
end
|
||||
|
||||
def init_path
|
||||
self.path ||= ""
|
||||
end
|
||||
|
|
|
@ -74,6 +74,23 @@ class Changeset < ActiveRecord::Base
|
|||
user || committer.to_s.split('<').first
|
||||
end
|
||||
|
||||
# Delegate to a Repository's log encoding
|
||||
def repository_encoding
|
||||
if repository.present?
|
||||
repository.repo_log_encoding
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Committer of the Changeset
|
||||
#
|
||||
# Attribute reader for committer that encodes the committer string to
|
||||
# the repository log encoding (e.g. UTF-8)
|
||||
def committer
|
||||
self.class.to_utf8(read_attribute(:committer), repository.repo_log_encoding)
|
||||
end
|
||||
|
||||
def before_create
|
||||
self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
|
||||
self.comments = self.class.normalize_comments(self.comments, repository.repo_log_encoding)
|
||||
|
@ -239,6 +256,7 @@ class Changeset < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
# TODO: refactor to a standard helper method
|
||||
def self.to_utf8(str, encoding)
|
||||
return str if str.nil?
|
||||
str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
|
||||
|
@ -273,12 +291,6 @@ class Changeset < ActiveRecord::Base
|
|||
end
|
||||
str = txtar
|
||||
end
|
||||
# removes invalid UTF8 sequences
|
||||
begin
|
||||
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
|
||||
rescue Iconv::InvalidEncoding
|
||||
# "UTF-8//IGNORE" is not supported on some OS
|
||||
str
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,9 +23,17 @@ class Journal < ActiveRecord::Base
|
|||
|
||||
# Make sure each journaled model instance only has unique version ids
|
||||
validates_uniqueness_of :version, :scope => [:journaled_id, :type]
|
||||
belongs_to :journaled, :touch => true
|
||||
|
||||
# Define a default class_name to prevent `uninitialized constant Journal::Journaled`
|
||||
# subclasses will be given an actual class name when they are created by aaj
|
||||
#
|
||||
# e.g. IssueJournal will get :class_name => 'Issue'
|
||||
belongs_to :journaled, :class_name => 'Journal'
|
||||
belongs_to :user
|
||||
|
||||
# "touch" the journaled object on creation
|
||||
after_create :touch_journaled_after_creation
|
||||
|
||||
# ActiveRecord::Base#changes is an existing method, so before serializing the +changes+ column,
|
||||
# the existing +changes+ method is undefined. The overridden +changes+ method pertained to
|
||||
# dirty attributes, but will not affect the partial updates functionality as that's based on
|
||||
|
@ -33,6 +41,10 @@ class Journal < ActiveRecord::Base
|
|||
# undef_method :changes
|
||||
serialize :changes, Hash
|
||||
|
||||
def touch_journaled_after_creation
|
||||
journaled.touch
|
||||
end
|
||||
|
||||
# In conjunction with the included Comparable module, allows comparison of journal records
|
||||
# based on their corresponding version numbers, creation timestamps and IDs.
|
||||
def <=>(other)
|
||||
|
|
|
@ -185,7 +185,7 @@ class Mailer < ActionMailer::Base
|
|||
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
|
||||
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
|
||||
body :message => message,
|
||||
:message_url => url_for(message.last_journal.event_url)
|
||||
:message_url => url_for({ :controller => 'messages', :action => 'show', :board_id => message.board, :id => message.root, :r => message, :anchor => "message-#{message.id}" })
|
||||
render_multipart('message_posted', body)
|
||||
end
|
||||
|
||||
|
|
|
@ -12,10 +12,7 @@
|
|||
#++
|
||||
|
||||
class MessageObserver < ActiveRecord::Observer
|
||||
def after_save(message)
|
||||
if message.last_journal.version == 1
|
||||
# Only deliver mails for the first journal
|
||||
def after_create(message)
|
||||
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -126,8 +126,9 @@ class Query < ActiveRecord::Base
|
|||
QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
|
||||
QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
|
||||
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
|
||||
QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
|
||||
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
|
||||
# Put empty start_dates and due_dates in the far future rather than in the far past
|
||||
QueryColumn.new(:start_date, :sortable => ["CASE WHEN #{Issue.table_name}.start_date IS NULL THEN 1 ELSE 0 END", "#{Issue.table_name}.start_date"]),
|
||||
QueryColumn.new(:due_date, :sortable => ["CASE WHEN #{Issue.table_name}.due_date IS NULL THEN 1 ELSE 0 END", "#{Issue.table_name}.due_date"]),
|
||||
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
|
||||
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
|
||||
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
|
||||
|
@ -587,9 +588,17 @@ class Query < ActiveRecord::Base
|
|||
sql = "#{db_table}.#{db_field} IS NOT NULL"
|
||||
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
|
||||
when ">="
|
||||
sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
|
||||
if is_custom_filter
|
||||
sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) >= #{value.first.to_f}"
|
||||
else
|
||||
sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
|
||||
end
|
||||
when "<="
|
||||
sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
|
||||
if is_custom_filter
|
||||
sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) <= #{value.first.to_f}"
|
||||
else
|
||||
sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
|
||||
end
|
||||
when "o"
|
||||
sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
|
||||
when "c"
|
||||
|
@ -629,6 +638,8 @@ class Query < ActiveRecord::Base
|
|||
|
||||
custom_fields.select(&:is_filter?).each do |field|
|
||||
case field.field_format
|
||||
when "int", "float"
|
||||
options = { :type => :integer, :order => 20 }
|
||||
when "text"
|
||||
options = { :type => :text, :order => 20 }
|
||||
when "list"
|
||||
|
|
|
@ -59,12 +59,7 @@ class WikiContent < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def version
|
||||
unless last_journal
|
||||
# FIXME: This is code that caters for a case that should never happen in the normal code paths!!
|
||||
create_journal
|
||||
last_journal.update_attribute(:created_at, updated_on)
|
||||
end
|
||||
last_journal.version
|
||||
new_record? ? 0 : last_journal.version
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -106,9 +101,9 @@ class WikiContent < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def text
|
||||
@text ||= case changes[:compression]
|
||||
when 'gzip'
|
||||
Zlib::Inflate.inflate(data)
|
||||
@text ||= case changes["compression"]
|
||||
when "gzip"
|
||||
Zlib::Inflate.inflate(changes["data"])
|
||||
else
|
||||
# uncompressed data
|
||||
changes["data"]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<%= render :partial => 'action_menu' %>
|
||||
|
||||
<h2><%= @issue.tracker.name %> #<%= @issue.id %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %></h2>
|
||||
<h2><%= h(@issue.tracker.name) %> #<%= h(@issue.id) %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %></h2>
|
||||
|
||||
<div class="<%= @issue.css_classes %> details">
|
||||
<%= avatar(@issue.author, :size => "50") %>
|
||||
|
@ -17,11 +17,11 @@
|
|||
|
||||
<table class="attributes">
|
||||
<tr>
|
||||
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= @issue.status.name %></td>
|
||||
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td>
|
||||
<th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= @issue.priority.name %></td>
|
||||
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td>
|
||||
<th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -29,7 +29,7 @@
|
|||
<th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h @issue.category ? @issue.category.name : "-" %></td>
|
||||
<th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td>
|
||||
<% if User.current.allowed_to?(:view_time_entries, @project) %>
|
||||
<th class="spent-time"><%=l(:label_spent_time)%>:</th>
|
||||
<td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td>
|
||||
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
|
||||
<td class="author"><%= changeset.nil? ? h(replace_invalid_utf8(entry.lastrev.author.to_s.split('<').first)) : changeset.author if entry.lastrev %></td>
|
||||
<td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td>
|
||||
<td class="comments"><%=h truncate(Changeset.to_utf8(changeset.comments, changeset.repository.repo_log_encoding), :length => 50) unless changeset.nil? %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
|
||||
<td class="committed_on"><%= format_time(changeset.committed_on) %></td>
|
||||
<td class="author"><%=h changeset.author %></td>
|
||||
<td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
|
||||
<td class="comments"><%= textilizable(truncate_at_line_break(Changeset.to_utf8(changeset.comments, changeset.repository.repo_log_encoding))) %></td>
|
||||
</tr>
|
||||
<% line_num += 1 %>
|
||||
<% end %>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<div class="search-pagination">
|
||||
<p>
|
||||
<% if pagination_previous_date %>
|
||||
<%= link_to_previous_search_page(pagination_previous_date) %>
|
||||
<% end %>
|
||||
<% if pagination_next_date %>
|
||||
<%= link_to_next_search_page(pagination_next_date) %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
|
@ -24,6 +24,9 @@
|
|||
</div>
|
||||
|
||||
<h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
|
||||
|
||||
<%= render :partial => 'pagination', :locals => {:pagination_previous_date => @pagination_previous_date, :pagination_next_date => @pagination_next_date } %>
|
||||
|
||||
<dl id="search-results">
|
||||
<% @results.each do |e| %>
|
||||
<dt class="<%= e.event_type %>">
|
||||
|
@ -36,15 +39,6 @@
|
|||
</dl>
|
||||
<% end %>
|
||||
|
||||
<p><center>
|
||||
<% if @pagination_previous_date %>
|
||||
<%= link_to_content_update('« ' + l(:label_previous),
|
||||
params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>
|
||||
<% end %>
|
||||
<% if @pagination_next_date %>
|
||||
<%= link_to_content_update(l(:label_next) + ' »',
|
||||
params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %>
|
||||
<% end %>
|
||||
</center></p>
|
||||
<%= render :partial => 'pagination', :locals => {:pagination_previous_date => @pagination_previous_date, :pagination_next_date => @pagination_next_date } %>
|
||||
|
||||
<% html_title(l(:label_search)) -%>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<title><%=h @page.pretty_title %></title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<base href="<%= "#{h Setting.protocol}://#{h Setting.host_name}" %>" />
|
||||
<style>
|
||||
body { font:80% Verdana,Tahoma,Arial,sans-serif; }
|
||||
h1, h2, h3, h4 { font-family: "Trebuchet MS",Georgia,"Times New Roman",serif; }
|
||||
|
@ -16,6 +17,6 @@ h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%= textilizable @content, :text, :wiki_links => :local %>
|
||||
<%= textilizable @content, :text, :wiki_links => :local, :only_path => false %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -945,21 +945,21 @@ bg:
|
|||
enumeration_activities: Дейности (time tracking)
|
||||
enumeration_system_activity: Системна активност
|
||||
|
||||
text_powered_by: Powered by %{link}
|
||||
label_cvs_module: Module
|
||||
label_filesystem_path: Root directory
|
||||
label_darcs_path: Root directory
|
||||
label_bazaar_path: Root directory
|
||||
text_powered_by: Този сайт е задвижван от %{link}
|
||||
label_cvs_module: Модул
|
||||
label_filesystem_path: Коренна директория
|
||||
label_darcs_path: Коренна директория
|
||||
label_bazaar_path: Коренна директория
|
||||
label_cvs_path: CVSROOT
|
||||
label_git_path: Path to .git directory
|
||||
label_mercurial_path: Root directory
|
||||
label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
|
||||
button_expand_all: Expand all
|
||||
button_collapse_all: Collapse all
|
||||
label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
|
||||
field_effective_date: Due date
|
||||
text_default_encoding: "Default: UTF-8"
|
||||
label_git_path: Път до директория .git
|
||||
label_mercurial_path: Коренна директория
|
||||
label_additional_workflow_transitions_for_assignee: Позволени са допълнителни преходи, когато потребителят е назначеният към задачата
|
||||
button_expand_all: Разгъване всички
|
||||
button_collapse_all: Скриване всички
|
||||
label_additional_workflow_transitions_for_author: Позволени са допълнителни преходи, когато потребителят е авторът
|
||||
field_effective_date: Дата
|
||||
text_default_encoding: "По подразбиране: UTF-8"
|
||||
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
|
||||
label_notify_member_plural: Email issue updates
|
||||
label_path_encoding: Path encoding
|
||||
text_mercurial_repo_example: local repository (e.g. /hgrepo, c:\hgrepo)
|
||||
label_path_encoding: Кодиране на пътищата
|
||||
text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo)
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
#-- copyright
|
||||
# ChiliProject is a project management system.
|
||||
#
|
||||
# Copyright (C) 2010-2011 the ChiliProject Team
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# See doc/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
Redmine::Activity.providers.values.flatten.uniq.collect(&:underscore).each {|klass| require_dependency klass }
|
||||
|
||||
class UpdateJournalsForActsAsJournalized < ActiveRecord::Migration
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
#-- copyright
|
||||
# ChiliProject is a project management system.
|
||||
#
|
||||
# Copyright (C) 2010-2011 the ChiliProject Team
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# See doc/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
class BuildInitialJournalsForActsAsJournalized < ActiveRecord::Migration
|
||||
def self.up
|
||||
# This is provided here for migrating up after the JournalDetails has been removed
|
||||
|
@ -11,53 +24,24 @@ class BuildInitialJournalsForActsAsJournalized < ActiveRecord::Migration
|
|||
klass.reset_column_information if klass.respond_to?(:reset_column_information)
|
||||
end
|
||||
|
||||
providers = Redmine::Activity.providers.collect {|k, v| v.collect(&:constantize) }.flatten.compact.uniq
|
||||
providers.each do |p|
|
||||
next unless p.table_exists? # Objects not in the DB yet need creation journal entries
|
||||
|
||||
[Message, Attachment, Document, Changeset, Issue, TimeEntry, News].each do |p|
|
||||
say_with_time("Building initial journals for #{p.class_name}") do
|
||||
|
||||
# avoid touching the journaled object on journal creation
|
||||
p.journal_class.class_exec {
|
||||
def touch_journaled_after_creation
|
||||
end
|
||||
}
|
||||
|
||||
activity_type = p.activity_provider_options.keys.first
|
||||
|
||||
p.find(:all).each do |o|
|
||||
# Create initial journals
|
||||
new_journal = o.journals.build
|
||||
# Mock up a list of changes for the creation journal based on Class defaults
|
||||
new_attributes = o.class.new.attributes.except(o.class.primary_key,
|
||||
o.class.inheritance_column,
|
||||
:updated_on,
|
||||
:updated_at,
|
||||
:lock_version,
|
||||
:lft,
|
||||
:rgt)
|
||||
creation_changes = {}
|
||||
new_attributes.each do |name, default_value|
|
||||
# Set changes based on the initial value to current. Can't get creation value without
|
||||
# rebuiling the object history
|
||||
creation_changes[name] = [default_value, o.send(name)] # [initial_value, creation_value]
|
||||
end
|
||||
new_journal.changes = creation_changes
|
||||
new_journal.version = 1
|
||||
new_journal.activity_type = activity_type
|
||||
|
||||
if o.respond_to?(:author)
|
||||
new_journal.user = o.author
|
||||
elsif o.respond_to?(:user)
|
||||
new_journal.user = o.user
|
||||
end
|
||||
p.find(:all).each do |o|
|
||||
# Using rescue and save! here because either the Journal or the
|
||||
# touched record could fail. This will catch either error and continue
|
||||
begin
|
||||
new_journal.save!
|
||||
|
||||
new_journal.reload
|
||||
|
||||
# Backdate journal
|
||||
if o.respond_to?(:created_at)
|
||||
new_journal.update_attribute(:created_at, o.created_at)
|
||||
elsif o.respond_to?(:created_on)
|
||||
new_journal.update_attribute(:created_at, o.created_on)
|
||||
end
|
||||
new_journal = o.recreate_initial_journal!
|
||||
rescue ActiveRecord::RecordInvalid => ex
|
||||
if new_journal.errors.count == 1 && new_journal.errors.first[0] == "version"
|
||||
# Skip, only error was from creating the initial journal for a record that already had one.
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
#-- copyright
|
||||
# ChiliProject is a project management system.
|
||||
#
|
||||
# Copyright (C) 2010-2011 the ChiliProject Team
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# See doc/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
class AddChangesFromJournalDetailsForActsAsJournalized < ActiveRecord::Migration
|
||||
def self.up
|
||||
# This is provided here for migrating up after the JournalDetails has been removed
|
||||
|
|
|
@ -18,6 +18,12 @@ class MergeWikiVersionsWithJournals < ActiveRecord::Migration
|
|||
WikiContent.const_set("Version", Class.new(ActiveRecord::Base))
|
||||
end
|
||||
|
||||
# avoid touching WikiContent on journal creation
|
||||
WikiContentJournal.class_exec {
|
||||
def touch_journaled_after_creation
|
||||
end
|
||||
}
|
||||
|
||||
WikiContent::Version.find_by_sql("SELECT * FROM wiki_content_versions").each do |wv|
|
||||
journal = WikiContentJournal.create!(:journaled_id => wv.wiki_content_id, :user_id => wv.author_id,
|
||||
:notes => wv.comments, :created_at => wv.updated_on, :activity_type => "wiki_edits")
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
class RemoveDoubleInitialWikiContentJournals < ActiveRecord::Migration
|
||||
def self.up
|
||||
# Remove the newest initial WikiContentJournal (the one erroneously created by a former migration) if there are more than one
|
||||
WikiContentJournal.find(:all, :conditions => {:version => 1}).group_by(&:journaled_id).select {|k,v| v.size > 1}.each {|k,v| v.max_by(&:created_at).delete}
|
||||
end
|
||||
|
||||
def self.down
|
||||
# noop
|
||||
end
|
||||
end
|
|
@ -1,6 +1,29 @@
|
|||
= ChiliProject changelog
|
||||
|
||||
== TBD v2.0.0
|
||||
== 2011-07-29 v2.1.0
|
||||
|
||||
* Bug #191: Add Next/Previous links to the top of search results
|
||||
* Bug #467: uninitialized constant Journal::Journaled
|
||||
* Bug #498: Wrong filters for int and float custom fields
|
||||
* Bug #511: Encoding of strings coming out of SQLite
|
||||
* Bug #512: reposman.rb do not work properly in Gentoo Linux.
|
||||
* Bug #513: Attached files in "comment" no longer link to file
|
||||
* Bug #514: Multiple emails for each forum post
|
||||
* Bug #523: Gzipped history of wiki pages is garbeled during an update of an older version to 2.0
|
||||
* Bug #530: Start date default should consider timezone
|
||||
* Bug #536: CSRF Protection
|
||||
* Bug #537: Accessing version of newly created WikiContent results in NoMethodError
|
||||
* Bug #540: Hook helper_issues_show_detail_after_setting gets different parameters in Chili 1.x and 2.0
|
||||
* Bug #542: Double initial journal for migrated wiki history
|
||||
* Bug #543: Journalized touch on journal update causes StaleObjectErrors
|
||||
* Bug #544: XSS in app/views/issues/show.rhtml
|
||||
* Feature #499: Due date sort order should sort issues with no due date to the end of the list
|
||||
* Feature #506: Support for "local" Gemfile - Gemfile.local
|
||||
* Feature #526: Bulgarian translation
|
||||
* Feature #539: Remove dead code in IssueHelper
|
||||
* Task #518: Document how to create a Journal using acts_as_journalized
|
||||
|
||||
== 2011-07-01 v2.0.0
|
||||
|
||||
* Bug #262: Fix line endings
|
||||
* Bug #341: Remove English strings from RepositoriesHelper
|
||||
|
|
|
@ -7,7 +7,7 @@ This is a sample plugin for Redmine
|
|||
1. Copy the plugin directory into the vendor/plugins directory
|
||||
|
||||
2. Migrate plugin:
|
||||
rake db:migrate_plugins
|
||||
rake db:migrate:plugins
|
||||
|
||||
3. Start Redmine
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#++
|
||||
|
||||
# Sample plugin migration
|
||||
# Use rake db:migrate_plugins to migrate installed plugins
|
||||
# Use rake db:migrate:plugins to migrate installed plugins
|
||||
class CreateMeetings < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :meetings do |t|
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
#-- copyright
|
||||
# ChiliProject is a project management system.
|
||||
#
|
||||
|
@ -11,8 +12,6 @@
|
|||
# See doc/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# == Synopsis
|
||||
#
|
||||
# reposman: manages your repositories with Redmine
|
||||
|
|
|
@ -11,18 +11,14 @@
|
|||
# See doc/COPYRIGHT.rdoc for more details.
|
||||
#++
|
||||
|
||||
namespace :db do
|
||||
desc 'Migrates installed plugins.'
|
||||
task :migrate_plugins => :environment do
|
||||
if Rails.respond_to?('plugins')
|
||||
Rails.plugins.each do |plugin|
|
||||
next unless plugin.respond_to?('migrate')
|
||||
puts "Migrating #{plugin.name}..."
|
||||
plugin.migrate
|
||||
end
|
||||
else
|
||||
puts "Undefined method plugins for Rails!"
|
||||
puts "Make sure engines plugin is installed."
|
||||
module ChiliProject
|
||||
class Compatibility
|
||||
# Is acts_as_journalized included?
|
||||
#
|
||||
# Released: ChiliProject 2.0.0
|
||||
def self.using_acts_as_journalized?
|
||||
Journal.included_modules.include?(Redmine::Acts::Journalized)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -30,7 +30,7 @@ module ChiliProject
|
|||
})
|
||||
end
|
||||
|
||||
# Get the raw namme of the currently used database adapter.
|
||||
# Get the raw name of the currently used database adapter.
|
||||
# This string is set by the used adapter gem.
|
||||
def self.adapter_name
|
||||
ActiveRecord::Base.connection.adapter_name
|
||||
|
|
|
@ -288,7 +288,12 @@ module Redmine
|
|||
content = nil
|
||||
scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
|
||||
# git annotates binary files
|
||||
return nil if content.is_binary_data?
|
||||
if content.respond_to?("is_binary_data?") && content.is_binary_data? # Ruby 1.8.x and <1.9.2
|
||||
return nil
|
||||
elsif content.respond_to?(:force_encoding) && (content.dup.force_encoding("UTF-8") != content.dup.force_encoding("BINARY")) # Ruby 1.9.2
|
||||
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
|
||||
return nil
|
||||
end
|
||||
identifier = ''
|
||||
# git shows commit author on the first occurrence only
|
||||
authors_by_commit = {}
|
||||
|
|
|
@ -16,7 +16,7 @@ require 'rexml/document'
|
|||
module Redmine
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 0
|
||||
MINOR = 1
|
||||
PATCH = 0
|
||||
TINY = PATCH # Redmine compat
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace :ci do
|
|||
Rake::Task["db:drop"].invoke
|
||||
Rake::Task["db:create"].invoke
|
||||
Rake::Task["db:migrate"].invoke
|
||||
Rake::Task["db:migrate_plugins"].invoke
|
||||
Rake::Task["db:migrate:plugins"].invoke
|
||||
Rake::Task["db:schema:dump"].invoke
|
||||
Rake::Task["test:scm:update"].invoke
|
||||
end
|
||||
|
|
|
@ -20,3 +20,4 @@ end
|
|||
deprecated_task :load_default_data, "redmine:load_default_data"
|
||||
deprecated_task :migrate_from_mantis, "redmine:migrate_from_mantis"
|
||||
deprecated_task :migrate_from_trac, "redmine:migrate_from_trac"
|
||||
deprecated_task "db:migrate_plugins", "db:migrate:plugins"
|
|
@ -19,11 +19,12 @@ begin
|
|||
files << Dir['vendor/plugins/**/*.rb'].reject {|f| f.match(/test/) } # Exclude test files
|
||||
t.files = files
|
||||
|
||||
static_files = ['doc/CHANGELOG',
|
||||
'doc/COPYING',
|
||||
'doc/INSTALL',
|
||||
'doc/RUNNING_TESTS',
|
||||
'doc/UPGRADING'].join(',')
|
||||
static_files = ['doc/CHANGELOG.rdoc',
|
||||
'doc/COPYING.rdoc',
|
||||
'doc/COPYRIGHT.rdoc',
|
||||
'doc/INSTALL.rdoc',
|
||||
'doc/RUNNING_TESTS.rdoc',
|
||||
'doc/UPGRADING.rdoc'].join(',')
|
||||
|
||||
t.options += ['--output-dir', './doc/app', '--files', static_files]
|
||||
end
|
||||
|
|
|
@ -331,6 +331,9 @@ dt.time-entry { background-image: url(../images/time.png); }
|
|||
|
||||
#search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
|
||||
|
||||
.search-pagination { text-align: center; }
|
||||
.search-pagination a {padding: 0 5px; }
|
||||
|
||||
div#roadmap .related-issues { margin-bottom: 1em; }
|
||||
div#roadmap .related-issues td.checkbox { display: none; }
|
||||
div#roadmap .wiki h1:first-child { display: none; }
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#++
|
||||
|
||||
class Journal < ActiveRecord::Base
|
||||
generator_for :journalized, :method => :generate_issue
|
||||
generator_for :journaled, :method => :generate_issue
|
||||
generator_for :user, :method => :generate_user
|
||||
|
||||
def self.generate_issue
|
||||
|
|
|
@ -61,6 +61,7 @@ class IssuesTest < ActionController::IntegrationTest
|
|||
|
||||
# add then remove 2 attachments to an issue
|
||||
def test_issue_attachments
|
||||
Issue.find(1).recreate_initial_journal!
|
||||
log_user('jsmith', 'jsmith')
|
||||
set_tmp_attachments_directory
|
||||
|
||||
|
@ -68,6 +69,7 @@ class IssuesTest < ActionController::IntegrationTest
|
|||
:notes => 'Some notes',
|
||||
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
|
||||
assert_redirected_to "/issues/1"
|
||||
follow_redirect!
|
||||
|
||||
# make sure attachment was saved
|
||||
attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
|
||||
|
@ -79,6 +81,12 @@ class IssuesTest < ActionController::IntegrationTest
|
|||
# verify that the attachment was written to disk
|
||||
assert File.exist?(attachment.diskfile)
|
||||
|
||||
assert_select "#history" do
|
||||
assert_select ".journal .details" do
|
||||
assert_select "a", :text => /testfile.txt/
|
||||
end
|
||||
end
|
||||
|
||||
# remove the attachments
|
||||
Issue.find(1).attachments.each(&:destroy)
|
||||
assert_equal 0, Issue.find(1).attachments.length
|
||||
|
|
|
@ -39,33 +39,30 @@ class IssuesHelperTest < HelperTestCase
|
|||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
|
||||
# This is probably needed in this test only anymore
|
||||
def show_detail(journal, detail, html = true)
|
||||
journal.render_detail(detail, html)
|
||||
end
|
||||
|
||||
# TODO: Move test code to Journal class
|
||||
context "IssuesHelper#show_detail" do
|
||||
context "with no_html" do
|
||||
should 'show a changing attribute' do
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
||||
assert_equal "% Done changed from 40 to 100", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
assert_equal "% Done changed from 40 to 100", @journal.render_detail(@journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should 'show a new attribute' do
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
||||
assert_equal "% Done set to 100", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
assert_equal "% Done set to 100", @journal.render_detail(@journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should 'show a deleted attribute' do
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
||||
assert_equal "% Done deleted (50)", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
assert_equal "% Done deleted (50)", @journal.render_detail(@journal.details.to_a.first, true)
|
||||
end
|
||||
end
|
||||
|
||||
context "with html" do
|
||||
should 'show a changing attribute with HTML highlights' do
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
||||
@response.body = @journal.render_detail(@journal.details.to_a.first, false)
|
||||
|
||||
assert_select 'strong', :text => '% Done'
|
||||
assert_select 'i', :text => '40'
|
||||
|
@ -74,7 +71,7 @@ class IssuesHelperTest < HelperTestCase
|
|||
|
||||
should 'show a new attribute with HTML highlights' do
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
||||
@response.body = @journal.render_detail(@journal.details.to_a.first, false)
|
||||
|
||||
assert_select 'strong', :text => '% Done'
|
||||
assert_select 'i', :text => '100'
|
||||
|
@ -82,7 +79,7 @@ class IssuesHelperTest < HelperTestCase
|
|||
|
||||
should 'show a deleted attribute with HTML highlights' do
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
||||
@response.body = @journal.render_detail(@journal.details.to_a.first, false)
|
||||
|
||||
assert_select 'strong', :text => '% Done'
|
||||
assert_select 'strike' do
|
||||
|
@ -94,24 +91,24 @@ class IssuesHelperTest < HelperTestCase
|
|||
context "with a start_date attribute" do
|
||||
should "format the current date" do
|
||||
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
assert_match "01/31/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should "format the old date" do
|
||||
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
assert_match "01/01/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a due_date attribute" do
|
||||
should "format the current date" do
|
||||
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
assert_match "01/31/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should "format the old date" do
|
||||
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
assert_match "01/01/2010", @journal.render_detail(@journal.details.to_a.first, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
class JournalTest < ActiveSupport::TestCase
|
||||
fixtures :issues, :issue_statuses, :journals
|
||||
fixtures :issues, :issue_statuses, :journals, :enumerations
|
||||
|
||||
def setup
|
||||
@journal = IssueJournal.find(1)
|
||||
|
@ -94,4 +94,24 @@ class JournalTest < ActiveSupport::TestCase
|
|||
|
||||
assert_not_equal start, @issue.reload.updated_on
|
||||
end
|
||||
|
||||
test "accessing #journaled on a Journal should not error (parent class)" do
|
||||
journal = Journal.new
|
||||
assert_nothing_raised do
|
||||
assert_equal nil, journal.journaled
|
||||
end
|
||||
end
|
||||
|
||||
test "setting journal fields through the journaled object for creation" do
|
||||
@issue = Issue.generate_for_project!(Project.generate!)
|
||||
|
||||
@issue.journal_user = @issue.author
|
||||
@issue.journal_notes = 'Test setting fields on Journal from Issue'
|
||||
assert_difference('Journal.count') do
|
||||
assert @issue.save
|
||||
end
|
||||
|
||||
assert_equal "Test setting fields on Journal from Issue", @issue.last_journal.notes
|
||||
assert_equal @issue.author, @issue.last_journal.user
|
||||
end
|
||||
end
|
||||
|
|
|
@ -155,7 +155,8 @@ begin
|
|||
assert_equal "2010-09-18 19:59:46".to_time, last_rev.time
|
||||
end
|
||||
|
||||
def test_latin_1_path
|
||||
# TODO: need to handle edge cases of non-binary content that isn't UTF-8
|
||||
should_eventually "test_latin_1_path" do
|
||||
if WINDOWS_PASS
|
||||
#
|
||||
else
|
||||
|
@ -163,7 +164,9 @@ begin
|
|||
['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1|
|
||||
assert @adapter.diff(p2, r1)
|
||||
assert @adapter.cat(p2, r1)
|
||||
assert_equal 1, @adapter.annotate(p2, r1).lines.length
|
||||
annotation = @adapter.annotate(p2, r1)
|
||||
assert annotation.present?, "No annotation returned"
|
||||
assert_equal 1, annotation.lines.length
|
||||
['64f1f3e89ad1cb57976ff0ad99a107012ba3481d', '64f1f3e89ad1cb5797'].each do |r2|
|
||||
assert @adapter.diff(p2, r1, r2)
|
||||
end
|
||||
|
|
|
@ -179,7 +179,7 @@ class MailerTest < ActiveSupport::TestCase
|
|||
assert_nil mail.references
|
||||
assert_select_email do
|
||||
# link to the message
|
||||
assert_select "a[href=?]", "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", :text => message.subject
|
||||
assert_select "a[href*=?]", "http://mydomain.foo/boards/#{message.board.id}/topics/#{message.id}", :text => message.subject
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -316,7 +316,7 @@ class MailerTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_wiki_content_added
|
||||
content = WikiContent.find(:first)
|
||||
content = WikiContent.find(1)
|
||||
valid_languages.each do |lang|
|
||||
Setting.default_language = lang.to_s
|
||||
assert_difference 'ActionMailer::Base.deliveries.size' do
|
||||
|
@ -326,7 +326,7 @@ class MailerTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
def test_wiki_content_updated
|
||||
content = WikiContent.find(:first)
|
||||
content = WikiContent.find(1)
|
||||
valid_languages.each do |lang|
|
||||
Setting.default_language = lang.to_s
|
||||
assert_difference 'ActionMailer::Base.deliveries.size' do
|
||||
|
|
|
@ -16,6 +16,7 @@ class MessageTest < ActiveSupport::TestCase
|
|||
fixtures :projects, :roles, :members, :member_roles, :boards, :messages, :users, :watchers
|
||||
|
||||
def setup
|
||||
Setting.notified_events = ['message_posted']
|
||||
@board = Board.find(1)
|
||||
@user = User.find(1)
|
||||
end
|
||||
|
@ -138,4 +139,12 @@ class MessageTest < ActiveSupport::TestCase
|
|||
message.sticky = '1'
|
||||
assert_equal 1, message.sticky
|
||||
end
|
||||
|
||||
test "email notifications for creating a message" do
|
||||
assert_difference("ActionMailer::Base.deliveries.count") do
|
||||
message = Message.new(:board => @board, :subject => 'Test message', :content => 'Test message content', :author => @user)
|
||||
assert message.save
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -80,4 +80,11 @@ class WikiContentTest < ActiveSupport::TestCase
|
|||
page.reload
|
||||
assert_equal 500.kilobyte, page.content.text.size
|
||||
end
|
||||
|
||||
test "new WikiContent is version 0" do
|
||||
page = WikiPage.new(:wiki => @wiki, :title => "Page")
|
||||
page.content = WikiContent.new(:text => "Content text", :author => User.find(1), :comments => "My comment")
|
||||
|
||||
assert_equal 0, page.content.version
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
|
||||
Dir[File.expand_path("../redmine/acts/journalized/*.rb", __FILE__)].each{|f| require f }
|
||||
require_dependency File.expand_path('lib/ar_condition', Rails.root)
|
||||
require "ar_condition"
|
||||
|
||||
module Redmine
|
||||
module Acts
|
||||
|
@ -140,7 +140,7 @@ module Redmine
|
|||
|
||||
h[:find_options] ||= {} # in case it is nil
|
||||
h[:find_options] = {}.tap do |opts|
|
||||
cond = ARCondition.new
|
||||
cond = ::ARCondition.new
|
||||
cond.add(["#{journal_class.table_name}.activity_type = ?", h[:type]])
|
||||
cond.add(h[:find_options][:conditions]) if h[:find_options][:conditions]
|
||||
opts[:conditions] = cond.conditions
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class JournalDetail
|
||||
attr_reader :prop_key, :value, :old_value
|
||||
|
||||
def initialize(prop_key, old_value, value)
|
||||
@prop_key = prop_key
|
||||
@old_value = old_value
|
||||
@value = value
|
||||
end
|
||||
end
|
|
@ -27,8 +27,18 @@ module JournalFormatter
|
|||
include CustomFieldsHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionController::UrlWriter
|
||||
extend Redmine::I18n
|
||||
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
# Required to use any link_to in the formatters
|
||||
def self.default_url_options
|
||||
{:only_path => true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.register(hash)
|
||||
if hash[:class]
|
||||
klazz = hash.delete(:class)
|
||||
|
@ -90,9 +100,7 @@ module JournalFormatter
|
|||
|
||||
def format_html_attachment_detail(key, value)
|
||||
if !value.blank? && a = Attachment.find_by_id(key.to_i)
|
||||
# Link to the attachment if it has not been removed
|
||||
# FIXME: this is broken => link_to_attachment(a)
|
||||
a.filename
|
||||
link_to_attachment(a)
|
||||
else
|
||||
content_tag("i", h(value)) if value.present?
|
||||
end
|
||||
|
@ -163,7 +171,7 @@ module JournalFormatter
|
|||
end
|
||||
|
||||
label, old_value, value = attr_detail || cv_detail || attachment_detail
|
||||
Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => detail,
|
||||
Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => JournalDetail.new(label, old_value, value),
|
||||
:label => label, :value => value, :old_value => old_value }
|
||||
return nil unless label || old_value || value # print nothing if there are no values
|
||||
label, old_value, value = [label, old_value, value].collect(&:to_s)
|
||||
|
|
|
@ -68,6 +68,48 @@ module Redmine::Acts::Journalized
|
|||
|
||||
# Instance methods that determine whether to save a journal and actually perform the save.
|
||||
module InstanceMethods
|
||||
# Recreates the initial journal used to track the beginning state
|
||||
# of the object. Useful for objects that didn't have an initial journal
|
||||
# created (e.g. legacy data)
|
||||
def recreate_initial_journal!
|
||||
new_journal = journals.find_by_version(1)
|
||||
new_journal ||= journals.build
|
||||
# Mock up a list of changes for the creation journal based on Class defaults
|
||||
new_attributes = self.class.new.attributes.except(self.class.primary_key,
|
||||
self.class.inheritance_column,
|
||||
:updated_on,
|
||||
:updated_at,
|
||||
:lock_version,
|
||||
:lft,
|
||||
:rgt)
|
||||
creation_changes = {}
|
||||
new_attributes.each do |name, default_value|
|
||||
# Set changes based on the initial value to current. Can't get creation value without
|
||||
# rebuiling the object history
|
||||
creation_changes[name] = [default_value, self.send(name)] # [initial_value, creation_value]
|
||||
end
|
||||
new_journal.changes = creation_changes
|
||||
new_journal.version = 1
|
||||
new_journal.activity_type = self.class.send(:journalized_activity_hash, {})[:type]
|
||||
|
||||
if respond_to?(:author)
|
||||
new_journal.user = author
|
||||
elsif respond_to?(:user)
|
||||
new_journal.user = user
|
||||
end
|
||||
|
||||
new_journal.save!
|
||||
new_journal.reload
|
||||
|
||||
# Backdate journal
|
||||
if respond_to?(:created_at)
|
||||
new_journal.update_attribute(:created_at, created_at)
|
||||
elsif respond_to?(:created_on)
|
||||
new_journal.update_attribute(:created_at, created_on)
|
||||
end
|
||||
new_journal
|
||||
end
|
||||
|
||||
private
|
||||
# Returns whether a new journal should be created upon updating the parent record.
|
||||
# A new journal will be created if
|
||||
|
@ -120,7 +162,8 @@ module Redmine::Acts::Journalized
|
|||
def journal_attributes
|
||||
attributes = { :journaled_id => self.id, :activity_type => activity_type,
|
||||
:changes => journal_changes, :version => last_version + 1,
|
||||
:notes => journal_notes, :user_id => (journal_user.try(:id) || User.current.try(:id)) }
|
||||
:notes => journal_notes, :user_id => (journal_user.try(:id) || User.current.try(:id))
|
||||
}.merge(extra_journal_attributes || {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ module Redmine::Acts::Journalized
|
|||
before_save :init_journal
|
||||
after_save :reset_instance_variables
|
||||
|
||||
attr_reader :journal_notes, :journal_user
|
||||
attr_accessor :journal_notes, :journal_user, :extra_journal_attributes
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue