From a09d0d8ccc65628c3f15c50e6630f6e8b5399b7c Mon Sep 17 00:00:00 2001
From: Ravi Kumar <rkumar@gitlab.com>
Date: Wed, 12 Jul 2023 22:36:20 +0000
Subject: [PATCH] Add the logic for searching and showing group wiki search

Now we are indexing the group wikis also so this MR implements
the searching and displaying group wikis on the results page.

Changelog: added
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123522
EE: true
---
 .rubocop_todo/search/namespaced_class.yml     |  1 +
 app/helpers/search_helper.rb                  |  4 ++
 app/views/search/results/_wiki_blob.html.haml |  5 +-
 doc/user/project/wiki/group.md                |  1 -
 doc/user/search/advanced_search.md            |  2 +-
 doc/user/search/index.md                      |  2 +-
 ee/app/helpers/ee/search_helper.rb            |  7 +++
 ee/app/models/ee/group.rb                     |  6 +++
 ee/lib/ee/gitlab/search/found_wiki_page.rb    | 19 +++++++
 ee/lib/elastic/latest/git_class_proxy.rb      | 48 +++++++++++++----
 ee/lib/elastic/latest/wiki_class_proxy.rb     |  9 ++++
 ee/lib/gitlab/elastic/search_results.rb       | 40 ++++++++++----
 .../search/elastic/global_search_spec.rb      | 20 ++++---
 .../search/elastic/group_search_spec.rb       | 16 +++---
 ee/spec/helpers/search_helper_spec.rb         |  9 ++++
 .../ee/gitlab/search/found_wiki_page_spec.rb  | 29 ++++++++++
 ee/spec/lib/elastic/latest/routing_spec.rb    |  2 +-
 .../lib/gitlab/elastic/search_results_spec.rb | 53 ++++++++++++++++++-
 ee/spec/models/ee/group_spec.rb               | 30 +++++++++++
 lib/gitlab/search/found_blob.rb               |  5 +-
 lib/gitlab/search/found_wiki_page.rb          |  5 +-
 spec/helpers/search_helper_spec.rb            |  9 ++++
 .../lib/gitlab/search/found_wiki_page_spec.rb |  2 +-
 23 files changed, 277 insertions(+), 47 deletions(-)
 create mode 100644 ee/lib/ee/gitlab/search/found_wiki_page.rb
 create mode 100644 ee/spec/lib/ee/gitlab/search/found_wiki_page_spec.rb

diff --git a/.rubocop_todo/search/namespaced_class.yml b/.rubocop_todo/search/namespaced_class.yml
index e80b4de305a7..53dae6519596 100644
--- a/.rubocop_todo/search/namespaced_class.yml
+++ b/.rubocop_todo/search/namespaced_class.yml
@@ -81,6 +81,7 @@ Search/NamespacedClass:
     - 'ee/lib/api/elasticsearch_indexed_namespaces.rb'
     - 'ee/lib/ee/api/helpers/search_helpers.rb'
     - 'ee/lib/ee/gitlab/group_search_results.rb'
+    - 'ee/lib/ee/gitlab/search/found_wiki_page.rb'
     - 'ee/lib/ee/gitlab/search/parsed_query.rb'
     - 'ee/lib/ee/gitlab/search_context.rb'
     - 'ee/lib/ee/gitlab/search_results.rb'
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index be0a1318f4b5..1dd949630c27 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -657,6 +657,10 @@ def sanitized_search_params
 
     sanitized_params
   end
+
+  def wiki_blob_link(wiki_blob)
+    project_wiki_path(wiki_blob.project, wiki_blob.basename)
+  end
 end
 
 SearchHelper.prepend_mod_with('SearchHelper')
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index d6900c397a05..08d8ffcf2509 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,9 +1,6 @@
-- project = wiki_blob.project
-- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
-
 %div{ class: 'search-result-row gl-pb-3! gl-mt-5 gl-mb-0!' }
   %span.gl-display-flex.gl-align-items-center
-    = link_to wiki_blob_link, data: { track_action: 'click_text', track_label: "wiki_title", track_property: 'search_result' }, class: 'gl-w-full' do
+    = link_to wiki_blob_link(wiki_blob), data: { track_action: 'click_text', track_label: "wiki_title", track_property: 'search_result' }, class: 'gl-w-full' do
       %span.term.str-truncated.gl-font-weight-bold= ::Wiki.canonicalize_filename(wiki_blob.path)
   .description.term.col-sm-10.gl-px-0
     = simple_search_highlight_and_truncate(wiki_blob.data, @search_term)
diff --git a/doc/user/project/wiki/group.md b/doc/user/project/wiki/group.md
index 2271c33b5b43..41fd7e81db50 100644
--- a/doc/user/project/wiki/group.md
+++ b/doc/user/project/wiki/group.md
@@ -14,7 +14,6 @@ to ensure all group members have the correct access permissions to contribute.
 Group wikis are similar to [project wikis](index.md), with a few limitations:
 
 - [Git LFS](../../../topics/git/lfs/index.md) is not supported.
-- Group wikis are not included in [global search](../../search/advanced_search.md).
 - Changes to group wikis don't show up in the [group's activity feed](../../group/manage.md#group-activity-analytics).
 
 For updates, follow [the epic that tracks feature parity with project wikis](https://gitlab.com/groups/gitlab-org/-/epics/2782).
diff --git a/doc/user/search/advanced_search.md b/doc/user/search/advanced_search.md
index 1444e5385f94..5a9f75f1d6cf 100644
--- a/doc/user/search/advanced_search.md
+++ b/doc/user/search/advanced_search.md
@@ -27,7 +27,7 @@ You can use advanced search in:
 - Code
 - Comments
 - Commits
-- Project wikis (not [group wikis](../project/wiki/group.md))
+- Project and group wikis
 
 ## Enable advanced search
 
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 599ac60078f2..46ec0bf62737 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -27,7 +27,7 @@ can limit the search scope by disabling the following [`ops` feature flags](../.
 | Issues | `global_search_issues_tab` | When enabled, global search includes issues. |
 | Merge requests | `global_search_merge_requests_tab` | When enabled, global search includes merge requests. |
 | Users | `global_search_users_tab` | When enabled, global search includes users. |
-| Wiki | `global_search_wiki_tab` | When enabled, global search includes project wikis (not [group wikis](../project/wiki/group.md)). |
+| Wiki | `global_search_wiki_tab` | When enabled, global search includes project and group wikis. |
 
 All global search scopes are enabled by default on self-managed instances.
 
diff --git a/ee/app/helpers/ee/search_helper.rb b/ee/app/helpers/ee/search_helper.rb
index bf865a198e31..13721f6c706c 100644
--- a/ee/app/helpers/ee/search_helper.rb
+++ b/ee/app/helpers/ee/search_helper.rb
@@ -158,6 +158,13 @@ def search_scope
       end
     end
 
+    override :wiki_blob_link
+    def wiki_blob_link(wiki_blob)
+      return group_wiki_path(wiki_blob.group, wiki_blob.basename) if wiki_blob.group_level_blob
+
+      super
+    end
+
     private
 
     def recent_epics_autocomplete(term)
diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb
index 5ac2e55acab5..7c74a13fdf91 100644
--- a/ee/app/models/ee/group.rb
+++ b/ee/app/models/ee/group.rb
@@ -832,6 +832,12 @@ def reached_project_access_token_limit?
       actual_limits.exceeded?(:project_access_token_limit, active_project_tokens_of_root_ancestor)
     end
 
+    def pending_delete?
+      return false unless deletion_schedule
+
+      deletion_schedule.marked_for_deletion_on.future?
+    end
+
     private
 
     def active_project_tokens_of_root_ancestor
diff --git a/ee/lib/ee/gitlab/search/found_wiki_page.rb b/ee/lib/ee/gitlab/search/found_wiki_page.rb
new file mode 100644
index 000000000000..a8b820a7b425
--- /dev/null
+++ b/ee/lib/ee/gitlab/search/found_wiki_page.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module EE
+  module Gitlab
+    module Search
+      module FoundWikiPage
+        extend ::Gitlab::Utils::Override
+        attr_reader :wiki
+
+        # @param found_blob [Gitlab::Search::FoundBlob]
+        override :initialize
+        def initialize(found_blob)
+          @wiki = found_blob.group.wiki if found_blob.group_level_blob
+          super
+        end
+      end
+    end
+  end
+end
diff --git a/ee/lib/elastic/latest/git_class_proxy.rb b/ee/lib/elastic/latest/git_class_proxy.rb
index c4f2407285df..2ffd56706bbe 100644
--- a/ee/lib/elastic/latest/git_class_proxy.rb
+++ b/ee/lib/elastic/latest/git_class_proxy.rb
@@ -38,8 +38,8 @@ def elastic_search_as_found_blob(query, page: 1, per: 20, options: {}, preload_m
         options = options.merge(highlight: true)
 
         elastic_search_and_wrap(query, type: es_type, page: page, per: per, options: options,
-          preload_method: preload_method) do |result, project|
-          ::Gitlab::Elastic::SearchResults.parse_search_result(result, project)
+          preload_method: preload_method) do |result, container|
+          ::Gitlab::Elastic::SearchResults.parse_search_result(result, container)
         end
       end
 
@@ -187,23 +187,25 @@ def elastic_search_and_wrap(query, type:, page: 1, per: 20, options: {}, preload
       end
 
       def yield_each_search_result(response, type, preload_method)
-        # Avoid one SELECT per result by loading all projects into a hash
+        group_ids = group_ids_from_wiki_response(type, response)
+        group_containers = Group.with_route.id_in(group_ids).includes(:deletion_schedule) # rubocop: disable CodeReuse/ActiveRecord
         project_ids = response.map { |result| project_id_for_commit_or_blob(result, type) }.uniq
-        projects = Project.with_route.id_in(project_ids)
-        projects = projects.public_send(preload_method) if preload_method # rubocop:disable GitlabSecurity/PublicSend
-        projects = projects.index_by(&:id)
+        # Avoid one SELECT per result by loading all projects into a hash
+        project_containers = Project.with_route.id_in(project_ids)
+        project_containers = project_containers.public_send(preload_method) if preload_method # rubocop:disable GitlabSecurity/PublicSend
+        containers = project_containers + group_containers
+        containers = containers.index_by { |container| "#{container.class.name.downcase}_#{container.id}" }
         total_count = response.total_count
 
         items = response.map do |result|
-          project_id = project_id_for_commit_or_blob(result, type)
-          project = projects[project_id]
+          container = get_container_from_containers_hash(type, result, containers)
 
-          if project.nil? || project.pending_delete?
+          if container.nil? || container.pending_delete?
             total_count -= 1
             next
           end
 
-          yield(result, project)
+          yield(result, container)
         end
 
         # Remove results for deleted projects
@@ -212,11 +214,37 @@ def yield_each_search_result(response, type, preload_method)
         [items, total_count]
       end
 
+      def group_ids_from_wiki_response(type, response)
+        return unless type.eql?('wiki_blob') && use_separate_wiki_index?
+
+        response.map { |result| group_id_for_wiki_blob(result) }
+      end
+
+      def get_container_from_containers_hash(type, result, containers)
+        if group_level_wiki_result?(result)
+          group_id = group_id_for_wiki_blob(result)
+          containers["group_#{group_id}"]
+        else
+          project_id = project_id_for_commit_or_blob(result, type)
+          containers["project_#{project_id}"]
+        end
+      end
+
+      def group_level_wiki_result?(result)
+        return false unless use_separate_wiki_index?
+
+        result['_source']['type'].eql?('wiki_blob') && result['_source']['rid'].match(/wiki_group_\d+/)
+      end
+
       # Indexed commit does not include project_id
       def project_id_for_commit_or_blob(result, type)
         (result.dig('_source', 'project_id') || result.dig('_source', type, 'rid') || result.dig('_source', 'rid')).to_i
       end
 
+      def group_id_for_wiki_blob(result)
+        result.dig('_source', 'group_id')
+      end
+
       def apply_simple_query_string(name:, fields:, query:, bool_expr:, count_only:)
         fields = remove_fields_boost(fields) if count_only
 
diff --git a/ee/lib/elastic/latest/wiki_class_proxy.rb b/ee/lib/elastic/latest/wiki_class_proxy.rb
index 8c05100fb232..abe94e41c4c5 100644
--- a/ee/lib/elastic/latest/wiki_class_proxy.rb
+++ b/ee/lib/elastic/latest/wiki_class_proxy.rb
@@ -4,6 +4,7 @@ module Elastic
   module Latest
     class WikiClassProxy < ApplicationClassProxy
       include GitClassProxy
+      include Routing
 
       def es_type
         'wiki_blob'
@@ -14,6 +15,14 @@ def elastic_search_as_wiki_page(*args, **kwargs)
           Gitlab::Search::FoundWikiPage.new(blob)
         end
       end
+
+      # Disable the routing for group level search
+      # Will be enabled from MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125137
+      def routing_options(options)
+        return {} if options[:group_ids].present?
+
+        super
+      end
     end
   end
 end
diff --git a/ee/lib/gitlab/elastic/search_results.rb b/ee/lib/gitlab/elastic/search_results.rb
index dc19d932f480..4f60266d5994 100644
--- a/ee/lib/gitlab/elastic/search_results.rb
+++ b/ee/lib/gitlab/elastic/search_results.rb
@@ -177,12 +177,21 @@ def milestones_count
       alias_method :limited_merge_requests_count, :merge_requests_count
       alias_method :limited_milestones_count, :milestones_count
 
-      def self.parse_search_result(result, project)
+      def self.parse_search_result(result, container)
         ref = extract_ref_from_result(result['_source'])
         path = extract_path_from_result(result['_source'])
         basename = File.join(File.dirname(path), File.basename(path, '.*'))
         content = extract_content_from_result(result['_source'])
-        project_id = result['_source']['project_id'].to_i
+        group_id = result['_source']['group_id']&.to_i
+        if group_level_result?(result['_source'])
+          group = container
+          group_level_blob = true
+        else
+          project = container
+          group = container.group
+          project_id = result['_source']['project_id'].to_i
+        end
+
         total_lines = content.lines.size
 
         highlight_content = get_highlight_content(result)
@@ -216,15 +225,20 @@ def self.parse_search_result(result, project)
         highlight_line = highlight_found ? found_line_number + 1 : nil
 
         ::Gitlab::Search::FoundBlob.new(
-          path: path,
-          basename: basename,
-          ref: ref,
-          matched_lines_count: matched_lines_count,
-          startline: from + 1,
-          highlight_line: highlight_line,
-          data: data.join,
-          project: project,
-          project_id: project_id
+          {
+            path: path,
+            basename: basename,
+            ref: ref,
+            matched_lines_count: matched_lines_count,
+            startline: from + 1,
+            highlight_line: highlight_line,
+            data: data.join,
+            project: project,
+            project_id: project_id,
+            group: group,
+            group_id: group_id,
+            group_level_blob: group_level_blob
+          }.compact
         )
       end
 
@@ -424,6 +438,10 @@ def self.extract_content_from_result(source)
         use_separate_wiki_index?(source['type']) ? source['content'] : source['blob']['content']
       end
 
+      def self.group_level_result?(source)
+        source['project_id'].blank?
+      end
+
       def self.get_highlight_content(result)
         content_key = use_separate_wiki_index?(result['_source']['type']) ? 'content' : 'blob.content'
         result.dig('highlight', content_key)&.first || ''
diff --git a/ee/spec/features/search/elastic/global_search_spec.rb b/ee/spec/features/search/elastic/global_search_spec.rb
index 500ae661211d..9873809a49fd 100644
--- a/ee/spec/features/search/elastic/global_search_spec.rb
+++ b/ee/spec/features/search/elastic/global_search_spec.rb
@@ -94,21 +94,27 @@
   end
 
   describe 'I search through the wiki blobs' do
-    before do
-      project.wiki.create_page('test.md', '# term')
-      project.wiki.index_wiki_blobs
+    include WikiHelpers
+    let_it_be(:group_wiki) { create(:group_wiki) }
 
+    before do
+      stub_group_wikis(true)
+      group_wiki.container.add_maintainer(user)
+      [group_wiki, project.wiki].each do |wiki|
+        wiki.create_page('test.md', '# term')
+        wiki.index_wiki_blobs
+      end
       ensure_elasticsearch_index!
     end
 
-    it "finds wiki pages" do
+    it 'finds wiki pages from groups and projects' do
       visit dashboard_projects_path
 
       submit_search('term')
       select_search_scope('Wiki')
 
-      expect(page).to have_selector('.search-result-row .description', text: 'term')
-      expect(page).to have_link('test')
+      expect(page).to have_selector('.search-result-row .description', text: 'term').twice
+      expect(page).to have_link('test').twice
     end
   end
 
@@ -118,7 +124,7 @@
       ensure_elasticsearch_index!
     end
 
-    it "finds commits" do
+    it 'finds commits' do
       visit dashboard_projects_path
 
       submit_search('add')
diff --git a/ee/spec/features/search/elastic/group_search_spec.rb b/ee/spec/features/search/elastic/group_search_spec.rb
index 4ea4be25cdf7..1ffdf68975b9 100644
--- a/ee/spec/features/search/elastic/group_search_spec.rb
+++ b/ee/spec/features/search/elastic/group_search_spec.rb
@@ -65,21 +65,25 @@ def choose_group(group)
   end
 
   describe 'wiki search' do
+    include WikiHelpers
     let(:wiki) { ProjectWiki.new(project, user) }
+    let(:group_wiki) { create(:group_wiki, group: group) }
 
     before do
-      wiki.create_page('test.md', '# term')
-      wiki.index_wiki_blobs
-
+      stub_group_wikis(true)
+      [group_wiki, wiki].each do |w|
+        w.create_page('test.md', '# term')
+        w.index_wiki_blobs
+      end
       ensure_elasticsearch_index!
     end
 
-    it 'finds wiki pages' do
+    it 'finds Project and Group wiki pages' do
       submit_search('term')
       select_search_scope('Wiki')
 
-      expect(page).to have_selector('.search-result-row .description', text: 'term')
-      expect(page).to have_link('test')
+      expect(page).to have_selector('.search-result-row .description', text: 'term').twice
+      expect(page).to have_link('test').twice
     end
   end
 
diff --git a/ee/spec/helpers/search_helper_spec.rb b/ee/spec/helpers/search_helper_spec.rb
index 1ab778a53375..aec7c4bf66c0 100644
--- a/ee/spec/helpers/search_helper_spec.rb
+++ b/ee/spec/helpers/search_helper_spec.rb
@@ -431,4 +431,13 @@
       expect(search_service.show_elasticsearch_tabs?).to eq(true)
     end
   end
+
+  describe '#wiki_blob_link' do
+    let_it_be(:group) { create(:group, :wiki_repo) }
+    let(:wiki_blob) do
+      Gitlab::Search::FoundBlob.new({ path: 'test', basename: 'test', ref: 'master', data: 'foo', startline: 2, group: group, group_id: group.id, group_level_blob: true })
+    end
+
+    it { expect(wiki_blob_link(wiki_blob)).to eq("/groups/#{group.path}/-/wikis/#{wiki_blob.path}") }
+  end
 end
diff --git a/ee/spec/lib/ee/gitlab/search/found_wiki_page_spec.rb b/ee/spec/lib/ee/gitlab/search/found_wiki_page_spec.rb
new file mode 100644
index 000000000000..10b17bafb3c1
--- /dev/null
+++ b/ee/spec/lib/ee/gitlab/search/found_wiki_page_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Search::FoundWikiPage, feature_category: :global_search do
+  describe '.initialize' do
+    subject { found_wiki_page.wiki }
+
+    let(:wiki_blob) { Gitlab::Search::FoundBlob.new(wiki_blob_params) }
+    let(:found_wiki_page) { described_class.new(wiki_blob) }
+    let(:wiki_blob_base_params) do
+      { path: 'test', basename: 'test', ref: 'master', data: "foo", startline: 2 }
+    end
+
+    context 'when found_blob is not a group_level_blob' do
+      let_it_be(:project) { create :project, :wiki_repo }
+      let(:wiki_blob_params) { wiki_blob_base_params.merge(project: project, project_id: project.id) }
+
+      it { is_expected.to be_an_instance_of(ProjectWiki) }
+    end
+
+    context 'when found_blob is a group_level_blob' do
+      let_it_be(:group) { create(:group, :wiki_repo) }
+      let(:wiki_blob_params) { wiki_blob_base_params.merge(group: group, group_id: group.id, group_level_blob: true) }
+
+      it { is_expected.to be_an_instance_of(GroupWiki) }
+    end
+  end
+end
diff --git a/ee/spec/lib/elastic/latest/routing_spec.rb b/ee/spec/lib/elastic/latest/routing_spec.rb
index 14f62f5af4f6..31156fa43d7b 100644
--- a/ee/spec/lib/elastic/latest/routing_spec.rb
+++ b/ee/spec/lib/elastic/latest/routing_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe Elastic::Latest::Routing do
+RSpec.describe Elastic::Latest::Routing, feature_category: :global_search do
   let(:proxified_class) { Issue }
   let(:included_class) { Elastic::Latest::ApplicationClassProxy }
   let(:project_ids) { [1, 2, 3] }
diff --git a/ee/spec/lib/gitlab/elastic/search_results_spec.rb b/ee/spec/lib/gitlab/elastic/search_results_spec.rb
index ec1899aa69a5..d690085ee51f 100644
--- a/ee/spec/lib/gitlab/elastic/search_results_spec.rb
+++ b/ee/spec/lib/gitlab/elastic/search_results_spec.rb
@@ -132,12 +132,12 @@
   end
 
   describe 'parse_search_result' do
-    let(:project) { double(:project) }
+    let_it_be(:project) { create(:project) }
     let(:content) { "foo\nbar\nbaz\n" }
     let(:path) { 'path/file.ext' }
     let(:source) do
       {
-        'project_id' => 1,
+        'project_id' => project.id,
         'blob' => {
           'commit_sha' => 'sha',
           'content' => content,
@@ -276,6 +276,30 @@
         end
       end
     end
+
+    context 'when blob is a group level result' do
+      before do
+        set_elasticsearch_migration_to(:migrate_wikis_to_separate_index, including: true)
+      end
+
+      let_it_be(:group) { create(:group) }
+      let_it_be(:source) do
+        {
+          'type' => 'wiki_blob',
+          'group_id' => group.id,
+          'commit_sha' => 'sha',
+          'content' => 'Test',
+          'path' => 'home.md'
+        }
+      end
+
+      it 'returns an instance of Gitlab::Search::FoundBlob with group_level_blob as true' do
+        parsed = described_class.parse_search_result({ '_source' => source }, group)
+
+        expect(parsed).to be_kind_of(::Gitlab::Search::FoundBlob)
+        expect(parsed).to have_attributes(group: group, project: nil, group_level_blob: true)
+      end
+    end
   end
 
   describe 'issues' do
@@ -1139,6 +1163,31 @@ def search_for(term)
         it { is_expected.to be_empty }
       end
     end
+
+    context 'for group wiki' do
+      let_it_be(:sub_group) { create(:group, :nested) }
+      let_it_be(:sub_group_wiki) { create(:group_wiki, group: sub_group) }
+      let_it_be(:parent_group) { sub_group.parent }
+      let_it_be(:parent_group_wiki) { create(:group_wiki, group: parent_group) }
+      let_it_be(:group_project) { create(:project, :public, :in_group) }
+      let_it_be(:group_project_wiki) { create(:project_wiki, project: group_project, user: user) }
+
+      before do
+        set_elasticsearch_migration_to(:migrate_wikis_to_separate_index, including: true)
+        [parent_group_wiki, sub_group_wiki, group_project_wiki].each do |wiki|
+          wiki.create_page('index_page', 'term')
+          wiki.index_wiki_blobs
+        end
+        ElasticWikiIndexerWorker.new.perform(project_1.id, project_1.class.name, force: true)
+        ensure_elasticsearch_index!
+      end
+
+      it 'includes all the wikis from groups, subgroups, projects and projects within the group' do
+        expect(results.wiki_blobs_count).to eq 4
+        wiki_containers = wiki_blobs.filter_map { |blob| blob.group_level_blob ? blob.group : blob.project }.uniq
+        expect(wiki_containers).to contain_exactly(parent_group, sub_group, group_project, project_1)
+      end
+    end
   end
 
   describe 'commits', :sidekiq_inline do
diff --git a/ee/spec/models/ee/group_spec.rb b/ee/spec/models/ee/group_spec.rb
index 44842d083f40..dd1ca64b930e 100644
--- a/ee/spec/models/ee/group_spec.rb
+++ b/ee/spec/models/ee/group_spec.rb
@@ -2780,4 +2780,34 @@ def webhook_headers
       end
     end
   end
+
+  describe '.pending_delete?' do
+    context 'when deletion_schedule is not present' do
+      it 'returns false' do
+        expect(group).not_to be_pending_delete
+      end
+    end
+
+    context 'when deletion_schedule is present' do
+      context 'when marked_for_deletion_on is from past' do
+        before do
+          create(:group_deletion_schedule, group: group, marked_for_deletion_on: 1.day.ago)
+        end
+
+        it 'returns false' do
+          expect(group).not_to be_pending_delete
+        end
+      end
+
+      context 'when marked_for_deletion_on is in future' do
+        before do
+          create(:group_deletion_schedule, group: group, marked_for_deletion_on: 2.days.from_now)
+        end
+
+        it 'returns true' do
+          expect(group).to be_pending_delete
+        end
+      end
+    end
+  end
 end
diff --git a/lib/gitlab/search/found_blob.rb b/lib/gitlab/search/found_blob.rb
index 79d6cfc84a3e..c9051b6a5ff2 100644
--- a/lib/gitlab/search/found_blob.rb
+++ b/lib/gitlab/search/found_blob.rb
@@ -9,7 +9,7 @@ class FoundBlob
       include Gitlab::Utils::StrongMemoize
       include BlobActiveModel
 
-      attr_reader :project, :content_match, :blob_path, :highlight_line, :matched_lines_count
+      attr_reader :project, :content_match, :blob_path, :highlight_line, :matched_lines_count, :group_level_blob, :group
 
       PATH_REGEXP = /\A(?<ref>[^:]*):(?<path>[^\x00]*)\x00/.freeze
       CONTENT_REGEXP = /^(?<ref>[^:]*):(?<path>[^\x00]*)\x00(?<startline>\d+)\x00/.freeze
@@ -31,14 +31,17 @@ def initialize(opts = {})
         @binary_data = opts.fetch(:data, nil)
         @per_page = opts.fetch(:per_page, 20)
         @project = opts.fetch(:project, nil)
+        @group = opts.fetch(:group, nil)
         # Some callers (e.g. Elasticsearch) do not have the Project object,
         # yet they can trigger many calls in one go,
         # causing duplicated queries.
         # Allow those to just pass project_id instead.
         @project_id = opts.fetch(:project_id, nil)
+        @group_id = opts.fetch(:group_id, nil)
         @content_match = opts.fetch(:content_match, nil)
         @blob_path = opts.fetch(:blob_path, nil)
         @repository = opts.fetch(:repository, nil)
+        @group_level_blob = opts.fetch(:group_level_blob, false)
       end
 
       def id
diff --git a/lib/gitlab/search/found_wiki_page.rb b/lib/gitlab/search/found_wiki_page.rb
index 99ca6a79fe2d..650bae2af4d9 100644
--- a/lib/gitlab/search/found_wiki_page.rb
+++ b/lib/gitlab/search/found_wiki_page.rb
@@ -14,7 +14,8 @@ def self.declarative_policy_class
       # @param found_blob [Gitlab::Search::FoundBlob]
       def initialize(found_blob)
         super
-        @wiki = found_blob.project.wiki
+
+        @wiki ||= found_blob.project.wiki
       end
 
       def to_ability_name
@@ -23,3 +24,5 @@ def to_ability_name
     end
   end
 end
+
+Gitlab::Search::FoundWikiPage.prepend_mod_with('Gitlab::Search::FoundWikiPage')
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 6634e7dbf27b..a71a8392e1c1 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -1454,4 +1454,13 @@ def simple_sanitize(str)
 
     it { is_expected.to eq(false) }
   end
+
+  describe '#wiki_blob_link' do
+    let_it_be(:project) { create :project, :wiki_repo }
+    let(:wiki_blob) do
+      Gitlab::Search::FoundBlob.new({ path: 'test', basename: 'test', ref: 'master', data: 'foo', startline: 2, project: project, project_id: project.id })
+    end
+
+    it { expect(wiki_blob_link(wiki_blob)).to eq("/#{project.namespace.path}/#{project.path}/-/wikis/#{wiki_blob.path}") }
+  end
 end
diff --git a/spec/lib/gitlab/search/found_wiki_page_spec.rb b/spec/lib/gitlab/search/found_wiki_page_spec.rb
index fc166ad38518..b84a27fa2cf1 100644
--- a/spec/lib/gitlab/search/found_wiki_page_spec.rb
+++ b/spec/lib/gitlab/search/found_wiki_page_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe Gitlab::Search::FoundWikiPage do
+RSpec.describe Gitlab::Search::FoundWikiPage, feature_category: :global_search do
   let(:project) { create(:project, :public, :repository) }
 
   describe 'policy' do
-- 
GitLab