From eb4d7a19d9ce344aa6eb7f51aeb759a9042bce93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 27 Oct 2021 11:42:24 +0200 Subject: [PATCH] Add CSV project export functionality --- app/controllers/projects_controller.rb | 43 ++++++- app/controllers/work_packages_controller.rb | 59 +++++---- app/models/exports/register.rb | 4 + app/models/projects/export.rb | 10 ++ app/models/projects/exports.rb | 35 ------ .../work_packages/exports/schedule_service.rb | 2 +- .../projects/_project_export_modal.html.erb | 16 +-- app/views/projects/index.html.erb | 50 ++++---- .../exports/cleanup_outdated_job.rb | 16 +-- app/workers/exports/export_job.rb | 117 ++++++++++++++++++ app/workers/projects/export_job.rb | 13 ++ app/workers/work_packages/export_job.rb | 27 ++++ .../work_packages/exports/export_job.rb | 116 ----------------- config/initializers/export_formats.rb | 15 +++ modules/bim/lib/open_project/bim/engine.rb | 8 +- .../lib/open_project/xls_export/engine.rb | 6 +- spec/features/projects/export_spec.rb | 85 +++++++++++++ .../projects/exporter/csv_integration_spec.rb | 50 ++++++-- spec/support/pages/projects/index.rb | 7 ++ 19 files changed, 436 insertions(+), 243 deletions(-) create mode 100644 app/models/projects/export.rb delete mode 100644 app/models/projects/exports.rb rename app/workers/{work_packages => }/exports/cleanup_outdated_job.rb (82%) create mode 100644 app/workers/exports/export_job.rb create mode 100644 app/workers/projects/export_job.rb create mode 100644 app/workers/work_packages/export_job.rb delete mode 100644 app/workers/work_packages/exports/export_job.rb create mode 100644 config/initializers/export_formats.rb create mode 100644 spec/features/projects/export_spec.rb rename app/models/work_package/exports.rb => spec/models/projects/exporter/csv_integration_spec.rb (53%) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 47b7a75da5..96828c331d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -56,7 +56,23 @@ class ProjectsController < ApplicationController @projects = load_projects query @orders = set_sorting query - render layout: 'no_menu' + respond_to do |format| + format.html do + render layout: 'no_menu' + end + + format.any(*supported_export_formats) do + export_list(request.format.symbol) + end + + format.atom do + atom_list + end + end + end + + current_menu_item :index do + :list_projects end def new @@ -70,8 +86,8 @@ class ProjectsController < ApplicationController # Delete @project def destroy service_call = ::Projects::ScheduleDeletionService - .new(user: current_user, model: @project) - .call + .new(user: current_user, model: @project) + .call if service_call.success? flash[:notice] = I18n.t('projects.delete.scheduled') @@ -135,7 +151,20 @@ class ProjectsController < ApplicationController @query end - protected + def export_list(mime_type) + job = Projects::ExportJob.perform_later( + export: Projects::Export.create, + user: current_user, + mime_type: mime_type, + query: Marshal.dump(@query) + ) + + if request.headers['Accept']&.include?('application/json') + render json: { job_id: job.job_id } + else + redirect_to job_status_path(job.job_id) + end + end def load_projects(query) query @@ -149,4 +178,10 @@ class ProjectsController < ApplicationController def set_sorting(query) query.orders.select(&:valid?).map { |o| [o.attribute.to_s, o.direction.to_s] } end + + def supported_export_formats + ::Exports::Register.list_formats(Project).map(&:to_s) + end + + helper_method :supported_export_formats end diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index 031811b9df..1fce6cd84b 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -87,9 +87,9 @@ class WorkPackagesController < ApplicationController def export_list(mime_type) job_id = WorkPackages::Exports::ScheduleService - .new(user: current_user) - .call(query: @query, mime_type: mime_type, params: params) - .result + .new(user: current_user) + .call(query: @query, mime_type: mime_type, params: params) + .result if request.headers['Accept']&.include?('application/json') render json: { job_id: job_id } @@ -99,9 +99,17 @@ class WorkPackagesController < ApplicationController end def export_single(mime_type) - exporter = WorkPackage::Exporter.for_single(mime_type) - exporter.single(work_package, params) do |export| - render_export_response export, fallback_path: work_package_path(work_package) + exporter = Exports::Register + .single_exporter(WorkPackage, mime_type) + .new(work_package, params) + + exporter.export! do |export| + send_data(export.content, + type: export.mime_type, + filename: export.title) + rescue ::Exports::ExportError => e + flash[:error] = e.message + redirect_back(fallback_location: work_package_path(work_package)) end end @@ -120,24 +128,13 @@ class WorkPackagesController < ApplicationController private - def render_export_response(export, fallback_path:) - if export.error? - flash[:error] = export.message - redirect_back(fallback_location: fallback_path) - else - send_data(export.content, - type: export.mime_type, - filename: export.title) - end - end - def authorize_on_work_package deny_access(not_found: true) unless work_package end def protect_from_unauthorized_export if (supported_list_formats + %w[atom]).include?(params[:format]) && - !User.current.allowed_to?(:export_work_packages, @project, global: @project.nil?) + !User.current.allowed_to?(:export_work_packages, @project, global: @project.nil?) deny_access false @@ -183,19 +180,19 @@ class WorkPackagesController < ApplicationController def journals @journals ||= begin - order = - if current_user.wants_comments_in_reverse_order? - Journal.arel_table['created_at'].desc - else - Journal.arel_table['created_at'].asc - end - - work_package - .journals - .changing - .includes(:user) - .order(order).to_a - end + order = + if current_user.wants_comments_in_reverse_order? + Journal.arel_table['created_at'].desc + else + Journal.arel_table['created_at'].asc + end + + work_package + .journals + .changing + .includes(:user) + .order(order).to_a + end end def index_redirect_path diff --git a/app/models/exports/register.rb b/app/models/exports/register.rb index 187ff72b84..9772d65cf7 100644 --- a/app/models/exports/register.rb +++ b/app/models/exports/register.rb @@ -33,6 +33,10 @@ module Exports class << self attr_reader :lists, :singles, :formatters + def register(&block) + instance_exec(&block) + end + def list(model, exporter) @lists ||= Hash.new do |hash, model_key| hash[model_key] = [] diff --git a/app/models/projects/export.rb b/app/models/projects/export.rb new file mode 100644 index 0000000000..648762681a --- /dev/null +++ b/app/models/projects/export.rb @@ -0,0 +1,10 @@ +class Projects::Export < Export + acts_as_attachable view_permission: :view_project, + add_permission: :view_project, + delete_permission: :view_project, + only_user_allowed: true + + def ready? + attachments.any? + end +end diff --git a/app/models/projects/exports.rb b/app/models/projects/exports.rb deleted file mode 100644 index 35200fd542..0000000000 --- a/app/models/projects/exports.rb +++ /dev/null @@ -1,35 +0,0 @@ -#-- encoding: UTF-8 - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-2021 the OpenProject GmbH -# -# 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 COPYRIGHT and LICENSE files for more details. -#++ - -module Projects::Exports - register = ::Exports::Register - - register.list(Project, Projects::Exports::CSV) -end diff --git a/app/services/work_packages/exports/schedule_service.rb b/app/services/work_packages/exports/schedule_service.rb index 992ea6e5b3..0543625a8d 100644 --- a/app/services/work_packages/exports/schedule_service.rb +++ b/app/services/work_packages/exports/schedule_service.rb @@ -45,7 +45,7 @@ class WorkPackages::Exports::ScheduleService private def schedule_export(export_storage, mime_type, params, query) - WorkPackages::Exports::ExportJob.perform_later(export: export_storage, + WorkPackages::ExportJob.perform_later(export: export_storage, user: user, mime_type: mime_type, options: params, diff --git a/app/views/projects/_project_export_modal.html.erb b/app/views/projects/_project_export_modal.html.erb index e74b63a13d..41fc9cf486 100644 --- a/app/views/projects/_project_export_modal.html.erb +++ b/app/views/projects/_project_export_modal.html.erb @@ -43,13 +43,15 @@ See COPYRIGHT and LICENSE files for more details.
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index 491fe180bc..5f4ab90316 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -65,36 +65,42 @@ See COPYRIGHT and LICENSE files for more details. <% end %> <% if current_user.admin? %> - +
+
  • + <%= link_to I18n.t('js.label_export'), + '', + title: I18n.t('js.label_export'), + class: 'modal-delivery-element--activation-link icon-context icon-export' %> +
  • + <%= render partial: 'project_export_modal' %> +
    + + <% end %> <% end %> <%= render partial: 'projects/filters/form', locals: { query: @query } %> <%= rails_cell Projects::TableCell, - @projects, - current_user: current_user, - orders: @orders, - params: params %> + @projects, + current_user: current_user, + orders: @orders, + params: params %> <% if User.current.admin? %>

    diff --git a/app/workers/work_packages/exports/cleanup_outdated_job.rb b/app/workers/exports/cleanup_outdated_job.rb similarity index 82% rename from app/workers/work_packages/exports/cleanup_outdated_job.rb rename to app/workers/exports/cleanup_outdated_job.rb index 7a7d997b02..dc9f3d9fce 100644 --- a/app/workers/work_packages/exports/cleanup_outdated_job.rb +++ b/app/workers/exports/cleanup_outdated_job.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class WorkPackages::Exports::CleanupOutdatedJob < ApplicationJob +class Exports::CleanupOutdatedJob < ApplicationJob queue_with_priority :low def self.perform_after_grace @@ -36,18 +36,8 @@ class WorkPackages::Exports::CleanupOutdatedJob < ApplicationJob end def perform - WorkPackages::Export - .where(too_old) + Export + .where('created_at <= ?', Time.current - OpenProject::Configuration.attachments_grace_period.minutes) .destroy_all end - - private - - def too_old - table = WorkPackages::Export.arel_table - - table[:created_at] - .lteq(Time.now - OpenProject::Configuration.attachments_grace_period.minutes) - .to_sql - end end diff --git a/app/workers/exports/export_job.rb b/app/workers/exports/export_job.rb new file mode 100644 index 0000000000..22d4ab7a74 --- /dev/null +++ b/app/workers/exports/export_job.rb @@ -0,0 +1,117 @@ +require 'active_storage/filename' + +module Exports + class ExportJob < ::ApplicationJob + def perform(export:, user:, mime_type:, query:, **options) + self.export = export + self.current_user = user + self.mime_type = mime_type + self.query = query + self.options = options + + User.execute_as(user) do + prepare! + export! + schedule_cleanup + rescue StandardError => e + Rails.logger.error "Failed to run export job for #{user}: #{e.message}" + raise e + end + end + + def status_reference + arguments.first[:export] + end + + def updates_own_status? + true + end + + protected + + class_attribute :model + + attr_accessor :export, :current_user, :mime_type, :query, :options + + def prepare! + raise NotImplementedError + end + + def export! + exporter_instance.export! do |result| + handle_export_result(export, result) + end + end + + def exporter_instance + ::Exports::Register + .list_exporter(model, mime_type) + .new(query, options) + end + + def handle_export_result(export, result) + case result.content + when File + store_attachment(export, result.content) + when Tempfile + store_from_tempfile(export, result) + else + store_from_string(export, result) + end + end + + def store_from_tempfile(export, export_result) + renamed_file_path = target_file_name(export_result) + File.rename(export_result.content.path, renamed_file_path) + file = File.open(renamed_file_path) + store_attachment(export, file) + file.close + end + + ## + # Create a target file name, replacing any invalid characters + def target_file_name(export_result) + target_name = ActiveStorage::Filename.new(export_result.title).sanitized + File.join(File.dirname(export_result.content.path), target_name) + end + + def schedule_cleanup + CleanupOutdatedJob.perform_after_grace + end + + def store_from_string(export, export_result) + with_tempfile(export_result.title, export_result.content) do |file| + store_attachment(export, file) + end + end + + def with_tempfile(title, content) + name_parts = [title[0..title.rindex('.') - 1], title[title.rindex('.')..-1]] + + Tempfile.create(name_parts, encoding: content.encoding) do |file| + file.write content + + yield file + end + end + + def store_attachment(container, file) + call = Attachments::CreateService + .bypass_whitelist(user: User.current) + .call(container: container, file: file, filename: File.basename(file), description: '') + + call.on_success do + download_url = ::API::V3::Utilities::PathHelper::ApiV3Path.attachment_content(call.result.id) + + upsert_status status: :success, + message: I18n.t('export.succeeded'), + payload: download_payload(download_url) + end + + call.on_failure do + upsert_status status: :failure, + message: I18n.t('export.failed', message: call.message) + end + end + end +end diff --git a/app/workers/projects/export_job.rb b/app/workers/projects/export_job.rb new file mode 100644 index 0000000000..26541e5140 --- /dev/null +++ b/app/workers/projects/export_job.rb @@ -0,0 +1,13 @@ +require 'active_storage/filename' + +module Projects + class ExportJob < ::Exports::ExportJob + self.model = Project + + private + + def prepare! + self.query = Marshal.load(query) + end + end +end diff --git a/app/workers/work_packages/export_job.rb b/app/workers/work_packages/export_job.rb new file mode 100644 index 0000000000..9480accda0 --- /dev/null +++ b/app/workers/work_packages/export_job.rb @@ -0,0 +1,27 @@ +require 'active_storage/filename' + +module WorkPackages + class ExportJob < ::Exports::ExportJob + self.model = WorkPackage + + def title + I18n.t('export.your_work_packages_export') + end + + private + + def prepare! + self.query = set_query_props(query || Query.new, options[:query_attributes]) + end + + def set_query_props(query, query_attributes) + filters = query_attributes.delete('filters') + filters = Queries::WorkPackages::FilterSerializer.load(filters) + + query.tap do |q| + q.attributes = query_attributes + q.filters = filters + end + end + end +end diff --git a/app/workers/work_packages/exports/export_job.rb b/app/workers/work_packages/exports/export_job.rb deleted file mode 100644 index 57d3d1c8c1..0000000000 --- a/app/workers/work_packages/exports/export_job.rb +++ /dev/null @@ -1,116 +0,0 @@ -require 'active_storage/filename' - -module WorkPackages - module Exports - class ExportJob < ::ApplicationJob - def perform(export:, user:, mime_type:, query:, query_attributes:, options:) - User.execute_as user do - query = set_query_props(query || Query.new, query_attributes) - export_work_packages(export, mime_type, query, options) - - schedule_cleanup - end - end - - def status_reference - arguments.first[:export] - end - - def updates_own_status? - true - end - - protected - - def title - I18n.t('export.your_work_packages_export') - end - - private - - def export_work_packages(export, mime_type, query, options) - ::Exports::Register - .list_exporter(WorkPackage, mime_type) - .new(query, options) - .export! do |result| - handle_export_result(export, result) - end - end - - def handle_export_result(export, result) - case result.content - when File - store_attachment(export, result.content) - when Tempfile - store_from_tempfile(export, result) - else - store_from_string(export, result) - end - end - - def store_from_tempfile(export, export_result) - renamed_file_path = target_file_name(export_result) - File.rename(export_result.content.path, renamed_file_path) - file = File.open(renamed_file_path) - store_attachment(export, file) - file.close - end - - ## - # Create a target file name, replacing any invalid characters - def target_file_name(export_result) - target_name = ActiveStorage::Filename.new(export_result.title).sanitized - File.join(File.dirname(export_result.content.path), target_name) - end - - def schedule_cleanup - ::WorkPackages::Exports::CleanupOutdatedJob.perform_after_grace - end - - def set_query_props(query, query_attributes) - filters = query_attributes.delete('filters') - filters = Queries::WorkPackages::FilterSerializer.load(filters) - - query.tap do |q| - q.attributes = query_attributes - q.filters = filters - end - end - - def store_from_string(export, export_result) - with_tempfile(export_result.title, export_result.content) do |file| - store_attachment(export, file) - end - end - - def with_tempfile(title, content) - name_parts = [title[0..title.rindex('.') - 1], title[title.rindex('.')..-1]] - - Tempfile.create(name_parts, encoding: content.encoding) do |file| - file.write content - - yield file - end - end - - def store_attachment(container, file) - call = Attachments::CreateService - .bypass_whitelist(user: User.current) - .call(container: container, file: file, filename: File.basename(file), description: '') - - call.on_success do - download_url = ::API::V3::Utilities::PathHelper::ApiV3Path.attachment_content(call.result.id) - - upsert_status status: :success, - message: I18n.t('export.succeeded'), - payload: download_payload(download_url) - end - - call.on_failure do - upsert_status status: :failure, - message: I18n.t('export.failed', message: call.message) - end - end - end - end -end diff --git a/config/initializers/export_formats.rb b/config/initializers/export_formats.rb new file mode 100644 index 0000000000..6da0210cdf --- /dev/null +++ b/config/initializers/export_formats.rb @@ -0,0 +1,15 @@ +OpenProject::Application.configure do |application| + application.config.to_prepare do + ::Exports::Register.register do + list WorkPackage, WorkPackage::Exports::CSV + list WorkPackage, ::WorkPackage::PDFExport::WorkPackageListToPdf + + single WorkPackage, ::WorkPackage::PDFExport::WorkPackageToPdf + + formatter WorkPackage, WorkPackage::Exports::Formatters::Costs + formatter WorkPackage, WorkPackage::Exports::Formatters::EstimatedHours + + list Project, Projects::Exports::CSV + end + end +end diff --git a/modules/bim/lib/open_project/bim/engine.rb b/modules/bim/lib/open_project/bim/engine.rb index e30353a6ec..1c1a6f8adc 100644 --- a/modules/bim/lib/open_project/bim/engine.rb +++ b/modules/bim/lib/open_project/bim/engine.rb @@ -209,10 +209,10 @@ module OpenProject::Bim end config.to_prepare do - register = ::Exports::Register - - register.list(WorkPackage, OpenProject::Bim::BcfXml::Exporter) - register.formatter(WorkPackage, OpenProject::Bim::WorkPackage::Exporter::Formatters::BcfThumbnail) + ::Exports::Register.register do + list ::WorkPackage, OpenProject::Bim::BcfXml::Exporter + formatter ::WorkPackage, OpenProject::Bim::WorkPackage::Exporter::Formatters::BcfThumbnail + end ::Queries::Register.filter ::Query, ::Bim::Queries::WorkPackages::Filter::BcfIssueAssociatedFilter ::Queries::Register.column ::Query, ::Bim::Queries::WorkPackages::Columns::BcfThumbnailColumn diff --git a/modules/xls_export/lib/open_project/xls_export/engine.rb b/modules/xls_export/lib/open_project/xls_export/engine.rb index 53990fa762..2ae601978f 100644 --- a/modules/xls_export/lib/open_project/xls_export/engine.rb +++ b/modules/xls_export/lib/open_project/xls_export/engine.rb @@ -35,9 +35,9 @@ module OpenProject::XlsExport class_inflection_override('xls' => 'XLS') config.to_prepare do - register = ::Exports::Register - - register.list(WorkPackage, XlsExport::WorkPackage::Exporter::XLS) + ::Exports::Register.register do + list(::WorkPackage, XlsExport::WorkPackage::Exporter::XLS) + end end end end diff --git a/spec/features/projects/export_spec.rb b/spec/features/projects/export_spec.rb new file mode 100644 index 0000000000..057b011a42 --- /dev/null +++ b/spec/features/projects/export_spec.rb @@ -0,0 +1,85 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require 'spec_helper' +require 'features/work_packages/work_packages_page' + +describe 'project export', type: :feature, js: true do + shared_let(:project1) { FactoryBot.create :project } + shared_let(:project2) { FactoryBot.create :project } + shared_let(:admin) { FactoryBot.create :admin } + + let(:index_page) { ::Pages::Projects::Index.new } + + let(:current_user) { admin } + + before do + @download_list = DownloadList.new + + login_as(current_user) + + index_page.visit! + end + + after do + DownloadList.clear + end + + subject { @download_list.refresh_from(page).latest_downloaded_content } + + def export!(expect_success = true) + index_page.click_more_menu_item 'Export' + click_on export_type + + # Expect to get a response regarding queuing + expect(page).to have_content I18n.t('js.job_status.generic_messages.in_queue'), + wait: 10 + + begin + perform_enqueued_jobs + rescue StandardError + # nothing + end + + if expect_success + expect(page).to have_text("The export has completed successfully") + end + end + + describe 'CSV export' do + let(:export_type) { 'CSV' } + + it 'exports the visible projects' do + expect(page).to have_selector('td.name', text: project1.name) + + export! + + expect(subject).to have_text(project1.name) + end + end +end diff --git a/app/models/work_package/exports.rb b/spec/models/projects/exporter/csv_integration_spec.rb similarity index 53% rename from app/models/work_package/exports.rb rename to spec/models/projects/exporter/csv_integration_spec.rb index e053a2e417..3acee5d538 100644 --- a/app/models/work_package/exports.rb +++ b/spec/models/projects/exporter/csv_integration_spec.rb @@ -28,14 +28,50 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module WorkPackage::Exports - register = ::Exports::Register +require 'spec_helper' - register.list(WorkPackage, WorkPackage::Exports::CSV) - register.list(WorkPackage, ::WorkPackage::PDFExport::WorkPackageListToPdf) +describe Projects::Exports::CSV, 'integration', type: :model do + before do + login_as current_user + end - register.single(WorkPackage, ::WorkPackage::PDFExport::WorkPackageToPdf) + let(:project) { FactoryBot.create(:project) } - register.formatter(WorkPackage, WorkPackage::Exports::Formatters::Costs) - register.formatter(WorkPackage, WorkPackage::Exports::Formatters::EstimatedHours) + let(:current_user) do + FactoryBot.create(:user, + member_in_project: project, + member_with_permissions: %i(view_projects)) + end + let(:query) { Queries::Projects::ProjectQuery.new } + let(:instance) do + described_class.new(query) + end + + it 'performs a successful export' 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'] + end + + context 'with no project visible' do + let(:current_user) { User.anonymous } + + it 'does not include the project' do + data = '' + + instance.export! do |result| + data = result.content + end + expect(data).not_to include project.identifier + + data = CSV.parse(data) + expect(data.size).to eq(1) + end + end end diff --git a/spec/support/pages/projects/index.rb b/spec/support/pages/projects/index.rb index 982ea29b48..a11fcc2995 100644 --- a/spec/support/pages/projects/index.rb +++ b/spec/support/pages/projects/index.rb @@ -128,6 +128,13 @@ module Pages click_button('Show/hide filters') end + def click_more_menu_item(item) + page.find('[data-qa-selector="project-more-dropdown-menu"]').click + page.within('.menu-drop-down-container') do + click_link(item) + end + end + def click_menu_item_of(title, project) activate_menu_of(project) do click_link title