Skip to content

Commit fbcd738

Browse files
authored
Merge pull request opf#18208 from opf/feature/61885-relations-ordering-in-relations-tab
[#61885] Sort relations in relations tab by created_at
2 parents a40aa88 + ae9e959 commit fbcd738

File tree

4 files changed

+276
-2
lines changed

4 files changed

+276
-2
lines changed

app/components/work_package_relations_tab/index_component.html.erb

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
# Combine visible and invisible relations into a single list
5858
all_relations = relation_group.visible_relations.map { |r| [r, :visible] } +
5959
relation_group.ghost_relations.map { |r| [r, :ghost] }
60+
all_relations.sort_by! { |r| r[0].id }
6061

6162
flex.with_row(mb: 4) do
6263
render_relation_group(
@@ -84,6 +85,7 @@
8485
# Combine visible and invisible children into a single list
8586
all_children = visible_children.map { |r| [r, :visible] } +
8687
ghost_children.map { |r| [r, :ghost] }
88+
all_children.sort_by! { |r| r[0].created_at }
8789

8890
flex.with_row do
8991
render_relation_group(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
require "rails_helper"
32+
33+
RSpec.describe WorkPackageRelationsTab::IndexComponent, type: :component do
34+
create_shared_association_defaults_for_work_package_factory
35+
36+
shared_let(:user) { create(:admin) }
37+
shared_let(:work_package) { create(:work_package) }
38+
39+
current_user { user }
40+
41+
def render_component(**params)
42+
render_inline(described_class.new(work_package:, **params))
43+
page
44+
end
45+
46+
context "with no relations" do
47+
it "renders a message" do
48+
expect(render_component).to have_heading "No relations"
49+
expect(page).to have_text "This work package does not have any relations yet."
50+
end
51+
end
52+
53+
context "with child relations" do
54+
shared_let_work_packages(<<~TABLE)
55+
hierarchy | MTWTFSS | scheduling mode |
56+
work_package | X | automatic |
57+
child1 | XXX | manual |
58+
child2 | | automatic |
59+
TABLE
60+
61+
it "renders the relations group" do
62+
expect(render_component).to have_test_selector("op-relation-group-children")
63+
end
64+
65+
it "renders the relations in child creation order" do
66+
expect(render_component).to have_list "Children"
67+
68+
list = page.find(:list, "Children")
69+
expect(list).to have_list_item count: 2, text: /child\d/
70+
expect(list).to have_list_item position: 1, text: "child1"
71+
expect(list).to have_list_item position: 2, text: "child2"
72+
end
73+
end
74+
75+
context "with follows relations" do
76+
shared_let_work_packages(<<~TABLE)
77+
hierarchy | MTWTFSS | scheduling mode | predecessors
78+
predecessor1 | XXX | manual |
79+
predecessor2 | XX | manual |
80+
predecessor3 | XX | manual |
81+
predecessor4 | | manual |
82+
work_package | X | automatic | predecessor1 with lag 2, predecessor2 with lag 7, predecessor3 with lag 7, predecessor4 with lag 10
83+
TABLE
84+
85+
it "renders the relations group" do
86+
expect(render_component).to have_test_selector("op-relation-group-follows")
87+
end
88+
89+
it "renders the relations in relation creation order" do
90+
expect(render_component).to have_list "Predecessors (before)"
91+
92+
list = page.find(:list, "Predecessors (before)")
93+
expect(list).to have_list_item count: 4, text: /predecessor\d/
94+
expect(list).to have_list_item position: 1, text: "predecessor1"
95+
expect(list).to have_list_item position: 2, text: "predecessor2"
96+
expect(list).to have_list_item position: 3, text: "predecessor3"
97+
expect(list).to have_list_item position: 4, text: "predecessor4"
98+
99+
# delete and recreate relation to predecessor2.
100+
relation_attributes = predecessor2.relations.first.attributes
101+
predecessor2.relations.first.destroy
102+
Relation.create!(relation_attributes.without("id"))
103+
104+
# predecessor2 should now be at last position
105+
render_component
106+
list = page.find(:list, "Predecessors (before)")
107+
expect(list).to have_list_item position: 1, text: "predecessor1"
108+
expect(list).to have_list_item position: 2, text: "predecessor3"
109+
expect(list).to have_list_item position: 3, text: "predecessor4"
110+
expect(list).to have_list_item position: 4, text: "predecessor2"
111+
end
112+
113+
it "renders the closest relation" do
114+
render_component
115+
116+
list_item = page.find(:list_item, text: "predecessor2")
117+
expect(list_item).to have_primer_label("Closest")
118+
end
119+
end
120+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
require "rails_helper"
32+
33+
RSpec.describe WorkPackageRelationsTab::RelationComponent, type: :component do
34+
shared_let(:user) { create(:admin) }
35+
shared_let(:work_package) { create(:work_package) }
36+
37+
current_user { user }
38+
39+
shared_let_work_packages(<<~TABLE)
40+
hierarchy | MTWTFSS | scheduling mode | predecessors
41+
predecessor | XXX | manual |
42+
work_package | X | automatic | predecessor with lag 2
43+
child | | automatic |
44+
TABLE
45+
46+
def render_component(**params)
47+
render_inline(described_class.new(work_package:, **params))
48+
end
49+
50+
context "with child relations" do
51+
context "when visible" do
52+
it "renders a title link" do
53+
expect(render_component(relation: nil, child: child, visibility: :visible))
54+
.to have_link "child"
55+
end
56+
57+
context "when editable" do
58+
it "renders an action menu" do
59+
component = render_component(relation: nil, child: child, visibility: :visible, editable: true)
60+
expect(component).to have_menu # FIXME: aria-labelledby does not resolve here "Relation actions"
61+
expect(component).to have_selector :menuitem, "Delete relation"
62+
end
63+
end
64+
end
65+
66+
context "when ghost" do
67+
it "does not render a title link" do
68+
expect(render_component(relation: nil, child: child, visibility: :ghost))
69+
.to have_no_link "child"
70+
end
71+
72+
it "renders a title and message without details" do
73+
expect(render_component(relation: nil, child: child, visibility: :ghost))
74+
.to have_text "Related work package"
75+
expect(render_component(relation: nil, child: child, visibility: :ghost))
76+
.to have_text "This is not visible to you due to permissions."
77+
end
78+
79+
it "does not render an action menu" do
80+
expect(render_component(relation: nil, child: child, visibility: :ghost))
81+
.to have_no_menu
82+
end
83+
end
84+
end
85+
86+
context "with follows relations" do
87+
context "when visible" do
88+
it "renders a title link" do
89+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :visible))
90+
.to have_link "predecessor"
91+
end
92+
93+
it "renders the lag" do
94+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :visible))
95+
.to have_text "Lag: 2 days"
96+
end
97+
98+
context "when editable" do
99+
it "renders a action menu" do
100+
component = render_component(relation: _table.relation(predecessor: predecessor), visibility: :visible, editable: true)
101+
expect(component).to have_menu # FIXME: aria-labelledby does not resolve here "Relation actions"
102+
expect(component).to have_selector :menuitem, "Edit relation"
103+
expect(component).to have_selector :menuitem, "Delete relation"
104+
end
105+
end
106+
end
107+
108+
context "when ghost" do
109+
it "does not render a title link" do
110+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :ghost))
111+
.to have_no_link "child"
112+
end
113+
114+
it "renders a title and message without details" do
115+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :ghost))
116+
.to have_text "Related work package"
117+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :ghost))
118+
.to have_text "This is not visible to you due to permissions."
119+
end
120+
121+
it "does not render an action menu" do
122+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :ghost))
123+
.to have_no_menu
124+
end
125+
end
126+
127+
context "when closest" do
128+
it "always renders a closest label" do
129+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :visible, closest: true))
130+
.to have_primer_label "Closest", scheme: :primary
131+
expect(render_component(relation: _table.relation(predecessor: predecessor), visibility: :ghost, closest: true))
132+
.to have_primer_label "Closest", scheme: :primary
133+
end
134+
end
135+
end
136+
end

spec/support/capybara/additional_accessible_selectors.rb

+18-2
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,29 @@
2929
#++
3030

3131
Capybara.add_selector(:list) do
32-
xpath { ".//ul | .//ol" }
32+
xpath do |*|
33+
XPath.descendant[[
34+
XPath.self(:ul),
35+
XPath.self(:ol)
36+
].reduce(:|)]
37+
end
38+
39+
locator_filter skip_if: nil do |node, locator, exact:, **|
40+
method = exact ? :eql? : :include?
41+
if node[:"aria-labelledby"]
42+
CapybaraAccessibleSelectors::Helpers.element_labelledby(node).public_send(method, locator)
43+
elsif node[:"aria-label"]
44+
node[:"aria-label"].public_send(method, locator.to_s)
45+
end
46+
end
3347
end
3448

3549
Capybara.add_selector(:list_item) do
3650
label "list item"
3751

38-
xpath { ".//li" }
52+
xpath do |*|
53+
XPath.descendant[XPath.self(:li)]
54+
end
3955

4056
expression_filter(:position) do |xpath, position|
4157
position ? "#{xpath}[#{position}]" : xpath

0 commit comments

Comments
 (0)