Adding some BCF import related specs

And some WIP logic for dealing with unkown people
pull/7251/head
Wieland Lindenthal 6 years ago
parent 3cde991e12
commit 9ecd01292c
  1. 3
      app/views/members/_member_form.html.erb
  2. 20
      modules/bcf/app/controllers/bcf/issues_controller.rb
  3. 2
      modules/bcf/config/locales/en.yml
  4. 10
      modules/bcf/lib/open_project/bcf/bcf_xml/importer.rb
  5. 2
      modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
  6. 2
      modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
  7. 16
      modules/bcf/lib/open_project/bcf/bcf_xml/markup_extractor.rb
  8. 26
      modules/bcf/spec/api/v3/work_packages/work_package_representer_spec.rb
  9. 16
      modules/bcf/spec/bcf/bcf_xml/issue_writer_spec.rb
  10. 102
      modules/bcf/spec/bcf/bcf_xml/markup_extractor_spec.rb
  11. 88
      modules/bcf/spec/controllers/issues_controller_spec.rb
  12. BIN
      modules/bcf/spec/fixtures/files/MaximumInformation.bcf

@ -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| %>
<a title="<%= t('js.close_form_title') %>" class="hide-add-member-form-link form--close icon-context icon-close"></a>
<% csp_onclick('hideAddMemberForm()', '.hide-add-member-form-link') %>
<div id="new-member-message"></div>

@ -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

@ -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'

@ -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

@ -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

@ -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)

@ -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)

@ -49,10 +49,10 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
<Labels>Structural</Labels>
<Labels>IT Development</Labels>
<CreationDate>2015-06-21T12:00:00Z</CreationDate>
<CreationAuthor>dangl@iabi.eu</CreationAuthor>
<CreationAuthor>mike@example.com</CreationAuthor>
<ModifiedDate>2015-06-21T14:22:47Z</ModifiedDate>
<ModifiedAuthor>dangl@iabi.eu</ModifiedAuthor>
<AssignedTo>linhard@iabi.eu</AssignedTo>
<ModifiedAuthor>mike@example.com</ModifiedAuthor>
<AssignedTo>andy@example.com</AssignedTo>
<Description>This is a topic with all informations present.</Description>
<BimSnippet SnippetType="JSON">
<Reference>JsonElement.json</Reference>
@ -70,29 +70,29 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
</Topic>
<Comment Guid="780FAE52-C432-42BE-ADEA-FF3E7A8CD8E1">
<Date>2015-08-31T12:40:17Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>This is an unmodified topic at the uppermost hierarchical level.
All times in the XML are marked as UTC times.</Comment>
</Comment>
<Comment Guid="897E4909-BDF3-4CC7-A283-6506CAFF93DD">
<Date>2015-08-31T14:00:01Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>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.</Comment>
</Comment>
<Comment Guid="39C4B780-1B48-44E5-9802-D359007AA44E">
<Date>2015-08-31T13:07:11Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>This comment again is in the highest hierarchy level.
It references a viewpoint.</Comment>
<Viewpoint Guid="8dc86298-9737-40b4-a448-98a9e953293a" />
</Comment>
<Comment Guid="BD17158C-4267-4433-98C1-904F9B41CA50">
<Date>2015-08-31T15:42:58Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>This comment contained some spllng errs.
Hopefully, the modifier did catch them all.</Comment>
<ModifiedDate>2015-08-31T16:07:11Z</ModifiedDate>
<ModifiedAuthor>dangl@iabi.eu</ModifiedAuthor>
<ModifiedAuthor>mike@example.com</ModifiedAuthor>
</Comment>
<Viewpoints Guid="8dc86298-9737-40b4-a448-98a9e953293a">
<Viewpoint>Viewpoint_8dc86298-9737-40b4-a448-98a9e953293a.bcfv</Viewpoint>
@ -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

@ -39,10 +39,10 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
<Labels>Structural</Labels>
<Labels>IT Development</Labels>
<CreationDate>2015-06-21T12:00:00Z</CreationDate>
<CreationAuthor>dangl@iabi.eu</CreationAuthor>
<CreationAuthor>mike@example.com</CreationAuthor>
<ModifiedDate>2015-06-21T14:22:47Z</ModifiedDate>
<ModifiedAuthor>dangl@iabi.eu</ModifiedAuthor>
<AssignedTo>linhard@iabi.eu</AssignedTo>
<ModifiedAuthor>mike@example.com</ModifiedAuthor>
<AssignedTo>andy@example.com</AssignedTo>
<Description>This is a topic with all informations present.</Description>
<BimSnippet SnippetType="JSON">
<Reference>JsonElement.json</Reference>
@ -60,29 +60,29 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
</Topic>
<Comment Guid="780FAE52-C432-42BE-ADEA-FF3E7A8CD8E1">
<Date>2015-08-31T12:40:17Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>This is an unmodified topic at the uppermost hierarchical level.
All times in the XML are marked as UTC times.</Comment>
</Comment>
<Comment Guid="897E4909-BDF3-4CC7-A283-6506CAFF93DD">
<Date>2015-08-31T14:00:01Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>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.</Comment>
</Comment>
<Comment Guid="39C4B780-1B48-44E5-9802-D359007AA44E">
<Date>2015-08-31T13:07:11Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>This comment again is in the highest hierarchy level.
It references a viewpoint.</Comment>
<Viewpoint Guid="8dc86298-9737-40b4-a448-98a9e953293a" />
</Comment>
<Comment Guid="BD17158C-4267-4433-98C1-904F9B41CA50">
<Date>2015-08-31T15:42:58Z</Date>
<Author>dangl@iabi.eu</Author>
<Author>mike@example.com</Author>
<Comment>This comment contained some spllng errs.
Hopefully, the modifier did catch them all.</Comment>
<ModifiedDate>2015-08-31T16:07:11Z</ModifiedDate>
<ModifiedAuthor>dangl@iabi.eu</ModifiedAuthor>
<ModifiedAuthor>mike@example.com</ModifiedAuthor>
</Comment>
<Viewpoints Guid="8dc86298-9737-40b4-a448-98a9e953293a">
<Viewpoint>Viewpoint_8dc86298-9737-40b4-a448-98a9e953293a.bcfv</Viewpoint>

@ -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

@ -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
Loading…
Cancel
Save