Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-10-10 21:06:01 +00:00
parent 7c862041c6
commit f607152a08
25 changed files with 851 additions and 79 deletions

View File

@ -9,6 +9,7 @@ module Analytics
belongs_to :project
alias_attribute :parent, :project
alias_attribute :parent_id, :project_id
end
end
end

View File

@ -47,11 +47,17 @@ module Analytics
!custom
end
# The model that is going to be queried, Issue or MergeRequest
def subject_model
# The model class that is going to be queried, Issue or MergeRequest
def subject_class
start_event.object_type
end
def matches_with_stage_params?(stage_params)
default_stage? &&
start_event_identifier.to_s.eql?(stage_params[:start_event_identifier].to_s) &&
end_event_identifier.to_s.eql?(stage_params[:end_event_identifier].to_s)
end
private
def validate_stage_event_pairs

View File

@ -591,3 +591,5 @@ class MergeRequestDiff < ApplicationRecord
end
end
end
MergeRequestDiff.prepend_if_ee('EE::MergeRequestDiff')

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class IndexTimestampColumnsForIssueMetrics < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(*index_arguments)
end
def down
remove_concurrent_index(*index_arguments)
end
private
def index_arguments
[
:issue_metrics,
[:issue_id, :first_mentioned_in_commit_at, :first_associated_with_milestone_at, :first_added_to_board_at],
{
name: 'index_issue_metrics_on_issue_id_and_timestamps'
}
]
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class IndexTimestampColumnsForMergeRequestsCreationDate < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(*index_arguments)
end
def down
remove_concurrent_index(*index_arguments)
end
private
def index_arguments
[
:merge_requests,
[:target_project_id, :created_at],
{
name: 'index_merge_requests_target_project_id_created_at'
}
]
end
end

View File

@ -1816,6 +1816,7 @@ ActiveRecord::Schema.define(version: 2019_10_04_134055) do
t.datetime "first_added_to_board_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["issue_id", "first_mentioned_in_commit_at", "first_associated_with_milestone_at", "first_added_to_board_at"], name: "index_issue_metrics_on_issue_id_and_timestamps"
t.index ["issue_id"], name: "index_issue_metrics"
end
@ -2226,6 +2227,7 @@ ActiveRecord::Schema.define(version: 2019_10_04_134055) do
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch"
t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))"
t.index ["target_branch"], name: "index_merge_requests_on_target_branch"
t.index ["target_project_id", "created_at"], name: "index_merge_requests_target_project_id_created_at"
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)"
t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id"

View File

@ -44,7 +44,7 @@ For instance, consider the following workflow:
First of all, you need to define a job in your `.gitlab-ci.yml` file that generates the
[Performance report artifact](../../../ci/yaml/README.md#artifactsreportsperformance-premium).
For more information on how the Performance job should look like, check the
example on [Testing Browser Performance](../../../ci/examples/browser_performance.md).
example on [Configuring Browser Performance Testing](#configuring-browser-performance-testing).
GitLab then checks this report, compares key performance metrics for each page
between the source and target branches, and shows the information right on the merge request.
@ -60,11 +60,6 @@ report will be shown properly.
## Configuring Browser Performance Testing
NOTE: **Note:**
The job definition shown below is supported in GitLab 11.5 and later versions.
It also requires GitLab Runner 11.5 or later. For earlier versions, use the
[previous job definitions](#previous-job-definitions).
This example shows how to run the [sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/)
on your code by using GitLab CI/CD and [sitespeed.io](https://www.sitespeed.io)
using Docker-in-Docker.
@ -73,29 +68,35 @@ First, you need GitLab Runner with
[docker-in-docker build](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
Once you set up the Runner, add a new job to `.gitlab-ci.yml` that generates the
expected report:
expected report.
For GitLab 12.4 and later, to define the `performance` job, you must
[include](../../../ci/yaml/README.md#includetemplate) the
[`Browser-Performance.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml)
that's provided as a part of your GitLab installation.
For GitLab versions earlier than 12.4, you can copy and use the job as defined
in that template.
CAUTION: **Caution:**
The job definition provided by the template does not support Kubernetes yet. For a complete example of a more complex setup
that works in Kubernetes, see [here](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml).
Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
template: Verify/Browser-Performance.gitlab-ci.yml
performance:
stage: performance
image: docker:git
variables:
URL: https://example.com
services:
- docker:stable-dind
script:
- mkdir gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
- sitespeed-results/
reports:
performance: performance.json
```
CAUTION: **Caution:**
The job definition provided by the template is supported in GitLab 11.5 and later versions.
It also requires GitLab Runner 11.5 or later. For earlier versions, use the
[previous job definitions](#previous-job-definitions).
The above example will create a `performance` job in your CI/CD pipeline and will run
sitespeed.io against the webpage you defined in `URL` to gather key metrics.
The [GitLab plugin for sitespeed.io](https://gitlab.com/gitlab-org/gl-performance)
@ -106,6 +107,20 @@ take the latest Performance artifact available.
The full HTML sitespeed.io report will also be saved as an artifact, and if you have
[GitLab Pages](../pages/index.md) enabled, it can be viewed directly in your browser.
It is also possible to customize options by setting the `SITESPEED_OPTIONS` variable.
For example, this is how to override the number of runs sitespeed.io
will make on the given URL:
```yaml
include:
template: Verify/Browser-Performance.gitlab-ci.yml
performance:
variables:
URL: https://example.com
SITESPEED_OPTIONS: -n 5
```
For further customization options for sitespeed.io, including the ability to provide a
list of URLs to test, please see the [Sitespeed.io Configuration](https://www.sitespeed.io/documentation/sitespeed.io/configuration/)
documentation.
@ -126,8 +141,9 @@ set this up:
as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`
in your job's `script`.
1. In the `performance` job, read the previous artifact into an environment
variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test
URLs.
variable, in this case `$URL` because this is what our sitespeed.io command
uses for the URL parameter. Because Review App URLs are dynamic, we define
the `URL` variable through `before_script` instead of `variables`.
1. You can now run the sitespeed.io container against the desired hostname and
paths.
@ -138,6 +154,9 @@ stages:
- deploy
- performance
include:
template: Verify/Browser-Performance.gitlab-ci.yml
review:
stage: deploy
environment:
@ -155,28 +174,12 @@ review:
- master
performance:
stage: performance
image: docker:git
services:
- docker:stable-dind
dependencies:
- review
script:
- export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
- mkdir gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
- sitespeed-results/
reports:
performance: performance.json
before_script:
- export URL=$(cat environment_url.txt)
```
A complete example can be found in our [Auto DevOps CI YML](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml).
### Previous job definitions
CAUTION: **Caution:**

View File

@ -0,0 +1,70 @@
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
class BaseQueryBuilder
include Gitlab::CycleAnalytics::MetricsTables
delegate :subject_class, to: :stage
# rubocop: disable CodeReuse/ActiveRecord
def initialize(stage:, params: {})
@stage = stage
@params = params
end
def build
query = subject_class
query = filter_by_parent_model(query)
query = filter_by_time_range(query)
query = stage.start_event.apply_query_customization(query)
query = stage.end_event.apply_query_customization(query)
query.where(duration_condition)
end
private
attr_reader :stage, :params
def duration_condition
stage.end_event.timestamp_projection.gteq(stage.start_event.timestamp_projection)
end
def filter_by_parent_model(query)
if parent_class.eql?(Project)
if subject_class.eql?(Issue)
query.where(project_id: stage.parent_id)
elsif subject_class.eql?(MergeRequest)
query.where(target_project_id: stage.parent_id)
else
raise ArgumentError, "unknown subject_class: #{subject_class}"
end
else
raise ArgumentError, "unknown parent_class: #{parent_class}"
end
end
def filter_by_time_range(query)
from = params.fetch(:from, 30.days.ago)
to = params[:to]
query = query.where(subject_table[:created_at].gteq(from))
query = query.where(subject_table[:created_at].lteq(to)) if to
query
end
def subject_table
subject_class.arel_table
end
def parent_class
stage.parent.class
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
# Arguments:
# stage - an instance of CycleAnalytics::ProjectStage or CycleAnalytics::GroupStage
# params:
# current_user: an instance of User
# from: DateTime
# to: DateTime
class DataCollector
include Gitlab::Utils::StrongMemoize
def initialize(stage:, params: {})
@stage = stage
@params = params
end
def records_fetcher
strong_memoize(:records_fetcher) do
RecordsFetcher.new(stage: stage, query: query, params: params)
end
end
def median
strong_memoize(:median) do
Median.new(stage: stage, query: query)
end
end
private
attr_reader :stage, :params
def query
BaseQueryBuilder.new(stage: stage, params: params).build
end
end
end
end
end

View File

@ -92,8 +92,8 @@ module Gitlab
name: 'production',
custom: false,
relative_position: 7,
start_event_identifier: :merge_request_merged,
end_event_identifier: :merge_request_first_deployed_to_production
start_event_identifier: :issue_created,
end_event_identifier: :production_stage_end
}
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
class Median
include StageQueryHelpers
def initialize(stage:, query:)
@stage = stage
@query = query
end
def seconds
@query = @query.select(median_duration_in_seconds.as('median'))
result = execute_query(@query).first || {}
result['median'] ? result['median'].to_i : nil
end
private
attr_reader :stage
def percentile_cont
percentile_cont_ordering = Arel::Nodes::UnaryOperation.new(Arel::Nodes::SqlLiteral.new('ORDER BY'), duration)
Arel::Nodes::NamedFunction.new(
'percentile_cont(0.5) WITHIN GROUP',
[percentile_cont_ordering]
)
end
def median_duration_in_seconds
Arel::Nodes::Extract.new(percentile_cont, :epoch)
end
end
end
end
end

View File

@ -0,0 +1,132 @@
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
class RecordsFetcher
include Gitlab::Utils::StrongMemoize
include StageQueryHelpers
include Gitlab::CycleAnalytics::MetricsTables
MAX_RECORDS = 20
MAPPINGS = {
Issue => {
finder_class: IssuesFinder,
serializer_class: AnalyticsIssueSerializer,
includes_for_query: { project: [:namespace], author: [] },
columns_for_select: %I[title iid id created_at author_id project_id]
},
MergeRequest => {
finder_class: MergeRequestsFinder,
serializer_class: AnalyticsMergeRequestSerializer,
includes_for_query: { target_project: [:namespace], author: [] },
columns_for_select: %I[title iid id created_at author_id state target_project_id]
}
}.freeze
delegate :subject_class, to: :stage
def initialize(stage:, query:, params: {})
@stage = stage
@query = query
@params = params
end
def serialized_records
strong_memoize(:serialized_records) do
# special case (legacy): 'Test' and 'Staging' stages should show Ci::Build records
if default_test_stage? || default_staging_stage?
AnalyticsBuildSerializer.new.represent(ci_build_records.map { |e| e['build'] })
else
records.map do |record|
project = record.project
attributes = record.attributes.merge({
project_path: project.path,
namespace_path: project.namespace.path,
author: record.author
})
serializer.represent(attributes)
end
end
end
end
private
attr_reader :stage, :query, :params
def finder_query
MAPPINGS
.fetch(subject_class)
.fetch(:finder_class)
.new(params.fetch(:current_user), finder_params.fetch(stage.parent.class))
.execute
end
def columns
MAPPINGS.fetch(subject_class).fetch(:columns_for_select).map do |column_name|
subject_class.arel_table[column_name]
end
end
# EE will override this to include Group rules
def finder_params
{
Project => { project_id: stage.parent_id }
}
end
def default_test_stage?
stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_test_stage)
end
def default_staging_stage?
stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_staging_stage)
end
def serializer
MAPPINGS.fetch(subject_class).fetch(:serializer_class).new
end
# Loading Ci::Build records instead of MergeRequest records
# rubocop: disable CodeReuse/ActiveRecord
def ci_build_records
ci_build_join = mr_metrics_table
.join(build_table)
.on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
.join_sources
q = ordered_and_limited_query
.joins(ci_build_join)
.select(build_table[:id], round_duration_to_seconds.as('total_time'))
results = execute_query(q).to_a
Gitlab::CycleAnalytics::Updater.update!(results, from: 'id', to: 'build', klass: ::Ci::Build.includes({ project: [:namespace], user: [], pipeline: [] }))
end
def ordered_and_limited_query
query
.reorder(stage.end_event.timestamp_projection.desc)
.limit(MAX_RECORDS)
end
def records
results = finder_query
.merge(ordered_and_limited_query)
.select(*columns, round_duration_to_seconds.as('total_time'))
# using preloader instead of includes to avoid AR generating a large column list
ActiveRecord::Associations::Preloader.new.preload(
results,
MAPPINGS.fetch(subject_class).fetch(:includes_for_query)
)
results
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageQueryHelpers
def execute_query(query)
ActiveRecord::Base.connection.execute(query.to_sql)
end
def zero_interval
Arel::Nodes::NamedFunction.new("CAST", [Arel.sql("'0' AS INTERVAL")])
end
def round_duration_to_seconds
Arel::Nodes::Extract.new(duration, :epoch)
end
def duration
Arel::Nodes::Subtraction.new(
stage.end_event.timestamp_projection,
stage.start_event.timestamp_projection
)
end
end
end
end
end

View File

@ -0,0 +1,29 @@
# Read more about the feature here: https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html
stages:
- build
- test
- deploy
- performance
performance:
stage: performance
image: docker:git
variables:
URL: https://example.com
SITESPEED_VERSION: 6.3.1
SITESPEED_OPTIONS: ''
services:
- docker:stable-dind
script:
- mkdir gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL $SITESPEED_OPTIONS
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
- performance.json
- sitespeed-results/
reports:
performance: performance.json

View File

@ -6030,6 +6030,15 @@ msgstr ""
msgid "Environments|An error occurred while fetching the environments."
msgstr ""
msgid "Environments|An error occurred while fetching the logs"
msgstr ""
msgid "Environments|An error occurred while fetching the logs - Error: %{message}"
msgstr ""
msgid "Environments|An error occurred while fetching the logs for this environment or pod. Please try again"
msgstr ""
msgid "Environments|An error occurred while making the request."
msgstr ""
@ -6075,9 +6084,6 @@ msgstr ""
msgid "Environments|No deployments yet"
msgstr ""
msgid "Environments|No pod name has been specified"
msgstr ""
msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
msgstr ""
@ -14986,9 +14992,6 @@ msgstr ""
msgid "Something went wrong on our end."
msgstr ""
msgid "Something went wrong on our end. %{message}"
msgstr ""
msgid "Something went wrong on our end. Please try again!"
msgstr ""

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
FactoryBot.define do
factory :cycle_analytics_project_stage, class: Analytics::CycleAnalytics::ProjectStage do
project
sequence(:name) { |n| "Stage ##{n}" }
hidden { false }
issue_stage
trait :issue_stage do
start_event_identifier { Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated.identifier }
end_event_identifier { Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd.identifier }
end
end
end

View File

@ -1,29 +1,102 @@
<div class="js-kubernetes-logs" data-logs-path="/root/kubernetes-app/environments/1/logs">
<div class="build-page">
<div
class="js-kubernetes-logs"
data-current-environment-name="production"
data-environments-path="/root/my-project/environments.json"
data-logs-page="/root/my-project/environments/1/logs"
data-logs-path="/root/my-project/environments/1/logs.json"
>
<div class="build-page-pod-logs">
<div class="build-trace-container prepend-top-default">
<div class="top-bar js-top-bar">
<div class="truncated-info hidden-xs pull-left"></div>
<div class="dropdown prepend-left-10 js-pod-dropdown">
<button aria-expanded="false" class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"></div>
<div class="top-bar js-top-bar d-flex">
<div class="row">
<div class="form-group col-6" role="group">
<label class="d-block col-form-label-sm col-form-label">
Environment
</label>
<div class="dropdown js-environment-dropdown d-flex">
<button
aria-expanded="false"
class="dropdown-menu-toggle d-flex align-content-center align-self-center"
data-toggle="dropdown"
type="button"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i>
<div class="dropdown-toggle-text">
&nbsp;
</div>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"></div>
</div>
</div>
<div class="form-group col-6" role="group">
<label class="d-block col-form-label-sm col-form-label">
Pod logs from
</label>
<div class="dropdown js-pod-dropdown d-flex">
<button
aria-expanded="false"
class="dropdown-menu-toggle d-flex align-content-center align-self-center"
data-toggle="dropdown"
type="button"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i>
<div class="dropdown-toggle-text">
&nbsp;
</div>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"></div>
</div>
</div>
</div>
<div class="controllers pull-right">
<div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to top">
<button class="js-scroll-up btn-scroll btn-transparent btn-blank" disabled type="button"></button>
<div class="controllers align-self-end">
<div
class="has-tooltip controllers-buttons"
data-container="body"
data-placement="top"
title="Scroll to top"
>
<button
class="js-scroll-up btn-scroll btn-transparent btn-blank"
disabled
type="button"
></button>
</div>
<div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to bottom">
<button class="js-scroll-down btn-scroll btn-transparent btn-blank" disabled type="button"></button>
<div
class="has-tooltip controllers-buttons"
data-container="body"
data-placement="top"
title="Scroll to bottom"
>
<button
class="js-scroll-down btn-scroll btn-transparent btn-blank"
disabled
type="button"
></button>
</div>
<div class="refresh-control pull-right">
<div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Refresh">
<button class="js-refresh-log btn-default btn-refresh" disabled type="button"></button>
<div class="refresh-control">
<div
class="has-tooltip controllers-buttons"
data-container="body"
data-placement="top"
title="Refresh"
>
<button
class="js-refresh-log btn btn-default btn-refresh h-32-px"
disabled
type="button"
></button>
</div>
</div>
</div>
</div>
<pre class="build-trace" id="build-trace"><code class="bash js-build-output"><div class="build-loader-animation js-build-refresh"></div></code></pre>
<pre class="build-trace" id="build-trace">
<code class="bash js-build-output"></code>
<div class="build-loader-animation js-build-refresh">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</pre>
</div>
</div>
</div>

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) }
let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) }
let(:params) { {} }
let(:records) do
stage = build(:cycle_analytics_project_stage, {
start_event_identifier: :merge_request_created,
end_event_identifier: :merge_request_merged,
project: project
})
described_class.new(stage: stage, params: params).build.to_a
end
before do
mr1.metrics.update!(merged_at: 1.month.ago)
mr2.metrics.update!(merged_at: Time.now)
end
around do |example|
Timecop.freeze { example.run }
end
describe 'date range parameters' do
context 'when filters by only the `from` parameter' do
before do
params[:from] = 4.months.ago
end
it { expect(records.size).to eq(2) }
end
context 'when filters by both `from` and `to` parameters' do
before do
params.merge!(from: 4.months.ago, to: 2.months.ago)
end
it { expect(records.size).to eq(1) }
end
context 'invalid date range is provided' do
before do
params.merge!(from: 1.month.ago, to: 10.months.ago)
end
it { expect(records.size).to eq(0) }
end
end
it 'scopes query within the target project' do
other_mr = create(:merge_request, source_project: create(:project), allow_broken: true, created_at: 2.months.ago)
other_mr.metrics.update!(merged_at: 1.month.ago)
params[:from] = 1.year.ago
expect(records.size).to eq(2)
end
end

View File

@ -0,0 +1,131 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
around do |example|
Timecop.freeze { example.run }
end
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:user) { create(:user) }
subject do
Gitlab::Analytics::CycleAnalytics::DataCollector.new(
stage: stage,
params: {
from: 1.year.ago,
current_user: user
}
).records_fetcher.serialized_records
end
describe '#serialized_records' do
shared_context 'when records are loaded by maintainer' do
before do
project.add_user(user, Gitlab::Access::MAINTAINER)
end
it 'returns all records' do
expect(subject.size).to eq(2)
end
end
describe 'for issue based stage' do
let_it_be(:issue1) { create(:issue, project: project) }
let_it_be(:issue2) { create(:issue, project: project, confidential: true) }
let(:stage) do
build(:cycle_analytics_project_stage, {
start_event_identifier: :plan_stage_start,
end_event_identifier: :issue_first_mentioned_in_commit,
project: project
})
end
before do
issue1.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue2.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
end
context 'when records are loaded by guest' do
before do
project.add_user(user, Gitlab::Access::GUEST)
end
it 'filters out confidential issues' do
expect(subject.size).to eq(1)
expect(subject.first[:iid].to_s).to eq(issue1.iid.to_s)
end
end
include_context 'when records are loaded by maintainer'
end
describe 'for merge request based stage' do
let(:mr1) { create(:merge_request, created_at: 5.days.ago, source_project: project, allow_broken: true) }
let(:mr2) { create(:merge_request, created_at: 4.days.ago, source_project: project, allow_broken: true) }
let(:stage) do
build(:cycle_analytics_project_stage, {
start_event_identifier: :merge_request_created,
end_event_identifier: :merge_request_merged,
project: project
})
end
before do
mr1.metrics.update(merged_at: 3.days.ago)
mr2.metrics.update(merged_at: 3.days.ago)
end
include_context 'when records are loaded by maintainer'
end
describe 'special case' do
let(:mr1) { create(:merge_request, source_project: project, allow_broken: true, created_at: 20.days.ago) }
let(:mr2) { create(:merge_request, source_project: project, allow_broken: true, created_at: 19.days.ago) }
let(:ci_build1) { create(:ci_build) }
let(:ci_build2) { create(:ci_build) }
let(:default_stages) { Gitlab::Analytics::CycleAnalytics::DefaultStages }
let(:stage) { build(:cycle_analytics_project_stage, default_stages.params_for_test_stage.merge(project: project)) }
before do
mr1.metrics.update!({
merged_at: 5.days.ago,
first_deployed_to_production_at: 1.day.ago,
latest_build_started_at: 5.days.ago,
latest_build_finished_at: 1.day.ago,
pipeline: ci_build1.pipeline
})
mr2.metrics.update!({
merged_at: 10.days.ago,
first_deployed_to_production_at: 5.days.ago,
latest_build_started_at: 9.days.ago,
latest_build_finished_at: 7.days.ago,
pipeline: ci_build2.pipeline
})
end
context 'returns build records' do
shared_examples 'orders build records by `latest_build_finished_at`' do
it 'orders by `latest_build_finished_at`' do
build_ids = subject.map { |item| item[:id] }
expect(build_ids).to eq([ci_build1.id, ci_build2.id])
end
end
context 'when requesting records for default test stage' do
include_examples 'orders build records by `latest_build_finished_at`'
end
context 'when requesting records for default staging stage' do
before do
stage.assign_attributes(default_stages.params_for_staging_stage)
end
include_examples 'orders build records by `latest_build_finished_at`'
end
end
end
end
end

View File

@ -26,6 +26,13 @@ describe Gitlab::CycleAnalytics::CodeStage do
it_behaves_like 'base stage'
context 'when using the new query backend' do
include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:expected_record_count) { 2 }
let(:expected_ordered_attribute_values) { [mr_2.title, mr_1.title] }
end
end
describe '#project_median' do
around do |example|
Timecop.freeze { example.run }

View File

@ -21,6 +21,13 @@ describe Gitlab::CycleAnalytics::IssueStage do
it_behaves_like 'base stage'
context 'when using the new query backend' do
include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:expected_record_count) { 3 }
let(:expected_ordered_attribute_values) { [issue_3.title, issue_2.title, issue_1.title] }
end
end
describe '#median' do
around do |example|
Timecop.freeze { example.run }

View File

@ -21,6 +21,13 @@ describe Gitlab::CycleAnalytics::PlanStage do
it_behaves_like 'base stage'
context 'when using the new query backend' do
include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:expected_record_count) { 2 }
let(:expected_ordered_attribute_values) { [issue_1.title, issue_2.title] }
end
end
describe '#project_median' do
around do |example|
Timecop.freeze { example.run }

View File

@ -52,3 +52,21 @@ shared_examples 'calculate #median with date range' do
it { expect(stage.project_median).to eq(nil) }
end
end
shared_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:stage_params) { Gitlab::Analytics::CycleAnalytics::DefaultStages.send("params_for_#{stage_name}_stage").merge(project: project) }
let(:stage) { Analytics::CycleAnalytics::ProjectStage.new(stage_params) }
let(:data_collector) { Gitlab::Analytics::CycleAnalytics::DataCollector.new(stage: stage, params: { from: stage_options[:from], current_user: project.creator }) }
let(:attribute_to_verify) { :title }
context 'provides the same results as the old implementation' do
it 'for the median' do
expect(data_collector.median.seconds).to eq(ISSUES_MEDIAN)
end
it 'for the list of event records' do
records = data_collector.records_fetcher.serialized_records
expect(records.map { |event| event[attribute_to_verify] }).to eq(expected_ordered_attribute_values)
end
end
end

View File

@ -12,17 +12,20 @@ describe Gitlab::CycleAnalytics::TestStage do
it_behaves_like 'base stage'
describe '#median' do
let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
let(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') }
let(:mr_5) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D') }
let(:ci_build1) { create(:ci_build, project: project) }
let(:ci_build2) { create(:ci_build, project: project) }
before do
issue_1 = create(:issue, project: project, created_at: 90.minutes.ago)
issue_2 = create(:issue, project: project, created_at: 60.minutes.ago)
issue_3 = create(:issue, project: project, created_at: 60.minutes.ago)
mr_1 = create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago)
mr_2 = create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A')
mr_3 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
mr_4 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C')
mr_5 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D')
mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago)
mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago)
mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago, pipeline_id: ci_build1.commit_id)
mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago, pipeline_id: ci_build2.commit_id)
mr_3.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
mr_4.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
mr_5.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
@ -43,5 +46,13 @@ describe Gitlab::CycleAnalytics::TestStage do
end
include_examples 'calculate #median with date range'
context 'when using the new query backend' do
include_examples 'Gitlab::Analytics::CycleAnalytics::DataCollector backend examples' do
let(:expected_record_count) { 2 }
let(:attribute_to_verify) { :id }
let(:expected_ordered_attribute_values) { [mr_1.metrics.pipeline.builds.first.id, mr_2.metrics.pipeline.builds.first.id] }
end
end
end
end

View File

@ -55,11 +55,11 @@ shared_examples_for 'cycle analytics stage' do
end
end
describe '#subject_model' do
describe '#subject_class' do
it 'infers the model from the start event' do
stage = described_class.new(valid_params)
expect(stage.subject_model).to eq(MergeRequest)
expect(stage.subject_class).to eq(MergeRequest)
end
end
@ -78,4 +78,30 @@ shared_examples_for 'cycle analytics stage' do
expect(stage.end_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged)
end
end
describe '#matches_with_stage_params?' do
let(:params) { Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_test_stage }
it 'matches with default stage params' do
stage = described_class.new(params)
expect(stage).to be_default_stage
expect(stage).to be_matches_with_stage_params(params)
end
it "mismatches when the stage is custom" do
stage = described_class.new(params.merge(custom: true))
expect(stage).not_to be_default_stage
expect(stage).not_to be_matches_with_stage_params(params)
end
end
describe '#parent_id' do
it "delegates to 'parent_name'_id attribute" do
stage = described_class.new(parent: parent)
expect(stage.parent_id).to eq(parent.id)
end
end
end