diff --git a/app/controllers/header/projects_controller.rb b/app/controllers/header/projects_controller.rb index dda501b4ea97..d671a47e0a26 100644 --- a/app/controllers/header/projects_controller.rb +++ b/app/controllers/header/projects_controller.rb @@ -141,31 +141,28 @@ def user_project_favorites # Builds a nested structure from a flat, lft-ordered list of projects. # Each level is sorted alphabetically by project name. def build_tree(projects) - nodes = projects.index_by(&:id).transform_values do |p| - { project: p, children: [], matches_query: @matching_ids.nil? || @matching_ids.include?(p.id) } - end - - roots = [] - projects.each do |project| - node = nodes[project.id] - parent = nodes[project.parent_id] + nodes = Project.build_projects_hierarchy(projects) + mark_query_matches(nodes) + sort_nodes(nodes) + end - if parent - parent[:children] << node - else - roots << node - end + def mark_query_matches(nodes) + nodes.each do |node| + node[:matches_query] = @matching_ids.nil? || @matching_ids.include?(node[:project].id) + mark_query_matches(node[:children]) end - - sort_nodes(roots) end def sort_nodes(nodes) nodes.sort_by { |n| n[:project].name.downcase }.each do |node| node[:children] = sort_nodes(node[:children]) - node[:expanded] = filter_mode == "favorited" || node[:children].any? do |c| - c[:project].id == @current_project_id || c[:expanded] - end + node[:expanded] = expanded_node?(node) + end + end + + def expanded_node?(node) + filter_mode == "favorited" || node[:children].any? do |child| + child[:project].id == @current_project_id || child[:expanded] end end end diff --git a/spec/controllers/header/projects_controller_spec.rb b/spec/controllers/header/projects_controller_spec.rb index 981b9aa42683..7f7695423280 100644 --- a/spec/controllers/header/projects_controller_spec.rb +++ b/spec/controllers/header/projects_controller_spec.rb @@ -69,6 +69,27 @@ expect(response).to render_template(layout: false) end + context "when an invisible project is between two visible projects" do + shared_let(:invisible_project) { create(:private_project, name: "Invisible", parent: parent_project) } + shared_let(:visible_grandchild) { create(:project, name: "Visible Grandchild", parent: invisible_project) } + + before do + create(:member, principal: current_user, project: visible_grandchild, roles: [role]) + end + + it "nests the grandchild below its nearest visible ancestor", :aggregate_failures do + make_request + + tree = assigns(:tree) + parent_node = tree.find { |node| node[:project] == parent_project } + + expect(assigns(:projects)).to include(visible_grandchild) + expect(assigns(:projects)).not_to include(invisible_project) + expect(tree.pluck(:project)).not_to include(visible_grandchild) + expect(parent_node[:children].pluck(:project)).to include(visible_grandchild) + end + end + context "when searching by query" do subject(:make_request) { get :index, params: { query: "Beta" } }