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