From ed5add1c2f001c9bd54e664b32f212de172eca6a Mon Sep 17 00:00:00 2001
From: GitLab Bot <gitlab-bot@gitlab.com>
Date: Fri, 10 Apr 2020 18:09:32 +0000
Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master

---
 .../resolvers/metrics/dashboard_resolver.rb   |  21 +++++
 app/graphql/types/environment_type.rb         |   4 +
 app/graphql/types/metrics/dashboard_type.rb   |  15 ++++
 .../prometheus_dashboard.rb                   |  34 +++++---
 app/models/prometheus_alert.rb                |   3 +-
 ...-ensure-correct-prometheus-alert-found.yml |   5 ++
 ...remove_trial_ends_on_from_group_export.yml |   5 ++
 ...add-metrics-dashboard-graphql-resource.yml |   5 ++
 doc/administration/instance_limits.md         |  12 +++
 .../graphql/reference/gitlab_schema.graphql   |  17 ++++
 doc/api/graphql/reference/gitlab_schema.json  |  54 ++++++++++++
 doc/api/graphql/reference/index.md            |   7 ++
 .../understanding_explain_plans.md            |   4 +-
 doc/user/project/integrations/prometheus.md   |   1 +
 lib/gitlab/alerting/alert.rb                  |  19 +++-
 lib/gitlab/diff/highlight_cache.rb            |   2 +-
 .../import_export/group/import_export.yml     |   3 +
 spec/fixtures/group.json                      |   1 +
 spec/fixtures/group_export.tar.gz             | Bin 2795 -> 3546 bytes
 .../group_export_invalid_subrelations.tar.gz  | Bin 3036 -> 3602 bytes
 .../group_exports/complex/group.json          |   9 --
 .../group_exports/no_children/group.json      |   3 -
 .../visibility_levels/internal/group.json     |  12 ---
 .../visibility_levels/private/group.json      |  12 ---
 .../visibility_levels/public/group.json       |  12 ---
 .../metrics/dashboard_resolver_spec.rb        |  44 ++++++++++
 spec/graphql/types/environment_type_spec.rb   |   2 +-
 .../types/metrics/dashboard_type_spec.rb      |  15 ++++
 spec/lib/gitlab/alerting/alert_spec.rb        |  42 ++++++++-
 spec/models/prometheus_alert_spec.rb          |   3 +-
 .../graphql/metrics/dashboard_query_spec.rb   |  82 ++++++++++++++++++
 31 files changed, 379 insertions(+), 69 deletions(-)
 create mode 100644 app/graphql/resolvers/metrics/dashboard_resolver.rb
 create mode 100644 app/graphql/types/metrics/dashboard_type.rb
 create mode 100644 changelogs/unreleased/204908-ensure-correct-prometheus-alert-found.yml
 create mode 100644 changelogs/unreleased/georgekoltsov-remove_trial_ends_on_from_group_export.yml
 create mode 100644 changelogs/unreleased/mwaw-211330-add-metrics-dashboard-graphql-resource.yml
 create mode 100644 spec/fixtures/group.json
 create mode 100644 spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
 create mode 100644 spec/graphql/types/metrics/dashboard_type_spec.rb
 create mode 100644 spec/requests/api/graphql/metrics/dashboard_query_spec.rb

diff --git a/app/graphql/resolvers/metrics/dashboard_resolver.rb b/app/graphql/resolvers/metrics/dashboard_resolver.rb
new file mode 100644
index 00000000000..05d82ca0f46
--- /dev/null
+++ b/app/graphql/resolvers/metrics/dashboard_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+  module Metrics
+    class DashboardResolver < Resolvers::BaseResolver
+      argument :path, GraphQL::STRING_TYPE,
+               required: true,
+               description: "Path to a file which defines metrics dashboard eg: 'config/prometheus/common_metrics.yml'"
+
+      type Types::Metrics::DashboardType, null: true
+
+      alias_method :environment, :object
+
+      def resolve(**args)
+        return unless environment
+
+        ::PerformanceMonitoring::PrometheusDashboard.find_for(project: environment.project, user: context[:current_user], path: args[:path], options: { environment: environment })
+      end
+    end
+  end
+end
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index c165c0ddc61..34a90006d03 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -15,5 +15,9 @@ module Types
 
     field :state, GraphQL::STRING_TYPE, null: false,
           description: 'State of the environment, for example: available/stopped'
+
+    field :metrics_dashboard, Types::Metrics::DashboardType, null: true,
+          description: 'Metrics dashboard schema for the environment',
+          resolver: Resolvers::Metrics::DashboardResolver
   end
 end
diff --git a/app/graphql/types/metrics/dashboard_type.rb b/app/graphql/types/metrics/dashboard_type.rb
new file mode 100644
index 00000000000..11e834013ca
--- /dev/null
+++ b/app/graphql/types/metrics/dashboard_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+  module Metrics
+    # rubocop: disable Graphql/AuthorizeTypes
+    # Authorization is performed at environment level
+    class DashboardType < ::Types::BaseObject
+      graphql_name 'MetricsDashboard'
+
+      field :path, GraphQL::STRING_TYPE, null: true,
+            description: 'Path to a file with the dashboard definition'
+    end
+    # rubocop: enable Graphql/AuthorizeTypes
+  end
+end
diff --git a/app/models/performance_monitoring/prometheus_dashboard.rb b/app/models/performance_monitoring/prometheus_dashboard.rb
index 5f2df444fd0..30fb1935a27 100644
--- a/app/models/performance_monitoring/prometheus_dashboard.rb
+++ b/app/models/performance_monitoring/prometheus_dashboard.rb
@@ -4,27 +4,41 @@ module PerformanceMonitoring
   class PrometheusDashboard
     include ActiveModel::Model
 
-    attr_accessor :dashboard, :panel_groups
+    attr_accessor :dashboard, :panel_groups, :path, :environment, :priority
 
     validates :dashboard, presence: true
     validates :panel_groups, presence: true
 
-    def self.from_json(json_content)
-      dashboard = new(
-        dashboard: json_content['dashboard'],
-        panel_groups: json_content['panel_groups'].map { |group| PrometheusPanelGroup.from_json(group) }
-      )
-
-      dashboard.tap(&:validate!)
+    class << self
+      def from_json(json_content)
+        dashboard = new(
+          dashboard: json_content['dashboard'],
+          panel_groups: json_content['panel_groups'].map { |group| PrometheusPanelGroup.from_json(group) }
+        )
+
+        dashboard.tap(&:validate!)
+      end
+
+      def find_for(project:, user:, path:, options: {})
+        dashboard_response = Gitlab::Metrics::Dashboard::Finder.find(project, user, options.merge(dashboard_path: path))
+        return unless dashboard_response[:status] == :success
+
+        new(
+          {
+            path: path,
+            environment: options[:environment]
+          }.merge(dashboard_response[:dashboard])
+        )
+      end
     end
 
     def to_yaml
-      self.as_json(only: valid_attributes).to_yaml
+      self.as_json(only: yaml_valid_attributes).to_yaml
     end
 
     private
 
-    def valid_attributes
+    def yaml_valid_attributes
       %w(panel_groups panels metrics group priority type title y_label weight id unit label query query_range dashboard)
     end
   end
diff --git a/app/models/prometheus_alert.rb b/app/models/prometheus_alert.rb
index a1303f59129..fbc0281296f 100644
--- a/app/models/prometheus_alert.rb
+++ b/app/models/prometheus_alert.rb
@@ -56,7 +56,8 @@ class PrometheusAlert < ApplicationRecord
       "for" => "5m",
       "labels" => {
         "gitlab" => "hook",
-        "gitlab_alert_id" => prometheus_metric_id
+        "gitlab_alert_id" => prometheus_metric_id,
+        "gitlab_prometheus_alert_id" => id
       }
     }
   end
diff --git a/changelogs/unreleased/204908-ensure-correct-prometheus-alert-found.yml b/changelogs/unreleased/204908-ensure-correct-prometheus-alert-found.yml
new file mode 100644
index 00000000000..35f48fd884b
--- /dev/null
+++ b/changelogs/unreleased/204908-ensure-correct-prometheus-alert-found.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent wrong environment being used when processing Prometheus alert
+merge_request: 29119
+author:
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-remove_trial_ends_on_from_group_export.yml b/changelogs/unreleased/georgekoltsov-remove_trial_ends_on_from_group_export.yml
new file mode 100644
index 00000000000..ff61cf92cc9
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-remove_trial_ends_on_from_group_export.yml
@@ -0,0 +1,5 @@
+---
+title: Exclude 'trial_ends_on', 'shared_runners_minutes_limit' & 'extra_shared_runners_minutes_limit' from list of exported Group attributes
+merge_request: 29259
+author:
+type: fixed
diff --git a/changelogs/unreleased/mwaw-211330-add-metrics-dashboard-graphql-resource.yml b/changelogs/unreleased/mwaw-211330-add-metrics-dashboard-graphql-resource.yml
new file mode 100644
index 00000000000..ab4801c4a48
--- /dev/null
+++ b/changelogs/unreleased/mwaw-211330-add-metrics-dashboard-graphql-resource.yml
@@ -0,0 +1,5 @@
+---
+title: Add graphQL interface to fetch metrics dashboard
+merge_request: 29112
+author:
+type: added
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index cf4cd9f4345..57acdec4ea2 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -178,6 +178,18 @@ Plan.default.limits.update!(ci_pipeline_schedules: 100)
 
 ## Instance monitoring and metrics
 
+### Incident Management inbound alert limits
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14932) in GitLab 12.5.
+
+Limiting inbound alerts for an incident reduces the number of alerts (issues)
+that can be created within a period of time, which can help prevent overloading
+your incident responders with duplicate issues. You can reduce the volume of
+alerts in the following ways:
+
+- Max requests per period per project, 3600 seconds by default.
+- Rate limit period in seconds, 3600 seconds by default.
+
 ### Prometheus Alert JSON payloads
 
 > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14929) in GitLab 12.6.
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index e79d52d4d10..22ca25e45d3 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -1882,6 +1882,16 @@ type Environment {
   """
   id: ID!
 
+  """
+  Metrics dashboard schema for the environment
+  """
+  metricsDashboard(
+    """
+    Path to a file which defines metrics dashboard eg: 'config/prometheus/common_metrics.yml'
+    """
+    path: String!
+  ): MetricsDashboard
+
   """
   Human-readable name of the environment
   """
@@ -5278,6 +5288,13 @@ type Metadata {
   version: String!
 }
 
+type MetricsDashboard {
+  """
+  Path to a file with the dashboard definition
+  """
+  path: String
+}
+
 """
 Represents a milestone.
 """
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 820f41755d0..40bd27062b3 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -5580,6 +5580,33 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "metricsDashboard",
+              "description": "Metrics dashboard schema for the environment",
+              "args": [
+                {
+                  "name": "path",
+                  "description": "Path to a file which defines metrics dashboard eg: 'config/prometheus/common_metrics.yml'",
+                  "type": {
+                    "kind": "NON_NULL",
+                    "name": null,
+                    "ofType": {
+                      "kind": "SCALAR",
+                      "name": "String",
+                      "ofType": null
+                    }
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "MetricsDashboard",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "name",
               "description": "Human-readable name of the environment",
@@ -15117,6 +15144,33 @@
           "enumValues": null,
           "possibleTypes": null
         },
+        {
+          "kind": "OBJECT",
+          "name": "MetricsDashboard",
+          "description": null,
+          "fields": [
+            {
+              "name": "path",
+              "description": "Path to a file with the dashboard definition",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            }
+          ],
+          "inputFields": null,
+          "interfaces": [
+
+          ],
+          "enumValues": null,
+          "possibleTypes": null
+        },
         {
           "kind": "OBJECT",
           "name": "Milestone",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6ed3c5a778e..e1375530bf4 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -324,6 +324,7 @@ Describes where code is deployed for a project
 | Name  | Type  | Description |
 | ---   |  ---- | ----------  |
 | `id` | ID! | ID of the environment |
+| `metricsDashboard` | MetricsDashboard | Metrics dashboard schema for the environment |
 | `name` | String! | Human-readable name of the environment |
 | `state` | String! | State of the environment, for example: available/stopped |
 
@@ -815,6 +816,12 @@ Autogenerated return type of MergeRequestSetWip
 | `revision` | String! | Revision |
 | `version` | String! | Version |
 
+## MetricsDashboard
+
+| Name  | Type  | Description |
+| ---   |  ---- | ----------  |
+| `path` | String | Path to a file with the dashboard definition |
+
 ## Milestone
 
 Represents a milestone.
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index 48791e201eb..8c71a27540d 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -721,7 +721,7 @@ For example, in order to test new index you can do the following:
 Create the index:
 
 ```sql
-exec CREATE INDEX index_projects_marked_for_deletion ON projects (marked_for_deletion_at) WHERE marked_for_deletion_at IS NOT NULL
+exec CREATE INDEX index_projects_last_activity ON projects (last_activity_at) WHERE last_activity_at IS NOT NULL
 ```
 
 Analyze the table to update its statistics:
@@ -733,7 +733,7 @@ exec ANALYZE projects
 Get the query plan:
 
 ```sql
-explain SELECT * FROM projects WHERE marked_for_deletion_at < CURRENT_DATE
+explain SELECT * FROM projects WHERE last_activity_at < CURRENT_DATE
 ```
 
 Once done you can rollback your changes:
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 4b07ecd2743..88a9bd40f07 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -309,6 +309,7 @@ The following tables outline the details of expected properties.
 | `label` | string | no, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. Can contain time series labels as interpolated variables. |
 | `query` | string | yes if `query_range` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
 | `query_range` | string | yes if `query` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+| `step` | number | no, value is calculated if not defined | Defines query resolution step width in float number of seconds. Metrics on the same panel should use the same `step` value. |
 
 ##### Dynamic labels
 
diff --git a/lib/gitlab/alerting/alert.rb b/lib/gitlab/alerting/alert.rb
index f6f52e392c6..7d97bd1bb52 100644
--- a/lib/gitlab/alerting/alert.rb
+++ b/lib/gitlab/alerting/alert.rb
@@ -21,6 +21,12 @@ module Gitlab
         end
       end
 
+      def gitlab_prometheus_alert_id
+        strong_memoize(:gitlab_prometheus_alert_id) do
+          payload&.dig('labels', 'gitlab_prometheus_alert_id')
+        end
+      end
+
       def title
         strong_memoize(:title) do
           gitlab_alert&.title || parse_title_from_payload
@@ -120,12 +126,19 @@ module Gitlab
       end
 
       def parse_gitlab_alert_from_payload
-        return unless metric_id
+        alerts_found = matching_gitlab_alerts
+
+        return if alerts_found.blank? || alerts_found.size > 1
+
+        alerts_found.first
+      end
+
+      def matching_gitlab_alerts
+        return unless metric_id || gitlab_prometheus_alert_id
 
         Projects::Prometheus::AlertsFinder
-          .new(project: project, metric: metric_id)
+          .new(project: project, metric: metric_id, id: gitlab_prometheus_alert_id)
           .execute
-          .first
       end
 
       def parse_title_from_payload
diff --git a/lib/gitlab/diff/highlight_cache.rb b/lib/gitlab/diff/highlight_cache.rb
index e79127108b4..055eae2c0fd 100644
--- a/lib/gitlab/diff/highlight_cache.rb
+++ b/lib/gitlab/diff/highlight_cache.rb
@@ -14,7 +14,7 @@ module Gitlab
 
       define_histogram :gitlab_redis_diff_caching_memory_usage_bytes do
         docstring 'Redis diff caching memory usage by key'
-        buckets [100, 1000, 10000, 100000, 1000000, 10000000]
+        buckets [100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000]
       end
 
       define_counter :gitlab_redis_diff_caching_hit do
diff --git a/lib/gitlab/import_export/group/import_export.yml b/lib/gitlab/import_export/group/import_export.yml
index 49b9e0f83d9..5008639077c 100644
--- a/lib/gitlab/import_export/group/import_export.yml
+++ b/lib/gitlab/import_export/group/import_export.yml
@@ -36,6 +36,9 @@ excluded_attributes:
     - :runners_token_encrypted
     - :saml_discovery_token
     - :visibility_level
+    - :trial_ends_on
+    - :shared_runners_minute_limit
+    - :extra_shared_runners_minutes_limit
   epics:
     - :state_id
 
diff --git a/spec/fixtures/group.json b/spec/fixtures/group.json
new file mode 100644
index 00000000000..86de34e2f3b
--- /dev/null
+++ b/spec/fixtures/group.json
@@ -0,0 +1 @@
+{"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2020-01-09 12:08:57 UTC","updated_at":"2020-01-09 12:08:57 UTC","description":"A voluptate non sequi temporibus quam at.","avatar":{"url":null},"membership_lock":false,"share_with_group_lock":false,"visibility_level":20,"request_access_enabled":true,"ldap_sync_status":"ready","ldap_sync_error":null,"ldap_sync_last_update_at":null,"ldap_sync_last_successful_update_at":null,"ldap_sync_last_sync_at":null,"lfs_enabled":null,"parent_id":null,"repository_size_limit":null,"require_two_factor_authentication":false,"two_factor_grace_period":48,"plan_id":null,"project_creation_level":2,"file_template_project_id":null,"saml_discovery_token":"JZGRJbe3","custom_project_templates_group_id":null,"auto_devops_enabled":null,"last_ci_minutes_notification_at":null,"last_ci_minutes_usage_notification_level":null,"subgroup_creation_level":1,"emails_disabled":null,"max_pages_size":null,"max_artifacts_size":null,"mentions_disabled":null,"milestones":[],"badges":[],"labels":[{"id":69,"title":"Amfunc","color":"#2658d0","project_id":null,"created_at":"2020-01-09T12:08:57.693Z","updated_at":"2020-01-09T12:08:57.693Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"},{"id":68,"title":"Aquacell","color":"#f658e0","project_id":null,"created_at":"2020-01-09T12:08:57.688Z","updated_at":"2020-01-09T12:08:57.688Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#333333"},{"id":62,"title":"Bryns","color":"#16f0b9","project_id":null,"created_at":"2020-01-09T12:08:57.672Z","updated_at":"2020-01-09T12:08:57.672Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"},{"id":61,"title":"Pephfunc","color":"#556319","project_id":null,"created_at":"2020-01-09T12:08:57.669Z","updated_at":"2020-01-09T12:08:57.669Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"},{"id":67,"title":"Sinesync","color":"#4c44e8","project_id":null,"created_at":"2020-01-09T12:08:57.685Z","updated_at":"2020-01-09T12:08:57.685Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"},{"id":65,"title":"Traphwood","color":"#71b900","project_id":null,"created_at":"2020-01-09T12:08:57.679Z","updated_at":"2020-01-09T12:08:57.679Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"},{"id":66,"title":"Tricell","color":"#179ad3","project_id":null,"created_at":"2020-01-09T12:08:57.683Z","updated_at":"2020-01-09T12:08:57.683Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"},{"id":63,"title":"Trienceforge","color":"#cb1e0e","project_id":null,"created_at":"2020-01-09T12:08:57.674Z","updated_at":"2020-01-09T12:08:57.674Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"},{"id":64,"title":"Trinix","color":"#66ca41","project_id":null,"created_at":"2020-01-09T12:08:57.677Z","updated_at":"2020-01-09T12:08:57.677Z","template":false,"description":null,"group_id":28,"type":"GroupLabel","priorities":[],"textColor":"#FFFFFF"}],"boards":[],"members":[{"id":8,"access_level":10,"source_id":28,"source_type":"Namespace","user_id":13,"notification_level":3,"created_at":"2020-01-09T12:08:57.884Z","updated_at":"2020-01-09T12:08:57.884Z","created_by_id":null,"invite_email":null,"invite_token":null,"invite_accepted_at":null,"requested_at":null,"expires_at":null,"ldap":false,"override":false,"user":{"id":13,"email":"lorraine@leannon.info","username":"elina.lakin"}},{"id":7,"access_level":30,"source_id":28,"source_type":"Namespace","user_id":1,"notification_level":3,"created_at":"2020-01-09T12:08:57.129Z","updated_at":"2020-01-09T12:08:57.898Z","created_by_id":null,"invite_email":null,"invite_token":null,"invite_accepted_at":null,"requested_at":null,"expires_at":null,"ldap":false,"override":false,"user":{"id":1,"email":"admin@example.com","username":"root"}},{"id":9,"access_level":20,"source_id":28,"source_type":"Namespace","user_id":8,"notification_level":3,"created_at":"2020-01-09T12:08:57.901Z","updated_at":"2020-01-09T12:08:57.901Z","created_by_id":null,"invite_email":null,"invite_token":null,"invite_accepted_at":null,"requested_at":null,"expires_at":null,"ldap":false,"override":false,"user":{"id":8,"email":"euna_schroeder@bernier.us","username":"loreen_medhurst"}},{"id":10,"access_level":40,"source_id":28,"source_type":"Namespace","user_id":10,"notification_level":3,"created_at":"2020-01-09T12:08:57.918Z","updated_at":"2020-01-09T12:08:57.918Z","created_by_id":null,"invite_email":null,"invite_token":null,"invite_accepted_at":null,"requested_at":null,"expires_at":null,"ldap":false,"override":false,"user":{"id":10,"email":"glady_green@prohaska.biz","username":"maia_kuhlman"}}],"epics":[{"id":31,"group_id":28,"author_id":13,"assignee_id":null,"iid":1,"updated_by_id":null,"last_edited_by_id":null,"lock_version":0,"start_date":null,"end_date":null,"last_edited_at":null,"created_at":"2020-01-09T12:11:12.006Z","updated_at":"2020-01-09T12:11:12.006Z","title":"Optio nihil consequatur et vel quaerat cupiditate.","description":"Vel molestias repellendus id voluptatem et non. Blanditiis natus veritatis adipisci qui illo. Sed est veritatis facilis dolore voluptas quibusdam aliquam omnis.\n\nFacere delectus quo architecto explicabo dolores. Quis odit nostrum nobis consequuntur cumque est officiis. Maiores ipsa nobis minus incidunt provident.\n\nVoluptate voluptatem debitis facilis architecto commodi vero. Sit quibusdam consectetur ad quis cupiditate. Exercitationem quasi inventore incidunt itaque optio autem accusantium.","start_date_sourcing_milestone_id":null,"due_date_sourcing_milestone_id":null,"start_date_fixed":null,"due_date_fixed":null,"start_date_is_fixed":null,"due_date_is_fixed":null,"closed_by_id":null,"closed_at":null,"parent_id":null,"relative_position":null,"state_id":"opened","start_date_sourcing_epic_id":null,"due_date_sourcing_epic_id":null,"notes":[]},{"id":32,"group_id":28,"author_id":1,"assignee_id":null,"iid":2,"updated_by_id":null,"last_edited_by_id":null,"lock_version":0,"start_date":null,"end_date":null,"last_edited_at":null,"created_at":"2020-01-09T12:11:12.021Z","updated_at":"2020-01-09T12:11:12.021Z","title":"Maxime cumque totam deleniti excepturi iusto qui sint.","description":"Nesciunt ut sit voluptas iusto rem. Sint doloribus ex et aliquam ea. Nihil necessitatibus distinctio similique incidunt. Consequatur voluptatibus excepturi qui quis quia. Quia optio quod non magnam dolorem id molestiae dignissimos.\n\nAt eligendi fuga veritatis accusamus quo dolores. Eos amet dolores nisi illo voluptatem consequuntur alias. Dolor veniam quas est sed. Ex corporis soluta sit ducimus facere et.\n\nEst odit asperiores vel quae quibusdam maiores quod. Debitis libero quo sed sunt voluptas praesentium. Ut nisi qui et culpa. Consequatur atque aut molestiae sint. Quaerat expedita animi distinctio repellat sed.","start_date_sourcing_milestone_id":null,"due_date_sourcing_milestone_id":null,"start_date_fixed":null,"due_date_fixed":null,"start_date_is_fixed":null,"due_date_is_fixed":null,"closed_by_id":null,"closed_at":null,"parent_id":null,"relative_position":null,"state_id":"opened","start_date_sourcing_epic_id":null,"due_date_sourcing_epic_id":null,"notes":[]},{"id":33,"group_id":28,"author_id":8,"assignee_id":null,"iid":3,"updated_by_id":null,"last_edited_by_id":null,"lock_version":0,"start_date":null,"end_date":null,"last_edited_at":null,"created_at":"2020-01-09T12:11:12.035Z","updated_at":"2020-01-09T12:11:12.035Z","title":"Corrupti perferendis labore omnis pariatur dolores aut repellendus quisquam id.","description":"Eligendi est asperiores minus nulla unde ex. Aspernatur repellat vel deleniti molestiae occaecati odio dicta. Ipsam excepturi quia modi ut autem sed. Omnis quae quam quas ut. Et corrupti aut omnis blanditiis.\n\nDolore dolorem accusamus tempore nihil dolores repellendus sed provident. Commodi consectetur blanditiis et consequatur laborum nulla eveniet facilis. Inventore aut officiis fuga est maiores rerum. Sint aspernatur et necessitatibus omnis qui et blanditiis.\n\nFugiat molestias nulla eos labore rerum perspiciatis non. Minus est et ipsum quis animi et. Id praesentium vitae in eum fugiat incidunt. Voluptas ut nemo ut cum saepe quasi dolores unde.","start_date_sourcing_milestone_id":null,"due_date_sourcing_milestone_id":null,"start_date_fixed":null,"due_date_fixed":null,"start_date_is_fixed":null,"due_date_is_fixed":null,"closed_by_id":null,"closed_at":null,"parent_id":null,"relative_position":null,"state_id":"opened","start_date_sourcing_epic_id":null,"due_date_sourcing_epic_id":null,"notes":[]},{"id":34,"group_id":28,"author_id":10,"assignee_id":null,"iid":4,"updated_by_id":null,"last_edited_by_id":null,"lock_version":0,"start_date":null,"end_date":null,"last_edited_at":null,"created_at":"2020-01-09T12:11:12.049Z","updated_at":"2020-01-09T12:11:12.049Z","title":"Architecto ipsa autem ducimus velit harum doloribus odio fugiat.","description":"Eos perferendis commodi labore id. Ipsa esse non voluptatem sit. Odit et et maiores quis adipisci sed earum.\n\nRerum asperiores consequuntur sed odit. Quis architecto nobis dolor sit consectetur. Inventore praesentium est magnam et reprehenderit impedit occaecati similique.\n\nTemporibus culpa ratione vel facilis perspiciatis rerum. Quisquam ut veniam quia sit totam. Quo ipsam quam dolorum quis. Totam porro nesciunt ut temporibus.","start_date_sourcing_milestone_id":null,"due_date_sourcing_milestone_id":null,"start_date_fixed":null,"due_date_fixed":null,"start_date_is_fixed":null,"due_date_is_fixed":null,"closed_by_id":null,"closed_at":null,"parent_id":null,"relative_position":null,"state_id":"opened","start_date_sourcing_epic_id":null,"due_date_sourcing_epic_id":null,"notes":[]},{"id":35,"group_id":28,"author_id":10,"assignee_id":null,"iid":5,"updated_by_id":null,"last_edited_by_id":null,"lock_version":0,"start_date":null,"end_date":null,"last_edited_at":null,"created_at":"2020-01-09T12:11:12.062Z","updated_at":"2020-01-09T12:11:12.062Z","title":"Accusantium illo a sint nobis qui explicabo eum iusto facere aut.","description":"Modi laudantium possimus beatae dolorem molestias ut neque. Voluptatem libero harum id cupiditate. Officia et enim quia dolores at adipisci.\n\nEx officia quia at et vel aut. Esse consequatur officiis magnam vel velit eius. Sit cumque quo qui laborum tempore rerum in.\n\nQuibusdam provident quis molestiae laborum odio commodi. Impedit iure voluptatem possimus necessitatibus et error non. Iure est et ipsa recusandae et.","start_date_sourcing_milestone_id":null,"due_date_sourcing_milestone_id":null,"start_date_fixed":null,"due_date_fixed":null,"start_date_is_fixed":null,"due_date_is_fixed":null,"closed_by_id":null,"closed_at":null,"parent_id":null,"relative_position":null,"state_id":"opened","start_date_sourcing_epic_id":null,"due_date_sourcing_epic_id":null,"notes":[]}]}
diff --git a/spec/fixtures/group_export.tar.gz b/spec/fixtures/group_export.tar.gz
index d76c6ddba25c1ef05c93368b02f6ce2d645408b7..5f5fd989f752e49bbba88fc295f34b2ab823d571 100644
GIT binary patch
literal 3546
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
U75qjTY2>)%e?sAhMgV9405dfCMgRZ+

literal 2795
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

diff --git a/spec/fixtures/group_export_invalid_subrelations.tar.gz b/spec/fixtures/group_export_invalid_subrelations.tar.gz
index 6a8f1453517aeced527c6c54ff472639e8b5d63c..6844d1662602f223f2d29b894e58fc2c0813569d 100644
GIT binary patch
literal 3602
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
Y-Ru6XL`NNU^qA5A0h8nH<N#^_0Bhz52><{9

literal 3036
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

diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/complex/group.json b/spec/fixtures/lib/gitlab/import_export/group_exports/complex/group.json
index 001bf7b43c1..a5a85dc661f 100644
--- a/spec/fixtures/lib/gitlab/import_export/group_exports/complex/group.json
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/complex/group.json
@@ -19,18 +19,15 @@
   "ldap_sync_last_sync_at": null,
   "lfs_enabled": null,
   "parent_id": 7,
-  "shared_runners_minutes_limit": null,
   "repository_size_limit": null,
   "require_two_factor_authentication": false,
   "two_factor_grace_period": 48,
   "plan_id": null,
   "project_creation_level": 2,
-  "trial_ends_on": null,
   "file_template_project_id": null,
   "saml_discovery_token": "rBKx3ioz",
   "custom_project_templates_group_id": null,
   "auto_devops_enabled": null,
-  "extra_shared_runners_minutes_limit": null,
   "last_ci_minutes_notification_at": null,
   "last_ci_minutes_usage_notification_level": null,
   "runners_token": "token",
@@ -1075,18 +1072,15 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 4351,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "saml_discovery_token": "ki3Xnjw3",
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
@@ -1649,18 +1643,15 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 4351,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "saml_discovery_token": "m7cx4AZi",
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/no_children/group.json b/spec/fixtures/lib/gitlab/import_export/group_exports/no_children/group.json
index 3ffa899405e..158cd22ed2f 100644
--- a/spec/fixtures/lib/gitlab/import_export/group_exports/no_children/group.json
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/no_children/group.json
@@ -19,18 +19,15 @@
   "ldap_sync_last_sync_at": null,
   "lfs_enabled": null,
   "parent_id": null,
-  "shared_runners_minutes_limit": null,
   "repository_size_limit": null,
   "require_two_factor_authentication": false,
   "two_factor_grace_period": 48,
   "plan_id": null,
   "project_creation_level": 2,
-  "trial_ends_on": null,
   "file_template_project_id": null,
   "saml_discovery_token": "rBKx3ioz",
   "custom_project_templates_group_id": null,
   "auto_devops_enabled": null,
-  "extra_shared_runners_minutes_limit": null,
   "last_ci_minutes_notification_at": null,
   "last_ci_minutes_usage_notification_level": null,
   "subgroup_creation_level": 1,
diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/internal/group.json b/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/internal/group.json
index f747088f87e..01dc44a28d5 100644
--- a/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/internal/group.json
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/internal/group.json
@@ -20,17 +20,14 @@
   "ldap_sync_last_sync_at": null,
   "lfs_enabled": null,
   "parent_id": null,
-  "shared_runners_minutes_limit": null,
   "repository_size_limit": null,
   "require_two_factor_authentication": false,
   "two_factor_grace_period": 48,
   "plan_id": null,
   "project_creation_level": 2,
-  "trial_ends_on": null,
   "file_template_project_id": null,
   "custom_project_templates_group_id": null,
   "auto_devops_enabled": null,
-  "extra_shared_runners_minutes_limit": null,
   "last_ci_minutes_notification_at": null,
   "last_ci_minutes_usage_notification_level": null,
   "subgroup_creation_level": 1,
@@ -61,17 +58,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
@@ -102,17 +96,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
@@ -143,17 +134,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/private/group.json b/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/private/group.json
index 1328e596fa5..c9323f27770 100644
--- a/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/private/group.json
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/private/group.json
@@ -20,17 +20,14 @@
   "ldap_sync_last_sync_at": null,
   "lfs_enabled": null,
   "parent_id": null,
-  "shared_runners_minutes_limit": null,
   "repository_size_limit": null,
   "require_two_factor_authentication": false,
   "two_factor_grace_period": 48,
   "plan_id": null,
   "project_creation_level": 2,
-  "trial_ends_on": null,
   "file_template_project_id": null,
   "custom_project_templates_group_id": null,
   "auto_devops_enabled": null,
-  "extra_shared_runners_minutes_limit": null,
   "last_ci_minutes_notification_at": null,
   "last_ci_minutes_usage_notification_level": null,
   "subgroup_creation_level": 1,
@@ -61,17 +58,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
@@ -102,17 +96,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
@@ -143,17 +134,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
diff --git a/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/public/group.json b/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/public/group.json
index 29020e92004..b4f746b28e2 100644
--- a/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/public/group.json
+++ b/spec/fixtures/lib/gitlab/import_export/group_exports/visibility_levels/public/group.json
@@ -20,17 +20,14 @@
   "ldap_sync_last_sync_at": null,
   "lfs_enabled": null,
   "parent_id": null,
-  "shared_runners_minutes_limit": null,
   "repository_size_limit": null,
   "require_two_factor_authentication": false,
   "two_factor_grace_period": 48,
   "plan_id": null,
   "project_creation_level": 2,
-  "trial_ends_on": null,
   "file_template_project_id": null,
   "custom_project_templates_group_id": null,
   "auto_devops_enabled": null,
-  "extra_shared_runners_minutes_limit": null,
   "last_ci_minutes_notification_at": null,
   "last_ci_minutes_usage_notification_level": null,
   "subgroup_creation_level": 1,
@@ -61,17 +58,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
@@ -102,17 +96,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
@@ -143,17 +134,14 @@
       "ldap_sync_last_sync_at": null,
       "lfs_enabled": null,
       "parent_id": 283,
-      "shared_runners_minutes_limit": null,
       "repository_size_limit": null,
       "require_two_factor_authentication": false,
       "two_factor_grace_period": 48,
       "plan_id": null,
       "project_creation_level": 2,
-      "trial_ends_on": null,
       "file_template_project_id": null,
       "custom_project_templates_group_id": null,
       "auto_devops_enabled": null,
-      "extra_shared_runners_minutes_limit": null,
       "last_ci_minutes_notification_at": null,
       "last_ci_minutes_usage_notification_level": null,
       "subgroup_creation_level": 1,
diff --git a/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
new file mode 100644
index 00000000000..6a8eb8a65af
--- /dev/null
+++ b/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::Metrics::DashboardResolver do
+  include GraphqlHelpers
+
+  let_it_be(:current_user) { create(:user) }
+
+  describe '#resolve' do
+    subject(:resolve_dashboard) { resolve(described_class, obj: parent_object, args: args, ctx: { current_user: current_user }) }
+
+    let(:args) do
+      {
+        path: 'config/prometheus/common_metrics.yml'
+      }
+    end
+
+    context 'for environment' do
+      let(:project) { create(:project) }
+      let(:parent_object) { create(:environment, project: project) }
+
+      before do
+        project.add_developer(current_user)
+      end
+
+      it 'use ActiveModel class to find matching dashboard', :aggregate_failures do
+        expected_arguments = { project: project, user: current_user, path: args[:path], options: { environment: parent_object } }
+
+        expect(PerformanceMonitoring::PrometheusDashboard).to receive(:find_for).with(expected_arguments).and_return(PerformanceMonitoring::PrometheusDashboard.new)
+        expect(resolve_dashboard).to be_instance_of PerformanceMonitoring::PrometheusDashboard
+      end
+
+      context 'without parent object' do
+        let(:parent_object) { nil }
+
+        it 'returns nil', :aggregate_failures do
+          expect(PerformanceMonitoring::PrometheusDashboard).not_to receive(:find_for)
+          expect(resolve_dashboard).to be_nil
+        end
+      end
+    end
+  end
+end
diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb
index b3711fa2f5c..24a8bddfa6a 100644
--- a/spec/graphql/types/environment_type_spec.rb
+++ b/spec/graphql/types/environment_type_spec.rb
@@ -7,7 +7,7 @@ describe GitlabSchema.types['Environment'] do
 
   it 'has the expected fields' do
     expected_fields = %w[
-      name id state
+      name id state metrics_dashboard
     ]
 
     expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/metrics/dashboard_type_spec.rb b/spec/graphql/types/metrics/dashboard_type_spec.rb
new file mode 100644
index 00000000000..4795fd77537
--- /dev/null
+++ b/spec/graphql/types/metrics/dashboard_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['MetricsDashboard'] do
+  it { expect(described_class.graphql_name).to eq('MetricsDashboard') }
+
+  it 'has the expected fields' do
+    expected_fields = %w[
+      path
+    ]
+
+    expect(described_class).to have_graphql_fields(*expected_fields)
+  end
+end
diff --git a/spec/lib/gitlab/alerting/alert_spec.rb b/spec/lib/gitlab/alerting/alert_spec.rb
index 8b67cf949ce..6d97f08af91 100644
--- a/spec/lib/gitlab/alerting/alert_spec.rb
+++ b/spec/lib/gitlab/alerting/alert_spec.rb
@@ -9,11 +9,14 @@ describe Gitlab::Alerting::Alert do
   let(:payload) { {} }
 
   shared_context 'gitlab alert' do
-    let(:gitlab_alert_id) { gitlab_alert.prometheus_metric_id.to_s }
     let!(:gitlab_alert) { create(:prometheus_alert, project: project) }
+    let(:gitlab_alert_id) { gitlab_alert.id }
 
     before do
-      payload['labels'] = { 'gitlab_alert_id' => gitlab_alert_id }
+      payload['labels'] = {
+        'gitlab_alert_id' => gitlab_alert.prometheus_metric_id.to_s,
+        'gitlab_prometheus_alert_id' => gitlab_alert_id
+      }
     end
   end
 
@@ -68,6 +71,41 @@ describe Gitlab::Alerting::Alert do
 
       it { is_expected.to be_nil }
     end
+
+    context 'when two alerts with the same metric exist' do
+      include_context 'gitlab alert'
+
+      let!(:second_gitlab_alert) do
+        create(:prometheus_alert,
+          project: project,
+          prometheus_metric_id: gitlab_alert.prometheus_metric_id
+        )
+      end
+
+      context 'alert id given in params' do
+        before do
+          payload['labels'] = {
+            'gitlab_alert_id' => gitlab_alert.prometheus_metric_id.to_s,
+            'gitlab_prometheus_alert_id' => second_gitlab_alert.id
+          }
+        end
+
+        it { is_expected.to eq(second_gitlab_alert) }
+      end
+
+      context 'metric id given in params' do
+        # This tests the case when two alerts are found, as metric id
+        # is not unique.
+
+        # Note the metric id was incorrectly named as 'gitlab_alert_id'
+        # in PrometheusAlert#to_param.
+        before do
+          payload['labels'] = { 'gitlab_alert_id' => gitlab_alert.prometheus_metric_id }
+        end
+
+        it { is_expected.to be_nil }
+      end
+    end
   end
 
   describe '#title' do
diff --git a/spec/models/prometheus_alert_spec.rb b/spec/models/prometheus_alert_spec.rb
index cdcdb46a6c4..1409cf65fee 100644
--- a/spec/models/prometheus_alert_spec.rb
+++ b/spec/models/prometheus_alert_spec.rb
@@ -96,7 +96,8 @@ describe PrometheusAlert do
         "for" => "5m",
         "labels" => {
           "gitlab" => "hook",
-          "gitlab_alert_id" => metric.id
+          "gitlab_alert_id" => metric.id,
+          "gitlab_prometheus_alert_id" => subject.id
         })
     end
   end
diff --git a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
new file mode 100644
index 00000000000..8b0965a815b
--- /dev/null
+++ b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Getting Metrics Dashboard' do
+  include GraphqlHelpers
+
+  let_it_be(:current_user) { create(:user) }
+  let(:project) { create(:project) }
+  let!(:environment) { create(:environment, project: project) }
+
+  let(:fields) do
+    <<~QUERY
+      #{all_graphql_fields_for('MetricsDashboard'.classify)}
+    QUERY
+  end
+
+  let(:query) do
+    %(
+      query {
+        project(fullPath:"#{project.full_path}") {
+          environments(name: "#{environment.name}") {
+            nodes {
+              metricsDashboard(path: "#{path}"){
+                #{fields}
+              }
+            }
+          }
+        }
+      }
+    )
+  end
+
+  context 'for anonymous user' do
+    before do
+      post_graphql(query, current_user: current_user)
+    end
+
+    context 'requested dashboard is available' do
+      let(:path) { 'config/prometheus/common_metrics.yml' }
+
+      it_behaves_like 'a working graphql query'
+
+      it 'returns nil' do
+        dashboard = graphql_data.dig('project', 'environments', 'nodes')
+
+        expect(dashboard).to be_nil
+      end
+    end
+  end
+
+  context 'for user with developer access' do
+    before do
+      project.add_developer(current_user)
+      post_graphql(query, current_user: current_user)
+    end
+
+    context 'requested dashboard is available' do
+      let(:path) { 'config/prometheus/common_metrics.yml' }
+
+      it_behaves_like 'a working graphql query'
+
+      it 'returns metrics dashboard' do
+        dashboard = graphql_data.dig('project', 'environments', 'nodes')[0]['metricsDashboard']
+
+        expect(dashboard).to eql("path" => path)
+      end
+    end
+
+    context 'requested dashboard can not be found' do
+      let(:path) { 'config/prometheus/i_am_not_here.yml' }
+
+      it_behaves_like 'a working graphql query'
+
+      it 'return snil' do
+        dashboard = graphql_data.dig('project', 'environments', 'nodes')[0]['metricsDashboard']
+
+        expect(dashboard).to be_nil
+      end
+    end
+  end
+end
-- 
2.30.9