Exporting Merge Requests by CSV ignores some filters

Summary

CSV export of merge requests returns significantly more results than displayed in the UI when using filters on the "merged" tab (specifically merged_by, target_branch, and deployed_after). Seemingly some filters are ignored when exporting the CSV and it results in a mismatch between the amount being filtered and the amount actually in the CSV.

Steps to reproduce

  1. Navigate to a project's merge requests page
  2. Apply a mix of the following filters on the "merged" tab:
    • Sort: created_asc
    • State: merged
    • Merged by: specific user (e.g., cleveland)
    • Target branch: main
    • Deployed after: 2025-01-01
  3. Note the count displayed in the UI (e.g., 933 entries)
  4. Click the three-vertical-dot menu and select "Export as CSV"
  5. Check the received CSV file

Example URL:

https://gitlab.example.com/project/-/merge_requests/?sort=created_asc&state=merged&merge_user=cleveland&target_branches%5B%5D=main&deployed_after=2025-01-01&first_page_size=20

Expected behavior

The CSV export should contain the same number of merge requests as displayed in the UI.

Actual behavior

The CSV export contains significantly more entries than shown in the UI (2000+ entries in the example, more than double the expected count).

Relevant logs and/or screenshots

Note in my example, I have two of my own users to test with. In the UI, I am filtering by one user and should have 3 MRs, but the CSV contains 4, one of which is my other user which was not filtered out.

SCR-20251212-knxr.png

SCR-20251212-knub.png

Environment

  • GitLab version: Reported in GitLab SM 18.2.1 (replicated on GitLab.com)

Root Cause Analysis (Duo Assisted)

The issue stems from three filters in MergeRequestsFinder that don't properly respect the state parameter constraint when joining related tables:

1. by_merge_user filter (MergeUserFilter)

  • Joins to merge_request_metrics table without re-applying state constraint
  • Returns MRs with matching metrics regardless of actual merge state

2. by_merged_at filter (MergedAtFilter)

  • Similar issue with merge_request_metrics join
  • Doesn't verify state after applying date filters

3. by_deployments filter

  • Joins to deployment_merge_requests without state verification
  • Can return non-merged MRs if deployment records exist

Code references:

Possible fixes

The filters should re-apply the state constraint after joining to related tables, or the join scopes should include state verification in their WHERE clauses.

Workaround

Use GraphQL API to query merge requests with proper filtering:

query {
  project(fullPath: "group/project") {
    mergeRequests(
      state: merged
      mergedBy: "username"
      deployedAfter: "2025-01-01T00:00:00Z"
      first: 100
    ) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          iid
          title
          state
          targetBranch
          sourceBranch
          mergeUser {
            username
          }
          createdAt
          mergedAt
        }
      }
    }
  }
}
Edited by 🤖 GitLab Bot 🤖