Conflicts: doc/CHANGELOG.mdpull/513/head
commit
0c1490d1e4
@ -0,0 +1,55 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2013 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 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. |
||||
# |
||||
# 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. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class WorkPackageCustomFieldDataMigration < ActiveRecord::Migration |
||||
def self.up |
||||
ActiveRecord::Base.connection.execute <<-SQL |
||||
UPDATE #{custom_fields_table} |
||||
SET type = #{quote_value('WorkPackageCustomfield')} |
||||
WHERE type = #{quote_value('IssueCustomField')} |
||||
SQL |
||||
end |
||||
|
||||
def self.down |
||||
ActiveRecord::Base.connection.execute <<-SQL |
||||
UPDATE #{custom_fields_table} |
||||
SET type = #{quote_value('IssueCustomField')} |
||||
WHERE type = #{quote_value('WorkPackageCustomfield')} |
||||
SQL |
||||
end |
||||
|
||||
private |
||||
|
||||
def custom_fields_table |
||||
@settings_table ||= ActiveRecord::Base.connection.quote_table_name('custom_fields') |
||||
end |
||||
|
||||
def quote_value s |
||||
ActiveRecord::Base.connection.quote(s) |
||||
end |
||||
end |
@ -0,0 +1,31 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# |
||||
# Copyright (C) 2012-2013 the OpenProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require_relative 'migration_utils/attachable_utils' |
||||
|
||||
class RepairWorkPackagesInitialAttachableJournal < ActiveRecord::Migration |
||||
include Migration::Utils |
||||
|
||||
LEGACY_JOURNAL_TYPE = 'IssueJournal' |
||||
JOURNAL_TYPE = 'WorkPackage' |
||||
|
||||
def up |
||||
say_with_time_silently "Repair initial attachable journals" do |
||||
repair_attachable_journal_entries(JOURNAL_TYPE, LEGACY_JOURNAL_TYPE) |
||||
end |
||||
end |
||||
|
||||
def down |
||||
say_with_time_silently "Remove initial attachable journals" do |
||||
remove_initial_journal_entries(JOURNAL_TYPE, LEGACY_JOURNAL_TYPE) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,54 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# |
||||
# Copyright (C) 2012-2013 the OpenProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require_relative 'migration_utils/attachable_utils' |
||||
|
||||
class RepairMessagesInitialAttachableJournal < ActiveRecord::Migration |
||||
include Migration::Utils |
||||
|
||||
JOURNAL_TYPE = 'Message' |
||||
|
||||
def up |
||||
say_with_time_silently "Repair initial attachable journals" do |
||||
result = missing_message_attachments |
||||
|
||||
repair_initial_journals(result, JOURNAL_TYPE) |
||||
end |
||||
end |
||||
|
||||
def down |
||||
say_with_time_silently "Remove initial attachable journals" do |
||||
result = missing_message_attachments |
||||
|
||||
remove_initial_journals(result, JOURNAL_TYPE) |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def missing_message_attachments |
||||
result = select_all <<-SQL |
||||
SELECT a.id, a.container_id, a.filename, last_version |
||||
FROM attachments AS a |
||||
JOIN (SELECT journable_id, MAX(version) AS last_version FROM journals |
||||
WHERE journable_type = '#{JOURNAL_TYPE}' |
||||
GROUP BY journable_id) AS j ON (a.container_id = j.journable_id) |
||||
WHERE container_type = '#{JOURNAL_TYPE}' |
||||
SQL |
||||
|
||||
result.each_with_object([]) do |row, a| |
||||
a << MissingAttachment.new(row['container_id'], |
||||
row['id'], |
||||
row['filename'], |
||||
row['last_version']) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,148 @@ |
||||
# OpenProject is a project management system. |
||||
# |
||||
# Copyright (C) 2012-2013 the OpenProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require_relative 'utils' |
||||
|
||||
module Migration |
||||
module Utils |
||||
MissingAttachment = Struct.new(:journaled_id, |
||||
:attachment_id, |
||||
:filename, |
||||
:last_version) |
||||
|
||||
def repair_attachable_journal_entries(journal_type, legacy_journal_type) |
||||
result = invalid_attachments(legacy_journal_type) |
||||
|
||||
repair_initial_journals(result, journal_type) |
||||
end |
||||
|
||||
def remove_initial_journal_entries(journal_type, legacy_journal_type) |
||||
result = invalid_attachments(legacy_journal_type) |
||||
|
||||
remove_initial_journals(result, journal_type) |
||||
end |
||||
|
||||
def repair_initial_journals(result, journal_type) |
||||
result.each do |m| |
||||
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, journal_type) |
||||
|
||||
journal_ids.each do |journal_id| |
||||
insert <<-SQL |
||||
INSERT INTO attachable_journals (journal_id, attachment_id, filename) |
||||
VALUES (#{journal_id}, #{m.attachment_id}, '#{m.filename}') |
||||
SQL |
||||
end |
||||
end |
||||
end |
||||
|
||||
def remove_initial_journals(result, journal_type) |
||||
result.each do |m| |
||||
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, journal_type) |
||||
|
||||
delete <<-SQL |
||||
DELETE FROM attachable_journals |
||||
WHERE journal_id IN (#{journal_ids.join(", ")}) |
||||
SQL |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
COLUMNS = ['changed_data', 'version', 'journaled_id'] |
||||
|
||||
def invalid_attachments(legacy_journal_type) |
||||
result = [] |
||||
|
||||
update_column_values('legacy_journals', |
||||
COLUMNS, |
||||
find_work_packages_with_missing_initial_attachment(legacy_journal_type, |
||||
result), |
||||
filter(legacy_journal_type)) |
||||
|
||||
result.flatten |
||||
end |
||||
|
||||
def filter(legacy_journal_type) |
||||
"type = '#{legacy_journal_type}' AND changed_data LIKE '%attachments%'" |
||||
end |
||||
|
||||
def find_work_packages_with_missing_initial_attachment(legacy_journal_type, result) |
||||
Proc.new do |row| |
||||
missing_entries = missing_initial_attachable_journals(legacy_journal_type, |
||||
row['id'], |
||||
row['journaled_id'], |
||||
row['version'], |
||||
row['changed_data']) |
||||
|
||||
result << missing_entries unless missing_entries.empty? |
||||
|
||||
UpdateResult.new(row, false) |
||||
end |
||||
end |
||||
|
||||
def missing_initial_attachable_journals(legacy_journal_type, journal_id, journaled_id, version, changed_data) |
||||
removed_attachments = parse_attachment_removals(changed_data) |
||||
|
||||
missing_entries = missing_initial_attachment_entries(legacy_journal_type, |
||||
journaled_id, |
||||
version, |
||||
removed_attachments) |
||||
|
||||
missing_entries.map { |e| MissingAttachment.new(journaled_id, |
||||
e[:id], |
||||
e[:filename], |
||||
version.to_i - 1) } |
||||
end |
||||
|
||||
############################################ |
||||
# Matches attachment removals of the form: # |
||||
# # |
||||
# attachments<id>: # |
||||
# - # |
||||
# - <filename> # |
||||
############################################ |
||||
ATTACHMENT_REMOVAL_REGEX = /attachments_(?<id>\d+): \n-\s(?<filename>.+)\n-\s$/ |
||||
|
||||
def parse_attachment_removals(changed_data) |
||||
matches = changed_data.scan(ATTACHMENT_REMOVAL_REGEX) |
||||
|
||||
matches.each_with_object([]) { |m, l| l << { id: m[0], filename: m[1] } } |
||||
end |
||||
|
||||
def missing_initial_attachment_entries(legacy_journal_type, journaled_id, version, attachments) |
||||
attachments.select do |a| |
||||
result = select_all <<-SQL |
||||
SELECT version |
||||
FROM legacy_journals |
||||
WHERE journaled_id = #{journaled_id} |
||||
AND type = '#{legacy_journal_type}' |
||||
AND version < #{version} |
||||
AND changed_data LIKE '%attachments#{a[:id]}:%' |
||||
AND changed_data LIKE '%- #{a[:filename]}%' |
||||
ORDER BY version |
||||
SQL |
||||
|
||||
result.empty? |
||||
end |
||||
end |
||||
|
||||
def affected_journal_ids(journaled_id, last_version, journal_type) |
||||
result_set = select_all <<-SQL |
||||
SELECT id |
||||
FROM journals |
||||
WHERE journable_id = #{journaled_id} |
||||
AND journable_type = '#{journal_type}' |
||||
AND version <= #{last_version} |
||||
SQL |
||||
|
||||
result_set.collect { |r| r['id'] } |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,41 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2013 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 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. |
||||
# |
||||
# 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. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject |
||||
module Journal |
||||
module AttachmentHelper |
||||
def attachments_changed(obj) |
||||
unless new_record? |
||||
add_journal |
||||
save |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,169 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2013 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 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. |
||||
# |
||||
# 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. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe MessagesController do |
||||
|
||||
let(:user) { FactoryGirl.create(:user) } |
||||
let(:project) { FactoryGirl.create(:project) } |
||||
let(:role) { FactoryGirl.create(:role) } |
||||
let!(:member) { FactoryGirl.create(:member, |
||||
project: project, |
||||
principal: user, |
||||
roles: [role]) } |
||||
let!(:board) { FactoryGirl.create(:board, |
||||
project: project) } |
||||
let(:filename) { "test1.test" } |
||||
|
||||
before { User.stub(:current).and_return user } |
||||
|
||||
describe :create do |
||||
context :attachments do |
||||
# see ticket #2464 on OpenProject.org |
||||
context "new attachment on new messages" do |
||||
before do |
||||
controller.should_receive(:authorize).and_return(true) |
||||
|
||||
Attachment.any_instance.stub(:filename).and_return(filename) |
||||
Attachment.any_instance.stub(:copy_file_to_destination) |
||||
|
||||
post 'create', board_id: board.id, |
||||
message: { subject: "Test created message", |
||||
content: "Messsage body" }, |
||||
attachments: { file: { file: filename, |
||||
description: '' } } |
||||
end |
||||
|
||||
describe :journal do |
||||
let(:attachment_id) { "attachments_#{Message.last.attachments.first.id}".to_sym } |
||||
|
||||
subject { Message.last.journals.last.changed_data } |
||||
|
||||
it { should have_key attachment_id } |
||||
|
||||
it { subject[attachment_id].should eq([nil, filename]) } |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe :attachment do |
||||
let!(:message) { FactoryGirl.create(:message) } |
||||
let(:attachment_id) { "attachments_#{message.attachments.first.id}".to_sym } |
||||
let(:params) { { id: message.id, |
||||
attachments: { '1' => { file: filename, |
||||
description: '' } } } } |
||||
|
||||
describe :add do |
||||
before do |
||||
Message.any_instance.stub(:editable_by?).and_return(true) |
||||
|
||||
Attachment.any_instance.stub(:filename).and_return(filename) |
||||
Attachment.any_instance.stub(:copy_file_to_destination) |
||||
end |
||||
|
||||
context "invalid attachment" do |
||||
let(:max_filesize) { Setting.attachment_max_size.to_i.kilobytes } |
||||
|
||||
before do |
||||
Attachment.any_instance.stub(:filesize).and_return(max_filesize + 1) |
||||
|
||||
post :update, params |
||||
end |
||||
|
||||
describe :view do |
||||
subject { response } |
||||
|
||||
it { should render_template('messages/edit', formats: ["html"]) } |
||||
end |
||||
|
||||
describe :error do |
||||
subject { assigns(:message).errors.messages } |
||||
|
||||
it { should have_key(:attachments) } |
||||
|
||||
it { subject[:attachments] =~ /too long/ } |
||||
end |
||||
end |
||||
|
||||
context :journal do |
||||
before do |
||||
put :update, params |
||||
|
||||
message.reload |
||||
end |
||||
|
||||
describe :key do |
||||
subject { message.journals.last.changed_data } |
||||
|
||||
it { should have_key attachment_id } |
||||
end |
||||
|
||||
describe :value do |
||||
subject { message.journals.last.changed_data[attachment_id].last } |
||||
|
||||
it { should eq(filename) } |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe :remove do |
||||
let!(:attachment) { FactoryGirl.create(:attachment, |
||||
container: message, |
||||
author: user, |
||||
filename: filename) } |
||||
let!(:attachable_journal) { FactoryGirl.create(:journal_attachable_journal, |
||||
journal: message.journals.last, |
||||
attachment: attachment, |
||||
filename: filename) } |
||||
|
||||
before do |
||||
message.reload |
||||
message.attachments.delete(attachment) |
||||
message.reload |
||||
end |
||||
|
||||
context :journal do |
||||
let(:attachment_id) { "attachments_#{attachment.id}".to_sym } |
||||
|
||||
describe :key do |
||||
subject { message.journals.last.changed_data } |
||||
|
||||
it { should have_key attachment_id } |
||||
end |
||||
|
||||
describe :value do |
||||
subject { message.journals.last.changed_data[attachment_id].first } |
||||
|
||||
it { should eq(filename) } |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,32 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2013 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 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. |
||||
# |
||||
# 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. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
FactoryGirl.define do |
||||
factory :journal_message_journal, class: Journal::MessageJournal do |
||||
end |
||||
end |
Loading…
Reference in new issue