Conflicts: doc/CHANGELOG.mdpull/457/head
commit
7ed41c47d5
@ -0,0 +1,81 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
# resolves either a given status (show) or returns a list of available statuses |
||||
# if the controller is called nested inside a project, it returns only the |
||||
# statuses that can be reached by the workflows of the project |
||||
module Api |
||||
module V2 |
||||
|
||||
class StatusesController < ApplicationController |
||||
include PaginationHelper |
||||
|
||||
include ::Api::V2::ApiController |
||||
rescue_from ActiveRecord::RecordNotFound, with: lambda{render_404} |
||||
|
||||
extend Pagination::Controller |
||||
paginate_model IssueStatus |
||||
|
||||
unloadable |
||||
|
||||
before_filter :require_login |
||||
before_filter :resolve_project |
||||
accept_key_auth :index, :show |
||||
|
||||
def index |
||||
if @project |
||||
@statuses = Type.issue_statuses(@project.types.map(&:id)) |
||||
else |
||||
visible_type_ids = Project.visible |
||||
.includes(:types) |
||||
.map(&:types).flatten |
||||
.map(&:id) |
||||
@statuses = Type.issue_statuses(visible_type_ids) |
||||
end |
||||
|
||||
respond_to do |format| |
||||
format.api |
||||
end |
||||
end |
||||
|
||||
def show |
||||
@status = IssueStatus.find(params[:id]) |
||||
|
||||
respond_to do |format| |
||||
format.api |
||||
end |
||||
end |
||||
|
||||
protected |
||||
def resolve_project |
||||
@project = Project.find(params[:project_id]) if params[:project_id] |
||||
end |
||||
end |
||||
end |
||||
|
||||
end |
@ -0,0 +1,57 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
class Journal::BaseJournal < ActiveRecord::Base |
||||
self.abstract_class = true |
||||
|
||||
belongs_to :journal |
||||
|
||||
def journaled_attributes |
||||
attributes.symbolize_keys.select{|k,_| self.class.journaled_attributes.include? k} |
||||
end |
||||
|
||||
private |
||||
|
||||
def self.journaled_attributes |
||||
@journaled_attributes ||= column_names.map{ |n| n.to_sym} - excluded_attributes |
||||
end |
||||
|
||||
def self.column_names |
||||
db_columns(table_name).map(&:name) |
||||
end |
||||
|
||||
def self.excluded_attributes |
||||
[primary_key.to_sym, inheritance_column.to_sym, :journal_id, :lock_version, :created_at, :root_id, :lft, :rgt] |
||||
end |
||||
|
||||
def self.db_columns(table_name) |
||||
ActiveRecord::Base.connection.columns table_name |
||||
end |
||||
|
||||
end |
@ -0,0 +1,34 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
api.array :statuses, :size => @statuses.size do |
||||
@statuses.each do |status| |
||||
render(:partial => '/api/v2/statuses/status.api', |
||||
:object => status) |
||||
end |
||||
end |
@ -0,0 +1,30 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
render(:partial => '/api/v2/reported_project_statuses/status.api', |
||||
:object => @status) |
@ -0,0 +1,63 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
|
||||
class LegacyAttachmentJournalData < ActiveRecord::Migration |
||||
|
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals |
||||
end |
||||
|
||||
private |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new("AttachmentJournal", "attachment_journals") do |
||||
|
||||
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id) |
||||
|
||||
rewrite_issue_container_to_work_package(to_insert) |
||||
|
||||
end |
||||
|
||||
def rewrite_issue_container_to_work_package(to_insert) |
||||
if to_insert['container_type'].last == 'Issue' |
||||
|
||||
to_insert['container_type'][-1] = 'WorkPackage' |
||||
|
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,47 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
|
||||
class LegacyChangesetJournalData < ActiveRecord::Migration |
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals |
||||
end |
||||
|
||||
private |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new("ChangesetJournal", "changeset_journals") |
||||
end |
||||
end |
@ -0,0 +1,47 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
|
||||
class LegacyNewsJournalData < ActiveRecord::Migration |
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals |
||||
end |
||||
|
||||
private |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new("NewsJournal", "news_journals") |
||||
end |
||||
end |
@ -0,0 +1,56 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
require_relative 'migration_utils/journal_migrator_concerns' |
||||
|
||||
class LegacyMessageJournalData < ActiveRecord::Migration |
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals 'attachable_journals' |
||||
end |
||||
|
||||
private |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new("MessageJournal", "message_journals") do |
||||
extend Migration::JournalMigratorConcerns::Attachable |
||||
|
||||
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id) |
||||
|
||||
migrate_attachments(to_insert, legacy_journal, journal_id) |
||||
|
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,56 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
require_relative 'migration_utils/journal_migrator_concerns' |
||||
|
||||
class LegacyTimeEntryJournalData < ActiveRecord::Migration |
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals 'customizable_journals' |
||||
end |
||||
|
||||
private |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new("TimeEntryJournal", "time_entry_journals") do |
||||
extend Migration::JournalMigratorConcerns::Customizable |
||||
|
||||
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id) |
||||
|
||||
migrate_custom_values(to_insert, legacy_journal, journal_id) |
||||
|
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,90 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
|
||||
class LegacyWikiContentJournalData < ActiveRecord::Migration |
||||
class UnsupportedWikiContentJournalCompressionError < ::StandardError |
||||
end |
||||
|
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals |
||||
end |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new("WikiContentJournal", "wiki_content_journals") do |
||||
|
||||
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id) |
||||
|
||||
# remove once lock_version is no longer a column in the wiki_content_journales table |
||||
if !to_insert.has_key?("lock_version") |
||||
|
||||
if !legacy_journal.has_key?("version") |
||||
raise WikiContentJournalVersionError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
There is a wiki content without a version. |
||||
The DB requires a version to be set |
||||
#{legacy_journal}, |
||||
#{to_insert} |
||||
MESSAGE |
||||
|
||||
end |
||||
|
||||
# as the old journals used the format [old_value, new_value] we have to fake it here |
||||
to_insert["lock_version"] = [nil,legacy_journal["version"]] |
||||
end |
||||
|
||||
if to_insert.has_key?("data") |
||||
|
||||
# Why is that checked but than the compression is not used in any way to read the data |
||||
if !to_insert.has_key?("compression") |
||||
|
||||
raise UnsupportedWikiContentJournalCompressionError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
There is a WikiContent journal that contains data in an |
||||
unsupported compression: #{compression} |
||||
MESSAGE |
||||
|
||||
end |
||||
|
||||
# as the old journals used the format [old_value, new_value] we have to fake it here |
||||
to_insert["text"] = [nil, to_insert.delete("data")] |
||||
|
||||
# fix non null constraint violation on page_id. |
||||
to_insert["page_id"] = [nil, journal_id] |
||||
|
||||
end |
||||
end |
||||
|
||||
end |
||||
end |
||||
end |
@ -0,0 +1,60 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
require_relative 'migration_utils/journal_migrator_concerns' |
||||
|
||||
class LegacyIssueJournalData < ActiveRecord::Migration |
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals 'customizable_journals', |
||||
'attachable_journals' |
||||
end |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new "IssueJournal", "work_package_journals" do |
||||
extend Migration::JournalMigratorConcerns::Attachable |
||||
extend Migration::JournalMigratorConcerns::Customizable |
||||
|
||||
self.journable_class = "WorkPackage" |
||||
|
||||
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id) |
||||
|
||||
migrate_attachments(to_insert, legacy_journal, journal_id) |
||||
|
||||
migrate_custom_values(to_insert, legacy_journal, journal_id) |
||||
|
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,141 @@ |
||||
#-- 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. |
||||
#++ |
||||
# |
||||
|
||||
require_relative 'migration_utils/legacy_journal_migrator' |
||||
require_relative 'migration_utils/journal_migrator_concerns' |
||||
|
||||
class LegacyPlanningElementJournalData < ActiveRecord::Migration |
||||
class UnknownJournaledError < ::StandardError |
||||
end |
||||
|
||||
class UnknownTypeError < ::StandardError |
||||
end |
||||
|
||||
def up |
||||
migrator.run |
||||
end |
||||
|
||||
def down |
||||
migrator.remove_journals_derived_from_legacy_journals 'customizable_journals', |
||||
'attachable_journals' |
||||
end |
||||
|
||||
def migrator |
||||
@migrator ||= Migration::LegacyJournalMigrator.new "Timelines_PlanningElementJournal", "work_package_journals" do |
||||
extend Migration::JournalMigratorConcerns::Attachable |
||||
extend Migration::JournalMigratorConcerns::Customizable |
||||
|
||||
self.journable_class = "WorkPackage" |
||||
|
||||
def migrate(legacy_journal) |
||||
update_journaled_id(legacy_journal) |
||||
|
||||
super |
||||
end |
||||
|
||||
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id) |
||||
|
||||
update_type_id(to_insert) |
||||
|
||||
migrate_attachments(to_insert, legacy_journal, journal_id) |
||||
|
||||
migrate_custom_values(to_insert, legacy_journal, journal_id) |
||||
|
||||
end |
||||
|
||||
def update_journaled_id(legacy_journal) |
||||
new_journaled_id = new_journaled_id_for_old(legacy_journal["journaled_id"]) |
||||
|
||||
if new_journaled_id.nil? |
||||
raise UnknownJournaledError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
No new journaled_id could be found to replace the journaled_id value of |
||||
#{legacy_journal["journaled_id"]} for the legacy journal with the id |
||||
#{legacy_journal["id"]} |
||||
MESSAGE |
||||
end |
||||
|
||||
legacy_journal["journaled_id"] = new_journaled_id |
||||
end |
||||
|
||||
def update_type_id(to_insert) |
||||
return if to_insert["planning_element_type_id"].nil? || |
||||
to_insert["planning_element_type_id"].last.nil? |
||||
|
||||
new_type_id = new_type_id_for_old(to_insert["planning_element_type_id"].last) |
||||
|
||||
if new_type_id.nil? |
||||
raise UnknownTypeError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
No new type_id could be found to replace the type_id value of |
||||
#{to_insert["planning_element_type_id"].last} |
||||
MESSAGE |
||||
end |
||||
|
||||
to_insert["type_id"] = [nil, new_type_id] |
||||
end |
||||
|
||||
def new_journaled_id_for_old(old_journaled_id) |
||||
@new_journaled_ids ||= begin |
||||
old_new = db_select_all <<-SQL |
||||
SELECT journaled_id AS old_id, new_id |
||||
FROM legacy_journals |
||||
LEFT JOIN legacy_planning_elements |
||||
ON legacy_journals.journaled_id = legacy_planning_elements.id |
||||
WHERE type = 'Timelines_PlanningElementJournal' |
||||
SQL |
||||
|
||||
old_new.inject({}) do |mem, entry| |
||||
mem[entry['old_id']] = entry['new_id'] |
||||
mem |
||||
end |
||||
end |
||||
|
||||
@new_journaled_ids[old_journaled_id] |
||||
end |
||||
|
||||
def new_type_id_for_old(old_type_id) |
||||
@new_type_ids ||= begin |
||||
old_new = db_select_all <<-SQL |
||||
SELECT id AS old_id, new_id |
||||
FROM legacy_planning_element_types |
||||
SQL |
||||
|
||||
old_new.inject({}) do |mem, entry| |
||||
# the old_type_id was casted to a fixnum |
||||
# cheaper to change this here |
||||
mem[entry['old_id'].to_i] = entry['new_id'].to_i |
||||
mem |
||||
end |
||||
end |
||||
|
||||
@new_type_ids[old_type_id] |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,50 @@ |
||||
#-- 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/utils' |
||||
|
||||
class UpdateAttachmentContainer < ActiveRecord::Migration |
||||
include Migration::Utils |
||||
|
||||
def up |
||||
say_with_time_silently "Changing container type from 'Issue' to 'WorkPackage'" do |
||||
update <<-SQL |
||||
UPDATE #{attachments_table} |
||||
SET container_type = #{work_package_type} |
||||
WHERE container_type = #{issue_type} |
||||
SQL |
||||
end |
||||
end |
||||
|
||||
def down |
||||
say_with_time_silently "Changing container type from 'WorkPackage' to 'Issue'" do |
||||
update <<-SQL |
||||
UPDATE #{attachments_table} |
||||
SET container_type = #{issue_type} |
||||
WHERE container_type = #{work_package_type} |
||||
SQL |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def attachments_table |
||||
ActiveRecord::Base.connection.quote_table_name('attachments') |
||||
end |
||||
|
||||
def issue_type |
||||
ActiveRecord::Base.connection.quote('Issue') |
||||
end |
||||
|
||||
def work_package_type |
||||
ActiveRecord::Base.connection.quote('WorkPackage') |
||||
end |
||||
end |
@ -0,0 +1,82 @@ |
||||
#-- 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/utils' |
||||
|
||||
class JournalActivitiesData < ActiveRecord::Migration |
||||
include Migration::Utils |
||||
|
||||
def up |
||||
say_with_time_silently "Changing activity type from 'issues' to 'work_packages'" do |
||||
update <<-SQL |
||||
UPDATE #{journals_table} |
||||
SET activity_type = #{work_package_activity} |
||||
WHERE journable_type = #{work_package_type} |
||||
SQL |
||||
end |
||||
end |
||||
|
||||
def down |
||||
if legacy_planning_elements_table_exists? |
||||
|
||||
say_with_time_silently "Changing activity type from 'work_packages' to 'planning_elements'" do |
||||
update <<-SQL |
||||
UPDATE #{journals_table} |
||||
SET activity_type = #{planning_element_activity} |
||||
WHERE #{journals_table}.journable_id IN (SELECT new_id |
||||
FROM #{legacy_planning_elements_table}) |
||||
SQL |
||||
end |
||||
else |
||||
say "Can not distinguish between former planning_elements and issues. Assuming all to be former issues." |
||||
end |
||||
|
||||
say_with_time_silently "Changing activity type from 'work_packages' to 'issues'" do |
||||
update <<-SQL |
||||
UPDATE #{journals_table} |
||||
SET activity_type = #{issue_activity} |
||||
WHERE activity_type = #{work_package_activity} |
||||
SQL |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def legacy_planning_elements_table_exists? |
||||
suppress_messages do |
||||
table_exists? legacy_planning_elements_table |
||||
end |
||||
end |
||||
|
||||
def journals_table |
||||
ActiveRecord::Base.connection.quote_table_name('journals') |
||||
end |
||||
|
||||
def legacy_planning_elements_table |
||||
ActiveRecord::Base.connection.quote_table_name('legacy_planning_elements') |
||||
end |
||||
|
||||
def work_package_type |
||||
ActiveRecord::Base.connection.quote('WorkPackage') |
||||
end |
||||
|
||||
def work_package_activity |
||||
ActiveRecord::Base.connection.quote('work_packages') |
||||
end |
||||
|
||||
def planning_element_activity |
||||
ActiveRecord::Base.connection.quote('timelines_planning_elements') |
||||
end |
||||
|
||||
def issue_activity |
||||
ActiveRecord::Base.connection.quote('issues') |
||||
end |
||||
end |
@ -0,0 +1,60 @@ |
||||
#-- 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 Migration |
||||
module DbWorker |
||||
def quote_value(name) |
||||
ActiveRecord::Base.connection.quote name |
||||
end |
||||
|
||||
def quoted_table_name(name) |
||||
ActiveRecord::Base.connection.quote_table_name name |
||||
end |
||||
|
||||
def db_table_exists?(name) |
||||
ActiveRecord::Base.connection.table_exists? name |
||||
end |
||||
|
||||
def db_columns(table_name) |
||||
ActiveRecord::Base.connection.columns table_name |
||||
end |
||||
|
||||
def db_select_all(statement) |
||||
ActiveRecord::Base.connection.select_all statement |
||||
end |
||||
|
||||
def db_execute(statement) |
||||
ActiveRecord::Base.connection.execute statement |
||||
end |
||||
|
||||
def db_delete(statement) |
||||
ActiveRecord::Base.connection.delete statement |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,146 @@ |
||||
#-- 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 Migration |
||||
module JournalMigratorConcerns |
||||
module Attachable |
||||
def migrate_attachments(to_insert, legacy_journal, journal_id) |
||||
attachments = to_insert.keys.select { |d| d =~ attachment_key_regexp } |
||||
|
||||
attachments.each do |key| |
||||
|
||||
attachment_id = attachment_key_regexp.match(key)[1] |
||||
|
||||
# if an attachment was added the value contains something like: |
||||
# [nil, "blubs.png"] |
||||
# if it was removed the value is something like |
||||
# ["blubs.png", nil] |
||||
removed_filename, added_filename = *to_insert[key] |
||||
|
||||
if added_filename && !removed_filename |
||||
# The attachment was added |
||||
|
||||
attachable = ActiveRecord::Base.connection.select_all <<-SQL |
||||
SELECT * |
||||
FROM #{attachable_table_name} AS a |
||||
WHERE a.journal_id = #{quote_value(journal_id)} AND a.attachment_id = #{attachment_id}; |
||||
SQL |
||||
|
||||
if attachable.size > 1 |
||||
|
||||
raise AmbiguousAttachableJournalError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
It appears there are ambiguous attachable journal data. |
||||
Please make sure attachable journal data are consistent and |
||||
that the unique constraint on journal_id and attachment_id |
||||
is met. |
||||
MESSAGE |
||||
|
||||
elsif attachable.size == 0 |
||||
|
||||
db_execute <<-SQL |
||||
INSERT INTO #{attachable_table_name}(journal_id, attachment_id, filename) |
||||
VALUES (#{quote_value(journal_id)}, #{quote_value(attachment_id)}, #{quote_value(added_filename)}); |
||||
SQL |
||||
end |
||||
|
||||
elsif removed_filename && !added_filename |
||||
# The attachment was removed |
||||
# we need to make certain that no subsequent journal adds an attachable_journal |
||||
# for this attachment |
||||
|
||||
to_insert.delete_if { |k, v| k =~ /attachments_?#{attachment_id}/ } |
||||
|
||||
else |
||||
raise InvalidAttachableJournalError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
There is a journal entry for an attachment but neither the old nor the new value contains anything: |
||||
#{to_insert} |
||||
#{legacy_journal} |
||||
MESSAGE |
||||
end |
||||
|
||||
end |
||||
|
||||
end |
||||
|
||||
def attachable_table_name |
||||
quoted_table_name("attachable_journals") |
||||
end |
||||
|
||||
def attachment_key_regexp |
||||
# Attachment journal entries can be written in two ways: |
||||
# attachments123 if the attachment was added |
||||
# attachments_123 if the attachment was removed |
||||
# |
||||
@attachment_key_regexp ||= /attachments_?(\d+)$/ |
||||
end |
||||
end |
||||
|
||||
module Customizable |
||||
def migrate_custom_values(to_insert, legacy_journal, journal_id) |
||||
keys = to_insert.keys |
||||
values = to_insert.values |
||||
|
||||
custom_values = keys.select { |d| d =~ /custom_values.*/ } |
||||
custom_values.each do |k| |
||||
|
||||
custom_field_id = k.split("_values").last.to_i |
||||
value = values[keys.index k].last |
||||
|
||||
customizable = db_select_all <<-SQL |
||||
SELECT * |
||||
FROM #{customizable_table_name} AS a |
||||
WHERE a.journal_id = #{quote_value(journal_id)} AND a.custom_field_id = #{custom_field_id}; |
||||
SQL |
||||
|
||||
if customizable.size > 1 |
||||
|
||||
raise AmbiguousCustomizableJournalError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
It appears there are ambiguous customizable journal |
||||
data. Please make sure customizable journal data are |
||||
consistent and that the unique constraint on journal_id and |
||||
custom_field_id is met. |
||||
MESSAGE |
||||
|
||||
elsif customizable.size == 0 |
||||
|
||||
db_execute <<-SQL |
||||
INSERT INTO #{customizable_table_name}(journal_id, custom_field_id, value) |
||||
VALUES (#{quote_value(journal_id)}, #{quote_value(custom_field_id)}, #{quote_value(value)}); |
||||
SQL |
||||
end |
||||
|
||||
end |
||||
end |
||||
|
||||
def customizable_table_name |
||||
quoted_table_name("customizable_journals") |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,407 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
require_relative 'db_worker' |
||||
require_relative 'legacy_table_checker' |
||||
|
||||
module Migration |
||||
class IncompleteJournalsError < ::StandardError |
||||
end |
||||
|
||||
class AmbiguousJournalsError < ::StandardError |
||||
end |
||||
|
||||
class LegacyJournalMigrator |
||||
include DbWorker |
||||
include LegacyTableChecker |
||||
|
||||
attr_accessor :table_name, |
||||
:type, |
||||
:journable_class |
||||
|
||||
def initialize(type, table_name, &block) |
||||
self.table_name = table_name |
||||
self.type = type |
||||
|
||||
instance_eval &block if block_given? |
||||
|
||||
if table_name.nil? || type.nil? |
||||
raise ArgumentError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
table_name and type have to be provided. Either as parameters |
||||
or set within the block. |
||||
MESSAGE |
||||
end |
||||
|
||||
self.journable_class ||= self.type.gsub(/Journal$/, "") |
||||
end |
||||
|
||||
def run |
||||
unless preconditions_met? |
||||
puts <<-MESSAGE |
||||
There is no legacy_journals table from which to derive the new |
||||
journals. Doing nothing ... |
||||
MESSAGE |
||||
return |
||||
end |
||||
|
||||
legacy_journals = fetch_legacy_journals |
||||
total_count = legacy_journals.count |
||||
|
||||
if total_count > 1 |
||||
progress_bar = ProgressBar.create(format: '%a <%B> %P%% %e', |
||||
total: total_count, |
||||
throttle_rate: 1, |
||||
smoothing: 0.5) |
||||
progress_bar.log "Migrating #{total_count} legacy journals." |
||||
|
||||
legacy_journals.each_with_index do |legacy_journal, count| |
||||
migrate(legacy_journal) |
||||
progress_bar.increment |
||||
end |
||||
end |
||||
end |
||||
|
||||
def remove_journals_derived_from_legacy_journals(*table_names) |
||||
|
||||
table_names << table_name |
||||
|
||||
if legacy_table_exists? |
||||
|
||||
table_names.each do |table_name| |
||||
|
||||
db_delete <<-SQL |
||||
DELETE |
||||
FROM #{quoted_table_name(table_name)} |
||||
WHERE journal_id in (SELECT id |
||||
FROM #{quoted_legacy_journals_table_name} |
||||
WHERE type=#{quote_value(type)}) |
||||
SQL |
||||
|
||||
end |
||||
|
||||
db_delete <<-SQL |
||||
DELETE |
||||
FROM journals |
||||
WHERE id in (SELECT id |
||||
FROM #{quoted_legacy_journals_table_name} |
||||
WHERE type=#{quote_value(type)}) |
||||
SQL |
||||
else |
||||
puts "No legacy table exists. Doing nothing" |
||||
end |
||||
end |
||||
|
||||
protected |
||||
|
||||
def migrate(legacy_journal) |
||||
journal = set_journal(legacy_journal) |
||||
journal_id = journal["id"] |
||||
|
||||
set_journal_data(journal_id, legacy_journal) |
||||
end |
||||
|
||||
def combine_journal(journaled_id, legacy_journal) |
||||
# compute the combined journal from current and all previous changesets. |
||||
combined_journal = legacy_journal["changed_data"] |
||||
if previous.journaled_id == journaled_id |
||||
combined_journal = previous.journal.merge(combined_journal) |
||||
end |
||||
|
||||
# remember the combined journal as the previous one for the next iteration. |
||||
previous.set(combined_journal, journaled_id) |
||||
|
||||
combined_journal |
||||
end |
||||
|
||||
def previous |
||||
@previous ||= PreviousState.new({}, 0) |
||||
end |
||||
|
||||
# here to be overwritten by instances |
||||
def migrate_key_value_pairs!(to_insert, legacy_journal, journal_id) end |
||||
|
||||
# fetches specific journal data row. might be empty. |
||||
def fetch_existing_data_journal(journal_id) |
||||
db_select_all <<-SQL |
||||
SELECT * |
||||
FROM #{journal_table_name} AS d |
||||
WHERE d.journal_id = #{quote_value(journal_id)}; |
||||
SQL |
||||
end |
||||
|
||||
# gets a journal row, and makes sure it has a valid id in the database. |
||||
# if the journal does not exist, it creates it |
||||
def set_journal(legacy_journal) |
||||
|
||||
journal = fetch_journal(legacy_journal) |
||||
|
||||
if journal.size > 1 |
||||
|
||||
raise AmbiguousJournalsError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
It appears there are ambiguous journals. Please make sure |
||||
journals are consistent and that the unique constraint on id, |
||||
type and version is met. |
||||
MESSAGE |
||||
|
||||
elsif journal.size == 0 |
||||
|
||||
journal = create_journal(legacy_journal) |
||||
|
||||
end |
||||
|
||||
journal.first |
||||
end |
||||
|
||||
# fetches specific journal row. might be empty. |
||||
def fetch_journal(legacy_journal) |
||||
id, version = legacy_journal["journaled_id"], legacy_journal["version"] |
||||
|
||||
db_select_all <<-SQL |
||||
SELECT * |
||||
FROM #{quoted_journals_table_name} AS j |
||||
WHERE j.journable_id = #{quote_value(id)} |
||||
AND j.journable_type = #{quote_value(journable_class)} |
||||
AND j.version = #{quote_value(version)}; |
||||
SQL |
||||
end |
||||
|
||||
# creates a valid journal. |
||||
# But might be not what is desired as an end result, yet. It is e.g. |
||||
# created with created_at set to now. This will need to be set to an actual |
||||
# date |
||||
def create_journal(legacy_journal) |
||||
|
||||
db_execute <<-SQL |
||||
INSERT INTO #{quoted_journals_table_name} ( |
||||
id, |
||||
journable_id, |
||||
version, |
||||
user_id, |
||||
notes, |
||||
activity_type, |
||||
created_at, |
||||
journable_type |
||||
) |
||||
VALUES ( |
||||
#{quote_value(legacy_journal["id"])}, |
||||
#{quote_value(legacy_journal["journaled_id"])}, |
||||
#{quote_value(legacy_journal["version"])}, |
||||
#{quote_value(legacy_journal["user_id"])}, |
||||
#{quote_value(legacy_journal["notes"])}, |
||||
#{quote_value(legacy_journal["activity_type"])}, |
||||
#{quote_value(legacy_journal["created_at"])}, |
||||
#{quote_value(journable_class)} |
||||
); |
||||
SQL |
||||
|
||||
fetch_journal(legacy_journal) |
||||
end |
||||
|
||||
def set_journal_data(journal_id, legacy_journal) |
||||
|
||||
deserialize_journal(legacy_journal) |
||||
journaled_id = legacy_journal["journaled_id"] |
||||
|
||||
combined_journal = combine_journal(journaled_id, legacy_journal) |
||||
migrate_key_value_pairs!(combined_journal, legacy_journal, journal_id) |
||||
|
||||
to_insert = insertable_data_journal(combined_journal) |
||||
|
||||
existing_data_journal = fetch_existing_data_journal(journal_id) |
||||
|
||||
if existing_data_journal.size > 1 |
||||
|
||||
raise AmbiguousJournalsError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
It appears there are ambiguous journal data. Please make sure |
||||
journal data are consistent and that the unique constraint on |
||||
journal_id is met. |
||||
MESSAGE |
||||
|
||||
elsif existing_data_journal.size == 0 |
||||
|
||||
existing_data_journal = create_data_journal(journal_id, to_insert) |
||||
|
||||
end |
||||
|
||||
existing_data_journal = existing_data_journal.first |
||||
|
||||
update_data_journal(existing_data_journal["id"], to_insert) |
||||
end |
||||
|
||||
def create_data_journal(journal_id, to_insert) |
||||
keys = to_insert.keys |
||||
values = to_insert.values |
||||
|
||||
db_execute <<-SQL |
||||
INSERT INTO #{journal_table_name} (journal_id#{", " + keys.join(", ") unless keys.empty? }) |
||||
VALUES (#{quote_value(journal_id)}#{", " + values.map{|d| quote_value(d)}.join(", ") unless values.empty?}); |
||||
SQL |
||||
|
||||
fetch_existing_data_journal(journal_id) |
||||
end |
||||
|
||||
def update_data_journal(id, to_insert) |
||||
db_execute <<-SQL unless to_insert.empty? |
||||
UPDATE #{journal_table_name} |
||||
SET #{(to_insert.each.map { |key,value| "#{key} = #{quote_value(value)}"}).join(", ") } |
||||
WHERE id = #{id}; |
||||
SQL |
||||
|
||||
end |
||||
|
||||
def deserialize_changed_data(journal) |
||||
changed_data = journal["changed_data"] |
||||
return Hash.new if changed_data.nil? |
||||
YAML.load(changed_data) |
||||
end |
||||
|
||||
def deserialize_journal(journal) |
||||
integerize_ids(journal) |
||||
journal["changed_data"] = deserialize_changed_data(journal) |
||||
end |
||||
|
||||
def insertable_data_journal(journal) |
||||
journal.inject({}) do |mem, (key, value)| |
||||
current_key = map_key(key) |
||||
|
||||
if column_names.include?(current_key) |
||||
# The old journal's values attribute was structured like |
||||
# [old_value, new_value] |
||||
# We only need the new_value |
||||
mem[current_key] = value.last |
||||
end |
||||
|
||||
mem |
||||
end |
||||
end |
||||
|
||||
def map_key(key) |
||||
case key |
||||
when "issue_id" |
||||
"work_package_id" |
||||
when "tracker_id" |
||||
"type_id" |
||||
when "end_date" |
||||
"due_date" |
||||
else |
||||
key |
||||
end |
||||
end |
||||
|
||||
def integerize_ids(journal) |
||||
# turn id fields into integers. |
||||
["id", "journaled_id", "user_id", "version"].each do |f| |
||||
journal[f] = journal[f].to_i |
||||
end |
||||
end |
||||
|
||||
# fetches legacy journals. might me empty. |
||||
def fetch_legacy_journals |
||||
db_select_all <<-SQL |
||||
SELECT * |
||||
FROM #{quoted_legacy_journals_table_name} AS j |
||||
WHERE (j.type = #{quote_value(type)}) |
||||
ORDER BY j.journaled_id, j.type, j.version; |
||||
SQL |
||||
end |
||||
|
||||
def preconditions_met? |
||||
legacy_table_exists? && check_legacy_journal_completeness |
||||
end |
||||
|
||||
def check_legacy_journal_completeness |
||||
|
||||
# SQL finds all those journals whose has more or less predecessors than |
||||
# it's version would require. Ignores the first journal. |
||||
# e.g. a journal with version 5 would have to have 5 predecessors |
||||
invalid_journals = db_select_all <<-SQL |
||||
SELECT DISTINCT tmp.id |
||||
FROM ( |
||||
SELECT |
||||
a.id AS id, |
||||
a.journaled_id, |
||||
a.type, |
||||
a.version AS version, |
||||
count(b.id) AS count |
||||
FROM |
||||
#{quoted_legacy_journals_table_name} AS a |
||||
LEFT JOIN |
||||
#{quoted_legacy_journals_table_name} AS b |
||||
ON a.version >= b.version |
||||
AND a.journaled_id = b.journaled_id |
||||
AND a.type = b.type |
||||
WHERE a.version > 1 |
||||
AND (a.type = #{quote_value(type)}) |
||||
GROUP BY |
||||
a.id, |
||||
a.journaled_id, |
||||
a.type, |
||||
a.version |
||||
) AS tmp |
||||
WHERE |
||||
NOT (tmp.version = tmp.count); |
||||
SQL |
||||
|
||||
unless invalid_journals.empty? |
||||
|
||||
raise IncompleteJournalsError, <<-MESSAGE.split("\n").map(&:strip!).join(" ") + "\n" |
||||
It appears there are incomplete journals. Please make sure |
||||
journals are consistent and that for every journal, there is an |
||||
initial journal containing all attribute values at the time of |
||||
creation. The offending journal ids are: #{invalid_journals} |
||||
MESSAGE |
||||
end |
||||
|
||||
true |
||||
end |
||||
|
||||
def journal_table_name |
||||
@journal_table_name ||= quoted_table_name(table_name) |
||||
end |
||||
|
||||
def quoted_legacy_journals_table_name |
||||
@quoted_legacy_journals_table_name ||= quoted_table_name 'legacy_journals' |
||||
end |
||||
|
||||
def quoted_journals_table_name |
||||
@quoted_journals_table_name ||= quoted_table_name 'journals' |
||||
end |
||||
|
||||
def column_names |
||||
@column_names ||= db_columns(table_name).map(&:name) |
||||
end |
||||
end |
||||
|
||||
class PreviousState < Struct.new(:journal, :journaled_id) |
||||
def set(journal, journaled_id) |
||||
self.journal = journal |
||||
self.journaled_id = journaled_id |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,39 @@ |
||||
#-- 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 Migration |
||||
module LegacyTableChecker |
||||
include DbWorker |
||||
|
||||
def legacy_table_exists? |
||||
db_table_exists? 'legacy_journals' |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,209 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
Feature: Filtering work packages via the api |
||||
Background: |
||||
Given there is 1 project with the following: |
||||
| identifier | sample_project | |
||||
| name | sample_project | |
||||
And I am working in project "sample_project" |
||||
And the project "sample_project" has the following types: |
||||
| name | position | |
||||
| Bug | 1 | |
||||
| Task | 2 | |
||||
| Story | 3 | |
||||
| Epic | 4 | |
||||
And there is a default issuepriority with: |
||||
| name | Normal | |
||||
And there is a issuepriority with: |
||||
| name | High | |
||||
And there is a issuepriority with: |
||||
| name | Immediate | |
||||
And there are the following issue status: |
||||
| name | is_closed | is_default | |
||||
| New | false | true | |
||||
| In Progress | false | true | |
||||
| Closed | false | true | |
||||
|
||||
And the project uses the following modules: |
||||
| timelines | |
||||
And there is a role "member" |
||||
And the role "member" may have the following rights: |
||||
| edit_work_packages | |
||||
| view_projects | |
||||
| view_reportings | |
||||
| view_timelines | |
||||
| view_work_packages | |
||||
|
||||
And there is 1 user with the following: |
||||
| login | bob | |
||||
And there is 1 user with the following: |
||||
| login | peter | |
||||
And there is 1 user with the following: |
||||
| login | pamela | |
||||
And the user "bob" is a "member" in the project "sample_project" |
||||
And the user "peter" is a "member" in the project "sample_project" |
||||
And the user "pamela" is a "member" in the project "sample_project" |
||||
And I am already logged in as "bob" |
||||
|
||||
Scenario: Call the endpoint of the api without filters |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | |
||||
| work_package#1 | Bug | |
||||
| work_package#2 | Story | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" without any filters |
||||
Then the json-response should include 2 work packages |
||||
And the json-response should contain a work_package "work_package#1" |
||||
And the json-response should contain a work_package "work_package#2" |
||||
|
||||
Scenario: Call the api filtering for type |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#1 | Bug | | |
||||
| work_package#1.1 | Bug | work_package#1 | |
||||
| work_package#2 | Story | | |
||||
| work_package#2.1 | Story | work_package#2 | |
||||
| work_package#3 | Epic | | |
||||
| work_package#3.1 | Story | work_package#3 | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Bug" |
||||
Then the json-response should include 2 work packages |
||||
Then the json-response should not contain a work_package "work_package#2" |
||||
And the json-response should contain a work_package "work_package#1" |
||||
|
||||
Scenario: Call the api filtering for status |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | status | |
||||
| work_package#1 | Bug | New | |
||||
| work_package#2 | Story | In Progress | |
||||
| work_package#3 | Epic | Closed | |
||||
|
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for status "In Progress" |
||||
Then the json-response should include 1 work package |
||||
Then the json-response should contain a work_package "work_package#2" |
||||
And the json-response should not contain a work_package "work_package#1" |
||||
|
||||
Scenario: Filtering multiple types |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#1 | Bug | | |
||||
| work_package#1.1 | Bug | work_package#1 | |
||||
| work_package#3 | Epic | | |
||||
| work_package#3.1 | Story | work_package#3 | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Bug,Epic" |
||||
Then the json-response should include 3 work packages |
||||
And the json-response should contain a work_package "work_package#1" |
||||
And the json-response should contain a work_package "work_package#3" |
||||
And the json-response should not contain a work_package "work_package#3.1" |
||||
|
||||
Scenario: Filter out children of work packages, if they don't have the right type |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#3 | Epic | | |
||||
| work_package#3.1 | Story | work_package#3 | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Epic" |
||||
Then the json-response should include 1 work package |
||||
And the json-response should contain a work_package "work_package#3" |
||||
And the json-response should not contain a work_package "work_package#3.1" |
||||
|
||||
Scenario: Filter out parents of work packages, if they don't have the right type |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | |
||||
| work_package#1 | Bug | |
||||
| work_package#2 | Story | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Story" |
||||
Then the json-response should include 1 work package |
||||
And the json-response should not contain a work_package "work_package#1" |
||||
And the json-response should contain a work_package "work_package#2" |
||||
|
||||
|
||||
Scenario: correctly export parent-child-relations |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#1 | Epic | | |
||||
| work_package#1.1 | Story | work_package#1 | |
||||
| work_package#2 | Task | work_package#1.1 | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" without any filters |
||||
Then the json-response should include 3 work packages |
||||
And the json-response should say that "work_package#1" is parent of "work_package#1.1" |
||||
|
||||
Scenario: Move parent-relations up the ancestor-chain, when intermediate packages are fitered |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#1 | Epic | | |
||||
| work_package#1.1 | Story | work_package#1 | |
||||
| work_package#1.1.1 | Task | work_package#1.1 | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Epic,Task" |
||||
Then the json-response should include 2 work packages |
||||
And the json-response should not contain a work_package "work_package#1.1" |
||||
And the json-response should contain a work_package "work_package#1" |
||||
And the json-response should contain a work_package "work_package#1.1.1" |
||||
And the json-response should say that "work_package#1" is parent of "work_package#1.1.1" |
||||
|
||||
Scenario: The parent should be rewired to the first ancestor present in the filtered set |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#1 | Epic | | |
||||
| work_package#1.1 | Task | work_package#1 | |
||||
| work_package#1.1.1 | Bug | work_package#1.1 | |
||||
| work_package#1.1.1.1 | Task | work_package#1.1.1 | |
||||
|
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Epic,Task" |
||||
Then the json-response should include 3 work packages |
||||
And the json-response should say that "work_package#1.1" is parent of "work_package#1.1.1.1" |
||||
|
||||
Scenario: When all ancestors are filtered, the work_package should have no parent |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#1 | Epic | | |
||||
| work_package#1.1 | Story | work_package#1 | |
||||
| work_package#1.1.1 | Task | work_package#1.1 | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Task" |
||||
Then the json-response should include 1 work packages |
||||
And the json-response should say that "work_package#1.1.1" has no parent |
||||
|
||||
Scenario: Children are filtered out |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | parent | |
||||
| work_package#1 | Epic | | |
||||
| work_package#1.1 | Task | work_package#1 | |
||||
| work_package#1.2 | Story | work_package#1 | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for type "Epic,Story" |
||||
And the json-response should say that "work_package#1" has 1 child |
||||
|
||||
Scenario: Filtering for responsibles |
||||
Given there are the following work packages in project "sample_project": |
||||
| subject | type | responsible | |
||||
| work_package#1 | Task | bob | |
||||
| work_package#2 | Task | peter | |
||||
| work_package#3 | Task | pamela | |
||||
When I call the work_package-api on project "sample_project" requesting format "json" filtering for responsible "peter" |
||||
Then the json-response should include 1 work package |
||||
And the json-response should not contain a work_package "work_package#1" |
||||
And the json-response should contain a work_package "work_package#2" |
||||
|
||||
|
@ -0,0 +1,150 @@ |
||||
#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. |
||||
#++ |
||||
|
||||
require "benchmark" |
||||
|
||||
When(/^I call the work_package\-api on project "(.*?)" requesting format "(.*?)" without any filters$/) do |project_name, format| |
||||
|
||||
@project = Project.find(project_name) |
||||
@unfiltered_benchmark = Benchmark.measure("Unfiltered Results") do |
||||
visit api_v2_project_planning_elements_path(project_id: project_name, format: format) |
||||
end |
||||
|
||||
end |
||||
|
||||
Then(/^the json\-response should include (\d+) work package(s?)$/) do |number_of_wps, plural| |
||||
expect(work_package_names.size).to eql number_of_wps.to_i |
||||
end |
||||
|
||||
Then(/^the json\-response should( not)? contain a work_package "(.*?)"$/) do |negation, work_package_name| |
||||
if negation |
||||
expect(work_package_names).not_to include work_package_name |
||||
else |
||||
expect(work_package_names).to include work_package_name |
||||
end |
||||
|
||||
end |
||||
|
||||
And(/^the json\-response should say that "(.*?)" is parent of "(.*?)"$/) do |parent_name, child_name| |
||||
child = decoded_json["planning_elements"].select {|wp| wp["name"] == child_name}.first |
||||
expect(child["parent"]["name"]).to eql parent_name |
||||
end |
||||
|
||||
And(/^the json\-response should say that "(.*?)" has no parent$/) do |child_name| |
||||
child = decoded_json["planning_elements"].select {|wp| wp["name"] == child_name}.first |
||||
expect(child["parent"]).to be_nil |
||||
end |
||||
|
||||
And(/^the json\-response should say that "(.*?)" has (\d+) child(ren)?$/) do |parent_name, nr_of_children,plural| |
||||
parent = decoded_json["planning_elements"].select {|wp| wp["name"] == parent_name}.first |
||||
expect(parent["children"].size).to eql nr_of_children.to_i |
||||
end |
||||
|
||||
|
||||
When(/^I call the work_package\-api on project "(.*?)" requesting format "(.*?)" filtering for status "(.*?)"$/) do |project_name, format, status_names| |
||||
statuses = IssueStatus.where(name: status_names.split(',')) |
||||
|
||||
get_filtered_json(project_name: project_name, |
||||
format: format, |
||||
filters: [:status_id], |
||||
operators: {status_id: "="}, |
||||
values: {status_id: statuses.map(&:id)} ) |
||||
end |
||||
|
||||
Then(/^I call the work_package\-api on project "(.*?)" requesting format "(.*?)" filtering for type "(.*?)"$/) do |project_name, format, type_names| |
||||
types = Project.find_by_identifier(project_name).types.where(name: type_names.split(",")) |
||||
|
||||
get_filtered_json(project_name: project_name, |
||||
format: format, |
||||
filters: [:type_id], |
||||
operators: {type_id: "="}, |
||||
values: {type_id: types.map(&:id)} ) |
||||
|
||||
|
||||
end |
||||
|
||||
When(/^I call the work_package\-api on project "(.*?)" requesting format "(.*?)" filtering for responsible "(.*?)"$/) do |project_name, format, responsible_names| |
||||
responsibles = User.where(login: responsible_names.split(',')) |
||||
|
||||
get_filtered_json(project_name: project_name, |
||||
format: format, |
||||
filters: [:responsible_id], |
||||
operators: {responsible_id: "="}, |
||||
values: {responsible_id: responsibles.map(&:id)} ) |
||||
|
||||
end |
||||
|
||||
|
||||
And(/^there are (\d+) work packages of type "(.*?)" in project "(.*?)"$/) do |nr_of_wps, type_name, project_name| |
||||
project = Project.find_by_identifier(project_name) |
||||
type = project.types.find_by_name(type_name) |
||||
|
||||
FactoryGirl.create_list(:work_package, nr_of_wps.to_i, project: project, type: type) |
||||
|
||||
end |
||||
|
||||
|
||||
And(/^the time to get the unfiltered results should not exceed (\d+)\.(\d+)s$/) do |seconds,milliseconds| |
||||
puts "----Unfiltered Benchmark----" |
||||
puts @unfiltered_benchmark |
||||
@unfiltered_benchmark.total.should < "#{seconds}.#{milliseconds}".to_f |
||||
end |
||||
|
||||
And(/^the time to get the filtered results should not exceed (\d+)\.(\d+)s$/) do |seconds, milliseconds| |
||||
puts "----Filtered Benchmark----" |
||||
puts @filtered_benchmark |
||||
@filtered_benchmark.total.should < "#{seconds}.#{milliseconds}".to_f |
||||
end |
||||
|
||||
Then(/^the time to get the filtered results should be faster than the time to get the unfiltered results$/) do |
||||
@filtered_benchmark.total.should < @unfiltered_benchmark.total |
||||
end |
||||
|
||||
def work_package_names |
||||
decoded_json["planning_elements"].map{|wp| wp["name"]} |
||||
end |
||||
|
||||
def decoded_json |
||||
@decoded_json ||= ActiveSupport::JSON.decode last_json |
||||
end |
||||
|
||||
def last_json |
||||
page.source |
||||
end |
||||
|
||||
def get_filtered_json(params) |
||||
@filtered_benchmark = Benchmark.measure("Filtered Results") do |
||||
visit api_v2_project_planning_elements_path(project_id: params[:project_name], |
||||
format: params[:format], |
||||
f: params[:filters], |
||||
op: params[:operators], |
||||
v: params[:values]) |
||||
end |
||||
end |
@ -0,0 +1,135 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
Feature: Timeline view with filter tests |
||||
As an openproject user |
||||
I want to view filtered timelines |
||||
|
||||
Background: |
||||
Given there is 1 user with: |
||||
| login | manager | |
||||
|
||||
And there is a role "manager" |
||||
And the role "manager" may have the following rights: |
||||
| view_timelines | |
||||
| edit_timelines | |
||||
| view_work_packages | |
||||
|
||||
And there are the following project types: |
||||
| Name | |
||||
| Pilot | |
||||
|
||||
And there is a project named "Space Pilot 3000" of type "Pilot" |
||||
And I am working in project "Space Pilot 3000" |
||||
And the project uses the following modules: |
||||
| timelines | |
||||
|
||||
And the user "manager" is a "manager" |
||||
And I am already logged in as "manager" |
||||
And there are the following types: |
||||
| Name | Is Milestone | In aggregation | |
||||
| Phase | false | true | |
||||
| Milestone | true | true | |
||||
|
||||
And the following types are enabled for projects of type "Pilot" |
||||
| Phase | |
||||
| Milestone | |
||||
And there is a timeline "Storyboard" for project "Space Pilot 3000" |
||||
|
||||
|
||||
@javascript |
||||
Scenario: The timeline w/o filters renders properly |
||||
Given there are the following work packages in project "Space Pilot 3000": |
||||
| Subject | Start date | Due date | Type | Parent | |
||||
| Mission to the moon | 3000-01-02 | 3000-01-03 | Phase | | |
||||
| Mom captures Nibblonians | 3000-04-01 | 3000-04-13 | Phase | | |
||||
|
||||
When I go to the page of the timeline of the project called "Space Pilot 3000" |
||||
And I wait for timeline to load table |
||||
Then I should see the work package "Mission to the moon" in the timeline |
||||
And I should see the work package "Mom captures Nibblonians" in the timeline |
||||
|
||||
@javascript |
||||
Scenario: The timeline w/ type filters renders properly |
||||
Given there are the following work packages in project "Space Pilot 3000": |
||||
| Subject | Start date | Due date | Type | Parent | |
||||
| Hubert Farnsworth's Birthday | 2841-04-09 | 2841-04-09 | Milestone | | |
||||
| Second year | 3000-01-01 | 3000-01-05 | Phase | | |
||||
| Hubert Farnsworth's second Birthday | 2842-04-09 | 2842-04-09 | Milestone | Second year | |
||||
| Hubert Farnsworth's third Birthday | 2843-04-09 | 2843-04-09 | Milestone | Second year | |
||||
And I am working in the timeline "Storyboard" of the project called "Space Pilot 3000" |
||||
When I go to the page of the timeline of the project called "Space Pilot 3000" |
||||
And I show only work packages which have the type "Milestone" |
||||
And I wait for timeline to load table |
||||
Then I should see the work package "Hubert Farnsworth's Birthday" in the timeline |
||||
Then I should see the work package "Hubert Farnsworth's second Birthday" in the timeline |
||||
Then I should see the work package "Hubert Farnsworth's third Birthday" in the timeline |
||||
And I should not see the work package "Second year" in the timeline |
||||
|
||||
@javascript |
||||
Scenario: The timeline w/ responsibles filters renders properly |
||||
Given there is 1 user with: |
||||
| Login | hubert | |
||||
| Firstname | Hubert | |
||||
| Lastname | Farnsworth | |
||||
And there are the following work packages in project "Space Pilot 3000": |
||||
| Subject | Start date | Due date | Responsible | Parent | |
||||
| Hubert Farnsworth's Birthday | 2841-04-09 | 2841-04-09 | hubert | | |
||||
| Second year | 3000-01-01 | 3000-01-05 | | | |
||||
| Hubert Farnsworth's second Birthday | 2842-04-09 | 2842-04-09 | hubert | Second year | |
||||
| Hubert Farnsworth's third Birthday | 2843-04-09 | 2843-04-09 | hubert | Second year | |
||||
And I am working in the timeline "Storyboard" of the project called "Space Pilot 3000" |
||||
When I go to the page of the timeline of the project called "Space Pilot 3000" |
||||
And I show only work packages which have the responsible "hubert" |
||||
And I wait for timeline to load table |
||||
Then I should see the work package "Hubert Farnsworth's Birthday" in the timeline |
||||
And I should see the work package "Hubert Farnsworth's second Birthday" in the timeline |
||||
And I should see the work package "Hubert Farnsworth's third Birthday" in the timeline |
||||
And I should not see the work package "Second year" in the timeline |
||||
|
||||
@javascript |
||||
Scenario: The timeline w/ responsibles filters renders properly |
||||
Given there is 1 user with: |
||||
| Login | hubert | |
||||
| Firstname | Hubert | |
||||
| Lastname | Farnsworth | |
||||
And there are the following work packages in project "Space Pilot 3000": |
||||
| Subject | Start date | Due date | Responsible | Parent | |
||||
| Hubert Farnsworth's Birthday | 2841-04-09 | 2841-04-09 | hubert | | |
||||
| Second year | 3000-01-01 | 3000-01-05 | | | |
||||
| Hubert Farnsworth's second Birthday | 2842-04-09 | 2842-04-09 | hubert | Second year | |
||||
| Hubert Farnsworth's third Birthday | 2843-04-09 | 2843-04-09 | hubert | Second year | |
||||
And I am working in the timeline "Storyboard" of the project called "Space Pilot 3000" |
||||
When I go to the page of the timeline of the project called "Space Pilot 3000" |
||||
And I show only work packages which have no responsible |
||||
And I wait for timeline to load table |
||||
Then I should not see the work package "Hubert Farnsworth's Birthday" in the timeline |
||||
And I should not see the work package "Hubert Farnsworth's second Birthday" in the timeline |
||||
And I should not see the work package "Hubert Farnsworth's third Birthday" in the timeline |
||||
And I should see the work package "Second year" in the timeline |
||||
|
@ -0,0 +1,131 @@ |
||||
#-- 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 File.expand_path('../../../../spec_helper', __FILE__) |
||||
|
||||
describe Api::V2::StatusesController do |
||||
|
||||
let(:valid_user) { FactoryGirl.create(:user) } |
||||
let(:status) {FactoryGirl.create(:issue_status)} |
||||
|
||||
before do |
||||
User.stub(:current).and_return valid_user |
||||
end |
||||
|
||||
describe 'authentication of index' do |
||||
def fetch |
||||
get 'index', :format => 'json' |
||||
end |
||||
it_should_behave_like "a controller action with require_login" |
||||
end |
||||
|
||||
describe 'authentication of show' do |
||||
def fetch |
||||
get 'show', :format => 'json', :id => status.id |
||||
end |
||||
it_should_behave_like "a controller action with require_login" |
||||
end |
||||
|
||||
describe 'looking up a singular status' do |
||||
let(:closed){FactoryGirl.create(:issue_status, name: "Closed")} |
||||
|
||||
it 'that does not exist should raise an error' do |
||||
get 'show', :id => '0', :format => 'json' |
||||
response.response_code.should == 404 |
||||
end |
||||
it 'that exists should return the proper status' do |
||||
get 'show', :id => closed.id, :format => 'json' |
||||
expect(assigns(:status)).to eql closed |
||||
end |
||||
|
||||
end |
||||
|
||||
describe 'looking up statuses' do |
||||
|
||||
let(:open) {FactoryGirl.create(:issue_status, name: "Open")} |
||||
let(:in_progress) {FactoryGirl.create(:issue_status, name: "In Progress")} |
||||
let(:closed){FactoryGirl.create(:issue_status, name: "Closed")} |
||||
let(:no_see_status){FactoryGirl.create(:issue_status, name: "You don't see me.")} |
||||
|
||||
let(:workflows) do |
||||
workflows = [FactoryGirl.create(:workflow, old_status: open, new_status: in_progress, role: role), |
||||
FactoryGirl.create(:workflow, old_status: in_progress, new_status: closed, role: role)] |
||||
end |
||||
|
||||
let(:no_see_workflows) do |
||||
workflows = [FactoryGirl.create(:workflow, old_status: closed, new_status: no_see_status, role: role)] |
||||
end |
||||
|
||||
let(:project) do |
||||
type = FactoryGirl.create(:type, name: "Standard", workflows: workflows) |
||||
project = FactoryGirl.create(:project, types: [type]) |
||||
end |
||||
let(:invisible_project) do |
||||
invisible_type = FactoryGirl.create(:type, name: "No See", workflows: no_see_workflows) |
||||
project = FactoryGirl.create(:project, types: [invisible_type], is_public: false) |
||||
end |
||||
|
||||
let(:role) { FactoryGirl.create(:role) } |
||||
let(:member) { FactoryGirl.create(:member, :project => project, |
||||
:user => valid_user, |
||||
:roles => [role]) } |
||||
|
||||
|
||||
before do |
||||
member |
||||
workflows |
||||
end |
||||
|
||||
describe 'with project-scope' do |
||||
it 'with unknown project raises ActiveRecord::RecordNotFound errors' do |
||||
get 'index', :project_id => '0', :format => 'json' |
||||
expect(response.response_code).to eql 404 |
||||
end |
||||
|
||||
it "should return the available statuses _only_ for the given project" do |
||||
get 'index', :project_id => project.id, :format => 'json' |
||||
expect(assigns(:statuses)).to include open, in_progress, closed |
||||
expect(assigns(:statuses)).not_to include no_see_status |
||||
end |
||||
|
||||
end |
||||
|
||||
describe 'without project-scope' do |
||||
it "should return only status for visible projects" do |
||||
# create the invisible type/workflow/status |
||||
invisible_project |
||||
get 'index', :format => 'json' |
||||
|
||||
expect(assigns(:statuses)).to include open, in_progress, closed |
||||
expect(assigns(:statuses)).not_to include no_see_status |
||||
end |
||||
end |
||||
end |
||||
|
||||
end |
||||
|
@ -0,0 +1,347 @@ |
||||
#-- 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 WorkPackages::ContextMenusController do |
||||
let(:user) { FactoryGirl.create(:user) } |
||||
let(:type) { FactoryGirl.create(:type_standard) } |
||||
let(:project_1) { FactoryGirl.create(:project, |
||||
types: [type]) } |
||||
let(:project_2) { FactoryGirl.create(:project, |
||||
types: [type], |
||||
is_public: false) } |
||||
let(:role) { FactoryGirl.create(:role, |
||||
permissions: [:view_work_packages, |
||||
:add_work_packages, |
||||
:edit_work_packages, |
||||
:move_work_packages, |
||||
:delete_work_packages]) } |
||||
let(:member) { FactoryGirl.create(:member, |
||||
project: project_1, |
||||
principal: user, |
||||
roles: [role]) } |
||||
let(:status_1) { FactoryGirl.create(:issue_status) } |
||||
let(:work_package_1) { FactoryGirl.create(:work_package, |
||||
author: user, |
||||
type: type, |
||||
status: status_1, |
||||
project: project_1) } |
||||
let(:work_package_2) { FactoryGirl.create(:work_package, |
||||
author: user, |
||||
type: type, |
||||
status: status_1, |
||||
project: project_1) } |
||||
let(:work_package_3) { FactoryGirl.create(:work_package, |
||||
author: user, |
||||
type: type, |
||||
status: status_1, |
||||
project: project_2) } |
||||
|
||||
before do |
||||
member |
||||
|
||||
User.stub(:current).and_return user |
||||
end |
||||
|
||||
describe :index do |
||||
render_views |
||||
|
||||
shared_examples_for "successful response" do |
||||
before { get :index, ids: ids } |
||||
|
||||
subject { response } |
||||
|
||||
it { should be_success } |
||||
|
||||
it { should render_template('context_menu') } |
||||
end |
||||
|
||||
shared_examples_for :edit do |
||||
let(:edit_link) { "/work_packages/#{ids.first}/edit" } |
||||
|
||||
it_behaves_like :edit_impl |
||||
end |
||||
|
||||
shared_examples_for :bulk_edit do |
||||
let(:edit_link) { "/issues/bulk_edit?#{ids_link}" } |
||||
|
||||
it_behaves_like :edit_impl |
||||
end |
||||
|
||||
shared_examples_for :edit_impl do |
||||
before { get :index, ids: ids } |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: 'Edit', |
||||
attributes: { href: edit_link, |
||||
:class => 'icon-edit' } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :status do |
||||
let(:status_2) { FactoryGirl.create(:issue_status) } |
||||
let(:status_3) { FactoryGirl.create(:issue_status) } |
||||
let(:workflow_1) { FactoryGirl.create(:workflow, |
||||
role: role, |
||||
type_id: type.id, |
||||
old_status: status_1, |
||||
new_status: status_2) } |
||||
let(:workflow_2) { FactoryGirl.create(:workflow, |
||||
role: role, |
||||
type_id: type.id, |
||||
old_status: status_2, |
||||
new_status: status_3) } |
||||
|
||||
before do |
||||
workflow_1 |
||||
workflow_2 |
||||
|
||||
get :index, ids: ids |
||||
end |
||||
|
||||
let(:status_link) { "/issues/bulk_update?#{ids_link}"\ |
||||
"&issue%5Bstatus_id%5D=#{status_2.id}" } |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: status_2.name, |
||||
attributes: { href: status_link, |
||||
:class => '' } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :priority do |
||||
let(:priority_immediate) { FactoryGirl.create(:priority_immediate) } |
||||
let(:priority_link) { "/issues/bulk_update?#{ids_link}"\ |
||||
"&issue%5Bpriority_id%5D=#{priority_immediate.id}" } |
||||
|
||||
before do |
||||
priority_immediate |
||||
|
||||
get :index, ids: ids |
||||
end |
||||
|
||||
it do |
||||
assert_tag :tag => 'a', |
||||
content: 'Immediate', |
||||
attributes: { href: priority_link, |
||||
:class => '' } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :version do |
||||
let(:version_1) { FactoryGirl.create(:version, |
||||
project: project_1) } |
||||
let(:version_2) { FactoryGirl.create(:version, |
||||
project: project_1) } |
||||
let(:version_link_1) { "/issues/bulk_update?#{ids_link}"\ |
||||
"&issue%5Bfixed_version_id%5D=#{version_1.id}" } |
||||
let(:version_link_2) { "/issues/bulk_update?#{ids_link}"\ |
||||
"&issue%5Bfixed_version_id%5D=#{version_2.id}" } |
||||
|
||||
before do |
||||
version_1 |
||||
version_2 |
||||
|
||||
get :index, ids: ids |
||||
end |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: version_2.name, |
||||
attributes: { href: version_link_2, |
||||
:class => '' } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :assigned_to do |
||||
let(:assigned_to_link) { "/issues/bulk_update?#{ids_link}"\ |
||||
"&issue%5Bassigned_to_id%5D=#{user.id}" } |
||||
|
||||
before { get :index, ids: ids } |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: user.name, |
||||
attributes: { href: assigned_to_link, |
||||
:class => '' } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :duplicate do |
||||
let(:duplicate_link) { "/projects/#{project_1.identifier}/work_packages"\ |
||||
"/new?copy_from=#{ids.first}" } |
||||
|
||||
before { get :index, ids: ids } |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: 'Duplicate', |
||||
attributes: { href: duplicate_link, |
||||
:class => 'icon-duplicate' } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :copy do |
||||
let(:copy_link) { "/work_packages/move/new?copy_options%5Bcopy%5D=t&"\ |
||||
"#{ids_link}" } |
||||
|
||||
before { get :index, ids: ids } |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: 'Copy', |
||||
attributes: { href: copy_link } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :move do |
||||
let(:move_link) { "/work_packages/move/new?#{ids_link}" } |
||||
|
||||
before { get :index, ids: ids } |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: 'Move', |
||||
attributes: { href: move_link } |
||||
end |
||||
end |
||||
|
||||
shared_examples_for :delete do |
||||
let(:delete_link) { "/work_packages?#{ids_link}" } |
||||
|
||||
before { get :index, ids: ids } |
||||
|
||||
it do |
||||
assert_tag tag: 'a', |
||||
content: 'Delete', |
||||
attributes: { href: delete_link } |
||||
end |
||||
end |
||||
|
||||
context "one work package" do |
||||
let(:ids) { [work_package_1.id] } |
||||
let(:ids_link) { ids.map {|id| "ids%5B%5D=#{id}"}.join('&') } |
||||
|
||||
it_behaves_like "successful response" |
||||
|
||||
it_behaves_like :edit |
||||
|
||||
it_behaves_like :status |
||||
|
||||
it_behaves_like :priority |
||||
|
||||
it_behaves_like :version |
||||
|
||||
it_behaves_like :assigned_to |
||||
|
||||
it_behaves_like :duplicate |
||||
|
||||
it_behaves_like :copy |
||||
|
||||
it_behaves_like :move |
||||
|
||||
it_behaves_like :delete |
||||
|
||||
context "anonymous user" do |
||||
let(:anonymous) { FactoryGirl.create(:anonymous) } |
||||
|
||||
before { User.stub(:current).and_return anonymous } |
||||
|
||||
it_behaves_like "successful response" |
||||
|
||||
describe :delete do |
||||
before { get :index, ids: ids } |
||||
|
||||
it { assert_select "a.disabled", :text => /Delete/ } |
||||
end |
||||
end |
||||
end |
||||
|
||||
context "multiple work packages" do |
||||
context "in same project" do |
||||
let(:ids) { [work_package_1.id, work_package_2.id] } |
||||
let(:ids_link) { ids.map {|id| "ids%5B%5D=#{id}"}.join('&') } |
||||
|
||||
it_behaves_like "successful response" |
||||
|
||||
it_behaves_like :bulk_edit |
||||
|
||||
it_behaves_like :status |
||||
|
||||
it_behaves_like :priority |
||||
|
||||
it_behaves_like :assigned_to |
||||
|
||||
it_behaves_like :copy |
||||
|
||||
it_behaves_like :move |
||||
|
||||
it_behaves_like :delete |
||||
end |
||||
|
||||
context "in different projects" do |
||||
let(:ids) { [work_package_1.id, work_package_2.id, work_package_3.id] } |
||||
|
||||
describe "with project rights" do |
||||
let(:ids_link) { ids.map {|id| "ids%5B%5D=#{id}"}.join('&') } |
||||
let(:member_2) { FactoryGirl.create(:member, |
||||
project: project_2, |
||||
principal: user, |
||||
roles: [role]) } |
||||
|
||||
before { member_2 } |
||||
|
||||
it_behaves_like "successful response" |
||||
|
||||
it_behaves_like :bulk_edit |
||||
|
||||
it_behaves_like :status |
||||
|
||||
it_behaves_like :priority |
||||
|
||||
it_behaves_like :assigned_to |
||||
|
||||
it_behaves_like :delete |
||||
end |
||||
|
||||
describe "w/o project rights" do |
||||
it_behaves_like "successful response" |
||||
|
||||
describe :work_packages do |
||||
before { get :index, ids: ids } |
||||
|
||||
it { assigns(:work_packages).collect(&:id).should =~ [work_package_1.id, work_package_2.id] } |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,42 @@ |
||||
#-- 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 WorkPackages::AutoCompletesController do |
||||
|
||||
it "should connect GET /work_packages/auto_completes to work_package/auto_complete#index" do |
||||
get("/work_packages/auto_complete").should route_to( controller: 'work_packages/auto_completes', |
||||
action: 'index' ) |
||||
end |
||||
|
||||
it "should connect PUT /work_packages/auto_completes to work_package/auto_complete#index" do |
||||
get("/work_packages/auto_complete").should route_to( controller: 'work_packages/auto_completes', |
||||
action: 'index' ) |
||||
end |
||||
end |
@ -0,0 +1,43 @@ |
||||
#-- 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 WorkPackages::CalendarsController do |
||||
|
||||
it "should connect GET /work_packages/calendar to work_package/calendar#index" do |
||||
get("/work_packages/calendar").should route_to( controller: 'work_packages/calendars', |
||||
action: 'index' ) |
||||
end |
||||
|
||||
it "should connect GET /project/1/work_packages/calendar to work_package/calendar#index" do |
||||
get("/projects/1/work_packages/calendar").should route_to( controller: 'work_packages/calendars', |
||||
action: 'index', |
||||
project_id: '1') |
||||
end |
||||
end |
@ -0,0 +1,37 @@ |
||||
#-- 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 WorkPackages::ContextMenusController do |
||||
|
||||
it "should connect GET /work_packages/context_menu to work_package/context_menu#index" do |
||||
get("/work_packages/context_menu").should route_to( controller: 'work_packages/context_menus', |
||||
action: 'index' ) |
||||
end |
||||
end |
@ -0,0 +1,44 @@ |
||||
#-- 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 WorkPackagesController do |
||||
|
||||
it "should connect PUT /work_packages/1/preview to work_packages#preview" do |
||||
put("/work_packages/1/preview").should route_to( :controller => 'work_packages', |
||||
:action => 'preview', |
||||
:id => '1' ) |
||||
end |
||||
|
||||
it "should connect PUT /project/1/work_packages/preview to work_packages#preview" do |
||||
put("/projects/1/work_packages/preview").should route_to( :controller => 'work_packages', |
||||
:action => 'preview', |
||||
:project_id => '1' ) |
||||
end |
||||
end |
@ -1,138 +0,0 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
require File.expand_path('../../../test_helper', __FILE__) |
||||
|
||||
class Issues::ContextMenusControllerTest < ActionController::TestCase |
||||
fixtures :all |
||||
|
||||
def test_context_menu_one_issue |
||||
@request.session[:user_id] = 2 |
||||
get :issues, :ids => [1] |
||||
assert_response :success |
||||
assert_template 'context_menu' |
||||
assert_tag :tag => 'a', :content => 'Edit', |
||||
:attributes => { :href => '/work_packages/1/edit', |
||||
:class => 'icon-edit' } |
||||
assert_tag :tag => 'a', :content => 'Closed', |
||||
:attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'Immediate', |
||||
:attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', |
||||
:class => '' } |
||||
# Versions |
||||
assert_tag :tag => 'a', :content => '2.0', |
||||
:attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'eCookbook Subproject 1 - 2.0', |
||||
:attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', |
||||
:class => '' } |
||||
|
||||
assert_tag :tag => 'a', :content => 'Dave Lopper', |
||||
:attributes => { :href => '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'Duplicate', |
||||
:attributes => { :href => '/projects/ecookbook/work_packages/new?copy_from=1', |
||||
:class => 'icon-duplicate' } |
||||
assert_tag :tag => 'a', :content => 'Copy', |
||||
:attributes => { :href => '/work_packages/move/new?copy_options%5Bcopy%5D=t&ids%5B%5D=1' } |
||||
assert_tag :tag => 'a', :content => 'Move', |
||||
:attributes => { :href => '/work_packages/move/new?ids%5B%5D=1'} |
||||
assert_tag :tag => 'a', :content => 'Delete', |
||||
:attributes => { :href => '/work_packages?ids%5B%5D=1' } |
||||
end |
||||
|
||||
def test_context_menu_one_issue_by_anonymous |
||||
get :issues, :ids => [1] |
||||
assert_response :success |
||||
assert_template 'context_menu' |
||||
assert_select "a.disabled", :text => /Delete/ |
||||
end |
||||
|
||||
def test_context_menu_multiple_issues_of_same_project |
||||
@request.session[:user_id] = 2 |
||||
get :issues, :ids => [1, 2] |
||||
assert_response :success |
||||
assert_template 'context_menu' |
||||
assert_not_nil assigns(:issues) |
||||
assert_equal [1, 2], assigns(:issues).map(&:id).sort |
||||
|
||||
ids = assigns(:issues).map(&:id).map {|i| "ids%5B%5D=#{i}"}.join('&') |
||||
assert_tag :tag => 'a', :content => 'Edit', |
||||
:attributes => { :href => "/issues/bulk_edit?#{ids}", |
||||
:class => 'icon-edit' } |
||||
assert_tag :tag => 'a', :content => 'Closed', |
||||
:attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'Immediate', |
||||
:attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'Dave Lopper', |
||||
:attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'Copy', |
||||
:attributes => { :href => "/work_packages/move/new?copy_options%5Bcopy%5D=t&#{ids}"} |
||||
assert_tag :tag => 'a', :content => 'Move', |
||||
:attributes => { :href => "/work_packages/move/new?#{ids}"} |
||||
assert_tag :tag => 'a', :content => 'Delete', |
||||
:attributes => { :href => "/work_packages?#{ids}"} |
||||
end |
||||
|
||||
def test_context_menu_multiple_issues_of_different_projects |
||||
@request.session[:user_id] = 2 |
||||
get :issues, :ids => [1, 2, 6] |
||||
assert_response :success |
||||
assert_template 'context_menu' |
||||
assert_not_nil assigns(:issues) |
||||
assert_equal [1, 2, 6], assigns(:issues).map(&:id).sort |
||||
|
||||
ids = assigns(:issues).map(&:id).map {|i| "ids%5B%5D=#{i}"}.join('&') |
||||
assert_tag :tag => 'a', :content => 'Edit', |
||||
:attributes => { :href => "/issues/bulk_edit?#{ids}", |
||||
:class => 'icon-edit' } |
||||
assert_tag :tag => 'a', :content => 'Closed', |
||||
:attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'Immediate', |
||||
:attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'John Smith', |
||||
:attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", |
||||
:class => '' } |
||||
assert_tag :tag => 'a', :content => 'Delete', |
||||
:attributes => { :href => "/work_packages?#{ids}"} |
||||
end |
||||
|
||||
def test_context_menu_issue_visibility |
||||
get :issues, :ids => [1, 4] |
||||
assert_response :success |
||||
assert_template 'context_menu' |
||||
assert_equal [1], assigns(:issues).collect(&:id) |
||||
end |
||||
end |
Loading…
Reference in new issue