Merge branch 'release/4.2' into release/4.3

pull/6827/head
Jan Sandbrink 9 years ago
commit 237741106e
  1. 8
      README.md
  2. 21
      app/controllers/cost_reports_controller.rb
  3. 75
      app/models/cost_query/cache.rb
  4. 13
      app/models/cost_query/custom_field_mixin.rb
  5. 5
      lib/open_project/reporting/engine.rb
  6. 31
      lib/open_project/reporting/patches/open_project/configuration_patch.rb
  7. 47
      spec/lib/open_project/configuration_spec.rb
  8. 124
      spec/models/cost_query/cache_spec.rb
  9. 45
      spec/models/cost_query/filter_spec.rb
  10. 72
      spec/models/cost_query/group_by_spec.rb
  11. 129
      spec/requests/custom_field_cache_spec.rb
  12. 31
      spec/support/configuration_helper.rb
  13. 34
      spec/support/custom_field_filter.rb

@ -46,6 +46,14 @@ from your `Gemfile.plugins` in your OpenProject installation folder and run:
to uninstall the ReportingEngine and the OpenProject Reporting plugin. to uninstall the ReportingEngine and the OpenProject Reporting plugin.
Configuration
-------------
* `cost_reporting_cache_filter_classes: true`
OpenProject Reporting, when not configured otherwise, optimizes response time by caching the filters and group by options generated for work package custom fields. Only when the custom fields are invalidated, does reporting recreate the elements by information from the database. In some scenarios, such a behavior might not be desirable. Especially, when databases are switched between requests to serve information from another installation, caching will almost always fail as the information is outdated and in some edge cases, filters and group by options are displayed erroneously. In such a setting, it is advisible to deactivate the caching by setting `cost_reporting_cache_filter_classes` to `false` in OpenProject's `config/configuration.yml`
Bug Reporting Bug Reporting
------------- -------------

@ -46,29 +46,12 @@ class CostReportsController < ApplicationController
helper_method :private_queries helper_method :private_queries
attr_accessor :cost_types, :unit_id, :cost_type attr_accessor :cost_types, :unit_id, :cost_type
cattr_accessor :custom_fields_updated_on, :custom_fields_id_sum
# Checks if custom fields have been updated, added or removed since we # Checks if custom fields have been updated, added or removed since we
# last saw them, to rebuild the filters and group bys. # last saw them, to rebuild the filters and group bys.
# Called once per request. # Called once per request.
def check_cache(force_update = false) def check_cache
custom_fields_updated_on = WorkPackageCustomField.maximum(:updated_at) CostQuery::Cache.check
custom_fields_id_sum = WorkPackageCustomField.sum(:id) + WorkPackageCustomField.count
if force_update or (custom_fields_updated_on && custom_fields_id_sum)
if force_update or (
self.class.custom_fields_updated_on != custom_fields_updated_on ||
self.class.custom_fields_id_sum != custom_fields_id_sum)
self.class.custom_fields_updated_on = custom_fields_updated_on
self.class.custom_fields_id_sum = custom_fields_id_sum
CostQuery::Filter.reset!
CostQuery::Filter::CustomFieldEntries.reset!
CostQuery::GroupBy.reset!
CostQuery::GroupBy::CustomFieldEntries.reset!
end
end
end end
## ##

@ -0,0 +1,75 @@
#-- copyright
# OpenProject Reporting Plugin
#
# Copyright (C) 2010 - 2014 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#++
module CostQuery::Cache
class << self
def check
reset! if reset_required?
end
def reset!
update_reset_on
CostQuery::Filter.reset!
CostQuery::Filter::CustomFieldEntries.reset!
CostQuery::GroupBy.reset!
CostQuery::GroupBy::CustomFieldEntries.reset!
end
protected
attr_accessor :latest_custom_field_change,
:custom_field_count
def invalid?
changed_on = fetch_latest_custom_field_change
field_count = fetch_current_custom_field_count
latest_custom_field_change != changed_on ||
custom_field_count != field_count
end
def update_reset_on
return if caching_disabled?
self.latest_custom_field_change = fetch_latest_custom_field_change
self.custom_field_count = fetch_current_custom_field_count
end
def fetch_latest_custom_field_change
WorkPackageCustomField.maximum(:updated_at)
end
def fetch_current_custom_field_count
WorkPackageCustomField.count
end
def caching_disabled?
!OpenProject::Configuration.cost_reporting_cache_filter_classes
end
def reset_required?
caching_disabled? || invalid?
end
end
# initialize to 0 to avoid forced cache reset on first request
self.custom_field_count = 0
end

@ -27,7 +27,8 @@ module CostQuery::CustomFieldMixin
'text' => mysql? ? 'char' : 'text', 'text' => mysql? ? 'char' : 'text',
'bool' => mysql? ? 'unsigned' : 'boolean', 'bool' => mysql? ? 'unsigned' : 'boolean',
'date' => 'date', 'date' => 'date',
'int' => 'decimal(60,3)', 'float' => 'decimal(60,3)' } 'int' => 'decimal(60,3)',
'float' => 'decimal(60,3)' }
def self.extended(base) def self.extended(base)
base.inherited_attribute :factory base.inherited_attribute :factory
@ -41,6 +42,8 @@ module CostQuery::CustomFieldMixin
def reset! def reset!
@all = nil @all = nil
remove_subclasses
end end
def generate_subclasses def generate_subclasses
@ -52,6 +55,14 @@ module CostQuery::CustomFieldMixin
end end
end end
def remove_subclasses
parent.constants.each do |constant|
if constant.to_s.match /^CustomField\d+/
parent.send(:remove_const, constant)
end
end
end
def factory? def factory?
factory == self factory == self
end end

@ -98,6 +98,9 @@ module OpenProject::Reporting
require_dependency 'cost_query/group_by' require_dependency 'cost_query/group_by'
end end
patches [:CostlogController, :TimelogController, :CustomFieldsController] patches [:CostlogController,
:TimelogController,
:CustomFieldsController,
:'OpenProject::Configuration']
end end
end end

@ -0,0 +1,31 @@
#-- copyright
# OpenProject Reporting Plugin
#
# Copyright (C) 2010 - 2014 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#++
require_dependency 'open_project/configuration'
module OpenProject::Reporting::Patches
module OpenProject::ConfigurationPatch
def self.included(base)
base.class_eval do
@defaults['cost_reporting_cache_filter_classes'] = true
end
end
end
end

@ -0,0 +1,47 @@
#-- copyright
# OpenProject Reporting Plugin
#
# Copyright (C) 2010 - 2014 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#++
require 'spec_helper'
describe 'OpenProject::Configuration' do
context '.cost_reporting_cache_filter_classes' do
before do
# This prevents the values from the actual configuration file to influence
# the test outcome.
#
# TODO: I propose to port this over to the core to always prevent this for specs.
OpenProject::Configuration.load(file: 'bogus')
end
after do
# resetting for now to avoid braking specs, who by now rely on having the file read.
OpenProject::Configuration.load
end
it 'is a true by default via the method' do
expect(OpenProject::Configuration.cost_reporting_cache_filter_classes).to be_truthy
end
it 'is true by default via the hash' do
expect(OpenProject::Configuration['cost_reporting_cache_filter_classes']).to be_truthy
end
end
end

@ -0,0 +1,124 @@
#-- copyright
# OpenProject Reporting Plugin
#
# Copyright (C) 2010 - 2014 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#++
require 'spec_helper'
require File.join(File.dirname(__FILE__), '..', '..', 'support', 'configuration_helper')
describe CostQuery::Cache do
include OpenProject::Reporting::SpecHelper::ConfigurationHelper
def all_caches
[ CostQuery::GroupBy::CustomFieldEntries,
CostQuery::GroupBy,
CostQuery::Filter::CustomFieldEntries,
CostQuery::Filter ]
end
def expect_reset_on_caches
all_caches.each do |klass|
expect(klass).to receive(:reset!)
end
end
def expect_no_reset_on_caches
all_caches.each do |klass|
expect(klass).to_not receive(:reset!)
end
end
def reset_cache_keys
# resetting internal caching keys to avoid dependencies with other specs
described_class.send(:latest_custom_field_change=, nil)
described_class.send(:custom_field_count=, 0)
end
def custom_fields_exist
allow(WorkPackageCustomField).to receive(:maximum).and_return(Time.now)
allow(WorkPackageCustomField).to receive(:count).and_return(23)
end
def no_custom_fields_exist
allow(WorkPackageCustomField).to receive(:maximum).and_return(nil)
allow(WorkPackageCustomField).to receive(:count).and_return(0)
end
before do
reset_cache_keys
end
after do
reset_cache_keys
end
describe '.check' do
context 'with cache_classes configuration enabled' do
before do
mock_cache_classes_setting_with(true)
end
it 'resets the caches on filters and group by' do
custom_fields_exist
expect_reset_on_caches
described_class.check
end
it 'stores when the last update was made and does not reset again if nothing changed' do
custom_fields_exist
expect_reset_on_caches
described_class.check
expect_no_reset_on_caches
described_class.check
end
it 'does reset the cache if last CustomField is removed' do
custom_fields_exist
expect_reset_on_caches
described_class.check
no_custom_fields_exist
expect_reset_on_caches
described_class.check
end
end
context 'with_cache_classes configuration disabled' do
before do
mock_cache_classes_setting_with(false)
end
it 'resets the cache again even if nothing changed' do
custom_fields_exist
expect_reset_on_caches
described_class.check
expect_reset_on_caches
described_class.check
end
end
end
end

@ -18,6 +18,7 @@
#++ #++
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
require File.join(File.dirname(__FILE__), '..', '..', 'support', 'custom_field_filter')
describe CostQuery, type: :model, reporting_query_helper: true do describe CostQuery, type: :model, reporting_query_helper: true do
minimal_query minimal_query
@ -324,17 +325,23 @@ describe CostQuery, type: :model, reporting_query_helper: true do
cf cf
end end
let(:custom_field2) do
FactoryGirl.build(:work_package_custom_field, name: 'Database',
field_format: "list",
possible_values: ['value'])
end
after(:all) do after(:all) do
clear_cache clear_cache
end end
def clear_cache def clear_cache
CostReportsController.new.check_cache(true) CostQuery::Cache.reset!
CostQuery::Filter::CustomFieldEntries.all CostQuery::Filter::CustomFieldEntries.all
end end
def delete_work_package_custom_field(name) def delete_work_package_custom_field(cf)
WorkPackageCustomField.find_by_name(name).destroy cf.destroy
clear_cache clear_cache
end end
@ -345,44 +352,40 @@ describe CostQuery, type: :model, reporting_query_helper: true do
clear_cache clear_cache
end end
def class_name_for(name) include OpenProject::Reporting::SpecHelper::CustomFieldFilterHelper
"CostQuery::Filter::CustomField#{WorkPackageCustomField.find_by_name(name).id}"
end
it "should create classes for custom fields that get added after starting the server" do it "should create classes for custom fields that get added after starting the server" do
custom_field custom_field
expect { class_name_for('My custom field').constantize }.not_to raise_error expect { filter_class_name_string(custom_field).constantize }.not_to raise_error
end end
it "should remove the custom field classes after it is deleted" do it "should remove the custom field classes after it is deleted" do
custom_field custom_field
class_name = class_name_for('My custom field') class_name = filter_class_name_string(custom_field)
delete_work_package_custom_field("My custom field") delete_work_package_custom_field(custom_field)
expect(CostQuery::Filter.all).not_to include class_name.constantize expect { filter_class_name_string(custom_field).constantize }.to raise_error NameError
end end
it "should provide the correct available values" do it "should provide the correct available values" do
FactoryGirl.create(:work_package_custom_field, name: 'Database', custom_field2.save
field_format: "list",
possible_values: ['value'])
clear_cache clear_cache
ao = class_name_for('Database').constantize.available_operators.map(&:name) ao = filter_class_name_string(custom_field2).constantize.available_operators.map(&:name)
CostQuery::Operator.null_operators.each do |o| CostQuery::Operator.null_operators.each do |o|
expect(ao).to include o.name expect(ao).to include o.name
end end
end end
it "should update the available values on change" do it "should update the available values on change" do
FactoryGirl.create(:work_package_custom_field, name: 'Database', custom_field2.save
field_format: "list",
possible_values: ['value'])
update_work_package_custom_field("Database", field_format: "string") update_work_package_custom_field("Database", field_format: "string")
ao = class_name_for('Database').constantize.available_operators.map(&:name) ao = filter_class_name_string(custom_field2).constantize.available_operators.map(&:name)
CostQuery::Operator.string_operators.each do |o| CostQuery::Operator.string_operators.each do |o|
expect(ao).to include o.name expect(ao).to include o.name
end end
update_work_package_custom_field("Database", field_format: "int") update_work_package_custom_field("Database", field_format: "int")
ao = class_name_for('Database').constantize.available_operators.map(&:name) ao = filter_class_name_string(custom_field2).constantize.available_operators.map(&:name)
CostQuery::Operator.integer_operators.each do |o| CostQuery::Operator.integer_operators.each do |o|
expect(ao).to include o.name expect(ao).to include o.name
end end
@ -391,13 +394,13 @@ describe CostQuery, type: :model, reporting_query_helper: true do
it "includes custom fields classes in CustomFieldEntries.all" do it "includes custom fields classes in CustomFieldEntries.all" do
custom_field custom_field
expect(CostQuery::Filter::CustomFieldEntries.all). expect(CostQuery::Filter::CustomFieldEntries.all).
to include(class_name_for('My custom field').constantize) to include(filter_class_name_string(custom_field).constantize)
end end
it "includes custom fields classes in Filter.all" do it "includes custom fields classes in Filter.all" do
custom_field custom_field
expect(CostQuery::Filter.all). expect(CostQuery::Filter.all).
to include(class_name_for('My custom field').constantize) to include(filter_class_name_string(custom_field).constantize)
end end
def create_searchable_fields_and_values def create_searchable_fields_and_values

@ -18,6 +18,7 @@
#++ #++
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
require File.join(File.dirname(__FILE__), '..', '..', 'support', 'custom_field_filter')
describe CostQuery, type: :model, reporting_query_helper: true do describe CostQuery, type: :model, reporting_query_helper: true do
let!(:type) { FactoryGirl.create(:type) } let!(:type) { FactoryGirl.create(:type) }
@ -216,74 +217,75 @@ describe CostQuery, type: :model, reporting_query_helper: true do
describe CostQuery::GroupBy::CustomFieldEntries do describe CostQuery::GroupBy::CustomFieldEntries do
let!(:project){ FactoryGirl.create(:project_with_types) } let!(:project){ FactoryGirl.create(:project_with_types) }
let!(:custom_field) do
FactoryGirl.create(:work_package_custom_field)
end
let(:custom_field2) do
FactoryGirl.build(:work_package_custom_field)
end
before do before do
create_work_package_custom_field("Searchable Field") check_cache
CostQuery::GroupBy.all.merge CostQuery::GroupBy::CustomFieldEntries.all CostQuery::GroupBy.all.merge CostQuery::GroupBy::CustomFieldEntries.all
end end
def check_cache def check_cache
CostReportsController.new.check_cache CostQuery::Cache.reset!
CostQuery::GroupBy::CustomFieldEntries.all CostQuery::GroupBy::CustomFieldEntries.all
end end
def create_work_package_custom_field(name) def delete_work_package_custom_field(custom_field)
WorkPackageCustomField.create(name: name, custom_field.destroy
min_length: 1,
regexp: "",
is_for_all: true,
max_length: 100,
possible_values: "",
is_required: false,
field_format: "string",
searchable: true,
default_value: "Default string",
editable: true)
check_cache check_cache
end end
def delete_work_package_custom_field(name) include OpenProject::Reporting::SpecHelper::CustomFieldFilterHelper
WorkPackageCustomField.find_by_name(name).destroy
check_cache
end
def class_name_for(name)
"CostQuery::GroupBy::CustomField#{WorkPackageCustomField.find_by_name(name).id}"
end
it "should create classes for custom fields" do it "should create classes for custom fields" do
# Would raise a name error # Would raise a name error
expect { class_name_for('Searchable Field').constantize }.to_not raise_error expect { group_by_class_name_string(custom_field).constantize }.to_not raise_error
end end
it "should create new classes for custom fields that get added after starting the server" do it "should create new classes for custom fields that get added after starting the server" do
create_work_package_custom_field("AFreshCustomField") custom_field2.save!
check_cache
# Would raise a name error # Would raise a name error
expect { class_name_for('AFreshCustomField').constantize }.to_not raise_error expect { group_by_class_name_string(custom_field2).constantize }.to_not raise_error
WorkPackageCustomField.find_by_name("AFreshCustomField").destroy
custom_field2.destroy
end end
it "should remove the custom field classes after it is deleted" do it "should remove the custom field classes after it is deleted" do
create_work_package_custom_field("AFreshCustomField") custom_field2.save!
name = class_name_for('AFreshCustomField')
delete_work_package_custom_field("AFreshCustomField") check_cache
expect(CostQuery::GroupBy.all).not_to include name.constantize
custom_field2.destroy
check_cache
expect { group_by_class_name_string(custom_field2).constantize }.to raise_error NameError
end end
it "includes custom fields classes in CustomFieldEntries.all" do it "includes custom fields classes in CustomFieldEntries.all" do
expect(CostQuery::GroupBy::CustomFieldEntries.all). expect(CostQuery::GroupBy::CustomFieldEntries.all).
to include(class_name_for('Searchable Field').constantize) to include(group_by_class_name_string(custom_field).constantize)
end end
it "includes custom fields classes in GroupBy.all" do it "includes custom fields classes in GroupBy.all" do
expect(CostQuery::GroupBy.all). expect(CostQuery::GroupBy.all).
to include(class_name_for('Searchable Field').constantize) to include(group_by_class_name_string(custom_field).constantize)
end end
it "is usable as filter" do it "is usable as filter" do
create_work_package_custom_field("Database") custom_field2.save!
id = WorkPackageCustomField.find_by_name('Database').id
@query.group_by "custom_field_#{id}".to_sym check_cache
@query.group_by "custom_field_#{custom_field2.id}".to_sym
footprint = @query.result.each_direct_result.map { |c| [c.count, c.units.to_i] }.sort footprint = @query.result.each_direct_result.map { |c| [c.count, c.units.to_i] }.sort
expect(footprint).to eq([[8, 8]]) expect(footprint).to eq([[8, 8]])
end end

@ -0,0 +1,129 @@
#-- copyright
# OpenProject Reporting Plugin
#
# Copyright (C) 2010 - 2014 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#++
require 'spec_helper'
require File.join(File.dirname(__FILE__), '..', 'support', 'custom_field_filter')
require File.join(File.dirname(__FILE__), '..', 'support', 'configuration_helper')
describe 'Custom field filter and group by caching', type: :request do
include OpenProject::Reporting::SpecHelper::CustomFieldFilterHelper
include OpenProject::Reporting::SpecHelper::ConfigurationHelper
let(:project) { FactoryGirl.create(:valid_project) }
let(:user) { FactoryGirl.create(:admin) }
let(:custom_field) { FactoryGirl.build(:work_package_custom_field) }
let(:custom_field2) { FactoryGirl.build(:work_package_custom_field) }
before do
allow(User).to receive(:current).and_return(user)
custom_field.save!
end
def expect_group_by_all_to_include(custom_field)
expect(CostQuery::GroupBy.all).to include(group_by_class_name_string(custom_field).constantize)
end
def expect_filter_all_to_include(custom_field)
expect(CostQuery::Filter.all).to include(filter_class_name_string(custom_field).constantize)
end
def expect_group_by_all_to_not_exist(custom_field)
# can not check for whether the element is included in CostQuery::GroupBy.all if it does not exist
expect { group_by_class_name_string(custom_field).constantize }.to raise_error NameError
end
def expect_filter_all_to_not_exist(custom_field)
# can not check for whether the element is included in CostQuery::Filter.all if it does not exist
expect { filter_class_name_string(custom_field).constantize }.to raise_error NameError
end
def visit_cost_reports_index
get "projects/#{project.id}/cost_reports"
end
it 'removes the filter/group_by if the custom field is removed' do
custom_field2.save!
visit_cost_reports_index
expect_group_by_all_to_include(custom_field)
expect_group_by_all_to_include(custom_field2)
expect_filter_all_to_include(custom_field)
expect_filter_all_to_include(custom_field2)
custom_field2.destroy
visit_cost_reports_index
expect_group_by_all_to_include(custom_field)
expect_group_by_all_to_not_exist(custom_field2)
expect_filter_all_to_include(custom_field)
expect_filter_all_to_not_exist(custom_field2)
end
it 'removes the filter/group_by if the last custom field is removed' do
visit_cost_reports_index
expect_group_by_all_to_include(custom_field)
expect_filter_all_to_include(custom_field)
custom_field.destroy
visit_cost_reports_index
expect_group_by_all_to_not_exist(custom_field)
expect_filter_all_to_not_exist(custom_field)
end
it 'allows for changing the db table between requests if no caching is done' do
old_table_name = WorkPackageCustomField.table_name
new_table_name = 'custom_fields_clone'
new_id = custom_field.id + 1
begin
mock_cache_classes_setting_with(false)
visit_cost_reports_index
expect_group_by_all_to_include(custom_field)
expect_filter_all_to_include(custom_field)
ActiveRecord::Base.connection.execute("CREATE TABLE #{new_table_name} AS SELECT * from custom_fields;")
ActiveRecord::Base.connection.execute("UPDATE #{new_table_name} SET id = #{new_id} WHERE id = #{custom_field.id};")
CustomField::Translation.where(custom_field_id: custom_field.id).update_all(custom_field_id: new_id)
WorkPackageCustomField.table_name = new_table_name
visit_cost_reports_index
expect_group_by_all_to_not_exist(custom_field)
expect_filter_all_to_not_exist(custom_field)
expect_group_by_all_to_include(new_id)
expect_filter_all_to_include(new_id)
ensure
WorkPackageCustomField.table_name = old_table_name
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS #{new_table_name}")
end
end
end

@ -0,0 +1,31 @@
#-- copyright
# OpenProject Reporting Plugin
#
# Copyright (C) 2010 - 2014 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#++
module OpenProject::Reporting::SpecHelper
module ConfigurationHelper
def mock_cache_classes_setting_with(value)
allow(OpenProject::Configuration).to receive(:[]).and_call_original
allow(OpenProject::Configuration).to receive(:[])
.with('cost_reporting_cache_filter_classes')
.and_return(value)
allow(OpenProject::Configuration).to receive(:cost_reporting_cache_filter_classes)
.and_return(value)
end
end
end

@ -0,0 +1,34 @@
#-- copyright
# OpenProject Reporting Plugin
#
# Copyright (C) 2010 - 2014 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#++
module OpenProject::Reporting::SpecHelper
module CustomFieldFilterHelper
def group_by_class_name_string(custom_field)
id = custom_field.is_a?(ActiveRecord::Base) ? custom_field.id : custom_field
"CostQuery::GroupBy::CustomField#{id}"
end
def filter_class_name_string(custom_field)
id = custom_field.is_a?(ActiveRecord::Base) ? custom_field.id : custom_field
"CostQuery::Filter::CustomField#{id}"
end
end
end
Loading…
Cancel
Save