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