Draft: [skip ci] Search subgroup milestones in basic group search

What does this MR do and why?

Basic (non-Elasticsearch) group-level milestone search now returns group milestones from the searched group and its subgroups. It no longer returns group milestones from ancestor groups.

In Gitlab::GroupSearchResults#filter_milestones_by_group, group milestones now come from group.self_and_descendants instead of group.self_and_ancestors. This applies to both the archived and non-archived branches. non_archived still excludes a group when it or an ancestor is archived.

Project milestones are unchanged. They already came from projects in the group and its subgroups (Search::GroupService#projects uses for_group_and_its_subgroups).

Example: searching Group A, which has subgroup Group B and parent Group P.

Milestones of Before After
Group A yes yes
Group B (subgroup) no yes
Group P (parent) yes no
Projects in A and B yes yes

Project-level basic search is unchanged. It still includes the milestones of the project's group and ancestor groups, because a project can use those milestones.

Why change the scope

Searching a group should find what is in it. The new scope matches the rest of the product:

  • The group milestones page uses self_and_descendants (Groups::MilestonesController#group_ids). It only adds ancestors for the JSON milestone picker, where an ancestor's milestone can be assigned.
  • Group-level work items in basic search use include_descendants: true, include_ancestors: false (EE::Gitlab::GroupSearchResults#work_items).
  • Project milestones from subgroup projects were already in the results, so group milestones from subgroups were the odd one out.
  • Advanced search scopes group milestones the same way in !260603.
Impact on the MCP use case

The ancestor scope came from https://gitlab.com/gitlab-org/gitlab/-/work_items/589711 (commit d634d98e). That change was driven by the gitlab_search MCP tool and /api/v4/search?scope=milestones.

After this MR, an agent searching in a subgroup for a milestone to assign no longer gets ancestor group milestones. It needs to search the ancestor group instead.

The author of https://gitlab.com/gitlab-org/gitlab/-/work_items/589711 should confirm this is fine for the MCP use case.

Database

The group list now comes from self_and_descendants instead of self_and_ancestors. It can be larger for groups with many subgroups. A database reviewer should look at the query. Query plans will follow.

Spec

In spec/lib/gitlab/group_search_results_spec.rb, the example includes milestones from ancestor groups is replaced. The new example uses a parent / searched group / subgroup hierarchy, with projects in the searched group and in the subgroup. It expects the milestones of the searched group, the subgroup and both projects, and not the parent's. It fails on the old code (subgroup missing, parent present).

References

Screenshots or screen recordings

Not applicable. There is no UI change, only the content of the results list.

Before After

How to set up and validate locally

This is basic search, so Advanced Search must be disabled or the search type forced to basic.

  1. Create the groups Group P > Group A > Group B.
  2. Add a milestone to each group, all named e.g. release.
  3. Search milestones in Group A: /search?group_id=<A id>&scope=milestones&search=release&search_type=basic
  4. Expect the milestones of Group A and Group B. Do not expect the milestone of Group P.

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

Merge request reports

Loading
Loading