Draft: [skip ci] Authorize group milestones in advanced search

What does this MR do and why?

Makes group milestones show up in advanced search for users who are allowed to see them. Closes #630618.

Depends on !260602 (merged), which fixes a crash in the combined filter. Until that merges and this branch is rebased, the existing milestone spec no results when the user cannot read cross project fails here.

Group milestones have been indexed since !257456 (merged), but they were never returned. Milestone search only had the project authorization filter. That filter needs issues_access_level and merge_requests_access_level, and group milestone documents do not have them.

Search::Elastic::MilestoneQueryBuilder now uses by_combined_search_level_and_membership:

  • The project branch is unchanged.
  • The group branch authorizes group milestones like GroupPolicy read_group, which grants read_milestone:
    • Public groups: everyone.
    • Internal groups: logged-in, non-external users.
    • Private groups: members (including inherited membership) and members of a project in the group (the has_projects rule).
    • Admins see all.
  • At group level, group milestones are scoped to the searched group and its subgroups, the same as group-level work items and the group milestones page. Basic search currently returns the group's own and its ancestors' group milestones instead; !260649 aligns it.
  • The group branch is behind a new gitlab_com_derisk feature flag, advanced_search_group_milestones (actor: current user, default off). Group milestones are only partly indexed until the backfill in #630857 runs.

ee/lib/gitlab/elastic/search_results.rb now preloads group: :route for milestone results. The result partial calls milestone_path and resource_parent.full_name.

Why the filter has a project_document_field option and a project_id guard

Group milestone documents store the group's visibility in visibility_level. Project milestone documents use the same field for the project's visibility.

Work items avoid this by using separate project_visibility_level and namespace_visibility_level fields. The main index mapping is dynamic: 'strict' and has no namespace_visibility_level.

Without a guard, the group branch would match project milestones by project visibility alone:

  • A non-member would see milestones of a public project whose issues and merge requests are private.
  • For admins, the exists visibility_level clause would match every document.

So the filter gained a project_document_field option. The group branch adds must_not exists project_id, named filters:group_documents_only.

I chose this over a new mapped field to avoid a mapping migration.

Why group searches are not routed by both n_<root> and project_<id>

The issue also asked to route group-level searches by both keys. This is not needed. Group and global search results do not pass project_ids, so those searches are not routed. They already reach all shards.

References

Screenshots or screen recordings

Not applicable. There is no UI change. Group milestones only appear in results once the flag is on.

Before After

How to set up and validate locally

  1. Enable advanced search.

  2. Create a public group with a milestone, and a private group with a milestone.

  3. In a Rails console, enable the flag:

    Feature.enable(:advanced_search_group_milestones)
  4. Search milestones as a non-member. You should see the public group's milestone only.

  5. Search milestones as a member of the private group. You should see both.

  6. Search milestones in a parent group that has a subgroup with its own milestone. You should see the subgroup's milestone. Searching the subgroup does not return the parent's milestone.

A group milestone created before the deploy of !257456 (merged) is not indexed yet. Index it in a Rails console:

Elastic::ProcessInitialBookkeepingService.track!(milestone)

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Edited by Ravi Kumar

Merge request reports

Loading
Loading