Merge branch 'ticket/unstable/123-journalized' into unstable
This commit is contained in:
commit
dabe5caa1a
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
@ -17,8 +17,7 @@ class IssueMovesController < ApplicationController
|
||||
moved_issues = []
|
||||
@issues.each do |issue|
|
||||
issue.reload
|
||||
issue.init_journal(User.current)
|
||||
issue.current_journal.notes = @notes if @notes.present?
|
||||
issue.init_journal(User.current, @notes || "")
|
||||
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)})
|
||||
moved_issues << r
|
||||
|
@ -31,6 +31,9 @@ class IssuesController < ApplicationController
|
||||
|
||||
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
|
||||
|
||||
helper :journals
|
||||
include JournalsHelper
|
||||
helper :projects
|
||||
include ProjectsHelper
|
||||
include CustomFieldsHelper
|
||||
include IssueRelationsHelper
|
||||
@ -92,8 +95,7 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
|
||||
@journals.each_with_index {|j,i| j.indice = i+1}
|
||||
@journals = @issue.journals.find(:all, :include => [:user], :order => "#{Journal.table_name}.created_at ASC")
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
@changesets = @issue.changesets.visible.all
|
||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
@ -144,6 +146,7 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
|
||||
def edit
|
||||
return render_reply(@journal) if @journal
|
||||
update_issue_from_params
|
||||
|
||||
@journal = @issue.current_journal
|
||||
@ -159,7 +162,7 @@ class IssuesController < ApplicationController
|
||||
JournalObserver.instance.send_notification = params[:send_notification] == '0' ? false : true
|
||||
if @issue.save_issue_with_child_records(params, @time_entry)
|
||||
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|
|
||||
format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
|
||||
@ -167,7 +170,7 @@ class IssuesController < ApplicationController
|
||||
end
|
||||
else
|
||||
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
|
||||
|
||||
respond_to do |format|
|
||||
@ -268,6 +271,7 @@ private
|
||||
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
|
||||
@issue.init_journal(User.current, @notes)
|
||||
@issue.safe_attributes = params[:issue]
|
||||
@journal = @issue.current_journal
|
||||
end
|
||||
|
||||
# TODO: Refactor, lots of extra code in here
|
||||
|
@ -1,115 +0,0 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 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 JournalsController < ApplicationController
|
||||
before_filter :find_journal, :only => [:edit, :diff]
|
||||
before_filter :find_issue, :only => [:new]
|
||||
before_filter :find_optional_project, :only => [:index]
|
||||
before_filter :authorize, :only => [:new, :edit, :diff]
|
||||
accept_key_auth :index
|
||||
menu_item :issues
|
||||
|
||||
include QueriesHelper
|
||||
include SortHelper
|
||||
|
||||
def index
|
||||
retrieve_query
|
||||
sort_init 'id', 'desc'
|
||||
sort_update(@query.sortable_columns)
|
||||
|
||||
if @query.valid?
|
||||
@journals = @query.journals(:order => "#{Journal.table_name}.created_on DESC",
|
||||
:limit => 25)
|
||||
end
|
||||
@title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
|
||||
render :layout => false, :content_type => 'application/atom+xml'
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def diff
|
||||
@issue = @journal.issue
|
||||
if params[:detail_id].present?
|
||||
@detail = @journal.details.find_by_id(params[:detail_id])
|
||||
else
|
||||
@detail = @journal.details.detect {|d| d.prop_key == 'description'}
|
||||
end
|
||||
(render_404; return false) unless @issue && @detail
|
||||
@diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value)
|
||||
end
|
||||
|
||||
def new
|
||||
journal = Journal.find(params[:journal_id]) if params[:journal_id]
|
||||
if journal
|
||||
user = journal.user
|
||||
text = journal.notes
|
||||
else
|
||||
user = @issue.author
|
||||
text = @issue.description
|
||||
end
|
||||
# Replaces pre blocks with [...]
|
||||
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
|
||||
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
|
||||
content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
||||
|
||||
render(:update) { |page|
|
||||
page.<< "$('notes').value = \"#{escape_javascript content}\";"
|
||||
page.show 'update'
|
||||
page << "Form.Element.focus('notes');"
|
||||
page << "Element.scrollTo('update');"
|
||||
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
|
||||
}
|
||||
end
|
||||
|
||||
def edit
|
||||
(render_403; return false) unless @journal.editable_by?(User.current)
|
||||
if request.post?
|
||||
@journal.update_attributes(:notes => params[:notes]) if params[:notes]
|
||||
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
|
||||
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id }
|
||||
format.js { render :action => 'update' }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
# TODO: implement non-JS journal update
|
||||
render :nothing => true
|
||||
}
|
||||
format.js
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_journal
|
||||
@journal = Journal.find(params[:id])
|
||||
@project = @journal.journalized.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
# TODO: duplicated in IssuesController
|
||||
def find_issue
|
||||
@issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
|
||||
@project = @issue.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
@ -97,7 +97,7 @@ class WikiController < ApplicationController
|
||||
@content.comments = nil
|
||||
|
||||
# To prevent StaleObjectError exception when reverting to a previous version
|
||||
@content.version = @page.content.version
|
||||
@content.lock_version = @page.content.lock_version
|
||||
end
|
||||
|
||||
verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
|
||||
@ -119,6 +119,7 @@ class WikiController < ApplicationController
|
||||
redirect_to :action => 'show', :project_id => @project, :id => @page.title
|
||||
return
|
||||
end
|
||||
params[:content].delete(:version) # The version count is automatically increased
|
||||
@content.attributes = params[:content]
|
||||
@content.author = User.current
|
||||
# if page is new @page.save will also save content, but not if page isn't a new record
|
||||
@ -160,7 +161,7 @@ class WikiController < ApplicationController
|
||||
@version_pages = Paginator.new self, @version_count, per_page_option, params['p']
|
||||
# don't load text
|
||||
@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',
|
||||
:limit => @version_pages.items_per_page + 1,
|
||||
:offset => @version_pages.current.offset
|
||||
|
@ -1,42 +0,0 @@
|
||||
# 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"
|
||||
|
||||
class Attachment < ActiveRecord::Base
|
||||
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_length_of :filename, :maximum => 255
|
||||
validates_length_of :disk_filename, :maximum => 255
|
||||
|
||||
acts_as_event :title => :filename,
|
||||
:url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
|
||||
acts_as_journalized :event_title => :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',
|
||||
:permission => :view_files,
|
||||
:author_key => :author_id,
|
||||
:find_options => {:select => "#{Attachment.table_name}.*",
|
||||
:joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
|
||||
"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 )"}
|
||||
|
||||
acts_as_activity_provider :type => 'documents',
|
||||
:permission => :view_documents,
|
||||
:author_key => :author_id,
|
||||
:find_options => {:select => "#{Attachment.table_name}.*",
|
||||
:joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
|
||||
"LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
|
||||
acts_as_activity :type => 'documents', :permission => :view_documents,
|
||||
:find_options => { :include => { :document => :project } }
|
||||
|
||||
# This method is called on save by the AttachmentJournal in order to
|
||||
# decide which kind of activity we are dealing with. When that activity
|
||||
# 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.
|
||||
def activity_type
|
||||
case container_type
|
||||
when "Document"
|
||||
"documents"
|
||||
when "Version"
|
||||
"files"
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
cattr_accessor :storage_path
|
||||
@@storage_path = Redmine::Configuration['attachments_storage_path'] || "#{RAILS_ROOT}/files"
|
||||
|
@ -23,19 +23,17 @@ class Changeset < ActiveRecord::Base
|
||||
has_many :changes, :dependent => :delete_all
|
||||
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))},
|
||||
:description => :long_comments,
|
||||
:datetime => :committed_on,
|
||||
:url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
|
||||
|
||||
acts_as_journalized :event_title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
|
||||
:event_description => :long_comments,
|
||||
:event_datetime => :committed_on,
|
||||
: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',
|
||||
:include => {:repository => :project},
|
||||
:project_key => "#{Repository.table_name}.project_id",
|
||||
: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_uniqueness_of :revision, :scope => :repository_id
|
||||
@ -193,7 +191,7 @@ class Changeset < ActiveRecord::Base
|
||||
# don't change the status is the issue 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
|
||||
unless Setting.commit_fix_done_ratio.blank?
|
||||
issue.done_ratio = Setting.commit_fix_done_ratio.to_i
|
||||
|
@ -20,11 +20,13 @@ class Document < ActiveRecord::Base
|
||||
belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
|
||||
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_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_length_of :title, :maximum => 60
|
||||
|
@ -27,7 +27,6 @@ class Issue < ActiveRecord::Base
|
||||
belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_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_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
|
||||
|
||||
@ -38,21 +37,25 @@ class Issue < ActiveRecord::Base
|
||||
acts_as_attachable :after_remove => :attachment_removed
|
||||
acts_as_customizable
|
||||
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' : '')}
|
||||
|
||||
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', 'description')
|
||||
|
||||
acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
|
||||
:include => [:project, :journals],
|
||||
# sort by id so that limited eager loading doesn't break with postgresql
|
||||
: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)
|
||||
|
||||
attr_reader :current_journal
|
||||
|
||||
validates_presence_of :subject, :priority, :project, :tracker, :author, :status
|
||||
|
||||
validates_length_of :subject, :maximum => 255
|
||||
@ -83,7 +86,7 @@ class Issue < ActiveRecord::Base
|
||||
|
||||
before_create :default_assign
|
||||
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
|
||||
|
||||
# Returns a SQL conditions string used to find all issues visible by the specified user
|
||||
@ -346,15 +349,11 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def init_journal(user, notes = "")
|
||||
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
|
||||
@issue_before_change = self.clone
|
||||
@issue_before_change.status = self.status
|
||||
@custom_values_before_change = {}
|
||||
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
|
||||
# Callback on attachment deletion
|
||||
def attachment_removed(obj)
|
||||
init_journal(User.current)
|
||||
create_journal
|
||||
last_journal.update_attribute(:changes, {"attachments_" + obj.id.to_s => [obj.filename, nil]}.to_yaml)
|
||||
end
|
||||
|
||||
# Return true if the issue is closed, otherwise false
|
||||
@ -556,13 +555,12 @@ class Issue < ActiveRecord::Base
|
||||
if valid?
|
||||
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
|
||||
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
|
||||
if save
|
||||
# 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
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
@ -777,22 +775,12 @@ class Issue < ActiveRecord::Base
|
||||
).each do |issue|
|
||||
next if issue.project.nil? || issue.fixed_version.nil?
|
||||
unless issue.project.shared_versions.include?(issue.fixed_version)
|
||||
issue.init_journal(User.current)
|
||||
issue.fixed_version = nil
|
||||
issue.save
|
||||
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
|
||||
def default_assign
|
||||
if assigned_to.nil? && category && category.assigned_to
|
||||
@ -817,40 +805,14 @@ class Issue < ActiveRecord::Base
|
||||
duplicate.reload
|
||||
# Don't re-close it if it's already closed
|
||||
next if duplicate.closed?
|
||||
# Same user and notes
|
||||
if @current_journal
|
||||
duplicate.init_journal(@current_journal.user, @current_journal.notes)
|
||||
end
|
||||
# Implicitely creates a new journal
|
||||
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
|
||||
|
||||
# Saves the changes in a Journal
|
||||
# Called after_save
|
||||
def create_journal
|
||||
if @current_journal
|
||||
# attributes changes
|
||||
(Issue.column_names - %w(id 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
|
||||
# based on specific criteria
|
||||
@ -880,4 +842,13 @@ class Issue < ActiveRecord::Base
|
||||
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
|
||||
|
@ -1,81 +0,0 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 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 <> '')"}
|
||||
|
||||
named_scope :visible, lambda {|*args| {
|
||||
:include => {:issue => :project},
|
||||
:conditions => Issue.visible_condition(args.first || User.current)
|
||||
}}
|
||||
|
||||
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
|
@ -1,20 +0,0 @@
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2011 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
|
||||
end
|
@ -159,21 +159,20 @@ class MailHandler < ActionMailer::Base
|
||||
# ignore CLI-supplied defaults for new issues
|
||||
@@handler_options[:issue].clear
|
||||
|
||||
journal = issue.init_journal(user)
|
||||
issue.safe_attributes = issue_attributes_from_keywords(issue)
|
||||
issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)}
|
||||
journal.notes = cleaned_up_text_body
|
||||
issue.init_journal(user, cleaned_up_text_body)
|
||||
add_attachments(issue)
|
||||
issue.save!
|
||||
logger.info "MailHandler: issue ##{issue.id} updated by #{user}" if logger && logger.info
|
||||
journal
|
||||
issue.last_journal
|
||||
end
|
||||
|
||||
# Reply will be added to the issue
|
||||
def receive_journal_reply(journal_id)
|
||||
journal = Journal.find_by_id(journal_id)
|
||||
if journal && journal.journalized_type == 'Issue'
|
||||
receive_issue_reply(journal.journalized_id)
|
||||
if journal and journal.journaled.is_a? Issue
|
||||
receive_issue_reply(journal.journaled_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -21,6 +21,7 @@ class Mailer < ActionMailer::Base
|
||||
layout 'mailer'
|
||||
helper :application
|
||||
helper :issues
|
||||
helper :journals
|
||||
helper :custom_fields
|
||||
|
||||
include ActionController::UrlWriter
|
||||
@ -58,7 +59,7 @@ class Mailer < ActionMailer::Base
|
||||
# issue_edit(journal) => tmail object
|
||||
# Mailer.deliver_issue_edit(journal) => sends an email to issue recipients
|
||||
def issue_edit(journal)
|
||||
issue = journal.journalized.reload
|
||||
issue = journal.journaled.reload
|
||||
redmine_headers 'Project' => issue.project.identifier,
|
||||
'Issue-Id' => issue.id,
|
||||
'Issue-Author' => issue.author.login,
|
||||
@ -71,7 +72,7 @@ class Mailer < ActionMailer::Base
|
||||
# Watchers in cc
|
||||
cc(issue.watcher_recipients - @recipients)
|
||||
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
|
||||
subject s
|
||||
body :issue => issue,
|
||||
@ -188,7 +189,7 @@ class Mailer < ActionMailer::Base
|
||||
cc((message.root.watcher_recipients + message.board.watcher_recipients).uniq - @recipients)
|
||||
subject "[#{message.board.project.name} - #{message.board.name} - msg#{message.root.id}] #{message.subject}"
|
||||
body :message => message,
|
||||
:message_url => url_for(message.event_url)
|
||||
:message_url => url_for(message.last_journal.event_url)
|
||||
render_multipart('message_posted', body)
|
||||
end
|
||||
|
||||
|
@ -22,18 +22,24 @@ class Message < ActiveRecord::Base
|
||||
acts_as_attachable
|
||||
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'],
|
||||
:include => {:board => :project},
|
||||
:project_key => 'project_id',
|
||||
: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
|
||||
|
||||
attr_protected :locked, :sticky
|
||||
|
@ -16,7 +16,10 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class MessageObserver < ActiveRecord::Observer
|
||||
def after_create(message)
|
||||
Mailer.deliver_message_posted(message) if Setting.notified_events.include?('message_posted')
|
||||
def after_save(message)
|
||||
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
|
||||
|
@ -24,10 +24,8 @@ class News < ActiveRecord::Base
|
||||
validates_length_of :title, :maximum => 60
|
||||
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_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
|
||||
acts_as_watchable
|
||||
|
||||
after_create :add_author_as_watcher
|
||||
|
@ -545,8 +545,8 @@ class Query < ActiveRecord::Base
|
||||
|
||||
# Returns the journals
|
||||
# Valid options are :order, :offset, :limit
|
||||
def journals(options={})
|
||||
Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
|
||||
def issue_journals(options={})
|
||||
IssueJournal.find :all, :joins => [:user, {:issue => [:project, :author, :tracker, :status]}],
|
||||
:conditions => statement,
|
||||
:order => options[:order],
|
||||
:limit => options[:limit],
|
||||
|
@ -26,14 +26,10 @@ class TimeEntry < ActiveRecord::Base
|
||||
attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
|
||||
|
||||
acts_as_customizable
|
||||
acts_as_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}},
|
||||
:author => :user,
|
||||
:description => :comments
|
||||
|
||||
acts_as_activity_provider :timestamp => "#{table_name}.created_on",
|
||||
:author_key => :user_id,
|
||||
:find_options => {:include => :project}
|
||||
acts_as_journalized :event_title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
|
||||
:event_url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
|
||||
:event_author => :user,
|
||||
:event_description => :comments
|
||||
|
||||
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
|
||||
validates_numericality_of :hours, :allow_nil => true, :message => :invalid
|
||||
|
@ -18,14 +18,22 @@
|
||||
require 'zlib'
|
||||
|
||||
class WikiContent < ActiveRecord::Base
|
||||
set_locking_column :version
|
||||
belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
|
||||
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
|
||||
validates_presence_of :text
|
||||
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)
|
||||
page.visible?(user)
|
||||
end
|
||||
@ -44,67 +52,71 @@ class WikiContent < ActiveRecord::Base
|
||||
notified.reject! {|user| !visible?(user)}
|
||||
notified.collect(&:mail)
|
||||
end
|
||||
|
||||
class Version
|
||||
belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id'
|
||||
belongs_to :author, :class_name => '::User', :foreign_key => 'author_id'
|
||||
|
||||
# FIXME: Deprecate
|
||||
def versions
|
||||
journals
|
||||
end
|
||||
|
||||
def version
|
||||
unless last_journal
|
||||
# FIXME: This is code that caters for a case that should never happen in the normal code paths!!
|
||||
create_journal
|
||||
last_journal.update_attribute(:created_at, updated_on)
|
||||
end
|
||||
last_journal.version
|
||||
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_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"},
|
||||
:description => :comments,
|
||||
:datetime => :updated_on,
|
||||
:type => 'wiki-page',
|
||||
:url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.page.wiki.project, :id => o.page.title, :version => o.version}}
|
||||
|
||||
acts_as_activity_provider :type => 'wiki_edits',
|
||||
:timestamp => "#{WikiContent.versioned_table_name}.updated_on",
|
||||
:author_key => "#{WikiContent.versioned_table_name}.author_id",
|
||||
:permission => :view_wiki_edits,
|
||||
:find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
|
||||
"#{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"}
|
||||
# Wiki Content might be large and the data should possibly be compressed
|
||||
def compress_version_text
|
||||
self.text = changes["text"].last if changes["text"]
|
||||
self.text ||= self.journaled.text
|
||||
end
|
||||
|
||||
def text=(plain)
|
||||
case Setting.wiki_compression
|
||||
when 'gzip'
|
||||
begin
|
||||
self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION)
|
||||
self.compression = 'gzip'
|
||||
rescue
|
||||
self.data = plain
|
||||
self.compression = ''
|
||||
end
|
||||
when "gzip"
|
||||
begin
|
||||
text_hash :text => Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION), :compression => Setting.wiki_compression
|
||||
rescue
|
||||
text_hash :text => plain, :compression => ''
|
||||
end
|
||||
else
|
||||
self.data = plain
|
||||
self.compression = ''
|
||||
text_hash :text => plain, :compression => ''
|
||||
end
|
||||
plain
|
||||
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
|
||||
@text ||= case compression
|
||||
@text ||= case changes[:compression]
|
||||
when 'gzip'
|
||||
Zlib::Inflate.inflate(data)
|
||||
else
|
||||
# uncompressed data
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
def project
|
||||
page.project
|
||||
changes["data"]
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the previous version or nil
|
||||
def previous
|
||||
@previous ||= WikiContent::Version.find(:first,
|
||||
:order => 'version DESC',
|
||||
:include => :author,
|
||||
:conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
|
||||
@previous ||= journaled.journals.at(version - 1)
|
||||
end
|
||||
|
||||
# FIXME: Deprecate
|
||||
def versioned
|
||||
journaled
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,20 +1,6 @@
|
||||
<% reply_links = authorize_for('issues', 'edit') -%>
|
||||
<% for journal in journals %>
|
||||
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>">
|
||||
<h4><div class="journal-link"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div>
|
||||
<%= 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>
|
||||
<%= render_journal issue, journal, :edit_permission => :edit_issue_notes,
|
||||
:edit_own_permission => :edit_own_issue_notes %>
|
||||
<%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
|
||||
<% end %>
|
||||
|
||||
|
@ -49,9 +49,9 @@ api.issue do
|
||||
api.created_on journal.created_on
|
||||
api.array :details do
|
||||
journal.details.each do |detail|
|
||||
api.detail :property => detail.property, :name => detail.prop_key do
|
||||
api.old_value detail.old_value
|
||||
api.new_value detail.value
|
||||
api.detail :property => journal.property(detail), :name => journal.prop_key(detail) do
|
||||
api.old_value journal.old_value(detail)
|
||||
api.new_value journal.value(detail)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,21 +0,0 @@
|
||||
<% form_remote_tag(:url => {}, :html => { :id => "journal-#{@journal.id}-form" }) do %>
|
||||
<%= text_area_tag :notes, @journal.notes,
|
||||
:id => "journal_#{@journal.id}_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_remote l(:label_preview),
|
||||
{ :url => preview_issue_path(:project_id => @project, :id => @journal.issue),
|
||||
:method => 'post',
|
||||
:update => "journal_#{@journal.id}_preview",
|
||||
:with => "Form.serialize('journal-#{@journal.id}-form')",
|
||||
:complete => "Element.scrollTo('journal_#{@journal.id}_preview')"
|
||||
}, :accesskey => accesskey(:preview) %>
|
||||
|
|
||||
<%= link_to l(:button_cancel), '#', :onclick => "Element.remove('journal-#{@journal.id}-form'); " +
|
||||
"Element.show('journal-#{@journal.id}-notes'); return false;" %></p>
|
||||
|
||||
<div id="journal_<%= @journal.id %>_preview" class="wiki"></div>
|
||||
<% end %>
|
||||
<%= wikitoolbar_for "journal_#{@journal.id}_notes" %>
|
@ -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.author { xml.name "#{Setting.app_title}" }
|
||||
@journals.each do |change|
|
||||
issue = change.issue
|
||||
issue = change.journaled
|
||||
xml.entry do
|
||||
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)
|
||||
@ -20,7 +20,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
|
||||
xml.content "type" => "html" do
|
||||
xml.text! '<ul>'
|
||||
change.details.each do |detail|
|
||||
xml.text! '<li>' + show_detail(detail, false) + '</li>'
|
||||
xml.text! '<li>' + change.render_detail(detail, false) + '</li>'
|
||||
end
|
||||
xml.text! '</ul>'
|
||||
xml.text! textilizable(change, :notes, :only_path => false) unless change.notes.blank?
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<ul>
|
||||
<% for detail in @journal.details %>
|
||||
<li><%= show_detail(detail, true) %></li>
|
||||
<li><%= @journal.render_detail(detail, true) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %>
|
||||
|
||||
<% for detail in @journal.details -%>
|
||||
<%= show_detail(detail, true) %>
|
||||
<%= @journal.render_detail(detail, true) %>
|
||||
<% end -%>
|
||||
|
||||
<%= @journal.notes if @journal.notes? %>
|
||||
|
@ -26,7 +26,10 @@
|
||||
<h3><%= l(:label_result_plural) %> (<%= @results_by_type.values.sum %>)</h3>
|
||||
<dl id="search-results">
|
||||
<% @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>
|
||||
<span class="author"><%= format_time(e.event_datetime) %></span></dd>
|
||||
<% end %>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<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| %>
|
||||
<%= f.hidden_field :version %>
|
||||
<%= f.hidden_field :lock_version %>
|
||||
<%= error_messages_for 'content' %>
|
||||
|
||||
<p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
|
||||
|
@ -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="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="updated_on"><%= format_time(ver.updated_on) %></td>
|
||||
<td class="author"><%= link_to_user ver.author %></td>
|
||||
<td class="comments"><%=h ver.comments %></td>
|
||||
<td class="updated_on"><%= format_time(ver.created_at) %></td>
|
||||
<td class="author"><%= link_to_user ver.user %></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>
|
||||
</tr>
|
||||
<% line_num += 1 %>
|
||||
|
@ -36,7 +36,7 @@ Rails::Initializer.run do |config|
|
||||
|
||||
# Activate observers that should always be running
|
||||
# config.active_record.observers = :cacher, :garbage_collector
|
||||
config.active_record.observers = :message_observer, :issue_observer, :journal_observer, :news_observer, :document_observer, :wiki_content_observer, :comment_observer
|
||||
config.active_record.observers = :journal_observer, :message_observer, :issue_observer, :news_observer, :document_observer, :wiki_content_observer, :comment_observer
|
||||
|
||||
# Make Active Record use UTC-base instead of local time
|
||||
# config.active_record.default_timezone = :utc
|
||||
|
@ -2,7 +2,7 @@ class RenameCommentToComments < ActiveRecord::Migration
|
||||
def self.up
|
||||
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_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(:changesets, :comment, :comments) if ActiveRecord::Base.connection.columns(Changeset.table_name).detect{|c| c.name == "comment"}
|
||||
end
|
||||
|
109
db/migrate/20100714111651_generalize_journals.rb
Normal file
109
db/migrate/20100714111651_generalize_journals.rb
Normal file
@ -0,0 +1,109 @@
|
||||
class GeneralizeJournals < ActiveRecord::Migration
|
||||
def self.up
|
||||
# This is provided here for migrating up after the JournalDetails has been removed
|
||||
unless Object.const_defined?("JournalDetails")
|
||||
Object.const_set("JournalDetails", Class.new(ActiveRecord::Base))
|
||||
end
|
||||
|
||||
change_table :journals do |t|
|
||||
t.rename :journalized_id, :journaled_id
|
||||
t.rename :created_on, :created_at
|
||||
|
||||
t.integer :version, :default => 0, :null => false
|
||||
t.string :activity_type
|
||||
t.text :changes
|
||||
t.string :type
|
||||
|
||||
t.index :journaled_id
|
||||
t.index :activity_type
|
||||
t.index :created_at
|
||||
t.index :type
|
||||
end
|
||||
|
||||
Journal.all.group_by(&:journaled_id).each_pair do |id, journals|
|
||||
journals.sort_by(&:created_at).each_with_index do |j, idx|
|
||||
j.update_attribute(:type, "#{j.journalized_type}Journal")
|
||||
j.update_attribute(:version, idx + 1)
|
||||
# FIXME: Find some way to choose the right activity here
|
||||
j.update_attribute(:activity_type, j.journalized_type.constantize.activity_provider_options.keys.first)
|
||||
end
|
||||
end
|
||||
|
||||
change_table :journals do |t|
|
||||
t.remove :journalized_type
|
||||
end
|
||||
|
||||
JournalDetails.all.each do |detail|
|
||||
journal = Journal.find(detail.journal_id)
|
||||
changes = journal.changes || {}
|
||||
if detail.property == 'attr' # Standard attributes
|
||||
changes[detail.prop_key.to_s] = [detail.old_value, detail.value]
|
||||
elsif detail.property == 'cf' # Custom fields
|
||||
changes["custom_values_" + detail.prop_key.to_s] = [detail.old_value, detail.value]
|
||||
elsif detail.property == 'attachment' # Attachment
|
||||
changes["attachments_" + detail.prop_key.to_s] = [detail.old_value, detail.value]
|
||||
end
|
||||
journal.update_attribute(:changes, changes.to_yaml)
|
||||
end
|
||||
|
||||
# Create creation journals for all activity providers
|
||||
providers = Redmine::Activity.providers.collect {|k, v| v.collect(&:constantize) }.flatten.compact.uniq
|
||||
providers.each do |p|
|
||||
next unless p.table_exists? # Objects not in the DB yet need creation journal entries
|
||||
p.find(:all).each do |o|
|
||||
unless o.last_journal
|
||||
o.send(:update_journal)
|
||||
created_at = nil
|
||||
[:created_at, :created_on, :updated_at, :updated_on].each do |m|
|
||||
if o.respond_to? m
|
||||
created_at = o.send(m)
|
||||
break
|
||||
end
|
||||
end
|
||||
p "Updating #{o}"
|
||||
o.last_journal.update_attribute(:created_at, created_at) if created_at and o.last_journal
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# drop_table :journal_details
|
||||
end
|
||||
|
||||
def self.down
|
||||
# create_table "journal_details", :force => true do |t|
|
||||
# t.integer "journal_id", :default => 0, :null => false
|
||||
# t.string "property", :limit => 30, :default => "", :null => false
|
||||
# t.string "prop_key", :limit => 30, :default => "", :null => false
|
||||
# t.string "old_value"
|
||||
# t.string "value"
|
||||
# end
|
||||
|
||||
change_table "journals" do |t|
|
||||
t.rename :journaled_id, :journalized_id
|
||||
t.rename :created_at, :created_on
|
||||
|
||||
t.string :journalized_type, :limit => 30, :default => "", :null => false
|
||||
end
|
||||
|
||||
custom_field_names = CustomField.all.group_by(&:type)[IssueCustomField].collect(&:name)
|
||||
Journal.all.each do |j|
|
||||
# Can't used j.journalized.class.name because the model changes make it nil
|
||||
j.update_attribute(:journalized_type, j.type.to_s.sub("Journal","")) if j.type.present?
|
||||
end
|
||||
|
||||
change_table "journals" do |t|
|
||||
t.remove_index :journaled_id
|
||||
t.remove_index :activity_type
|
||||
t.remove_index :created_at
|
||||
t.remove_index :type
|
||||
|
||||
t.remove :type
|
||||
t.remove :version
|
||||
t.remove :activity_type
|
||||
t.remove :changes
|
||||
end
|
||||
|
||||
# add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id"
|
||||
# add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id"
|
||||
end
|
||||
end
|
@ -0,0 +1,49 @@
|
||||
class MergeWikiVersionsWithJournals < ActiveRecord::Migration
|
||||
def self.up
|
||||
# This is provided here for migrating up after the WikiContent::Version class has been removed
|
||||
unless WikiContent.const_defined?("Version")
|
||||
WikiContent.const_set("Version", Class.new(ActiveRecord::Base))
|
||||
end
|
||||
|
||||
WikiContent::Version.find_by_sql("SELECT * FROM wiki_content_versions").each do |wv|
|
||||
journal = WikiContentJournal.create!(:journaled_id => wv.wiki_content_id, :user_id => wv.author_id,
|
||||
:notes => wv.comments, :activity_type => "wiki_edits")
|
||||
changes = {}
|
||||
changes["compression"] = wv.compression
|
||||
changes["data"] = wv.data
|
||||
journal.update_attribute(:changes, changes.to_yaml)
|
||||
journal.update_attribute(:version, wv.version)
|
||||
end
|
||||
# drop_table :wiki_content_versions
|
||||
|
||||
change_table :wiki_contents do |t|
|
||||
t.rename :version, :lock_version
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
change_table :wiki_contents do |t|
|
||||
t.rename :lock_version, :version
|
||||
end
|
||||
|
||||
# create_table :wiki_content_versions do |t|
|
||||
# t.column :wiki_content_id, :integer, :null => false
|
||||
# t.column :page_id, :integer, :null => false
|
||||
# t.column :author_id, :integer
|
||||
# t.column :data, :binary
|
||||
# t.column :compression, :string, :limit => 6, :default => ""
|
||||
# t.column :comments, :string, :limit => 255, :default => ""
|
||||
# t.column :updated_on, :datetime, :null => false
|
||||
# t.column :version, :integer, :null => false
|
||||
# end
|
||||
# add_index :wiki_content_versions, :wiki_content_id, :name => :wiki_content_versions_wcid
|
||||
#
|
||||
# WikiContentJournal.all.each do |j|
|
||||
# WikiContent::Version.create(:wiki_content_id => j.journaled_id, :page_id => j.journaled.page_id,
|
||||
# :author_id => j.user_id, :data => j.changes["data"], :compression => j.changes["compression"],
|
||||
# :comments => j.notes, :updated_on => j.created_at, :version => j.version)
|
||||
# end
|
||||
|
||||
WikiContentJournal.destroy_all
|
||||
end
|
||||
end
|
@ -1,11 +1,9 @@
|
||||
class Meeting < ActiveRecord::Base
|
||||
belongs_to :project
|
||||
|
||||
acts_as_event :title => Proc.new {|o| "#{o.scheduled_on} Meeting"},
|
||||
:datetime => :scheduled_on,
|
||||
:author => nil,
|
||||
:url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}}
|
||||
|
||||
acts_as_activity_provider :timestamp => 'scheduled_on',
|
||||
:find_options => { :include => :project }
|
||||
acts_as_journalized :event_title => Proc.new {|o| "#{o.scheduled_on} Meeting"},
|
||||
:event_datetime => :scheduled_on,
|
||||
:event_author => nil,
|
||||
:event_url => Proc.new {|o| {:controller => 'meetings', :action => 'show', :id => o.id}}
|
||||
:activity_timestamp => 'scheduled_on'
|
||||
end
|
||||
|
@ -208,12 +208,12 @@ Redmine::MenuManager.map :project_menu do |menu|
|
||||
end
|
||||
|
||||
Redmine::Activity.map do |activity|
|
||||
activity.register :issues, :class_name => %w(Issue Journal)
|
||||
activity.register :issues, :class_name => 'Issue'
|
||||
activity.register :changesets
|
||||
activity.register :news
|
||||
activity.register :documents, :class_name => %w(Document 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 :time_entries, :default => false
|
||||
end
|
||||
|
@ -38,7 +38,17 @@ module Redmine
|
||||
return @event_types unless @event_types.nil?
|
||||
|
||||
@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
|
||||
end
|
||||
|
||||
|
@ -359,13 +359,13 @@ module Redmine
|
||||
pdf.SetFontStyle('B',9)
|
||||
pdf.RDMCell(190,5, l(:label_history), "B")
|
||||
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.RDMCell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
|
||||
pdf.RDMCell(190,5, format_time(journal.created_at) + " - " + journal.user.name)
|
||||
pdf.Ln
|
||||
pdf.SetFontStyle('I',8)
|
||||
for detail in journal.details
|
||||
pdf.RDMCell(190,5, "- " + show_detail(detail, true))
|
||||
pdf.RDMCell(190,5, "- " + journal.render_detail(detail, true))
|
||||
pdf.Ln
|
||||
end
|
||||
if journal.notes?
|
||||
|
@ -95,7 +95,7 @@ module Redmine
|
||||
page = nil
|
||||
if args.size > 0
|
||||
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
|
||||
else
|
||||
raise 'With no argument, this macro can be called from wiki pages only.'
|
||||
|
36
test/fixtures/journal_details.yml
vendored
36
test/fixtures/journal_details.yml
vendored
@ -1,36 +0,0 @@
|
||||
---
|
||||
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
|
||||
journal_details_004:
|
||||
old_value: "This word was removed and an other was"
|
||||
property: attr
|
||||
id: 4
|
||||
value: "This word was and an other was added"
|
||||
prop_key: description
|
||||
journal_id: 3
|
||||
journal_details_005:
|
||||
old_value: Old value
|
||||
property: cf
|
||||
id: 5
|
||||
value: New value
|
||||
prop_key: 2
|
||||
journal_id: 3
|
230
test/fixtures/journals.yml
vendored
230
test/fixtures/journals.yml
vendored
@ -1,29 +1,217 @@
|
||||
---
|
||||
journals_001:
|
||||
created_on: <%= 2.days.ago.to_date.to_s(:db) %>
|
||||
notes: "Journal notes"
|
||||
---
|
||||
journals_001:
|
||||
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
|
||||
journalized_id: 1
|
||||
journals_002:
|
||||
created_on: <%= 1.days.ago.to_date.to_s(:db) %>
|
||||
notes: "Some notes with Redmine links: #2, r2."
|
||||
notes: "Journal notes"
|
||||
journaled_id: 1
|
||||
changes: |
|
||||
---
|
||||
status_id:
|
||||
- 1
|
||||
- 2
|
||||
done_ratio:
|
||||
- 40
|
||||
- 30
|
||||
journals_002:
|
||||
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
|
||||
journalized_id: 1
|
||||
journals_003:
|
||||
created_on: <%= 1.days.ago.to_date.to_s(:db) %>
|
||||
notes: "A comment with inline image: !picture.jpg!"
|
||||
notes: "Some notes with Redmine links: #2, r2."
|
||||
journaled_id: 1
|
||||
changes: "--- {}"
|
||||
journals_003:
|
||||
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
|
||||
journalized_id: 2
|
||||
journals_004:
|
||||
created_on: <%= 1.days.ago.to_date.to_s(:db) %>
|
||||
notes: "A comment with a private version."
|
||||
notes: "A comment with inline image: !picture.jpg!"
|
||||
journaled_id: 2
|
||||
changes: "--- {}"
|
||||
journals_004:
|
||||
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
|
||||
journalized_id: 6
|
||||
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
|
||||
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_018:
|
||||
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: "--- {}"
|
56
test/fixtures/wiki_content_versions.yml
vendored
56
test/fixtures/wiki_content_versions.yml
vendored
@ -1,56 +0,0 @@
|
||||
---
|
||||
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:
|
||||
|
20
test/fixtures/wiki_contents.yml
vendored
20
test/fixtures/wiki_contents.yml
vendored
@ -9,7 +9,7 @@ wiki_contents_001:
|
||||
updated_on: 2007-03-07 00:10:51 +01:00
|
||||
page_id: 1
|
||||
id: 1
|
||||
version: 3
|
||||
lock_version: 3
|
||||
author_id: 1
|
||||
comments: Gzip compression activated
|
||||
wiki_contents_002:
|
||||
@ -22,7 +22,7 @@ wiki_contents_002:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 2
|
||||
id: 2
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_003:
|
||||
@ -33,7 +33,7 @@ wiki_contents_003:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 3
|
||||
id: 3
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_004:
|
||||
@ -46,7 +46,7 @@ wiki_contents_004:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 4
|
||||
id: 4
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_005:
|
||||
@ -57,7 +57,7 @@ wiki_contents_005:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 5
|
||||
id: 5
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_006:
|
||||
@ -68,7 +68,7 @@ wiki_contents_006:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 6
|
||||
id: 6
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_007:
|
||||
@ -76,7 +76,7 @@ wiki_contents_007:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 7
|
||||
id: 7
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_008:
|
||||
@ -84,7 +84,7 @@ wiki_contents_008:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 8
|
||||
id: 8
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_009:
|
||||
@ -92,7 +92,7 @@ wiki_contents_009:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 9
|
||||
id: 9
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
wiki_contents_010:
|
||||
@ -100,7 +100,7 @@ wiki_contents_010:
|
||||
updated_on: 2007-03-08 00:18:07 +01:00
|
||||
page_id: 10
|
||||
id: 10
|
||||
version: 1
|
||||
lock_version: 1
|
||||
author_id: 1
|
||||
comments:
|
||||
|
@ -10,10 +10,10 @@ class ActivitiesControllerTest < ActionController::TestCase
|
||||
assert_not_nil assigns(:events_by_day)
|
||||
|
||||
assert_tag :tag => "h3",
|
||||
:content => /#{2.days.ago.to_date.day}/,
|
||||
:content => /#{1.day.ago.to_date.day}/,
|
||||
:sibling => { :tag => "dl",
|
||||
:child => { :tag => "dt",
|
||||
:attributes => { :class => /issue-edit/ },
|
||||
:attributes => { :class => /issue/ },
|
||||
:child => { :tag => "a",
|
||||
:content => /(#{IssueStatus.find(2).name})/,
|
||||
}
|
||||
@ -46,12 +46,12 @@ class ActivitiesControllerTest < ActionController::TestCase
|
||||
assert_not_nil assigns(:events_by_day)
|
||||
|
||||
assert_tag :tag => "h3",
|
||||
:content => /#{5.day.ago.to_date.day}/,
|
||||
:content => /#{3.day.ago.to_date.day}/,
|
||||
:sibling => { :tag => "dl",
|
||||
:child => { :tag => "dt",
|
||||
:attributes => { :class => /issue/ },
|
||||
:child => { :tag => "a",
|
||||
:content => /#{Issue.find(5).subject}/,
|
||||
:content => /#{Issue.find(1).subject}/,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,10 +120,9 @@ class AttachmentsControllerTest < ActionController::TestCase
|
||||
# no referrer
|
||||
assert_redirected_to '/projects/ecookbook'
|
||||
assert_nil Attachment.find_by_id(1)
|
||||
j = issue.journals.find(:first, :order => 'created_on DESC')
|
||||
assert_equal 'attachment', j.details.first.property
|
||||
assert_equal '1', j.details.first.prop_key
|
||||
assert_equal 'error281.txt', j.details.first.old_value
|
||||
j = issue.journals.find(:first, :order => 'created_at DESC')
|
||||
assert_equal ['attachments_1'], j.details.keys
|
||||
assert_equal 'error281.txt', j.details['attachments_1'].first
|
||||
end
|
||||
|
||||
def test_destroy_wiki_page_attachment
|
||||
|
@ -43,7 +43,6 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:custom_fields_trackers,
|
||||
:time_entries,
|
||||
:journals,
|
||||
:journal_details,
|
||||
:queries
|
||||
|
||||
def setup
|
||||
@ -663,14 +662,14 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
|
||||
context "#update" do
|
||||
should "ignore status change" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
|
||||
end
|
||||
assert_equal 1, Issue.find(1).status_id
|
||||
end
|
||||
|
||||
should "ignore attributes changes" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
|
||||
end
|
||||
issue = Issue.find(1)
|
||||
@ -690,21 +689,21 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
|
||||
context "#update" do
|
||||
should "accept authorized status" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
|
||||
end
|
||||
assert_equal 3, Issue.find(1).status_id
|
||||
end
|
||||
|
||||
should "ignore unauthorized status" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
|
||||
end
|
||||
assert_equal 1, Issue.find(1).status_id
|
||||
end
|
||||
|
||||
should "accept authorized attributes changes" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
|
||||
end
|
||||
issue = Issue.find(1)
|
||||
@ -712,7 +711,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
end
|
||||
|
||||
should "ignore unauthorized attributes changes" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
|
||||
end
|
||||
issue = Issue.find(1)
|
||||
@ -726,21 +725,21 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
end
|
||||
|
||||
should "accept authorized status" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
|
||||
end
|
||||
assert_equal 3, Issue.find(1).status_id
|
||||
end
|
||||
|
||||
should "ignore unauthorized status" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
|
||||
end
|
||||
assert_equal 1, Issue.find(1).status_id
|
||||
end
|
||||
|
||||
should "accept authorized attributes changes" do
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
|
||||
end
|
||||
issue = Issue.find(1)
|
||||
@ -838,15 +837,17 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal '125', issue.custom_value_for(2).value
|
||||
old_subject = issue.subject
|
||||
new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
|
||||
|
||||
assert_difference('Journal.count') do
|
||||
assert_difference('JournalDetail.count', 2) do
|
||||
put :update, :id => 1, :issue => {:subject => new_subject,
|
||||
:priority_id => '6',
|
||||
:category_id => '1' # no change
|
||||
}
|
||||
end
|
||||
|
||||
assert_difference('IssueJournal.count') do
|
||||
put :update, :id => 1, :issue => {:subject => new_subject,
|
||||
:priority_id => '6',
|
||||
:category_id => '1' # no change
|
||||
}
|
||||
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'
|
||||
issue.reload
|
||||
assert_equal new_subject, issue.subject
|
||||
@ -862,17 +863,21 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
def test_put_update_with_custom_field_change
|
||||
@request.session[:user_id] = 2
|
||||
issue = Issue.find(1)
|
||||
ActionMailer::Base.deliveries.clear
|
||||
assert_equal '125', issue.custom_value_for(2).value
|
||||
|
||||
assert_difference('Journal.count') do
|
||||
assert_difference('JournalDetail.count', 3) do
|
||||
put :update, :id => 1, :issue => {:subject => 'Custom field change',
|
||||
:priority_id => '6',
|
||||
:category_id => '1', # no change
|
||||
:custom_field_values => { '2' => 'New custom value' }
|
||||
}
|
||||
end
|
||||
assert_difference('IssueJournal.count') do
|
||||
put :update, :id => 1, :issue => {:subject => 'Custom field change',
|
||||
:priority_id => '6',
|
||||
:category_id => '1', # no change
|
||||
:custom_field_values => { '2' => 'New custom value' }
|
||||
}
|
||||
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'
|
||||
issue.reload
|
||||
assert_equal 'New custom value', issue.custom_value_for(2).value
|
||||
@ -896,7 +901,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_redirected_to :action => 'show', :id => '1'
|
||||
issue.reload
|
||||
assert_equal 2, issue.status_id
|
||||
j = Journal.find(:first, :order => 'id DESC')
|
||||
j = IssueJournal.find(:first, :order => 'id DESC')
|
||||
assert_equal 'Assigned to dlopper', j.notes
|
||||
assert_equal 2, j.details.size
|
||||
|
||||
@ -913,7 +918,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:id => 1,
|
||||
:notes => notes
|
||||
assert_redirected_to :action => 'show', :id => '1'
|
||||
j = Journal.find(:first, :order => 'id DESC')
|
||||
j = IssueJournal.find(:first, :order => 'id DESC')
|
||||
assert_equal notes, j.notes
|
||||
assert_equal 0, j.details.size
|
||||
assert_equal User.anonymous, j.user
|
||||
@ -935,7 +940,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
|
||||
issue = Issue.find(1)
|
||||
|
||||
j = Journal.find(:first, :order => 'id DESC')
|
||||
j = IssueJournal.find(:first, :order => 'id DESC')
|
||||
assert_equal '2.5 hours added', j.notes
|
||||
assert_equal 0, j.details.size
|
||||
|
||||
@ -947,10 +952,6 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
|
||||
def test_put_update_with_attachment_only
|
||||
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
|
||||
put :update,
|
||||
@ -958,10 +959,10 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:notes => '',
|
||||
:attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
|
||||
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_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
|
||||
|
||||
mail = ActionMailer::Base.deliveries.last
|
||||
@ -973,7 +974,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
|
||||
# Delete all fixtured journals, a race condition can occur causing the wrong
|
||||
# journal to get fetched in the next find.
|
||||
Journal.delete_all
|
||||
IssueJournal.delete_all
|
||||
|
||||
# Mock out the unsaved attachment
|
||||
Attachment.any_instance.stubs(:create).returns(Attachment.new)
|
||||
@ -990,16 +991,16 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
|
||||
def test_put_update_with_no_change
|
||||
issue = Issue.find(1)
|
||||
issue.journals.clear
|
||||
ActionMailer::Base.deliveries.clear
|
||||
|
||||
put :update,
|
||||
:id => 1,
|
||||
:notes => ''
|
||||
assert_no_difference('IssueJournal.count') do
|
||||
put :update,
|
||||
:id => 1,
|
||||
:notes => ''
|
||||
end
|
||||
assert_redirected_to :action => 'show', :id => '1'
|
||||
|
||||
issue.reload
|
||||
assert issue.journals.empty?
|
||||
# No email should be sent
|
||||
assert ActionMailer::Base.deliveries.empty?
|
||||
end
|
||||
@ -1038,7 +1039,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
@request.session[:user_id] = 2
|
||||
notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
|
||||
|
||||
assert_no_difference('Journal.count') do
|
||||
assert_no_difference('IssueJournal.count') do
|
||||
put :update,
|
||||
:id => 1,
|
||||
:notes => notes,
|
||||
@ -1056,7 +1057,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
@request.session[:user_id] = 2
|
||||
notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
|
||||
|
||||
assert_no_difference('Journal.count') do
|
||||
assert_no_difference('IssueJournal.count') do
|
||||
put :update,
|
||||
:id => 1,
|
||||
:notes => notes,
|
||||
@ -1165,7 +1166,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
|
||||
|
||||
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 'Bulk editing', journal.notes
|
||||
assert_equal 1, journal.details.size
|
||||
@ -1203,7 +1204,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
|
||||
|
||||
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 'Bulk editing', journal.notes
|
||||
assert_equal 1, journal.details.size
|
||||
@ -1220,7 +1221,7 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:assigned_to_id => '',
|
||||
:custom_field_values => {'2' => ''}}
|
||||
assert_response 403
|
||||
assert_not_equal "Bulk should fail", Journal.last.notes
|
||||
assert_not_equal "Bulk should fail", IssueJournal.last.notes
|
||||
end
|
||||
|
||||
def test_bullk_update_should_send_a_notification
|
||||
@ -1278,11 +1279,11 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
assert_response 302
|
||||
|
||||
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 1, journal.details.size
|
||||
assert_equal '125', journal.details.first.old_value
|
||||
assert_equal '777', journal.details.first.value
|
||||
assert_equal '125', journal.old_value(journal.details.first)
|
||||
assert_equal '777', journal.value(journal.details.first)
|
||||
end
|
||||
|
||||
def test_bulk_update_unassign
|
||||
@ -1392,4 +1393,12 @@ class IssuesControllerTest < ActionController::TestCase
|
||||
:child => {:tag => 'form',
|
||||
:child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
|
||||
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
|
||||
|
@ -43,7 +43,6 @@ class IssuesControllerTransactionTest < ActionController::TestCase
|
||||
:custom_fields_trackers,
|
||||
:time_entries,
|
||||
:journals,
|
||||
:journal_details,
|
||||
:queries
|
||||
|
||||
self.use_transactional_fixtures = false
|
||||
|
@ -22,9 +22,8 @@ require 'journals_controller'
|
||||
class JournalsController; def rescue_action(e) raise e end; end
|
||||
|
||||
class JournalsControllerTest < ActionController::TestCase
|
||||
fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules,
|
||||
:trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects
|
||||
|
||||
fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :enabled_modules
|
||||
|
||||
def setup
|
||||
@controller = JournalsController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@ -32,46 +31,6 @@ class JournalsControllerTest < ActionController::TestCase
|
||||
User.current = nil
|
||||
end
|
||||
|
||||
def test_index
|
||||
get :index, :project_id => 1
|
||||
assert_response :success
|
||||
assert_not_nil assigns(:journals)
|
||||
assert_equal 'application/atom+xml', @response.content_type
|
||||
end
|
||||
|
||||
def test_diff
|
||||
get :diff, :id => 3, :detail_id => 4
|
||||
assert_response :success
|
||||
assert_template 'diff'
|
||||
|
||||
assert_tag 'span',
|
||||
:attributes => {:class => 'diff_out'},
|
||||
:content => /removed/
|
||||
assert_tag 'span',
|
||||
:attributes => {:class => 'diff_in'},
|
||||
:content => /added/
|
||||
end
|
||||
|
||||
def test_reply_to_issue
|
||||
@request.session[:user_id] = 2
|
||||
get :new, :id => 6
|
||||
assert_response :success
|
||||
assert_select_rjs :show, "update"
|
||||
end
|
||||
|
||||
def test_reply_to_issue_without_permission
|
||||
@request.session[:user_id] = 7
|
||||
get :new, :id => 6
|
||||
assert_response 403
|
||||
end
|
||||
|
||||
def test_reply_to_note
|
||||
@request.session[:user_id] = 2
|
||||
get :new, :id => 6, :journal_id => 4
|
||||
assert_response :success
|
||||
assert_select_rjs :show, "update"
|
||||
end
|
||||
|
||||
def test_get_edit
|
||||
@request.session[:user_id] = 1
|
||||
xhr :get, :edit, :id => 2
|
||||
|
@ -22,7 +22,7 @@ require 'projects_controller'
|
||||
class ProjectsController; def rescue_action(e) raise e end; end
|
||||
|
||||
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,
|
||||
:attachments, :custom_fields, :custom_values, :time_entries
|
||||
|
||||
|
@ -38,7 +38,7 @@ class SearchControllerTest < ActionController::TestCase
|
||||
assert assigns(:results).include?(Changeset.find(101))
|
||||
assert_tag :dt, :attributes => { :class => /issue/ },
|
||||
: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_equal 5, assigns(:results_by_type)['changesets']
|
||||
|
@ -22,8 +22,8 @@ require 'wiki_controller'
|
||||
class WikiController; def rescue_action(e) raise e end; end
|
||||
|
||||
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
|
||||
@controller = WikiController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@ -83,8 +83,7 @@ class WikiControllerTest < ActionController::TestCase
|
||||
put :update, :project_id => 1,
|
||||
:id => 'New page',
|
||||
:content => {:comments => 'Created the page',
|
||||
:text => "h1. New page\n\nThis is a new page",
|
||||
:version => 0}
|
||||
:text => "h1. New page\n\nThis is a new page" }
|
||||
assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'New_page'
|
||||
page = Project.find(1).wiki.find_page('New page')
|
||||
assert !page.new_record?
|
||||
@ -100,7 +99,7 @@ class WikiControllerTest < ActionController::TestCase
|
||||
:id => 'New page',
|
||||
:content => {:comments => 'Created the 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')}}
|
||||
end
|
||||
end
|
||||
@ -113,13 +112,13 @@ class WikiControllerTest < ActionController::TestCase
|
||||
@request.session[:user_id] = 2
|
||||
assert_no_difference 'WikiPage.count' do
|
||||
assert_no_difference 'WikiContent.count' do
|
||||
assert_difference 'WikiContent::Version.count' do
|
||||
assert_difference 'WikiContentJournal.count' do
|
||||
put :update, :project_id => 1,
|
||||
:id => 'Another_page',
|
||||
:content => {
|
||||
:comments => "my comments",
|
||||
:text => "edited",
|
||||
:version => 1
|
||||
:lock_version => 1
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -136,13 +135,13 @@ class WikiControllerTest < ActionController::TestCase
|
||||
@request.session[:user_id] = 2
|
||||
assert_no_difference 'WikiPage.count' do
|
||||
assert_no_difference 'WikiContent.count' do
|
||||
assert_no_difference 'WikiContent::Version.count' do
|
||||
assert_no_difference 'WikiContentJournal.count' do
|
||||
put :update, :project_id => 1,
|
||||
:id => 'Another_page',
|
||||
:content => {
|
||||
:comments => 'a' * 300, # failure here, comment is too long
|
||||
:text => 'edited',
|
||||
:version => 1
|
||||
:lock_version => 1
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -152,7 +151,7 @@ class WikiControllerTest < ActionController::TestCase
|
||||
|
||||
assert_error_tag :descendant => {:content => /Comment is too long/}
|
||||
assert_tag :tag => 'textarea', :attributes => {:id => 'content_text'}, :content => 'edited'
|
||||
assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'}
|
||||
assert_tag :tag => 'input', :attributes => {:id => 'content_lock_version', :value => '1'}
|
||||
end
|
||||
|
||||
def test_update_stale_page_should_not_raise_an_error
|
||||
@ -164,13 +163,13 @@ class WikiControllerTest < ActionController::TestCase
|
||||
|
||||
assert_no_difference 'WikiPage.count' do
|
||||
assert_no_difference 'WikiContent.count' do
|
||||
assert_no_difference 'WikiContent::Version.count' do
|
||||
assert_no_difference 'WikiContentJournal.count' do
|
||||
put :update, :project_id => 1,
|
||||
:id => 'Another_page',
|
||||
:content => {
|
||||
:comments => 'My comments',
|
||||
:text => 'Text should not be lost',
|
||||
:version => 1
|
||||
:lock_version => 1
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -196,7 +195,7 @@ class WikiControllerTest < ActionController::TestCase
|
||||
xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation',
|
||||
:content => { :comments => '',
|
||||
:text => 'this is a *previewed text*',
|
||||
:version => 3 }
|
||||
:lock_version => 3 }
|
||||
assert_response :success
|
||||
assert_template 'common/_preview'
|
||||
assert_tag :tag => 'strong', :content => /previewed text/
|
||||
@ -207,7 +206,7 @@ class WikiControllerTest < ActionController::TestCase
|
||||
xhr :post, :preview, :project_id => 1, :id => 'New page',
|
||||
:content => { :text => 'h1. New page',
|
||||
:comments => '',
|
||||
:version => 0 }
|
||||
:lock_version => 0 }
|
||||
assert_response :success
|
||||
assert_template 'common/_preview'
|
||||
assert_tag :tag => 'h1', :content => /New page/
|
||||
|
@ -39,7 +39,6 @@ class ApiTest::IssuesTest < ActionController::IntegrationTest
|
||||
:custom_fields_trackers,
|
||||
:time_entries,
|
||||
:journals,
|
||||
:journal_details,
|
||||
:queries
|
||||
|
||||
def setup
|
||||
|
@ -18,7 +18,7 @@
|
||||
require File.expand_path('../../../test_helper', __FILE__)
|
||||
|
||||
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,
|
||||
:attachments, :custom_fields, :custom_values, :time_entries
|
||||
|
||||
|
@ -166,21 +166,17 @@ class ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
should "use the new value's name" do
|
||||
@detail = JournalDetail.generate!(:property => 'attr',
|
||||
:old_value => @old_value.id,
|
||||
:value => @new_value.id,
|
||||
:prop_key => prop_key)
|
||||
|
||||
assert_match @new_value.name, show_detail(@detail, true)
|
||||
@detail = IssueJournal.generate(:version => 1, :journaled => Issue.last)
|
||||
@detail.update_attribute(:changes, {prop_key => [@old_value.id, @new_value.id]}.to_yaml)
|
||||
|
||||
assert_match @new_value.class.find(@new_value.id).name, @detail.render_detail(prop_key, true)
|
||||
end
|
||||
|
||||
should "use the old value's name" do
|
||||
@detail = JournalDetail.generate!(:property => 'attr',
|
||||
:old_value => @old_value.id,
|
||||
:value => @new_value.id,
|
||||
:prop_key => prop_key)
|
||||
|
||||
assert_match @old_value.name, show_detail(@detail, true)
|
||||
@detail = IssueJournal.generate(:version => 1, :journaled => Issue.last)
|
||||
@detail.update_attribute(:changes, {prop_key => [@old_value.id, @new_value.id]}.to_yaml)
|
||||
|
||||
assert_match @old_value.class.find(@old_value.id).name, @detail.render_detail(prop_key, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,11 +18,16 @@
|
||||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
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
|
||||
|
||||
def setup
|
||||
@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
|
||||
|
||||
def test_activity_without_subprojects
|
||||
@ -51,7 +56,7 @@ class ActivityTest < ActiveSupport::TestCase
|
||||
assert events.include?(Issue.find(1))
|
||||
assert events.include?(Message.find(5))
|
||||
# Issue of a private project
|
||||
assert !events.include?(Issue.find(4))
|
||||
assert !events.include?(Issue.find(6))
|
||||
end
|
||||
|
||||
def test_global_activity_logged_user
|
||||
@ -60,7 +65,7 @@ class ActivityTest < ActiveSupport::TestCase
|
||||
|
||||
assert events.include?(Issue.find(1))
|
||||
# Issue of a private project the user belongs to
|
||||
assert events.include?(Issue.find(4))
|
||||
assert events.include?(Issue.find(6))
|
||||
end
|
||||
|
||||
def test_user_activity
|
||||
@ -78,15 +83,18 @@ class ActivityTest < ActiveSupport::TestCase
|
||||
events = f.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('Version', 1))
|
||||
assert_equal [Attachment], events.collect(&:class).uniq
|
||||
assert_equal %w(Project Version), events.collect(&:container_type).uniq.sort
|
||||
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).last_journal)
|
||||
assert_equal [Attachment], events.collect(&:journaled).collect(&:class).uniq
|
||||
assert_equal %w(Project Version), events.collect(&:journaled).collect(&:container_type).uniq.sort
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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
|
||||
|
@ -43,29 +43,34 @@ class IssuesHelperTest < HelperTestCase
|
||||
def request
|
||||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
|
||||
# This is probably needed in this test only anymore
|
||||
def show_detail(journal, detail, html = true)
|
||||
journal.render_detail(detail, html)
|
||||
end
|
||||
|
||||
context "IssuesHelper#show_detail" do
|
||||
context "with no_html" do
|
||||
should 'show a changing attribute' do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
|
||||
assert_equal "% Done changed from 40 to 100", show_detail(@detail, true)
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
||||
assert_equal "% Done changed from 40 to 100", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should 'show a new attribute' do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
|
||||
assert_equal "% Done set to 100", show_detail(@detail, true)
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
||||
assert_equal "% Done set to 100", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should 'show a deleted attribute' do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
|
||||
assert_equal "% Done deleted (50)", show_detail(@detail, true)
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
||||
assert_equal "% Done deleted (50)", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
end
|
||||
end
|
||||
|
||||
context "with html" do
|
||||
should 'show a changing attribute with HTML highlights' do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
|
||||
@response.body = show_detail(@detail, false)
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [40, 100]}, :journaled => Issue.last)
|
||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
||||
|
||||
assert_select 'strong', :text => '% Done'
|
||||
assert_select 'i', :text => '40'
|
||||
@ -73,16 +78,16 @@ class IssuesHelperTest < HelperTestCase
|
||||
end
|
||||
|
||||
should 'show a new attribute with HTML highlights' do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
|
||||
@response.body = show_detail(@detail, false)
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [nil, 100]}, :journaled => Issue.last)
|
||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
||||
|
||||
assert_select 'strong', :text => '% Done'
|
||||
assert_select 'i', :text => '100'
|
||||
end
|
||||
|
||||
should 'show a deleted attribute with HTML highlights' do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
|
||||
@response.body = show_detail(@detail, false)
|
||||
@journal = IssueJournal.generate!(:changes => {"done_ratio" => [50, nil]}, :journaled => Issue.last)
|
||||
@response.body = show_detail(@journal, @journal.details.to_a.first, false)
|
||||
|
||||
assert_select 'strong', :text => '% Done'
|
||||
assert_select 'strike' do
|
||||
@ -93,25 +98,25 @@ class IssuesHelperTest < HelperTestCase
|
||||
|
||||
context "with a start_date attribute" 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')
|
||||
assert_match "01/31/2010", show_detail(@detail, true)
|
||||
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should "format the old date" do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'start_date')
|
||||
assert_match "01/01/2010", show_detail(@detail, true)
|
||||
@journal = IssueJournal.generate!(:changes => {"start_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a due_date attribute" 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')
|
||||
assert_match "01/31/2010", show_detail(@detail, true)
|
||||
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/31/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
end
|
||||
|
||||
should "format the old date" do
|
||||
@detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'due_date')
|
||||
assert_match "01/01/2010", show_detail(@detail, true)
|
||||
@journal = IssueJournal.generate!(:changes => {"due_date" => ['2010-01-01', '2010-01-31']}, :journaled => Issue.last)
|
||||
assert_match "01/01/2010", show_detail(@journal, @journal.details.to_a.first, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -208,10 +208,8 @@ class IssueNestedSetTest < ActiveSupport::TestCase
|
||||
issue3.save!
|
||||
|
||||
assert_difference 'Issue.count', -2 do
|
||||
assert_difference 'Journal.count', -1 do
|
||||
assert_difference 'JournalDetail.count', -1 do
|
||||
Issue.find(issue2.id).destroy
|
||||
end
|
||||
assert_difference 'IssueJournal.count', -3 do
|
||||
Issue.find(issue2.id).destroy
|
||||
end
|
||||
end
|
||||
|
||||
@ -234,18 +232,17 @@ class IssueNestedSetTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
def test_destroy_child_issue_with_children
|
||||
root = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'root')
|
||||
child = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => root.id)
|
||||
leaf = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'leaf', :parent_issue_id => child.id)
|
||||
root = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'root').reload
|
||||
child = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => root.id).reload
|
||||
leaf = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'leaf', :parent_issue_id => child.id).reload
|
||||
leaf.init_journal(User.find(2))
|
||||
leaf.subject = 'leaf with journal'
|
||||
leaf.save!
|
||||
|
||||
|
||||
total_journals_on_children = leaf.reload.journals.count + child.reload.journals.count
|
||||
assert_difference 'Issue.count', -2 do
|
||||
assert_difference 'Journal.count', -1 do
|
||||
assert_difference 'JournalDetail.count', -1 do
|
||||
Issue.find(child.id).destroy
|
||||
end
|
||||
assert_difference 'IssueJournal.count', -total_journals_on_children do
|
||||
Issue.find(child.id).destroy
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -630,41 +630,40 @@ class IssueTest < ActiveSupport::TestCase
|
||||
|
||||
i.init_journal(User.find(2))
|
||||
i.description = new_description
|
||||
assert_difference 'Journal.count', 1 do
|
||||
assert_difference 'JournalDetail.count', 1 do
|
||||
i.save!
|
||||
end
|
||||
assert_difference 'IssueJournal.count', 1 do
|
||||
i.save!
|
||||
end
|
||||
|
||||
detail = JournalDetail.first(:order => 'id DESC')
|
||||
assert_equal i, detail.journal.journalized
|
||||
assert_equal 'attr', detail.property
|
||||
assert_equal 'description', detail.prop_key
|
||||
assert_equal old_description, detail.old_value
|
||||
assert_equal new_description, detail.value
|
||||
journal = IssueJournal.first(:order => 'id DESC')
|
||||
assert_equal i, journal.journaled
|
||||
assert journal.changes.has_key? "description"
|
||||
assert_equal old_description, journal.old_value("description")
|
||||
assert_equal new_description, journal.value("description")
|
||||
end
|
||||
|
||||
# TODO: This test has become somewhat obsolete with the new journalized scheme
|
||||
def test_saving_twice_should_not_duplicate_journal_details
|
||||
i = Issue.find(:first)
|
||||
i.init_journal(User.find(2), 'Some notes')
|
||||
# initial changes
|
||||
i.subject = 'New subject'
|
||||
i.done_ratio = i.done_ratio + 10
|
||||
assert_difference 'Journal.count' do
|
||||
assert_difference 'IssueJournal.count' do
|
||||
assert i.save
|
||||
end
|
||||
assert i.current_journal.changes.has_key? "subject"
|
||||
assert i.current_journal.changes.has_key? "done_ratio"
|
||||
|
||||
# 1 more change
|
||||
i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
|
||||
assert_no_difference 'Journal.count' do
|
||||
assert_difference 'JournalDetail.count', 1 do
|
||||
i.save
|
||||
end
|
||||
assert_difference 'IssueJournal.count' do
|
||||
i.save
|
||||
end
|
||||
assert i.current_journal.changes.has_key? "priority_id"
|
||||
|
||||
# no more change
|
||||
assert_no_difference 'Journal.count' do
|
||||
assert_no_difference 'JournalDetail.count' do
|
||||
i.save
|
||||
end
|
||||
assert_no_difference 'IssueJournal.count' do
|
||||
i.save
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -18,11 +18,15 @@
|
||||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
class JournalObserverTest < ActiveSupport::TestCase
|
||||
fixtures :issues, :issue_statuses, :journals, :journal_details
|
||||
fixtures :issues, :issue_statuses, :journals
|
||||
|
||||
def setup
|
||||
ActionMailer::Base.deliveries.clear
|
||||
@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
|
||||
|
||||
# context: issue_updated notified_events
|
||||
@ -30,9 +34,9 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = ['issue_updated']
|
||||
issue = Issue.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
|
||||
end
|
||||
|
||||
@ -40,9 +44,9 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = []
|
||||
issue = Issue.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
|
||||
end
|
||||
|
||||
@ -51,10 +55,9 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = ['issue_note_added']
|
||||
issue = Issue.find(:first)
|
||||
user = User.find(:first)
|
||||
journal = issue.init_journal(user, issue)
|
||||
journal.notes = 'This update has a note'
|
||||
issue.init_journal(user, 'This update has a note')
|
||||
|
||||
assert journal.save
|
||||
assert issue.save
|
||||
assert_equal 1, ActionMailer::Base.deliveries.size
|
||||
end
|
||||
|
||||
@ -62,10 +65,9 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = []
|
||||
issue = Issue.find(:first)
|
||||
user = User.find(:first)
|
||||
journal = issue.init_journal(user, issue)
|
||||
journal.notes = 'This update has a note'
|
||||
issue.init_journal(user, 'This update has a note')
|
||||
|
||||
assert journal.save
|
||||
assert issue.save
|
||||
assert_equal 0, ActionMailer::Base.deliveries.size
|
||||
end
|
||||
|
||||
@ -74,7 +76,7 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = ['issue_status_updated']
|
||||
issue = Issue.find(:first)
|
||||
user = User.find(:first)
|
||||
issue.init_journal(user, issue)
|
||||
issue.init_journal(user)
|
||||
issue.status = IssueStatus.last
|
||||
|
||||
assert issue.save
|
||||
@ -85,7 +87,7 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = []
|
||||
issue = Issue.find(:first)
|
||||
user = User.find(:first)
|
||||
issue.init_journal(user, issue)
|
||||
issue.init_journal(user)
|
||||
issue.status = IssueStatus.last
|
||||
|
||||
assert issue.save
|
||||
@ -97,7 +99,7 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = ['issue_priority_updated']
|
||||
issue = Issue.find(:first)
|
||||
user = User.find(:first)
|
||||
issue.init_journal(user, issue)
|
||||
issue.init_journal(user)
|
||||
issue.priority = IssuePriority.last
|
||||
|
||||
assert issue.save
|
||||
@ -108,7 +110,7 @@ class JournalObserverTest < ActiveSupport::TestCase
|
||||
Setting.notified_events = []
|
||||
issue = Issue.find(:first)
|
||||
user = User.find(:first)
|
||||
issue.init_journal(user, issue)
|
||||
issue.init_journal(user)
|
||||
issue.priority = IssuePriority.last
|
||||
|
||||
assert issue.save
|
||||
|
@ -18,14 +18,14 @@
|
||||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
class JournalTest < ActiveSupport::TestCase
|
||||
fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, :users, :members, :member_roles
|
||||
fixtures :issues, :issue_statuses, :journals
|
||||
|
||||
def setup
|
||||
@journal = Journal.find 1
|
||||
@journal = IssueJournal.find(1)
|
||||
end
|
||||
|
||||
def test_journalized_is_an_issue
|
||||
issue = @journal.issue
|
||||
issue = @journal.journalized
|
||||
assert_kind_of Issue, issue
|
||||
assert_equal 1, issue.id
|
||||
end
|
||||
@ -40,14 +40,18 @@ class JournalTest < ActiveSupport::TestCase
|
||||
def test_create_should_send_email_notification
|
||||
ActionMailer::Base.deliveries.clear
|
||||
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)
|
||||
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
|
||||
end
|
||||
|
||||
def test_visible_scope_for_anonymous
|
||||
should_eventually "test_visible_scope_for_anonymous" do
|
||||
# Anonymous user should see issues of public projects only
|
||||
journals = Journal.visible(User.anonymous).all
|
||||
assert journals.any?
|
||||
@ -57,8 +61,8 @@ class JournalTest < ActiveSupport::TestCase
|
||||
journals = Journal.visible(User.anonymous).all
|
||||
assert journals.empty?
|
||||
end
|
||||
|
||||
def test_visible_scope_for_user
|
||||
|
||||
should_eventually "test_visible_scope_for_user" do
|
||||
user = User.find(9)
|
||||
assert user.projects.empty?
|
||||
# Non member user should see issues of public projects only
|
||||
@ -78,7 +82,7 @@ class JournalTest < ActiveSupport::TestCase
|
||||
assert_nil journals.detect {|journal| journal.issue.project_id != 1}
|
||||
end
|
||||
|
||||
def test_visible_scope_for_admin
|
||||
should_eventually "test_visible_scope_for_admin" do
|
||||
user = User.find(1)
|
||||
user.members.each(&:destroy)
|
||||
assert user.projects.empty?
|
||||
@ -92,10 +96,12 @@ class JournalTest < ActiveSupport::TestCase
|
||||
ActionMailer::Base.deliveries.clear
|
||||
issue = Issue.find(:first)
|
||||
user = User.find(:first)
|
||||
journal = issue.init_journal(user, issue)
|
||||
journal = issue.init_journal(user, "A note")
|
||||
JournalObserver.instance.send_notification = false
|
||||
|
||||
assert journal.save
|
||||
assert_difference("Journal.count") do
|
||||
assert issue.save
|
||||
end
|
||||
assert_equal 0, ActionMailer::Base.deliveries.size
|
||||
end
|
||||
end
|
||||
|
@ -300,7 +300,7 @@ class MailHandlerTest < ActiveSupport::TestCase
|
||||
journal = submit_email('ticket_reply.eml')
|
||||
assert journal.is_a?(Journal)
|
||||
assert_equal User.find_by_login('jsmith'), journal.user
|
||||
assert_equal Issue.find(2), journal.journalized
|
||||
assert_equal Issue.find(2), journal.journaled
|
||||
assert_match /This is reply/, journal.notes
|
||||
assert_equal 'Feature request', journal.issue.tracker.name
|
||||
end
|
||||
@ -309,9 +309,9 @@ class MailHandlerTest < ActiveSupport::TestCase
|
||||
# This email contains: 'Status: Resolved'
|
||||
journal = submit_email('ticket_reply_with_status.eml')
|
||||
assert journal.is_a?(Journal)
|
||||
issue = Issue.find(journal.issue.id)
|
||||
issue = Issue.find(journal.journaled.id)
|
||||
assert_equal User.find_by_login('jsmith'), journal.user
|
||||
assert_equal Issue.find(2), journal.journalized
|
||||
assert_equal Issue.find(2), journal.journaled
|
||||
assert_match /This is reply/, journal.notes
|
||||
assert_equal 'Feature request', journal.issue.tracker.name
|
||||
assert_equal IssueStatus.find_by_name("Resolved"), issue.status
|
||||
|
@ -172,7 +172,7 @@ class MailerTest < ActiveSupport::TestCase
|
||||
mail = ActionMailer::Base.deliveries.last
|
||||
assert_not_nil mail
|
||||
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
|
||||
|
||||
def test_message_posted_message_id
|
||||
|
@ -192,8 +192,7 @@ class ProjectTest < ActiveSupport::TestCase
|
||||
assert_equal 0, Member.count, "Members were not deleted: #{Member.all.inspect}"
|
||||
assert_equal 0, MemberRole.count
|
||||
assert_equal 0, Issue.count
|
||||
assert_equal 0, Journal.count
|
||||
assert_equal 0, JournalDetail.count
|
||||
assert_equal 0, IssueJournal.count
|
||||
assert_equal 0, Attachment.count
|
||||
assert_equal 0, EnabledModule.count
|
||||
assert_equal 0, IssueCategory.count
|
||||
@ -212,7 +211,7 @@ class ProjectTest < ActiveSupport::TestCase
|
||||
assert_equal 0, Wiki.count
|
||||
assert_equal 0, WikiPage.count
|
||||
assert_equal 0, WikiContent.count
|
||||
assert_equal 0, WikiContent::Version.count
|
||||
assert_equal 0, WikiContentJournal.count
|
||||
assert_equal 0, Project.connection.select_all("SELECT * FROM projects_trackers").size
|
||||
assert_equal 0, Project.connection.select_all("SELECT * FROM custom_fields_projects").size
|
||||
assert_equal 0, CustomValue.count(:conditions => {:customized_type => ['Project', 'Issue', 'TimeEntry', 'Version']})
|
||||
|
@ -249,7 +249,7 @@ class RepositoryGitTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
def test_activities
|
||||
c = Changeset.new(:repository => @repository,
|
||||
c = Changeset.create(:repository => @repository,
|
||||
:committed_on => Time.now,
|
||||
:revision => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
|
||||
:scmid => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
|
||||
|
@ -228,7 +228,7 @@ class RepositoryMercurialTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
def test_activities
|
||||
c = Changeset.new(:repository => @repository,
|
||||
c = Changeset.create(:repository => @repository,
|
||||
:committed_on => Time.now,
|
||||
:revision => '123',
|
||||
:scmid => 'abc400bb8672',
|
||||
|
@ -128,14 +128,14 @@ class RepositorySubversionTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
def test_activities
|
||||
c = Changeset.new(:repository => @repository, :committed_on => Time.now,
|
||||
c = Changeset.create(:repository => @repository, :committed_on => Time.now,
|
||||
:revision => '1', :comments => 'test')
|
||||
assert c.event_title.include?('1:')
|
||||
assert_equal '1', c.event_url[:rev]
|
||||
end
|
||||
|
||||
def test_activities_nine_digit
|
||||
c = Changeset.new(:repository => @repository, :committed_on => Time.now,
|
||||
c = Changeset.create(:repository => @repository, :committed_on => Time.now,
|
||||
:revision => '123456789', :comments => 'test')
|
||||
assert c.event_title.include?('123456789:')
|
||||
assert_equal '123456789', c.event_url[:rev]
|
||||
|
@ -97,7 +97,7 @@ class RepositoryTest < ActiveSupport::TestCase
|
||||
assert_equal [101], fixed_issue.changeset_ids
|
||||
|
||||
# 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 'Applied in changeset r2.', journal.notes
|
||||
|
||||
|
@ -27,7 +27,6 @@ class SearchTest < ActiveSupport::TestCase
|
||||
:issues,
|
||||
:trackers,
|
||||
:journals,
|
||||
:journal_details,
|
||||
:repositories,
|
||||
:changesets
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
class WikiContentTest < ActiveSupport::TestCase
|
||||
fixtures :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions, :users
|
||||
fixtures :wikis, :wiki_pages, :wiki_contents, :journals, :users
|
||||
|
||||
def setup
|
||||
@wiki = Wiki.find(1)
|
||||
@ -72,9 +72,9 @@ class WikiContentTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
def test_fetch_history
|
||||
assert !@page.content.versions.empty?
|
||||
@page.content.versions.each do |version|
|
||||
assert_kind_of String, version.text
|
||||
assert !@page.content.journals.empty?
|
||||
@page.content.journals.each do |journal|
|
||||
assert_kind_of String, journal.text
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
class WikiPageTest < ActiveSupport::TestCase
|
||||
fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
|
||||
fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :journals
|
||||
|
||||
def setup
|
||||
@wiki = Wiki.find(1)
|
||||
@ -101,11 +101,14 @@ class WikiPageTest < ActiveSupport::TestCase
|
||||
|
||||
def test_destroy
|
||||
page = WikiPage.find(1)
|
||||
content_ids = WikiContent.find_all_by_page_id(1).collect(&:id)
|
||||
page.destroy
|
||||
assert_nil WikiPage.find_by_id(1)
|
||||
# make sure that page content and its history are deleted
|
||||
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
|
||||
|
||||
def test_destroy_should_not_nullify_children
|
||||
|
@ -20,8 +20,8 @@
|
||||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
class WikiTest < ActiveSupport::TestCase
|
||||
fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
|
||||
|
||||
fixtures :projects, :wikis, :wiki_pages, :wiki_contents, :journals
|
||||
|
||||
def test_create
|
||||
wiki = Wiki.new(:project => Project.find(2))
|
||||
assert !wiki.save
|
||||
|
@ -68,12 +68,12 @@ module Redmine
|
||||
scope_options[:conditions] = cond.conditions
|
||||
if options[:limit]
|
||||
# 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]
|
||||
end
|
||||
|
||||
with_scope(:find => scope_options) do
|
||||
find(:all, provider_options[:find_options].dup)
|
||||
|
||||
journal_class.with_scope(:find => scope_options) do
|
||||
journal_class.find(:all, provider_options[:find_options].dup)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
22
vendor/plugins/acts_as_journalized/.gitignore
vendored
Normal file
22
vendor/plugins/acts_as_journalized/.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
## MAC OS
|
||||
.DS_Store
|
||||
|
||||
## TEXTMATE
|
||||
*.tmproj
|
||||
tmtags
|
||||
|
||||
## EMACS
|
||||
*~
|
||||
\#*
|
||||
.\#*
|
||||
|
||||
## VIM
|
||||
*.swp
|
||||
|
||||
## PROJECT::GENERAL
|
||||
coverage
|
||||
rdoc
|
||||
pkg
|
||||
|
||||
## PROJECT::SPECIFIC
|
||||
*.db
|
339
vendor/plugins/acts_as_journalized/GPL.txt
vendored
Normal file
339
vendor/plugins/acts_as_journalized/GPL.txt
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
87
vendor/plugins/acts_as_journalized/LICENSE
vendored
Normal file
87
vendor/plugins/acts_as_journalized/LICENSE
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
"Acts_as_journalized" is a Redmine core plugin derived from the vestal_versions
|
||||
Ruby on Rails plugin. The parts are under different copyright and license conditions
|
||||
noted below.
|
||||
|
||||
The overall license terms applying to "Acts_as_journalized" as in
|
||||
this distribution are 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.
|
||||
|
||||
|
||||
|
||||
For the individual files, the following copyrights and licenses apply:
|
||||
|
||||
app/controllers/**
|
||||
app/views/**
|
||||
app/helpers/**
|
||||
app/models/journal_observer.rb
|
||||
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.
|
||||
|
||||
|
||||
lib/acts_as_journalized.rb
|
||||
lib/journal_formatter.rb
|
||||
lib/redmine/acts/journalized/permissions.rb
|
||||
lib/redmine/acts/journalized/save_hooks.rb
|
||||
lib/redmine/acts/journalized/format_hooks.rb
|
||||
lib/redmine/acts/journalized/deprecated.rb
|
||||
Copyright (c) 2010 Finn GmbH
|
||||
|
||||
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.
|
||||
|
||||
|
||||
All remaining files are:
|
||||
Copyright (c) 2009 Steve Richert
|
||||
|
||||
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.
|
3
vendor/plugins/acts_as_journalized/README.rdoc
vendored
Normal file
3
vendor/plugins/acts_as_journalized/README.rdoc
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
acts as journalized
|
||||
|
||||
A redmine core plugin for unification of journals, events and activities in redmine
|
1
vendor/plugins/acts_as_journalized/REVISION
vendored
Normal file
1
vendor/plugins/acts_as_journalized/REVISION
vendored
Normal file
@ -0,0 +1 @@
|
||||
67a8c4bee0a06420f1ba64eb9906a15d63bf5ac5 https://github.com/edavis10/acts_as_journalized
|
45
vendor/plugins/acts_as_journalized/app/controllers/journals_controller.rb
vendored
Normal file
45
vendor/plugins/acts_as_journalized/app/controllers/journals_controller.rb
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
# This file is part of the acts_as_journalized plugin for the 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 journal 2
|
||||
# of the License, or (at your option) any later journal.
|
||||
#
|
||||
# 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 JournalsController < ApplicationController
|
||||
unloadable
|
||||
before_filter :find_journal
|
||||
|
||||
def edit
|
||||
if request.post?
|
||||
@journal.update_attribute(:notes, params[:notes]) if params[:notes]
|
||||
@journal.destroy if @journal.details.empty? && @journal.notes.blank?
|
||||
call_hook(:controller_journals_edit_post, { :journal => @journal, :params => params})
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => @journal.journaled.class.name.pluralize.downcase,
|
||||
:action => 'show', :id => @journal.journaled_id }
|
||||
format.js { render :action => 'update' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def find_journal
|
||||
@journal = Journal.find(params[:id])
|
||||
(render_403; return false) unless @journal.editable_by?(User.current)
|
||||
@project = @journal.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
128
vendor/plugins/acts_as_journalized/app/helpers/journals_helper.rb
vendored
Normal file
128
vendor/plugins/acts_as_journalized/app/helpers/journals_helper.rb
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2006-2008 Jean-Philippe Lang
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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 journal 2
|
||||
# of the License, or (at your option) any later journal.
|
||||
#
|
||||
# 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
|
||||
unloadable
|
||||
include ApplicationHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
if respond_to? :before_filter
|
||||
before_filter :find_optional_journal, :only => [:edit]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_journal(model, journal, options = {})
|
||||
return "" if journal.initial?
|
||||
journal_content = render_journal_details(journal, :label_updated_time_by)
|
||||
journal_content += render_notes(model, journal, options) unless journal.notes.blank?
|
||||
content_tag "div", journal_content, { :id => "change-#{journal.id}", :class => journal.css_classes }
|
||||
end
|
||||
|
||||
# This renders a journal entry wiht a header and details
|
||||
def render_journal_details(journal, header_label = :label_updated_time_by)
|
||||
header = <<-HTML
|
||||
<h4>
|
||||
<div style="float:right;">#{link_to "##{journal.anchor}", :anchor => "note-#{journal.anchor}"}</div>
|
||||
#{avatar(journal.user, :size => "24")}
|
||||
#{content_tag('a', '', :name => "note-#{journal.anchor}")}
|
||||
#{authoring journal.created_at, journal.user, :label => header_label}
|
||||
</h4>
|
||||
HTML
|
||||
|
||||
if journal.details.any?
|
||||
details = content_tag "ul", :class => "details" do
|
||||
journal.details.collect do |detail|
|
||||
if d = journal.render_detail(detail)
|
||||
content_tag("li", d)
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
end
|
||||
|
||||
content_tag("div", "#{header}#{details}", :id => "change-#{journal.id}", :class => "journal")
|
||||
end
|
||||
|
||||
def render_notes(model, journal, options={})
|
||||
controller = model.class.name.downcase.pluralize
|
||||
action = 'edit'
|
||||
reply_links = authorize_for(controller, action)
|
||||
|
||||
if User.current.logged?
|
||||
editable = User.current.allowed_to?(options[:edit_permission], journal.project) if options[:edit_permission]
|
||||
if journal.user == User.current && options[:edit_own_permission]
|
||||
editable ||= User.current.allowed_to?(options[:edit_own_permission], journal.project)
|
||||
end
|
||||
end
|
||||
|
||||
unless journal.notes.blank?
|
||||
links = returning [] do |l|
|
||||
if reply_links
|
||||
l << link_to_remote(image_tag('comment.png'), :title => l(:button_quote),
|
||||
:url => {:controller => controller, :action => action, :id => model, :journal_id => journal})
|
||||
end
|
||||
if editable
|
||||
l << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
|
||||
{ :controller => 'journals', :action => 'edit', :id => journal },
|
||||
:title => l(:button_edit))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
content = ''
|
||||
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
|
||||
|
||||
# This may conveniently be used by controllers to find journals referred to in the current request
|
||||
def find_optional_journal
|
||||
@journal = Journal.find_by_id(params[:journal_id])
|
||||
end
|
||||
|
||||
def render_reply(journal)
|
||||
user = journal.user
|
||||
text = journal.notes
|
||||
|
||||
# Replaces pre blocks with [...]
|
||||
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
|
||||
content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
|
||||
content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
||||
|
||||
render(:update) do |page|
|
||||
page << "$('notes').value = \"#{escape_javascript content}\";"
|
||||
page.show 'update'
|
||||
page << "Form.Element.focus('notes');"
|
||||
page << "Element.scrollTo('update');"
|
||||
page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
|
||||
end
|
||||
end
|
||||
end
|
111
vendor/plugins/acts_as_journalized/app/models/journal.rb
vendored
Normal file
111
vendor/plugins/acts_as_journalized/app/models/journal.rb
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
# Copyright (c) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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 journal 2
|
||||
# of the License, or (at your option) any later journal.
|
||||
#
|
||||
# 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.
|
||||
|
||||
require_dependency 'journal_formatter'
|
||||
|
||||
# The ActiveRecord model representing journals.
|
||||
class Journal < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
include Comparable
|
||||
include JournalFormatter
|
||||
include JournalDeprecated
|
||||
|
||||
# Make sure each journaled model instance only has unique version ids
|
||||
validates_uniqueness_of :version, :scope => [:journaled_id, :type]
|
||||
belongs_to :journaled
|
||||
belongs_to :user
|
||||
|
||||
# ActiveRecord::Base#changes is an existing method, so before serializing the +changes+ column,
|
||||
# the existing +changes+ method is undefined. The overridden +changes+ method pertained to
|
||||
# dirty attributes, but will not affect the partial updates functionality as that's based on
|
||||
# an underlying +changed_attributes+ method, not +changes+ itself.
|
||||
# undef_method :changes
|
||||
serialize :changes, Hash
|
||||
|
||||
# In conjunction with the included Comparable module, allows comparison of journal records
|
||||
# based on their corresponding version numbers, creation timestamps and IDs.
|
||||
def <=>(other)
|
||||
[version, created_at, id].map(&:to_i) <=> [other.version, other.created_at, other.id].map(&:to_i)
|
||||
end
|
||||
|
||||
# Returns whether the version has a version number of 1. Useful when deciding whether to ignore
|
||||
# the version during reversion, as initial versions have no serialized changes attached. Helps
|
||||
# maintain backwards compatibility.
|
||||
def initial?
|
||||
version < 2
|
||||
end
|
||||
|
||||
# The anchor number for html output
|
||||
def anchor
|
||||
version - 1
|
||||
end
|
||||
|
||||
# Possible shortcut to the associated project
|
||||
def project
|
||||
if journaled.respond_to?(:project)
|
||||
journaled.project
|
||||
elsif journaled.is_a? Project
|
||||
journaled
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def editable_by?(user)
|
||||
journaled.journal_editable_by?(user)
|
||||
end
|
||||
|
||||
def details
|
||||
attributes["changes"] || {}
|
||||
end
|
||||
|
||||
alias_method :changes, :details
|
||||
|
||||
def new_value_for(prop)
|
||||
details[prop.to_s].last if details.keys.include? prop.to_s
|
||||
end
|
||||
|
||||
def old_value_for(prop)
|
||||
details[prop.to_s].first if details.keys.include? prop.to_s
|
||||
end
|
||||
|
||||
# Returns a string of css classes
|
||||
def css_classes
|
||||
s = 'journal'
|
||||
s << ' has-notes' unless notes.blank?
|
||||
s << ' has-details' unless details.empty?
|
||||
s
|
||||
end
|
||||
|
||||
# This is here to allow people to disregard the difference between working with a
|
||||
# Journal and the object it is attached to.
|
||||
# The lookup is as follows:
|
||||
## => Call super if the method corresponds to one of our attributes (will end up in AR::Base)
|
||||
## => Try the journaled object with the same method and arguments
|
||||
## => On error, call super
|
||||
def method_missing(method, *args, &block)
|
||||
return super if attributes[method.to_s]
|
||||
journaled.send(method, *args, &block)
|
||||
rescue NoMethodError => e
|
||||
e.name == method ? super : raise(e)
|
||||
end
|
||||
|
||||
end
|
@ -17,18 +17,24 @@
|
||||
|
||||
class JournalObserver < ActiveRecord::Observer
|
||||
attr_accessor :send_notification
|
||||
|
||||
|
||||
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) if self.send_notification
|
||||
if journal.type == "IssueJournal" and journal.version > 1 and self.send_notification
|
||||
after_create_issue_journal(journal)
|
||||
end
|
||||
clear_notification
|
||||
end
|
||||
|
||||
# Wrap send_notification so it defaults to true, when it's nil
|
||||
def after_create_issue_journal(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
|
||||
|
||||
# Wrap send_notification so it defaults to true, when it's nil
|
||||
def send_notification
|
||||
return true if @send_notification.nil?
|
||||
return @send_notification
|
||||
@ -40,4 +46,5 @@ class JournalObserver < ActiveRecord::Observer
|
||||
def clear_notification
|
||||
@send_notification = true
|
||||
end
|
||||
|
||||
end
|
8
vendor/plugins/acts_as_journalized/app/views/journals/_notes_form.rhtml
vendored
Normal file
8
vendor/plugins/acts_as_journalized/app/views/journals/_notes_form.rhtml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<% 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 %>
|
@ -2,7 +2,8 @@ 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.replace "journal-#{@journal.id}-notes", render_notes(@journal.journaled, @journal,
|
||||
:edit_permission => :edit_issue_notes, :edit_own_permission => :edit_own_issue_notes)
|
||||
page.show "journal-#{@journal.id}-notes"
|
||||
page.remove "journal-#{@journal.id}-form"
|
||||
end
|
17
vendor/plugins/acts_as_journalized/init.rb
vendored
Normal file
17
vendor/plugins/acts_as_journalized/init.rb
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
$LOAD_PATH.unshift File.expand_path("../lib/", __FILE__)
|
||||
|
||||
require "acts_as_journalized"
|
||||
ActiveRecord::Base.send(:include, Redmine::Acts::Journalized)
|
||||
|
||||
require 'dispatcher'
|
||||
Dispatcher.to_prepare do
|
||||
# Model
|
||||
require_dependency "journal"
|
||||
|
||||
# this is for compatibility with current trunk
|
||||
# once the plugin is part of the core, this will not be needed
|
||||
# patches should then be ported onto the core
|
||||
# require_dependency File.dirname(__FILE__) + '/lib/acts_as_journalized/journal_patch'
|
||||
# require_dependency File.dirname(__FILE__) + '/lib/acts_as_journalized/journal_observer_patch'
|
||||
# require_dependency File.dirname(__FILE__) + '/lib/acts_as_journalized/activity_fetcher_patch'
|
||||
end
|
181
vendor/plugins/acts_as_journalized/lib/acts_as_journalized.rb
vendored
Normal file
181
vendor/plugins/acts_as_journalized/lib/acts_as_journalized.rb
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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 journal 2
|
||||
# of the License, or (at your option) any later journal.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
Dir[File.expand_path("../redmine/acts/journalized/*.rb", __FILE__)].each{|f| require f }
|
||||
require_dependency 'lib/ar_condition'
|
||||
|
||||
module Redmine
|
||||
module Acts
|
||||
module Journalized
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
base.extend Versioned
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
def plural_name
|
||||
self.name.underscore.pluralize
|
||||
end
|
||||
|
||||
# A model might provide as many activity_types as it wishes.
|
||||
# Activities are just different search options for the event a model provides
|
||||
def acts_as_activity(options = {})
|
||||
activity_hash = journalized_activity_hash(options)
|
||||
type = activity_hash[:type]
|
||||
acts_as_activity_provider activity_hash
|
||||
unless Redmine::Activity.providers[type].include? self.name
|
||||
Redmine::Activity.register type.to_sym, :class_name => self.name
|
||||
end
|
||||
end
|
||||
|
||||
# This call will add an activity and, if neccessary, start the journaling and
|
||||
# add an event callback on the model.
|
||||
# Versioning and acting as an Event may only be applied once.
|
||||
# To apply more than on activity, use acts_as_activity
|
||||
def acts_as_journalized(options = {}, &block)
|
||||
activity_hash, event_hash, journal_hash = split_option_hashes(options)
|
||||
|
||||
acts_as_activity(activity_hash)
|
||||
|
||||
return if journaled?
|
||||
|
||||
include Options
|
||||
include Changes
|
||||
include Creation
|
||||
include Users
|
||||
include Reversion
|
||||
include Reset
|
||||
include Reload
|
||||
include Permissions
|
||||
include SaveHooks
|
||||
include FormatHooks
|
||||
|
||||
# FIXME: When the transition to the new API is complete, remove me
|
||||
include Deprecated
|
||||
|
||||
journal_class.acts_as_event journalized_event_hash(event_hash)
|
||||
|
||||
(journal_hash[:except] ||= []) << self.primary_key << inheritance_column <<
|
||||
:updated_on << :updated_at << :lock_version << :lft << :rgt
|
||||
prepare_journaled_options(journal_hash)
|
||||
has_many :journals, journal_hash.merge({:class_name => journal_class.name,
|
||||
:foreign_key => "journaled_id"}), &block
|
||||
end
|
||||
|
||||
def journal_class
|
||||
journal_class_name = "#{name.gsub("::", "_")}Journal"
|
||||
if Object.const_defined?(journal_class_name)
|
||||
Object.const_get(journal_class_name)
|
||||
else
|
||||
Object.const_set(journal_class_name, Class.new(Journal)).tap do |c|
|
||||
# Run after the inherited hook to associate with the parent record.
|
||||
# This eager loads the associated project (for permissions) if possible
|
||||
if project_assoc = reflect_on_association(:project).try(:name)
|
||||
include_option = ", :include => :#{project_assoc.to_s}"
|
||||
end
|
||||
c.class_eval("belongs_to :journaled, :class_name => '#{name}' #{include_option}")
|
||||
c.class_eval("belongs_to :#{name.gsub("::", "_").underscore},
|
||||
:foreign_key => 'journaled_id' #{include_option}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Splits an option has into three hashes:
|
||||
## => [{ options prefixed with "activity_" }, { options prefixed with "event_" }, { other options }]
|
||||
def split_option_hashes(options)
|
||||
activity_hash = {}
|
||||
event_hash = {}
|
||||
journal_hash = {}
|
||||
|
||||
options.each_pair do |k, v|
|
||||
case
|
||||
when k.to_s =~ /^activity_(.+)$/
|
||||
activity_hash[$1.to_sym] = v
|
||||
when k.to_s =~ /^event_(.+)$/
|
||||
event_hash[$1.to_sym] = v
|
||||
else
|
||||
journal_hash[k.to_sym] = v
|
||||
end
|
||||
end
|
||||
[activity_hash, event_hash, journal_hash]
|
||||
end
|
||||
|
||||
# Merges the passed activity_hash with the options we require for
|
||||
# acts_as_journalized to work, as follows:
|
||||
# # type is the supplied or the pluralized class name
|
||||
# # timestamp is supplied or the journal's created_at
|
||||
# # author_key will always be the journal's author
|
||||
# #
|
||||
# # find_options are merged as follows:
|
||||
# # # select statement is enriched with the journal fields
|
||||
# # # journal association is added to the includes
|
||||
# # # if a project is associated with the model, this is added to the includes
|
||||
# # # the find conditions are extended to only choose journals which have the proper activity_type
|
||||
# => a valid activity hash
|
||||
def journalized_activity_hash(options)
|
||||
options.tap do |h|
|
||||
h[:type] ||= plural_name
|
||||
h[:timestamp] = "#{journal_class.table_name}.created_at"
|
||||
h[:author_key] = "#{journal_class.table_name}.user_id"
|
||||
|
||||
h[:find_options] ||= {} # in case it is nil
|
||||
h[:find_options] = {}.tap do |opts|
|
||||
cond = ARCondition.new
|
||||
cond.add(["#{journal_class.table_name}.activity_type = ?", h[:type]])
|
||||
cond.add(h[:find_options][:conditions]) if h[:find_options][:conditions]
|
||||
opts[:conditions] = cond.conditions
|
||||
|
||||
include_opts = []
|
||||
include_opts << :project if reflect_on_association(:project)
|
||||
if h[:find_options][:include]
|
||||
include_opts += case h[:find_options][:include]
|
||||
when Array then h[:find_options][:include]
|
||||
else [h[:find_options][:include]]
|
||||
end
|
||||
end
|
||||
include_opts.uniq!
|
||||
opts[:include] = [:journaled => include_opts]
|
||||
|
||||
#opts[:joins] = h[:find_options][:joins] if h[:find_options][:joins]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Merges the event hashes defaults with the options provided by the user
|
||||
# The defaults take their details from the journal
|
||||
def journalized_event_hash(options)
|
||||
unless options.has_key? :url
|
||||
options[:url] = Proc.new do |journal|
|
||||
{ :controller => plural_name,
|
||||
:action => 'show',
|
||||
:id => journal.journaled_id,
|
||||
:anchor => ("note-#{journal.anchor}" unless journal.initial?) }
|
||||
end
|
||||
end
|
||||
{ :description => :notes, :author => :user }.reverse_merge options
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
49
vendor/plugins/acts_as_journalized/lib/journal_deprecated.rb
vendored
Normal file
49
vendor/plugins/acts_as_journalized/lib/journal_deprecated.rb
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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 journal 2
|
||||
# of the License, or (at your option) any later journal.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This module holds the formatting methods that each journal has.
|
||||
# It provides the hooks to apply different formatting to the details
|
||||
# of a specific journal.
|
||||
module JournalDeprecated
|
||||
unloadable
|
||||
# Old timestamps. created_at is what t.timestamps creates in recent Rails journals
|
||||
def created_on
|
||||
created_at
|
||||
end
|
||||
|
||||
# Old naming
|
||||
def journalized
|
||||
journaled
|
||||
end
|
||||
|
||||
# Old naming
|
||||
def journalized= obj
|
||||
journaled = obj
|
||||
end
|
||||
|
||||
|
||||
# Shortcut from more issue-specific journals
|
||||
def attachments
|
||||
journalized.respond_to?(:attachments) ? journalized.attachments : nil
|
||||
end
|
||||
|
||||
# deprecate :created_on => "use #created_at"
|
||||
# deprecate :journalized => "use journaled"
|
||||
# deprecate :attachments => "implement it yourself"
|
||||
end
|
190
vendor/plugins/acts_as_journalized/lib/journal_formatter.rb
vendored
Normal file
190
vendor/plugins/acts_as_journalized/lib/journal_formatter.rb
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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 journal 2
|
||||
# of the License, or (at your option) any later journal.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This module holds the formatting methods that each journal has.
|
||||
# It provides the hooks to apply different formatting to the details
|
||||
# of a specific journal.
|
||||
module JournalFormatter
|
||||
unloadable
|
||||
mattr_accessor :formatters, :registered_fields
|
||||
include ApplicationHelper
|
||||
include CustomFieldsHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
extend Redmine::I18n
|
||||
|
||||
def self.register(hash)
|
||||
if hash[:class]
|
||||
klazz = hash.delete(:class)
|
||||
registered_fields[klazz] ||= {}
|
||||
registered_fields[klazz].merge!(hash)
|
||||
else
|
||||
formatters.merge(hash)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Document Formatters (can take up to three params, value, journaled, field ...)
|
||||
def self.default_formatters
|
||||
{ :plaintext => (Proc.new {|v,*| v.try(:to_s) }),
|
||||
:datetime => (Proc.new {|v,*| format_date(v.to_date) }),
|
||||
:named_association => (Proc.new do |value, journaled, field|
|
||||
association = journaled.class.reflect_on_association(field.to_sym)
|
||||
if association
|
||||
record = association.class_name.constantize.find_by_id(value.to_i)
|
||||
record.name if record
|
||||
end
|
||||
end),
|
||||
:fraction => (Proc.new {|v,*| "%0.02f" % v.to_f }),
|
||||
:decimal => (Proc.new {|v,*| v.to_i.to_s }),
|
||||
:id => (Proc.new {|v,*| "##{v}" }) }
|
||||
end
|
||||
|
||||
self.formatters = default_formatters
|
||||
self.registered_fields = {}
|
||||
|
||||
def format_attribute_detail(key, values, no_html=false)
|
||||
field = key.to_s.gsub(/\_id$/, "")
|
||||
label = l(("field_" + field).to_sym)
|
||||
|
||||
if format = JournalFormatter.registered_fields[self.class.name.to_sym][key]
|
||||
formatter = JournalFormatter.formatters[format]
|
||||
old_value = formatter.call(values.first, journaled, field) if values.first
|
||||
value = formatter.call(values.last, journaled, field) if values.last
|
||||
[label, old_value, value]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def format_custom_value_detail(custom_field, values, no_html)
|
||||
label = custom_field.name
|
||||
old_value = format_value(values.first, custom_field.field_format) if values.first
|
||||
value = format_value(values.last, custom_field.field_format) if values.last
|
||||
|
||||
[label, old_value, value]
|
||||
end
|
||||
|
||||
def format_attachment_detail(key, values, no_html)
|
||||
label = l(:label_attachment)
|
||||
old_value = values.first
|
||||
value = values.last
|
||||
|
||||
[label, old_value, value]
|
||||
end
|
||||
|
||||
def format_html_attachment_detail(key, value)
|
||||
if !value.blank? && a = Attachment.find_by_id(key.to_i)
|
||||
# Link to the attachment if it has not been removed
|
||||
# FIXME: this is broken => link_to_attachment(a)
|
||||
a.filename
|
||||
else
|
||||
content_tag("i", h(value)) if value.present?
|
||||
end
|
||||
end
|
||||
|
||||
def format_html_detail(label, old_value, value)
|
||||
label = content_tag('strong', label)
|
||||
old_value = content_tag("i", h(old_value)) if old_value && !old_value.blank?
|
||||
old_value = content_tag("strike", old_value) if old_value and value.blank?
|
||||
value = content_tag("i", h(value)) if value.present?
|
||||
value ||= ""
|
||||
[label, old_value, value]
|
||||
end
|
||||
|
||||
def property(detail)
|
||||
key = prop_key(detail)
|
||||
if key.start_with? "custom_values"
|
||||
:custom_field
|
||||
elsif key.start_with? "attachments"
|
||||
:attachment
|
||||
elsif journaled.class.columns.collect(&:name).include? key
|
||||
:attribute
|
||||
end
|
||||
end
|
||||
|
||||
def prop_key(detail)
|
||||
if detail.respond_to? :to_ary
|
||||
detail.first
|
||||
else
|
||||
detail
|
||||
end
|
||||
end
|
||||
|
||||
def values(detail)
|
||||
key = prop_key(detail)
|
||||
if detail != key
|
||||
detail.last
|
||||
else
|
||||
details[key.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def old_value(detail)
|
||||
values(detail).first
|
||||
end
|
||||
|
||||
def value(detail)
|
||||
values(detail).last
|
||||
end
|
||||
|
||||
def render_detail(detail, no_html=false)
|
||||
if detail.respond_to? :to_ary
|
||||
key = detail.first
|
||||
values = detail.last
|
||||
else
|
||||
key = detail
|
||||
values = details[key.to_s]
|
||||
end
|
||||
|
||||
case property(detail)
|
||||
when :attribute
|
||||
attr_detail = format_attribute_detail(key, values, no_html)
|
||||
when :custom_field
|
||||
custom_field = CustomField.find_by_id(key.sub("custom_values", "").to_i)
|
||||
cv_detail = format_custom_value_detail(custom_field, values, no_html)
|
||||
when :attachment
|
||||
attachment_detail = format_attachment_detail(key.sub("attachments", ""), values, no_html)
|
||||
end
|
||||
|
||||
label, old_value, value = attr_detail || cv_detail || attachment_detail
|
||||
Redmine::Hook.call_hook :helper_issues_show_detail_after_setting, {:detail => detail,
|
||||
:label => label, :value => value, :old_value => old_value }
|
||||
return nil unless label || old_value || value # print nothing if there are no values
|
||||
label, old_value, value = [label, old_value, value].collect(&:to_s)
|
||||
|
||||
unless no_html
|
||||
label, old_value, value = *format_html_detail(label, old_value, value)
|
||||
value = format_html_attachment_detail(key.sub("attachments", ""), value) if attachment_detail
|
||||
end
|
||||
|
||||
unless value.blank?
|
||||
if attr_detail || cv_detail
|
||||
unless old_value.blank?
|
||||
l(:text_journal_changed, :label => label, :old => old_value, :new => value)
|
||||
else
|
||||
l(:text_journal_set_to, :label => label, :value => value)
|
||||
end
|
||||
elsif attachment_detail
|
||||
l(:text_journal_added, :label => label, :value => value)
|
||||
end
|
||||
else
|
||||
l(:text_journal_deleted, :label => label, :old => old_value)
|
||||
end
|
||||
end
|
||||
end
|
162
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/changes.rb
vendored
Normal file
162
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/changes.rb
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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 journal 2
|
||||
# of the License, or (at your option) any later journal.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Provides the ability to manipulate hashes in the specific format that ActiveRecord gives to
|
||||
# dirty attribute changes: string keys and unique, two-element array values.
|
||||
module Changes
|
||||
def self.included(base) # :nodoc:
|
||||
Hash.send(:include, HashMethods)
|
||||
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
|
||||
after_update :merge_journal_changes
|
||||
end
|
||||
end
|
||||
|
||||
# Methods available to journaled ActiveRecord::Base instances in order to manage changes used
|
||||
# for journal creation.
|
||||
module InstanceMethods
|
||||
# Collects an array of changes from a record's journals between the given range and compiles
|
||||
# them into one summary hash of changes. The +from+ and +to+ arguments can each be either a
|
||||
# version number, a symbol representing an association proxy method, a string representing a
|
||||
# journal tag or a journal object itself.
|
||||
def changes_between(from, to)
|
||||
from_number, to_number = journals.journal_at(from), journals.journal_at(to)
|
||||
return {} if from_number == to_number
|
||||
chain = journals.between(from_number, to_number).reject(&:initial?)
|
||||
return {} if chain.empty?
|
||||
|
||||
backward = from_number > to_number
|
||||
backward ? chain.pop : chain.shift unless from_number == 1 || to_number == 1
|
||||
|
||||
chain.inject({}) do |changes, journal|
|
||||
changes.append_changes!(backward ? journal.changes.reverse_changes : journal.changes)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Before a new journal is created, the newly-changed attributes are appended onto a hash
|
||||
# of previously-changed attributes. Typically the previous changes will be empty, except in
|
||||
# the case that a control block is used where journals are to be merged. See
|
||||
# VestalVersions::Control for more information.
|
||||
def merge_journal_changes
|
||||
journal_changes.append_changes!(incremental_journal_changes)
|
||||
end
|
||||
|
||||
# Stores the cumulative changes that are eventually used for journal creation.
|
||||
def journal_changes
|
||||
@journal_changes ||= {}
|
||||
end
|
||||
|
||||
# Stores the incremental changes that are appended to the cumulative changes before journal
|
||||
# creation. Incremental changes are reset when the record is saved because they represent
|
||||
# a subset of the dirty attribute changes, which are reset upon save.
|
||||
def incremental_journal_changes
|
||||
changes.slice(*journaled_columns)
|
||||
end
|
||||
|
||||
# Simply resets the cumulative changes after journal creation.
|
||||
def reset_journal_changes
|
||||
@journal_changes = nil
|
||||
end
|
||||
end
|
||||
|
||||
# Instance methods included into Hash for dealing with manipulation of hashes in the specific
|
||||
# format of ActiveRecord::Base#changes.
|
||||
module HashMethods
|
||||
# When called on a hash of changes and given a second hash of changes as an argument,
|
||||
# +append_changes+ will run the second hash on top of the first, updating the last element
|
||||
# of each array value with its own, or creating its own key/value pair for missing keys.
|
||||
# Resulting non-unique array values are removed.
|
||||
#
|
||||
# == Example
|
||||
#
|
||||
# first = {
|
||||
# "first_name" => ["Steve", "Stephen"],
|
||||
# "age" => [25, 26]
|
||||
# }
|
||||
# second = {
|
||||
# "first_name" => ["Stephen", "Steve"],
|
||||
# "last_name" => ["Richert", "Jobs"],
|
||||
# "age" => [26, 54]
|
||||
# }
|
||||
# first.append_changes(second)
|
||||
# # => {
|
||||
# "last_name" => ["Richert", "Jobs"],
|
||||
# "age" => [25, 54]
|
||||
# }
|
||||
def append_changes(changes)
|
||||
changes.inject(self) do |new_changes, (attribute, change)|
|
||||
new_change = [new_changes.fetch(attribute, change).first, change.last]
|
||||
new_changes.merge(attribute => new_change)
|
||||
end.reject do |attribute, change|
|
||||
change.first == change.last
|
||||
end
|
||||
end
|
||||
|
||||
# Destructively appends a given hash of changes onto an existing hash of changes.
|
||||
def append_changes!(changes)
|
||||
replace(append_changes(changes))
|
||||
end
|
||||
|
||||
# Appends the existing hash of changes onto a given hash of changes. Relates to the
|
||||
# +append_changes+ method in the same way that Hash#reverse_merge relates to
|
||||
# Hash#merge.
|
||||
def prepend_changes(changes)
|
||||
changes.append_changes(self)
|
||||
end
|
||||
|
||||
# Destructively prepends a given hash of changes onto an existing hash of changes.
|
||||
def prepend_changes!(changes)
|
||||
replace(prepend_changes(changes))
|
||||
end
|
||||
|
||||
# Reverses the array values of a hash of changes. Useful for rejournal both backward and
|
||||
# forward through a record's history of changes.
|
||||
def reverse_changes
|
||||
inject({}){|nc,(a,c)| nc.merge!(a => c.reverse) }
|
||||
end
|
||||
|
||||
# Destructively reverses the array values of a hash of changes.
|
||||
def reverse_changes!
|
||||
replace(reverse_changes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
77
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/configuration.rb
vendored
Normal file
77
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/configuration.rb
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Allows for easy application-wide configuration of options passed into the +journaled+ method.
|
||||
module Configuration
|
||||
# The VestalVersions module is extended by VestalVersions::Configuration, allowing the
|
||||
# +configure method+ to be used as follows in a Rails initializer:
|
||||
#
|
||||
# VestalVersions.configure do |config|
|
||||
# config.class_name = "MyCustomVersion"
|
||||
# config.dependent = :destroy
|
||||
# end
|
||||
#
|
||||
# Each variable assignment in the +configure+ block corresponds directly with the options
|
||||
# available to the +journaled+ method. Assigning common options in an initializer can keep your
|
||||
# models tidy.
|
||||
#
|
||||
# If an option is given in both an initializer and in the options passed to +journaled+, the
|
||||
# value given in the model itself will take precedence.
|
||||
def configure
|
||||
yield Configuration
|
||||
end
|
||||
|
||||
class << self
|
||||
# Simply stores a hash of options given to the +configure+ block.
|
||||
def options
|
||||
@options ||= {}
|
||||
end
|
||||
|
||||
# If given a setter method name, will assign the first argument to the +options+ hash with
|
||||
# the method name (sans "=") as the key. If given a getter method name, will attempt to
|
||||
# a value from the +options+ hash for that key. If the key doesn't exist, defers to +super+.
|
||||
def method_missing(symbol, *args)
|
||||
if (method = symbol.to_s).sub!(/\=$/, '')
|
||||
options[method.to_sym] = args.first
|
||||
else
|
||||
options.fetch(method.to_sym, super)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
127
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/creation.rb
vendored
Normal file
127
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/creation.rb
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Adds the functionality necessary to control journal creation on a journaled instance of
|
||||
# ActiveRecord::Base.
|
||||
module Creation
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
include InstanceMethods
|
||||
|
||||
after_save :create_journal, :if => :create_journal?
|
||||
|
||||
class << self
|
||||
alias_method_chain :prepare_journaled_options, :creation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Class methods added to ActiveRecord::Base to facilitate the creation of new journals.
|
||||
module ClassMethods
|
||||
# Overrides the basal +prepare_journaled_options+ method defined in VestalVersions::Options
|
||||
# to extract the <tt>:only</tt> and <tt>:except</tt> options into +vestal_journals_options+.
|
||||
def prepare_journaled_options_with_creation(options)
|
||||
result = prepare_journaled_options_without_creation(options)
|
||||
|
||||
self.vestal_journals_options[:only] = Array(options.delete(:only)).map(&:to_s).uniq if options[:only]
|
||||
self.vestal_journals_options[:except] = Array(options.delete(:except)).map(&:to_s).uniq if options[:except]
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
# Instance methods that determine whether to save a journal and actually perform the save.
|
||||
module InstanceMethods
|
||||
private
|
||||
# Returns whether a new journal should be created upon updating the parent record.
|
||||
# A new journal will be created if
|
||||
# a) attributes have changed
|
||||
# b) no previous journal exists
|
||||
# c) journal notes were added
|
||||
# d) the parent record is already saved
|
||||
def create_journal?
|
||||
update_journal
|
||||
(journal_changes.present? or journal_notes.present? or journals.empty?) and !new_record?
|
||||
end
|
||||
|
||||
# Creates a new journal upon updating the parent record.
|
||||
# "update_journal" has been called in "update_journal?" at this point (to get a hold on association changes)
|
||||
# It must not be called again here.
|
||||
def create_journal
|
||||
journals << self.class.journal_class.create(journal_attributes)
|
||||
reset_journal_changes
|
||||
reset_journal
|
||||
true
|
||||
rescue Exception => e # FIXME: What to do? This likely means that the parent record is invalid!
|
||||
p e
|
||||
p e.message
|
||||
p e.backtrace
|
||||
false
|
||||
end
|
||||
|
||||
# Returns an array of column names that should be included in the changes of created
|
||||
# journals. If <tt>vestal_journals_options[:only]</tt> is specified, only those columns
|
||||
# will be journaled. Otherwise, if <tt>vestal_journals_options[:except]</tt> is specified,
|
||||
# all columns will be journaled other than those specified. Without either option, the
|
||||
# default is to journal all columns. At any rate, the four "automagic" timestamp columns
|
||||
# maintained by Rails are never journaled.
|
||||
def journaled_columns
|
||||
case
|
||||
when vestal_journals_options[:only] then self.class.column_names & vestal_journals_options[:only]
|
||||
when vestal_journals_options[:except] then self.class.column_names - vestal_journals_options[:except]
|
||||
else self.class.column_names
|
||||
end - %w(created_at updated_at)
|
||||
end
|
||||
|
||||
# Returns the activity type. Should be overridden in the journalized class to offer
|
||||
# multiple types
|
||||
def activity_type
|
||||
self.class.name.underscore.pluralize
|
||||
end
|
||||
|
||||
# Specifies the attributes used during journal creation. This is separated into its own
|
||||
# method so that it can be overridden by the VestalVersions::Users feature.
|
||||
def journal_attributes
|
||||
attributes = { :journaled_id => self.id, :activity_type => activity_type,
|
||||
:changes => journal_changes, :version => last_version + 1,
|
||||
:notes => journal_notes, :user_id => (journal_user.try(:id) || User.current) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
68
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/deprecated.rb
vendored
Normal file
68
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/deprecated.rb
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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.
|
||||
|
||||
# These hooks make sure journals are properly created and updated with Redmine user detail,
|
||||
# notes and associated custom fields
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
module Deprecated
|
||||
# Old mailer API
|
||||
def recipients
|
||||
notified = project.notified_users
|
||||
notified.reject! {|user| !visible?(user)}
|
||||
notified.collect(&:mail)
|
||||
end
|
||||
|
||||
def current_journal
|
||||
last_journal
|
||||
end
|
||||
|
||||
# FIXME: When the new API is settled, remove me
|
||||
Redmine::Acts::Event::InstanceMethods.instance_methods(false).each do |m|
|
||||
if m.start_with? "event_"
|
||||
class_eval(<<-RUBY, __FILE__, __LINE__)
|
||||
def #{m}
|
||||
if last_journal.nil?
|
||||
begin
|
||||
journals << self.class.journal_class.create(journal_attributes)
|
||||
reset_journal_changes
|
||||
reset_journal
|
||||
true
|
||||
rescue Exception => e # FIXME: What to do? This likely means that the parent record is invalid!
|
||||
p e
|
||||
p e.message
|
||||
p e.backtrace
|
||||
false
|
||||
end
|
||||
journals.reload
|
||||
end
|
||||
return last_journal.#{m}
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
def event_url(options = {})
|
||||
last_journal.event_url(options)
|
||||
end
|
||||
|
||||
# deprecate :recipients => "use #last_journal.recipients"
|
||||
# deprecate :current_journal => "use #last_journal"
|
||||
end
|
||||
end
|
42
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/format_hooks.rb
vendored
Normal file
42
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/format_hooks.rb
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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 Redmine::Acts::Journalized
|
||||
module FormatHooks
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Shortcut to register a formatter for a number of fields
|
||||
def register_on_journal_formatter(formatter, *field_names)
|
||||
formatter = formatter.to_sym
|
||||
field_names.collect(&:to_s).each do |field|
|
||||
JournalFormatter.register :class => self.journal_class.name.to_sym, field => formatter
|
||||
end
|
||||
end
|
||||
|
||||
# Shortcut to register a new proc as a named formatter. Overwrites
|
||||
# existing formatters with the same name
|
||||
def register_journal_formatter(formatter)
|
||||
JournalFormatter.register formatter.to_sym => Proc.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
81
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/options.rb
vendored
Normal file
81
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/options.rb
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Provides +journaled+ options conjournal and cleanup.
|
||||
module Options
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
# Class methods that provide preparation of options passed to the +journaled+ method.
|
||||
module ClassMethods
|
||||
# The +prepare_journaled_options+ method has three purposes:
|
||||
# 1. Populate the provided options with default values where needed
|
||||
# 2. Prepare options for use with the +has_many+ association
|
||||
# 3. Save user-configurable options in a class-level variable
|
||||
#
|
||||
# Options are given priority in the following order:
|
||||
# 1. Those passed directly to the +journaled+ method
|
||||
# 2. Those specified in an initializer +configure+ block
|
||||
# 3. Default values specified in +prepare_journaled_options+
|
||||
#
|
||||
# The method is overridden in feature modules that require specific options outside the
|
||||
# standard +has_many+ associations.
|
||||
def prepare_journaled_options(options)
|
||||
options.symbolize_keys!
|
||||
options.reverse_merge!(Configuration.options)
|
||||
options.reverse_merge!(
|
||||
:class_name => 'Journal',
|
||||
:dependent => :delete_all
|
||||
)
|
||||
options.reverse_merge!(
|
||||
:order => "#{options[:class_name].constantize.table_name}.version ASC"
|
||||
)
|
||||
|
||||
class_inheritable_accessor :vestal_journals_options
|
||||
self.vestal_journals_options = options.dup
|
||||
|
||||
options.merge!(
|
||||
:extend => Array(options[:extend]).unshift(Versions)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
36
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/permissions.rb
vendored
Normal file
36
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/permissions.rb
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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 Redmine::Acts::Journalized
|
||||
module Permissions
|
||||
# Default implementation of journal editing permission
|
||||
# Is overridden if defined in the journalized model directly
|
||||
def journal_editable_by?(user)
|
||||
return true if user.admin?
|
||||
if respond_to? :editable_by?
|
||||
editable_by? user
|
||||
else
|
||||
permission = :"edit_#{self.class.to_s.pluralize.downcase}"
|
||||
p = @project || (project if respond_to? :project)
|
||||
options = { :global => p.present? }
|
||||
user.allowed_to? permission, p, options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
60
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/reload.rb
vendored
Normal file
60
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/reload.rb
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Ties into the existing ActiveRecord::Base#reload method to ensure that journal information
|
||||
# is properly reset.
|
||||
module Reload
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
|
||||
alias_method_chain :reload, :journals
|
||||
end
|
||||
end
|
||||
|
||||
# Adds instance methods into ActiveRecord::Base to tap into the +reload+ method.
|
||||
module InstanceMethods
|
||||
# Overrides ActiveRecord::Base#reload, resetting the instance-variable-cached journal number
|
||||
# before performing the original +reload+ method.
|
||||
def reload_with_journals(*args)
|
||||
reset_journal
|
||||
reload_without_journals(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
65
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/reset.rb
vendored
Normal file
65
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/reset.rb
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Adds the ability to "reset" (or hard revert) a journaled ActiveRecord::Base instance.
|
||||
module Reset
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
# Adds the instance methods required to reset an object to a previous journal.
|
||||
module InstanceMethods
|
||||
# Similar to +revert_to!+, the +reset_to!+ method reverts an object to a previous journal,
|
||||
# only instead of creating a new record in the journal history, +reset_to!+ deletes all of
|
||||
# the journal history that occurs after the journal reverted to.
|
||||
#
|
||||
# The action taken on each journal record after the point of rejournal is determined by the
|
||||
# <tt>:dependent</tt> option given to the +journaled+ method. See the +journaled+ method
|
||||
# documentation for more details.
|
||||
def reset_to!(value)
|
||||
if saved = skip_journal{ revert_to!(value) }
|
||||
journals.send(:delete_records, journals.after(value))
|
||||
reset_journal
|
||||
end
|
||||
saved
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
110
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/reversion.rb
vendored
Normal file
110
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/reversion.rb
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Enables versioned ActiveRecord::Base instances to revert to a previously saved version.
|
||||
module Reversion
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
# Provides the base instance methods required to revert a journaled instance.
|
||||
module InstanceMethods
|
||||
# Returns the current version number for the versioned object.
|
||||
def version
|
||||
@version ||= last_version
|
||||
end
|
||||
|
||||
def last_journal
|
||||
journals.last
|
||||
end
|
||||
|
||||
# Accepts a value corresponding to a specific journal record, builds a history of changes
|
||||
# between that journal and the current journal, and then iterates over that history updating
|
||||
# the object's attributes until the it's reverted to its prior state.
|
||||
#
|
||||
# The single argument should adhere to one of the formats as documented in the +at+ method of
|
||||
# VestalVersions::Versions.
|
||||
#
|
||||
# After the object is reverted to the target journal, it is not saved. In order to save the
|
||||
# object after the rejournal, use the +revert_to!+ method.
|
||||
#
|
||||
# The journal number of the object will reflect whatever journal has been reverted to, and
|
||||
# the return value of the +revert_to+ method is also the target journal number.
|
||||
def revert_to(value)
|
||||
to_number = journals.journal_at(value)
|
||||
|
||||
changes_between(journal, to_number).each do |attribute, change|
|
||||
write_attribute(attribute, change.last)
|
||||
end
|
||||
|
||||
reset_journal(to_number)
|
||||
end
|
||||
|
||||
# Behaves similarly to the +revert_to+ method except that it automatically saves the record
|
||||
# after the rejournal. The return value is the success of the save.
|
||||
def revert_to!(value)
|
||||
revert_to(value)
|
||||
reset_journal if saved = save
|
||||
saved
|
||||
end
|
||||
|
||||
# Returns a boolean specifying whether the object has been reverted to a previous journal or
|
||||
# if the object represents the latest journal in the journal history.
|
||||
def reverted?
|
||||
version != last_version
|
||||
end
|
||||
|
||||
private
|
||||
# Returns the number of the last created journal in the object's journal history.
|
||||
#
|
||||
# If no associated journals exist, the object is considered at version 0.
|
||||
def last_version
|
||||
@last_version ||= journals.maximum(:version) || 0
|
||||
end
|
||||
|
||||
# Clears the cached version number instance variables so that they can be recalculated.
|
||||
# Useful after a new version is created.
|
||||
def reset_journal(version = nil)
|
||||
@last_version = nil if version.nil?
|
||||
@version = version
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
115
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb
vendored
Normal file
115
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
# This file is part of the acts_as_journalized plugin for the redMine
|
||||
# project management software
|
||||
#
|
||||
# Copyright (C) 2010 Finn GmbH, http://finn.de
|
||||
#
|
||||
# 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.
|
||||
|
||||
# These hooks make sure journals are properly created and updated with Redmine user detail,
|
||||
# notes and associated custom fields
|
||||
module Redmine::Acts::Journalized
|
||||
module SaveHooks
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
|
||||
base.class_eval do
|
||||
before_save :init_journal
|
||||
after_save :reset_instance_variables
|
||||
|
||||
attr_reader :journal_notes, :journal_user
|
||||
end
|
||||
end
|
||||
|
||||
# Saves the current custom values, notes and journal to include them in the next journal
|
||||
# Called before save
|
||||
def init_journal(user = User.current, notes = "")
|
||||
@journal_notes ||= notes
|
||||
@journal_user ||= user
|
||||
@associations_before_save ||= {}
|
||||
|
||||
@associations = {}
|
||||
save_possible_association :custom_values, :key => :custom_field_id, :value => :value
|
||||
save_possible_association :attachments, :key => :id, :value => :filename
|
||||
|
||||
@current_journal ||= last_journal
|
||||
end
|
||||
|
||||
# Saves the notes and custom value changes in the last Journal
|
||||
# Called before create_journal
|
||||
def update_journal
|
||||
unless (@associations || {}).empty?
|
||||
changed_associations = {}
|
||||
changed_associations.merge!(possibly_updated_association :custom_values)
|
||||
changed_associations.merge!(possibly_updated_association :attachments)
|
||||
end
|
||||
|
||||
unless changed_associations.blank?
|
||||
update_extended_journal_contents(changed_associations)
|
||||
end
|
||||
end
|
||||
|
||||
def reset_instance_variables
|
||||
if last_journal != @current_journal
|
||||
if last_journal.user != @journal_user
|
||||
last_journal.update_attribute(:user_id, @journal_user.id)
|
||||
end
|
||||
end
|
||||
@associations_before_save = @current_journal = @journal_notes = @journal_user = nil
|
||||
end
|
||||
|
||||
def save_possible_association(method, options)
|
||||
@associations[method] = options
|
||||
if self.respond_to? method
|
||||
@associations_before_save[method] ||= send(method).inject({}) do |hash, cv|
|
||||
hash[cv.send(options[:key])] = cv.send(options[:value])
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def possibly_updated_association(method)
|
||||
if @associations_before_save[method]
|
||||
# Has custom values from init_journal_notes
|
||||
return changed_associations(method, @associations_before_save[method])
|
||||
end
|
||||
{}
|
||||
end
|
||||
|
||||
# Saves the notes and changed custom values to the journal
|
||||
# Creates a new journal, if no immediate attributes were changed
|
||||
def update_extended_journal_contents(changed_associations)
|
||||
journal_changes.merge!(changed_associations)
|
||||
end
|
||||
|
||||
def changed_associations(method, previous)
|
||||
send(method).reload # Make sure the associations are reloaded
|
||||
send(method).inject({}) do |hash, c|
|
||||
key = c.send(@associations[method][:key])
|
||||
new_value = c.send(@associations[method][:value])
|
||||
|
||||
if previous[key].blank? && new_value.blank?
|
||||
# The key was empty before, don't add a blank value
|
||||
elsif previous[key] != new_value
|
||||
# The key's value changed
|
||||
hash["#{method}#{key}"] = [previous[key], new_value]
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
86
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/users.rb
vendored
Normal file
86
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/users.rb
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Provides a way for information to be associated with specific journals as to who was
|
||||
# responsible for the associated update to the parent.
|
||||
module Users
|
||||
def self.included(base) # :nodoc:
|
||||
Journal.send(:include, JournalMethods)
|
||||
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
|
||||
attr_accessor :updated_by
|
||||
alias_method_chain :journal_attributes, :user
|
||||
end
|
||||
end
|
||||
|
||||
# Methods added to journaled ActiveRecord::Base instances to enable journaling with additional
|
||||
# user information.
|
||||
module InstanceMethods
|
||||
private
|
||||
# Overrides the +journal_attributes+ method to include user information passed into the
|
||||
# parent object, by way of a +updated_by+ attr_accessor.
|
||||
def journal_attributes_with_user
|
||||
journal_attributes_without_user.merge(:user => updated_by || User.current)
|
||||
end
|
||||
end
|
||||
|
||||
# Instance methods added to Redmine::Acts::Journalized::Journal to accomodate incoming
|
||||
# user information.
|
||||
module JournalMethods
|
||||
def self.included(base) # :nodoc:
|
||||
base.class_eval do
|
||||
belongs_to :user
|
||||
|
||||
alias_method_chain :user=, :name
|
||||
end
|
||||
end
|
||||
|
||||
# Overrides the +user=+ method created by the polymorphic +belongs_to+ user association.
|
||||
# Based on the class of the object given, either the +user+ association columns or the
|
||||
# +user_name+ string column is populated.
|
||||
def user_with_name=(value)
|
||||
case value
|
||||
when ActiveRecord::Base then self.user_without_name = value
|
||||
else self.user = User.find_by_login(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
67
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/versioned.rb
vendored
Normal file
67
vendor/plugins/acts_as_journalized/lib/redmine/acts/journalized/versioned.rb
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
# This file included as part of the acts_as_journalized plugin for
|
||||
# the redMine project management 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.
|
||||
#
|
||||
# The original copyright and license conditions are:
|
||||
# Copyright (c) 2009 Steve Richert
|
||||
#
|
||||
# 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.
|
||||
|
||||
module Redmine::Acts::Journalized
|
||||
# Simply adds a flag to determine whether a model class if journaled.
|
||||
module Versioned
|
||||
def self.extended(base) # :nodoc:
|
||||
base.class_eval do
|
||||
class << self
|
||||
alias_method_chain :acts_as_journalized, :flag
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Overrides the +journaled+ method to first define the +journaled?+ class method before
|
||||
# deferring to the original +journaled+.
|
||||
def acts_as_journalized_with_flag(*args)
|
||||
acts_as_journalized_without_flag(*args)
|
||||
|
||||
class << self
|
||||
def journaled?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# For all ActiveRecord::Base models that do not call the +journaled+ method, the +journaled?+
|
||||
# method will return false.
|
||||
def journaled?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user