From 79c628c043b0ec93e097734d2dddf027f676d5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 9 Aug 2017 12:52:09 +0200 Subject: [PATCH 1/2] Bump VERSION to 7.2.1 --- lib/open_project/reporting/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/open_project/reporting/version.rb b/lib/open_project/reporting/version.rb index c7450b6d6a..0d947a6e56 100644 --- a/lib/open_project/reporting/version.rb +++ b/lib/open_project/reporting/version.rb @@ -19,6 +19,6 @@ module OpenProject module Reporting - VERSION = "7.2.0" + VERSION = "7.2.1" end end From 5343e95e5ba09c4508466a5326ae9b4a5c271aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 15 Aug 2017 15:59:14 +0200 Subject: [PATCH 2/2] [26050] Add custom aggregation for list CFs (#121) https://community.openproject.com/wp/26050 [ci skip] --- Gemfile.plugins | 4 +- app/models/cost_query/custom_field_mixin.rb | 41 +++++- spec/features/custom_fields_spec.rb | 135 ++++++++++++++++++++ 3 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 spec/features/custom_fields_spec.rb diff --git a/Gemfile.plugins b/Gemfile.plugins index 094337c260..af7ee8e96a 100644 --- a/Gemfile.plugins +++ b/Gemfile.plugins @@ -1,7 +1,7 @@ # Dependencies (need to be called before the actual gem) group :opf_plugins do - gem "openproject-costs", :git => "https://github.com/finnlabs/openproject-costs.git", :branch => "dev" - gem "reporting_engine", :git => "https://github.com/finnlabs/reporting_engine.git", :branch => "dev" + gem "openproject-costs", :git => "https://github.com/finnlabs/openproject-costs.git", :branch => ENV['TRAVIS_BRANCH'] + gem "reporting_engine", :git => "https://github.com/finnlabs/reporting_engine.git", :branch => ENV['TRAVIS_BRANCH'] # Used by travis to bundle this plugin with the OpenProject core. # The tested plugin will be moved to the path `./plugins/this` diff --git a/app/models/cost_query/custom_field_mixin.rb b/app/models/cost_query/custom_field_mixin.rb index 3fa7eb5e38..952e7393e8 100644 --- a/app/models/cost_query/custom_field_mixin.rb +++ b/app/models/cost_query/custom_field_mixin.rb @@ -87,7 +87,44 @@ module CostQuery::CustomFieldMixin @class_name = class_name dont_inherit :group_fields db_field table_name - join_table (<<-SQL % [CustomValue.table_name, table_name, field.id, field.name, SQL_TYPES[field.field_format]]).gsub(/^ /, '') + if field.list? + join_table list_join_table(field) + else + join_table default_join_table(field) + end + instance_eval(&on_prepare) + self + end + + def list_join_table(field) + cast_as = SQL_TYPES[field.field_format] + cf_name = "custom_field#{field.id}" + + custom_values_table = CustomValue.table_name + custom_options_table = CustomOption.table_name + + <<-SQL + -- BEGIN Custom Field Join: #{cf_name} + LEFT OUTER JOIN ( + SELECT + CAST(co.value AS #{cast_as}) AS #{cf_name}, + cv.customized_type, + cv.custom_field_id, + cv.customized_id + FROM #{custom_values_table} cv + INNER JOIN #{custom_options_table} co + ON cv.custom_field_id = co.custom_field_id AND CAST(cv.value AS decimal(60,3)) = co.id + ) AS #{cf_name} + ON #{cf_name}.customized_type = 'WorkPackage' + + AND #{cf_name}.custom_field_id = #{field.id} + AND #{cf_name}.customized_id = entries.work_package_id + -- END Custom Field Join: #{cf_name} + SQL + end + + def default_join_table(field) + <<-SQL % [CustomValue.table_name, table_name, field.id, field.name, SQL_TYPES[field.field_format]] -- BEGIN Custom Field Join: "%4$s" LEFT OUTER JOIN ( \tSELECT @@ -103,8 +140,6 @@ module CostQuery::CustomFieldMixin AND %2$s.customized_id = entries.work_package_id -- END Custom Field Join: "%4$s" SQL - instance_eval(&on_prepare) - self end def new(*) diff --git a/spec/features/custom_fields_spec.rb b/spec/features/custom_fields_spec.rb new file mode 100644 index 0000000000..360e19f001 --- /dev/null +++ b/spec/features/custom_fields_spec.rb @@ -0,0 +1,135 @@ +#-- copyright +# OpenProject Costs Plugin +# +# Copyright (C) 2009 - 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 'Custom fields reporting', type: :feature, js: true do + let(:type) { FactoryGirl.create :type } + let(:project) { FactoryGirl.create :project, types: [type] } + + let(:user) { FactoryGirl.create :admin } + + let(:work_package) { + FactoryGirl.create :work_package, + project: project, + custom_values: initial_custom_values + } + + let!(:time_entry1) { + FactoryGirl.create :time_entry, + user: user, + work_package: work_package, + project: project, + hours: 10 + } + + let!(:time_entry2) { + FactoryGirl.create :time_entry, + user: user, + work_package: work_package, + project: project, + hours: 2.50 + } + + before do + login_as(user) + visit '/cost_reports' + end + + context 'with multi value cf' do + let!(:custom_field) do + FactoryGirl.create( + :list_wp_custom_field, + name: "List CF", + multi_value: true, + types: [type], + projects: [project], + possible_values: ['First option', 'Second option'] + ) + end + + let(:initial_custom_values) { { custom_field.id => 1 } } + + it 'groups by the multi CF (Regression #26050)' do + expect(page).to have_selector('#group-by--add-columns') + expect(page).to have_selector('#group-by--add-rows') + + select 'List CF', from: 'group-by--add-columns' + select 'Work package', from: 'group-by--add-rows' + + find('#query-icon-apply-button').click + + # Expect row of work package + within('#result-table') do + expect(page).to have_selector('a.issue', text: "#{work_package.type.to_s} ##{work_package.id}") + expect(page).to have_selector('th.inner', text: 'First option') + expect(page).to have_no_selector('th.inner', text: 'Second option') + + # Only first option should have content for the work package + expect(page).to have_selector('table.report tbody tr', count: 1) + row_elements = page.all('table.report tr.odd th') + + expect(row_elements[0].text).to eq(project.name) + expect(row_elements[1].text).to eq(work_package.to_s) + + row_elements = page.all('table.report tr.odd td') + expect(row_elements[0].text).to eq('12.50 hours') + end + end + end + + context 'with text CF' do + let(:custom_field) do + FactoryGirl.create( + :text_wp_custom_field, + name: 'Text CF', + types: [type], + projects: [project] + ) + end + let(:initial_custom_values) { { custom_field.id => 'foo' } } + + it 'groups by a text CF' do + expect(page).to have_selector('#group-by--add-columns') + expect(page).to have_selector('#group-by--add-rows') + + select 'Text CF', from: 'group-by--add-columns' + select 'Work package', from: 'group-by--add-rows' + + find('#query-icon-apply-button').click + + # Expect row of work package + within('#result-table') do + expect(page).to have_selector('a.issue', text: "#{work_package.type.to_s} ##{work_package.id}") + expect(page).to have_selector('th.inner', text: 'foo') + expect(page).to have_no_selector('th.inner', text: 'None') + + # Only first option should have content for the work package + expect(page).to have_selector('table.report tbody tr', count: 1) + row_elements = page.all('table.report tr.odd th') + + expect(row_elements[0].text).to eq(project.name) + expect(row_elements[1].text).to eq(work_package.to_s) + + row_elements = page.all('table.report tr.odd td') + expect(row_elements[0].text).to eq('12.50 hours') + end + end + end +end