diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 89b3bbac9384a032e976ab5fae58938f4da52232..d9ee329897e64c22be688bcc642af4ee2c5dc704 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -4302,6 +4302,41 @@ type Group {
     state: [VulnerabilityState!]
   ): VulnerabilityConnection
 
+  """
+  Number of vulnerabilities per severity level, per day, for the projects in the group and its subgroups
+  """
+  vulnerabilitiesCountByDayAndSeverity(
+    """
+    Returns the elements in the list that come after the specified cursor.
+    """
+    after: String
+
+    """
+    Returns the elements in the list that come before the specified cursor.
+    """
+    before: String
+
+    """
+    Last day for which to fetch vulnerability history
+    """
+    endDate: ISO8601Date!
+
+    """
+    Returns the first _n_ elements from the list.
+    """
+    first: Int
+
+    """
+    Returns the last _n_ elements from the list.
+    """
+    last: Int
+
+    """
+    First day for which to fetch vulnerability history
+    """
+    startDate: ISO8601Date!
+  ): VulnerabilitiesCountByDayAndSeverityConnection
+
   """
   Web URL of the group
   """
@@ -4324,6 +4359,11 @@ enum HealthStatus {
   onTrack
 }
 
+"""
+An ISO 8601-encoded date
+"""
+scalar ISO8601Date
+
 type InstanceSecurityDashboard {
   """
   Projects selected in Instance Security Dashboard
@@ -8223,6 +8263,41 @@ type Query {
     """
     state: [VulnerabilityState!]
   ): VulnerabilityConnection
+
+  """
+  Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard
+  """
+  vulnerabilitiesCountByDayAndSeverity(
+    """
+    Returns the elements in the list that come after the specified cursor.
+    """
+    after: String
+
+    """
+    Returns the elements in the list that come before the specified cursor.
+    """
+    before: String
+
+    """
+    Last day for which to fetch vulnerability history
+    """
+    endDate: ISO8601Date!
+
+    """
+    Returns the first _n_ elements from the list.
+    """
+    first: Int
+
+    """
+    Returns the last _n_ elements from the list.
+    """
+    last: Int
+
+    """
+    First day for which to fetch vulnerability history
+    """
+    startDate: ISO8601Date!
+  ): VulnerabilitiesCountByDayAndSeverityConnection
 }
 
 """
@@ -10720,6 +10795,61 @@ enum VisibilityScopesEnum {
   public
 }
 
+"""
+Represents the number of vulnerabilities for a particular severity on a particular day
+"""
+type VulnerabilitiesCountByDayAndSeverity {
+  """
+  Number of vulnerabilities
+  """
+  count: Int
+
+  """
+  Date for the count
+  """
+  day: ISO8601Date
+
+  """
+  Severity of the counted vulnerabilities
+  """
+  severity: VulnerabilitySeverity
+}
+
+"""
+The connection type for VulnerabilitiesCountByDayAndSeverity.
+"""
+type VulnerabilitiesCountByDayAndSeverityConnection {
+  """
+  A list of edges.
+  """
+  edges: [VulnerabilitiesCountByDayAndSeverityEdge]
+
+  """
+  A list of nodes.
+  """
+  nodes: [VulnerabilitiesCountByDayAndSeverity]
+
+  """
+  Information to aid in pagination.
+  """
+  pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type VulnerabilitiesCountByDayAndSeverityEdge {
+  """
+  A cursor for use in pagination.
+  """
+  cursor: String!
+
+  """
+  The item at the end of the edge.
+  """
+  node: VulnerabilitiesCountByDayAndSeverity
+}
+
 """
 Represents a vulnerability.
 """
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 3e845667e80c9d665a8fb518a3054efab50ff90d..aec6b19fbb314850ffa8d53b942a7c572c41f66b 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -11939,6 +11939,87 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "vulnerabilitiesCountByDayAndSeverity",
+              "description": "Number of vulnerabilities per severity level, per day, for the projects in the group and its subgroups",
+              "args": [
+                {
+                  "name": "startDate",
+                  "description": "First day for which to fetch vulnerability history",
+                  "type": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "SCALAR",
+                      "name": "ISO8601Date",
+                      "ofType": null
+                    }
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "endDate",
+                  "description": "Last day for which to fetch vulnerability history",
+                  "type": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "SCALAR",
+                      "name": "ISO8601Date",
+                      "ofType": null
+                    }
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "after",
+                  "description": "Returns the elements in the list that come after the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "before",
+                  "description": "Returns the elements in the list that come before the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "first",
+                  "description": "Returns the first _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "last",
+                  "description": "Returns the last _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "VulnerabilitiesCountByDayAndSeverityConnection",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "webUrl",
               "description": "Web URL of the group",
@@ -12035,6 +12116,16 @@
           "enumValues": null,
           "possibleTypes": null
         },
+        {
+          "kind": "SCALAR",
+          "name": "ISO8601Date",
+          "description": "An ISO 8601-encoded date",
+          "fields": null,
+          "inputFields": null,
+          "interfaces": null,
+          "enumValues": null,
+          "possibleTypes": null
+        },
         {
           "kind": "OBJECT",
           "name": "InstanceSecurityDashboard",
@@ -24232,6 +24323,87 @@
               },
               "isDeprecated": false,
               "deprecationReason": null
+            },
+            {
+              "name": "vulnerabilitiesCountByDayAndSeverity",
+              "description": "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard",
+              "args": [
+                {
+                  "name": "startDate",
+                  "description": "First day for which to fetch vulnerability history",
+                  "type": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "SCALAR",
+                      "name": "ISO8601Date",
+                      "ofType": null
+                    }
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "endDate",
+                  "description": "Last day for which to fetch vulnerability history",
+                  "type": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "SCALAR",
+                      "name": "ISO8601Date",
+                      "ofType": null
+                    }
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "after",
+                  "description": "Returns the elements in the list that come after the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "before",
+                  "description": "Returns the elements in the list that come before the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "first",
+                  "description": "Returns the first _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "last",
+                  "description": "Returns the last _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "VulnerabilitiesCountByDayAndSeverityConnection",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
             }
           ],
           "inputFields": null,
@@ -31893,6 +32065,173 @@
           ],
           "possibleTypes": null
         },
+        {
+          "kind": "OBJECT",
+          "name": "VulnerabilitiesCountByDayAndSeverity",
+          "description": "Represents the number of vulnerabilities for a particular severity on a particular day",
+          "fields": [
+            {
+              "name": "count",
+              "description": "Number of vulnerabilities",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "SCALAR",
+                "name": "Int",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "day",
+              "description": "Date for the count",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "SCALAR",
+                "name": "ISO8601Date",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "severity",
+              "description": "Severity of the counted vulnerabilities",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "ENUM",
+                "name": "VulnerabilitySeverity",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            }
+          ],
+          "inputFields": null,
+          "interfaces": [
+
+          ],
+          "enumValues": null,
+          "possibleTypes": null
+        },
+        {
+          "kind": "OBJECT",
+          "name": "VulnerabilitiesCountByDayAndSeverityConnection",
+          "description": "The connection type for VulnerabilitiesCountByDayAndSeverity.",
+          "fields": [
+            {
+              "name": "edges",
+              "description": "A list of edges.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "LIST",
+                "name": null,
+                "ofType": {
+                  "kind": "OBJECT",
+                  "name": "VulnerabilitiesCountByDayAndSeverityEdge",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "nodes",
+              "description": "A list of nodes.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "LIST",
+                "name": null,
+                "ofType": {
+                  "kind": "OBJECT",
+                  "name": "VulnerabilitiesCountByDayAndSeverity",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "pageInfo",
+              "description": "Information to aid in pagination.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "OBJECT",
+                  "name": "PageInfo",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            }
+          ],
+          "inputFields": null,
+          "interfaces": [
+
+          ],
+          "enumValues": null,
+          "possibleTypes": null
+        },
+        {
+          "kind": "OBJECT",
+          "name": "VulnerabilitiesCountByDayAndSeverityEdge",
+          "description": "An edge in a connection.",
+          "fields": [
+            {
+              "name": "cursor",
+              "description": "A cursor for use in pagination.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "String",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "node",
+              "description": "The item at the end of the edge.",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "VulnerabilitiesCountByDayAndSeverity",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            }
+          ],
+          "inputFields": null,
+          "interfaces": [
+
+          ],
+          "enumValues": null,
+          "possibleTypes": null
+        },
         {
           "kind": "OBJECT",
           "name": "Vulnerability",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 3ca7164bff521c04c8c625660454c8af928cc1d5..f289a057cbcfa03e955135aa09bd10cf85b401ce 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1633,6 +1633,16 @@ Autogenerated return type of UpdateSnippet
 | ---   |  ---- | ----------  |
 | `createSnippet` | Boolean! | Indicates the user can perform `create_snippet` on this resource |
 
+## VulnerabilitiesCountByDayAndSeverity
+
+Represents the number of vulnerabilities for a particular severity on a particular day
+
+| Name  | Type  | Description |
+| ---   |  ---- | ----------  |
+| `count` | Int | Number of vulnerabilities |
+| `day` | ISO8601Date | Date for the count |
+| `severity` | VulnerabilitySeverity | Severity of the counted vulnerabilities |
+
 ## Vulnerability
 
 Represents a vulnerability.
diff --git a/ee/app/graphql/ee/types/group_type.rb b/ee/app/graphql/ee/types/group_type.rb
index 4780135346eb9b71d6eb4dce885c7364ec010218..a09d07b11e205d313ca9a629b7e02735c32084cf 100644
--- a/ee/app/graphql/ee/types/group_type.rb
+++ b/ee/app/graphql/ee/types/group_type.rb
@@ -31,6 +31,12 @@ module GroupType
               null: true,
               description: 'Vulnerabilities reported on the projects in the group and its subgroups',
               resolver: ::Resolvers::VulnerabilitiesResolver
+
+        field :vulnerabilities_count_by_day_and_severity,
+              ::Types::VulnerabilitiesCountByDayAndSeverityType.connection_type,
+              null: true,
+              description: 'Number of vulnerabilities per severity level, per day, for the projects in the group and its subgroups',
+              resolver: ::Resolvers::VulnerabilitiesHistoryResolver
       end
     end
   end
diff --git a/ee/app/graphql/ee/types/query_type.rb b/ee/app/graphql/ee/types/query_type.rb
index 60839e3940664b355f8e389bb0fcd3424e8d908b..64ae6414ef4922392c58463cdb3b850f3e22fdeb 100644
--- a/ee/app/graphql/ee/types/query_type.rb
+++ b/ee/app/graphql/ee/types/query_type.rb
@@ -15,6 +15,12 @@ module QueryType
               description: "Vulnerabilities reported on projects on the current user's instance security dashboard",
               resolver: ::Resolvers::VulnerabilitiesResolver
 
+        field :vulnerabilities_count_by_day_and_severity,
+              ::Types::VulnerabilitiesCountByDayAndSeverityType.connection_type,
+              null: true,
+              description: "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard",
+              resolver: ::Resolvers::VulnerabilitiesHistoryResolver
+
         field :design_management, ::Types::DesignManagementType,
               null: false,
               description: 'Fields related to design management'
diff --git a/ee/app/graphql/resolvers/vulnerabilities_base_resolver.rb b/ee/app/graphql/resolvers/vulnerabilities_base_resolver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dfd1e44380a03d4de932a7465906a3e7f95e0361
--- /dev/null
+++ b/ee/app/graphql/resolvers/vulnerabilities_base_resolver.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# VulnerabilitiesBaseResolver is an abstract class that is inherited by
+# vulnerability related resolvers. It contains the somewhat obtuse logic related
+# to finding the object to get vulnerabilities from so that developers writing
+# new resolvers don't have to repeat it.
+
+module Resolvers
+  class VulnerabilitiesBaseResolver < BaseResolver
+    include Gitlab::Utils::StrongMemoize
+
+    protected
+
+    # `vulnerable` will be a Project, Group, or InstanceSecurityDashboard
+    def vulnerable
+      # A project or group could have been loaded in batch by `BatchLoader`.
+      # At this point we need the `id` of the project or group to query for vulnerabilities, so
+      # make sure it's loaded and not `nil` before continuing.
+
+      strong_memoize(:vulnerable) do
+        if resolve_vulnerabilities_for_instance_security_dashboard?
+          ::InstanceSecurityDashboard.new(current_user)
+        elsif object.respond_to?(:sync)
+          object.sync
+        else
+          object
+        end
+      end
+    end
+
+    def resolve_vulnerabilities_for_instance_security_dashboard?
+      # object will be nil when we're fetching vulnerabilities from QueryType,
+      # which is the source of vulnerability data for the instance security
+      # dashboard
+      object.nil? && current_user.present?
+    end
+  end
+end
diff --git a/ee/app/graphql/resolvers/vulnerabilities_history_resolver.rb b/ee/app/graphql/resolvers/vulnerabilities_history_resolver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d4fbbc8eff47bedd4934ce06a52ae383648338a6
--- /dev/null
+++ b/ee/app/graphql/resolvers/vulnerabilities_history_resolver.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Resolvers
+  class VulnerabilitiesHistoryResolver < VulnerabilitiesBaseResolver
+    include Gitlab::Utils::StrongMemoize
+
+    MAX_DAYS = ::Vulnerability::MAX_DAYS_OF_HISTORY
+
+    type Types::VulnerabilitiesCountByDayAndSeverityType, null: true
+
+    argument :start_date, GraphQL::Types::ISO8601Date, required: true,
+              description: 'First day for which to fetch vulnerability history'
+
+    argument :end_date, GraphQL::Types::ISO8601Date, required: true,
+              description: 'Last day for which to fetch vulnerability history'
+
+    def resolve(**args)
+      return [] unless vulnerable
+
+      start_date = args[:start_date]
+      end_date = args[:end_date]
+      days = end_date - start_date + 1
+
+      if days > MAX_DAYS
+        raise ::Vulnerability::TooManyDaysError, "Cannot fetch counts for more than #{MAX_DAYS} days"
+      else
+        vulnerable.vulnerabilities.counts_by_day_and_severity(start_date, end_date).to_a
+      end
+    end
+  end
+end
diff --git a/ee/app/graphql/resolvers/vulnerabilities_resolver.rb b/ee/app/graphql/resolvers/vulnerabilities_resolver.rb
index 7db52be0547da4a73716cfa5fb47c1d5e6f63c84..031476d8b142bc1e7e936906c2c81fda69f14863 100644
--- a/ee/app/graphql/resolvers/vulnerabilities_resolver.rb
+++ b/ee/app/graphql/resolvers/vulnerabilities_resolver.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module Resolvers
-  class VulnerabilitiesResolver < BaseResolver
+  class VulnerabilitiesResolver < VulnerabilitiesBaseResolver
     include Gitlab::Utils::StrongMemoize
 
     type Types::VulnerabilityType, null: true
@@ -30,32 +30,8 @@ def resolve(**args)
 
     private
 
-    # `vulnerable` will be a Project, Group, or InstanceSecurityDashboard
-    def vulnerable
-      # A project or group could have been loaded in batch by `BatchLoader`.
-      # At this point we need the `id` of the project or group to query for vulnerabilities, so
-      # make sure it's loaded and not `nil` before continuing.
-
-      strong_memoize(:vulnerable) do
-        if resolve_vulnerabilities_for_instance_security_dashboard?
-          ::InstanceSecurityDashboard.new(current_user)
-        elsif object.respond_to?(:sync)
-          object.sync
-        else
-          object
-        end
-      end
-    end
-
     def vulnerabilities(filters)
       Security::VulnerabilitiesFinder.new(vulnerable, filters).execute
     end
-
-    def resolve_vulnerabilities_for_instance_security_dashboard?
-      # object will be nil when we're fetching vulnerabilities from QueryType,
-      # which is the source of vulnerability data for the instance security
-      # dashboard
-      object.nil? && current_user.present?
-    end
   end
 end
diff --git a/ee/app/graphql/types/vulnerabilities_count_by_day_and_severity_type.rb b/ee/app/graphql/types/vulnerabilities_count_by_day_and_severity_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..90f9a78c4a6eab1a2adfbb5d8a75e460c5876eb1
--- /dev/null
+++ b/ee/app/graphql/types/vulnerabilities_count_by_day_and_severity_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+  # rubocop: disable Graphql/AuthorizeTypes
+  class VulnerabilitiesCountByDayAndSeverityType < BaseObject
+    graphql_name 'VulnerabilitiesCountByDayAndSeverity'
+    description 'Represents the number of vulnerabilities for a particular severity on a particular day'
+
+    field :count, GraphQL::INT_TYPE, null: true,
+          description: 'Number of vulnerabilities'
+
+    field :day, GraphQL::Types::ISO8601Date, null: true,
+          description: 'Date for the count'
+
+    field :severity, VulnerabilitySeverityEnum, null: true,
+          description: 'Severity of the counted vulnerabilities'
+  end
+end
diff --git a/ee/app/models/vulnerability.rb b/ee/app/models/vulnerability.rb
index 919cce8fded28d2571327ceab4ed1ee413e3d453..ab096d9be50d43146751cb9ab9d55066ff952b60 100644
--- a/ee/app/models/vulnerability.rb
+++ b/ee/app/models/vulnerability.rb
@@ -9,7 +9,7 @@ class Vulnerability < ApplicationRecord
 
   TooManyDaysError = Class.new(StandardError)
 
-  MAX_DAYS_IN_PAST = 10
+  MAX_DAYS_OF_HISTORY = 10
 
   cache_markdown_field :title, pipeline: :single_line
   cache_markdown_field :description, issuable_state_filter_enabled: true
@@ -67,19 +67,21 @@ def with_vulnerability_links
   scope :with_states, -> (states) { where(state: states) }
   scope :counts_by_severity, -> { group(:severity).count }
 
-  def self.counts_by_day_and_severity(num_days_in_past, end_date = Date.current)
+  def self.counts_by_day_and_severity(start_date, end_date)
     return [] unless Feature.enabled?(:vulnerability_history, default_enabled: true)
 
+    num_days_of_history = end_date - start_date + 1
+
     # this clause guards against query timeouts
-    raise TooManyDaysError, "Cannot fetch counts for more than #{MAX_DAYS_IN_PAST} days" if num_days_in_past > MAX_DAYS_IN_PAST
+    raise TooManyDaysError, "Cannot fetch counts for more than #{MAX_DAYS_OF_HISTORY} days" if num_days_of_history > MAX_DAYS_OF_HISTORY
 
-    quoted_num_days_in_past = connection.quote(num_days_in_past)
+    quoted_start_date = connection.quote(start_date)
     quoted_end_date = connection.quote(end_date)
 
     select(
       'DATE(calendar.entry) AS day, severity, COUNT(*)'
     ).from(
-      "generate_series(DATE #{quoted_end_date} - INTERVAL '#{quoted_num_days_in_past} days', DATE #{quoted_end_date}, INTERVAL '1 day') as calendar(entry)"
+      "generate_series(DATE #{quoted_start_date}, DATE #{quoted_end_date}, INTERVAL '1 day') as calendar(entry)"
     ).joins(
       'INNER JOIN vulnerabilities ON vulnerabilities.created_at <= calendar.entry'
     ).where(
diff --git a/ee/changelogs/unreleased/add-vulnerability-history-to-graphql.yml b/ee/changelogs/unreleased/add-vulnerability-history-to-graphql.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bf8751381aec77c46a67f7d57e1784fccc8ca91f
--- /dev/null
+++ b/ee/changelogs/unreleased/add-vulnerability-history-to-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Add vulnerability history to graphQL
+merge_request: 30674
+author:
+type: added
diff --git a/ee/spec/graphql/ee/types/group_type_spec.rb b/ee/spec/graphql/ee/types/group_type_spec.rb
index 8d10d8eae271b62e6b0bf8ed3ea3111dc991aca5..311de82c5937b3b0fc44306408ead5a060f138fc 100644
--- a/ee/spec/graphql/ee/types/group_type_spec.rb
+++ b/ee/spec/graphql/ee/types/group_type_spec.rb
@@ -12,6 +12,7 @@
   it { expect(described_class).to have_graphql_field(:groupTimelogsEnabled) }
   it { expect(described_class).to have_graphql_field(:timelogs, complexity: 5) }
   it { expect(described_class).to have_graphql_field(:vulnerabilities) }
+  it { expect(described_class).to have_graphql_field(:vulnerabilities_count_by_day_and_severity) }
 
   describe 'timelogs field' do
     subject { described_class.fields['timelogs'] }
diff --git a/ee/spec/graphql/resolvers/vulnerabilities_history_resolver_spec.rb b/ee/spec/graphql/resolvers/vulnerabilities_history_resolver_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ab0ad0227e22e93783e340defaa0f6044f6a83f7
--- /dev/null
+++ b/ee/spec/graphql/resolvers/vulnerabilities_history_resolver_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::VulnerabilitiesHistoryResolver do
+  include GraphqlHelpers
+
+  subject { resolve(described_class, obj: group, args: args, ctx: { current_user: user }) }
+
+  let_it_be(:group) { create(:group) }
+  let_it_be(:project) { create(:project, namespace: group) }
+  let_it_be(:user) { create(:user) }
+
+  describe '#resolve' do
+    let(:args) { { start_date: Date.parse('2019-10-15'), end_date: Date.parse('2019-10-21') } }
+
+    it "fetches historical vulnerability data from the start date to the end date" do
+      Timecop.freeze(Date.parse('2019-10-31')) do
+        create(:vulnerability, :critical, created_at: 15.days.ago, dismissed_at: 10.days.ago, project: project)
+        create(:vulnerability, :high, created_at: 15.days.ago, dismissed_at: 11.days.ago, project: project)
+        create(:vulnerability, :critical, created_at: 14.days.ago, resolved_at: 12.days.ago, project: project)
+
+        ordered_history = subject.sort_by { |count| [count['day'], count['severity']] }
+
+        expect(ordered_history.to_json).to eq([
+          { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-16', 'count' => 1 },
+          { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-16', 'count' => 1 },
+          { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-17', 'count' => 2 },
+          { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-17', 'count' => 1 },
+          { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-18', 'count' => 2 },
+          { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-18', 'count' => 1 },
+          { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-19', 'count' => 1 },
+          { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-19', 'count' => 1 },
+          { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-20', 'count' => 1 }
+        ].to_json)
+      end
+    end
+
+    context 'when given more than 10 days' do
+      let(:args) { { start_date: Date.parse('2019-10-11'), end_date: Date.parse('2019-10-21') } }
+
+      it 'raises an error stating that no more than 10 days can be requested' do
+        expect { subject }.to raise_error(::Vulnerability::TooManyDaysError, 'Cannot fetch counts for more than 10 days')
+      end
+    end
+  end
+end
diff --git a/ee/spec/graphql/types/query_type_spec.rb b/ee/spec/graphql/types/query_type_spec.rb
index c14fd3441ae751cc6c5a4a027ba9d63b32e968c9..6c846d35279f297006cc06dd304b230383263689 100644
--- a/ee/spec/graphql/types/query_type_spec.rb
+++ b/ee/spec/graphql/types/query_type_spec.rb
@@ -8,7 +8,8 @@
       :design_management,
       :geo_node,
       :vulnerabilities,
-      :instance_security_dashboard
+      :instance_security_dashboard,
+      :vulnerabilities_count_by_day_and_severity
     ).at_least
   end
 end
diff --git a/ee/spec/graphql/types/vulnerabilities_count_by_day_and_severity_type_spec.rb b/ee/spec/graphql/types/vulnerabilities_count_by_day_and_severity_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..94a70e5f4453f65a6a8919b0123bee2cccaac721
--- /dev/null
+++ b/ee/spec/graphql/types/vulnerabilities_count_by_day_and_severity_type_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['VulnerabilitiesCountByDayAndSeverity'] do
+  it { expect(described_class).to have_graphql_fields(:count, :day, :severity) }
+end
diff --git a/ee/spec/models/vulnerability_spec.rb b/ee/spec/models/vulnerability_spec.rb
index 63969f25e12130e584aeac20babc9bc794a130f8..67a9db499af79548b3115c41eabe88be936ae02a 100644
--- a/ee/spec/models/vulnerability_spec.rb
+++ b/ee/spec/models/vulnerability_spec.rb
@@ -178,7 +178,7 @@
       it 'returns an empty array' do
         create(:vulnerability, created_at: 1.day.ago)
 
-        counts_by_day_and_severity = Vulnerability.counts_by_day_and_severity(6)
+        counts_by_day_and_severity = Vulnerability.counts_by_day_and_severity(1.day.ago, Date.current)
 
         expect(counts_by_day_and_severity).to be_empty
       end
@@ -189,54 +189,28 @@
         stub_feature_flags(vulnerability_history: true)
       end
 
-      context 'when not given an end date' do
-        it 'returns the count of unresolved, undismissed vulnerabilities for each severity from the current day to the given number of days in the past' do
-          Timecop.freeze(Time.zone.parse('2019-10-31')) do
-            create(:vulnerability, created_at: 5.days.ago, dismissed_at: Date.current, severity: :critical)
-            create(:vulnerability, created_at: 5.days.ago, dismissed_at: 1.day.ago, severity: :high)
-            create(:vulnerability, created_at: 4.days.ago, resolved_at: 2.days.ago, severity: :critical)
-
-            counts_by_day_and_severity = Vulnerability.counts_by_day_and_severity(6)
-
-            expect(counts_by_day_and_severity.order(:day, :severity).to_json).to eq([
-              { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-26', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-26', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-27', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-27', 'count' => 2 },
-              { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-28', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-28', 'count' => 2 },
-              { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-29', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-29', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-30', 'count' => 1 }
-            ].to_json)
-          end
+      it 'returns the count of unresolved, undismissed vulnerabilities for each severity for each day from the start date to the end date' do
+        Timecop.freeze(Time.zone.parse('2019-10-31')) do
+          create(:vulnerability, created_at: 5.days.ago, dismissed_at: Date.current, severity: :critical)
+          create(:vulnerability, created_at: 5.days.ago, dismissed_at: 1.day.ago, severity: :high)
+          create(:vulnerability, created_at: 4.days.ago, resolved_at: 2.days.ago, severity: :critical)
+
+          counts_by_day_and_severity = Vulnerability.counts_by_day_and_severity(Date.parse('2019-10-22'), Date.parse('2019-10-28'))
+
+          expect(counts_by_day_and_severity.order(:day, :severity).to_json).to eq([
+            { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-26', 'count' => 1 },
+            { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-26', 'count' => 1 },
+            { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-27', 'count' => 1 },
+            { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-27', 'count' => 2 },
+            { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-28', 'count' => 1 },
+            { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-28', 'count' => 2 }
+          ].to_json)
         end
       end
 
-      context 'when given an end date' do
-        it 'returns the count of unresolved, undismissed vulnerabilities for each severity for each day from the given end date to the given number of days in the past' do
-          Timecop.freeze(Time.zone.parse('2019-10-31')) do
-            create(:vulnerability, created_at: 5.days.ago, dismissed_at: Date.current, severity: :critical)
-            create(:vulnerability, created_at: 5.days.ago, dismissed_at: 1.day.ago, severity: :high)
-            create(:vulnerability, created_at: 4.days.ago, resolved_at: 2.days.ago, severity: :critical)
-
-            counts_by_day_and_severity = Vulnerability.counts_by_day_and_severity(6, Date.parse('2019-10-28'))
-
-            expect(counts_by_day_and_severity.order(:day, :severity).to_json).to eq([
-              { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-26', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-26', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-27', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-27', 'count' => 2 },
-              { 'id' => nil, 'severity' => 'high', 'day' => '2019-10-28', 'count' => 1 },
-              { 'id' => nil, 'severity' => 'critical', 'day' => '2019-10-28', 'count' => 2 }
-            ].to_json)
-          end
-        end
-      end
-
-      context 'when given a number of past days greater than 10' do
+      context 'there are more than 10 days between the start and end dates' do
         it 'raises a TooManyDaysError' do
-          expect { Vulnerability.counts_by_day_and_severity(11) }.to raise_error(
+          expect { Vulnerability.counts_by_day_and_severity(10.days.ago.to_date, Date.current) }.to raise_error(
             Vulnerability::TooManyDaysError,
             'Cannot fetch counts for more than 10 days'
           )
diff --git a/ee/spec/requests/api/graphql/group/vulnerability_severities_count_by_day_spec.rb b/ee/spec/requests/api/graphql/group/vulnerability_severities_count_by_day_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d4be1f0c2553c9543a63af601cbf2866b2c5aca1
--- /dev/null
+++ b/ee/spec/requests/api/graphql/group/vulnerability_severities_count_by_day_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'group(fullPath).vulnerabilitiesCountByDayAndSeverity' do
+  include GraphqlHelpers
+
+  let_it_be(:group) { create(:group) }
+  let_it_be(:project) { create(:project, namespace: group) }
+  let_it_be(:current_user) { create(:user) }
+
+  let(:query) { graphql_query_for(:group, { fullPath: group.full_path }, history_field) }
+  let(:query_result) { graphql_data.dig('group', 'vulnerabilitiesCountByDayAndSeverity', 'nodes') }
+
+  let(:history_field) do
+    query_graphql_field(
+      :vulnerabilitiesCountByDayAndSeverity,
+      {
+        start_date: Date.parse('2019-10-15').iso8601,
+        end_date: Date.parse('2019-10-21').iso8601
+      },
+      history_fields
+    )
+  end
+
+  let(:history_fields) do
+    query_graphql_field(:nodes, nil, <<~FIELDS)
+      count
+      day
+      severity
+    FIELDS
+  end
+
+  it "fetches historical vulnerability data from the start date to the end date for projects in the group and its subgroups" do
+    Timecop.freeze(Time.zone.parse('2019-10-31')) do
+      project.add_developer(current_user)
+
+      create(:vulnerability, :critical, created_at: 15.days.ago, dismissed_at: 10.days.ago, project: project)
+      create(:vulnerability, :high, created_at: 15.days.ago, dismissed_at: 11.days.ago, project: project)
+      create(:vulnerability, :critical, created_at: 14.days.ago, resolved_at: 12.days.ago, project: project)
+
+      post_graphql(query, current_user: current_user)
+
+      ordered_history = query_result.sort_by { |count| [count['day'], count['severity']] }
+
+      expect(ordered_history).to eq([
+        { 'severity' => 'CRITICAL', 'day' => '2019-10-16', 'count' => 1 },
+        { 'severity' => 'HIGH', 'day' => '2019-10-16', 'count' => 1 },
+        { 'severity' => 'CRITICAL', 'day' => '2019-10-17', 'count' => 2 },
+        { 'severity' => 'HIGH', 'day' => '2019-10-17', 'count' => 1 },
+        { 'severity' => 'CRITICAL', 'day' => '2019-10-18', 'count' => 2 },
+        { 'severity' => 'HIGH', 'day' => '2019-10-18', 'count' => 1 },
+        { 'severity' => 'CRITICAL', 'day' => '2019-10-19', 'count' => 1 },
+        { 'severity' => 'HIGH', 'day' => '2019-10-19', 'count' => 1 },
+        { 'severity' => 'CRITICAL', 'day' => '2019-10-20', 'count' => 1 }
+      ])
+    end
+  end
+end
diff --git a/ee/spec/requests/api/graphql/query_spec.rb b/ee/spec/requests/api/graphql/query_spec.rb
index 26b4c6eafd7c834da70936af356c738444ed0646..e421daf3437b640d70c08641f9540cf5f7aa609b 100644
--- a/ee/spec/requests/api/graphql/query_spec.rb
+++ b/ee/spec/requests/api/graphql/query_spec.rb
@@ -92,4 +92,54 @@
       end
     end
   end
+
+  describe '.vulnerabilitiesCountByDayAndSeverity' do
+    let(:query_result) { graphql_data.dig('vulnerabilitiesCountByDayAndSeverity', 'nodes') }
+
+    let(:query) do
+      graphql_query_for(
+        :vulnerabilitiesCountByDayAndSeverity,
+        {
+          start_date: Date.parse('2019-10-15').iso8601,
+          end_date: Date.parse('2019-10-21').iso8601
+        },
+        history_fields
+      )
+    end
+
+    let(:history_fields) do
+      query_graphql_field(:nodes, nil, <<~FIELDS)
+        count
+        day
+        severity
+      FIELDS
+    end
+
+    it "fetches historical vulnerability data from the start date to the end date for projects on the current user's instance security dashboard" do
+      Timecop.freeze(Time.zone.parse('2019-10-31')) do
+        current_user.security_dashboard_projects << project
+        project.add_developer(developer)
+
+        create(:vulnerability, :critical, created_at: 15.days.ago, dismissed_at: 10.days.ago, project: project)
+        create(:vulnerability, :high, created_at: 15.days.ago, dismissed_at: 11.days.ago, project: project)
+        create(:vulnerability, :critical, created_at: 14.days.ago, resolved_at: 12.days.ago, project: project)
+
+        post_graphql(query, current_user: current_user)
+
+        ordered_history = query_result.sort_by { |count| [count['day'], count['severity']] }
+
+        expect(ordered_history).to eq([
+          { 'severity' => 'CRITICAL', 'day' => '2019-10-16', 'count' => 1 },
+          { 'severity' => 'HIGH', 'day' => '2019-10-16', 'count' => 1 },
+          { 'severity' => 'CRITICAL', 'day' => '2019-10-17', 'count' => 2 },
+          { 'severity' => 'HIGH', 'day' => '2019-10-17', 'count' => 1 },
+          { 'severity' => 'CRITICAL', 'day' => '2019-10-18', 'count' => 2 },
+          { 'severity' => 'HIGH', 'day' => '2019-10-18', 'count' => 1 },
+          { 'severity' => 'CRITICAL', 'day' => '2019-10-19', 'count' => 1 },
+          { 'severity' => 'HIGH', 'day' => '2019-10-19', 'count' => 1 },
+          { 'severity' => 'CRITICAL', 'day' => '2019-10-20', 'count' => 1 }
+        ])
+      end
+    end
+  end
 end