Resolve: Security Dashboard - VulnerabilitiesOverTime: Validate and Update Filtering Logic

What does this MR do and why?

This MR reviews and updates filtering for the VulnerabilitiesOverTime view in the Security Dashboard, ensuring it matches the clarified scope from epic &17874 (closed) and this comment. With this MR, we are making sure the following scope is correctly implemented:

Panel-level

  • Filter by Severity
  • Grouping by Severity
  • Grouping by Report Type

Page-level (inherited filters)

  • Filter by Report Type
  • Filter by Project

This MR includes:

  • Adds support for filtering by report_type at the page level
  • Ensures project_id filtering works as intended (page level)
  • Ensures severity filtering is applied correctly (panel level)
  • Removes scanner filtering (see discussion)

References

Screenshots or screen recordings

How to set up and validate locally

Step 1: ElasticSearch Setup + Feature Flag
  1. Make sure you have ElasticSearch running on your local env. Follow the steps: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/howto/elasticsearch.md#setup
  2. Run the migration in Rails console:
Elastic::DataMigrationService[20250408180015].migrate
  1. Index vulnerability data:
Vulnerabilities::Read.all.each { |v| ::Elastic::ProcessBookkeepingService.track!(Search::Elastic::References::Vulnerability.new(v.vulnerability_id, "group_#{v.project.namespace.root_ancestor.id}")) }
  1. Then run:
Elastic::ProcessBookkeepingService.new.execute
  1. Now enable the feature flag in Rails console:
Feature.enable(:group_security_dashboard_new)
Step 2: Testing
Page Level Filtering
  1. Now that you have ES running on your gdk, navigate to http://gdk.test:3000/-/graphql-explorer and run the following query. Only SAST vulnerabilities should be returned:
{
  group(fullPath: "gitlab-org") {
    id
    securityMetrics(
      reportType:[SAST]
    ) {
      vulnerabilitiesOverTime(
        startDate: "2025-01-01T00:00:00Z"
        endDate: "2026-01-01T00:00:00Z"
      ) {
        nodes {
          date
          byReportType {
            reportType
            count
          }
        }
      }
    }
  }
}
  1. Now, let's test this on with multiple report types. Only SAST, DAST, and COVERAGE_FUZZING should be returned:
{
  group(fullPath: "gitlab-org") {
    id
    securityMetrics(
      reportType:[SAST, DAST, COVERAGE_FUZZING]
    ) {
      vulnerabilitiesOverTime(
        startDate: "2025-01-01T00:00:00Z"
        endDate: "2026-01-01T00:00:00Z"
      ) {
        nodes {
          date
          byReportType {
            reportType
            count
          }
        }
      }
    }
  }
}
  1. Now, let's test for project filtering. First, run the following query, so we know which project_id to use while filtering.
{
  group(fullPath: "gitlab-org") {
    id
    projects {
      edges {
        node {
          id
        }
      }
    }
  }
}
  1. Now filter for one project, then for more than one
{
  group(fullPath: "gitlab-org") {
    id
    securityMetrics(projectId: [45]) { # Use one of the project_id's you got returned in the previous query
      vulnerabilitiesOverTime(
        startDate: "2025-04-20T00:00:00Z"
        endDate: "2025-07-01T00:00:00Z"
      ) {
        nodes {
          date
          byReportType { reportType count }
          bySeverity { severity count }
        }
      }
    }
  }
}
  1. Now, filter on more than one project_id
{
  group(fullPath: "gitlab-org") {
    id
    securityMetrics(projectId: [45, 46, 47]) { # Use the project_id's you got returned in the previous query
      vulnerabilitiesOverTime(
        startDate: "2025-04-20T00:00:00Z"
        endDate: "2025-07-01T00:00:00Z"
      ) {
        nodes {
          date
          byReportType {
            reportType
            count
          }
          bySeverity {
            severity
            count
          }
        }
      }
    }
  }
}
Panel Level Filtering
  1. Run the following query. Only severities that are critical should be returned.
{
  group(fullPath: "gitlab-org") {
    id
    securityMetrics {
      vulnerabilitiesOverTime(
        startDate: "2025-04-20T00:00:00Z"
        endDate: "2025-07-01T00:00:00Z"
        severity: [CRITICAL]
      ) {
        nodes {
          date
          bySeverity {
            severity
            count
          }
        }
      }
    }
  }
}
  1. Run the same query, but this time with multiple severities:
{
  group(fullPath: "gitlab-org") {
    id
    securityMetrics {
      vulnerabilitiesOverTime(
        startDate: "2025-04-20T00:00:00Z"
        endDate: "2025-07-01T00:00:00Z"
        severity: [CRITICAL, HIGH, MEDIUM]
      ) {
        nodes {
          date
          bySeverity {
            severity
            count
          }
        }
      }
    }
  }
}
Page level and Panel level filtering
  1. Now let's filter on both page, and panel level:
{
  group(fullPath: "gitlab-org") {
    id
    projects {
      edges {
        node {
          id
        }
      }
    }
    securityMetrics(
      projectId: [47, 46, 45] # use the project ids you got returned 
      reportType:[SAST, DAST, COVERAGE_FUZZING]
    ) {
      vulnerabilitiesOverTime(
        startDate: "2025-01-01T00:00:00Z"
        endDate: "2026-01-01T00:00:00Z"
        severity: [LOW, MEDIUM, HIGH, INFO]
      ) {
        nodes {
          date
          byReportType {
            reportType
            count
          }
          bySeverity {
            severity
            count
          }
        }
      }
    }
  }
}

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.

Edited by Charlie Kroon

Merge request reports

Loading