mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Introduce let_work_packages helpers
It allows to declare work packages and their properties from a visual table. It is especially useful to visualize hierarchies and data as if it were presented in the work package table page in OpenProject app.
This commit is contained in:
@@ -575,67 +575,74 @@ RSpec.describe WorkPackages::UpdateAncestorsService, type: :model do
|
||||
end
|
||||
|
||||
context 'for the new ancestors chain' do
|
||||
shared_context 'when called with children' do |children_remaining_hours:|
|
||||
let(:children) do
|
||||
(children_remaining_hours.size - 1).downto(0).map do |i|
|
||||
create(:work_package,
|
||||
subject: "child #{i}",
|
||||
parent:,
|
||||
status: open_status,
|
||||
remaining_hours: children_remaining_hours[i],
|
||||
derived_remaining_hours: children_remaining_hours[i])
|
||||
end
|
||||
end
|
||||
context 'with parent having no remaining work' do
|
||||
let_work_packages(<<~TABLE)
|
||||
hierarchy | remaining work |
|
||||
parent | |
|
||||
child1 | 0h |
|
||||
child2 | |
|
||||
child3 | 2.5h |
|
||||
TABLE
|
||||
|
||||
subject(:call_result) do
|
||||
described_class.new(user:, work_package: children.first)
|
||||
described_class.new(user:, work_package: child1)
|
||||
.call(%i(remaining_hours))
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'derived remaining hours' do |children_remaining_hours:, expected_derived_remaining_hours:|
|
||||
context "for #{children_remaining_hours.count} children with remaining hours being #{children_remaining_hours.inspect}" do
|
||||
include_context('when called with children', children_remaining_hours:)
|
||||
|
||||
it "sets parent derived remaining hours to #{expected_derived_remaining_hours}", :aggregate_failures do
|
||||
expect(call_result).to be_success
|
||||
expect(call_result.dependent_results.map(&:result))
|
||||
.to contain_exactly(parent)
|
||||
expect(call_result.dependent_results.first.result.derived_remaining_hours)
|
||||
.to eq(expected_derived_remaining_hours)
|
||||
end
|
||||
it 'sets parent derived remaining work to the sum of children remaining work' do
|
||||
expect(call_result).to be_success
|
||||
updated_work_packages = call_result.dependent_results.map(&:result)
|
||||
expect_work_packages(updated_work_packages, <<~TABLE)
|
||||
subject | derived remaining work
|
||||
parent | 2.5h
|
||||
TABLE
|
||||
end
|
||||
end
|
||||
|
||||
context 'with parent having no remaining hours' do
|
||||
include_examples 'derived remaining hours',
|
||||
children_remaining_hours: [0.0, 2.0, nil],
|
||||
expected_derived_remaining_hours: 2.0
|
||||
context 'with parent having some remaining work' do
|
||||
let_work_packages(<<~TABLE)
|
||||
hierarchy | remaining work |
|
||||
parent | 5.25h |
|
||||
child1 | 0h |
|
||||
child2 | |
|
||||
child3 | 2.5h |
|
||||
TABLE
|
||||
|
||||
include_examples 'derived remaining hours',
|
||||
children_remaining_hours: [1, 2, 5, 42],
|
||||
expected_derived_remaining_hours: 50
|
||||
end
|
||||
|
||||
context 'with parent having 2.0 remaining hours' do
|
||||
before do
|
||||
parent.update(remaining_hours: 2.0)
|
||||
subject(:call_result) do
|
||||
described_class.new(user:, work_package: child1)
|
||||
.call(%i(remaining_hours))
|
||||
end
|
||||
|
||||
include_examples 'derived remaining hours',
|
||||
children_remaining_hours: [0.0, 2.0, nil],
|
||||
expected_derived_remaining_hours: 4.0
|
||||
|
||||
include_examples 'derived remaining hours',
|
||||
children_remaining_hours: [1, 2, 5, 42],
|
||||
expected_derived_remaining_hours: 52
|
||||
it 'sets parent derived remaining work to the sum of itself and children remaining work' do
|
||||
expect(call_result).to be_success
|
||||
updated_work_packages = call_result.dependent_results.map(&:result)
|
||||
expect_work_packages(updated_work_packages, <<~TABLE)
|
||||
subject | derived remaining work
|
||||
parent | 7.75h
|
||||
TABLE
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no remaining hours' do
|
||||
include_context('when called with children', children_remaining_hours: [nil, nil, nil])
|
||||
context 'with parent and children having no remaining work' do
|
||||
let_work_packages(<<~TABLE)
|
||||
hierarchy | remaining work |
|
||||
parent | |
|
||||
child1 | |
|
||||
child2 | |
|
||||
TABLE
|
||||
|
||||
it 'does not update the parent derived remaining hours' do
|
||||
subject(:call_result) do
|
||||
described_class.new(user:, work_package: child1)
|
||||
.call(%i(remaining_hours))
|
||||
end
|
||||
|
||||
it 'does not update the parent derived remaining work' do
|
||||
expect(call_result).to be_success
|
||||
expect(call_result.dependent_results).to be_empty
|
||||
expect_work_packages([parent.reload], <<~TABLE)
|
||||
subject | derived remaining work
|
||||
parent |
|
||||
TABLE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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.
|
||||
#++
|
||||
|
||||
Dir[Rails.root.join('spec/support/table_helpers/*.rb')].each { |f| require f }
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.extend TableHelpers::LetWorkPackages
|
||||
config.include TableHelpers::ExampleMethods
|
||||
|
||||
RSpec::Matchers.define :match_table do |expected|
|
||||
match do |actual_work_packages|
|
||||
expected_data = TableHelpers::TableData.for(expected)
|
||||
actual_data = TableHelpers::TableData.from_work_packages(actual_work_packages, expected_data.columns)
|
||||
|
||||
representer = TableHelpers::TableRepresenter.new(tables_data: [expected_data, actual_data],
|
||||
columns: expected_data.columns)
|
||||
@expected = representer.render(expected_data)
|
||||
@actual = representer.render(actual_data)
|
||||
|
||||
values_match? @expected, @actual
|
||||
end
|
||||
|
||||
diffable
|
||||
attr_reader :expected, :actual
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,176 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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_relative 'identifier'
|
||||
|
||||
module TableHelpers
|
||||
module Column
|
||||
extend Identifier
|
||||
|
||||
def self.for(header)
|
||||
case header
|
||||
when /\A\s*estimated hours/i
|
||||
raise ArgumentError, 'Please use "work" instead of "estimated hours"'
|
||||
when /derived estimated hours/i
|
||||
raise ArgumentError, 'Please use "derived work" instead of "derived estimated hours"'
|
||||
when /derived remaining hours/i
|
||||
raise ArgumentError, 'Please use "derived remaining work" instead of "derived remaining hours"'
|
||||
when /\A\s*remaining hours/i
|
||||
raise ArgumentError, 'Please use "remaining work" instead of "remaining hours"'
|
||||
when /\A\s*work/i
|
||||
Duration.new(header:, attribute: :estimated_hours)
|
||||
when /derived work/i
|
||||
Duration.new(header:, attribute: :derived_estimated_hours)
|
||||
when /\A\s*remaining work/i
|
||||
Duration.new(header:, attribute: :remaining_hours)
|
||||
when /derived remaining work/i
|
||||
Duration.new(header:, attribute: :derived_remaining_hours)
|
||||
when /subject/
|
||||
Subject.new(header:)
|
||||
when /hierarchy/
|
||||
Hierarchy.new(header:)
|
||||
else
|
||||
assert_work_package_attribute_exists(header)
|
||||
Generic.new(header:)
|
||||
end
|
||||
end
|
||||
|
||||
def self.assert_work_package_attribute_exists(attribute)
|
||||
attribute = to_identifier(attribute).to_s
|
||||
return if WorkPackage.attribute_names.include?(attribute)
|
||||
|
||||
raise ArgumentError, "WorkPackage does not have an attribute named #{attribute.inspect}"
|
||||
end
|
||||
|
||||
class Generic
|
||||
include Identifier
|
||||
|
||||
attr_reader :attribute, :title, :raw_header
|
||||
|
||||
def initialize(header:, attribute: nil)
|
||||
@raw_header = header
|
||||
@title = header.strip
|
||||
@attribute = attribute || to_identifier(title)
|
||||
end
|
||||
|
||||
def format(value)
|
||||
value.to_s
|
||||
end
|
||||
|
||||
def cell_format(value, size)
|
||||
format(value).send(text_align, size)
|
||||
end
|
||||
|
||||
def parse(raw_value)
|
||||
raw_value.strip
|
||||
end
|
||||
|
||||
def text_align
|
||||
:ljust
|
||||
end
|
||||
|
||||
def read_and_update_work_packages_data(work_packages_data)
|
||||
work_packages_data.each do |work_package_data|
|
||||
work_package_data => { attributes:, row: }
|
||||
raw_value = row[raw_header]
|
||||
work_package_data.merge!(metadata_for_value(raw_value))
|
||||
attributes.merge!(attributes_for_raw_value(raw_value, work_package_data, work_packages_data))
|
||||
end
|
||||
end
|
||||
|
||||
def attributes_for_raw_value(raw_value, _data, _work_packages_data)
|
||||
{ attribute => parse(raw_value) }
|
||||
end
|
||||
|
||||
def metadata_for_value(_raw_value)
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
module Identifiable
|
||||
include Identifier
|
||||
|
||||
def metadata_for_value(raw_value, *)
|
||||
super.merge(identifier: to_identifier(raw_value))
|
||||
end
|
||||
end
|
||||
|
||||
class Duration < Generic
|
||||
def text_align
|
||||
:rjust
|
||||
end
|
||||
|
||||
def format(value)
|
||||
if value.nil?
|
||||
''
|
||||
elsif value == value.truncate
|
||||
"%sh" % value.to_i
|
||||
else
|
||||
"%sh" % value
|
||||
end
|
||||
end
|
||||
|
||||
def parse(raw_value)
|
||||
raw_value.blank? ? nil : raw_value.to_f
|
||||
end
|
||||
end
|
||||
|
||||
class Subject < Generic
|
||||
include Identifiable
|
||||
end
|
||||
|
||||
class Hierarchy < Generic
|
||||
include Identifiable
|
||||
|
||||
def attributes_for_raw_value(raw_value, data, work_packages_data)
|
||||
{
|
||||
parent: find_parent(data, work_packages_data),
|
||||
subject: parse(raw_value)
|
||||
}
|
||||
end
|
||||
|
||||
def metadata_for_value(raw_value)
|
||||
super.merge(hierarchy_indent: raw_value[/\A */].size)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_parent(data, work_packages_data)
|
||||
return if data[:hierarchy_indent] == 0
|
||||
|
||||
work_packages_data
|
||||
.slice(0, data[:index])
|
||||
.reverse
|
||||
.find { _1[:hierarchy_indent] < data[:hierarchy_indent] }
|
||||
.then { _1&.fetch(:identifier) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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 TableHelpers
|
||||
module ExampleMethods
|
||||
# Create work packages and relations from a visual chart representation.
|
||||
#
|
||||
# For instance:
|
||||
#
|
||||
# create_table(<<~TABLE)
|
||||
# hierarchy | work |
|
||||
# parent | 1h |
|
||||
# child | 2.5h |
|
||||
# another one | |
|
||||
# TABLE
|
||||
#
|
||||
# is equivalent to:
|
||||
#
|
||||
# create(:work_package, subject: 'parent', estimated_hours: 1)
|
||||
# create(:work_package, subject: 'child', parent: parent, estimated_hours: 2.5)
|
||||
# create(:work_package, subject: 'another one')
|
||||
def create_table(table_representation)
|
||||
table_data = TableData.for(table_representation)
|
||||
table_data.create_work_packages
|
||||
end
|
||||
|
||||
# Expect the given work packages to match a visual table representation.
|
||||
#
|
||||
# It uses +match_table+ internally.
|
||||
#
|
||||
# For instance:
|
||||
#
|
||||
# it 'is scheduled' do
|
||||
# expect_work_packages(work_packages, <<~TABLE)
|
||||
# subject | work | derived work |
|
||||
# parent | 1h | 3h |
|
||||
# child | 2h | 2h |
|
||||
# TABLE
|
||||
# end
|
||||
#
|
||||
# is equivalent to:
|
||||
#
|
||||
# it 'is scheduled' do
|
||||
# expect(work_packages).to match_table(<<~TABLE)
|
||||
# subject | work | derived work |
|
||||
# parent | 1h | 3h |
|
||||
# child | 2h | 2h |
|
||||
# TABLE
|
||||
# end
|
||||
def expect_work_packages(work_packages, table_representation)
|
||||
expect(work_packages).to match_table(table_representation)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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 TableHelpers
|
||||
module Identifier
|
||||
extend self
|
||||
|
||||
def to_identifier(name)
|
||||
name = name.downcase
|
||||
name.strip!
|
||||
name.tr!(' \-', '_')
|
||||
name.gsub!(/_+(?=\d)/, '')
|
||||
name.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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 TableHelpers
|
||||
module LetWorkPackages
|
||||
# Declare work packages and relations from a visual chart representation.
|
||||
#
|
||||
# It uses +create_table+ internally and is useful to have direct access
|
||||
# to the created work packages.
|
||||
#
|
||||
# To see supported columns, see +TableHelpers::Column+.
|
||||
#
|
||||
# For instance:
|
||||
#
|
||||
# let_work_packages(<<~TABLE)
|
||||
# hierarchy | work |
|
||||
# parent | 1h |
|
||||
# child | 2.5h |
|
||||
# another one | |
|
||||
# TABLE
|
||||
#
|
||||
# is equivalent to:
|
||||
#
|
||||
# let!(:_table) do
|
||||
# create_table(table_representation)
|
||||
# end
|
||||
# let(:parent) do
|
||||
# _table.work_package(:parent)
|
||||
# end
|
||||
# let(:child) do
|
||||
# _table.work_package(:child)
|
||||
# end
|
||||
# let(:another_one) do
|
||||
# _table.work_package(:another_one)
|
||||
# end
|
||||
def let_work_packages(table_representation)
|
||||
let!(:_table) { create_table(table_representation) }
|
||||
|
||||
table_data = TableData.for(table_representation)
|
||||
table_data.work_package_identifiers.each do |identifier|
|
||||
let(identifier) { _table.work_package(identifier) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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 TableHelpers
|
||||
class Table
|
||||
def initialize(work_packages_by_identifier)
|
||||
@work_packages_by_identifier = work_packages_by_identifier
|
||||
end
|
||||
|
||||
def work_package(name)
|
||||
name = normalize_name(name)
|
||||
@work_packages_by_identifier[name]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize_name(name)
|
||||
symbolic_name = name.to_sym
|
||||
return symbolic_name if @work_packages_by_identifier.has_key?(symbolic_name)
|
||||
|
||||
spell_checker = DidYouMean::SpellChecker.new(dictionary: @work_packages_by_identifier.keys.map(&:to_s))
|
||||
suggestions = spell_checker.correct(name).map(&:inspect).join(' ')
|
||||
did_you_mean = " Did you mean #{suggestions} instead?" if suggestions.present?
|
||||
raise "No work package with name #{name.inspect} in _table.#{did_you_mean}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,121 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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 TableHelpers
|
||||
# Contains work packages information from a table representation.
|
||||
class TableData
|
||||
extend Identifier
|
||||
|
||||
attr_reader :work_packages_data
|
||||
|
||||
def self.for(representation)
|
||||
work_packages_data = TableParser.new.parse(representation)
|
||||
TableData.new(work_packages_data)
|
||||
end
|
||||
|
||||
def self.from_work_packages(work_packages, columns)
|
||||
attribute_names = columns.map(&:attribute)
|
||||
work_packages_data = work_packages.map do |work_package|
|
||||
attributes = attribute_names.to_h { [_1, work_package.read_attribute(_1)] }
|
||||
attributes[:parent] &&= to_identifier(attributes[:parent].subject)
|
||||
row = columns.to_h { [_1.title, nil] }
|
||||
identifier = to_identifier(work_package.subject)
|
||||
{
|
||||
attributes:,
|
||||
row:,
|
||||
identifier:
|
||||
}
|
||||
end
|
||||
TableData.new(work_packages_data)
|
||||
end
|
||||
|
||||
def initialize(work_packages_data)
|
||||
@work_packages_data = work_packages_data
|
||||
end
|
||||
|
||||
def columns
|
||||
headers.map do |header|
|
||||
Column.for(header)
|
||||
end
|
||||
end
|
||||
|
||||
def headers
|
||||
work_packages_data.first[:row].keys
|
||||
end
|
||||
|
||||
def values_for_attribute(attribute)
|
||||
work_packages_data.map do |work_package_data|
|
||||
work_package_data.dig(:attributes, attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def work_package_identifiers
|
||||
work_packages_data.pluck(:identifier)
|
||||
end
|
||||
|
||||
def create_work_packages
|
||||
work_packages_by_identifier = Factory.new(self).create
|
||||
Table.new(work_packages_by_identifier)
|
||||
end
|
||||
|
||||
class Factory
|
||||
attr_reader :table_data, :work_packages_by_identifier
|
||||
|
||||
def initialize(table_data)
|
||||
@table_data = table_data
|
||||
@work_packages_by_identifier = {}
|
||||
end
|
||||
|
||||
def create
|
||||
table_data.work_package_identifiers.map do |identifier|
|
||||
create_work_package(identifier)
|
||||
end
|
||||
work_packages_by_identifier
|
||||
end
|
||||
|
||||
def create_work_package(identifier)
|
||||
@work_packages_by_identifier[identifier] ||= begin
|
||||
attributes = work_package_attributes(identifier)
|
||||
attributes[:parent] = lookup_parent(attributes[:parent])
|
||||
FactoryBot.create(:work_package, attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def lookup_parent(identifier)
|
||||
if identifier
|
||||
@work_packages_by_identifier[identifier] || create_work_package(identifier)
|
||||
end
|
||||
end
|
||||
|
||||
def work_package_attributes(identifier)
|
||||
data = table_data.work_packages_data.find { |wpa| wpa[:identifier] == identifier.to_sym }
|
||||
data[:attributes]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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 TableHelpers
|
||||
class TableParser
|
||||
def parse(representation)
|
||||
headers, *rows = representation.split("\n")
|
||||
headers = headers.split('|')
|
||||
rows = rows.filter_map { |row| parse_row(row, headers) }
|
||||
work_packages_data = rows.map.with_index do |row, index|
|
||||
{
|
||||
attributes: {},
|
||||
index:,
|
||||
row:
|
||||
}
|
||||
end
|
||||
headers.compact_blank.each do |header|
|
||||
column = Column.for(header)
|
||||
column.read_and_update_work_packages_data(work_packages_data)
|
||||
end
|
||||
work_packages_data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_row(row, headers)
|
||||
case row
|
||||
when '', /^\s*#/
|
||||
# noop
|
||||
else
|
||||
values = row.split('|')
|
||||
headers.zip(values).to_h.compact_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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 TableHelpers
|
||||
class TableRepresenter
|
||||
attr_reader :tables_data, :columns
|
||||
|
||||
def initialize(tables_data:, columns:)
|
||||
@tables_data = tables_data
|
||||
@columns = columns
|
||||
end
|
||||
|
||||
# rubocop:disable Style/MultilineBlockChain
|
||||
def render(table_data)
|
||||
column_and_cell_sizes
|
||||
.map do |column, cell_size|
|
||||
header = column.title.ljust(cell_size)
|
||||
cells = table_data.values_for_attribute(column.attribute).map { column.cell_format(_1, cell_size) }
|
||||
[header, *cells]
|
||||
end
|
||||
.transpose
|
||||
.map { |row| "| #{row.join(' | ')} |\n" }
|
||||
.join
|
||||
end
|
||||
# rubocop:enable Style/MultilineBlockChain
|
||||
|
||||
private
|
||||
|
||||
def column_and_cell_sizes
|
||||
@column_and_cell_sizes ||=
|
||||
columns.index_with do |column|
|
||||
values = tables_data.flat_map { _1.values_for_attribute(column.attribute) }
|
||||
values_max_size = values.map { column.format(_1).size }.max
|
||||
[column.title.size, values_max_size].max
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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'
|
||||
|
||||
module TableHelpers::Column
|
||||
RSpec.describe Generic do
|
||||
subject(:column) { described_class.new(header: 'Some header') }
|
||||
|
||||
describe '#format' do
|
||||
it 'renders the value as string' do
|
||||
expect(column.format('hello')).to eq 'hello'
|
||||
expect(column.format(42)).to eq '42'
|
||||
expect(column.format(3.5)).to eq '3.5'
|
||||
expect(column.format(nil)).to eq ''
|
||||
expect(column.format(true)).to eq 'true'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cell_format' do
|
||||
it 'renders the value on the left side of the cell' do
|
||||
expect(column.cell_format('hello', 0)).to eq 'hello'
|
||||
expect(column.cell_format('hello', 10)).to eq 'hello '
|
||||
expect(column.cell_format('hello', 20)).to eq 'hello '
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Duration do
|
||||
subject(:column) { described_class.new(header: 'Duration in hours') }
|
||||
|
||||
describe '#parse' do
|
||||
it 'parses empty string as nil' do
|
||||
expect(column.parse('')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#format' do
|
||||
it 'renders the duration with a "h" suffix' do
|
||||
expect(column.format(3.5)).to eq '3.5h'
|
||||
end
|
||||
|
||||
it 'renders the duration without the decimal part if the decimal part is 0' do
|
||||
expect(column.format(3.0)).to eq '3h'
|
||||
end
|
||||
|
||||
it 'renders nothing if nil' do
|
||||
expect(column.format(nil)).to eq ''
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cell_format' do
|
||||
it 'renders the duration on the right side of the cell' do
|
||||
expect(column.cell_format(3.5, 0)).to eq '3.5h'
|
||||
expect(column.cell_format(3.5, 10)).to eq ' 3.5h'
|
||||
expect(column.cell_format(3.5, 20)).to eq ' 3.5h'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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'
|
||||
|
||||
module TableHelpers
|
||||
RSpec.describe Identifier do
|
||||
shared_examples 'an identifier' do |name:, expected_identifier:|
|
||||
it "converts #{name.inspect} to identifier #{expected_identifier.inspect}" do
|
||||
expect(described_class.to_identifier(name)).to eq(expected_identifier)
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'an identifier', name: 'Subject', expected_identifier: :subject
|
||||
include_examples 'an identifier', name: 'Work package', expected_identifier: :work_package
|
||||
include_examples 'an identifier', name: 'grand-child', expected_identifier: :grand_child
|
||||
include_examples 'an identifier', name: 'Child 1', expected_identifier: :child1
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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'
|
||||
|
||||
module TableHelpers
|
||||
RSpec.describe TableData do
|
||||
describe '.for' do
|
||||
it 'reads a table representation and stores its data' do
|
||||
table = <<~TABLE
|
||||
| subject | remaining work |
|
||||
| work package | 3h |
|
||||
| another one | |
|
||||
TABLE
|
||||
|
||||
table_data = described_class.for(table)
|
||||
expect(table_data.work_packages_data.size).to eq(2)
|
||||
expect(table_data.columns.size).to eq(2)
|
||||
expect(table_data.headers).to eq([' subject ', ' remaining work '])
|
||||
expect(table_data.work_package_identifiers).to eq(%i[work_package another_one])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.from_work_packages' do
|
||||
it 'reads data from work packages according to the given columns' do
|
||||
table = <<~TABLE
|
||||
| subject | remaining work |
|
||||
| work package | 3h |
|
||||
| another one | |
|
||||
TABLE
|
||||
columns = described_class.for(table).columns
|
||||
|
||||
work_package = build(:work_package, subject: 'work package', remaining_hours: 3)
|
||||
another_one = build(:work_package, subject: 'another one')
|
||||
|
||||
table_data = described_class.from_work_packages([work_package, another_one], columns)
|
||||
expect(table_data.work_packages_data.size).to eq(2)
|
||||
expect(table_data.columns.size).to eq(2)
|
||||
expect(table_data.headers).to eq(['subject', 'remaining work'])
|
||||
expect(table_data.work_package_identifiers).to eq(%i[work_package another_one])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#values_for_attribute' do
|
||||
it 'returns all the values of the work packages for the given attribute' do
|
||||
table = <<~TABLE
|
||||
| subject | remaining work |
|
||||
| work package | 3h |
|
||||
| another one | |
|
||||
TABLE
|
||||
|
||||
table_data = described_class.for(table)
|
||||
expect(table_data.values_for_attribute(:remaining_hours)).to eq([3.0, nil])
|
||||
expect(table_data.values_for_attribute(:subject)).to eq(['work package', 'another one'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,150 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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'
|
||||
|
||||
RSpec.describe TableHelpers::TableParser do
|
||||
let(:parsed_data) { described_class.new.parse(table) }
|
||||
|
||||
it 'ignores empty decorative columns' do
|
||||
table = <<~TABLE
|
||||
| subject | description |
|
||||
| foo | bar |
|
||||
TABLE
|
||||
parsed_data = described_class.new.parse(table)
|
||||
expect(parsed_data.dig(0, :attributes).keys).to eq(%i[subject description])
|
||||
end
|
||||
|
||||
it 'normalizes column names to identifiers' do
|
||||
table = <<~TABLE
|
||||
| DeScRiPtIoN |
|
||||
| value |
|
||||
TABLE
|
||||
parsed_data = described_class.new.parse(table)
|
||||
expect(parsed_data.dig(0, :attributes).keys).to eq(%i[description])
|
||||
end
|
||||
|
||||
it 'ignores comments and empty lines' do
|
||||
table = <<~TABLE
|
||||
| subject |
|
||||
# this comment and the following empty line are ignored
|
||||
|
||||
| value |
|
||||
TABLE
|
||||
parsed_data = described_class.new.parse(table)
|
||||
expect(parsed_data.length).to eq(1)
|
||||
end
|
||||
|
||||
it 'raises a error if the header name is deprecated ' \
|
||||
'(for example "remaining hours" instead of "remaining work")' do
|
||||
table = <<~TABLE
|
||||
subject | remaining hours
|
||||
wp | 4h
|
||||
TABLE
|
||||
expect { described_class.new.parse(table) }
|
||||
.to raise_error(ArgumentError, 'Please use "remaining work" instead of "remaining hours"')
|
||||
end
|
||||
|
||||
describe 'subject column' do
|
||||
let(:table) do
|
||||
<<~TABLE
|
||||
| subject |
|
||||
| Work Package |
|
||||
TABLE
|
||||
end
|
||||
|
||||
it 'sets the subject attribute' do
|
||||
data = parsed_data.first
|
||||
expect(data.dig(:attributes, :subject)).to eq('Work Package')
|
||||
end
|
||||
|
||||
it 'sets the identifier as the subject snake-cased' do
|
||||
data = parsed_data.first
|
||||
expect(data[:identifier]).to eq(:work_package)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'hierarchy column' do
|
||||
let(:table) do
|
||||
<<~TABLE
|
||||
hierarchy
|
||||
Parent
|
||||
Child
|
||||
Grand-Child
|
||||
Child 2
|
||||
Child 3
|
||||
Another child
|
||||
Root sibling
|
||||
TABLE
|
||||
end
|
||||
|
||||
it 'sets the parent attribute by its identifier' do
|
||||
attributes = parsed_data.flat_map { _1[:attributes] }
|
||||
expect(attributes.pluck(:parent)).to eq([nil, :parent, :child, :parent, :parent, :child3, nil])
|
||||
end
|
||||
|
||||
it 'sets the subject attribute' do
|
||||
attributes = parsed_data.flat_map { _1[:attributes] }
|
||||
expect(attributes.pluck(:subject))
|
||||
.to eq(['Parent', 'Child', 'Grand-Child', 'Child 2', 'Child 3', 'Another child', 'Root sibling'])
|
||||
end
|
||||
|
||||
it 'sets the identifier metadata as the subject snake-cased' do
|
||||
expect(parsed_data.pluck(:identifier))
|
||||
.to eq(%i[parent child grand_child child2 child3 another_child root_sibling])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'remaining work column' do
|
||||
let(:table) do
|
||||
<<~TABLE
|
||||
subject | remaining work
|
||||
wp | 9h
|
||||
TABLE
|
||||
end
|
||||
|
||||
it 'sets the derived remaining work attribute' do
|
||||
expect(parsed_data.first[:attributes]).to include(remaining_hours: 9)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'derived remaining work column' do
|
||||
let(:table) do
|
||||
<<~TABLE
|
||||
subject | derived remaining work
|
||||
wp | 9h
|
||||
TABLE
|
||||
end
|
||||
|
||||
it 'sets the derived remaining work attribute' do
|
||||
expect(parsed_data.first[:attributes]).to include(derived_remaining_hours: 9)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2012-2024 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'
|
||||
|
||||
module TableHelpers
|
||||
RSpec.describe TableRepresenter do
|
||||
let(:table) do
|
||||
<<~TABLE
|
||||
| subject | remaining work | derived remaining work |
|
||||
| Work Package | 1.5h | 9h |
|
||||
TABLE
|
||||
end
|
||||
let(:table_data) { TableData.for(table) }
|
||||
let(:tables_data) { [table_data] }
|
||||
|
||||
subject(:representer) { described_class.new(tables_data:, columns:) }
|
||||
|
||||
context 'when using a second table for the size' do
|
||||
let(:twin_table) do
|
||||
<<~TABLE
|
||||
| subject |
|
||||
| A quite long work package name |
|
||||
TABLE
|
||||
end
|
||||
let(:twin_table_data) { TableData.for(twin_table) }
|
||||
|
||||
let(:tables_data) { [table_data, twin_table_data] }
|
||||
let(:columns) { [Column.for('subject')] }
|
||||
|
||||
it 'adapts the column sizes to fit the largest value of both tables ' \
|
||||
'so that they can be compared and diffed' do
|
||||
expect(representer.render(table_data)).to eq <<~TABLE
|
||||
| subject |
|
||||
| Work Package |
|
||||
TABLE
|
||||
expect(representer.render(twin_table_data)).to eq <<~TABLE
|
||||
| subject |
|
||||
| A quite long work package name |
|
||||
TABLE
|
||||
end
|
||||
end
|
||||
|
||||
describe 'subject column' do
|
||||
let(:columns) { [Column.for('subject')] }
|
||||
|
||||
it 'is rendered as text' do
|
||||
expect(representer.render(table_data)).to eq <<~TABLE
|
||||
| subject |
|
||||
| Work Package |
|
||||
TABLE
|
||||
end
|
||||
end
|
||||
|
||||
describe 'remaining work column' do
|
||||
let(:columns) { [Column.for('remaining work')] }
|
||||
|
||||
it 'is rendered as a duration' do
|
||||
expect(representer.render(table_data)).to eq <<~TABLE
|
||||
| remaining work |
|
||||
| 1.5h |
|
||||
TABLE
|
||||
end
|
||||
end
|
||||
|
||||
describe 'derived remaining work column' do
|
||||
let(:columns) { [Column.for('derived remaining work')] }
|
||||
|
||||
it 'sets the derived remaining work attribute' do
|
||||
expect(representer.render(table_data)).to eq <<~TABLE
|
||||
| derived remaining work |
|
||||
| 9h |
|
||||
TABLE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user