From e3f12bcd074a3ca25bf74d305f4fe53ff50cffa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 27 Oct 2021 15:20:21 +0200 Subject: [PATCH] Add support and test for custom fields in CSV export --- app/models/exports/formatters/custom_field.rb | 29 ++++++ config/initializers/export_formats.rb | 2 + .../projects/exporter/csv_integration_spec.rb | 93 ++++++++++++++++--- 3 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 app/models/exports/formatters/custom_field.rb diff --git a/app/models/exports/formatters/custom_field.rb b/app/models/exports/formatters/custom_field.rb new file mode 100644 index 0000000000..93667100f9 --- /dev/null +++ b/app/models/exports/formatters/custom_field.rb @@ -0,0 +1,29 @@ +module Exports + module Formatters + class CustomField < Default + ## + # Checks if this column is applicable for this column + def self.apply?(attribute) + attribute.start_with?('cf_') + end + + ## + # Takes a WorkPackage and an attribute and returns the value to be exported. + def format(object, **) + return '' if custom_field.nil? + + object.formatted_custom_value_for(custom_field) + end + + ## + # Finds a custom field from the attribute identifier + def custom_field + unless defined?(@custom_field) + @custom_field = ::CustomField.find_by(id: attribute.to_s.sub('cf_', '').to_i) + end + + @custom_field + end + end + end +end diff --git a/config/initializers/export_formats.rb b/config/initializers/export_formats.rb index 6da0210cdf..7b82029dbf 100644 --- a/config/initializers/export_formats.rb +++ b/config/initializers/export_formats.rb @@ -8,8 +8,10 @@ OpenProject::Application.configure do |application| formatter WorkPackage, WorkPackage::Exports::Formatters::Costs formatter WorkPackage, WorkPackage::Exports::Formatters::EstimatedHours + formatter WorkPackage, Exports::Formatters::CustomField list Project, Projects::Exports::CSV + formatter Project, Exports::Formatters::CustomField end end end diff --git a/spec/models/projects/exporter/csv_integration_spec.rb b/spec/models/projects/exporter/csv_integration_spec.rb index 3acee5d538..d1c4570014 100644 --- a/spec/models/projects/exporter/csv_integration_spec.rb +++ b/spec/models/projects/exporter/csv_integration_spec.rb @@ -35,7 +35,41 @@ describe Projects::Exports::CSV, 'integration', type: :model do login_as current_user end - let(:project) { FactoryBot.create(:project) } + shared_let(:version_cf) { FactoryBot.create(:version_project_custom_field) } + shared_let(:bool_cf) { FactoryBot.create(:bool_project_custom_field) } + shared_let(:user_cf) { FactoryBot.create(:user_project_custom_field) } + shared_let(:int_cf) { FactoryBot.create(:int_project_custom_field) } + shared_let(:float_cf) { FactoryBot.create(:float_project_custom_field) } + shared_let(:text_cf) { FactoryBot.create(:text_project_custom_field) } + shared_let(:string_cf) { FactoryBot.create(:string_project_custom_field) } + shared_let(:date_cf) { FactoryBot.create(:date_project_custom_field) } + + shared_let(:system_version) { FactoryBot.create(:version, sharing: 'system') } + + shared_let(:role) do + FactoryBot.create(:role) + end + + shared_let(:other_user) do + FactoryBot.create(:user, + firstname: 'Other', + lastname: 'User') + end + + shared_let(:project) do + FactoryBot.create(:project, members: { other_user => role }).tap do |p| + p.send(:"custom_field_#{int_cf.id}=", 5) + p.send(:"custom_field_#{bool_cf.id}=", true) + p.send(:"custom_field_#{version_cf.id}=", system_version) + p.send(:"custom_field_#{float_cf.id}=", 4.5) + p.send(:"custom_field_#{text_cf.id}=", 'Some **long** text') + p.send(:"custom_field_#{string_cf.id}=", 'Some small text') + p.send(:"custom_field_#{date_cf.id}=", Date.today) + p.send(:"custom_field_#{user_cf.id}=", other_user) + + p.save!(validate: false) + end + end let(:current_user) do FactoryBot.create(:user, @@ -47,31 +81,62 @@ describe Projects::Exports::CSV, 'integration', type: :model do described_class.new(query) end - it 'performs a successful export' do + let(:custom_fields) { project.available_custom_fields } + + let(:output) do data = '' instance.export! do |result| data = result.content end - data = CSV.parse(data) - expect(data.size).to eq(2) - expect(data.last).to eq [project.id.to_s, project.identifier, project.name, '', 'false'] + data end - context 'with no project visible' do - let(:current_user) { User.anonymous } + let(:parsed) do + CSV.parse(output) + end - it 'does not include the project' do - data = '' + let(:header) { parsed.first } + + let(:rows) { parsed.drop(1) } - instance.export! do |result| - data = result.content + it 'performs a successful export' do + expect(parsed.size).to eq(2) + expect(parsed.last).to eq [project.id.to_s, project.identifier, project.name, '', 'false'] + end + + describe 'custom field columns selected' do + before do + Setting.enabled_projects_columns += custom_fields.map { |cf| "cf_#{cf.id}" } + end + + context 'when ee enabled', with_ee: %i[custom_fields_in_projects_list] do + it 'renders all those columns' do + expect(parsed.size).to eq 2 + + cf_names = custom_fields.map(&:name) + expect(header).to eq ['id', 'Identifier', 'Name', 'Status', 'Public', *cf_names] + + custom_values = custom_fields.map { |cf| project.formatted_custom_value_for(cf) } + expect(rows.first) + .to eq [project.id.to_s, project.identifier, project.name, '', 'false', *custom_values] + end + end + + context 'when ee not enabled' do + it 'renders only the default columns' do + expect(header).to eq %w[id Identifier Name Status Public] end - expect(data).not_to include project.identifier + end + end - data = CSV.parse(data) - expect(data.size).to eq(1) + context 'with no project visible' do + let(:current_user) { User.anonymous } + + it 'does not include the project' do + expect(output).not_to include project.identifier + expect(parsed.size).to eq(1) end end end