diff --git a/app/views/members/_member_form.html.erb b/app/views/members/_member_form.html.erb
index 0578df202b..7b837399ed 100644
--- a/app/views/members/_member_form.html.erb
+++ b/app/views/members/_member_form.html.erb
@@ -29,10 +29,9 @@ See docs/COPYRIGHT.rdoc for more details.
<%= javascript_include_tag "members_select_boxes.js" %>
-<%= labelled_tabular_form_for(:member, url: {controller: 'members', action: 'create', project_id: project},
+<%= labelled_tabular_form_for(:member, url: main_app.project_members_path,
method: :post,
- html: {id: "members_add_form", class: "form -vertical -bordered -medium-compressed"}) do |f| %>
-
+ html: { id: "members_add_form", class: "form -vertical -bordered -medium-compressed" }) do |f| %>
<% csp_onclick('hideAddMemberForm()', '.hide-add-member-form-link') %>
diff --git a/modules/bcf/app/controllers/bcf/issues_controller.rb b/modules/bcf/app/controllers/bcf/issues_controller.rb
index 49a0e4c31e..0e10681b37 100644
--- a/modules/bcf/app/controllers/bcf/issues_controller.rb
+++ b/modules/bcf/app/controllers/bcf/issues_controller.rb
@@ -29,10 +29,24 @@ module ::Bcf
@bcf_file = params[:bcf_file]
begin
+ @roles = Role.find_all_givable
+
@listing = @importer.get_extractor_list! @bcf_file.path
- @issues = ::Bcf::Issue.with_markup
- .includes(work_package: %i[status priority assigned_to])
- .where(uuid: @listing.map { |e| e[:uuid] })
+ if @listing.blank?
+ raise(StandardError.new I18n.t('bcf.exceptions.file_invalid'))
+ end
+
+ @issues = ::Bcf::Issue.with_markup.includes(work_package: %i[status priority assigned_to]).where(uuid: @listing.map { |e| e[:uuid] })
+
+
+ all_people = @listing.map { |entry| entry[:people] }.flatten.uniq
+ all_mails = @listing.map { |entry| entry[:mail_addresses] }.flatten.uniq
+
+ @known_users = User.where(mail: all_mails).includes(:memberships)
+ @unknown_mails = all_mails.map(&:downcase) - @known_users.map(&:mail).map(&:downcase)
+ @members = @known_users.select { |user| user.projects.map(&:id).include? @project.id }
+ @non_members = @known_users - @members
+ @invalid_people = all_people - all_mails
rescue StandardError => e
flash[:error] = I18n.t('bcf.bcf_xml.import_failed', error: e.message)
redirect_to action: :index
diff --git a/modules/bcf/config/locales/en.yml b/modules/bcf/config/locales/en.yml
index c9ddfab020..1cd59167b2 100644
--- a/modules/bcf/config/locales/en.yml
+++ b/modules/bcf/config/locales/en.yml
@@ -4,6 +4,8 @@ en:
label_bcf: 'BCF'
issues: "Issues"
experimental_badge: "Experimental"
+ exceptions:
+ file_invalid: "BCF file invalid"
x_bcf_issues:
zero: 'No BCF issues'
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/importer.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/importer.rb
index e55a82d880..4e33938476 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_xml/importer.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/importer.rb
@@ -15,7 +15,7 @@ module OpenProject::Bcf::BcfXml
# but do not perform the import
def get_extractor_list!(file)
Zip::File.open(file) do |zip|
- yield_topic_entries(zip)
+ yield_markup_bcf_files(zip)
.map do |entry|
to_listing(MarkupExtractor.new(entry))
end
@@ -44,11 +44,13 @@ module OpenProject::Bcf::BcfXml
Hash[keys.map { |k| [k, extractor.public_send(k)] }].tap do |attributes|
attributes[:viewpoint_count] = extractor.viewpoints.count
attributes[:comments_count] = extractor.comments.count
+ attributes[:people] = extractor.people
+ attributes[:mail_addresses] = extractor.mail_addresses
end
end
def synchronize_topics(zip)
- yield_topic_entries(zip)
+ yield_markup_bcf_files(zip)
.map do |entry|
issue = IssueReader.new(project, zip, entry, current_user: current_user).extract!
issue.save
@@ -57,9 +59,9 @@ module OpenProject::Bcf::BcfXml
end
##
- # Yields topic entries and their uuid from the ZIP files
+ # Yields topic bcf files (that contain topic entries and their uuid) from the ZIP files
# while skipping all other entries
- def yield_topic_entries(zip)
+ def yield_markup_bcf_files(zip)
zip.select { |entry| entry.name.end_with?('markup.bcf') }
end
end
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
index ffc4e2bd11..176b5b71f4 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
@@ -94,7 +94,7 @@ module OpenProject::Bcf::BcfXml
# Extend comments with new or updated values from XML
def build_comments
extractor.comments.each do |data|
- next if issue.comments.has_uuid?(data[:uuid])
+ next if issue.comments.has_uuid?(data[:uuid]) # Comment has already been imported once.
comment = issue.comments.build data.slice(:uuid)
# Cannot link to a journal when no work package
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
index eab2c26bcf..6ce2ec3dee 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
@@ -220,7 +220,7 @@ module OpenProject::Bcf::BcfXml
end
##
- # Create a single topic node
+ # Create a single comment node
def comment_node(xml, uuid, journal)
xml.Comment "Guid" => uuid do
xml.Date to_bcf_datetime(journal.created_at)
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/markup_extractor.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/markup_extractor.rb
index 8270c96fb2..3aa83897f0 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_xml/markup_extractor.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/markup_extractor.rb
@@ -57,7 +57,7 @@ module OpenProject::Bcf::BcfXml
uuid: node['Guid'],
viewpoint: node.xpath('Viewpoint/text()').to_s,
snapshot: node.xpath('Snapshot/text()').to_s
- }
+ }.with_indifferent_access
end
end
@@ -68,10 +68,22 @@ module OpenProject::Bcf::BcfXml
date: node.xpath('Date/text()').to_s,
author: node.xpath('Author/text()').to_s,
comment: node.xpath('Comment/text()').to_s
- }
+ }.with_indifferent_access
end
end
+ def mail_addresses
+ people.filter do |person|
+ # person value is an email address
+ person =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
+ end
+ .uniq
+ end
+
+ def people
+ ([assignee, author] + comments.map { |comment| comment[:author] }).filter(&:present?).uniq
+ end
+
private
def extract_non_empty(path, prefix: '/Markup/Topic/'.freeze, attribute: false)
diff --git a/modules/bcf/spec/api/v3/work_packages/work_package_representer_spec.rb b/modules/bcf/spec/api/v3/work_packages/work_package_representer_spec.rb
index 176133d6f8..424250fa57 100644
--- a/modules/bcf/spec/api/v3/work_packages/work_package_representer_spec.rb
+++ b/modules/bcf/spec/api/v3/work_packages/work_package_representer_spec.rb
@@ -49,10 +49,10 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
Structural
IT Development
2015-06-21T12:00:00Z
- dangl@iabi.eu
+ mike@example.com
2015-06-21T14:22:47Z
- dangl@iabi.eu
- linhard@iabi.eu
+ mike@example.com
+ andy@example.com
This is a topic with all informations present.
JsonElement.json
@@ -70,29 +70,29 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
2015-08-31T12:40:17Z
- dangl@iabi.eu
+ mike@example.com
This is an unmodified topic at the uppermost hierarchical level.
All times in the XML are marked as UTC times.
2015-08-31T14:00:01Z
- dangl@iabi.eu
+ mike@example.com
This comment was a reply to the first comment in BCF v2.0. This is a no longer supported functionality and therefore is to be treated as a regular comment in v2.1.
2015-08-31T13:07:11Z
- dangl@iabi.eu
+ mike@example.com
This comment again is in the highest hierarchy level.
It references a viewpoint.
2015-08-31T15:42:58Z
- dangl@iabi.eu
+ mike@example.com
This comment contained some spllng errs.
Hopefully, the modifier did catch them all.
2015-08-31T16:07:11Z
- dangl@iabi.eu
+ mike@example.com
Viewpoint_8dc86298-9737-40b4-a448-98a9e953293a.bcfv
@@ -140,5 +140,15 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
.including('id')
.at_path('bcf/viewpoints/')
end
+
+ it "contains topic labels" do
+ is_expected.to be_json_eql([
+ {
+ labels: ["Structural", "IT Development"]
+ }
+ ].to_json)
+ .including('id')
+ .at_path('bcf/topic/')
+ end
end
end
diff --git a/modules/bcf/spec/bcf/bcf_xml/issue_writer_spec.rb b/modules/bcf/spec/bcf/bcf_xml/issue_writer_spec.rb
index e650089609..78f93a0b5f 100644
--- a/modules/bcf/spec/bcf/bcf_xml/issue_writer_spec.rb
+++ b/modules/bcf/spec/bcf/bcf_xml/issue_writer_spec.rb
@@ -39,10 +39,10 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
Structural
IT Development
2015-06-21T12:00:00Z
- dangl@iabi.eu
+ mike@example.com
2015-06-21T14:22:47Z
- dangl@iabi.eu
- linhard@iabi.eu
+ mike@example.com
+ andy@example.com
This is a topic with all informations present.
JsonElement.json
@@ -60,29 +60,29 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
2015-08-31T12:40:17Z
- dangl@iabi.eu
+ mike@example.com
This is an unmodified topic at the uppermost hierarchical level.
All times in the XML are marked as UTC times.
2015-08-31T14:00:01Z
- dangl@iabi.eu
+ mike@example.com
This comment was a reply to the first comment in BCF v2.0. This is a no longer supported functionality and therefore is to be treated as a regular comment in v2.1.
2015-08-31T13:07:11Z
- dangl@iabi.eu
+ mike@example.com
This comment again is in the highest hierarchy level.
It references a viewpoint.
2015-08-31T15:42:58Z
- dangl@iabi.eu
+ mike@example.com
This comment contained some spllng errs.
Hopefully, the modifier did catch them all.
2015-08-31T16:07:11Z
- dangl@iabi.eu
+ mike@example.com
Viewpoint_8dc86298-9737-40b4-a448-98a9e953293a.bcfv
diff --git a/modules/bcf/spec/bcf/bcf_xml/markup_extractor_spec.rb b/modules/bcf/spec/bcf/bcf_xml/markup_extractor_spec.rb
new file mode 100644
index 0000000000..0c475abe46
--- /dev/null
+++ b/modules/bcf/spec/bcf/bcf_xml/markup_extractor_spec.rb
@@ -0,0 +1,102 @@
+#-- 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 ::OpenProject::Bcf::BcfXml::MarkupExtractor do
+ let(:filename) { 'MaximumInformation.bcf' }
+ let(:file) do
+ Rack::Test::UploadedFile.new(
+ File.join(Rails.root, "modules/bcf/spec/fixtures/files/#{filename}"),
+ 'application/octet-stream')
+ end
+ let(:entries) do
+ Zip::File.open(file) do |zip|
+ zip.select { |entry| entry.name.end_with?('markup.bcf') }
+ end
+ end
+
+ subject { described_class.new entries.second }
+
+ it '#initialize' do
+ expect(subject).to be_a described_class
+ expect(subject.markup).to be_a String
+ expect(subject.doc).to be_a Nokogiri::XML::Document
+ end
+
+ it '#uuid' do
+ expect(subject.uuid).to be_eql '63E78882-7C6A-4BF7-8982-FC478AFB9C97'
+ end
+
+ it '#title' do
+ expect(subject.title).to be_eql 'Maximum Content'
+ end
+
+ it '#priority' do
+ expect(subject.priority).to be_eql 'High'
+ end
+
+ it '#status' do
+ expect(subject.status).to be_eql 'Open'
+ end
+
+ it '#description' do
+ expect(subject.description).to be_eql 'This is a topic with all informations present.'
+ end
+
+ it '#author' do
+ expect(subject.author).to be_eql 'mike@example.com'
+ end
+
+ it '#assignee' do
+ expect(subject.assignee).to be_eql 'andy@example.com'
+ end
+
+ it '#due_date' do
+ expect(subject.due_date).to be_nil
+ end
+
+ it '#viewpoints' do
+ expect(subject.viewpoints.size).to eql 3
+ expect(subject.viewpoints.first[:uuid]).to eql '8dc86298-9737-40b4-a448-98a9e953293a'
+ expect(subject.viewpoints.first[:viewpoint]).to eql 'Viewpoint_8dc86298-9737-40b4-a448-98a9e953293a.bcfv'
+ expect(subject.viewpoints.first[:snapshot]).to eql 'Snapshot_8dc86298-9737-40b4-a448-98a9e953293a.png'
+ end
+
+ it '#comments' do
+ expect(subject.comments.size).to eql 4
+ expect(subject.comments.first[:uuid]).to eql '780FAE52-C432-42BE-ADEA-FF3E7A8CD8E1'
+ expect(subject.comments.first[:date]).to eql '2015-08-31T12:40:17Z'
+ expect(subject.comments.first[:author]).to eql 'mike@example.com'
+ expect(subject.comments.first[:comment]).to eql 'This is an unmodified topic at the uppermost hierarchical level.
+All times in the XML are marked as UTC times.'
+ end
+
+ it '#people' do
+ expect(subject.people.size).to eql 2
+ expect(subject.people.first).to eql 'andy@example.com'
+ expect(subject.people.second).to eql 'mike@example.com'
+ end
+
+ it '#mail_addresses' do
+ expect(subject.mail_addresses.size).to eql 2
+ expect(subject.mail_addresses.first).to eql 'andy@example.com'
+ expect(subject.mail_addresses.second).to eql 'mike@example.com'
+ end
+end
diff --git a/modules/bcf/spec/controllers/issues_controller_spec.rb b/modules/bcf/spec/controllers/issues_controller_spec.rb
index e69de29bb2..714849b718 100644
--- a/modules/bcf/spec/controllers/issues_controller_spec.rb
+++ b/modules/bcf/spec/controllers/issues_controller_spec.rb
@@ -0,0 +1,88 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
+#++
+
+require 'spec_helper'
+
+describe ::Bcf::IssuesController, type: :controller do
+ let(:manage_bcf_role) { FactoryBot.create(:role, permissions: %i[manage_bcf view_linked_issues view_work_packages]) }
+ let(:collaborator_role) {FactoryBot.create(:role, permissions: %i[view_linked_issues view_work_packages])}
+ let(:bcf_manager) { FactoryBot.create(:user) }
+ let(:collaborator) { FactoryBot.create(:user) }
+
+ let(:non_member) { FactoryBot.create(:user) }
+ let(:project) { FactoryBot.create(:project,
+ identifier: 'bim_project'
+ ) }
+ let(:member) {
+ FactoryBot.create(:member,
+ project: project,
+ user: collaborator,
+ roles: [collaborator_role])
+ }
+ let(:bcf_manager_member) {
+ FactoryBot.create(:member,
+ project: project,
+ user: bcf_manager,
+ roles: [manage_bcf_role])
+ }
+ before do
+ bcf_manager_member
+ member
+ allow(User).to receive(:current).and_return(bcf_manager)
+ end
+
+ describe '#prepare_import' do
+ let(:params) { { project_id: project.identifier.to_s,
+ bcf_file: file} }
+
+ let(:action) do
+ post :prepare_import, params: params
+ end
+
+ context 'with valid BCF file' do
+ let(:filename) { 'MaximumInformation.bcf' }
+ let(:file) { Rack::Test::UploadedFile.new(
+ File.join(Rails.root, "modules/bcf/spec/fixtures/files/#{filename}"),
+ 'application/octet-stream') }
+
+ it 'should be successful' do
+ expect { action }.to change { Attachment.count }.by(1)
+ expect(response).to be_successful
+ end
+ end
+
+ context 'with invalid BCF file' do
+ let(:file) { FileHelpers.mock_uploaded_file }
+
+ it 'should redirect back to where we started from' do
+ expect { action }.to change { Attachment.count }.by(1)
+ expect(response).to redirect_to '/projects/bim_project/issues'
+ end
+ end
+ end
+end
diff --git a/modules/bcf/spec/fixtures/files/MaximumInformation.bcf b/modules/bcf/spec/fixtures/files/MaximumInformation.bcf
index d79df69448..d1fdbc9106 100644
Binary files a/modules/bcf/spec/fixtures/files/MaximumInformation.bcf and b/modules/bcf/spec/fixtures/files/MaximumInformation.bcf differ