Commit 0ccf5b4a authored by Pavel Shutsin's avatar Pavel Shutsin

Add Time to Restore Service DORA metric

Adds Time to Restore Service DORA metric to
our Rest API and GraphQL API.
No data backfill support for now.

Changelog: added
EE: true
parent a29830c9
# frozen_string_literal: true
class AddTimeToRestoreServiceDoraMetric < Gitlab::Database::Migration[1.0]
def change
add_column :dora_daily_metrics, :time_to_restore_service_in_seconds, :integer
end
end
# frozen_string_literal: true
class AddIndexOnIssuesClosedIncidents < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
INDEX_NAME = 'index_on_issues_closed_incidents_by_project_id_and_closed_at'
def up
add_concurrent_index :issues, [:project_id, :closed_at], where: "issue_type = 1 AND state_id = 2", name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :issues, INDEX_NAME
end
end
3385dc0dc2a3d306e01a719b7a21197ea8468976d37abab932beade4780bb4ff
\ No newline at end of file
9e62675366f9c2f0fc159a9748409dbcaea240c813ab19ea26d24c966e5fd6c8
\ No newline at end of file
...@@ -14461,6 +14461,7 @@ CREATE TABLE dora_daily_metrics ( ...@@ -14461,6 +14461,7 @@ CREATE TABLE dora_daily_metrics (
date date NOT NULL, date date NOT NULL,
deployment_frequency integer, deployment_frequency integer,
lead_time_for_changes_in_seconds integer, lead_time_for_changes_in_seconds integer,
time_to_restore_service_in_seconds integer,
CONSTRAINT dora_daily_metrics_deployment_frequency_positive CHECK ((deployment_frequency >= 0)), CONSTRAINT dora_daily_metrics_deployment_frequency_positive CHECK ((deployment_frequency >= 0)),
CONSTRAINT dora_daily_metrics_lead_time_for_changes_in_seconds_positive CHECK ((lead_time_for_changes_in_seconds >= 0)) CONSTRAINT dora_daily_metrics_lead_time_for_changes_in_seconds_positive CHECK ((lead_time_for_changes_in_seconds >= 0))
); );
...@@ -28291,6 +28292,8 @@ CREATE INDEX index_on_identities_lower_extern_uid_and_provider ON identities USI ...@@ -28291,6 +28292,8 @@ CREATE INDEX index_on_identities_lower_extern_uid_and_provider ON identities USI
CREATE UNIQUE INDEX index_on_instance_statistics_recorded_at_and_identifier ON analytics_usage_trends_measurements USING btree (identifier, recorded_at); CREATE UNIQUE INDEX index_on_instance_statistics_recorded_at_and_identifier ON analytics_usage_trends_measurements USING btree (identifier, recorded_at);
CREATE INDEX index_on_issues_closed_incidents_by_project_id_and_closed_at ON issues USING btree (project_id, closed_at) WHERE ((issue_type = 1) AND (state_id = 2));
CREATE INDEX index_on_label_links_all_columns ON label_links USING btree (target_id, label_id, target_type); CREATE INDEX index_on_label_links_all_columns ON label_links USING btree (target_id, label_id, target_type);
CREATE INDEX index_on_merge_request_assignees_state ON merge_request_assignees USING btree (state) WHERE (state = 2); CREATE INDEX index_on_merge_request_assignees_state ON merge_request_assignees USING btree (state) WHERE (state = 2);
...@@ -9,6 +9,7 @@ type: reference, api ...@@ -9,6 +9,7 @@ type: reference, api
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in GitLab 13.10. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in GitLab 13.10.
> - The legacy key/value pair `{ "<date>" => "<value>" }` was removed from the payload in GitLab 14.0. > - The legacy key/value pair `{ "<date>" => "<value>" }` was removed from the payload in GitLab 14.0.
> `time_to_restore_service` metric was introduced in GitLab 14.9.
All methods require at least the Reporter role. All methods require at least the Reporter role.
...@@ -20,14 +21,14 @@ Get project-level DORA metrics. ...@@ -20,14 +21,14 @@ Get project-level DORA metrics.
GET /projects/:id/dora/metrics GET /projects/:id/dora/metrics
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
|-------------- |-------- |----------|----------------------- | |-------------- |-------- |----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../index.md#namespaced-path-encoding) can be accessed by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](../index.md#namespaced-path-encoding) can be accessed by the authenticated user. |
| `metric` | string | yes | The [metric name](../../user/analytics/ci_cd_analytics.md#supported-metrics-in-gitlab). One of `deployment_frequency` or `lead_time_for_changes`. | | `metric` | string | yes | The [metric name](../../user/analytics/ci_cd_analytics.md#supported-metrics-in-gitlab). One of `deployment_frequency`, `lead_time_for_changes` or `time_to_restore_service`.|
| `start_date` | string | no | Date range to start from. ISO 8601 Date format, for example `2021-03-01`. Default is 3 months ago. | | `start_date` | string | no | Date range to start from. ISO 8601 Date format, for example `2021-03-01`. Default is 3 months ago. |
| `end_date` | string | no | Date range to end at. ISO 8601 Date format, for example `2021-03-01`. Default is the current date. | | `end_date` | string | no | Date range to end at. ISO 8601 Date format, for example `2021-03-01`. Default is the current date. |
| `interval` | string | no | The bucketing interval. One of `all`, `monthly` or `daily`. Default is `daily`. | | `interval` | string | no | The bucketing interval. One of `all`, `monthly` or `daily`. Default is `daily`. |
| `environment_tier` | string | no | The [tier of the environment](../../ci/environments/index.md#deployment-tier-of-environments). Default is `production`. | | `environment_tier` | string | no | The [tier of the environment](../../ci/environments/index.md#deployment-tier-of-environments). Default is `production`. |
Example request: Example request:
...@@ -63,7 +64,7 @@ GET /groups/:id/dora/metrics ...@@ -63,7 +64,7 @@ GET /groups/:id/dora/metrics
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
|-------------- |-------- |----------|----------------------- | |-------------- |-------- |----------|----------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../index.md#namespaced-path-encoding) can be accessed by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](../index.md#namespaced-path-encoding) can be accessed by the authenticated user. |
| `metric` | string | yes | The [metric name](../../user/analytics/ci_cd_analytics.md#supported-metrics-in-gitlab). One of `deployment_frequency` or `lead_time_for_changes`. | | `metric` | string | yes | The [metric name](../../user/analytics/ci_cd_analytics.md#supported-metrics-in-gitlab). One of `deployment_frequency`, `lead_time_for_changes` or `time_to_restore_service`. |
| `start_date` | string | no | Date range to start from. ISO 8601 Date format, for example `2021-03-01`. Default is 3 months ago. | | `start_date` | string | no | Date range to start from. ISO 8601 Date format, for example `2021-03-01`. Default is 3 months ago. |
| `end_date` | string | no | Date range to end at. ISO 8601 Date format, for example `2021-03-01`. Default is the current date. | | `end_date` | string | no | Date range to end at. ISO 8601 Date format, for example `2021-03-01`. Default is the current date. |
| `interval` | string | no | The bucketing interval. One of `all`, `monthly` or `daily`. Default is `daily`. | | `interval` | string | no | The bucketing interval. One of `all`, `monthly` or `daily`. Default is `daily`. |
...@@ -97,6 +98,7 @@ API response has a different meaning depending on the provided `metric` query ...@@ -97,6 +98,7 @@ API response has a different meaning depending on the provided `metric` query
parameter: parameter:
| `metric` query parameter | Description of `value` in response | | `metric` query parameter | Description of `value` in response |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------------------------ |--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `deployment_frequency` | The number of successful deployments during the time period. | | `deployment_frequency` | The number of successful deployments during the time period. |
| `lead_time_for_changes` | The median number of seconds between the merge of the merge request (MR) and the deployment of the MR's commits for all MRs deployed during the time period. | | `lead_time_for_changes` | The median number of seconds between the merge of the merge request (MR) and the deployment of the MR's commits for all MRs deployed during the time period. |
| `time_to_restore_service` | The median number of seconds an incident was open during the time period. Available only for production environment |
...@@ -18098,6 +18098,7 @@ All supported DORA metric types. ...@@ -18098,6 +18098,7 @@ All supported DORA metric types.
| ----- | ----------- | | ----- | ----------- |
| <a id="dorametrictypedeployment_frequency"></a>`DEPLOYMENT_FREQUENCY` | Deployment frequency. | | <a id="dorametrictypedeployment_frequency"></a>`DEPLOYMENT_FREQUENCY` | Deployment frequency. |
| <a id="dorametrictypelead_time_for_changes"></a>`LEAD_TIME_FOR_CHANGES` | Lead time for changes. | | <a id="dorametrictypelead_time_for_changes"></a>`LEAD_TIME_FOR_CHANGES` | Lead time for changes. |
| <a id="dorametrictypetime_to_restore_service"></a>`TIME_TO_RESTORE_SERVICE` | Time to restore service. |
### `EntryType` ### `EntryType`
...@@ -5,7 +5,8 @@ module Types ...@@ -5,7 +5,8 @@ module Types
graphql_name 'DoraMetricType' graphql_name 'DoraMetricType'
description 'All supported DORA metric types.' description 'All supported DORA metric types.'
value 'DEPLOYMENT_FREQUENCY', description: 'Deployment frequency.', value: 'deployment_frequency' value 'DEPLOYMENT_FREQUENCY', description: 'Deployment frequency.', value: Dora::DailyMetrics::METRIC_DEPLOYMENT_FREQUENCY
value 'LEAD_TIME_FOR_CHANGES', description: 'Lead time for changes.', value: 'lead_time_for_changes' value 'LEAD_TIME_FOR_CHANGES', description: 'Lead time for changes.', value: Dora::DailyMetrics::METRIC_LEAD_TIME_FOR_CHANGES
value 'TIME_TO_RESTORE_SERVICE', description: 'Time to restore service.', value: Dora::DailyMetrics::METRIC_TIME_TO_RESTORE_SERVICE
end end
end end
...@@ -15,7 +15,8 @@ module Dora ...@@ -15,7 +15,8 @@ module Dora
INTERVAL_DAILY = 'daily' INTERVAL_DAILY = 'daily'
METRIC_DEPLOYMENT_FREQUENCY = 'deployment_frequency' METRIC_DEPLOYMENT_FREQUENCY = 'deployment_frequency'
METRIC_LEAD_TIME_FOR_CHANGES = 'lead_time_for_changes' METRIC_LEAD_TIME_FOR_CHANGES = 'lead_time_for_changes'
AVAILABLE_METRICS = [METRIC_DEPLOYMENT_FREQUENCY, METRIC_LEAD_TIME_FOR_CHANGES].freeze METRIC_TIME_TO_RESTORE_SERVICE = 'time_to_restore_service'
AVAILABLE_METRICS = [METRIC_DEPLOYMENT_FREQUENCY, METRIC_LEAD_TIME_FOR_CHANGES, METRIC_TIME_TO_RESTORE_SERVICE].freeze
AVAILABLE_INTERVALS = [INTERVAL_ALL, INTERVAL_MONTHLY, INTERVAL_DAILY].freeze AVAILABLE_INTERVALS = [INTERVAL_ALL, INTERVAL_MONTHLY, INTERVAL_DAILY].freeze
scope :for_environments, -> (environments) do scope :for_environments, -> (environments) do
...@@ -32,6 +33,7 @@ module Dora ...@@ -32,6 +33,7 @@ module Dora
deployment_frequency = deployment_frequency(environment, date) deployment_frequency = deployment_frequency(environment, date)
lead_time_for_changes = lead_time_for_changes(environment, date) lead_time_for_changes = lead_time_for_changes(environment, date)
time_to_restore_service = time_to_restore_service(environment, date)
# This query is concurrent safe upsert with the unique index. # This query is concurrent safe upsert with the unique index.
connection.execute(<<~SQL) connection.execute(<<~SQL)
...@@ -39,18 +41,21 @@ module Dora ...@@ -39,18 +41,21 @@ module Dora
environment_id, environment_id,
date, date,
deployment_frequency, deployment_frequency,
lead_time_for_changes_in_seconds lead_time_for_changes_in_seconds,
time_to_restore_service_in_seconds
) )
VALUES ( VALUES (
#{environment.id}, #{environment.id},
#{connection.quote(date.to_s)}, #{connection.quote(date.to_s)},
(#{deployment_frequency}), (#{deployment_frequency}),
(#{lead_time_for_changes}) (#{lead_time_for_changes}),
(#{time_to_restore_service})
) )
ON CONFLICT (environment_id, date) ON CONFLICT (environment_id, date)
DO UPDATE SET DO UPDATE SET
deployment_frequency = (#{deployment_frequency}), deployment_frequency = (#{deployment_frequency}),
lead_time_for_changes_in_seconds = (#{lead_time_for_changes}) lead_time_for_changes_in_seconds = (#{lead_time_for_changes}),
time_to_restore_service_in_seconds = (#{time_to_restore_service})
SQL SQL
end end
...@@ -84,6 +89,9 @@ module Dora ...@@ -84,6 +89,9 @@ module Dora
when METRIC_LEAD_TIME_FOR_CHANGES when METRIC_LEAD_TIME_FOR_CHANGES
# Median # Median
'(PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY lead_time_for_changes_in_seconds)) AS data' '(PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY lead_time_for_changes_in_seconds)) AS data'
when METRIC_TIME_TO_RESTORE_SERVICE
# Median
'(PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY time_to_restore_service_in_seconds)) AS data'
else else
raise ArgumentError, 'Unknown metric' raise ArgumentError, 'Unknown metric'
end end
...@@ -129,6 +137,20 @@ module Dora ...@@ -129,6 +137,20 @@ module Dora
deployments[:finished_at].lteq(date.end_of_day), deployments[:finished_at].lteq(date.end_of_day),
deployments[:status].eq(Deployment.statuses[:success])].reduce(&:and) deployments[:status].eq(Deployment.statuses[:success])].reduce(&:and)
end end
def time_to_restore_service(environment, date)
# Non-production environments are ignored as we assume all Incidents happen on production
# See https://gitlab.com/gitlab-org/gitlab/-/issues/299096#note_550275633 for details
return Arel.sql('NULL') unless environment.production?
Issue.incident.closed.select(
Arel.sql(
'PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY EXTRACT(EPOCH FROM (issues.closed_at - issues.created_at)))'
)
).where("closed_at >= ? AND closed_at <= ?", date.beginning_of_day, date.end_of_day)
.where(project_id: environment.project_id)
.to_sql
end
end end
end end
end end
...@@ -82,6 +82,18 @@ module EE ...@@ -82,6 +82,18 @@ module EE
after_transition do |issue| after_transition do |issue|
issue.refresh_blocking_and_blocked_issues_cache! issue.refresh_blocking_and_blocked_issues_cache!
end end
after_transition any => :closed do |issue|
next unless issue.incident?
related_production_env = issue.project.environments.production.first
next unless related_production_env
issue.run_after_commit do
::Dora::DailyMetrics::RefreshWorker.perform_async(related_production_env.id, issue.closed_at.to_date.to_s)
end
end
end end
end end
......
...@@ -6,7 +6,8 @@ RSpec.describe Types::DoraMetricTypeEnum do ...@@ -6,7 +6,8 @@ RSpec.describe Types::DoraMetricTypeEnum do
it 'includes a value for each DORA metric type' do it 'includes a value for each DORA metric type' do
expect(described_class.values).to match( expect(described_class.values).to match(
'DEPLOYMENT_FREQUENCY' => have_attributes(value: 'deployment_frequency'), 'DEPLOYMENT_FREQUENCY' => have_attributes(value: 'deployment_frequency'),
'LEAD_TIME_FOR_CHANGES' => have_attributes(value: 'lead_time_for_changes') 'LEAD_TIME_FOR_CHANGES' => have_attributes(value: 'lead_time_for_changes'),
'TIME_TO_RESTORE_SERVICE' => have_attributes(value: 'time_to_restore_service')
) )
end end
end end
...@@ -150,6 +150,41 @@ RSpec.describe Dora::DailyMetrics, type: :model do ...@@ -150,6 +150,41 @@ RSpec.describe Dora::DailyMetrics, type: :model do
end end
end end
context 'with closed issues' do
before do
create(:issue, :incident, :closed, project: project, created_at: date - 7.days, closed_at: date)
create(:issue, :incident, :closed, project: project, created_at: date - 5.days, closed_at: date)
create(:issue, :incident, :closed, project: project, created_at: date - 3.days, closed_at: date)
create(:issue, :incident, :closed, project: project, created_at: date - 1.day, closed_at: date)
# Issues which shouldn't be included in calculation
create(:issue, :closed, project: project, created_at: date - 1.year, closed_at: date) # not an incident
create(:issue, :incident, project: project, created_at: date - 1.year) # not closed yet
create(:issue, :incident, :closed, created_at: date - 1.year, closed_at: date) # different project
create(:issue, :incident, :closed, project: project, created_at: date - 1.year, closed_at: date + 1.day) # different date
end
context 'for production environment' do
let_it_be(:environment) { create(:environment, :production, project: project) }
it 'inserts the daily metrics with time_to_restore_service' do
subject
metrics = environment.dora_daily_metrics.find_by_date(date)
expect(metrics.time_to_restore_service_in_seconds).to eq(4.days.to_i) # median
end
end
context 'for non-production environment' do
it 'does not calculate time_to_restore_service daily metric' do
subject
metrics = environment.dora_daily_metrics.find_by_date(date)
expect(metrics.time_to_restore_service_in_seconds).to be_nil
end
end
end
context 'when date is invalid type' do context 'when date is invalid type' do
let(:date) { '2021-02-03' } let(:date) { '2021-02-03' }
...@@ -215,19 +250,21 @@ RSpec.describe Dora::DailyMetrics, type: :model do ...@@ -215,19 +250,21 @@ RSpec.describe Dora::DailyMetrics, type: :model do
end end
end end
context 'when metric is lead time for changes' do shared_examples 'median metric' do |metric|
subject { described_class.aggregate_for!(metric, interval) }
before_all do before_all do
create(:dora_daily_metrics, lead_time_for_changes_in_seconds: 100, date: '2021-01-01') column_name = :"#{metric}_in_seconds"
create(:dora_daily_metrics, lead_time_for_changes_in_seconds: 90, date: '2021-01-01')
create(:dora_daily_metrics, lead_time_for_changes_in_seconds: 80, date: '2021-01-02') create(:dora_daily_metrics, column_name => 100, :date => '2021-01-01')
create(:dora_daily_metrics, lead_time_for_changes_in_seconds: 70, date: '2021-01-02') create(:dora_daily_metrics, column_name => 90, :date => '2021-01-01')
create(:dora_daily_metrics, lead_time_for_changes_in_seconds: 60, date: '2021-01-03') create(:dora_daily_metrics, column_name => 80, :date => '2021-01-02')
create(:dora_daily_metrics, lead_time_for_changes_in_seconds: 50, date: '2021-01-03') create(:dora_daily_metrics, column_name => 70, :date => '2021-01-02')
create(:dora_daily_metrics, lead_time_for_changes_in_seconds: nil, date: '2021-01-04') create(:dora_daily_metrics, column_name => 60, :date => '2021-01-03')
create(:dora_daily_metrics, column_name => 50, :date => '2021-01-03')
create(:dora_daily_metrics, column_name => nil, :date => '2021-01-04')
end end
let(:metric) { described_class::METRIC_LEAD_TIME_FOR_CHANGES }
context 'when interval is all' do context 'when interval is all' do
let(:interval) { described_class::INTERVAL_ALL } let(:interval) { described_class::INTERVAL_ALL }
...@@ -262,6 +299,14 @@ RSpec.describe Dora::DailyMetrics, type: :model do ...@@ -262,6 +299,14 @@ RSpec.describe Dora::DailyMetrics, type: :model do
end end
end end
context 'when metric is lead time for changes' do
include_examples 'median metric', described_class::METRIC_LEAD_TIME_FOR_CHANGES
end
context 'when metric is time_to_restore_service' do
include_examples 'median metric', described_class::METRIC_TIME_TO_RESTORE_SERVICE
end
context 'when metric is unknown' do context 'when metric is unknown' do
let(:metric) { 'unknown' } let(:metric) { 'unknown' }
let(:interval) { described_class::INTERVAL_ALL } let(:interval) { described_class::INTERVAL_ALL }
......
...@@ -353,6 +353,45 @@ RSpec.describe Issue do ...@@ -353,6 +353,45 @@ RSpec.describe Issue do
it { is_expected.to have_one(:status_page_published_incident) } it { is_expected.to have_one(:status_page_published_incident) }
end end
describe 'state machine' do
context 'daily dora metrics refresh' do
let_it_be(:production_env) { create(:environment, :production) }
context 'when incident is closed' do
let(:issue) { create(:issue, :incident, project: production_env.project) }
it 'schedules Dora::DailyMetrics::RefreshWorker' do
freeze_time do
expect(::Dora::DailyMetrics::RefreshWorker)
.to receive(:perform_async).with(production_env.id, Time.current.to_date.to_s)
issue.close!
end
end
end
context 'when there is no production env' do
let(:issue) { create(:issue, :incident) }
it 'does not schedule Dora::DailyMetrics::RefreshWorker' do
expect(::Dora::DailyMetrics::RefreshWorker).not_to receive(:perform_async)
issue.close!
end
end
context 'when issue is not an incident' do
let(:issue) { create(:issue, project: production_env.project) }
it 'does not schedule Dora::DailyMetrics::RefreshWorker' do
expect(::Dora::DailyMetrics::RefreshWorker).not_to receive(:perform_async)
issue.close!
end
end
end
end
it_behaves_like 'an editable mentionable with EE-specific mentions' do it_behaves_like 'an editable mentionable with EE-specific mentions' do
subject { create(:issue, project: create(:project, :repository)) } subject { create(:issue, project: create(:project, :repository)) }
......
...@@ -3,16 +3,15 @@ ...@@ -3,16 +3,15 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Dora::Metrics do RSpec.describe API::Dora::Metrics do
describe 'GET /projects/:id/dora/metrics' do let_it_be(:group) { create(:group) }
subject { get api(url, user), params: params } let_it_be(:project) { create(:project, group: group) }
let_it_be(:production) { create(:environment, :production, project: project) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:project) { create(:project) } shared_examples 'common dora metrics endpoint' do
let_it_be(:production) { create(:environment, :production, project: project) } using RSpec::Parameterized::TableSyntax
let_it_be(:maintainer) { create(:user) }
let_it_be(:guest) { create(:user) }
let(:url) { "/projects/#{project.id}/dora/metrics" }
let(:params) { { metric: :deployment_frequency } }
let(:user) { maintainer } let(:user) { maintainer }
around do |example| around do |example|
...@@ -22,26 +21,45 @@ RSpec.describe API::Dora::Metrics do ...@@ -22,26 +21,45 @@ RSpec.describe API::Dora::Metrics do
end end
before_all do before_all do
project.add_maintainer(maintainer) create(:dora_daily_metrics,
project.add_guest(guest) deployment_frequency: 1,
create(:dora_daily_metrics, deployment_frequency: 1, environment: production, date: '2021-01-01') lead_time_for_changes_in_seconds: 3,
create(:dora_daily_metrics, deployment_frequency: 2, environment: production, date: '2021-01-02') time_to_restore_service_in_seconds: 5,
environment: production,
date: '2021-01-01')
create(:dora_daily_metrics,
deployment_frequency: 2,
lead_time_for_changes_in_seconds: 4,
time_to_restore_service_in_seconds: 6,
environment: production,
date: '2021-01-02')
end end
before do before do
stub_licensed_features(dora4_analytics: true) stub_licensed_features(dora4_analytics: true)
end end
it 'returns data' do where(:metric, :value1, :value2) do
subject :deployment_frequency | 1 | 2
:lead_time_for_changes | 3 | 4
:time_to_restore_service | 5 | 6
end
with_them do
let(:params) { { metric: metric } }
expect(response).to have_gitlab_http_status(:ok) it 'returns data' do
expect(json_response).to eq([{ 'date' => '2021-01-01', 'value' => 1 }, subject
{ 'date' => '2021-01-02', 'value' => 2 }])
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_array([{ 'date' => '2021-01-01', 'value' => value1 },
{ 'date' => '2021-01-02', 'value' => value2 }])
end
end end
context 'when user is guest' do context 'when user is guest' do
let(:user) { guest } let(:user) { guest }
let(:params) { { metric: :deployment_frequency } }
it 'returns authorization error' do it 'returns authorization error' do
subject subject
...@@ -52,53 +70,25 @@ RSpec.describe API::Dora::Metrics do ...@@ -52,53 +70,25 @@ RSpec.describe API::Dora::Metrics do
end end
end end
describe 'GET /groups/:id/dora/metrics' do describe 'GET /projects/:id/dora/metrics' do
subject { get api(url, user), params: params } subject { get api("/projects/#{project.id}/dora/metrics", user), params: params }
let_it_be(:group) { create(:group) } before_all do
let_it_be(:project) { create(:project, group: group) } project.add_maintainer(maintainer)
let_it_be(:production) { create(:environment, :production, project: project) } project.add_guest(guest)
let_it_be(:maintainer) { create(:user) } end
let_it_be(:guest) { create(:user) }
let(:url) { "/groups/#{group.id}/dora/metrics" } include_examples 'common dora metrics endpoint'
let(:params) { { metric: :deployment_frequency } } end
let(:user) { maintainer }
around do |example| describe 'GET /groups/:id/dora/metrics' do
freeze_time do subject { get api("/groups/#{group.id}/dora/metrics", user), params: params }
example.run
end
end
before_all do before_all do
group.add_maintainer(maintainer) group.add_maintainer(maintainer)
group.add_guest(guest) group.add_guest(guest)
create(:dora_daily_metrics, deployment_frequency: 1, environment: production, date: 1.day.ago.to_date)
create(:dora_daily_metrics, deployment_frequency: 2, environment: production, date: Time.current.to_date)
end
before do
stub_licensed_features(dora4_analytics: true)
end end
it 'returns data' do include_examples 'common dora metrics endpoint'
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq([{ 'date' => 1.day.ago.to_date.to_s, 'value' => 1 },
{ 'date' => Time.current.to_date.to_s, 'value' => 2 }])
end
context 'when user is guest' do
let(:user) { guest }
it 'returns authorization error' do
subject
expect(response).to have_gitlab_http_status(:unauthorized)
expect(json_response['message']).to eq('You do not have permission to access dora metrics.')
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment