Commit 70e10ab4 authored by Felix Schäfer's avatar Felix Schäfer

Merge the master-journalized branch from https://github.com/finnlabs/redmine/

parents 78cee48c 77b0a567
[submodule "vendor/plugins/acts_as_journalized"]
path = vendor/plugins/acts_as_journalized
url = git://github.com/finnlabs/acts_as_journalized
...@@ -17,8 +17,7 @@ class IssueMovesController < ApplicationController ...@@ -17,8 +17,7 @@ class IssueMovesController < ApplicationController
moved_issues = [] moved_issues = []
@issues.each do |issue| @issues.each do |issue|
issue.reload issue.reload
issue.init_journal(User.current) issue.init_journal(User.current, @notes || "")
issue.current_journal.notes = @notes if @notes.present?
call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy }) call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy })
if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)}) if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)})
moved_issues << r moved_issues << r
......
...@@ -32,6 +32,7 @@ class IssuesController < ApplicationController ...@@ -32,6 +32,7 @@ class IssuesController < ApplicationController
rescue_from Query::StatementInvalid, :with => :query_statement_invalid rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :journals helper :journals
include JournalsHelper
helper :projects helper :projects
include ProjectsHelper include ProjectsHelper
helper :custom_fields helper :custom_fields
...@@ -103,8 +104,7 @@ class IssuesController < ApplicationController ...@@ -103,8 +104,7 @@ class IssuesController < ApplicationController
end end
def show def show
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") @journals = @issue.journals.find(:all, :include => [:user], :order => "#{Journal.table_name}.created_at ASC")
@journals.each_with_index {|j,i| j.indice = i+1}
@journals.reverse! if User.current.wants_comments_in_reverse_order? @journals.reverse! if User.current.wants_comments_in_reverse_order?
@changesets = @issue.changesets.visible.all @changesets = @issue.changesets.visible.all
@changesets.reverse! if User.current.wants_comments_in_reverse_order? @changesets.reverse! if User.current.wants_comments_in_reverse_order?
...@@ -154,6 +154,7 @@ class IssuesController < ApplicationController ...@@ -154,6 +154,7 @@ class IssuesController < ApplicationController
end end
def edit def edit
return render_reply(@journal) if @journal
update_issue_from_params update_issue_from_params
@journal = @issue.current_journal @journal = @issue.current_journal
...@@ -169,7 +170,7 @@ class IssuesController < ApplicationController ...@@ -169,7 +170,7 @@ class IssuesController < ApplicationController
if @issue.save_issue_with_child_records(params, @time_entry) if @issue.save_issue_with_child_records(params, @time_entry)
render_attachment_warning_if_needed(@issue) render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? flash[:notice] = l(:notice_successful_update) unless @issue.current_journal == @journal
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default({:action => 'show', :id => @issue}) } format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
...@@ -177,7 +178,7 @@ class IssuesController < ApplicationController ...@@ -177,7 +178,7 @@ class IssuesController < ApplicationController
end end
else else
render_attachment_warning_if_needed(@issue) render_attachment_warning_if_needed(@issue)
flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? flash[:notice] = l(:notice_successful_update) unless @issue.current_journal == @journal
@journal = @issue.current_journal @journal = @issue.current_journal
respond_to do |format| respond_to do |format|
...@@ -271,6 +272,7 @@ private ...@@ -271,6 +272,7 @@ private
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
@issue.init_journal(User.current, @notes) @issue.init_journal(User.current, @notes)
@issue.safe_attributes = params[:issue] @issue.safe_attributes = params[:issue]
@journal = @issue.current_journal
end end
# TODO: Refactor, lots of extra code in here # TODO: Refactor, lots of extra code in here
......
...@@ -92,7 +92,7 @@ class WikiController < ApplicationController ...@@ -92,7 +92,7 @@ class WikiController < ApplicationController
@content.comments = nil @content.comments = nil
# To prevent StaleObjectError exception when reverting to a previous version # To prevent StaleObjectError exception when reverting to a previous version
@content.version = @page.content.version @content.lock_version = @page.content.lock_version
rescue ActiveRecord::StaleObjectError rescue ActiveRecord::StaleObjectError
# Optimistic locking exception # Optimistic locking exception
flash[:error] = l(:notice_locking_conflict) flash[:error] = l(:notice_locking_conflict)
...@@ -117,6 +117,7 @@ class WikiController < ApplicationController ...@@ -117,6 +117,7 @@ class WikiController < ApplicationController
redirect_to :action => 'show', :project_id => @project, :id => @page.title redirect_to :action => 'show', :project_id => @project, :id => @page.title
return return
end end
params[:content].delete(:version) # The version count is automatically increased
@content.attributes = params[:content] @content.attributes = params[:content]
@content.author = User.current @content.author = User.current
# if page is new @page.save will also save content, but not if page isn't a new record # if page is new @page.save will also save content, but not if page isn't a new record
...@@ -157,7 +158,7 @@ class WikiController < ApplicationController ...@@ -157,7 +158,7 @@ class WikiController < ApplicationController
@version_pages = Paginator.new self, @version_count, per_page_option, params['p'] @version_pages = Paginator.new self, @version_count, per_page_option, params['p']
# don't load text # don't load text
@versions = @page.content.versions.find :all, @versions = @page.content.versions.find :all,
:select => "id, author_id, comments, updated_on, version", :select => "id, user_id, notes, created_at, version",
:order => 'version DESC', :order => 'version DESC',
:limit => @version_pages.items_per_page + 1, :limit => @version_pages.items_per_page + 1,
:offset => @version_pages.current.offset :offset => @version_pages.current.offset
......
# redMine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module JournalsHelper
def render_notes(issue, journal, options={})
content = ''
editable = User.current.logged? && (User.current.allowed_to?(:edit_issue_notes, issue.project) || (journal.user == User.current && User.current.allowed_to?(:edit_own_issue_notes, issue.project)))
links = []
if !journal.notes.blank?
links << link_to_remote(image_tag('comment.png'),
{ :url => {:controller => 'journals', :action => 'new', :id => issue, :journal_id => journal} },
:title => l(:button_quote)) if options[:reply_links]
links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
{ :controller => 'journals', :action => 'edit', :id => journal },
:title => l(:button_edit)) if editable
end
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
content << textilizable(journal, :notes)
css_classes = "wiki"
css_classes << " editable" if editable
content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
end
def link_to_in_place_notes_editor(text, field_id, url, options={})
onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;"
link_to text, '#', options.merge(:onclick => onclick)
end
end
...@@ -19,28 +19,43 @@ require "digest/md5" ...@@ -19,28 +19,43 @@ require "digest/md5"
class Attachment < ActiveRecord::Base class Attachment < ActiveRecord::Base
belongs_to :container, :polymorphic => true belongs_to :container, :polymorphic => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
# FIXME: Remove these once the Versions, Documents and Projects themselves can provide file events
belongs_to :version, :foreign_key => "container_id"
belongs_to :document, :foreign_key => "container_id"
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
validates_presence_of :container, :filename, :author validates_presence_of :container, :filename, :author
validates_length_of :filename, :maximum => 255 validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255 validates_length_of :disk_filename, :maximum => 255
acts_as_event :title => :filename, acts_as_journalized :event_title => :filename,
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}} :event_url => (Proc.new do |o|
{ :controller => 'attachments', :action => 'download',
:id => o.journaled_id, :filename => o.filename }
end),
:activity_type => 'files',
:activity_permission => :view_files,
:activity_find_options => { :include => { :version => :project } }
acts_as_activity_provider :type => 'files', acts_as_activity :type => 'documents', :permission => :view_documents,
:permission => :view_files, :find_options => { :include => { :document => :project } }
:author_key => :author_id,
:find_options => {:select => "#{Attachment.table_name}.*", # This method is called on save by the AttachmentJournal in order to
:joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " + # decide which kind of activity we are dealing with. When that activity
"LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"} # is retrieved later, we don't need to check the container_type in
# SQL anymore as that will be just the one we have specified here.
acts_as_activity_provider :type => 'documents', def activity_type
:permission => :view_documents, case container_type
:author_key => :author_id, when "Document"
:find_options => {:select => "#{Attachment.table_name}.*", "documents"
:joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " + when "Version"
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} "files"
else
super
end
end
cattr_accessor :storage_path cattr_accessor :storage_path
@@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files" @@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files"
......
...@@ -23,19 +23,17 @@ class Changeset < ActiveRecord::Base ...@@ -23,19 +23,17 @@ class Changeset < ActiveRecord::Base
has_many :changes, :dependent => :delete_all has_many :changes, :dependent => :delete_all
has_and_belongs_to_many :issues has_and_belongs_to_many :issues
acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))}, acts_as_journalized :event_title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
:description => :long_comments, :event_description => :long_comments,
:datetime => :committed_on, :event_datetime => :committed_on,
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}} :event_url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}},
:activity_timestamp => "#{table_name}.committed_on",
:activity_find_options => {:include => [:user, {:repository => :project}]}
acts_as_searchable :columns => 'comments', acts_as_searchable :columns => 'comments',
:include => {:repository => :project}, :include => {:repository => :project},
:project_key => "#{Repository.table_name}.project_id", :project_key => "#{Repository.table_name}.project_id",
:date_column => 'committed_on' :date_column => 'committed_on'
acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
:author_key => :user_id,
:find_options => {:include => [:user, {:repository => :project}]}
validates_presence_of :repository_id, :revision, :committed_on, :commit_date validates_presence_of :repository_id, :revision, :committed_on, :commit_date
validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :revision, :scope => :repository_id
...@@ -202,7 +200,7 @@ class Changeset < ActiveRecord::Base ...@@ -202,7 +200,7 @@ class Changeset < ActiveRecord::Base
# don't change the status is the issue is closed # don't change the status is the issue is closed
return if issue.status && issue.status.is_closed? return if issue.status && issue.status.is_closed?
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag)) issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
issue.status = status issue.status = status
unless Setting.commit_fix_done_ratio.blank? unless Setting.commit_fix_done_ratio.blank?
issue.done_ratio = Setting.commit_fix_done_ratio.to_i issue.done_ratio = Setting.commit_fix_done_ratio.to_i
......
...@@ -20,11 +20,13 @@ class Document < ActiveRecord::Base ...@@ -20,11 +20,13 @@ class Document < ActiveRecord::Base
belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id" belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
acts_as_attachable :delete_permission => :manage_documents acts_as_attachable :delete_permission => :manage_documents
acts_as_journalized :event_title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
:event_url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.journaled_id}},
:event_author => (Proc.new do |o|
o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC").try(:author)
end)
acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"},
:author => Proc.new {|o| (a = o.attachments.find(:first, :order => "#{Attachment.table_name}.created_on ASC")) ? a.author : nil },
:url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}}
acts_as_activity_provider :find_options => {:include => :project}
validates_presence_of :project, :title, :category validates_presence_of :project, :title, :category
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
......
...@@ -27,7 +27,6 @@ class Issue < ActiveRecord::Base ...@@ -27,7 +27,6 @@ class Issue < ActiveRecord::Base
belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id' belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all has_many :time_entries, :dependent => :delete_all
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
...@@ -38,21 +37,26 @@ class Issue < ActiveRecord::Base ...@@ -38,21 +37,26 @@ class Issue < ActiveRecord::Base
acts_as_attachable :after_remove => :attachment_removed acts_as_attachable :after_remove => :attachment_removed
acts_as_customizable acts_as_customizable
acts_as_watchable acts_as_watchable
acts_as_journalized :event_title => Proc.new {|o| "#{o.tracker.name} ##{o.journaled_id} (#{o.status}): #{o.subject}"},
:event_type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') },
:except => [:description]
register_on_journal_formatter(:id, 'parent_id')
register_on_journal_formatter(:named_association, 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
'priority_id', 'category_id', 'fixed_version_id')
register_on_journal_formatter(:fraction, 'estimated_hours')
register_on_journal_formatter(:decimal, 'done_ratio')
register_on_journal_formatter(:datetime, 'due_date', 'start_date')
register_on_journal_formatter(:plaintext, 'subject')
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
:include => [:project, :journals], :include => [:project, :journals],
# sort by id so that limited eager loading doesn't break with postgresql # sort by id so that limited eager loading doesn't break with postgresql
:order_column => "#{table_name}.id" :order_column => "#{table_name}.id"
acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
:type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
:author_key => :author_id
DONE_RATIO_OPTIONS = %w(issue_field issue_status) DONE_RATIO_OPTIONS = %w(issue_field issue_status)
attr_reader :current_journal
validates_presence_of :subject, :priority, :project, :tracker, :author, :status validates_presence_of :subject, :priority, :project, :tracker, :author, :status
validates_length_of :subject, :maximum => 255 validates_length_of :subject, :maximum => 255
...@@ -88,7 +92,7 @@ class Issue < ActiveRecord::Base ...@@ -88,7 +92,7 @@ class Issue < ActiveRecord::Base
before_create :default_assign before_create :default_assign
before_save :close_duplicates, :update_done_ratio_from_issue_status before_save :close_duplicates, :update_done_ratio_from_issue_status
after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes
after_destroy :update_parent_attributes after_destroy :update_parent_attributes
# Returns true if usr or current user is allowed to view the issue # Returns true if usr or current user is allowed to view the issue
...@@ -346,15 +350,11 @@ class Issue < ActiveRecord::Base ...@@ -346,15 +350,11 @@ class Issue < ActiveRecord::Base
end end
end end
def init_journal(user, notes = "") # Callback on attachment deletion
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) def attachment_removed(obj)
@issue_before_change = self.clone init_journal(User.current)
@issue_before_change.status = self.status create_journal
@custom_values_before_change = {} last_journal.update_attribute(:changes, {obj.id => [obj.filename, nil]}.to_yaml)
self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
# Make sure updated_on is updated when adding a note.
updated_on_will_change!
@current_journal
end end
# Return true if the issue is closed, otherwise false # Return true if the issue is closed, otherwise false
...@@ -549,13 +549,12 @@ class Issue < ActiveRecord::Base ...@@ -549,13 +549,12 @@ class Issue < ActiveRecord::Base
if valid? if valid?
attachments = Attachment.attach_files(self, params[:attachments]) attachments = Attachment.attach_files(self, params[:attachments])
attachments[:files].each {|a| @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
# TODO: Rename hook # TODO: Rename hook
Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => current_journal})
begin begin
if save if save
# TODO: Rename hook # TODO: Rename hook
Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal}) Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => current_journal})
else else
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
...@@ -770,22 +769,12 @@ class Issue < ActiveRecord::Base ...@@ -770,22 +769,12 @@ class Issue < ActiveRecord::Base
).each do |issue| ).each do |issue|
next if issue.project.nil? || issue.fixed_version.nil? next if issue.project.nil? || issue.fixed_version.nil?
unless issue.project.shared_versions.include?(issue.fixed_version) unless issue.project.shared_versions.include?(issue.fixed_version)
issue.init_journal(User.current)
issue.fixed_version = nil issue.fixed_version = nil
issue.save issue.save
end end
end end
end end
# Callback on attachment deletion
def attachment_removed(obj)
journal = init_journal(User.current)
journal.details << JournalDetail.new(:property => 'attachment',
:prop_key => obj.id,
:old_value => obj.filename)
journal.save
end
# Default assignment based on category # Default assignment based on category
def default_assign def default_assign
if assigned_to.nil? && category && category.assigned_to if assigned_to.nil? && category && category.assigned_to
...@@ -810,40 +799,14 @@ class Issue < ActiveRecord::Base ...@@ -810,40 +799,14 @@ class Issue < ActiveRecord::Base
duplicate.reload duplicate.reload
# Don't re-close it if it's already closed # Don't re-close it if it's already closed
next if duplicate.closed? next if duplicate.closed?
# Same user and notes # Implicitely creates a new journal
if @current_journal
duplicate.init_journal(@current_journal.user, @current_journal.notes)
end
duplicate.update_attribute :status, self.status duplicate.update_attribute :status, self.status
# Same user and notes
duplicate.journals.last.user = current_journal.user
duplicate.journals.last.notes = current_journal.notes
end end
end end
end end
# Saves the changes in a Journal
# Called after_save
def create_journal
if @current_journal
# attributes changes
(Issue.column_names - %w(id description root_id lft rgt lock_version created_on updated_on)).each {|c|
@current_journal.details << JournalDetail.new(:property => 'attr',
:prop_key => c,
:old_value => @issue_before_change.send(c),
:value => send(c)) unless send(c)==@issue_before_change.send(c)
}
# custom fields changes
custom_values.each {|c|
next if (@custom_values_before_change[c.custom_field_id]==c.value ||
(@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
@current_journal.details << JournalDetail.new(:property => 'cf',
:prop_key => c.custom_field_id,
:old_value => @custom_values_before_change[c.custom_field_id],
:value => c.value)
}
@current_journal.save
# reset current journal
init_journal @current_journal.user, @current_journal.notes
end
end
# Query generator for selecting groups of issue counts for a project # Query generator for selecting groups of issue counts for a project
# based on specific criteria # based on specific criteria
...@@ -873,4 +836,13 @@ class Issue < ActiveRecord::Base ...@@ -873,4 +836,13 @@ class Issue < ActiveRecord::Base
end end
IssueJournal.class_eval do
# Shortcut
def new_status
if details.keys.include? 'status_id'
(newval = details['status_id'].last) ? IssueStatus.find_by_id(newval.to_i) : nil
end
end
end
end end
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Journal < ActiveRecord::Base
belongs_to :journalized, :polymorphic => true
# added as a quick fix to allow eager loading of the polymorphic association
# since always associated to an issue, for now
belongs_to :issue, :foreign_key => :journalized_id
belongs_to :user
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
attr_accessor :indice
acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" },
:description => :notes,
:author => :user,
:type => Proc.new {|o| (s = o.new_status) ? (s.is_closed? ? 'issue-closed' : 'issue-edit') : 'issue-note' },
:url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue.id, :anchor => "change-#{o.id}"}}
acts_as_activity_provider :type => 'issues',
:permission => :view_issues,
:author_key => :user_id,
:find_options => {:include => [{:issue => :project}, :details, :user],
:conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" +
" (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"}
def save(*args)
# Do not save an empty journal
(details.empty? && notes.blank?) ? false : super
end
# Returns the new status if the journal contains a status change, otherwise nil
def new_status
c = details.detect {|detail| detail.prop_key == 'status_id'}
(c && c.value) ? IssueStatus.find_by_id(c.value.to_i) : nil
end
def new_value_for(prop)
c = details.detect {|detail| detail.prop_key == prop}
c ? c.value : nil
end
def editable_by?(usr)
usr && usr.logged? && (usr.allowed_to?(:edit_issue_notes, project) || (self.user == usr && usr.allowed_to?(:edit_own_issue_notes, project)))
end
def project
journalized.respond_to?(:project) ? journalized.project : nil
end
def attachments
journalized.respond_to?(:attachments) ? journalized.attachments : nil
end
# Returns a string of css classes
def css_classes
s = 'journal'
s << ' has-notes' unless notes.blank?
s << ' has-details' unless details.blank?
s
end
end
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class JournalDetail < ActiveRecord::Base
belongs_to :journal
def before_save
self.value = value[0..254] if value && value.is_a?(String)
self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
end
end
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class JournalObserver < ActiveRecord::Observer
def after_create(journal)
if Setting.notified_events.include?('issue_updated') ||
(Setting.notified_events.include?('issue_note_added') && journal.notes.present?) ||
(Setting.notified_events.include?('issue_status_updated') && journal.new_status.present?) ||
(Setting.notified_events.include?('issue_priority_updated') && journal.new_value_for('priority_id').present?)
Mailer.deliver_issue_edit(journal)
end
end
end
...@@ -159,20 +159,20 @@ class MailHandler < ActionMailer::Base ...@@ -159,20 +159,20 @@ class MailHandler < ActionMailer::Base
# ignore CLI-supplied defaults for new issues # ignore CLI-supplied defaults for new issues
@@handler_options[:issue].clear @@handler_options[:issue].clear
journal = issue.init_journal(user, cleaned_up_text_body) issue.init_journal(user, cleaned_up_text_body)
issue.safe_attributes = issue_attributes_from_keywords(issue) issue.safe_attributes = issue_attributes_from_keywords(issue)
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
add_attachments(issue) add_attachments(issue)
issue.save! issue.save!
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
journal issue.last_journal
end end
# Reply will be added to the issue # Reply will be added to the issue
def receive_journal_reply(journal_id) def receive_journal_reply(journal_id)
journal = Journal.find_by_id(journal_id) journal = Journal.find_by_id(journal_id)
if journal && journal.journalized_type == 'Issue' if journal and journal.journaled.is_a? Issue
receive_issue_reply(journal.journalized_id) receive_issue_reply(journal.journaled_id)
end end
end end
......
...@@ -21,6 +21,7 @@ class Mailer < ActionMailer::Base ...@@ -21,6 +21,7 @@ class Mailer < ActionMailer::Base
layout 'mailer' layout 'mailer'
helper :application helper :application
helper :issues helper :issues
helper :journals
helper :custom_fields helper :custom_fields
include ActionController::UrlWriter include ActionController::UrlWriter
...@@ -58,7 +59,7 @@ class Mailer < ActionMailer::Base ...@@ -58,7 +59,7 @@ class Mailer < ActionMailer::Base
# issue_edit(journal) => tmail object # issue_edit(journal) => tmail object
# Mailer.deliver_issue_edit(journal) => sends an email to issue recipients # Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
def issue_edit(journal) def issue_edit(journal)
issue = journal.journalized.reload issue = journal.journaled.reload
redmine_headers 'Project' => issue.project.identifier, redmine_headers 'Project' => issue.project.identifier,
'Issue-Id' => issue.id, 'Issue-Id' => issue.id,
'Issue-Author' => issue.author.login, 'Issue-Author' => issue.author.login,
...@@ -71,7 +72,7 @@ class Mailer < ActionMailer::Base ...@@ -71,7 +72,7 @@ class Mailer < ActionMailer::Base
# Watchers in cc # Watchers in cc
cc(issue.watcher_recipients - @recipients) cc(issue.watcher_recipients - @recipients)
s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] " s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
s << "(#{issue.status.name}) " if journal.new_value_for('status_id') s << "(#{issue.status.name}) " if journal.details['status_id']
s << issue.subject s << issue.subject
subject s subject s
body :issue => issue, body :issue => issue,
...@@ -170,7 +171,7 @@ class Mailer < ActionMailer::Base ...@@ -170,7 +171,7 @@ class Mailer < ActionMailer::Base
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients) 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}" subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
body :message => message, body :message => message,
:message_url => url_for(message.event_url) :message_url => url_for(message.last_journal.event_url)
render_multipart('message_posted', body) render_multipart('message_posted', body)
end end
......
...@@ -22,18 +22,24 @@ class Message < ActiveRecord::Base ...@@ -22,18 +22,24 @@ class Message < ActiveRecord::Base
acts_as_attachable acts_as_attachable
belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id'
acts_as_journalized :event_title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
:event_description => :content,
:event_type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
:event_url => (Proc.new do |o|
msg = o.journaled
if msg.parent_id.nil?
{:id => msg.id}
else
{:id => msg.parent_id, :r => msg.id, :anchor => "message-#{msg.id}"}
end.reverse_merge :controller => 'messages', :action => 'show', :board_id => msg.board_id
end),
:activity_find_options => { :include => { :board => :project } }
acts_as_searchable :columns => ['subject', 'content'], acts_as_searchable :columns => ['subject', 'content'],
:include => {:board => :project}, :include => {:board => :project},
:project_key => 'project_id', :project_key => 'project_id',
:date_column => "#{table_name}.created_on" :date_column => "#{table_name}.created_on"
acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"},
:description => :content,
:type => Proc.new {|o| o.parent_id.nil? ? 'message' : 'reply'},
:url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} :
{:id => o.parent_id, :r => o.id, :anchor => "message-#{o.id}"})}
acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]},
:author_key => :author_id
acts_as_watchable acts_as_watchable
attr_protected :locked, :sticky attr_protected :locked, :sticky
......
...@@ -16,7 +16,10 @@ ...@@ -16,7 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MessageObserver < ActiveRecord::Observer class MessageObserver < ActiveRecord::Observer
def after_create(message) def after_save(message)
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted') if message.last_journal.version == 1
# Only deliver mails for the first journal
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
end
end end
end end
...@@ -24,10 +24,8 @@ class News < ActiveRecord::Base ...@@ -24,10 +24,8 @@ class News < ActiveRecord::Base
validates_length_of :title, :maximum => 60 validates_length_of :title, :maximum => 60
validates_length_of :summary, :maximum => 255 validates_length_of :summary, :maximum => 255
acts_as_journalized :event_url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.journaled_id} }
acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project
acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}}
acts_as_activity_provider :find_options => {:include => [:project, :author]},
:author_key => :author_id
named_scope :visible, lambda {|*args| { named_scope :visible, lambda {|*args| {
:include => :project, :include => :project,
......
...@@ -540,8 +540,8 @@ class Query < ActiveRecord::Base ...@@ -540,8 +540,8 @@ class Query < ActiveRecord::Base
# Returns the journals # Returns the journals
# Valid options are :order, :offset, :limit # Valid options are :order, :offset, :limit
def journals(options={}) def issue_journals(options={})
Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], IssueJournal.find :all, :joins => [:user, {:issue => [:project, :author, :tracker, :status]}],
:conditions => statement, :conditions => statement,
:order => options[:order], :order => options[:order],
:limit => options[:limit], :limit => options[:limit],
......
...@@ -26,14 +26,10 @@ class TimeEntry < ActiveRecord::Base ...@@ -26,14 +26,10 @@ class TimeEntry < ActiveRecord::Base
attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
acts_as_customizable acts_as_customizable
acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"}, acts_as_journalized :event_title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
:url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}}, :event_url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
:author => :user, :event_author => :user,
:description => :comments :event_description => :comments
acts_as_activity_provider :timestamp => "#{table_name}.created_on",
:author_key => :user_id,
:find_options => {:include => :project}
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
validates_numericality_of :hours, :allow_nil => true, :message => :invalid validates_numericality_of :hours, :allow_nil => true, :message => :invalid
......
...@@ -18,14 +18,22 @@ ...@@ -18,14 +18,22 @@
require 'zlib' require 'zlib'
class WikiContent < ActiveRecord::Base class WikiContent < ActiveRecord::Base
set_locking_column :version
belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id' belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
validates_presence_of :text validates_presence_of :text
validates_length_of :comments, :maximum => 255, :allow_nil => true validates_length_of :comments, :maximum => 255, :allow_nil => true
acts_as_versioned acts_as_journalized :event_type => 'wiki-page',
:event_title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"},
:event_url => Proc.new {|o| {:controller => 'wiki', :id => o.page.wiki.project_id, :page => o.page.title, :version => o.version}},
:activity_type => 'wiki_edits',
:activity_permission => :view_wiki_edits,
:activity_find_options => { :include => { :page => { :wiki => :project } } }
def activity_type
'wiki_edits'
end
def visible?(user=User.current) def visible?(user=User.current)
page.visible?(user) page.visible?(user)
end end
...@@ -44,67 +52,71 @@ class WikiContent < ActiveRecord::Base ...@@ -44,67 +52,71 @@ class WikiContent < ActiveRecord::Base
notified.reject! {|user| !visible?(user)} notified.reject! {|user| !visible?(user)}
notified.collect(&:mail) notified.collect(&:mail)
end end
class Version
belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id'
belongs_to :author, :class_name => '::User', :foreign_key => 'author_id'
attr_protected :data
acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"}, # FIXME: Deprecate
:description => :comments, def versions
:datetime => :updated_on, journals
:type => 'wiki-page', end
:url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.page.wiki.project, :id => o.page.title, :version => o.version}}
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
end
# FIXME: This is for backwards compatibility only. Remove once we decide it is not needed anymore
WikiContentJournal.class_eval do
attr_protected :data
after_save :compress_version_text
acts_as_activity_provider :type => 'wiki_edits', # Wiki Content might be large and the data should possibly be compressed
:timestamp => "#{WikiContent.versioned_table_name}.updated_on", def compress_version_text
:author_key => "#{WikiContent.versioned_table_name}.author_id", self.text = changes["text"].last if changes["text"]
:permission => :view_wiki_edits, self.text ||= self.journaled.text
:find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + end
"#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
"#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
"#{WikiContent.versioned_table_name}.id",
:joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
"LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
"LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
def text=(plain) def text=(plain)
case Setting.wiki_compression case Setting.wiki_compression
when 'gzip' when "gzip"
begin begin
self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION) text_hash :text => Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION), :compression => Setting.wiki_compression
self.compression = 'gzip' rescue
rescue text_hash :text => plain, :compression => ''
self.data = plain end
self.compression = ''
end
else else
self.data = plain text_hash :text => plain, :compression => ''
self.compression = ''
end end
plain plain
end end
def text_hash(hash)
changes.delete("text")
changes["data"] = hash[:text]
changes["compression"] = hash[:compression]
update_attribute(:changes, changes.to_yaml)
end
def text def text
@text ||= case compression @text ||= case changes[:compression]
when 'gzip' when 'gzip'
Zlib::Inflate.inflate(data) Zlib::Inflate.inflate(data)
else else
# uncompressed data # uncompressed data
data changes["data"]
end end
end
def project
page.project
end end
# Returns the previous version or nil # Returns the previous version or nil
def previous def previous
@previous ||= WikiContent::Version.find(:first, @previous ||= journaled.journals.at(version - 1)
:order => 'version DESC', end
:include => :author,
:conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version]) # FIXME: Deprecate
def versioned
journaled
end end
end end
end end
<% reply_links = authorize_for('issues', 'edit') -%>
<% for journal in journals %> <% for journal in journals %>
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>"> <%= render_journal issue, journal, :edit_permission => :edit_issue_notes,
<h4><div class="journal-link"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div> :edit_own_permission => :edit_own_issue_notes %>
<%= avatar(journal.user, :size => "24") %>
<%= content_tag('a', '', :name => "note-#{journal.indice}")%>
<%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %></h4>
<% if journal.details.any? %>
<ul class="details">
<% for detail in journal.details %>
<li><%= show_detail(detail) %></li>
<% end %>
</ul>
<% end %>
<%= render_notes(issue, journal, :reply_links => reply_links) unless journal.notes.blank? %>
</div>
<%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %> <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
<% end %> <% end %>
<% form_remote_tag(:url => {}, :html => { :id => "journal-#{@journal.id}-form" }) do %>
<%= text_area_tag :notes, @journal.notes, :class => 'wiki-edit',
:rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
<%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %>
<p><%= submit_tag l(:button_save) %>
<%= link_to l(:button_cancel), '#', :onclick => "Element.remove('journal-#{@journal.id}-form'); " +
"Element.show('journal-#{@journal.id}-notes'); return false;" %></p>
<% end %>
page.hide "journal-#{@journal.id}-notes"
page.insert_html :after, "journal-#{@journal.id}-notes",
:partial => 'notes_form'
...@@ -7,7 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do ...@@ -7,7 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
xml.updated((@journals.first ? @journals.first.event_datetime : Time.now).xmlschema) xml.updated((@journals.first ? @journals.first.event_datetime : Time.now).xmlschema)
xml.author { xml.name "#{Setting.app_title}" } xml.author { xml.name "#{Setting.app_title}" }
@journals.each do |change| @journals.each do |change|
issue = change.issue issue = change.journaled
xml.entry do xml.entry do
xml.title "#{issue.project.name} - #{issue.tracker.name} ##{issue.id}: #{issue.subject}" xml.title "#{issue.project.name} - #{issue.tracker.name} ##{issue.id}: #{issue.subject}"
xml.link "rel" => "alternate", "href" => url_for(:controller => 'issues' , :action => 'show', :id => issue, :only_path => false) xml.link "rel" => "alternate", "href" => url_for(:controller => 'issues' , :action => 'show', :id => issue, :only_path => false)
...@@ -20,7 +20,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do ...@@ -20,7 +20,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
xml.content "type" => "html" do xml.content "type" => "html" do
xml.text! '<ul>' xml.text! '<ul>'
change.details.each do |detail| change.details.each do |detail|
xml.text! '<li>' + show_detail(detail, false) + '</li>' xml.text! '<li>' + show_detail(change, detail, false) + '</li>'
end end
xml.text! '</ul>' xml.text! '</ul>'
xml.text! textilizable(change, :notes, :only_path => false) unless change.notes.blank? xml.text! textilizable(change, :notes, :only_path => false) unless change.notes.blank?
......
if @journal.frozen?
# journal was destroyed
page.remove "change-#{@journal.id}"
else
page.replace "journal-#{@journal.id}-notes", render_notes(@journal.issue, @journal, :reply_links => authorize_for('issues', 'edit'))
page.show "journal-#{@journal.id}-notes"
page.remove "journal-#{@journal.id}-form"
end
call_hook(:view_journals_update_rjs_bottom, { :page => page, :journal => @journal })
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<ul> <ul>
<% for detail in @journal.details %> <% for detail in @journal.details %>
<li><%= show_detail(detail, true) %></li> <li><%= @journal.render_detail(detail, true) %></li>
<% end %> <% end %>
</ul> </ul>
......
<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %> <%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %>
<% for detail in @journal.details -%> <% for detail in @journal.details -%>
<%= show_detail(detail, true) %> <%= @journal.render_detail(detail, true) %>
<% end -%> <% end -%>
<%= @journal.notes if @journal.notes? %> <%= @journal.notes if @journal.notes? %>
......
...@@ -26,7 +26,10 @@ ...@@ -26,7 +26,10 @@
<h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3> <h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
<dl id="search-results"> <dl id="search-results">
<% @results.each do |e| %> <% @results.each do |e| %>
<dt class="<%= e.event_type %>"><%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %> <%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url %></dt> <dt class="<%= e.event_type %>">
<%= content_tag('span', h(e.project), :class => 'project') unless @project == e.project %>
<%= link_to highlight_tokens(truncate(e.event_title, :length => 255), @tokens), e.event_url %>
</dt>
<dd><span class="description"><%= highlight_tokens(e.event_description, @tokens) %></span> <dd><span class="description"><%= highlight_tokens(e.event_description, @tokens) %></span>
<span class="author"><%= format_time(e.event_datetime) %></span></dd> <span class="author"><%= format_time(e.event_datetime) %></span></dd>
<% end %> <% end %>
......
<h2><%=h @page.pretty_title %></h2> <h2><%=h @page.pretty_title %></h2>
<% form_for :content, @content, :url => {:action => 'update', :id => @page.title}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %> <% form_for :content, @content, :url => {:action => 'update', :id => @page.title}, :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %>
<%= f.hidden_field :version %> <%= f.hidden_field :lock_version %>
<%= error_messages_for 'content' %> <%= error_messages_for 'content' %>
<p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p> <p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
......
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
<td class="id"><%= link_to ver.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => ver.version %></td> <td class="id"><%= link_to ver.version, :action => 'show', :id => @page.title, :project_id => @page.project, :version => ver.version %></td>
<td class="checkbox"><%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < @versions.size) %></td> <td class="checkbox"><%= radio_button_tag('version', ver.version, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < @versions.size) %></td>
<td class="checkbox"><%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}") if show_diff && (line_num > 1) %></td> <td class="checkbox"><%= radio_button_tag('version_from', ver.version, (line_num==2), :id => "cbto-#{line_num}") if show_diff && (line_num > 1) %></td>
<td class="updated_on"><%= format_time(ver.updated_on) %></td> <td class="updated_on"><%= format_time(ver.created_at) %></td>
<td class="author"><%= link_to_user ver.author %></td> <td class="author"><%= link_to_user ver.user %></td>
<td class="comments"><%=h ver.comments %></td> <td class="comments"><%=h ver.notes %></td>
<td class="buttons"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %></td> <td class="buttons"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %></td>
</tr> </tr>
<% line_num += 1 %> <% line_num += 1 %>
......
...@@ -36,7 +36,7 @@ Rails::Initializer.run do |config| ...@@ -36,7 +36,7 @@ Rails::Initializer.run do |config|
# Activate observers that should always be running # Activate observers that should always be running
# config.active_record.observers = :cacher, :garbage_collector # config.active_record.observers = :cacher, :garbage_collector
config.active_record.observers = :message_observer, :issue_observer, :journal_observer, :news_observer, :document_observer, :wiki_content_observer config.active_record.observers = :journal_observer, :message_observer, :issue_observer, :news_observer, :document_observer, :wiki_content_observer
# Make Active Record use UTC-base instead of local time # Make Active Record use UTC-base instead of local time
# config.active_record.default_timezone = :utc # config.active_record.default_timezone = :utc
......
...@@ -2,7 +2,7 @@ class RenameCommentToComments < ActiveRecord::Migration ...@@ -2,7 +2,7 @@ class RenameCommentToComments < ActiveRecord::Migration
def self.up def self.up
rename_column(:comments, :comment, :comments) if ActiveRecord::Base.connection.columns(Comment.table_name).detect{|c| c.name == "comment"} rename_column(:comments, :comment, :comments) if ActiveRecord::Base.connection.columns(Comment.table_name).detect{|c| c.name == "comment"}
rename_column(:wiki_contents, :comment, :comments) if ActiveRecord::Base.connection.columns(WikiContent.table_name).detect{|c| c.name == "comment"} rename_column(:wiki_contents, :comment, :comments) if ActiveRecord::Base.connection.columns(WikiContent.table_name).detect{|c| c.name == "comment"}
rename_column(:wiki_content_versions, :comment, :comments) if ActiveRecord::Base.connection.columns(WikiContent.versioned_table_name).detect{|c| c.name == "comment"} rename_column(:wiki_content_versions, :comment, :comments) if ActiveRecord::Base.connection.columns("wiki_content_versions").detect{|c| c.name == "comment"}
rename_column(:time_entries, :comment, :comments) if ActiveRecord::Base.connection.columns(TimeEntry.table_name).detect{|c| c.name == "comment"} rename_column(:time_entries, :comment, :comments) if ActiveRecord::Base.connection.columns(TimeEntry.table_name).detect{|c| c.name == "comment"}
rename_column(:changesets, :comment, :comments) if ActiveRecord::Base.connection.columns(Changeset.table_name).detect{|c| c.name == "comment"} rename_column(:changesets, :comment, :comments) if ActiveRecord::Base.connection.columns(Changeset.table_name).detect{|c| c.name == "comment"}
end end
......
class Meeting < ActiveRecord::Base class Meeting < ActiveRecord::Base
belongs_to :project belongs_to :project
acts_as_event :title => Proc.new {|o| "#{o.scheduled_on} Meeting"}, acts_as_journalized :event_title => Proc.new {|o| "#{o.scheduled_on} Meeting"},
:datetime => :scheduled_on, :event_datetime => :scheduled_on,
:author => nil, :event_author => nil,
:url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}} :event_url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}}
:activity_timestamp => 'scheduled_on'
acts_as_activity_provider :timestamp => 'scheduled_on',
:find_options => { :include => :project }
end end
...@@ -206,12 +206,12 @@ Redmine::MenuManager.map :project_menu do |menu| ...@@ -206,12 +206,12 @@ Redmine::MenuManager.map :project_menu do |menu|
end end
Redmine::Activity.map do |activity| Redmine::Activity.map do |activity|
activity.register :issues, :class_name => %w(Issue Journal) activity.register :issues, :class_name => 'Issue'
activity.register :changesets activity.register :changesets
activity.register :news activity.register :news
activity.register :documents, :class_name => %w(Document Attachment) activity.register :documents, :class_name => %w(Document Attachment)
activity.register :files, :class_name => 'Attachment' activity.register :files, :class_name => 'Attachment'
activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false activity.register :wiki_edits, :class_name => 'WikiContent', :default => false
activity.register :messages, :default => false activity.register :messages, :default => false
activity.register :time_entries, :default => false activity.register :time_entries, :default => false
end end
......
...@@ -38,7 +38,17 @@ module Redmine ...@@ -38,7 +38,17 @@ module Redmine
return @event_types unless @event_types.nil? return @event_types unless @event_types.nil?
@event_types = Redmine::Activity.available_event_types @event_types = Redmine::Activity.available_event_types
@event_types = @event_types.select {|o| @project.self_and_descendants.detect {|p| @user.allowed_to?("view_#{o}".to_sym, p)}} if @project if @project
@event_types = @event_types.select do |o|
@project.self_and_descendants.detect do |p|
permissions = constantized_providers(o).collect do |p|
p.activity_provider_options[o].try(:[], :permission)
end.compact
return @user.allowed_to?("view_#{o}".to_sym, @project) if permissions.blank?
permissions.all? {|p| @user.allowed_to?(p, @project) } if @project
end
end
end
@event_types @event_types
end end
......
...@@ -280,13 +280,13 @@ module Redmine ...@@ -280,13 +280,13 @@ module Redmine
pdf.SetFontStyle('B',9) pdf.SetFontStyle('B',9)
pdf.Cell(190,5, l(:label_history), "B") pdf.Cell(190,5, l(:label_history), "B")
pdf.Ln pdf.Ln
for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC") for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_at ASC")
pdf.SetFontStyle('B',8) pdf.SetFontStyle('B',8)
pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name) pdf.Cell(190,5, format_time(journal.created_at) + " - " + journal.user.name)
pdf.Ln pdf.Ln
pdf.SetFontStyle('I',8) pdf.SetFontStyle('I',8)
for detail in journal.details for detail in journal.details
pdf.Cell(190,5, "- " + show_detail(detail, true)) pdf.Cell(190,5, "- " + show_detail(journal, detail, true))
pdf.Ln pdf.Ln
end end
if journal.notes? if journal.notes?
......
...@@ -95,7 +95,7 @@ module Redmine ...@@ -95,7 +95,7 @@ module Redmine
page = nil page = nil
if args.size > 0 if args.size > 0
page = Wiki.find_page(args.first.to_s, :project => @project) page = Wiki.find_page(args.first.to_s, :project => @project)
elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version) elsif obj.is_a?(WikiContent)
page = obj.page page = obj.page
else else
raise 'With no argument, this macro can be called from wiki pages only.' raise 'With no argument, this macro can be called from wiki pages only.'
......
---
journal_details_001:
old_value: "1"
property: attr
id: 1
value: "2"
prop_key: status_id
journal_id: 1
journal_details_002:
old_value: "40"
property: attr
id: 2
value: "30"
prop_key: done_ratio
journal_id: 1
journal_details_003:
old_value: nil
property: attr
id: 3
value: "6"
prop_key: fixed_version_id
journal_id: 4
--- ---
journals_001: journals_001:
created_on: <%= 2.days.ago.to_date.to_s(:db) %>
notes: "Journal notes"
id: 1 id: 1
journalized_type: Issue type: "IssueJournal"
activity_type: "issues"
created_at: <%= 3.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 1 user_id: 1
journalized_id: 1 notes: "Journal notes"
journals_002: journaled_id: 1
created_on: <%= 1.days.ago.to_date.to_s(:db) %> changes: |
notes: "Some notes with Redmine links: #2, r2." ---
status_id:
- 1
- 2
done_ratio:
- 40
- 30
journals_002:
id: 2 id: 2
journalized_type: Issue type: "IssueJournal"
activity_type: "issues"
created_at: <%= 1.days.ago.to_date.to_s(:db) %>
version: 2
user_id: 2 user_id: 2
journalized_id: 1 notes: "Some notes with Redmine links: #2, r2."
journals_003: journaled_id: 1
created_on: <%= 1.days.ago.to_date.to_s(:db) %> changes: "--- {}"
notes: "A comment with inline image: !picture.jpg!" journals_003:
id: 3 id: 3
journalized_type: Issue type: "IssueJournal"
activity_type: "issues"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2 user_id: 2
journalized_id: 2 notes: "A comment with inline image: !picture.jpg!"
journals_004: journaled_id: 2
created_on: <%= 1.days.ago.to_date.to_s(:db) %> changes: "--- {}"
notes: "A comment with a private version." journals_004:
id: 4 id: 4
journalized_type: Issue type: "IssueJournal"
activity_type: "issues"
created_at: <%= 1.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "A comment with a private version."
journaled_id: 6
changes: |
---
fixed_version_id:
-
- 6
journals_005:
id: 5
type: "IssueJournal"
activity_type: "issues"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 0
user_id: 1
notes:
journaled_id: 5
changes: "--- {}"
journals_006:
id: 6
type: "WikiContentJournal"
activity_type: "wiki_edits"
created_at: 2007-03-07 00:08:07 +01:00
version: 1
user_id: 2
notes: Page creation
journaled_id: 1
changes: |
---
compression: ""
data: |-
h1. CookBook documentation
Some [[documentation]] here...
journals_007:
id: 7
type: "WikiContentJournal"
activity_type: "wiki_edits"
created_at: 2007-03-07 00:08:34 +01:00
version: 2
user_id: 1 user_id: 1
journalized_id: 6 notes: Small update
journaled_id: 1
changes: |
---
compression: ""
data: |-
h1. CookBook documentation
Some updated [[documentation]] here...
journals_008:
id: 8
type: "WikiContentJournal"
activity_type: "wiki_edits"
created_at: 2007-03-07 00:10:51 +01:00
version: 3
user_id: 1
notes: ""
journaled_id: 1
changes: |
---
compression: ""
data: |-
h1. CookBook documentation
Some updated [[documentation]] here...
journals_009:
id: 9
type: "WikiContentJournal"
activity_type: "wiki_edits"
created_at: 2007-03-08 00:18:07 +01:00
version: 1
user_id: 1
notes:
journaled_id: 2
changes: |
---
data: |-
h1. Another page
This is a link to a ticket: #2
journals_010:
id: 10
type: "MessageJournal"
activity_type: "messages"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 1
notes:
journaled_id: 5
changes: --- {}
journals_011:
id: 11
type: "AttachmentJournal"
activity_type: "files"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "An attachment on a version"
journaled_id: 9
changes: --- {}
journals_012:
id: 12
type: "AttachmentJournal"
activity_type: "files"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "An attachment on a project"
journaled_id: 8
changes: --- {}
journals_013:
id: 13
type: "AttachmentJournal"
activity_type: "files"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "An attachment on an issue"
journaled_id: 7
changes: --- {}
journals_014:
id: 14
type: "AttachmentJournal"
activity_type: "documents"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "An attachment on a document"
journaled_id: 2
changes: --- {}
journals_015:
id: 15
type: "MessageJournal"
activity_type: "messages"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "A message journal"
journaled_id: 1
journals_016:
id: 16
type: "MessageJournal"
activity_type: "messages"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "A message journal"
journaled_id: 2
journals_017:
id: 17
type: "MessageJournal"
activity_type: "messages"
created_at: <%= 2.days.ago.to_date.to_s(:db) %>
version: 1
user_id: 2
notes: "A message journal"
journaled_id: 3
journals_005:
id: 18
type: "IssueJournal"
activity_type: "issues"
created_at: <%= 3.days.ago.to_date.to_s(:db) %>
version: 0
user_id: 2
notes:
journaled_id: 11
changes: "--- {}"
\ No newline at end of file
---
wiki_content_versions_001:
updated_on: 2007-03-07 00:08:07 +01:00
page_id: 1
id: 1
version: 1
author_id: 2
comments: Page creation
wiki_content_id: 1
compression: ""
data: |-
h1. CookBook documentation
Some [[documentation]] here...
wiki_content_versions_002:
updated_on: 2007-03-07 00:08:34 +01:00
page_id: 1
id: 2
version: 2
author_id: 1
comments: Small update
wiki_content_id: 1
compression: ""
data: |-
h1. CookBook documentation
Some updated [[documentation]] here...
wiki_content_versions_003:
updated_on: 2007-03-07 00:10:51 +01:00
page_id: 1
id: 3
version: 3
author_id: 1
comments: ""
wiki_content_id: 1
compression: ""
data: |-
h1. CookBook documentation
Some updated [[documentation]] here...
wiki_content_versions_004:
data: |-
h1. Another page
This is a link to a ticket: #2
updated_on: 2007-03-08 00:18:07 +01:00
page_id: 2
wiki_content_id: 2
id: 4
version: 1
author_id: 1
comments:
...@@ -9,7 +9,7 @@ wiki_contents_001: ...@@ -9,7 +9,7 @@ wiki_contents_001:
updated_on: 2007-03-07 00:10:51 +01:00 updated_on: 2007-03-07 00:10:51 +01:00
page_id: 1 page_id: 1
id: 1 id: 1
version: 3 lock_version: 3
author_id: 1 author_id: 1
comments: Gzip compression activated comments: Gzip compression activated
wiki_contents_002: wiki_contents_002:
...@@ -22,7 +22,7 @@ wiki_contents_002: ...@@ -22,7 +22,7 @@ wiki_contents_002:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 2 page_id: 2
id: 2 id: 2
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_003: wiki_contents_003:
...@@ -33,7 +33,7 @@ wiki_contents_003: ...@@ -33,7 +33,7 @@ wiki_contents_003:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 3 page_id: 3
id: 3 id: 3
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_004: wiki_contents_004:
...@@ -46,7 +46,7 @@ wiki_contents_004: ...@@ -46,7 +46,7 @@ wiki_contents_004:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 4 page_id: 4
id: 4 id: 4
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_005: wiki_contents_005:
...@@ -57,7 +57,7 @@ wiki_contents_005: ...@@ -57,7 +57,7 @@ wiki_contents_005:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 5 page_id: 5
id: 5 id: 5
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_006: wiki_contents_006:
...@@ -68,7 +68,7 @@ wiki_contents_006: ...@@ -68,7 +68,7 @@ wiki_contents_006:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 6 page_id: 6
id: 6 id: 6
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_007: wiki_contents_007:
...@@ -76,7 +76,7 @@ wiki_contents_007: ...@@ -76,7 +76,7 @@ wiki_contents_007:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 7 page_id: 7
id: 7 id: 7
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_008: wiki_contents_008:
...@@ -84,7 +84,7 @@ wiki_contents_008: ...@@ -84,7 +84,7 @@ wiki_contents_008:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 8 page_id: 8
id: 8 id: 8
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_009: wiki_contents_009:
...@@ -92,7 +92,7 @@ wiki_contents_009: ...@@ -92,7 +92,7 @@ wiki_contents_009:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 9 page_id: 9
id: 9 id: 9
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
wiki_contents_010: wiki_contents_010:
...@@ -100,7 +100,7 @@ wiki_contents_010: ...@@ -100,7 +100,7 @@ wiki_contents_010:
updated_on: 2007-03-08 00:18:07 +01:00 updated_on: 2007-03-08 00:18:07 +01:00
page_id: 10 page_id: 10
id: 10 id: 10
version: 1 lock_version: 1
author_id: 1 author_id: 1
comments: comments:
\ No newline at end of file
...@@ -10,10 +10,10 @@ class ActivitiesControllerTest < ActionController::TestCase ...@@ -10,10 +10,10 @@ class ActivitiesControllerTest < ActionController::TestCase
assert_not_nil assigns(:events_by_day) assert_not_nil assigns(:events_by_day)
assert_tag :tag => "h3", assert_tag :tag => "h3",
:content => /#{2.days.ago.to_date.day}/, :content => /#{1.day.ago.to_date.day}/,
:sibling => { :tag => "dl", :sibling => { :tag => "dl",
:child => { :tag => "dt", :child => { :tag => "dt",
:attributes => { :class => /issue-edit/ }, :attributes => { :class => /issue/ },
:child => { :tag => "a", :child => { :tag => "a",
:content => /(#{IssueStatus.find(2).name})/, :content => /(#{IssueStatus.find(2).name})/,
} }
...@@ -46,12 +46,12 @@ class ActivitiesControllerTest < ActionController::TestCase ...@@ -46,12 +46,12 @@ class ActivitiesControllerTest < ActionController::TestCase
assert_not_nil assigns(:events_by_day) assert_not_nil assigns(:events_by_day)
assert_tag :tag => "h3", assert_tag :tag => "h3",
:content => /#{5.day.ago.to_date.day}/, :content => /#{3.day.ago.to_date.day}/,
:sibling => { :tag => "dl", :sibling => { :tag => "dl",
:child => { :tag => "dt", :child => { :tag => "dt",
:attributes => { :class => /issue/ }, :attributes => { :class => /issue/ },
:child => { :tag => "a", :child => { :tag => "a",
:content => /#{Issue.find(5).subject}/, :content => /#{Issue.find(1).subject}/,
} }
} }
} }
......
...@@ -120,10 +120,9 @@ class AttachmentsControllerTest < ActionController::TestCase ...@@ -120,10 +120,9 @@ class AttachmentsControllerTest < ActionController::TestCase
# no referrer # no referrer
assert_redirected_to '/projects/ecookbook' assert_redirected_to '/projects/ecookbook'
assert_nil Attachment.find_by_id(1) assert_nil Attachment.find_by_id(1)
j = issue.journals.find(:first, :order => 'created_on DESC') j = issue.journals.find(:first, :order => 'created_at DESC')
assert_equal 'attachment', j.details.first.property assert_equal [1], j.details.keys
assert_equal '1', j.details.first.prop_key assert_equal 'error281.txt', j.details[1].first
assert_equal 'error281.txt', j.details.first.old_value
end end
def test_destroy_wiki_page_attachment def test_destroy_wiki_page_attachment
......
...@@ -43,7 +43,6 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -43,7 +43,6 @@ class IssuesControllerTest < ActionController::TestCase
:custom_fields_trackers, :custom_fields_trackers,
:time_entries, :time_entries,
:journals, :journals,
:journal_details,
:queries :queries
def setup def setup
...@@ -822,15 +821,17 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -822,15 +821,17 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal '125', issue.custom_value_for(2).value assert_equal '125', issue.custom_value_for(2).value
old_subject = issue.subject old_subject = issue.subject
new_subject = 'Subject modified by IssuesControllerTest#test_post_edit' new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
assert_difference('Journal.count') do assert_difference('IssueJournal.count') do
assert_difference('JournalDetail.count', 2) do put :update, :id => 1, :issue => {:subject => new_subject,
put :update, :id => 1, :issue => {:subject => new_subject, :priority_id => '6',
:priority_id => '6', :category_id => '1' # no change
:category_id => '1' # no change }
}
end
end end
assert issue.current_journal.changes.has_key? "subject"
assert issue.current_journal.changes.has_key? "priority_id"
assert !issue.current_journal.changes.has_key?("category_id")
assert_redirected_to :action => 'show', :id => '1' assert_redirected_to :action => 'show', :id => '1'
issue.reload issue.reload
assert_equal new_subject, issue.subject assert_equal new_subject, issue.subject
...@@ -846,17 +847,21 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -846,17 +847,21 @@ class IssuesControllerTest < ActionController::TestCase
def test_put_update_with_custom_field_change def test_put_update_with_custom_field_change
@request.session[:user_id] = 2 @request.session[:user_id] = 2
issue = Issue.find(1) issue = Issue.find(1)
ActionMailer::Base.deliveries.clear
assert_equal '125', issue.custom_value_for(2).value assert_equal '125', issue.custom_value_for(2).value
assert_difference('Journal.count') do assert_difference('Journal.count') do
assert_difference('JournalDetail.count', 3) do put :update, :id => 1, :issue => {:subject => 'Custom field change',
put :update, :id => 1, :issue => {:subject => 'Custom field change', :priority_id => '6',
:priority_id => '6', :category_id => '1', # no change
:category_id => '1', # no change :custom_field_values => { '2' => 'New custom value' }
:custom_field_values => { '2' => 'New custom value' } }
}
end
end end
assert issue.current_journal.changes.has_key? "subject"
assert issue.current_journal.changes.has_key? "priority_id"
assert !issue.current_journal.changes.has_key?("category_id")
assert issue.current_journal.changes.has_key? "custom_values2"
assert_redirected_to :action => 'show', :id => '1' assert_redirected_to :action => 'show', :id => '1'
issue.reload issue.reload
assert_equal 'New custom value', issue.custom_value_for(2).value assert_equal 'New custom value', issue.custom_value_for(2).value
...@@ -931,10 +936,6 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -931,10 +936,6 @@ class IssuesControllerTest < ActionController::TestCase
def test_put_update_with_attachment_only def test_put_update_with_attachment_only
set_tmp_attachments_directory set_tmp_attachments_directory
# Delete all fixtured journals, a race condition can occur causing the wrong
# journal to get fetched in the next find.
Journal.delete_all
# anonymous user # anonymous user
put :update, put :update,
...@@ -942,10 +943,10 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -942,10 +943,10 @@ class IssuesControllerTest < ActionController::TestCase
:notes => '', :notes => '',
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
assert_redirected_to :action => 'show', :id => '1' assert_redirected_to :action => 'show', :id => '1'
j = Issue.find(1).journals.find(:first, :order => 'id DESC') j = Issue.find(1).last_journal
assert j.notes.blank? assert j.notes.blank?
assert_equal 1, j.details.size assert_equal 1, j.details.size
assert_equal 'testfile.txt', j.details.first.value assert_equal 'testfile.txt', j.value(j.details.first)
assert_equal User.anonymous, j.user assert_equal User.anonymous, j.user
mail = ActionMailer::Base.deliveries.last mail = ActionMailer::Base.deliveries.last
...@@ -983,7 +984,6 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -983,7 +984,6 @@ class IssuesControllerTest < ActionController::TestCase
assert_redirected_to :action => 'show', :id => '1' assert_redirected_to :action => 'show', :id => '1'
issue.reload issue.reload
assert issue.journals.empty?
# No email should be sent # No email should be sent
assert ActionMailer::Base.deliveries.empty? assert ActionMailer::Base.deliveries.empty?
end end
...@@ -1109,7 +1109,7 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -1109,7 +1109,7 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id} assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
issue = Issue.find(1) issue = Issue.find(1)
journal = issue.journals.find(:first, :order => 'created_on DESC') journal = issue.journals.find(:first, :order => 'created_at DESC')
assert_equal '125', issue.custom_value_for(2).value assert_equal '125', issue.custom_value_for(2).value
assert_equal 'Bulk editing', journal.notes assert_equal 'Bulk editing', journal.notes
assert_equal 1, journal.details.size assert_equal 1, journal.details.size
...@@ -1128,7 +1128,7 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -1128,7 +1128,7 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id) assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
issue = Issue.find(1) issue = Issue.find(1)
journal = issue.journals.find(:first, :order => 'created_on DESC') journal = issue.journals.find(:first, :order => 'created_at DESC')
assert_equal '125', issue.custom_value_for(2).value assert_equal '125', issue.custom_value_for(2).value
assert_equal 'Bulk editing', journal.notes assert_equal 'Bulk editing', journal.notes
assert_equal 1, journal.details.size assert_equal 1, journal.details.size
...@@ -1190,11 +1190,11 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -1190,11 +1190,11 @@ class IssuesControllerTest < ActionController::TestCase
assert_response 302 assert_response 302
issue = Issue.find(1) issue = Issue.find(1)
journal = issue.journals.find(:first, :order => 'created_on DESC') journal = issue.journals.last
assert_equal '777', issue.custom_value_for(2).value assert_equal '777', issue.custom_value_for(2).value
assert_equal 1, journal.details.size assert_equal 1, journal.details.size
assert_equal '125', journal.details.first.old_value assert_equal '125', journal.old_value(journal.details.first)
assert_equal '777', journal.details.first.value assert_equal '777', journal.value(journal.details.first)
end end
def test_bulk_update_unassign def test_bulk_update_unassign
...@@ -1292,4 +1292,12 @@ class IssuesControllerTest < ActionController::TestCase ...@@ -1292,4 +1292,12 @@ class IssuesControllerTest < ActionController::TestCase
:child => {:tag => 'form', :child => {:tag => 'form',
:child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}} :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
end end
def test_reply_to_note
@request.session[:user_id] = 2
get :edit, :id => 1, :journal_id => 1
assert_response :success
assert_select_rjs :show, "update"
end
end end
...@@ -43,7 +43,6 @@ class IssuesControllerTransactionTest < ActionController::TestCase ...@@ -43,7 +43,6 @@ class IssuesControllerTransactionTest < ActionController::TestCase
:custom_fields_trackers, :custom_fields_trackers,
:time_entries, :time_entries,
:journals, :journals,
:journal_details,
:queries :queries
self.use_transactional_fixtures = false self.use_transactional_fixtures = false
......
...@@ -22,8 +22,8 @@ require 'journals_controller' ...@@ -22,8 +22,8 @@ require 'journals_controller'
class JournalsController; def rescue_action(e) raise e end; end class JournalsController; def rescue_action(e) raise e end; end
class JournalsControllerTest < ActionController::TestCase class JournalsControllerTest < ActionController::TestCase
fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :enabled_modules
def setup def setup
@controller = JournalsController.new @controller = JournalsController.new
@request = ActionController::TestRequest.new @request = ActionController::TestRequest.new
......
...@@ -22,7 +22,7 @@ require 'projects_controller' ...@@ -22,7 +22,7 @@ require 'projects_controller'
class ProjectsController; def rescue_action(e) raise e end; end class ProjectsController; def rescue_action(e) raise e end; end
class ProjectsControllerTest < ActionController::TestCase class ProjectsControllerTest < ActionController::TestCase
fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals,
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
:attachments, :custom_fields, :custom_values, :time_entries :attachments, :custom_fields, :custom_values, :time_entries
......
...@@ -38,7 +38,7 @@ class SearchControllerTest < ActionController::TestCase ...@@ -38,7 +38,7 @@ class SearchControllerTest < ActionController::TestCase
assert assigns(:results).include?(Changeset.find(101)) assert assigns(:results).include?(Changeset.find(101))
assert_tag :dt, :attributes => { :class => /issue/ }, assert_tag :dt, :attributes => { :class => /issue/ },
:child => { :tag => 'a', :content => /Add ingredients categories/ }, :child => { :tag => 'a', :content => /Add ingredients categories/ },
:sibling => { :tag => 'dd', :content => /should be classified by categories/ } :sibling => { :tag => 'dd', :content => /A comment with inline image: !picture.jpg!/ }
assert assigns(:results_by_type).is_a?(Hash) assert assigns(:results_by_type).is_a?(Hash)
assert_equal 5, assigns(:results_by_type)['changesets'] assert_equal 5, assigns(:results_by_type)['changesets']
......
...@@ -22,8 +22,8 @@ require 'wiki_controller' ...@@ -22,8 +22,8 @@ require 'wiki_controller'
class WikiController; def rescue_action(e) raise e end; end class WikiController; def rescue_action(e) raise e end; end
class WikiControllerTest < ActionController::TestCase class WikiControllerTest < ActionController::TestCase
fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :attachments fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules, :wikis, :wiki_pages, :wiki_contents, :journals, :attachments
def setup def setup
@controller = WikiController.new @controller = WikiController.new
@request = ActionController::TestRequest.new @request = ActionController::TestRequest.new
...@@ -83,8 +83,7 @@ class WikiControllerTest < ActionController::TestCase ...@@ -83,8 +83,7 @@ class WikiControllerTest < ActionController::TestCase
put :update, :project_id => 1, put :update, :project_id => 1,
:id => 'New page', :id => 'New page',
:content => {:comments => 'Created the page', :content => {:comments => 'Created the page',
:text => "h1. New page\n\nThis is a new page", :text => "h1. New page\n\nThis is a new page" }
:version => 0}
assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page' assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
page = Project.find(1).wiki.find_page('New page') page = Project.find(1).wiki.find_page('New page')
assert !page.new_record? assert !page.new_record?
...@@ -100,7 +99,7 @@ class WikiControllerTest < ActionController::TestCase ...@@ -100,7 +99,7 @@ class WikiControllerTest < ActionController::TestCase
:id => 'New page', :id => 'New page',
:content => {:comments => 'Created the page', :content => {:comments => 'Created the page',
:text => "h1. New page\n\nThis is a new page", :text => "h1. New page\n\nThis is a new page",
:version => 0}, :lock_version => 0},
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
end end
end end
......
...@@ -39,7 +39,6 @@ class ApiTest::IssuesTest < ActionController::IntegrationTest ...@@ -39,7 +39,6 @@ class ApiTest::IssuesTest < ActionController::IntegrationTest
:custom_fields_trackers, :custom_fields_trackers,
:time_entries, :time_entries,
:journals, :journals,
:journal_details,
:queries :queries
def setup def setup
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
require File.expand_path('../../../test_helper', __FILE__) require File.expand_path('../../../test_helper', __FILE__)
class ApiTest::ProjectsTest < ActionController::IntegrationTest class ApiTest::ProjectsTest < ActionController::IntegrationTest
fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals,
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
:attachments, :custom_fields, :custom_values, :time_entries :attachments, :custom_fields, :custom_values, :time_entries
......
...@@ -158,21 +158,17 @@ class ActiveSupport::TestCase ...@@ -158,21 +158,17 @@ class ActiveSupport::TestCase
end end
should "use the new value's name" do should "use the new value's name" do
@detail = JournalDetail.generate!(:property => 'attr', @detail = IssueJournal.generate(:version => 1, :journaled => Issue.last)
:old_value => @old_value.id, @detail.update_attribute(:changes, {prop_key => [@old_value.id, @new_value.id]}.to_yaml)
:value => @new_value.id,
:prop_key => prop_key) assert_match @new_value.class.find(@new_value.id).name, @detail.render_detail(prop_key, true)
assert_match @new_value.name, show_detail(@detail, true)
end end
should "use the old value's name" do should "use the old value's name" do
@detail = JournalDetail.generate!(:property => 'attr', @detail = IssueJournal.generate(:version => 1, :journaled => Issue.last)
:old_value => @old_value.id, @detail.update_attribute(:changes, {prop_key => [@old_value.id, @new_value.id]}.to_yaml)
:value => @new_value.id,
:prop_key => prop_key) assert_match @old_value.class.find(@old_value.id).name, @detail.render_detail(prop_key, true)
assert_match @old_value.name, show_detail(@detail, true)
end end
end end
end end
......
...@@ -18,11 +18,16 @@ ...@@ -18,11 +18,16 @@
require File.expand_path('../../test_helper', __FILE__) require File.expand_path('../../test_helper', __FILE__)
class ActivityTest < ActiveSupport::TestCase class ActivityTest < ActiveSupport::TestCase
fixtures :projects, :versions, :attachments, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details, fixtures :projects, :versions, :attachments, :users, :roles, :members, :member_roles, :issues, :journals,
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages
def setup def setup
@project = Project.find(1) @project = Project.find(1)
[1,4,5,6].each do |issue_id|
i = Issue.find(issue_id)
i.init_journal(User.current, "A journal to find")
i.save!
end
end end
def test_activity_without_subprojects def test_activity_without_subprojects
...@@ -51,7 +56,7 @@ class ActivityTest < ActiveSupport::TestCase ...@@ -51,7 +56,7 @@ class ActivityTest < ActiveSupport::TestCase
assert events.include?(Issue.find(1)) assert events.include?(Issue.find(1))
assert events.include?(Message.find(5)) assert events.include?(Message.find(5))
# Issue of a private project # Issue of a private project
assert !events.include?(Issue.find(4)) assert !events.include?(Issue.find(6))
end end
def test_global_activity_logged_user def test_global_activity_logged_user
...@@ -60,7 +65,7 @@ class ActivityTest < ActiveSupport::TestCase ...@@ -60,7 +65,7 @@ class ActivityTest < ActiveSupport::TestCase
assert events.include?(Issue.find(1)) assert events.include?(Issue.find(1))
# Issue of a private project the user belongs to # Issue of a private project the user belongs to
assert events.include?(Issue.find(4)) assert events.include?(Issue.find(6))
end end
def test_user_activity def test_user_activity
...@@ -78,15 +83,18 @@ class ActivityTest < ActiveSupport::TestCase ...@@ -78,15 +83,18 @@ class ActivityTest < ActiveSupport::TestCase
events = f.events events = f.events
assert_kind_of Array, events assert_kind_of Array, events
assert events.include?(Attachment.find_by_container_type_and_container_id('Project', 1)) assert events.include?(Attachment.find_by_container_type_and_container_id('Project', 1).last_journal)
assert events.include?(Attachment.find_by_container_type_and_container_id('Version', 1)) assert events.include?(Attachment.find_by_container_type_and_container_id('Version', 1).last_journal)
assert_equal [Attachment], events.collect(&:class).uniq assert_equal [Attachment], events.collect(&:journaled).collect(&:class).uniq
assert_equal %w(Project Version), events.collect(&:container_type).uniq.sort assert_equal %w(Project Version), events.collect(&:journaled).collect(&:container_type).uniq.sort
end end
private private
def find_events(user, options={}) def find_events(user, options={})
Redmine::Activity::Fetcher.new(user, options).events(Date.today - 30, Date.today + 1) events = Redmine::Activity::Fetcher.new(user, options).events(Date.today - 30, Date.today + 1)
# Because events are provided by the journals, but we want to test for
# their targets here, transform that
events.group_by(&:journaled).keys
end end
end end
...@@ -43,29 +43,33 @@ class IssuesHelperTest < HelperTestCase ...@@ -43,29 +43,33 @@ class IssuesHelperTest < HelperTestCase
def request def request
@request ||= ActionController::TestRequest.new @request ||= ActionController::TestRequest.new
end end
def show_detail(journal, detail, html = true)
journal.render_detail(detail, html)
end
context "IssuesHelper#show_detail" do context "IssuesHelper#show_detail" do
context "with no_html" do context "with no_html" do
should 'show a changing attribute' do should 'show a changing attribute' do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio') @journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
assert_equal "% Done changed from 40 to 100", show_detail(@detail, true) assert_equal "% Done changed from 40 to 100", show_detail(@journal, @journal.details.to_a.first, true)
end end
should 'show a new attribute' do should 'show a new attribute' do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio') @journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
assert_equal "% Done set to 100", show_detail(@detail, true) assert_equal "% Done set to 100", show_detail(@journal, @journal.details.to_a.first, true)
end end
should 'show a deleted attribute' do should 'show a deleted attribute' do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio') @journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
assert_equal "% Done deleted (50)", show_detail(@detail, true) assert_equal "% Done deleted (50)", show_detail(@journal, @journal.details.to_a.first, true)
end end
end end
context "with html" do context "with html" do
should 'show a changing attribute with HTML highlights' do should 'show a changing attribute with HTML highlights' do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio') @journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
@response.body = show_detail(@detail, false) @response.body = show_detail(@journal, @journal.details.to_a.first, false)
assert_select 'strong', :text => '% Done' assert_select 'strong', :text => '% Done'
assert_select 'i', :text => '40' assert_select 'i', :text => '40'
...@@ -73,16 +77,16 @@ class IssuesHelperTest < HelperTestCase ...@@ -73,16 +77,16 @@ class IssuesHelperTest < HelperTestCase
end end
should 'show a new attribute with HTML highlights' do should 'show a new attribute with HTML highlights' do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio') @journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
@response.body = show_detail(@detail, false) @response.body = show_detail(@journal, @journal.details.to_a.first, false)
assert_select 'strong', :text => '% Done' assert_select 'strong', :text => '% Done'
assert_select 'i', :text => '100' assert_select 'i', :text => '100'
end end
should 'show a deleted attribute with HTML highlights' do should 'show a deleted attribute with HTML highlights' do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio') @journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
@response.body = show_detail(@detail, false) @response.body = show_detail(@journal, @journal.details.to_a.first, false)
assert_select 'strong', :text => '% Done' assert_select 'strong', :text => '% Done'
assert_select 'strike' do assert_select 'strike' do
...@@ -93,25 +97,25 @@ class IssuesHelperTest < HelperTestCase ...@@ -93,25 +97,25 @@ class IssuesHelperTest < HelperTestCase
context "with a start_date attribute" do context "with a start_date attribute" do
should "format the current date" do should "format the current date" do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'start_date') @journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
assert_match "01/31/2010", show_detail(@detail, true) assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
end end
should "format the old date" do should "format the old date" do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'start_date') @journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
assert_match "01/01/2010", show_detail(@detail, true) assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
end end
end end
context "with a due_date attribute" do context "with a due_date attribute" do
should "format the current date" do should "format the current date" do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'due_date') @journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
assert_match "01/31/2010", show_detail(@detail, true) assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
end end
should "format the old date" do should "format the old date" do
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'due_date') @journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
assert_match "01/01/2010", show_detail(@detail, true) assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
end end
end end
......
...@@ -603,18 +603,19 @@ class IssueTest < ActiveSupport::TestCase ...@@ -603,18 +603,19 @@ class IssueTest < ActiveSupport::TestCase
assert_difference 'Journal.count' do assert_difference 'Journal.count' do
assert i.save assert i.save
end end
assert i.current_journal.changes.has_key? "subject"
assert i.current_journal.changes.has_key? "done_ratio"
# 1 more change # 1 more change
i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id]) i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
assert_no_difference 'Journal.count' do assert_difference 'Journal.count' do
assert_difference 'JournalDetail.count', 1 do i.save
i.save
end
end end
assert i.current_journal.changes.has_key? "priority_id"
# no more change # no more change
assert_no_difference 'Journal.count' do assert_no_difference 'Journal.count' do
assert_no_difference 'JournalDetail.count' do i.save
i.save
end
end end
end end
......
...@@ -18,11 +18,15 @@ ...@@ -18,11 +18,15 @@
require File.expand_path('../../test_helper', __FILE__) require File.expand_path('../../test_helper', __FILE__)
class JournalObserverTest < ActiveSupport::TestCase class JournalObserverTest < ActiveSupport::TestCase
fixtures :issues, :issue_statuses, :journals, :journal_details fixtures :issues, :issue_statuses, :journals
def setup def setup
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
@journal = Journal.find 1 @journal = Journal.find 1
if (i = Issue.find(:first)).journals.empty?
i.init_journal(User.current, 'Creation') # Make sure the initial journal is created
i.save
end
end end
# context: issue_updated notified_events # context: issue_updated notified_events
...@@ -30,9 +34,9 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -30,9 +34,9 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = ['issue_updated'] Setting.notified_events = ['issue_updated']
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
journal = issue.init_journal(user, issue) issue.init_journal(user)
assert journal.save assert issue.send(:create_journal)
assert_equal 1, ActionMailer::Base.deliveries.size assert_equal 1, ActionMailer::Base.deliveries.size
end end
...@@ -40,9 +44,9 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -40,9 +44,9 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = [] Setting.notified_events = []
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
journal = issue.init_journal(user, issue) issue.init_journal(user)
assert journal.save assert issue.save
assert_equal 0, ActionMailer::Base.deliveries.size assert_equal 0, ActionMailer::Base.deliveries.size
end end
...@@ -51,10 +55,9 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -51,10 +55,9 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = ['issue_note_added'] Setting.notified_events = ['issue_note_added']
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
journal = issue.init_journal(user, issue) issue.init_journal(user, 'This update has a note')
journal.notes = 'This update has a note'
assert journal.save assert issue.save
assert_equal 1, ActionMailer::Base.deliveries.size assert_equal 1, ActionMailer::Base.deliveries.size
end end
...@@ -62,10 +65,9 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -62,10 +65,9 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = [] Setting.notified_events = []
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
journal = issue.init_journal(user, issue) issue.init_journal(user, 'This update has a note')
journal.notes = 'This update has a note'
assert journal.save assert issue.save
assert_equal 0, ActionMailer::Base.deliveries.size assert_equal 0, ActionMailer::Base.deliveries.size
end end
...@@ -74,7 +76,7 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -74,7 +76,7 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = ['issue_status_updated'] Setting.notified_events = ['issue_status_updated']
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
issue.init_journal(user, issue) issue.init_journal(user)
issue.status = IssueStatus.last issue.status = IssueStatus.last
assert issue.save assert issue.save
...@@ -85,7 +87,7 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -85,7 +87,7 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = [] Setting.notified_events = []
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
issue.init_journal(user, issue) issue.init_journal(user)
issue.status = IssueStatus.last issue.status = IssueStatus.last
assert issue.save assert issue.save
...@@ -97,7 +99,7 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -97,7 +99,7 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = ['issue_priority_updated'] Setting.notified_events = ['issue_priority_updated']
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
issue.init_journal(user, issue) issue.init_journal(user)
issue.priority = IssuePriority.last issue.priority = IssuePriority.last
assert issue.save assert issue.save
...@@ -108,7 +110,7 @@ class JournalObserverTest < ActiveSupport::TestCase ...@@ -108,7 +110,7 @@ class JournalObserverTest < ActiveSupport::TestCase
Setting.notified_events = [] Setting.notified_events = []
issue = Issue.find(:first) issue = Issue.find(:first)
user = User.find(:first) user = User.find(:first)
issue.init_journal(user, issue) issue.init_journal(user)
issue.priority = IssuePriority.last issue.priority = IssuePriority.last
assert issue.save assert issue.save
......
...@@ -18,14 +18,14 @@ ...@@ -18,14 +18,14 @@
require File.expand_path('../../test_helper', __FILE__) require File.expand_path('../../test_helper', __FILE__)
class JournalTest < ActiveSupport::TestCase class JournalTest < ActiveSupport::TestCase
fixtures :issues, :issue_statuses, :journals, :journal_details fixtures :issues, :issue_statuses, :journals
def setup def setup
@journal = Journal.find 1 @journal = IssueJournal.first
end end
def test_journalized_is_an_issue def test_journalized_is_an_issue
issue = @journal.issue issue = @journal.journalized
assert_kind_of Issue, issue assert_kind_of Issue, issue
assert_equal 1, issue.id assert_equal 1, issue.id
end end
...@@ -40,10 +40,14 @@ class JournalTest < ActiveSupport::TestCase ...@@ -40,10 +40,14 @@ class JournalTest < ActiveSupport::TestCase
def test_create_should_send_email_notification def test_create_should_send_email_notification
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
issue = Issue.find(:first) issue = Issue.find(:first)
if issue.journals.empty?
issue.init_journal(User.current, "This journal represents the creational journal version 1")
issue.save
end
user = User.find(:first) user = User.find(:first)
journal = issue.init_journal(user, issue)
assert journal.save assert_equal 0, ActionMailer::Base.deliveries.size
issue.update_attribute(:subject, "New subject to trigger automatic journal entry")
assert_equal 1, ActionMailer::Base.deliveries.size assert_equal 1, ActionMailer::Base.deliveries.size
end end
......
...@@ -308,7 +308,7 @@ class MailHandlerTest < ActiveSupport::TestCase ...@@ -308,7 +308,7 @@ class MailHandlerTest < ActiveSupport::TestCase
# This email contains: 'Status: Resolved' # This email contains: 'Status: Resolved'
journal = submit_email('ticket_reply_with_status.eml') journal = submit_email('ticket_reply_with_status.eml')
assert journal.is_a?(Journal) assert journal.is_a?(Journal)
issue = Issue.find(journal.issue.id) issue = Issue.find(journal.journalized.id)
assert_equal User.find_by_login('jsmith'), journal.user assert_equal User.find_by_login('jsmith'), journal.user
assert_equal Issue.find(2), journal.journalized assert_equal Issue.find(2), journal.journalized
assert_match /This is reply/, journal.notes assert_match /This is reply/, journal.notes
......
...@@ -20,8 +20,8 @@ require File.expand_path('../../test_helper', __FILE__) ...@@ -20,8 +20,8 @@ require File.expand_path('../../test_helper', __FILE__)
class MailerTest < ActiveSupport::TestCase class MailerTest < ActiveSupport::TestCase
include Redmine::I18n include Redmine::I18n
include ActionController::Assertions::SelectorAssertions include ActionController::Assertions::SelectorAssertions
fixtures :projects, :enabled_modules, :issues, :users, :members, :member_roles, :roles, :documents, :attachments, :news, :tokens, :journals, :journal_details, :changesets, :trackers, :issue_statuses, :enumerations, :messages, :boards, :repositories fixtures :projects, :enabled_modules, :issues, :users, :members, :member_roles, :roles, :documents, :attachments, :news, :tokens, :journals, :changesets, :trackers, :issue_statuses, :enumerations, :messages, :boards, :repositories
def setup def setup
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
Setting.host_name = 'mydomain.foo' Setting.host_name = 'mydomain.foo'
...@@ -171,7 +171,7 @@ class MailerTest < ActiveSupport::TestCase ...@@ -171,7 +171,7 @@ class MailerTest < ActiveSupport::TestCase
mail = ActionMailer::Base.deliveries.last mail = ActionMailer::Base.deliveries.last
assert_not_nil mail assert_not_nil mail
assert_equal Mailer.message_id_for(journal), mail.message_id assert_equal Mailer.message_id_for(journal), mail.message_id
assert_equal Mailer.message_id_for(journal.issue), mail.references.first.to_s assert_equal Mailer.message_id_for(journal.journaled), mail.references.first.to_s
end end
def test_message_posted_message_id def test_message_posted_message_id
......
...@@ -97,7 +97,7 @@ class RepositoryTest < ActiveSupport::TestCase ...@@ -97,7 +97,7 @@ class RepositoryTest < ActiveSupport::TestCase
assert_equal [101], fixed_issue.changeset_ids assert_equal [101], fixed_issue.changeset_ids
# issue change # issue change
journal = fixed_issue.journals.find(:first, :order => 'created_on desc') journal = fixed_issue.journals.last
assert_equal User.find_by_login('dlopper'), journal.user assert_equal User.find_by_login('dlopper'), journal.user
assert_equal 'Applied in changeset r2.', journal.notes assert_equal 'Applied in changeset r2.', journal.notes
......
...@@ -27,7 +27,6 @@ class SearchTest < ActiveSupport::TestCase ...@@ -27,7 +27,6 @@ class SearchTest < ActiveSupport::TestCase
:issues, :issues,
:trackers, :trackers,
:journals, :journals,
:journal_details,
:repositories, :repositories,
:changesets :changesets
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
require File.expand_path('../../test_helper', __FILE__) require File.expand_path('../../test_helper', __FILE__)
class WikiContentTest < ActiveSupport::TestCase class WikiContentTest < ActiveSupport::TestCase
fixtures :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :users fixtures :wikis, :wiki_pages, :wiki_contents, :journals, :users
def setup def setup
@wiki = Wiki.find(1) @wiki = Wiki.find(1)
...@@ -72,9 +72,9 @@ class WikiContentTest < ActiveSupport::TestCase ...@@ -72,9 +72,9 @@ class WikiContentTest < ActiveSupport::TestCase
end end
def test_fetch_history def test_fetch_history
assert !@page.content.versions.empty? assert !@page.content.journals.empty?
@page.content.versions.each do |version| @page.content.journals.each do |journal|
assert_kind_of String, version.text assert_kind_of String, journal.text
end end
end end
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
require File.expand_path('../../test_helper', __FILE__) require File.expand_path('../../test_helper', __FILE__)
class WikiPageTest < ActiveSupport::TestCase class WikiPageTest < ActiveSupport::TestCase
fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :journals
def setup def setup
@wiki = Wiki.find(1) @wiki = Wiki.find(1)
...@@ -101,11 +101,14 @@ class WikiPageTest < ActiveSupport::TestCase ...@@ -101,11 +101,14 @@ class WikiPageTest < ActiveSupport::TestCase
def test_destroy def test_destroy
page = WikiPage.find(1) page = WikiPage.find(1)
content_ids = WikiContent.find_all_by_page_id(1).collect(&:id)
page.destroy page.destroy
assert_nil WikiPage.find_by_id(1) assert_nil WikiPage.find_by_id(1)
# make sure that page content and its history are deleted # make sure that page content and its history are deleted
assert WikiContent.find_all_by_page_id(1).empty? assert WikiContent.find_all_by_page_id(1).empty?
assert WikiContent.versioned_class.find_all_by_page_id(1).empty? content_ids.each do |wiki_content_id|
assert WikiContent.journal_class.find_all_by_journaled_id(wiki_content_id).empty?
end
end end
def test_destroy_should_not_nullify_children def test_destroy_should_not_nullify_children
......
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
require File.expand_path('../../test_helper', __FILE__) require File.expand_path('../../test_helper', __FILE__)
class WikiTest < ActiveSupport::TestCase class WikiTest < ActiveSupport::TestCase
fixtures :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions fixtures :wikis, :wiki_pages, :wiki_contents, :journals
def test_create def test_create
wiki = Wiki.new(:project => Project.find(2)) wiki = Wiki.new(:project => Project.find(2))
assert !wiki.save assert !wiki.save
......
...@@ -68,12 +68,12 @@ module Redmine ...@@ -68,12 +68,12 @@ module Redmine
scope_options[:conditions] = cond.conditions scope_options[:conditions] = cond.conditions
if options[:limit] if options[:limit]
# id and creation time should be in same order in most cases # id and creation time should be in same order in most cases
scope_options[:order] = "#{table_name}.id DESC" scope_options[:order] = "#{journal_class.table_name}.id DESC"
scope_options[:limit] = options[:limit] scope_options[:limit] = options[:limit]
end end
with_scope(:find => scope_options) do journal_class.with_scope(:find => scope_options) do
find(:all, provider_options[:find_options].dup) journal_class.find(:all, provider_options[:find_options].dup)
end end
end end
end end
......
Subproject commit 6683304d3cca187795b344f0b831edbc7cf709c0
*SVN* (version numbers are overrated)
* (5 Oct 2006) Allow customization of #versions association options [Dan Peterson]
*0.5.1*
* (8 Aug 2006) Versioned models now belong to the unversioned model. @article_version.article.class => Article [Aslak Hellesoy]
*0.5* # do versions even matter for plugins?
* (21 Apr 2006) Added without_locking and without_revision methods.
Foo.without_revision do
@foo.update_attributes ...
end
*0.4*
* (28 March 2006) Rename non_versioned_fields to non_versioned_columns (old one is kept for compatibility).
* (28 March 2006) Made explicit documentation note that string column names are required for non_versioned_columns.
*0.3.1*
* (7 Jan 2006) explicitly set :foreign_key option for the versioned model's belongs_to assocation for STI [Caged]
* (7 Jan 2006) added tests to prove has_many :through joins work
*0.3*
* (2 Jan 2006) added ability to share a mixin with versioned class
* (2 Jan 2006) changed the dynamic version model to MyModel::Version
*0.2.4*
* (27 Nov 2005) added note about possible destructive behavior of if_changed? [Michael Schuerig]
*0.2.3*
* (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig]
* (12 Nov 2005) updated tests to use ActiveRecord Schema
*0.2.2*
* (3 Nov 2005) added documentation note to #acts_as_versioned [Martin Jul]
*0.2.1*
* (6 Oct 2005) renamed dirty? to changed? to keep it uniform. it was aliased to keep it backwards compatible.
*0.2*
* (6 Oct 2005) added find_versions and find_version class methods.
* (6 Oct 2005) removed transaction from create_versioned_table().
this way you can specify your own transaction around a group of operations.
* (30 Sep 2005) fixed bug where find_versions() would order by 'version' twice. (found by Joe Clark)
* (26 Sep 2005) added :sequence_name option to acts_as_versioned to set the sequence name on the versioned model
*0.1.3* (18 Sep 2005)
* First RubyForge release
*0.1.2*
* check if module is already included when acts_as_versioned is called
*0.1.1*
* Adding tests and rdocs
*0.1*
* Initial transfer from Rails ticket: http://dev.rubyonrails.com/ticket/1974
\ No newline at end of file
Copyright (c) 2005 Rick Olson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
= acts_as_versioned
This library adds simple versioning to an ActiveRecord module. ActiveRecord is required.
== Resources
Install
* gem install acts_as_versioned
Rubyforge project
* http://rubyforge.org/projects/ar-versioned
RDocs
* http://ar-versioned.rubyforge.org
Subversion
* http://techno-weenie.net/svn/projects/acts_as_versioned
Collaboa
* http://collaboa.techno-weenie.net/repository/browse/acts_as_versioned
Special thanks to Dreamer on ##rubyonrails for help in early testing. His ServerSideWiki (http://serversidewiki.com)
was the first project to use acts_as_versioned <em>in the wild</em>.
\ No newline at end of file
== Creating the test database
The default name for the test databases is "activerecord_versioned". If you
want to use another database name then be sure to update the connection
adapter setups you want to test with in test/connections/<your database>/connection.rb.
When you have the database online, you can import the fixture tables with
the test/fixtures/db_definitions/*.sql files.
Make sure that you create database objects with the same user that you specified in i
connection.rb otherwise (on Postgres, at least) tests for default values will fail.
== Running with Rake
The easiest way to run the unit tests is through Rake. The default task runs
the entire test suite for all the adapters. You can also run the suite on just
one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite,
or test_postresql. For more information, checkout the full array of rake tasks with "rake -T"
Rake can be found at http://rake.rubyforge.org
== Running by hand
Unit tests are located in test directory. If you only want to run a single test suite,
or don't want to bother with Rake, you can do so with something like:
cd test; ruby -I "connections/native_mysql" base_test.rb
That'll run the base suite using the MySQL-Ruby adapter. Change the adapter
and test suite name as needed.
== Faster tests
If you are using a database that supports transactions, you can set the
"AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures.
This gives a very large speed boost. With rake:
rake AR_TX_FIXTURES=yes
Or, by hand:
AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb
require 'rubygems'
Gem::manage_gems
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/testtask'
require 'rake/contrib/rubyforgepublisher'
PKG_NAME = 'acts_as_versioned'
PKG_VERSION = '0.3.1'
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
PROD_HOST = "technoweenie@bidwell.textdrive.com"
RUBY_FORGE_PROJECT = 'ar-versioned'
RUBY_FORGE_USER = 'technoweenie'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the calculations plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the calculations plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "#{PKG_NAME} -- Simple versioning with active record models"
rdoc.options << '--line-numbers --inline-source'
rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS')
rdoc.rdoc_files.include('lib/**/*.rb')
end
spec = Gem::Specification.new do |s|
s.name = PKG_NAME
s.version = PKG_VERSION
s.platform = Gem::Platform::RUBY
s.summary = "Simple versioning with active record models"
s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS)
s.files.delete "acts_as_versioned_plugin.sqlite.db"
s.files.delete "acts_as_versioned_plugin.sqlite3.db"
s.files.delete "test/debug.log"
s.require_path = 'lib'
s.autorequire = 'acts_as_versioned'
s.has_rdoc = true
s.test_files = Dir['test/**/*_test.rb']
s.add_dependency 'activerecord', '>= 1.10.1'
s.add_dependency 'activesupport', '>= 1.1.1'
s.author = "Rick Olson"
s.email = "technoweenie@gmail.com"
s.homepage = "http://techno-weenie.net"
end
Rake::GemPackageTask.new(spec) do |pkg|
pkg.need_tar = true
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
end
desc 'Publish the gem and API docs'
task :publish => [:pdoc, :rubyforge_upload]
desc "Publish the release files to RubyForge."
task :rubyforge_upload => :package do
files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
if RUBY_FORGE_PROJECT then
require 'net/http'
require 'open-uri'
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
project_data = open(project_uri) { |data| data.read }
group_id = project_data[/[?&]group_id=(\d+)/, 1]
raise "Couldn't get group id" unless group_id
# This echos password to shell which is a bit sucky
if ENV["RUBY_FORGE_PASSWORD"]
password = ENV["RUBY_FORGE_PASSWORD"]
else
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
password = STDIN.gets.chomp
end
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
data = [
"login=1",
"form_loginname=#{RUBY_FORGE_USER}",
"form_pw=#{password}"
].join("&")
http.post("/account/login.php", data)
end
cookie = login_response["set-cookie"]
raise "Login failed" unless cookie
headers = { "Cookie" => cookie }
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
release_data = open(release_uri, headers) { |data| data.read }
package_id = release_data[/[?&]package_id=(\d+)/, 1]
raise "Couldn't get package id" unless package_id
first_file = true
release_id = ""
files.each do |filename|
basename = File.basename(filename)
file_ext = File.extname(filename)
file_data = File.open(filename, "rb") { |file| file.read }
puts "Releasing #{basename}..."
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
type_map = {
".zip" => "3000",
".tgz" => "3110",
".gz" => "3110",
".gem" => "1400"
}; type_map.default = "9999"
type = type_map[file_ext]
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
query_hash = if first_file then
{
"group_id" => group_id,
"package_id" => package_id,
"release_name" => PKG_FILE_NAME,
"release_date" => release_date,
"type_id" => type,
"processor_id" => "8000", # Any
"release_notes" => "",
"release_changes" => "",
"preformatted" => "1",
"submit" => "1"
}
else
{
"group_id" => group_id,
"release_id" => release_id,
"package_id" => package_id,
"step2" => "1",
"type_id" => type,
"processor_id" => "8000", # Any
"submit" => "Add This File"
}
end
query = "?" + query_hash.map do |(name, value)|
[name, URI.encode(value)].join("=")
end.join("&")
data = [
"--" + boundary,
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
"Content-Type: application/octet-stream",
"Content-Transfer-Encoding: binary",
"", file_data, ""
].join("\x0D\x0A")
release_headers = headers.merge(
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
)
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
http.post(target + query, data, release_headers)
end
if first_file then
release_id = release_response.body[/release_id=(\d+)/, 1]
raise("Couldn't get release id") unless release_id
end
first_file = false
end
end
end
\ No newline at end of file
require 'acts_as_versioned'
\ No newline at end of file
$:.unshift(File.dirname(__FILE__) + '/../../../rails/activesupport/lib')
$:.unshift(File.dirname(__FILE__) + '/../../../rails/activerecord/lib')
$:.unshift(File.dirname(__FILE__) + '/../lib')
require 'test/unit'
begin
require 'active_support'
require 'active_record'
require 'active_record/fixtures'
rescue LoadError
require 'rubygems'
retry
end
require 'acts_as_versioned'
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
load(File.dirname(__FILE__) + "/schema.rb")
# set up custom sequence on widget_versions for DBs that support sequences
if ENV['DB'] == 'postgresql'
ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil
ActiveRecord::Base.connection.remove_column :widget_versions, :id
ActiveRecord::Base.connection.execute "CREATE SEQUENCE widgets_seq START 101;"
ActiveRecord::Base.connection.execute "ALTER TABLE widget_versions ADD COLUMN id INTEGER PRIMARY KEY DEFAULT nextval('widgets_seq');"
end
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
$:.unshift(Test::Unit::TestCase.fixture_path)
class Test::Unit::TestCase #:nodoc:
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
self.use_transactional_fixtures = true
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
self.use_instantiated_fixtures = false
# Add more helper methods to be used by all tests here...
end
\ No newline at end of file
sqlite:
:adapter: sqlite
:dbfile: acts_as_versioned_plugin.sqlite.db
sqlite3:
:adapter: sqlite3
:dbfile: acts_as_versioned_plugin.sqlite3.db
postgresql:
:adapter: postgresql
:username: postgres
:password: postgres
:database: acts_as_versioned_plugin_test
:min_messages: ERROR
mysql:
:adapter: mysql
:host: localhost
:username: rails
:password:
:database: acts_as_versioned_plugin_test
\ No newline at end of file
caged:
id: 1
name: caged
mly:
id: 2
name: mly
\ No newline at end of file
class Landmark < ActiveRecord::Base
acts_as_versioned :if_changed => [ :name, :longitude, :latitude ]
end
washington:
id: 1
landmark_id: 1
version: 1
name: Washington, D.C.
latitude: 38.895
longitude: -77.036667
washington:
id: 1
name: Washington, D.C.
latitude: 38.895
longitude: -77.036667
version: 1
welcome:
id: 1
title: Welcome to the weblog
lock_version: 24
type: LockedPage
thinking:
id: 2
title: So I was thinking
lock_version: 24
type: SpecialLockedPage
welcome_1:
id: 1
page_id: 1
title: Welcome to the weblg
version: 23
version_type: LockedPage
welcome_2:
id: 2
page_id: 1
title: Welcome to the weblog
version: 24
version_type: LockedPage
thinking_1:
id: 3
page_id: 2
title: So I was thinking!!!
version: 23
version_type: SpecialLockedPage
thinking_2:
id: 4
page_id: 2
title: So I was thinking
version: 24
version_type: SpecialLockedPage
class AddVersionedTables < ActiveRecord::Migration
def self.up
create_table("things") do |t|
t.column :title, :text
end
Thing.create_versioned_table
end
def self.down
Thing.drop_versioned_table
drop_table "things" rescue nil
end
end
\ No newline at end of file
class Page < ActiveRecord::Base
belongs_to :author
has_many :authors, :through => :versions, :order => 'name'
belongs_to :revisor, :class_name => 'Author'
has_many :revisors, :class_name => 'Author', :through => :versions, :order => 'name'
acts_as_versioned :if => :feeling_good? do
def self.included(base)
base.cattr_accessor :feeling_good
base.feeling_good = true
base.belongs_to :author
base.belongs_to :revisor, :class_name => 'Author'
end
def feeling_good?
@@feeling_good == true
end
end
end
module LockedPageExtension
def hello_world
'hello_world'
end
end
class LockedPage < ActiveRecord::Base
acts_as_versioned \
:inheritance_column => :version_type,
:foreign_key => :page_id,
:table_name => :locked_pages_revisions,
:class_name => 'LockedPageRevision',
:version_column => :lock_version,
:limit => 2,
:if_changed => :title,
:extend => LockedPageExtension
end
class SpecialLockedPage < LockedPage
end
class Author < ActiveRecord::Base
has_many :pages
end
\ No newline at end of file
welcome_2:
id: 1
page_id: 1
title: Welcome to the weblog
body: Such a lovely day
version: 24
author_id: 1
revisor_id: 1
welcome_1:
id: 2
page_id: 1
title: Welcome to the weblg
body: Such a lovely day
version: 23
author_id: 2
revisor_id: 2
welcome:
id: 1
title: Welcome to the weblog
body: Such a lovely day
version: 24
author_id: 1
revisor_id: 1
\ No newline at end of file
class Widget < ActiveRecord::Base
acts_as_versioned :sequence_name => 'widgets_seq', :association_options => {
:dependent => :nullify, :order => 'version desc'
}
non_versioned_columns << 'foo'
end
\ No newline at end of file
require File.join(File.dirname(__FILE__), 'abstract_unit')
if ActiveRecord::Base.connection.supports_migrations?
class Thing < ActiveRecord::Base
attr_accessor :version
acts_as_versioned
end
class MigrationTest < Test::Unit::TestCase
self.use_transactional_fixtures = false
def teardown
if ActiveRecord::Base.connection.respond_to?(:initialize_schema_information)
ActiveRecord::Base.connection.initialize_schema_information
ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0"
else
ActiveRecord::Base.connection.initialize_schema_migrations_table
ActiveRecord::Base.connection.assume_migrated_upto_version(0)
end
Thing.connection.drop_table "things" rescue nil
Thing.connection.drop_table "thing_versions" rescue nil
Thing.reset_column_information
end
def test_versioned_migration
assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
# take 'er up
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
t = Thing.create :title => 'blah blah', :price => 123.45, :type => 'Thing'
assert_equal 1, t.versions.size
# check that the price column has remembered its value correctly
assert_equal t.price, t.versions.first.price
assert_equal t.title, t.versions.first.title
assert_equal t[:type], t.versions.first[:type]
# make sure that the precision of the price column has been preserved
assert_equal 7, Thing::Version.columns.find{|c| c.name == "price"}.precision
assert_equal 2, Thing::Version.columns.find{|c| c.name == "price"}.scale
# now lets take 'er back down
ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/')
assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
end
end
end
ActiveRecord::Schema.define(:version => 0) do
create_table :pages, :force => true do |t|
t.column :version, :integer
t.column :title, :string, :limit => 255
t.column :body, :text
t.column :updated_on, :datetime
t.column :author_id, :integer
t.column :revisor_id, :integer
end
create_table :page_versions, :force => true do |t|
t.column :page_id, :integer
t.column :version, :integer
t.column :title, :string, :limit => 255
t.column :body, :text
t.column :updated_on, :datetime
t.column :author_id, :integer
t.column :revisor_id, :integer
end
create_table :authors, :force => true do |t|
t.column :page_id, :integer
t.column :name, :string
end
create_table :locked_pages, :force => true do |t|
t.column :lock_version, :integer
t.column :title, :string, :limit => 255
t.column :type, :string, :limit => 255
end
create_table :locked_pages_revisions, :force => true do |t|
t.column :page_id, :integer
t.column :version, :integer
t.column :title, :string, :limit => 255
t.column :version_type, :string, :limit => 255
t.column :updated_at, :datetime
end
create_table :widgets, :force => true do |t|
t.column :name, :string, :limit => 50
t.column :foo, :string
t.column :version, :integer
t.column :updated_at, :datetime
end
create_table :widget_versions, :force => true do |t|
t.column :widget_id, :integer
t.column :name, :string, :limit => 50
t.column :version, :integer
t.column :updated_at, :datetime
end
create_table :landmarks, :force => true do |t|
t.column :name, :string
t.column :latitude, :float
t.column :longitude, :float
t.column :version, :integer
end
create_table :landmark_versions, :force => true do |t|
t.column :landmark_id, :integer
t.column :name, :string
t.column :latitude, :float
t.column :longitude, :float
t.column :version, :integer
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment