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 belongs_to :project
alias_attribute :parent, :project alias_attribute :parent, :project
alias_attribute :parent_id, :project_id
end end
end end
end end

View File

@ -47,11 +47,17 @@ module Analytics
!custom !custom
end end
# The model that is going to be queried, Issue or MergeRequest # The model class that is going to be queried, Issue or MergeRequest
def subject_model def subject_class
start_event.object_type start_event.object_type
end 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 private
def validate_stage_event_pairs def validate_stage_event_pairs

View File

@ -591,3 +591,5 @@ class MergeRequestDiff < ApplicationRecord
end end
end 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 "first_added_to_board_at"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_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" t.index ["issue_id"], name: "index_issue_metrics"
end 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 ["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 ["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_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", 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", "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" 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 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). [Performance report artifact](../../../ci/yaml/README.md#artifactsreportsperformance-premium).
For more information on how the Performance job should look like, check the 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 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. 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 ## 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/) 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) on your code by using GitLab CI/CD and [sitespeed.io](https://www.sitespeed.io)
using Docker-in-Docker. 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). [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 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 ```yaml
include:
template: Verify/Browser-Performance.gitlab-ci.yml
performance: performance:
stage: performance
image: docker:git
variables: variables:
URL: https://example.com 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 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. 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) 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 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. [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 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/) list of URLs to test, please see the [Sitespeed.io Configuration](https://www.sitespeed.io/documentation/sitespeed.io/configuration/)
documentation. documentation.
@ -126,8 +141,9 @@ set this up:
as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt` as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`
in your job's `script`. in your job's `script`.
1. In the `performance` job, read the previous artifact into an environment 1. In the `performance` job, read the previous artifact into an environment
variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test variable, in this case `$URL` because this is what our sitespeed.io command
URLs. 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 1. You can now run the sitespeed.io container against the desired hostname and
paths. paths.
@ -138,6 +154,9 @@ stages:
- deploy - deploy
- performance - performance
include:
template: Verify/Browser-Performance.gitlab-ci.yml
review: review:
stage: deploy stage: deploy
environment: environment:
@ -155,28 +174,12 @@ review:
- master - master
performance: performance:
stage: performance
image: docker:git
services:
- docker:stable-dind
dependencies: dependencies:
- review - review
script: before_script:
- export CI_ENVIRONMENT_URL=$(cat environment_url.txt) - export 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
``` ```
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 ### Previous job definitions
CAUTION: **Caution:** 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', name: 'production',
custom: false, custom: false,
relative_position: 7, relative_position: 7,
start_event_identifier: :merge_request_merged, start_event_identifier: :issue_created,
end_event_identifier: :merge_request_first_deployed_to_production end_event_identifier: :production_stage_end
} }
end 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." msgid "Environments|An error occurred while fetching the environments."
msgstr "" 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." msgid "Environments|An error occurred while making the request."
msgstr "" msgstr ""
@ -6075,9 +6084,6 @@ msgstr ""
msgid "Environments|No deployments yet" msgid "Environments|No deployments yet"
msgstr "" 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." 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 "" msgstr ""
@ -14986,9 +14992,6 @@ msgstr ""
msgid "Something went wrong on our end." msgid "Something went wrong on our end."
msgstr "" msgstr ""
msgid "Something went wrong on our end. %{message}"
msgstr ""
msgid "Something went wrong on our end. Please try again!" msgid "Something went wrong on our end. Please try again!"
msgstr "" 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
<div class="build-page"> 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="build-trace-container prepend-top-default">
<div class="top-bar js-top-bar"> <div class="top-bar js-top-bar d-flex">
<div class="truncated-info hidden-xs pull-left"></div> <div class="row">
<div class="dropdown prepend-left-10 js-pod-dropdown"> <div class="form-group col-6" role="group">
<button aria-expanded="false" class="dropdown-menu-toggle" data-toggle="dropdown" type="button"> <label class="d-block col-form-label-sm col-form-label">
<i class="fa fa-chevron-down"></i> Environment
</button> </label>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"></div> <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>
<div class="controllers pull-right"> <div class="controllers align-self-end">
<div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to top"> <div
<button class="js-scroll-up btn-scroll btn-transparent btn-blank" disabled type="button"></button> 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>
<div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to bottom"> <div
<button class="js-scroll-down btn-scroll btn-transparent btn-blank" disabled type="button"></button> 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>
<div class="refresh-control pull-right"> <div class="refresh-control">
<div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Refresh"> <div
<button class="js-refresh-log btn-default btn-refresh" disabled type="button"></button> 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>
</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> </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' 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 describe '#project_median' do
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze { example.run }

View File

@ -21,6 +21,13 @@ describe Gitlab::CycleAnalytics::IssueStage do
it_behaves_like 'base stage' 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 describe '#median' do
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze { example.run }

View File

@ -21,6 +21,13 @@ describe Gitlab::CycleAnalytics::PlanStage do
it_behaves_like 'base stage' 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 describe '#project_median' do
around do |example| around do |example|
Timecop.freeze { example.run } 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) } it { expect(stage.project_median).to eq(nil) }
end end
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' it_behaves_like 'base stage'
describe '#median' do 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 before do
issue_1 = create(:issue, project: project, created_at: 90.minutes.ago) issue_1 = create(:issue, project: project, created_at: 90.minutes.ago)
issue_2 = create(:issue, project: project, created_at: 60.minutes.ago) issue_2 = create(:issue, project: project, created_at: 60.minutes.ago)
issue_3 = 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_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 = create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') 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 = 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_3.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil) 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_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) mr_5.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
@ -43,5 +46,13 @@ describe Gitlab::CycleAnalytics::TestStage do
end end
include_examples 'calculate #median with date range' 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
end end

View File

@ -55,11 +55,11 @@ shared_examples_for 'cycle analytics stage' do
end end
end end
describe '#subject_model' do describe '#subject_class' do
it 'infers the model from the start event' do it 'infers the model from the start event' do
stage = described_class.new(valid_params) stage = described_class.new(valid_params)
expect(stage.subject_model).to eq(MergeRequest) expect(stage.subject_class).to eq(MergeRequest)
end end
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) expect(stage.end_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged)
end end
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 end