From 42d0ffdcccc137b87ec50782be7bb2c98832c7ee Mon Sep 17 00:00:00 2001
From: Josh Adam <josh.adam@phac-aspc.gc.ca>
Date: Thu, 3 Oct 2024 14:16:54 -0500
Subject: [PATCH] Enhancement: Subgroup projects searching and pagination
 (#792)

* chore: Working on search in subgroups

* chore: Working on auto-submit

* chore: Just need to figure out flattening on search

* chore: This wasn't needed

* chore: Working search for subgroups and project

* test: This does not need to be there anymore

* chore: Fixed Shared groups and projects content

* test: Update test for pagination

* chore: Fixed flaky test

* chore: Fixed test

* test: Test searching

* chore: Cleaned up methods

* feat: Updated with ability to set the icon size

* chore: Removed the search from the turbo response
---
 .../namespace_tree_component.html.erb         |  3 +-
 .../namespace_tree_component.rb               |  6 +-
 .../row/row_contents_component.html.erb       |  4 +-
 .../row/row_contents_component.rb             |  5 +-
 .../row/with_children_component.html.erb      | 14 +--
 .../row/with_children_component.rb            |  3 +-
 .../row/without_children_component.html.erb   |  5 +-
 .../row/without_children_component.rb         |  4 +-
 .../namespace_tree/row_component.rb           |  8 +-
 .../namespace_tree_container_component.rb     |  8 +-
 .../dashboard/groups_controller.rb            |  3 +-
 .../groups/subgroups_controller.rb            | 11 +--
 app/controllers/groups_controller.rb          | 21 ++++
 app/models/namespace.rb                       |  7 ++
 .../dashboard/groups/group.turbo_stream.erb   |  3 +-
 app/views/dashboard/groups/index.html.erb     |  2 +-
 app/views/groups/show.html.erb                | 96 +++++++++++--------
 app/views/groups/subgroups/_index.html.erb    | 20 ++++
 config/locales/en.yml                         |  2 +
 config/locales/fr.yml                         |  2 +
 .../groups/subgroups_controller_test.rb       |  6 --
 test/system/groups_test.rb                    | 20 ++--
 22 files changed, 161 insertions(+), 92 deletions(-)
 create mode 100644 app/views/groups/subgroups/_index.html.erb

diff --git a/app/components/namespace_tree/namespace_tree_component.html.erb b/app/components/namespace_tree/namespace_tree_component.html.erb
index 9b3379c53f..1755f6e554 100644
--- a/app/components/namespace_tree/namespace_tree_component.html.erb
+++ b/app/components/namespace_tree/namespace_tree_component.html.erb
@@ -1,4 +1,4 @@
-<ul class="groups-list namespace-list-tree flex flex-col">
+<ul class="flex flex-col groups-list namespace-list-tree">
   <%= render NamespaceTree::RowComponent.with_collection(
     namespaces,
     path: path,
@@ -6,5 +6,6 @@
     type: @type,
     collapsed: collapsed,
     render_flat_list: render_flat_list,
+    icon_size: icon_size,
   ) %>
 </ul>
diff --git a/app/components/namespace_tree/namespace_tree_component.rb b/app/components/namespace_tree/namespace_tree_component.rb
index d23779dc82..61a7b9ea66 100644
--- a/app/components/namespace_tree/namespace_tree_component.rb
+++ b/app/components/namespace_tree/namespace_tree_component.rb
@@ -3,10 +3,11 @@
 module NamespaceTree
   # Component to render a namespace tree
   class NamespaceTreeComponent < Component
-    attr_reader :parent, :namespaces, :path, :path_args, :collapsed, :render_flat_list
+    attr_reader :collapsed, :icon_size, :namespaces, :parent, :path, :path_args, :render_flat_list
 
     # rubocop: disable Metrics/ParameterLists
-    def initialize(namespaces:, type:, parent: nil, path: nil, path_args: {}, render_flat_list: false)
+    def initialize(namespaces:, type:, parent: nil, path: nil, path_args: {}, render_flat_list: false,
+                   icon_size: :small)
       @parent = parent
       @namespaces = namespaces
       @path = path
@@ -14,6 +15,7 @@ def initialize(namespaces:, type:, parent: nil, path: nil, path_args: {}, render
       @type = type
       @collapsed = true
       @render_flat_list = render_flat_list
+      @icon_size = icon_size
     end
 
     # rubocop: enable Metrics/ParameterLists
diff --git a/app/components/namespace_tree/row/row_contents_component.html.erb b/app/components/namespace_tree/row/row_contents_component.html.erb
index 7990c26ab5..4fb2695c74 100644
--- a/app/components/namespace_tree/row/row_contents_component.html.erb
+++ b/app/components/namespace_tree/row/row_contents_component.html.erb
@@ -2,7 +2,7 @@
   <%= viral_icon(name: :squares_2x2, classes: "h-5 w-5 text-slate-400 mr-2") %>
   <%= viral_avatar(
     name: @namespace.name,
-    size: :medium,
+    size: @icon_size,
     colour_string: "#{@namespace.name}-#{@namespace.id}",
     data: {
       turbo: false,
@@ -13,7 +13,7 @@
   <%= viral_icon(name: :rectangle_stack, classes: "h-5 w-5 text-slate-400 mr-2") %>
   <%= viral_avatar(
     name: @namespace.name,
-    size: :small,
+    size: @icon_size,
     colour_string: "#{@namespace.name}-#{@namespace.id}",
     data: {
       turbo: false,
diff --git a/app/components/namespace_tree/row/row_contents_component.rb b/app/components/namespace_tree/row/row_contents_component.rb
index ce7d352313..071600e084 100644
--- a/app/components/namespace_tree/row/row_contents_component.rb
+++ b/app/components/namespace_tree/row/row_contents_component.rb
@@ -4,13 +4,16 @@ module NamespaceTree
   module Row
     # Component for the contents of NamespaceTree row
     class RowContentsComponent < Viral::Component
-      def initialize(namespace:, path: nil, path_args: {}, collapsed: false, sample_count: 0)
+      # rubocop: disable Metrics/ParameterLists
+      def initialize(namespace:, path: nil, path_args: {}, collapsed: false, sample_count: 0, icon_size: :small)
         @namespace = namespace
         @path = path
         @path_args = path_args
         @collapsed = collapsed
         @sample_count = sample_count
+        @icon_size = icon_size
       end
+      # rubocop: enable Metrics/ParameterLists
     end
   end
 end
diff --git a/app/components/namespace_tree/row/with_children_component.html.erb b/app/components/namespace_tree/row/with_children_component.html.erb
index 83d77bb8a6..063d775fc3 100644
--- a/app/components/namespace_tree/row/with_children_component.html.erb
+++ b/app/components/namespace_tree/row/with_children_component.html.erb
@@ -6,13 +6,7 @@
 >
   <div
     class="
-      flex
-      items-center
-      w-full
-      py-3
-      pr-3
-      text-left
-      namespace-entry-contents
+      flex items-center w-full py-3 pr-3 text-left namespace-entry-contents
     "
     data-action="click->groups--row#toggle"
     aria-label="<%= t(:'dashboard.groups.index.row_aria_label', name: @namespace.name) %>"
@@ -32,7 +26,8 @@
       namespace: @namespace,
       path: @path,
       path_args: @path_args,
-      collapsed: @collapsed
+      collapsed: @collapsed,
+      icon_size: @icon_size,
     ) %>
   </div>
   <% unless @collapsed %>
@@ -41,7 +36,8 @@
       namespaces: @children,
       path: @path,
       path_args: @path_args,
-      type: @type
+      type: @type,
+      icon_size: @icon_size,
     ) %>
   <% end %>
 </li>
diff --git a/app/components/namespace_tree/row/with_children_component.rb b/app/components/namespace_tree/row/with_children_component.rb
index c0ece97b79..5a17b70554 100644
--- a/app/components/namespace_tree/row/with_children_component.rb
+++ b/app/components/namespace_tree/row/with_children_component.rb
@@ -5,13 +5,14 @@ module Row
     # Component for the contents of a namespace row that has children
     class WithChildrenComponent < Viral::Component
       # rubocop:disable Metrics/ParameterLists
-      def initialize(namespace:, children:, type:, path: nil, path_args: {}, collapsed: false)
+      def initialize(namespace:, children:, type:, path: nil, path_args: {}, collapsed: false, icon_size: :small)
         @namespace = namespace
         @children = children
         @type = type
         @path = path
         @path_args = path_args
         @collapsed = collapsed
+        @icon_size = icon_size
       end
 
       # rubocop:enable Metrics/ParameterLists
diff --git a/app/components/namespace_tree/row/without_children_component.html.erb b/app/components/namespace_tree/row/without_children_component.html.erb
index b8dfe1a3e6..bb124f4d62 100644
--- a/app/components/namespace_tree/row/without_children_component.html.erb
+++ b/app/components/namespace_tree/row/without_children_component.html.erb
@@ -1,8 +1,8 @@
 <li
   class="namespace-entry <%= @namespace.description.present? ? "has-description" : nil %>"
 >
-  <div class="namespace-entry-contents py-3 pr-3 flex items-center">
-    <span class="folder-toggle-wrap mr-2 flex items-center">
+  <div class="flex items-center py-3 pr-3 namespace-entry-contents">
+    <span class="flex items-center mr-2 folder-toggle-wrap">
       <%= viral_icon(name: :blank, classes: "h-3 w-3") %>
     </span>
     <%= render NamespaceTree::Row::RowContentsComponent.new(
@@ -10,6 +10,7 @@
       path: @path,
       path_args: @path_args,
       sample_count: @sample_count,
+      icon_size: @icon_size,
     ) %>
   </div>
 </li>
diff --git a/app/components/namespace_tree/row/without_children_component.rb b/app/components/namespace_tree/row/without_children_component.rb
index b879b4d091..3c468031a6 100644
--- a/app/components/namespace_tree/row/without_children_component.rb
+++ b/app/components/namespace_tree/row/without_children_component.rb
@@ -4,11 +4,11 @@ module NamespaceTree
   module Row
     # Component for the contents of a namespace row that has no children
     class WithoutChildrenComponent < Viral::Component
-      def initialize(namespace:, path: nil, path_args: {})
+      def initialize(namespace:, path: nil, path_args: {}, icon_size: :small)
         @namespace = namespace
         @path = path
         @path_args = path_args
-        @sample_count = @namespace.type == 'Project' ? @namespace.project.samples.size : 0
+        @icon_size = icon_size
       end
     end
   end
diff --git a/app/components/namespace_tree/row_component.rb b/app/components/namespace_tree/row_component.rb
index 749230597c..3af6c9fa49 100644
--- a/app/components/namespace_tree/row_component.rb
+++ b/app/components/namespace_tree/row_component.rb
@@ -7,20 +7,22 @@ class RowComponent < ViewComponent::Base
 
     erb_template <<~ERB
       <% if @namespace.children_of_type?(@type) && !@render_flat_list %>
-        <%= render NamespaceTree::Row::WithChildrenComponent.new(namespace: @namespace, type: @type, children: Group.none, path: @path, path_args: @path_args, collapsed: @collapsed) %>
+        <%= render NamespaceTree::Row::WithChildrenComponent.new(namespace: @namespace, type: @type, children: Group.none, path: @path, path_args: @path_args, collapsed: @collapsed, icon_size: @icon_size) %>
         <% else %>
-        <%= render NamespaceTree::Row::WithoutChildrenComponent.new(namespace: @namespace, path: @path, path_args: @path_args) %>
+        <%= render NamespaceTree::Row::WithoutChildrenComponent.new(namespace: @namespace, path: @path, path_args: @path_args, icon_size: @icon_size) %>
       <% end %>
     ERB
 
     # rubocop:disable Metrics/ParameterLists
-    def initialize(namespace:, type:, path: nil, path_args: {}, collapsed: true, render_flat_list: false)
+    def initialize(namespace:, type:, path: nil, path_args: {}, collapsed: true, render_flat_list: false,
+                   icon_size: :small)
       @namespace = namespace
       @type = type
       @path = path
       @path_args = path_args
       @collapsed = collapsed
       @render_flat_list = render_flat_list
+      @icon_size = icon_size
     end
 
     # rubocop:enable Metrics/ParameterLists
diff --git a/app/components/namespace_tree_container_component.rb b/app/components/namespace_tree_container_component.rb
index 55dd65722a..9b91737693 100644
--- a/app/components/namespace_tree_container_component.rb
+++ b/app/components/namespace_tree_container_component.rb
@@ -4,15 +4,19 @@
 class NamespaceTreeContainerComponent < ViewComponent::Base
   erb_template <<-ERB
       <div class="namespace-tree-container">
-        <%= render NamespaceTree::NamespaceTreeComponent.new(namespaces: @namespaces, path: @path, path_args: @path_args, type: @type, render_flat_list: @render_flat_list) %>
+        <%= render NamespaceTree::NamespaceTreeComponent.new(namespaces: @namespaces, path: @path, path_args: @path_args, type: @type, render_flat_list: @render_flat_list, icon_size: @icon_size) %>
       </div>
   ERB
 
-  def initialize(namespaces:, path: nil, path_args: {}, type: Group.sti_name, render_flat_list: false)
+  # rubocop: disable Metrics/ParameterLists
+  def initialize(namespaces:, path: nil, path_args: {}, type: Group.sti_name, render_flat_list: false,
+                 icon_size: :small)
     @namespaces = namespaces
     @path = path
     @path_args = path_args
     @type = type
     @render_flat_list = render_flat_list
+    @icon_size = icon_size
   end
+  # rubocop: enable Metrics/ParameterLists
 end
diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb
index 49e8dd9a77..e38aca4f44 100644
--- a/app/controllers/dashboard/groups_controller.rb
+++ b/app/controllers/dashboard/groups_controller.rb
@@ -6,7 +6,6 @@ class GroupsController < ApplicationController
     before_action :current_page
 
     def index
-      @render_flat_list = flat_list_requested?
       @q = build_ransack_query
       set_default_sort
       @pagy, @groups = pagy(@q.result.include_route)
@@ -54,7 +53,7 @@ def toggle_group
     end
 
     def authorized_groups
-      if @render_flat_list
+      if flat_list_requested?
         authorized_scope(Group, type: :relation)
       else
         authorized_scope(Group, type: :relation).without_descendants
diff --git a/app/controllers/groups/subgroups_controller.rb b/app/controllers/groups/subgroups_controller.rb
index 071e56e552..85c55b3f6a 100644
--- a/app/controllers/groups/subgroups_controller.rb
+++ b/app/controllers/groups/subgroups_controller.rb
@@ -7,16 +7,7 @@ class SubgroupsController < ApplicationController
 
     def index
       authorize! @group, to: :read?
-      respond_to do |format|
-        format.html { redirect_to group_path(@group) }
-        format.turbo_stream do
-          if params.key? :parent_id
-            render_subgroup
-          else
-            @namespaces = namespace_children
-          end
-        end
-      end
+      render_subgroup
     end
 
     private
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index a6d2a22e97..05f035ec51 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -15,6 +15,14 @@ def index
 
   def show
     authorize! @group, to: :read?
+
+    @q = if flat_list_requested?
+           namespace_descendants.ransack(params[:q])
+         else
+           namespace_children.ransack(params[:q])
+         end
+    set_default_sort
+    @pagy, @namespaces = pagy(@q.result.include_route)
   end
 
   def new
@@ -105,6 +113,14 @@ def transfer # rubocop:disable Metrics/AbcSize
 
   private
 
+  def set_default_sort
+    @q.sorts = 'created_at desc' if @q.sorts.empty?
+  end
+
+  def flat_list_requested?
+    params.dig(:q, :name_or_puid_cont).present?
+  end
+
   def parent_group
     @group = Group.find(params[:parent_id]) if params[:parent_id]
   end
@@ -130,6 +146,11 @@ def namespace_children
     )
   end
 
+  def namespace_descendants
+    @group.self_and_descendants_of_type([Namespaces::ProjectNamespace.sti_name,
+                                         Group.sti_name])
+  end
+
   def resolve_layout
     case action_name
     when 'show', 'edit', 'update', 'activity'
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index f6b97d6ae9..ea7f9a54c2 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -150,6 +150,13 @@ def self_and_descendants
     self.class.joins(:route).where(route_path.matches_any([full_path, "#{full_path}/%"]))
   end
 
+  def self_and_descendants_of_type(types)
+    route_path = Route.arel_table[:path]
+
+    Namespace.joins(:route).where(route_path.matches_any([full_path, "#{full_path}/%"]))
+             .where(type: types)
+  end
+
   def self_and_descendant_ids
     self_and_descendants.as_ids
   end
diff --git a/app/views/dashboard/groups/group.turbo_stream.erb b/app/views/dashboard/groups/group.turbo_stream.erb
index d06b262263..544625995b 100644
--- a/app/views/dashboard/groups/group.turbo_stream.erb
+++ b/app/views/dashboard/groups/group.turbo_stream.erb
@@ -4,6 +4,7 @@
     children: @children,
     collapsed: @collapsed,
     path: "dashboard_groups_path",
-    type: [Group.sti_name]
+    type: [Group.sti_name],
+    icon_size: :medium,
   ) %>
 <% end %>
diff --git a/app/views/dashboard/groups/index.html.erb b/app/views/dashboard/groups/index.html.erb
index c3dbd7f03c..b88e3b1a67 100644
--- a/app/views/dashboard/groups/index.html.erb
+++ b/app/views/dashboard/groups/index.html.erb
@@ -15,7 +15,6 @@
   >
     <div class="flex flex-row items-center ml-auto space-x-2 font-normal">
       <%= search_form_for @q, url: dashboard_groups_url, html: { "data-controller": "filters", "data-turbo-permanent": "true" } do |f| %>
-        <%= f.hidden_field :format, value: "turbo_stream" %>
         <%= f.label :name_or_puid_cont, "SEARCH", class: "sr-only" %>
         <div class="relative lg:w-72">
           <div
@@ -56,6 +55,7 @@
         namespaces: @groups,
         path: "dashboard_groups_path",
         render_flat_list: @render_flat_list,
+        icon_size: :medium,
       ) %>
       <div class="flex flex-row-reverse">
         <%= render Viral::Pagy::PaginationComponent.new(
diff --git a/app/views/groups/show.html.erb b/app/views/groups/show.html.erb
index 0f82030447..1b04a85729 100644
--- a/app/views/groups/show.html.erb
+++ b/app/views/groups/show.html.erb
@@ -1,19 +1,19 @@
 <%= render Viral::PageHeaderComponent.new(title: @group.name, id: @group.puid, subtitle: @group.description) do |component| %>
   <%= component.icon do %>
     <%= viral_avatar(
-          name: @group.name,
-          colour_string: "#{@group.name}-#{@group.id}",
-          size: :large
-        ) %>
+      name: @group.name,
+      colour_string: "#{@group.name}-#{@group.id}",
+      size: :large,
+    ) %>
   <% end %>
   <%= component.with_buttons do %>
     <% if allowed_to?(:new?, @group) %>
       <%= link_to t(:"groups.show.create_subgroup_button"),
-                  new_group_path(parent_id: @group.id),
-                  class: "button button--size-default button--state-default" %>
+      new_group_path(parent_id: @group.id),
+      class: "button button--size-default button--state-default" %>
       <%= link_to t(:"groups.show.create_project_button"),
-                  new_project_path(group_id: @group.id),
-                  class: "button button--size-default button--state-primary ml-2" %>
+      new_project_path(group_id: @group.id),
+      class: "button button--size-default button--state-primary ml-2" %>
     <% end %>
   <% end %>
 <% end %>
@@ -23,44 +23,58 @@
     <%= t(:".tabs.subgroups_and_projects") %>
   <% end %>
   <%= tabs.with_tab(url: group_path(@group, tab: "shared_namespaces"), controls: "group-projects", selected: @tab == "shared_namespaces") do %>
-    <%= t(:'.tabs.shared_namespaces') %>
+    <%= t(:".tabs.shared_namespaces") %>
   <% end %>
 
   <%= tabs.with_tab_content do %>
-    <%= turbo_frame_tag "group_show_tab_content", "data-turbo-temporary": true, src: (
-      if @tab == "shared_namespaces"
-        group_shared_namespaces_path(@group, format: :turbo_stream)
-      else
-        group_subgroups_path(@group, format: :turbo_stream)
-      end
-    ) do %>
-      <table class="min-w-full table-fixed dark:divide-slate-600">
-        <tbody
-          class="
-            bg-white
-            divide-y
-            divide-slate-200
-            dark:bg-slate-800
-            dark:divide-slate-700
-          "
-        >
-        <% 10.times do %>
-          <tr>
+    <div class="mt-2">
+      <% if @tab == "shared_namespaces" %>
+        <%= turbo_frame_tag "group_show_tab_content", "data-turbo-temporary": true, src: group_shared_namespaces_path(@group, format: :turbo_stream) do %>
+          <table class="min-w-full table-fixed dark:divide-slate-600">
+            <tbody
+              class="
+                bg-white divide-y divide-slate-200 dark:bg-slate-800 dark:divide-slate-700
+              "
+            >
+              <% 10.times do %>
+                <tr>
 
-            <td class="p-4 animate-pulse">
-              <div class="flex-1 py-1 space-y-6">
-                <div class="space-y-3">
-                  <div class="w-48 h-2 rounded bg-slate-200"></div>
-                  <div class="w-32 h-2 rounded bg-slate-200"></div>
-                </div>
-              </div>
-            </td>
+                  <td class="p-4 animate-pulse">
+                    <div class="flex-1 py-1 space-y-6">
+                      <div class="space-y-3">
+                        <div class="w-48 h-2 rounded bg-slate-200"></div>
+                        <div class="w-32 h-2 rounded bg-slate-200"></div>
+                      </div>
+                    </div>
+                  </td>
 
-          </tr>
+                </tr>
+              <% end %>
+            </tbody>
+          </table>
         <% end %>
-        </tbody>
-      </table>
-    <% end %>
+      <% else %>
+        <div class="flex flex-row-reverse mb-2">
+          <%= search_form_for @q, url: group_path(@group), html: { "data-controller": "filters" } do |f| %>
+            <%= f.label :name_or_puid_cont, t("general.search.name_puid"), class: "sr-only" %>
+            <div class="relative lg:w-72">
+              <div
+                class="
+                  absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none
+                "
+              >
+                <%= viral_icon(name: "magnifying_glass", classes: "h-5 w-5") %>
+              </div>
+              <%= f.search_field :name_or_puid_cont,
+                             "data-action": "filters#submit",
+                             class:
+                               "block w-full p-2.5 pl-10 text-sm text-slate-900 border border-slate-300 rounded-lg bg-slate-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-slate-700 dark:border-slate-600 dark:placeholder-slate-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500",
+                             placeholder: t("general.search.name_puid") %>
+            </div>
+          <% end %>
+        </div>
+        <%= render "groups/subgroups/index", locals: { namespaces: @namespaces } %>
+      <% end %>
+    </div>
   <% end %>
 <% end %>
-
diff --git a/app/views/groups/subgroups/_index.html.erb b/app/views/groups/subgroups/_index.html.erb
new file mode 100644
index 0000000000..1ba13fbc81
--- /dev/null
+++ b/app/views/groups/subgroups/_index.html.erb
@@ -0,0 +1,20 @@
+<div class="space-y-2">
+
+  <% if @namespaces.length > 0 %>
+    <%= render NamespaceTreeContainerComponent.new(
+      namespaces: @namespaces,
+      path: "group_subgroups_path",
+      path_args: {
+        group_id: @group.full_path,
+      },
+      type: [Group.sti_name, Namespaces::ProjectNamespace.sti_name],
+    ) %>
+    <%= render Viral::Pagy::FullComponent.new(@pagy, item: "Subgroups and projects") %>
+  <% else %>
+    <%= viral_empty(
+      title: t(:"groups.show.subgroups.no_subgroups.title"),
+      description: t(:"groups.show.subgroups.no_subgroups.description"),
+      icon_name: :squares_2x2,
+    ) %>
+  <% end %>
+</div>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2159877c69..73369e4564 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -580,6 +580,8 @@ en:
         project: Create new project
     screen_reader:
       close: Close
+    search:
+      name_puid: Search by name or ID
   groups:
     activity:
       title: Group activity
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index ef9f21b03b..525d6a40b8 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -580,6 +580,8 @@ fr:
         project: Create new project
     screen_reader:
       close: Close
+    search:
+      name_puid: Search by name or ID
   groups:
     activity:
       title: Group activity
diff --git a/test/controllers/groups/subgroups_controller_test.rb b/test/controllers/groups/subgroups_controller_test.rb
index 4883ea56b7..4f4f062b9e 100644
--- a/test/controllers/groups/subgroups_controller_test.rb
+++ b/test/controllers/groups/subgroups_controller_test.rb
@@ -8,12 +8,6 @@ class SubgroupsControllerTest < ActionDispatch::IntegrationTest
       sign_in users(:john_doe)
       @group = groups(:group_one)
     end
-
-    test 'should redirect to groups index when html format requested' do
-      get group_subgroups_url(@group)
-      assert_redirected_to group_url(@group)
-    end
-
     test 'should get fragment of namesapce tree' do
       get group_subgroups_url(@group), params: { parent_id: @group.id, format: :turbo_stream }
       assert_response :ok
diff --git a/test/system/groups_test.rb b/test/system/groups_test.rb
index 7624e6ff87..2fa507d9f2 100644
--- a/test/system/groups_test.rb
+++ b/test/system/groups_test.rb
@@ -132,11 +132,9 @@ def setup
     click_on I18n.t('groups.sidebar.settings')
     click_link I18n.t('groups.sidebar.general')
 
-    within all('form[action="/group-1"]')[0] do
-      fill_in I18n.t('activerecord.attributes.group.name'), with: group_name
-      fill_in I18n.t('activerecord.attributes.group.description'), with: group_description
-      click_on I18n.t('groups.edit.details.submit')
-    end
+    fill_in I18n.t('activerecord.attributes.group.name'), with: group_name
+    fill_in I18n.t('activerecord.attributes.group.description'), with: group_description
+    click_on I18n.t('groups.edit.details.submit')
 
     assert_text I18n.t('groups.update.success', group_name:)
 
@@ -389,7 +387,9 @@ def setup
     assert_selector 'h1', text: @group.name
 
     assert_selector 'a.active', text: I18n.t(:'groups.show.tabs.subgroups_and_projects')
-    assert_selector 'li.namespace-entry', count: 21
+    assert_selector 'li.namespace-entry', count: 20
+    click_on I18n.t(:'components.pagination.next')
+    assert_selector 'li.namespace-entry', count: 1
 
     click_on I18n.t(:'groups.show.tabs.shared_namespaces')
     assert_selector 'a.active', text: I18n.t(:'groups.show.tabs.shared_namespaces')
@@ -403,4 +403,12 @@ def setup
     assert_selector 'div.namespace-entry-contents', count: 0
     assert_text I18n.t('groups.show.shared_namespaces.no_shared.title')
   end
+
+  test 'search subgroups and projects' do
+    @group = groups(:group_one)
+    visit group_url(@group)
+    assert_text I18n.t(:'components.pagination.next')
+    fill_in I18n.t('general.search.name_puid'), with: 'project 2'
+    assert_selector 'li.namespace-entry', count: 5
+  end
 end