Add labels and milestone to the replacement MR overview

This MR adds a labels and milestone card to the merge request overview sidebar, under the reviewers panel. The card is hidden when the merge request has neither. It was split out of !252999 (merged) so each panel can be reviewed on its own.

  1. !252481 (merged) - flag, toggle, empty container (merged)
  2. !255057 (merged) - data layer (merged)
  3. !252997 (merged) - merge readiness (merged)
  4. !252998 (merged) - reviewers and recommended reviewers (merged)
  5. !252999 (merged) - description (merged)
  6. !257511 (merged) - labels and milestone (this MR)
  7. !257492 (merged) - risk assessment (stacked on this MR)
  8. !257832 (merged) - discussions (stacked on the risk assessment MR)

The card under the reviewers:

mr-257511-labels

Detailed context for AI agents

Chain context

!252999 (merged) (description) has merged, and this MR has been rebased onto master on top of it. The rebase conflicts were only in the files shared with !252999 (merged) - app.vue imports, the overview query, mock_data.js, app_spec.js, and the feature spec - and all were resolved by keeping both sides' changes.

The risk assessment MR (!257492 (merged)) is stacked on this MR, and the discussions MR (!257832 (merged)) is stacked on the risk assessment MR. Each is retargeted as the MR below it merges.

This card used to be part of !252999 (merged), alongside the description panel. It was split out on request so each panel reviews alone.

Metadata panel

New ee/app/assets/javascripts/merge_requests/ai_overview/components/metadata_panel.vue, in the sidebar under the reviewers panel.

A "Labels" section shows one GlLabel per label, using its background colour and title, with the description as the tooltip. Scoped labels (key::value) get the matching scoped style, via isScopedLabel from ~/lib/utils/common_utils. A "Milestone" row shows the milestone title, linking to its webPath. Each part renders only when present, and the whole card is hidden when there are neither labels nor a milestone.

The card is not collapsible: it is short enough not to need it.

GraphQL query

Following review from the frontend maintainer, labels and milestone are no longer in the main overview query, and there is no shared metadata fragment any more. metadata_panel.vue now runs its own query, queries/mr_ai_overview_metadata.query.graphql (mrAiOverviewMetadata), injecting projectPath and iid like the risk panel in !257492 (merged) does. This keeps labels and milestone out of the main overview query, whose complexity would otherwise grow. The main overview query, mr_ai_overview.query.graphql, is now unchanged from master.

The panel owns its two subscriptions itself, via its own subscribeToMore: mr_ai_overview_labels.subscription.graphql (issuableLabelsUpdated) and mr_ai_overview_milestone.subscription.graphql (issuableMilestoneUpdated). app.vue no longer subscribes to them; its subscribeToMore list is back to merge status and reviewers only.

The old single fragment, mr_ai_overview_metadata.fragment.graphql, was split into mr_ai_overview_labels.fragment.graphql (MrAiOverviewLabels: id and labels nodes id/title/color/description) and mr_ai_overview_milestone.fragment.graphql (MrAiOverviewMilestone: id and milestone id/title/webPath). Each subscription spreads only its own fragment, so the labels subscription no longer returns the milestone and vice versa. The panel's own query spreads both.

The panel takes no props any more. While its query is loading, or if it fails, the card renders nothing; a query error is reported with Sentry.captureException.

This is the first frontend consumer of issuableMilestoneUpdated, and no backend spec covers the merge request update path firing it, so it was checked on a local GDK: running MergeRequests::UpdateService inside a rolled back transaction fired the labels trigger on adding a label, and the milestone trigger on both setting and clearing the milestone.

The panel reads this.mergeRequest?.labels.nodes ?? [], where the ?. / ?? [] only cover the panel's own query not having loaded yet. labels itself still has no null check, for the same resolver reason as before: the schema marks labels nullable, but its resolver (Resolvers::BulkLabelsResolver) always returns a list, falling back to an empty one when the merge request has no labels.

Labels are not links to the filtered merge request list.

app.vue's diff against master is now just the import, the component registration, and <metadata-panel /> in the sidebar.

Strings

No new strings; the card reuses "Labels" and "Milestone". locale/gitlab.pot is unchanged.

There are no backend changes.

Files changed (11)

  • ee/app/assets/javascripts/merge_requests/ai_overview/components/app.vue
  • ee/app/assets/javascripts/merge_requests/ai_overview/components/metadata_panel.vue (new)
  • ee/app/assets/javascripts/merge_requests/ai_overview/queries/mr_ai_overview_metadata.query.graphql (new)
  • ee/app/assets/javascripts/merge_requests/ai_overview/queries/mr_ai_overview_labels.fragment.graphql (new)
  • ee/app/assets/javascripts/merge_requests/ai_overview/queries/mr_ai_overview_milestone.fragment.graphql (new)
  • ee/app/assets/javascripts/merge_requests/ai_overview/queries/mr_ai_overview_labels.subscription.graphql (new)
  • ee/app/assets/javascripts/merge_requests/ai_overview/queries/mr_ai_overview_milestone.subscription.graphql (new)
  • ee/spec/features/merge_request/user_toggles_ai_overview_spec.rb (one new example: the label title and the milestone link show)
  • ee/spec/frontend/merge_requests/ai_overview/components/app_spec.js (one new example: the panel renders)
  • ee/spec/frontend/merge_requests/ai_overview/components/metadata_panel_spec.js (new; mounts with a mock Apollo provider and covers rendering, loading, error, and both subscriptions)
  • ee/spec/frontend/merge_requests/ai_overview/mock_data.js (adds label and milestone builders plus makeMetadata / metadataResponse for the panel's own query; the main mock merge request no longer carries labels or milestone)

Verification performed

  • Jest: ee/spec/frontend/merge_requests/ai_overview - 91 examples, all pass. The panel spec's three subscription examples fail when the subscriptions are removed from the panel.
  • ESLint is clean on the changed JS/Vue files, Prettier on those plus the GraphQL files, and Rubocop on the feature spec. graphql-eslint could not run locally (no schema dump in the worktree), so the GraphQL files were checked against GDK instead (next bullet).
  • The new query was run against a local GDK GraphQL endpoint and returned labels and milestone for a real merge request. Both subscription documents passed schema validation there; they only stopped at the HTTP transport, which cannot carry subscriptions.
  • Checked on a local GDK that MergeRequests::UpdateService fires issuableLabelsUpdated on adding a label, and issuableMilestoneUpdated on both setting and clearing the milestone, inside a rolled back transaction.
  • Feature spec: the previous MR pipeline (2878720624, before this rebase and review change) ran the feature spec in CI and passed. It was not re-run locally after this change, because GDK's Vite serves the main checkout, not this branch's worktree.
  • Manual check on a local GDK at head 0f27c8e0, with no JavaScript console errors: a merge request with three labels, including a scoped one, and a milestone, shown alongside the merged description panel.
Edited by Marc Shaw

Merge request reports

Loading
Loading