decrease dependency of issue on xml

pull/7893/head
ulferts 5 years ago
parent 0878f8a9d6
commit 13297c9bf0
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 2
      modules/bcf/app/controllers/bcf/issues_controller.rb
  2. 122
      modules/bcf/app/models/bcf/issue.rb
  3. 27
      modules/bcf/app/representers/bcf/api/v2_1/topics/single_representer.rb
  4. 8
      modules/bcf/app/services/bcf/issues/transform_attributes_service.rb
  5. 93
      modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
  6. 85
      modules/bcf/spec/api/v3/work_packages/work_package_representer_spec.rb
  7. 3
      modules/bcf/spec/bcf/bcf_xml/issue_reader_spec.rb
  8. 4
      modules/bcf/spec/bcf/bcf_xml/issue_writer_spec.rb
  9. 27
      modules/bcf/spec/models/bcf/issue_spec.rb
  10. 3
      modules/bcf/spec/representers/bcf/api/v2_1/topics/single_representer_rendering_spec.rb
  11. 3
      modules/bcf/spec/requests/api/bcf/v2_1/topics_api_spec.rb

@ -141,7 +141,7 @@ module ::Bcf
raise(StandardError.new(I18n.t('bcf.exceptions.file_invalid')))
end
@issues = ::Bcf::Issue.with_markup
@issues = ::Bcf::Issue
.includes(work_package: %i[status priority assigned_to])
.where(uuid: @listing.map { |e| e[:uuid] }, project: @project)
render 'bcf/issues/diff_on_work_packages'

@ -11,118 +11,12 @@ module Bcf
validates :work_package, presence: true
# TODO: remove xml extraction as properties are either stored
# in specific columns of the bcf_issues table or in the referenced work package
class << self
def with_markup
select '*',
extract_first_node(title_path, 'title'),
extract_first_node(description_path, 'description'),
extract_first_node(priority_text_path, 'priority_text'),
extract_first_node(status_text_path, 'status_text'),
extract_first_node(type_text_path, 'type_text'),
extract_first_node(assignee_text_path, 'assignee_text'),
extract_first_node(due_date_text_path, 'due_date_text'),
extract_first_node(creation_date_text_path, 'creation_date_text'),
extract_first_node(creation_author_text_path, 'creation_author_text'),
extract_first_node(modified_date_text_path, 'modified_date_text'),
extract_first_node(modified_author_text_path, 'modified_author_text'),
extract_first_node(index_text_path, 'index_text'),
extract_first_node(stage_text_path, 'stage_text'),
extract_nodes(labels_path, 'labels')
end
def of_project(project)
includes(:work_package)
.references(:work_packages)
.merge(WorkPackage.for_projects(project))
end
protected
def title_path
'/Markup/Topic/Title/text()'
end
def description_path
'/Markup/Topic/Description/text()'
end
def priority_text_path
'/Markup/Topic/Priority/text()'
end
def status_text_path
'/Markup/Topic/@TopicStatus'
end
def type_text_path
'/Markup/Topic/@TopicType'
end
def assignee_text_path
'/Markup/Topic/AssignedTo/text()'
end
def due_date_text_path
'/Markup/Topic/DueDate/text()'
end
def stage_text_path
'/Markup/Topic/Stage/text()'
end
def creation_date_text_path
'/Markup/Topic/CreationDate/text()'
end
def creation_author_text_path
'/Markup/Topic/CreationAuthor/text()'
end
def modified_date_text_path
'/Markup/Topic/ModifiedDate/text()'
end
def modified_author_text_path
'/Markup/Topic/ModifiedAuthor/text()'
end
def index_text_path
'/Markup/Topic/Index/text()'
end
def labels_path
'/Markup/Topic/Labels/text()'
end
private
def extract_first_node(path, as)
"(xpath('#{path}', markup))[1] AS #{as}"
end
def extract_nodes(path, as)
"(xpath('#{path}', markup)) AS #{as}"
end
end
%i[title
description
priority_text
status_text
type_text
assignee_text
due_date_text
creation_date_text
creation_author_text
modified_date_text
modified_author_text
stage_text
index_text].each do |name|
define_method name do
from_attributes_or_doc name
end
end
def markup_doc
@ -132,21 +26,5 @@ module Bcf
def invalidate_markup_cache
@markup_doc = nil
end
private
def from_attributes_or_doc(key, multiple: false)
if attributes.keys.include? key.to_s
self[key]
else
path = markup_doc.xpath(self.class.send("#{key}_path"))
if multiple
path.map(&:to_s)
else
path.first.to_s.presence
end
end
end
end
end

@ -65,30 +65,26 @@ module Bcf::API::V2_1
property :labels
property :creation_date_text,
as: :creation_date,
property :creation_date,
getter: ->(decorator:, **) {
decorator
.formatted_date_time(:created_at)
}
property :creation_author_text,
as: :creation_author,
property :creation_author,
getter: ->(*) {
work_package
.author
.mail
}
property :modified_date_text,
as: :modified_date,
property :modified_date,
getter: ->(decorator:, **) {
decorator
.formatted_date_time(:updated_at)
}
property :modified_author_text,
as: :modified_author,
property :modified_author,
getter: ->(*) {
work_package
.journals
@ -112,8 +108,19 @@ module Bcf::API::V2_1
work_package.description
}
property :due_date_text,
as: :due_date
property :due_date,
getter: ->(decorator:, **) {
decorator.datetime_formatter.format_date(work_package.due_date, allow_nil: true)
},
setter: ->(fragment:, decorator:, **) {
date = decorator
.datetime_formatter
.parse_date(fragment,
due_date,
allow_nil: true)
self.due_date = date
}
def datetime_formatter
::API::V3::Utilities::DateTimeFormatter

@ -37,6 +37,12 @@ module Bcf::Issues
private
##
# BCF issues might have empty titles. OP needs one.
def title(attributes)
attributes[:title] || '(Imported BCF issue contained no title)'
end
def author(project, attributes)
find_user_in_project(project, attributes[:author]) || User.system
end
@ -83,7 +89,7 @@ module Bcf::Issues
type: type(attributes),
# Native attributes from the extractor
subject: attributes[:title],
subject: title(attributes),
description: attributes[:description],
due_date: attributes[:due_date],
start_date: attributes[:start_date],

@ -26,7 +26,6 @@ module OpenProject::Bcf::BcfXml
def extract!
@doc = extractor.doc
treat_empty_titles
treat_unknown_types
treat_unknown_statuses
treat_unknown_priorities
@ -51,15 +50,6 @@ module OpenProject::Bcf::BcfXml
private
##
# BCF issues might have empty titles. OP needs one.
def treat_empty_titles
title_node = @doc.xpath('/Markup/Topic/Title').first
return if title_node&.content&.present?
title_node.content = "(Imported BCF issue contained no title)"
end
##
# Handle unknown types during import
def treat_unknown_types
@ -152,9 +142,7 @@ module OpenProject::Bcf::BcfXml
end
def create_work_package
call = WorkPackages::CreateService.new(user: user).call(work_package_attributes
.merge(send_notifications: false)
.symbolize_keys)
call = WorkPackages::CreateService.new(user: user).call(work_package_attributes)
force_overwrite(call.result) if call.success?
@ -165,62 +153,43 @@ module OpenProject::Bcf::BcfXml
find_user_in_project(extractor.author) || User.system
end
def assignee
find_user_in_project(extractor.assignee)
end
def type
type_name = extractor.type
type = ::Type.find_by(name: type_name)
return type if type.present?
return ::Type.default&.first if import_options[:unknown_types_action] == 'default'
if import_options[:unknown_types_action] == 'chose' &&
import_options[:unknown_types_chose_ids].any?
return ::Type.find_by(id: import_options[:unknown_types_chose_ids].first)
else
ServiceResult.new success: false,
errors: issue.errors,
result: issue
end
end
def start_date
extractor.creation_date.to_date unless is_update
end
def update_work_package
if import_is_newer?
WorkPackages::UpdateService
.new(user: user, model: issue.work_package)
.call(work_package_attributes.merge(send_notifications: false).symbolize_keys)
.call(work_package_attributes)
else
import_is_outdated(issue)
end
end
##
# Get mapped and raw attributes from MarkupExtractor
# and return all values that are non-nil
###
## Get mapped and raw attributes from MarkupExtractor
## and return all values that are non-nil
def work_package_attributes
{
# Fixed attributes we know
project: project,
type: type,
attributes = ::Bcf::Issues::TransformAttributesService
.new
.call(extractor_attributes)
.result
.merge(send_notifications: false, import_options: import_options)
.symbolize_keys
# Native attributes from the extractor
subject: extractor.title,
attributes[:start_date] = extractor.creation_date.to_date unless is_update
attributes
end
def extractor_attributes
{
project_id: project.id,
type: extractor.type,
title: extractor.title,
description: extractor.description,
due_date: extractor.due_date,
start_date: start_date,
# Mapped attributes
assigned_to: assignee,
status_id: statuses.fetch(extractor.status, statuses[:default]),
priority_id: priorities.fetch(extractor.priority, priorities[:default])
}.compact
assignee: extractor.assignee,
status: extractor.status,
priority: extractor.priority
}
end
##
@ -402,18 +371,6 @@ module OpenProject::Bcf::BcfXml
bcf_comment.journal.save
end
##
# Keep a hash map of current status ids for faster lookup
def statuses
@statuses ||= Hash[Status.pluck(:name, :id)].merge(default: Status.default.id)
end
##
# Keep a hash map of current status ids for faster lookup
def priorities
@priorities ||= Hash[IssuePriority.pluck(:name, :id)].merge(default: IssuePriority.default.try(:id))
end
def import_is_outdated(issue)
issue.errors.add :base,
:conflict,

@ -31,91 +31,12 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
member_in_project: project,
member_through_role: role)
end
let(:markup) do
<<-MARKUP
<Markup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Header>
<File IfcProject="0M6o7Znnv7hxsbWgeu7oQq" IfcSpatialStructureElement="23B$bNeGHFQuMYJzvUX0FD" isExternal="false">
<Filename>IfcPile_01.ifc</Filename>
<Date>2014-10-27T16:27:27Z</Date>
<Reference>../IfcPile_01.ifc</Reference>
</File>
</Header>
<Topic Guid="63E78882-7C6A-4BF7-8982-FC478AFB9C97" TopicType="Structural" TopicStatus="Open">
<ReferenceLink>https://bim--it.net</ReferenceLink>
<Title>Maximum Content</Title>
<Priority>High</Priority>
<Index>0</Index>
<Labels>Structural</Labels>
<Labels>IT Development</Labels>
<CreationDate>2015-06-21T12:00:00Z</CreationDate>
<CreationAuthor>mike@example.com</CreationAuthor>
<ModifiedDate>2015-06-21T14:22:47Z</ModifiedDate>
<ModifiedAuthor>mike@example.com</ModifiedAuthor>
<AssignedTo>andy@example.com</AssignedTo>
<Description>This is a topic with all information present.</Description>
<BimSnippet SnippetType="JSON">
<Reference>JsonElement.json</Reference>
<ReferenceSchema>http://json-schema.org</ReferenceSchema>
</BimSnippet>
<DocumentReference isExternal="true">
<ReferencedDocument>https://github.com/BuildingSMART/BCF-XML</ReferencedDocument>
<Description>GitHub BCF Specification</Description>
</DocumentReference>
<DocumentReference>
<ReferencedDocument>../markup.xsd</ReferencedDocument>
<Description>Markup.xsd Schema</Description>
</DocumentReference>
<RelatedTopic Guid="5019D939-62A4-45D9-B205-FAB602C98FE8" />
</Topic>
<Comment Guid="780FAE52-C432-42BE-ADEA-FF3E7A8CD8E1">
<Date>2015-08-31T12:40:17Z</Date>
<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>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>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>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>mike@example.com</ModifiedAuthor>
</Comment>
<Viewpoints Guid="8dc86298-9737-40b4-a448-98a9e953293a">
<Viewpoint>Viewpoint_8dc86298-9737-40b4-a448-98a9e953293a.bcfv</Viewpoint>
<Snapshot>Snapshot_8dc86298-9737-40b4-a448-98a9e953293a.png</Snapshot>
</Viewpoints>
<Viewpoints Guid="21dd4807-e9af-439e-a980-04d913a6b1ce">
<Viewpoint>Viewpoint_21dd4807-e9af-439e-a980-04d913a6b1ce.bcfv</Viewpoint>
<Snapshot>Snapshot_21dd4807-e9af-439e-a980-04d913a6b1ce.png</Snapshot>
</Viewpoints>
<Viewpoints Guid="81daa431-bf01-4a49-80a2-1ab07c177717">
<Viewpoint>Viewpoint_81daa431-bf01-4a49-80a2-1ab07c177717.bcfv</Viewpoint>
<Snapshot>Snapshot_81daa431-bf01-4a49-80a2-1ab07c177717.png</Snapshot>
</Viewpoints>
</Markup>
MARKUP
end
let(:bcf_issue) do
FactoryBot.create(:bcf_issue_with_comment, markup: markup)
let!(:bcf_issue) do
FactoryBot.create(:bcf_issue_with_comment, work_package: work_package)
end
let(:work_package) do
FactoryBot.create(:work_package,
project_id: project.id,
bcf_issue: bcf_issue)
project_id: project.id)
end
let(:representer) do
described_class.new(work_package,

@ -160,7 +160,8 @@ describe ::OpenProject::Bcf::BcfXml::IssueReader do
context 'on updating import' do
context '#update_comment' do
let!(:bcf_issue) { FactoryBot.create :bcf_issue_with_comment }
let(:work_package) { FactoryBot.create(:work_package) }
let!(:bcf_issue) { FactoryBot.create :bcf_issue_with_comment, work_package: work_package }
before do
allow(subject).to receive(:issue).and_return(bcf_issue)

@ -84,6 +84,7 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
end
let(:bcf_issue) do
FactoryBot.create(:bcf_issue_with_comment,
work_package: work_package,
markup: markup)
end
let(:priority) { FactoryBot.create :priority_low }
@ -93,7 +94,6 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
let(:work_package) do
FactoryBot.create(:work_package,
project_id: project.id,
bcf_issue: bcf_issue,
priority: priority,
author: current_user,
assigned_to: current_user,
@ -104,7 +104,7 @@ describe ::OpenProject::Bcf::BcfXml::IssueWriter do
before do
allow(User).to receive(:current).and_return current_user
work_package.bcf_issue.comments.first.journal.update_attribute('journable_id', work_package.id)
bcf_issue.comments.first.journal.update_attribute('journable_id', work_package.id)
FactoryBot.create(:work_package_journal, notes: "Some note created in OP.", journable_id: work_package.id)
work_package.reload
end

@ -33,31 +33,6 @@ describe ::Bcf::Issue, type: :model do
let(:work_package) { FactoryBot.create :work_package, type: type }
let(:issue) { FactoryBot.create :bcf_issue, work_package: work_package }
shared_examples_for 'provides attributes' do
it "provides attributes" do
expect(subject.title).to be_eql 'Maximum Content'
expect(subject.description).to be_eql 'This is a topic with all information present.'
expect(subject.priority_text).to be_eql 'High'
expect(subject.status_text).to be_eql 'Open'
expect(subject.type_text).to be_eql 'Structural'
expect(subject.assignee_text).to be_eql 'andy@example.com'
expect(subject.index_text).to be_eql '0'
expect(subject.labels).to contain_exactly 'Structural', 'IT Development'
expect(subject.due_date_text).to be_nil
expect(subject.creation_date_text).to eql "2015-06-21T12:00:00Z"
expect(subject.creation_author_text).to eql "mike@example.com"
expect(subject.modified_date_text).to eql "2015-06-21T14:22:47Z"
expect(subject.modified_author_text).to eql "michelle@example.com"
expect(subject.stage_text).to eql "Construction start"
end
end
context '#self.with_markup' do
subject { ::Bcf::Issue.with_markup.find_by id: issue.id }
it_behaves_like 'provides attributes'
end
context '#markup_doc' do
subject { issue }
@ -76,8 +51,6 @@ describe ::Bcf::Issue, type: :model do
subject.save
expect(subject.markup_doc).to_not be_eql(first_fetched_doc)
end
it_behaves_like 'provides attributes'
end
describe '.of_project' do

@ -44,6 +44,7 @@ describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
let(:work_package) do
FactoryBot.build_stubbed(:stubbed_work_package,
assigned_to: assignee,
due_date: Date.today,
status: status,
type: type).tap do |wp|
allow(wp)
@ -144,7 +145,7 @@ describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
context 'due_date' do
it_behaves_like 'attribute' do
let(:value) { issue.due_date_text }
let(:value) { work_package.due_date.iso8601 }
let(:path) { 'due_date' }
end
end

@ -202,6 +202,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
labels: labels,
stage: stage,
index: index,
due_date: Date.today.iso8601,
assigned_to: view_only_user.mail,
description: description
}
@ -229,7 +230,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
api_v3_paths.work_package(work_package.id)
],
assigned_to: view_only_user.mail,
due_date: nil,
due_date: Date.today.iso8601,
stage: stage,
creation_author: edit_member_user.mail,
creation_date: work_package.created_at.iso8601,

Loading…
Cancel
Save