Merge branch 'ticket/unstable/808-issue-description-diff' into unstable
This commit is contained in:
commit
f835420383
@ -12,6 +12,8 @@
|
|||||||
# See doc/COPYRIGHT.rdoc for more details.
|
# See doc/COPYRIGHT.rdoc for more details.
|
||||||
#++
|
#++
|
||||||
|
|
||||||
|
require 'diff'
|
||||||
|
|
||||||
class JournalsController < ApplicationController
|
class JournalsController < ApplicationController
|
||||||
before_filter :find_journal, :only => [:edit, :diff]
|
before_filter :find_journal, :only => [:edit, :diff]
|
||||||
before_filter :find_issue, :only => [:new]
|
before_filter :find_issue, :only => [:new]
|
||||||
@ -84,6 +86,22 @@ class JournalsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def diff
|
||||||
|
if valid_field?(params[:field])
|
||||||
|
from = @journal.changes[params[:field]][0]
|
||||||
|
to = @journal.changes[params[:field]][1]
|
||||||
|
|
||||||
|
@diff = Redmine::Helpers::Diff.new(to, from)
|
||||||
|
@issue = @journal.journaled
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { }
|
||||||
|
format.js { render :layout => false }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
render_404
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_journal
|
def find_journal
|
||||||
@ -100,4 +118,9 @@ class JournalsController < ApplicationController
|
|||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Is this a valid field for diff'ing?
|
||||||
|
def valid_field?(field)
|
||||||
|
field.to_s.strip == "description"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
9
app/views/journals/diff.html.erb
Normal file
9
app/views/journals/diff.html.erb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<p><%= authoring @journal.created_at, @journal.user, :label => :label_updated_time_by %></p>
|
||||||
|
|
||||||
|
<div class="text-diff">
|
||||||
|
<%= simple_format_without_paragraph @diff.to_html %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p><%= link_to(l(:button_back), issue_path(@issue)) %></p>
|
||||||
|
|
||||||
|
<% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
|
@ -145,6 +145,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
|
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
|
||||||
|
<div id="dialog-window" style="display:none;"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<%= call_hook :view_layouts_base_body_bottom %>
|
<%= call_hook :view_layouts_base_body_bottom %>
|
||||||
|
@ -95,7 +95,8 @@ ActionController::Routing::Routes.draw do |map|
|
|||||||
map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
|
map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post }
|
||||||
map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
|
map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post }
|
||||||
map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
|
map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy
|
||||||
|
map.journal_diff '/journals/:id/diff/:field', :controller => 'journals', :action => 'diff', :conditions => { :method => :get }
|
||||||
|
|
||||||
map.resource :gantt, :path_prefix => '/issues', :controller => 'gantts', :only => [:show, :update]
|
map.resource :gantt, :path_prefix => '/issues', :controller => 'gantts', :only => [:show, :update]
|
||||||
map.resource :gantt, :path_prefix => '/projects/:project_id/issues', :controller => 'gantts', :only => [:show, :update]
|
map.resource :gantt, :path_prefix => '/projects/:project_id/issues', :controller => 'gantts', :only => [:show, :update]
|
||||||
map.resource :calendar, :path_prefix => '/issues', :controller => 'calendars', :only => [:show, :update]
|
map.resource :calendar, :path_prefix => '/issues', :controller => 'calendars', :only => [:show, :update]
|
||||||
|
@ -465,6 +465,15 @@ jQuery.viewportHeight = function() {
|
|||||||
document.body.clientHeight;
|
document.body.clientHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Automatically use format.js for jQuery Ajax
|
||||||
|
jQuery.ajaxSetup({
|
||||||
|
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show/hide the ajax indicators
|
||||||
|
jQuery("#ajax-indicator").ajaxStart(function(){ jQuery(this).show().css('z-index', '9999'); });
|
||||||
|
jQuery("#ajax-indicator").ajaxStop(function(){ jQuery(this).hide(); });
|
||||||
|
|
||||||
/* TODO: integrate with existing code and/or refactor */
|
/* TODO: integrate with existing code and/or refactor */
|
||||||
jQuery(document).ready(function($) {
|
jQuery(document).ready(function($) {
|
||||||
|
|
||||||
@ -608,4 +617,31 @@ jQuery(document).ready(function($) {
|
|||||||
$('#nav-login-content').click(function(event){
|
$('#nav-login-content').click(function(event){
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.lightbox-ajax').click(function(event) {
|
||||||
|
$('#dialog-window').
|
||||||
|
html('').
|
||||||
|
load(this.href, function() {
|
||||||
|
// Set width to the content width
|
||||||
|
var width = $('#content').width();
|
||||||
|
$(this).dialog("option", "width", width).dialog('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configures the default dialog window
|
||||||
|
function setUpDialogWindow() {
|
||||||
|
$('#dialog-window').
|
||||||
|
dialog({
|
||||||
|
autoOpen: false,
|
||||||
|
minWidth: 400,
|
||||||
|
width: 800
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpDialogWindow();
|
||||||
});
|
});
|
||||||
|
@ -81,4 +81,68 @@ class JournalsControllerTest < ActionController::TestCase
|
|||||||
assert_select_rjs :show, "update"
|
assert_select_rjs :show, "update"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "#diff" do
|
||||||
|
setup do
|
||||||
|
@request.session[:user_id] = 1
|
||||||
|
@issue = Issue.find(6)
|
||||||
|
@previous_description = @issue.description
|
||||||
|
@new_description = "New description"
|
||||||
|
|
||||||
|
assert_difference("Journal.count") do
|
||||||
|
@issue.description = @new_description
|
||||||
|
assert @issue.save
|
||||||
|
end
|
||||||
|
@last_journal = @issue.last_journal
|
||||||
|
end
|
||||||
|
|
||||||
|
context "without a valid journal" do
|
||||||
|
should "return a 404" do
|
||||||
|
get :diff, :id => '0'
|
||||||
|
assert_response :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
context "with no field parameter" do
|
||||||
|
should "return a 404" do
|
||||||
|
get :diff, :id => @last_journal.id
|
||||||
|
assert_response :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for an invalid field" do
|
||||||
|
should "return a 404" do
|
||||||
|
get :diff, :id => @last_journal.id, :field => 'id'
|
||||||
|
assert_response :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "without permission to view_issues" do
|
||||||
|
should "return a 403" do
|
||||||
|
@request.session[:user_id] = 7
|
||||||
|
get :diff, :id => @last_journal.id, :field => 'description'
|
||||||
|
|
||||||
|
assert_response :forbidden
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with permission to view_issues" do
|
||||||
|
setup do
|
||||||
|
get :diff, :id => @last_journal.id, :field => 'description'
|
||||||
|
end
|
||||||
|
|
||||||
|
should "create a diff" do
|
||||||
|
assert_not_nil assigns(:diff)
|
||||||
|
assert assigns(:diff).is_a?(Redmine::Helpers::Diff)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "render an inline diff" do
|
||||||
|
assert_select "#content .text-diff"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
47
test/integration/journals_test.rb
Normal file
47
test/integration/journals_test.rb
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#-- encoding: UTF-8
|
||||||
|
#-- copyright
|
||||||
|
# ChiliProject is a project management system.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2010-2011 the ChiliProject Team
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
#++
|
||||||
|
require File.expand_path('../../test_helper', __FILE__)
|
||||||
|
require 'capybara/rails'
|
||||||
|
|
||||||
|
class JournalsTest < ActionController::IntegrationTest
|
||||||
|
fixtures :all
|
||||||
|
|
||||||
|
include IntegrationTestHelpers::CapybaraHelpers
|
||||||
|
include Capybara::DSL
|
||||||
|
|
||||||
|
test "showing issue description changes as a diff" do
|
||||||
|
# Description change
|
||||||
|
@issue = Issue.find(1)
|
||||||
|
@issue.recreate_initial_journal!
|
||||||
|
@issue.reload
|
||||||
|
assert_difference("Journal.count") do
|
||||||
|
@issue.journal_user = User.find_by_login('jsmith')
|
||||||
|
@issue.description = "A new description"
|
||||||
|
assert @issue.save
|
||||||
|
end
|
||||||
|
|
||||||
|
log_user('jsmith', 'jsmith')
|
||||||
|
|
||||||
|
# Issue page
|
||||||
|
visit_issue_page(@issue)
|
||||||
|
assert has_selector?("#history .journal-attributes li i", :text => 'A new description')
|
||||||
|
within("#history .journal-attributes li") do
|
||||||
|
find_link("More").click
|
||||||
|
end
|
||||||
|
|
||||||
|
# Diff page
|
||||||
|
assert_response :success
|
||||||
|
assert has_selector?("#content .text-diff", :text => /A new description/)
|
||||||
|
end
|
||||||
|
end
|
@ -109,6 +109,10 @@ class RoutingTest < ActionController::IntegrationTest
|
|||||||
should_route :post, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_update'
|
should_route :post, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_update'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "journals" do
|
||||||
|
should_route :get, "/journals/100/diff/description", :controller => 'journals', :action => 'diff', :id => '100', :field => 'description'
|
||||||
|
end
|
||||||
|
|
||||||
context "issue categories" do
|
context "issue categories" do
|
||||||
should_route :get, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
|
should_route :get, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ module IntegrationTestHelpers
|
|||||||
visit "/login"
|
visit "/login"
|
||||||
fill_in 'Login', :with => user
|
fill_in 'Login', :with => user
|
||||||
fill_in 'Password', :with => password
|
fill_in 'Password', :with => password
|
||||||
click_button 'login'
|
click_button 'Login'
|
||||||
assert_response :success
|
assert_response :success
|
||||||
assert User.current.logged?
|
assert User.current.logged?
|
||||||
end
|
end
|
||||||
|
@ -28,6 +28,7 @@ module JournalFormatter
|
|||||||
include CustomFieldsHelper
|
include CustomFieldsHelper
|
||||||
include ActionView::Helpers::TagHelper
|
include ActionView::Helpers::TagHelper
|
||||||
include ActionView::Helpers::UrlHelper
|
include ActionView::Helpers::UrlHelper
|
||||||
|
include ActionView::Helpers::TextHelper
|
||||||
include ActionController::UrlWriter
|
include ActionController::UrlWriter
|
||||||
extend Redmine::I18n
|
extend Redmine::I18n
|
||||||
|
|
||||||
@ -122,6 +123,16 @@ module JournalFormatter
|
|||||||
[label, old_value, value]
|
[label, old_value, value]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Formats a detail to be used with a Journal diff
|
||||||
|
#
|
||||||
|
# Truncates the content. Adds a link to view a diff.
|
||||||
|
def format_html_diff_detail(key, label, old_value, value)
|
||||||
|
link = link_to(l(:label_more), {:controller => 'journals', :action => 'diff', :id => id, :field => key.to_s}, :class => 'lightbox-ajax')
|
||||||
|
old_value = truncate(old_value, :length => 80)
|
||||||
|
value = truncate(value, :length => 80) + " " + link
|
||||||
|
[old_value, value]
|
||||||
|
end
|
||||||
|
|
||||||
def property(detail)
|
def property(detail)
|
||||||
key = prop_key(detail)
|
key = prop_key(detail)
|
||||||
if key.start_with? "custom_values"
|
if key.start_with? "custom_values"
|
||||||
@ -186,6 +197,9 @@ module JournalFormatter
|
|||||||
unless no_html
|
unless no_html
|
||||||
label, old_value, value = *format_html_detail(label, old_value, value)
|
label, old_value, value = *format_html_detail(label, old_value, value)
|
||||||
value = format_html_attachment_detail(key.sub("attachments", ""), value) if attachment_detail
|
value = format_html_attachment_detail(key.sub("attachments", ""), value) if attachment_detail
|
||||||
|
if property(detail) == :attribute && key == "description"
|
||||||
|
old_value, value = *format_html_diff_detail(key, label, old_value, value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unless value.blank?
|
unless value.blank?
|
||||||
|
Loading…
x
Reference in New Issue
Block a user