summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue2
-rw-r--r--app/assets/javascripts/ide/services/index.js8
-rw-r--r--app/assets/javascripts/ide/stores/utils.js2
-rw-r--r--app/assets/stylesheets/pages/groups.scss4
-rw-r--r--app/controllers/admin/requests_profiles_controller.rb8
-rw-r--r--app/models/cycle_analytics/group_level.rb31
-rw-r--r--app/models/cycle_analytics/level_base.rb (renamed from app/models/cycle_analytics/base.rb)6
-rw-r--r--app/models/cycle_analytics/project_level.rb5
-rw-r--r--app/serializers/analytics_issue_entity.rb6
-rw-r--r--app/serializers/analytics_merge_request_entity.rb2
-rw-r--r--app/serializers/analytics_stage_entity.rb4
-rw-r--r--app/serializers/group_analytics_stage_entity.rb16
-rw-r--r--app/serializers/group_analytics_stage_serializer.rb5
-rw-r--r--app/services/commits/create_service.rb18
-rw-r--r--app/services/files/multi_service.rb1
-rw-r--r--app/views/admin/requests_profiles/index.html.haml3
-rw-r--r--app/views/devise/passwords/edit.html.haml6
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml6
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml6
-rw-r--r--app/views/devise/shared/_tabs_normal.html.haml4
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml4
-rw-r--r--app/views/projects/pipelines/charts.html.haml7
-rw-r--r--app/views/projects/pipelines/charts/_overall.haml21
-rw-r--r--app/views/projects/pipelines/charts/_pipeline_statistics.haml14
-rw-r--r--app/views/projects/pipelines/charts/_pipelines.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml4
-rw-r--r--changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml5
-rw-r--r--changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml5
-rw-r--r--changelogs/unreleased/ci_default_git_depth_only.yml5
-rw-r--r--changelogs/unreleased/ds-charts-whitespace.yml5
-rw-r--r--changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml5
-rw-r--r--config/initializers/7_prometheus_metrics.rb3
-rw-r--r--config/initializers/peek.rb2
-rw-r--r--config/routes/admin.rb2
-rw-r--r--doc/administration/job_artifacts.md1
-rw-r--r--doc/administration/merge_request_diffs.md1
-rw-r--r--doc/administration/monitoring/performance/img/request_profile_result.pngbin3216 -> 33920 bytes
-rw-r--r--doc/administration/monitoring/performance/request_profiling.md4
-rw-r--r--doc/administration/uploads.md17
-rw-r--r--doc/api/commits.md9
-rw-r--r--doc/user/project/clusters/serverless/index.md37
-rw-r--r--[-rwxr-xr-x]doc/user/project/img/autocomplete_characters_example1_v12_0.pngbin17510 -> 17510 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/img/autocomplete_characters_example2_v12_0.pngbin14623 -> 14623 bytes
-rw-r--r--doc/user/project/issues/confidential_issues.md46
-rw-r--r--doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.pngbin0 -> 121644 bytes
-rw-r--r--doc/user/project/issues/img/confidential_mr_dropdown_v12_1.pngbin0 -> 128156 bytes
-rw-r--r--doc/user/project/merge_requests/index.md5
-rw-r--r--doc/workflow/lfs/lfs_administration.md1
-rw-r--r--lib/api/commits.rb14
-rw-r--r--lib/api/helpers/projects_helpers.rb1
-rw-r--r--lib/gitlab/ci/templates/Serverless.gitlab-ci.yml13
-rw-r--r--lib/gitlab/cycle_analytics/base_event_fetcher.rb21
-rw-r--r--lib/gitlab/cycle_analytics/base_query.rb14
-rw-r--r--lib/gitlab/cycle_analytics/base_stage.rb24
-rw-r--r--lib/gitlab/cycle_analytics/code_event_fetcher.rb6
-rw-r--r--lib/gitlab/cycle_analytics/group_stage_summary.rb24
-rw-r--r--lib/gitlab/cycle_analytics/issue_event_fetcher.rb6
-rw-r--r--lib/gitlab/cycle_analytics/issue_helper.rb3
-rw-r--r--lib/gitlab/cycle_analytics/metrics_tables.rb8
-rw-r--r--lib/gitlab/cycle_analytics/permissions.rb2
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb6
-rw-r--r--lib/gitlab/cycle_analytics/plan_helper.rb13
-rw-r--r--lib/gitlab/cycle_analytics/production_event_fetcher.rb6
-rw-r--r--lib/gitlab/cycle_analytics/review_event_fetcher.rb6
-rw-r--r--lib/gitlab/cycle_analytics/summary/group/base.rb24
-rw-r--r--lib/gitlab/cycle_analytics/summary/group/deploy.rb29
-rw-r--r--lib/gitlab/cycle_analytics/summary/group/issue.rb25
-rw-r--r--lib/gitlab/git.rb5
-rw-r--r--lib/gitlab/git/repository.rb4
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb9
-rw-r--r--lib/gitlab/request_profiler/middleware.rb62
-rw-r--r--lib/gitlab/request_profiler/profile.rb25
-rw-r--r--lib/peek/views/redis_detailed.rb (renamed from lib/peek/views/redis.rb)48
-rw-r--r--lib/prometheus/pid_provider.rb42
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/main/login.rb10
-rw-r--r--qa/qa/page/main/menu.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb4
-rw-r--r--spec/controllers/admin/requests_profiles_controller_spec.rb63
-rw-r--r--spec/features/admin/admin_requests_profiles_spec.rb103
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-sast-report.json20
-rw-r--r--spec/fixtures/security-reports/master/gl-sast-report.json20
-rw-r--r--spec/frontend/ide/services/index_spec.js32
-rw-r--r--spec/frontend/notes/components/discussion_jump_to_next_button_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js5
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js2
-rw-r--r--spec/javascripts/notes/components/note_actions/reply_button_spec.js5
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb6
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb98
-rw-r--r--spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb88
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb73
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb78
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb68
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_event_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb70
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb2
-rw-r--r--spec/lib/gitlab/git_spec.rb20
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb25
-rw-r--r--spec/lib/prometheus/pid_provider_spec.rb79
-rw-r--r--spec/models/cycle_analytics/code_spec.rb4
-rw-r--r--spec/models/cycle_analytics/group_level_spec.rb46
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/production_spec.rb4
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb4
-rw-r--r--spec/models/cycle_analytics/test_spec.rb8
-rw-r--r--spec/requests/api/commits_spec.rb203
-rw-r--r--spec/requests/request_profiler_spec.rb20
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb6
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb6
-rw-r--r--spec/serializers/analytics_merge_request_serializer_spec.rb6
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb10
-rw-r--r--spec/services/submodules/update_service_spec.rb2
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb12
-rw-r--r--spec/support/helpers/expect_request_with_status.rb11
-rw-r--r--yarn.lock25
137 files changed, 1655 insertions, 454 deletions
diff --git a/Gemfile b/Gemfile
index 8bffc2a973d..de1f44642f2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -299,7 +299,6 @@ gem 'peek-gc', '~> 0.0.2'
gem 'peek-mysql2', '~> 1.2.0', group: :mysql
gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
-gem 'peek-redis', '~> 1.2.0'
# Memory benchmarks
gem 'derailed_benchmarks', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 60939ae918c..2bcc3527de4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -76,7 +76,6 @@ GEM
asciidoctor-plantuml (0.0.9)
asciidoctor (>= 1.5.6, < 3.0.0)
ast (2.4.0)
- atomic (1.1.99)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
attr_required (1.0.1)
@@ -658,10 +657,6 @@ GEM
peek-rblineprof (0.2.0)
peek
rblineprof
- peek-redis (1.2.0)
- atomic (>= 1.0.0)
- peek
- redis
pg (1.1.4)
po_to_json (1.0.1)
json (>= 1.6.0)
@@ -1199,7 +1194,6 @@ DEPENDENCIES
peek-mysql2 (~> 1.2.0)
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
- peek-redis (~> 1.2.0)
pg (~> 1.1)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.8)
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 9909f437fc8..830385941d8 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -129,7 +129,7 @@ export default {
<item-stats-value
:icon-name="visibilityIcon"
:title="visibilityTooltip"
- css-class="item-visibility d-inline-flex align-items-center prepend-top-8 append-right-4"
+ css-class="item-visibility d-inline-flex align-items-center prepend-top-8 append-right-4 text-secondary"
/>
<span v-if="group.permission" class="user-access-role prepend-top-8">
{{ group.permission }}
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index 840761f68db..ba33b6826d6 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -56,13 +56,7 @@ export default {
return Api.branchSingle(projectId, currentBranchId);
},
commit(projectId, payload) {
- // Currently the `commit` endpoint does not support `start_sha` so we
- // have to make the request in the FE. This is not ideal and will be
- // resolved soon. https://gitlab.com/gitlab-org/gitlab-ce/issues/59023
- const { branch, start_sha: ref } = payload;
- const branchPromise = ref ? Api.createBranch(projectId, { ref, branch }) : Promise.resolve();
-
- return branchPromise.then(() => Api.commitMultiple(projectId, payload));
+ return Api.commitMultiple(projectId, payload);
},
getFiles(projectUrl, branchId) {
const url = `${projectUrl}/files/${branchId}`;
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 01f78a29cf6..04470064c1f 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -155,7 +155,7 @@ export const createCommitPayload = ({
last_commit_id:
newBranch || f.deleted || f.prevPath || f.replaces ? undefined : f.lastCommitSha,
})),
- start_sha: newBranch ? rootGetters.lastCommit.short_id : undefined,
+ start_sha: newBranch ? rootGetters.lastCommit.id : undefined,
});
export const createNewMergeRequestUrl = (projectUrl, source, target) =>
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index cff2e274390..1502cf18440 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -412,10 +412,6 @@ table.pipeline-project-metrics tr td {
font-size: $gl-font-size-large;
}
- .item-visibility {
- color: $gl-text-color-secondary;
- }
-
@include media-breakpoint-down(md) {
.title {
font-size: $gl-font-size;
diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb
index 89d4c4f18d9..1d14812186a 100644
--- a/app/controllers/admin/requests_profiles_controller.rb
+++ b/app/controllers/admin/requests_profiles_controller.rb
@@ -10,10 +10,10 @@ class Admin::RequestsProfilesController < Admin::ApplicationController
clean_name = Rack::Utils.clean_path_info(params[:name])
profile = Gitlab::RequestProfiler::Profile.find(clean_name)
- if profile
- render html: profile.content.html_safe
- else
- redirect_to admin_requests_profiles_path, alert: 'Profile not found'
+ unless profile && profile.content_type
+ return redirect_to admin_requests_profiles_path, alert: 'Profile not found'
end
+
+ send_file profile.file_path, type: "#{profile.content_type}; charset=utf-8", disposition: 'inline'
end
end
diff --git a/app/models/cycle_analytics/group_level.rb b/app/models/cycle_analytics/group_level.rb
new file mode 100644
index 00000000000..508cde0ca00
--- /dev/null
+++ b/app/models/cycle_analytics/group_level.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module CycleAnalytics
+ class GroupLevel
+ include LevelBase
+ attr_reader :options, :group
+
+ def initialize(group:, options:)
+ @group = group
+ @options = options.merge(group: group)
+ end
+
+ def summary
+ @summary ||= ::Gitlab::CycleAnalytics::GroupStageSummary.new(group,
+ from: options[:from],
+ current_user: options[:current_user]).data
+ end
+
+ def permissions(*)
+ STAGES.each_with_object({}) do |stage, obj|
+ obj[stage] = true
+ end
+ end
+
+ def stats
+ @stats ||= STAGES.map do |stage_name|
+ self[stage_name].as_json(serializer: GroupAnalyticsStageSerializer)
+ end
+ end
+ end
+end
diff --git a/app/models/cycle_analytics/base.rb b/app/models/cycle_analytics/level_base.rb
index d7b28cd1b67..543349ebf8f 100644
--- a/app/models/cycle_analytics/base.rb
+++ b/app/models/cycle_analytics/level_base.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
module CycleAnalytics
- class Base
+ module LevelBase
STAGES = %i[issue plan code test review staging production].freeze
def all_medians_by_stage
STAGES.each_with_object({}) do |stage_name, medians_per_stage|
- medians_per_stage[stage_name] = self[stage_name].median
+ medians_per_stage[stage_name] = self[stage_name].project_median
end
end
@@ -21,7 +21,7 @@ module CycleAnalytics
end
def [](stage_name)
- Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options)
+ Gitlab::CycleAnalytics::Stage[stage_name].new(options: options)
end
end
end
diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb
index 22631cc7d41..4aa426c58a1 100644
--- a/app/models/cycle_analytics/project_level.rb
+++ b/app/models/cycle_analytics/project_level.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
module CycleAnalytics
- class ProjectLevel < Base
+ class ProjectLevel
+ include LevelBase
attr_reader :project, :options
def initialize(project, options:)
@project = project
- @options = options
+ @options = options.merge(project: project)
end
def summary
diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb
index ab15bd0ac7a..29d4a6ae1d0 100644
--- a/app/serializers/analytics_issue_entity.rb
+++ b/app/serializers/analytics_issue_entity.rb
@@ -20,12 +20,12 @@ class AnalyticsIssueEntity < Grape::Entity
end
expose :url do |object|
- url_to(:namespace_project_issue, id: object[:iid].to_s)
+ url_to(:namespace_project_issue, object)
end
private
- def url_to(route, id)
- public_send("#{route}_url", request.project.namespace, request.project, id) # rubocop:disable GitlabSecurity/PublicSend
+ def url_to(route, object)
+ public_send("#{route}_url", object[:path], object[:name], object[:iid].to_s) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/serializers/analytics_merge_request_entity.rb b/app/serializers/analytics_merge_request_entity.rb
index b7134da9461..21d7eeb81b0 100644
--- a/app/serializers/analytics_merge_request_entity.rb
+++ b/app/serializers/analytics_merge_request_entity.rb
@@ -4,6 +4,6 @@ class AnalyticsMergeRequestEntity < AnalyticsIssueEntity
expose :state
expose :url do |object|
- url_to(:namespace_project_merge_request, id: object[:iid].to_s)
+ url_to(:namespace_project_merge_request, object)
end
end
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index 8bc6da5aeeb..eb38b90fb18 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -8,9 +8,9 @@ class AnalyticsStageEntity < Grape::Entity
expose :legend
expose :description
- expose :median, as: :value do |stage|
+ expose :project_median, as: :value do |stage|
# median returns a BatchLoader instance which we first have to unwrap by using to_f
# we use to_f to make sure results below 1 are presented to the end-user
- stage.median.to_f.nonzero? ? distance_of_time_in_words(stage.median) : nil
+ stage.project_median.to_f.nonzero? ? distance_of_time_in_words(stage.project_median) : nil
end
end
diff --git a/app/serializers/group_analytics_stage_entity.rb b/app/serializers/group_analytics_stage_entity.rb
new file mode 100644
index 00000000000..81be20e7dd8
--- /dev/null
+++ b/app/serializers/group_analytics_stage_entity.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class GroupAnalyticsStageEntity < Grape::Entity
+ include EntityDateHelper
+
+ expose :title
+ expose :name
+ expose :legend
+ expose :description
+
+ expose :group_median, as: :value do |stage|
+ # group_median returns a BatchLoader instance which we first have to unwrap by using to_f
+ # we use to_f to make sure results below 1 are presented to the end-user
+ stage.group_median.to_f.nonzero? ? distance_of_time_in_words(stage.group_median) : nil
+ end
+end
diff --git a/app/serializers/group_analytics_stage_serializer.rb b/app/serializers/group_analytics_stage_serializer.rb
new file mode 100644
index 00000000000..ec448dea602
--- /dev/null
+++ b/app/serializers/group_analytics_stage_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class GroupAnalyticsStageSerializer < BaseSerializer
+ entity GroupAnalyticsStageEntity
+end
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index bb34a3d3352..f3be68f9602 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -10,6 +10,7 @@ module Commits
@start_project = params[:start_project] || @project
@start_branch = params[:start_branch]
+ @start_sha = params[:start_sha]
@branch_name = params[:branch_name]
@force = params[:force] || false
end
@@ -40,7 +41,7 @@ module Commits
end
def different_branch?
- @start_branch != @branch_name || @start_project != @project
+ @start_project != @project || @start_branch != @branch_name || @start_sha.present?
end
def force?
@@ -49,6 +50,7 @@ module Commits
def validate!
validate_permissions!
+ validate_start_sha!
validate_on_branch!
validate_branch_existence!
@@ -63,7 +65,21 @@ module Commits
end
end
+ def validate_start_sha!
+ return unless @start_sha
+
+ if @start_branch
+ raise_error("You can't pass both start_branch and start_sha")
+ elsif !Gitlab::Git.commit_id?(@start_sha)
+ raise_error("Invalid start_sha '#{@start_sha}'")
+ elsif !@start_project.repository.commit(@start_sha)
+ raise_error("Cannot find start_sha '#{@start_sha}'")
+ end
+ end
+
def validate_on_branch!
+ return unless @start_branch
+
if !@start_project.empty_repo? && !@start_project.repository.branch_exists?(@start_branch)
raise_error('You can only create or edit files when you are on a branch')
end
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index d8c4e5bc5e8..65af4dd5a28 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -47,6 +47,7 @@ module Files
author_name: @author_name,
start_project: @start_project,
start_branch_name: @start_branch,
+ start_sha: @start_sha,
force: force?
)
rescue ArgumentError => e
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
index adfc67d66d0..86bfeef580c 100644
--- a/app/views/admin/requests_profiles/index.html.haml
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -19,7 +19,8 @@
%ul.content-list
- profiles.each do |profile|
%li
- = link_to profile.time.to_s(:long), admin_requests_profile_path(profile)
+ = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize,
+ admin_requests_profile_path(profile)
- else
%p
No profiles found
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 09ea7716a47..fee87c6324c 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -7,12 +7,12 @@
= f.hidden_field :reset_password_token
.form-group
= f.label 'New password', for: "user_password"
- = f.password_field :password, class: "form-control top qa-password-field", required: true, title: 'This field is required'
+ = f.password_field :password, class: "form-control top", required: true, title: 'This field is required', data: { qa_selector: 'password_field'}
.form-group
= f.label 'Confirm new password', for: "user_password_confirmation"
- = f.password_field :password_confirmation, class: "form-control bottom qa-password-confirmation", title: 'This field is required', required: true
+ = f.password_field :password_confirmation, class: "form-control bottom", title: 'This field is required', data: { qa_selector: 'password_confirmation_field' }, required: true
.clearfix
- = f.submit "Change your password", class: "btn btn-primary qa-change-password-button"
+ = f.submit "Change your password", class: "btn btn-primary", data: { qa_selector: 'change_password_button' }
.clearfix.prepend-top-20
%p
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index f856773526d..a8e34b6da9c 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -3,13 +3,13 @@
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group
= label_tag :username, "#{server['label']} Username"
- = text_field_tag :username, nil, { class: "form-control top qa-username-field", title: "This field is required.", autofocus: "autofocus", required: true }
+ = text_field_tag :username, nil, { class: "form-control top", title: "This field is required.", autofocus: "autofocus", data: { qa_selector: 'username_field' } required: true }
.form-group
= label_tag :password
- = password_field_tag :password, nil, { class: "form-control bottom qa-password-field", title: "This field is required.", required: true }
+ = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", data: { qa_selector: 'password_field' }required: true }
- if devise_mapping.rememberable?
.remember-me
%label{ for: "remember_me" }
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = submit_tag "Sign in", class: "btn-success btn qa-sign-in-button"
+ = submit_tag "Sign in", class: "btn-success btn", data: { qa_selector: 'sign_in_button' }
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index b1a9470cf1c..db54c166a53 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -5,13 +5,13 @@
= render_if_exists "devise/shared/kerberos_tab"
- @ldap_servers.each_with_index do |server, i|
%li.nav-item
- = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && form_based_auth_provider_has_active_class?(:ldapmain))} qa-ldap-tab", 'data-toggle' => 'tab'
+ = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', qa_selector: 'ldap_tab' }
= render_if_exists 'devise/shared/tab_smartcard'
- if password_authentication_enabled_for_web?
%li.nav-item
- = link_to 'Standard', '#login-pane', class: 'nav-link qa-standard-tab', 'data-toggle' => 'tab'
+ = link_to 'Standard', '#login-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'standard_tab' }
- if allow_signup?
%li.nav-item
- = link_to 'Register', '#register-pane', class: 'nav-link qa-register-tab', 'data-toggle' => 'tab'
+ = link_to 'Register', '#register-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'register_tab' }
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
index ab8c22532fd..b6a1b8805ee 100644
--- a/app/views/devise/shared/_tabs_normal.html.haml
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -1,6 +1,6 @@
%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
- %a.nav-link.qa-sign-in-tab.active{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
+ %a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab', qa_selector: 'sign_in_tab' }, role: 'tab' } Sign in
- if allow_signup?
%li.nav-item{ role: 'presentation' }
- %a.nav-link.qa-register-tab{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_event: 'click_button', track_value: '', toggle: 'tab' }, role: 'tab' } Register
+ %a.nav-link{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_event: 'click_button', track_value: '', toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' } Register
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 45fc39dbbdb..808290afcad 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -20,8 +20,8 @@
= link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username }
- if current_user_menu?(:settings)
%li
- = link_to s_("CurrentUser|Settings"), profile_path
+ = link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' }
- if current_user_menu?(:sign_out)
%li.divider
%li
- = link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link qa-sign-out-link"
+ = link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link", data: { qa_selector: 'sign_out_link' }
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 4d1d078661d..6b4110e07d2 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -2,12 +2,9 @@
- page_title _('CI / CD Charts')
%div{ class: container_class }
+
#charts.ci-charts
- .row
- .col-md-6
- = render 'projects/pipelines/charts/overall'
- .col-md-6
- = render 'projects/pipelines/charts/pipeline_times'
+ = render 'projects/pipelines/charts/overall'
%hr
= render 'projects/pipelines/charts/pipelines'
diff --git a/app/views/projects/pipelines/charts/_overall.haml b/app/views/projects/pipelines/charts/_overall.haml
index 66786c7ff59..651f9217455 100644
--- a/app/views/projects/pipelines/charts/_overall.haml
+++ b/app/views/projects/pipelines/charts/_overall.haml
@@ -1,15 +1,6 @@
-%h4= s_("PipelineCharts|Overall statistics")
-%ul
- %li
- = s_("PipelineCharts|Total:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
- %li
- = s_("PipelineCharts|Successful:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
- %li
- = s_("PipelineCharts|Failed:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
- %li
- = s_("PipelineCharts|Success ratio:")
- %strong
- #{success_ratio(@counts)}%
+%h4.mt-4.mb-4= s_("PipelineCharts|Overall statistics")
+.row
+ .col-md-6
+ = render 'projects/pipelines/charts/pipeline_statistics'
+ .col-md-6
+ = render 'projects/pipelines/charts/pipeline_times'
diff --git a/app/views/projects/pipelines/charts/_pipeline_statistics.haml b/app/views/projects/pipelines/charts/_pipeline_statistics.haml
new file mode 100644
index 00000000000..b323e290ed4
--- /dev/null
+++ b/app/views/projects/pipelines/charts/_pipeline_statistics.haml
@@ -0,0 +1,14 @@
+%ul
+ %li
+ = s_("PipelineCharts|Total:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
+ %li
+ = s_("PipelineCharts|Successful:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
+ %li
+ = s_("PipelineCharts|Failed:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
+ %li
+ = s_("PipelineCharts|Success ratio:")
+ %strong
+ #{success_ratio(@counts)}%
diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml
index 47f1f074210..afff9e82e45 100644
--- a/app/views/projects/pipelines/charts/_pipelines.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -1,4 +1,4 @@
-%h4= _("Pipelines charts")
+%h4.mt-4.mb-4= _("Pipelines charts")
%p
&nbsp;
%span.legend-success
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 71bd9320593..04f72420fe1 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -37,7 +37,7 @@
%span.project-name<
= project.name
- %span.metadata-info.visibility-icon.append-right-10.prepend-top-8.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
+ %span.metadata-info.visibility-icon.append-right-10.prepend-top-8.text-secondary.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
= visibility_level_icon(project.visibility_level, fw: true)
- if explore_projects_tab? && project.repository.license
@@ -89,4 +89,4 @@
%span.icon-wrapper.pipeline-status
= render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), type: 'commit', tooltip_placement: 'top', path: pipeline_path
.updated-note
- %span Updated #{updated_tooltip}
+ %span _('Updated') #{updated_tooltip}
diff --git a/changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml b/changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml
new file mode 100644
index 00000000000..0d2fbaf01ed
--- /dev/null
+++ b/changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure visibility icons in group/project listings are grey
+merge_request: 30858
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml b/changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml
new file mode 100644
index 00000000000..f810c2c5ada
--- /dev/null
+++ b/changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for start_sha to commits API
+merge_request: 29598
+author:
+type: changed
diff --git a/changelogs/unreleased/ci_default_git_depth_only.yml b/changelogs/unreleased/ci_default_git_depth_only.yml
new file mode 100644
index 00000000000..928e2fe3ec4
--- /dev/null
+++ b/changelogs/unreleased/ci_default_git_depth_only.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Allow changing only ci_default_git_depth'
+merge_request: 30888
+author: Mathieu Parent
+type: fixed
diff --git a/changelogs/unreleased/ds-charts-whitespace.yml b/changelogs/unreleased/ds-charts-whitespace.yml
new file mode 100644
index 00000000000..210261764a2
--- /dev/null
+++ b/changelogs/unreleased/ds-charts-whitespace.yml
@@ -0,0 +1,5 @@
+---
+title: Improves section header whitespace on the CI/CD Charts page
+merge_request: 30531
+author:
+type: fixed
diff --git a/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml b/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml
new file mode 100644
index 00000000000..bd9001bd671
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml
@@ -0,0 +1,5 @@
+---
+title: Deploy serverless apps with gitlabktl
+merge_request: 30740
+author:
+type: added
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 53c3eac3c74..3f2691dde95 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -1,5 +1,4 @@
require 'prometheus/client'
-require 'prometheus/client/support/unicorn'
# Keep separate directories for separate processes
def prometheus_default_multiproc_dir
@@ -23,7 +22,7 @@ Prometheus::Client.configure do |config|
config.multiprocess_files_dir = ENV['prometheus_multiproc_dir'] || prometheus_default_multiproc_dir
- config.pid_provider = Prometheus::Client::Support::Unicorn.method(:worker_pid_provider)
+ config.pid_provider = Prometheus::PidProvider.method(:worker_id)
end
Gitlab::Application.configure do |config|
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index d492b60705d..2aec32e878c 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -29,7 +29,7 @@ end
Peek.into PEEK_DB_VIEW
Peek.into Peek::Views::Gitaly
Peek.into Peek::Views::Rblineprof
-Peek.into Peek::Views::Redis
+Peek.into Peek::Views::RedisDetailed
Peek.into Peek::Views::GC
Peek.into Peek::Views::Tracing if Labkit::Tracing.tracing_url_enabled?
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index f609739d9fd..6f9a5552564 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -73,7 +73,7 @@ namespace :admin do
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
- resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
+ resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.(html|txt)/ }
resources :projects, only: [:index]
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 5490a59ceac..2b624f8de77 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -115,6 +115,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
+| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with AWS v4 signatures (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true
| `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index 99cd9051778..0b065082ded 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -90,6 +90,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
+| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with AWS v4 signatures (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true
| `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
diff --git a/doc/administration/monitoring/performance/img/request_profile_result.png b/doc/administration/monitoring/performance/img/request_profile_result.png
index 1b06e240fa0..3b34f207974 100644
--- a/doc/administration/monitoring/performance/img/request_profile_result.png
+++ b/doc/administration/monitoring/performance/img/request_profile_result.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md
index 726882fbb87..9f671e0db11 100644
--- a/doc/administration/monitoring/performance/request_profiling.md
+++ b/doc/administration/monitoring/performance/request_profiling.md
@@ -5,9 +5,9 @@
1. Grab the profiling token from **Monitoring > Requests Profiles** admin page
(highlighted in a blue in the image below).
![Profile token](img/request_profiling_token.png)
-1. Pass the header `X-Profile-Token: <token>` to the request you want to profile. You can use:
+1. Pass the header `X-Profile-Token: <token>` and `X-Profile-Mode: <mode>`(where <mode> can be `execution` or `memory`) to the request you want to profile. You can use:
- Browser extensions. For example, [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj) Chrome extension.
- - `curl`. For example, `curl --header 'X-Profile-Token: <token>' https://gitlab.example.com/group/project`.
+ - `curl`. For example, `curl --header 'X-Profile-Token: <token>' --header 'X-Profile-Mode: <mode>' https://gitlab.example.com/group/project`.
1. Once request is finished (which will take a little longer than usual), you can
view the profiling output from **Monitoring > Requests Profiles** admin page.
![Profiling output](img/request_profile_result.png)
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 51e267c865e..99f1c386183 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -78,6 +78,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
+| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with AWS v4 signatures (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true
| `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
@@ -140,6 +141,22 @@ _The uploads are stored by default in
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
+### Oracle Cloud S3 connection settings
+
+Note that Oracle Cloud S3 must be sure to use the following settings:
+
+| Setting | Value |
+|---------|-------|
+| `enable_signature_v4_streaming` | false |
+| `path_style` | true |
+
+If `enable_signature_v4_streaming` is set to `true`, you may see the
+following error:
+
+```
+STREAMING-AWS4-HMAC-SHA256-PAYLOAD is not supported
+```
+
### OpenStack compatible connection settings
The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 6eb4c47415f..1a835c0a872 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -72,15 +72,16 @@ POST /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide `start_branch`. |
+| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`. |
| `commit_message` | string | yes | Commit message |
-| `start_branch` | string | no | Name of the branch to start the new commit from |
-| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the commit from. Defaults to the value of `id`. |
+| `start_branch` | string | no | Name of the branch to start the new branch from |
+| `start_sha` | string | no | SHA of the commit to start the new branch from |
+| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the new branch from. Defaults to the value of `id`. |
| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. |
| `author_email` | string | no | Specify the commit author's email address |
| `author_name` | string | no | Specify the commit author's name |
| `stats` | boolean | no | Include commit stats. Default is true |
-| `force` | boolean | no | When `true` overwrites the target branch with a new commit based on the `start_branch` |
+| `force` | boolean | no | When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha` |
| `actions[]` Attribute | Type | Required | Description |
| --------------------- | ---- | -------- | ----------- |
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 14ee6303bf9..a32759c7bdc 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -41,8 +41,7 @@ To run Knative on Gitlab, you will need:
external IP address or hostname for that domain.
1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko)
to build the application. We also use [gitlabktl](https://gitlab.com/gitlab-org/gitlabktl)
- and [TriggerMesh CLI](https://github.com/triggermesh/tm) CLIs to simplify the
- deployment of services and functions to Knative.
+ CLI to simplify the deployment of services and functions to Knative.
1. **`serverless.yml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yml` file
will contain the information for all the functions being hosted in the repository as well as a reference to the
runtime being used.
@@ -249,7 +248,7 @@ Explanation of the fields used above:
| Parameter | Description |
|-----------|-------------|
-| `name` | Indicates which provider is used to execute the `serverless.yml` file. In this case, the TriggerMesh `tm` CLI. |
+| `name` | Indicates which provider is used to execute the `serverless.yml` file. In this case, the TriggerMesh middleware. |
| `environment` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are he variable contents. You may replace this with you own variables. |
### `functions`
@@ -343,27 +342,23 @@ Go to the **CI/CD > Pipelines** and click on the pipeline that deployed your app
The output will look like this:
```bash
-Running with gitlab-runner 11.5.0~beta.844.g96d88322 (96d88322)
- on docker-auto-scale 72989761
-Using Docker executor with image gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 ...
-Pulling docker image gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 ...
-Using docker image sha256:6b3f6590a9b30bd7aafb9573f047d930c70066e43955b4beb18a1eee175f6de1 for gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 ...
-Running on runner-72989761-project-4342902-concurrent-0 via runner-72989761-stg-srm-1541795796-27929c96...
-Cloning repository...
-Cloning into '/builds/danielgruesso/knative'...
-Checking out 8671ad20 as master...
-Skipping Git submodules setup
-$ echo "$CI_REGISTRY_IMAGE"
-registry.staging.gitlab.com/danielgruesso/knative
-$ tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
-Deployment started. Run "tm -n knative-4342902 describe service knative" to see the details
-Waiting for ready state.......
-Service domain: knative.knative-4342902.example.com
+Running with gitlab-runner 12.1.0-rc1 (6da35412)
+ on prm-com-gitlab-org ae3bfce3
+Using Docker executor with image registry.gitlab.com/gitlab-org/gitlabktl:latest ...
+Running on runner-ae3bfc-concurrent-0 via runner-ae3bfc ...
+Fetching changes...
+Authenticating with credentials from job payload (GitLab Registry)
+$ /usr/bin/gitlabktl application deploy
+Welcome to gitlabktl tool
+time="2019-07-15T10:51:07Z" level=info msg="deploying registry credentials"
+Creating app-hello function
+Waiting for app-hello ready state
+Service app-hello URL: http://app-hello.serverless.example.com
Job succeeded
```
-The second to last line, labeled **Service domain** contains the URL for the deployment. Copy and paste the domain into your
-browser to see the app live.
+The second to last line, labeled **Service domain** contains the URL for the
+deployment. Copy and paste the domain into your browser to see the app live.
![knative app](img/knative-app.png)
diff --git a/doc/user/project/img/autocomplete_characters_example1_v12_0.png b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
index 9c6fa923b80..9c6fa923b80 100755..100644
--- a/doc/user/project/img/autocomplete_characters_example1_v12_0.png
+++ b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
Binary files differ
diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
index b2e8a782a0b..b2e8a782a0b 100755..100644
--- a/doc/user/project/img/autocomplete_characters_example2_v12_0.png
+++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
Binary files differ
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 2c755e0fb4d..c10ef564f0d 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -77,3 +77,49 @@ project's search results respectively.
| Maintainer access | Guest access |
| :-----------: | :----------: |
| ![Confidential issues search master](img/confidential_issues_search_master.png) | ![Confidential issues search guest](img/confidential_issues_search_guest.png) |
+
+## Merge Requests for Confidential Issues
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/58583) in GitLab 12.1.
+
+To help prevent confidential information being leaked from a public project
+in the process of resolving a confidential issue, confidential issues can be
+resolved by creating a merge request from a private fork.
+
+The merge request created will target the default branch of the private fork,
+not the default branch of the public upstream project. This prevents the merge
+request, branch, and commits entering the public repository, and revealing
+confidential information prematurely. When the confidential commits are ready
+to be made public, this can be done by opening a merge request from the private
+fork to the public upstream project.
+
+TIP: **Best practice:**
+If you create a long-lived private fork in the same group or in a sub-group of
+the original upstream, all the users with Developer membership to the public
+project will also have the same permissions in the private project. This way,
+all the Developers, who have access to view confidential issues, will have a
+streamlined workflow for fixing them.
+
+### How it works
+
+On a confidential issue, a **Create confidential merge request** button is
+available. Clicking on it will open a dropdown where you can choose to
+**Create confidential merge request and branch** or **Create branch**:
+
+| Create confidential merge request | Create branch |
+| :-------------------------------: | :-----------: |
+| ![Create Confidential Merge Request Dropdown](img/confidential_mr_dropdown_v12_1.png) | ![Create Confidential Branch Dropdown](img/confidential_mr_branch_dropdown_v12_1.png) |
+
+The **Project** dropdown includes the list of private forks the user is a member
+of as at least a Developer and merge requests are enabled.
+
+Whenever the **Branch name** and **Source (branch or tag)** fields change, the
+availability of the target or source branch will be checked. Both branches should
+be available in the private fork selected.
+
+By clicking the **Create confidential merge request** button, GitLab will create
+the branch and merge request in the private fork. When you choose
+**Create branch**, GitLab will only create the branch.
+
+Once the branch is created in the private fork, developers can now push code to
+that branch to fix the confidential issue.
diff --git a/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png b/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png
new file mode 100644
index 00000000000..7c24226a6c4
--- /dev/null
+++ b/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png
Binary files differ
diff --git a/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png
new file mode 100644
index 00000000000..d6d391c6dd9
--- /dev/null
+++ b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index f593046fa8b..a4cda65bf6b 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -365,6 +365,11 @@ have been marked as a **Work In Progress**.
[Learn more about setting a merge request as "Work In Progress".](work_in_progress_merge_requests.md)
+## Merge Requests for Confidential Issues
+
+Create [merge requests to resolve confidential issues](../issues/confidential_issues.md#merge-requests-for-confidential-issues)
+for preventing leakage or early release of sentive data through regular merge requests.
+
## Merge request approvals **(STARTER)**
> Included in [GitLab Starter][products].
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 55183408a10..3160b0c4deb 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -91,6 +91,7 @@ Here is a configuration example with S3.
| `aws_access_key_id` | AWS credentials, or compatible | `ABC123DEF456` |
| `aws_secret_access_key` | AWS credentials, or compatible | `ABC123DEF456ABC123DEF456ABC123DEF456` |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
+| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with AWS v4 signatures (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true
| `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index fe910d46f6c..e4f4e79cd46 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -76,7 +76,7 @@ module API
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.', allow_blank: false
+ requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.', allow_blank: false
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array, desc: 'Actions to perform in commit' do
requires :action, type: String, desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze
@@ -98,12 +98,16 @@ module API
requires :execute_filemode, type: Boolean, desc: 'When `true/false` enables/disables the execute flag on the file.'
end
end
- optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
- optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the commit from'
+
+ optional :start_branch, type: String, desc: 'Name of the branch to start the new branch from'
+ optional :start_sha, type: String, desc: 'SHA of the commit to start the new branch from'
+ mutually_exclusive :start_branch, :start_sha
+
+ optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the new branch from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
- optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch`'
+ optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha`'
end
post ':id/repository/commits' do
if params[:start_project]
@@ -118,7 +122,7 @@ module API
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
- attrs[:start_branch] ||= attrs[:branch_name]
+ attrs[:start_branch] ||= attrs[:branch_name] unless attrs[:start_sha]
attrs[:start_project] = start_project if start_project
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 833e3b9ebaf..51b7cf05c8f 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -71,6 +71,7 @@ module API
:build_timeout,
:builds_access_level,
:ci_config_path,
+ :ci_default_git_depth,
:container_registry_enabled,
:default_branch,
:description,
diff --git a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
index a3db2705bf6..280e75d46f5 100644
--- a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
@@ -8,26 +8,23 @@ stages:
- deploy
.serverless:build:image:
- stage: build
image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ stage: build
script: /usr/bin/gitlabktl app build
.serverless:deploy:image:
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
stage: deploy
- image: gcr.io/triggermesh/tm@sha256:3cfdd470a66b741004fb02354319d79f1598c70117ce79978d2e07e192bfb336 # v0.0.11
environment: development
- script:
- - echo "$CI_REGISTRY_IMAGE"
- - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
+ script: /usr/bin/gitlabktl app deploy
.serverless:build:functions:
- stage: build
- environment: development
image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ stage: build
script: /usr/bin/gitlabktl serverless build
.serverless:deploy:functions:
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
stage: deploy
environment: development
- image: registry.gitlab.com/gitlab-org/gitlabktl:latest
script: /usr/bin/gitlabktl serverless deploy
diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
index 0cacef5b278..96aa799e864 100644
--- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
@@ -5,12 +5,11 @@ module Gitlab
class BaseEventFetcher
include BaseQuery
- attr_reader :projections, :query, :stage, :order, :project, :options
+ attr_reader :projections, :query, :stage, :order, :options
MAX_EVENTS = 50
- def initialize(project: nil, stage:, options:)
- @project = project
+ def initialize(stage:, options:)
@stage = stage
@options = options
end
@@ -68,11 +67,23 @@ module Gitlab
end
def allowed_ids_source
- { project_id: project.id }
+ group ? { group_id: group.id, include_subgroups: true } : { project_id: project.id }
+ end
+
+ def serialization_context
+ {}
end
def projects
- [project]
+ group ? Project.inside_path(group.full_path) : [project]
+ end
+
+ def group
+ @group ||= options.fetch(:group, nil)
+ end
+
+ def project
+ @project ||= options.fetch(:project, nil)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb
index 39fc1759cfc..9c98c0bfbf2 100644
--- a/lib/gitlab/cycle_analytics/base_query.rb
+++ b/lib/gitlab/cycle_analytics/base_query.rb
@@ -16,17 +16,25 @@ module Gitlab
def stage_query(project_ids)
query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id]))
.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
+ .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
+ .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids))
+ .where(routes_table[:source_type].eq('Namespace'))
.where(issue_table[:created_at].gteq(options[:from]))
# Load merge_requests
- query = query.join(mr_table, Arel::Nodes::OuterJoin)
+
+ query = load_merge_requests(query)
+
+ query
+ end
+
+ def load_merge_requests(query)
+ query.join(mr_table, Arel::Nodes::OuterJoin)
.on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id]))
.join(mr_metrics_table)
.on(mr_table[:id].eq(mr_metrics_table[:merge_request_id]))
-
- query
end
end
end
diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb
index 98b86e54340..678a891e941 100644
--- a/lib/gitlab/cycle_analytics/base_stage.rb
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -5,10 +5,9 @@ module Gitlab
class BaseStage
include BaseQuery
- attr_reader :project, :options
+ attr_reader :options
- def initialize(project: nil, options:)
- @project = project
+ def initialize(options:)
@options = options
end
@@ -24,7 +23,7 @@ module Gitlab
raise NotImplementedError.new("Expected #{self.name} to implement title")
end
- def median
+ def project_median
return if project.nil?
BatchLoader.for(project.id).batch(key: name) do |project_ids, loader|
@@ -42,6 +41,10 @@ module Gitlab
end
end
+ def group_median
+ median_query(projects.map(&:id))
+ end
+
def median_query(project_ids)
# Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
# Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
@@ -67,8 +70,7 @@ module Gitlab
private
def event_fetcher
- @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(project: project,
- stage: name,
+ @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(stage: name,
options: event_options)
end
@@ -77,7 +79,15 @@ module Gitlab
end
def projects
- [project]
+ group ? Project.inside_path(group.full_path) : [project]
+ end
+
+ def group
+ @group ||= options.fetch(:group, nil)
+ end
+
+ def project
+ @project ||= options.fetch(:project, nil)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
index 9e7ca529579..1e4e9b9e02c 100644
--- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
@@ -11,7 +11,9 @@ module Gitlab
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
- mr_table[:author_id]]
+ mr_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
@order = mr_table[:created_at]
super(*args)
@@ -20,7 +22,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: project).represent(event)
+ AnalyticsMergeRequestSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/group_stage_summary.rb b/lib/gitlab/cycle_analytics/group_stage_summary.rb
new file mode 100644
index 00000000000..7b5c74e1a1b
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/group_stage_summary.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ class GroupStageSummary
+ def initialize(group, from:, current_user:)
+ @group = group
+ @from = from
+ @current_user = current_user
+ end
+
+ def data
+ [serialize(Summary::Group::Issue.new(group: @group, from: @from, current_user: @current_user)),
+ serialize(Summary::Group::Deploy.new(group: @group, from: @from))]
+ end
+
+ private
+
+ def serialize(summary_object)
+ AnalyticsSummarySerializer.new.represent(summary_object)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
index bb3520ae920..2d03e425a6a 100644
--- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
- issue_table[:author_id]]
+ issue_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -18,7 +20,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: project).represent(event)
+ AnalyticsIssueSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/issue_helper.rb b/lib/gitlab/cycle_analytics/issue_helper.rb
index ac836b8bf0f..0fc4f1dd41a 100644
--- a/lib/gitlab/cycle_analytics/issue_helper.rb
+++ b/lib/gitlab/cycle_analytics/issue_helper.rb
@@ -5,8 +5,11 @@ module Gitlab
module IssueHelper
def stage_query(project_ids)
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
+ .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
+ .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids))
+ .where(routes_table[:source_type].eq('Namespace'))
.where(issue_table[:created_at].gteq(options[:from]))
.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
diff --git a/lib/gitlab/cycle_analytics/metrics_tables.rb b/lib/gitlab/cycle_analytics/metrics_tables.rb
index 3e0302d308d..015f7bfde24 100644
--- a/lib/gitlab/cycle_analytics/metrics_tables.rb
+++ b/lib/gitlab/cycle_analytics/metrics_tables.rb
@@ -35,6 +35,14 @@ module Gitlab
User.arel_table
end
+ def projects_table
+ Project.arel_table
+ end
+
+ def routes_table
+ Route.arel_table
+ end
+
def build_table
::CommitStatus.arel_table
end
diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb
index 03ba98b4dfb..55214e6b896 100644
--- a/lib/gitlab/cycle_analytics/permissions.rb
+++ b/lib/gitlab/cycle_analytics/permissions.rb
@@ -23,7 +23,7 @@ module Gitlab
end
def get
- ::CycleAnalytics::Base::STAGES.each do |stage|
+ ::CycleAnalytics::LevelBase::STAGES.each do |stage|
@stage_permission_hash[stage] = authorized_stage?(stage)
end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index 49a6b099f34..77cc358daa9 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
- issue_table[:author_id]]
+ issue_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -18,7 +20,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: project).represent(event)
+ AnalyticsIssueSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/plan_helper.rb b/lib/gitlab/cycle_analytics/plan_helper.rb
index ae578d45ad5..c3f742503a9 100644
--- a/lib/gitlab/cycle_analytics/plan_helper.rb
+++ b/lib/gitlab/cycle_analytics/plan_helper.rb
@@ -5,14 +5,21 @@ module Gitlab
module PlanHelper
def stage_query(project_ids)
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
+ .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
+ .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(options[:from]))
- .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
- .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
+ .where(routes_table[:source_type].eq('Namespace'))
+ query = add_conditions_to_query(query)
query
end
+
+ def add_conditions_to_query(query)
+ query.where(issue_table[:created_at].gteq(options[:from]))
+ .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
+ .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
+ end
end
end
end
diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb
index 949119d69a0..404b2460814 100644
--- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb
@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
- issue_table[:author_id]]
+ issue_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -18,7 +20,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: project).represent(event)
+ AnalyticsIssueSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
index d31736e755d..6acd12517fa 100644
--- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
@@ -11,7 +11,9 @@ module Gitlab
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
- mr_table[:author_id]]
+ mr_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -19,7 +21,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: project).represent(event)
+ AnalyticsMergeRequestSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/summary/group/base.rb b/lib/gitlab/cycle_analytics/summary/group/base.rb
new file mode 100644
index 00000000000..7f18b61d309
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/summary/group/base.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ module Summary
+ module Group
+ class Base
+ def initialize(group:, from:)
+ @group = group
+ @from = from
+ end
+
+ def title
+ raise NotImplementedError.new("Expected #{self.name} to implement title")
+ end
+
+ def value
+ raise NotImplementedError.new("Expected #{self.name} to implement value")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/summary/group/deploy.rb b/lib/gitlab/cycle_analytics/summary/group/deploy.rb
new file mode 100644
index 00000000000..d8fcd8f2ce4
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/summary/group/deploy.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ module Summary
+ module Group
+ class Deploy < Group::Base
+ def title
+ n_('Deploy', 'Deploys', value)
+ end
+
+ def value
+ @value ||= Deployment.joins(:project)
+ .where(projects: { id: projects })
+ .where("deployments.created_at > ?", @from)
+ .success
+ .count
+ end
+
+ private
+
+ def projects
+ Project.inside_path(@group.full_path).ids
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/summary/group/issue.rb b/lib/gitlab/cycle_analytics/summary/group/issue.rb
new file mode 100644
index 00000000000..70073e6d843
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/summary/group/issue.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ module Summary
+ module Group
+ class Issue < Group::Base
+ def initialize(group:, from:, current_user:)
+ @group = group
+ @from = from
+ @current_user = current_user
+ end
+
+ def title
+ n_('New Issue', 'New Issues', value)
+ end
+
+ def value
+ @value ||= IssuesFinder.new(@current_user, group_id: @group.id, include_subgroups: true, created_after: @from).execute.count
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 44a62586a23..df9f33baec2 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -9,6 +9,7 @@ module Gitlab
# https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
BLANK_SHA = ('0' * 40).freeze
+ COMMIT_ID = /\A[0-9a-f]{40}\z/.freeze
TAG_REF_PREFIX = "refs/tags/".freeze
BRANCH_REF_PREFIX = "refs/heads/".freeze
@@ -65,6 +66,10 @@ module Gitlab
ref == BLANK_SHA
end
+ def commit_id?(ref)
+ COMMIT_ID.match?(ref)
+ end
+
def version
Gitlab::Git::Version.git_version
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index a7d9ba51277..6e8aa5d578e 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -873,13 +873,13 @@ module Gitlab
def multi_action(
user, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
- start_branch_name: nil, start_repository: self,
+ start_branch_name: nil, start_sha: nil, start_repository: self,
force: false)
wrapped_gitaly_errors do
gitaly_operation_client.user_commit_files(user, branch_name,
message, actions, author_email, author_name,
- start_branch_name, start_repository, force)
+ start_branch_name, start_repository, force, start_sha)
end
end
# rubocop:enable Metrics/ParameterLists
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 783c2ff0915..33ca428a942 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -325,11 +325,11 @@ module Gitlab
# rubocop:disable Metrics/ParameterLists
def user_commit_files(
user, branch_name, commit_message, actions, author_email, author_name,
- start_branch_name, start_repository, force = false)
+ start_branch_name, start_repository, force = false, start_sha = nil)
req_enum = Enumerator.new do |y|
header = user_commit_files_request_header(user, branch_name,
commit_message, actions, author_email, author_name,
- start_branch_name, start_repository, force)
+ start_branch_name, start_repository, force, start_sha)
y.yield Gitaly::UserCommitFilesRequest.new(header: header)
@@ -445,7 +445,7 @@ module Gitlab
# rubocop:disable Metrics/ParameterLists
def user_commit_files_request_header(
user, branch_name, commit_message, actions, author_email, author_name,
- start_branch_name, start_repository, force)
+ start_branch_name, start_repository, force, start_sha)
Gitaly::UserCommitFilesRequestHeader.new(
repository: @gitaly_repo,
@@ -456,7 +456,8 @@ module Gitlab
commit_author_email: encode_binary(author_email),
start_branch_name: encode_binary(start_branch_name),
start_repository: start_repository.gitaly_repository,
- force: force
+ force: force,
+ start_sha: encode_binary(start_sha)
)
end
# rubocop:enable Metrics/ParameterLists
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
index 7615f6f443b..99958d7a211 100644
--- a/lib/gitlab/request_profiler/middleware.rb
+++ b/lib/gitlab/request_profiler/middleware.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'ruby-prof'
+require 'memory_profiler'
module Gitlab
module RequestProfiler
@@ -28,22 +29,73 @@ module Gitlab
end
def call_with_profiling(env)
+ case env['HTTP_X_PROFILE_MODE']
+ when 'execution', nil
+ call_with_call_stack_profiling(env)
+ when 'memory'
+ call_with_memory_profiling(env)
+ else
+ raise ActionController::BadRequest, invalid_profile_mode(env)
+ end
+ end
+
+ def invalid_profile_mode(env)
+ <<~HEREDOC
+ Invalid X-Profile-Mode: #{env['HTTP_X_PROFILE_MODE']}.
+ Supported profile mode request header:
+ - X-Profile-Mode: execution
+ - X-Profile-Mode: memory
+ HEREDOC
+ end
+
+ def call_with_call_stack_profiling(env)
ret = nil
- result = RubyProf::Profile.profile do
+ report = RubyProf::Profile.profile do
ret = catch(:warden) do
@app.call(env)
end
end
- printer = RubyProf::CallStackPrinter.new(result)
- file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}.html"
+ generate_report(env, 'execution', 'html') do |file|
+ printer = RubyProf::CallStackPrinter.new(report)
+ printer.print(file)
+ end
+
+ handle_request_ret(ret)
+ end
+
+ def call_with_memory_profiling(env)
+ ret = nil
+ report = MemoryProfiler.report do
+ ret = catch(:warden) do
+ @app.call(env)
+ end
+ end
+
+ generate_report(env, 'memory', 'txt') do |file|
+ report.pretty_print(to_file: file)
+ end
+
+ handle_request_ret(ret)
+ end
+
+ def generate_report(env, report_type, extension)
+ file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}"\
+ "_#{report_type}.#{extension}"
file_path = "#{PROFILES_DIR}/#{file_name}"
FileUtils.mkdir_p(PROFILES_DIR)
- File.open(file_path, 'wb') do |file|
- printer.print(file)
+
+ begin
+ File.open(file_path, 'wb') do |file|
+ yield(file)
+ end
+ rescue
+ FileUtils.rm(file_path)
end
+ end
+ def handle_request_ret(ret)
if ret.is_a?(Array)
ret
else
diff --git a/lib/gitlab/request_profiler/profile.rb b/lib/gitlab/request_profiler/profile.rb
index 46996ef8c51..74f2ec1d083 100644
--- a/lib/gitlab/request_profiler/profile.rb
+++ b/lib/gitlab/request_profiler/profile.rb
@@ -3,28 +3,26 @@
module Gitlab
module RequestProfiler
class Profile
- attr_reader :name, :time, :request_path
+ attr_reader :name, :time, :file_path, :request_path, :profile_mode, :type
alias_method :to_param, :name
def self.all
- Dir["#{PROFILES_DIR}/*.html"].map do |path|
+ Dir["#{PROFILES_DIR}/*.{html,txt}"].map do |path|
new(File.basename(path))
end
end
def self.find(name)
- name_dup = name.dup
- name_dup << '.html' unless name.end_with?('.html')
-
- file_path = "#{PROFILES_DIR}/#{name_dup}"
+ file_path = File.join(PROFILES_DIR, name)
return unless File.exist?(file_path)
- new(name_dup)
+ new(name)
end
def initialize(name)
@name = name
+ @file_path = File.join(PROFILES_DIR, name)
set_attributes
end
@@ -33,12 +31,23 @@ module Gitlab
File.read("#{PROFILES_DIR}/#{name}")
end
+ def content_type
+ case type
+ when 'html'
+ 'text/html'
+ when 'txt'
+ 'text/plain'
+ end
+ end
+
private
def set_attributes
- _, path, timestamp = name.split(/(.*)_(\d+)\.html$/)
+ _, path, timestamp, profile_mode, type = name.split(/(.*)_(\d+)_(.*)\.(html|txt)$/)
@request_path = path.tr('|', '/')
@time = Time.at(timestamp.to_i).utc
+ @profile_mode = profile_mode
+ @type = type
end
end
end
diff --git a/lib/peek/views/redis.rb b/lib/peek/views/redis_detailed.rb
index 73de8672fa4..12760c9b75e 100644
--- a/lib/peek/views/redis.rb
+++ b/lib/peek/views/redis_detailed.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'redis'
-require 'peek-redis'
module Gitlab
module Peek
@@ -36,11 +35,42 @@ end
module Peek
module Views
- module RedisDetailed
+ class RedisDetailed < View
REDACTED_MARKER = "<redacted>"
+ def key
+ 'redis'
+ end
+
def results
- super.merge(details: details)
+ {
+ calls: calls,
+ duration: formatted_duration,
+ details: details
+ }
+ end
+
+ def detail_store
+ ::Gitlab::SafeRequestStore['redis_call_details'] ||= []
+ end
+
+ private
+
+ def formatted_duration
+ ms = duration * 1000
+ if ms >= 1000
+ "%.2fms" % ms
+ else
+ "%.0fms" % ms
+ end
+ end
+
+ def duration
+ detail_store.map { |entry| entry[:duration] }.sum # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def calls
+ detail_store.count
end
def details
@@ -49,10 +79,6 @@ module Peek
.map(&method(:format_call_details))
end
- def detail_store
- ::Gitlab::SafeRequestStore['redis_call_details'] ||= []
- end
-
def format_call_details(call)
call.merge(cmd: format_command(call[:cmd]),
duration: (call[:duration] * 1000).round(3))
@@ -76,11 +102,3 @@ end
class Redis::Client
prepend Gitlab::Peek::RedisInstrumented
end
-
-module Peek
- module Views
- class Redis < View
- prepend Peek::Views::RedisDetailed
- end
- end
-end
diff --git a/lib/prometheus/pid_provider.rb b/lib/prometheus/pid_provider.rb
new file mode 100644
index 00000000000..c92522c73c5
--- /dev/null
+++ b/lib/prometheus/pid_provider.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'prometheus/client/support/unicorn'
+
+module Prometheus
+ module PidProvider
+ extend self
+
+ def worker_id
+ if Sidekiq.server?
+ 'sidekiq'
+ elsif defined?(Unicorn::Worker)
+ "unicorn_#{unicorn_worker_id}"
+ elsif defined?(::Puma)
+ "puma_#{puma_worker_id}"
+ else
+ "process_#{Process.pid}"
+ end
+ end
+
+ private
+
+ # This is not fully accurate as we don't really know if the nil returned
+ # is actually means we're on master or not.
+ # Follow up issue was created to address this problem and
+ # to introduce more structrured approach to a current process discovery:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/64740
+ def unicorn_worker_id
+ ::Prometheus::Client::Support::Unicorn.worker_id || 'master'
+ end
+
+ # See the comment for #unicorn_worker_id
+ def puma_worker_id
+ match = process_name.match(/cluster worker ([0-9]+):/)
+ match ? match[1] : 'master'
+ end
+
+ def process_name
+ $0
+ end
+ end
+end
diff --git a/package.json b/package.json
index 4ba9a0d9a1b..aebb52dd07c 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.67.0",
- "@gitlab/ui": "^5.6.0",
+ "@gitlab/ui": "^5.7.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 8970eeb6678..c2b0482d789 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -6,7 +6,7 @@ module QA
class Login < Page::Base
view 'app/views/devise/passwords/edit.html.haml' do
element :password_field
- element :password_confirmation
+ element :password_confirmation_field
element :change_password_button
end
@@ -44,7 +44,7 @@ module QA
def sign_in_using_credentials(user = nil)
# Don't try to log-in if we're already logged-in
- return if Page::Main::Menu.act { has_personal_area?(wait: 0) }
+ return if Page::Main::Menu.perform { |menu| menu.has_personal_area?(wait: 0) }
using_wait_time 0 do
set_initial_password_if_present
@@ -58,7 +58,7 @@ module QA
end
end
- Page::Main::Menu.act { has_personal_area? }
+ Page::Main::Menu.perform(&:has_personal_area?)
end
def sign_in_using_admin_credentials
@@ -73,7 +73,7 @@ module QA
sign_in_using_gitlab_credentials(admin)
end
- Page::Main::Menu.act { has_personal_area? }
+ Page::Main::Menu.perform(&:has_personal_area?)
end
def self.path
@@ -154,7 +154,7 @@ module QA
return unless has_content?('Change your password')
fill_element :password_field, Runtime::User.password
- fill_element :password_confirmation, Runtime::User.password
+ fill_element :password_confirmation_field, Runtime::User.password
click_element :change_password_button
end
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index c98d85d7911..d86d554356e 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -6,7 +6,7 @@ module QA
class Menu < Page::Base
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
element :sign_out_link
- element :settings_link, 'link_to s_("CurrentUser|Settings")' # rubocop:disable QA/ElementWithPattern
+ element :settings_link
end
view 'app/views/layouts/header/_default.html.haml' do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index 2363836d5e3..c9acd7df776 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Project transfer between groups' do
it 'user transfers a project between groups' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
source_group = Resource::Group.fabricate_via_api! do |group|
group.path = 'source-group'
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
index 09d1d3fe76e..6556c28ccab 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'basic user login' do
it 'user logs in using basic credentials and logs out' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
index 72dde4e5bd8..10cd8470a8f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'LDAP login' do
it 'user logs into GitLab using LDAP credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
index 87f0e9030d2..101143399f6 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
@@ -6,9 +6,9 @@ module QA
it 'User logs in to gitlab with SAML SSO' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_with_saml }
+ Page::Main::Login.perform(&:sign_in_with_saml)
- Vendor::SAMLIdp::Page::Login.act { login }
+ Vendor::SAMLIdp::Page::Login.perform(&:login)
expect(page).to have_content('Welcome to GitLab')
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 6632c2977ef..fbe857dc2a5 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
created_project = Resource::Project.fabricate_via_browser_ui! do |project|
project.name = 'awesome-project'
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index a9eafd61a91..4f8c46cbd5f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -24,16 +24,16 @@ module QA
it 'user imports a GitHub repo' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
imported_project # import the project
- Page::Main::Menu.act { go_to_projects }
+ Page::Main::Menu.perform(&:go_to_projects)
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(imported_project.name)
end
- Page::Project::Show.act { wait_for_import }
+ Page::Project::Show.perform(&:wait_for_import)
verify_repository_import
verify_issues_import
@@ -50,7 +50,7 @@ module QA
def verify_issues_import
QA::Support::Retrier.retry_on_exception do
- Page::Project::Menu.act { click_issues }
+ Page::Project::Menu.perform(&:click_issues)
expect(page).to have_content('This is a sample issue')
click_link 'This is a sample issue'
@@ -73,7 +73,7 @@ module QA
end
def verify_merge_requests_import
- Page::Project::Menu.act { click_merge_requests }
+ Page::Project::Menu.perform(&:click_merge_requests)
expect(page).to have_content('Improve README.md')
click_link 'Improve README.md'
@@ -108,7 +108,7 @@ module QA
end
def verify_wiki_import
- Page::Project::Menu.act { click_wiki }
+ Page::Project::Menu.perform(&:click_wiki)
expect(page).to have_content('Welcome to the test-project wiki!')
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
index a62a51b11f4..2e71ecb5718 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Issue comments' do
it 'user comments on an issue and edits the comment' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = 'issue title'
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index dc04e7c22a9..342f65593f6 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -8,7 +8,7 @@ module QA
it 'user creates an issue' do
create_issue
- Page::Project::Menu.act { click_issues }
+ Page::Project::Menu.perform(&:click_issues)
expect(page).to have_content(issue_title)
end
@@ -39,7 +39,7 @@ module QA
def create_issue
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Resource::Issue.fabricate_via_browser_ui! do |issue|
issue.title = issue_title
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index 301836f5ce8..af9514008f9 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -7,7 +7,7 @@ module QA
it 'user filters comments and activities in an issue' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = issue_title
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
index f41240b7605..56a7a04e840 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
@@ -7,7 +7,7 @@ module QA
it 'user adds and then removes an SSH key', :smoke do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
key = Resource::SSHKey.fabricate! do |resource|
resource.title = key_title
@@ -16,8 +16,8 @@ module QA
expect(page).to have_content("Title: #{key_title}")
expect(page).to have_content(key.fingerprint)
- Page::Main::Menu.act { click_settings_link }
- Page::Profile::Menu.act { click_ssh_keys }
+ Page::Main::Menu.perform(&:click_settings_link)
+ Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |ssh_keys|
ssh_keys.remove_key(key_title)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index d345fbfe995..51a1c19f0f7 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Files management' do
it 'user creates, edits and deletes a file via the Web' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
# Create
file_name = 'QA Test - File name'
@@ -27,7 +27,7 @@ module QA
updated_file_content = 'QA Test - Updated file content'
commit_message_for_update = 'QA Test - Update file'
- Page::File::Show.act { click_edit }
+ Page::File::Show.perform(&:click_edit)
Page::File::Form.act do
remove_content
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index 6aebd04af03..e159e517cbb 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -13,7 +13,7 @@ module QA
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
end
after do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index 3af7db751e7..33744236dd4 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -11,7 +11,7 @@ module QA
it 'user registers a new specific runner' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Resource::Runner.fabricate! do |runner|
runner.name = executor
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
index 791dc62e32f..ec0c45652fd 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Deploy token creation' do
it 'user adds a deploy token' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
deploy_token_name = 'deploy token name'
one_week_from_now = Date.today + 7
diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
index 8383dcdb983..94d20106de4 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
@@ -5,8 +5,8 @@ module QA
describe 'Mattermost support' do
it 'user creates a group with a mattermost team' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
- Page::Main::Menu.act { go_to_groups }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ Page::Main::Menu.perform(&:go_to_groups)
Page::Dashboard::Groups.perform do |page|
page.click_new_group
diff --git a/spec/controllers/admin/requests_profiles_controller_spec.rb b/spec/controllers/admin/requests_profiles_controller_spec.rb
index 10850cb4603..289bb58c5a8 100644
--- a/spec/controllers/admin/requests_profiles_controller_spec.rb
+++ b/spec/controllers/admin/requests_profiles_controller_spec.rb
@@ -10,38 +10,63 @@ describe Admin::RequestsProfilesController do
end
describe '#show' do
- let(:basename) { "profile_#{Time.now.to_i}.html" }
let(:tmpdir) { Dir.mktmpdir('profiler-test') }
let(:test_file) { File.join(tmpdir, basename) }
- let(:profile) { Gitlab::RequestProfiler::Profile.new(basename) }
- let(:sample_data) do
- <<~HTML
- <!DOCTYPE html>
- <html>
- <body>
- <h1>My First Heading</h1>
- <p>My first paragraph.</p>
- </body>
- </html>
- HTML
+
+ subject do
+ get :show, params: { name: basename }
end
before do
stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
- output = File.open(test_file, 'w')
- output.write(sample_data)
- output.close
+ File.write(test_file, sample_data)
end
after do
File.unlink(test_file)
end
- it 'loads an HTML profile' do
- get :show, params: { name: basename }
+ context 'when loading HTML profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_execution.html" }
+
+ let(:sample_data) do
+ '<html> <body> <h1>Heading</h1> <p>paragraph.</p> </body> </html>'
+ end
+
+ it 'renders the data' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(sample_data)
+ end
+ end
+
+ context 'when loading TXT profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_memory.txt" }
+
+ let(:sample_data) do
+ <<~TXT
+ Total allocated: 112096396 bytes (1080431 objects)
+ Total retained: 10312598 bytes (53567 objects)
+ TXT
+ end
+
+ it 'renders the data' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(sample_data)
+ end
+ end
+
+ context 'when loading PDF profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_anything.pdf" }
+
+ let(:sample_data) { 'mocked pdf content' }
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq(sample_data)
+ it 'fails to render the data' do
+ expect { subject }.to raise_error(ActionController::UrlGenerationError, /No route matches.*unmatched constraints:/)
+ end
end
end
end
diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb
index 2503fc9067d..a962af4952b 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -19,42 +19,97 @@ describe 'Admin::RequestsProfilesController' do
expect(page).to have_content("X-Profile-Token: #{Gitlab::RequestProfiler.profile_token}")
end
- it 'lists all available profiles' do
- time1 = 1.hour.ago
- time2 = 2.hours.ago
- time3 = 3.hours.ago
- profile1 = "|gitlab-org|gitlab-ce_#{time1.to_i}.html"
- profile2 = "|gitlab-org|gitlab-ce_#{time2.to_i}.html"
- profile3 = "|gitlab-com|infrastructure_#{time3.to_i}.html"
-
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile1}")
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile2}")
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile3}")
-
- visit admin_requests_profiles_path
+ context 'when having multiple profiles' do
+ let(:time1) { 1.hour.ago }
+ let(:time2) { 2.hours.ago }
+
+ let(:profiles) do
+ [
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time1.to_i}_execution.html",
+ created: time1,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time2.to_i}_execution.html",
+ created: time2,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time1.to_i}_memory.html",
+ created: time1,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time2.to_i}_memory.html",
+ created: time2,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time1.to_i}_execution.html",
+ created: time1,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time2.to_i}_memory.html",
+ created: time2,
+ profile_mode: 'Memory'
+ }
+ ]
+ end
- within('.card', text: '/gitlab-org/gitlab-ce') do
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile1)}']", text: time1.to_s(:long))
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile2)}']", text: time2.to_s(:long))
+ before do
+ profiles.each do |profile|
+ FileUtils.touch(File.join(Gitlab::RequestProfiler::PROFILES_DIR, profile[:name]))
+ end
end
- within('.card', text: '/gitlab-com/infrastructure') do
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile3)}']", text: time3.to_s(:long))
+ it 'lists all available profiles' do
+ visit admin_requests_profiles_path
+
+ profiles.each do |profile|
+ within('.card', text: profile[:request_path]) do
+ expect(page).to have_selector(
+ "a[href='#{admin_requests_profile_path(profile[:name])}']",
+ text: "#{profile[:created].to_s(:long)} #{profile[:profile_mode]}")
+ end
+ end
end
end
end
describe 'GET /admin/requests_profiles/:profile' do
context 'when a profile exists' do
- it 'displays the content of the profile' do
- content = 'This is a request profile'
- profile = "|gitlab-org|gitlab-ce_#{Time.now.to_i}.html"
-
+ before do
File.write("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile}", content)
+ end
+
+ context 'when is valid call stack profile' do
+ let(:content) { 'This is a call stack request profile' }
+ let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_execution.html" }
+
+ it 'displays the content' do
+ visit admin_requests_profile_path(profile)
+
+ expect(page).to have_content(content)
+ end
+ end
+
+ context 'when is valid memory profile' do
+ let(:content) { 'This is a memory request profile' }
+ let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_memory.txt" }
- visit admin_requests_profile_path(profile)
+ it 'displays the content' do
+ visit admin_requests_profile_path(profile)
- expect(page).to have_content(content)
+ expect(page).to have_content(content)
+ end
end
end
diff --git a/spec/fixtures/security-reports/deprecated/gl-sast-report.json b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
index a85b9be8b5f..2f7e47281e2 100644
--- a/spec/fixtures/security-reports/deprecated/gl-sast-report.json
+++ b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
@@ -838,6 +838,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -870,6 +875,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - fopen",
+ "value": "fopen"
+ },
+ {
"type": "cwe",
"name": "CWE-362",
"value": "362",
@@ -897,6 +907,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -930,6 +945,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - strcpy",
+ "value": "strcpy"
+ },
+ {
"type": "cwe",
"name": "CWE-120",
"value": "120",
diff --git a/spec/fixtures/security-reports/master/gl-sast-report.json b/spec/fixtures/security-reports/master/gl-sast-report.json
index 4bef3d22f70..345e1e9f83a 100644
--- a/spec/fixtures/security-reports/master/gl-sast-report.json
+++ b/spec/fixtures/security-reports/master/gl-sast-report.json
@@ -840,6 +840,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -872,6 +877,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - fopen",
+ "value": "fopen"
+ },
+ {
"type": "cwe",
"name": "CWE-362",
"value": "362",
@@ -899,6 +909,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -932,6 +947,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - strcpy",
+ "value": "strcpy"
+ },
+ {
"type": "cwe",
"name": "CWE-120",
"value": "120",
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 499fa8fc012..3d5ed4b5c0c 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -16,40 +16,16 @@ describe('IDE services', () => {
branch: TEST_BRANCH,
commit_message: 'Hello world',
actions: [],
- start_sha: undefined,
+ start_sha: TEST_COMMIT_SHA,
};
- Api.createBranch.mockReturnValue(Promise.resolve());
Api.commitMultiple.mockReturnValue(Promise.resolve());
});
- describe.each`
- startSha | shouldCreateBranch
- ${undefined} | ${false}
- ${TEST_COMMIT_SHA} | ${true}
- `('when start_sha is $startSha', ({ startSha, shouldCreateBranch }) => {
- beforeEach(() => {
- payload.start_sha = startSha;
+ it('should commit', () => {
+ services.commit(TEST_PROJECT_ID, payload);
- return services.commit(TEST_PROJECT_ID, payload);
- });
-
- if (shouldCreateBranch) {
- it('should create branch', () => {
- expect(Api.createBranch).toHaveBeenCalledWith(TEST_PROJECT_ID, {
- ref: TEST_COMMIT_SHA,
- branch: TEST_BRANCH,
- });
- });
- } else {
- it('should not create branch', () => {
- expect(Api.createBranch).not.toHaveBeenCalled();
- });
- }
-
- it('should commit', () => {
- expect(Api.commitMultiple).toHaveBeenCalledWith(TEST_PROJECT_ID, payload);
- });
+ expect(Api.commitMultiple).toHaveBeenCalledWith(TEST_PROJECT_ID, payload);
});
});
});
diff --git a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
index 989b0458481..fd439ba46bd 100644
--- a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
+++ b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
@@ -23,8 +23,7 @@ describe('JumpToNextDiscussionButton', () => {
button.trigger('click');
- expect(wrapper.emitted()).toEqual({
- onClick: [[]],
- });
+ expect(wrapper.emitted().onClick).toBeTruthy();
+ expect(wrapper.emitted().onClick.length).toBe(1);
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 3b6f67457ad..d69b4c7c162 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -73,7 +73,10 @@ describe('Suggestion Diff component', () => {
});
it('emits apply', () => {
- expect(wrapper.emittedByOrder()).toEqual([{ name: 'apply', args: [expect.any(Function)] }]);
+ expect(wrapper.emittedByOrder()).toContainEqual({
+ name: 'apply',
+ args: [expect.any(Function)],
+ });
});
it('hides apply button', () => {
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 8a3c132972e..590af53b3f2 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -245,7 +245,7 @@ describe('IDE commit module actions', () => {
master: {
workingReference: '1',
commit: {
- short_id: TEST_COMMIT_SHA,
+ id: TEST_COMMIT_SHA,
},
},
},
diff --git a/spec/javascripts/notes/components/note_actions/reply_button_spec.js b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
index 11fb89808d9..003773d07ea 100644
--- a/spec/javascripts/notes/components/note_actions/reply_button_spec.js
+++ b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
@@ -25,8 +25,7 @@ describe('ReplyButton', () => {
button.trigger('click');
- expect(wrapper.emitted()).toEqual({
- startReplying: [[]],
- });
+ expect(wrapper.emitted().startReplying).toBeTruthy();
+ expect(wrapper.emitted().startReplying.length).toBe(1);
});
});
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index 8b07da11c5d..b7a64adc2ff 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -9,12 +9,12 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:options) do
{ start_time_attrs: start_time_attrs,
end_time_attrs: end_time_attrs,
- from: 30.days.ago }
+ from: 30.days.ago,
+ project: project }
end
subject do
- described_class.new(project: project,
- stage: :issue,
+ described_class.new(stage: :issue,
options: options).fetch
end
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
index c738cc49c1f..933f3c7896e 100644
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -5,40 +5,114 @@ describe Gitlab::CycleAnalytics::CodeStage do
let(:stage_name) { :code }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
- let!(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
- let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
+ let(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
end
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes merge requests that closes issues' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_1) { create(:merge_request, source_project: project_2, created_at: 15.minutes.ago) }
+ let(:mr_2_2) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'A') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ issue_2_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:mr_3_1) { create(:merge_request, source_project: project_4, created_at: 15.minutes.ago) }
+ let(:mr_3_2) { create(:merge_request, source_project: project_5, created_at: 10.minutes.ago, source_branch: 'A') }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ issue_3_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_requests_closing_issues, merge_request: mr_3_1, issue: issue_3_1)
+ create(:merge_requests_closing_issues, merge_request: mr_3_2, issue: issue_3_2)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title, mr_3_1.title, mr_3_2.title)
+ end
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == mr_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb
new file mode 100644
index 00000000000..eea4f33ccb8
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::CycleAnalytics::GroupStageSummary do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, namespace: group) }
+ let(:project_2) { create(:project, :repository, namespace: group) }
+ let(:from) { 1.day.ago }
+ let(:user) { create(:user, :admin) }
+
+ subject { described_class.new(group, from: Time.now, current_user: user).data }
+
+ describe "#new_issues" do
+ context 'with from date' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:issue, project: project) }
+ Timecop.freeze(5.days.ago) { create(:issue, project: project_2) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
+ end
+
+ it "finds the number of issues created after it" do
+ expect(subject.first[:value]).to eq(2)
+ end
+
+ context 'with subgroups' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group, parent: group))) }
+ end
+
+ it "finds issues from them" do
+ expect(subject.first[:value]).to eq(3)
+ end
+ end
+ end
+
+ context 'with other projects' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group))) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
+ end
+
+ it "doesn't find issues from them" do
+ expect(subject.first[:value]).to eq(2)
+ end
+ end
+ end
+
+ describe "#deploys" do
+ context 'with from date' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
+ Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project_2) }
+ Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project_2) }
+ end
+
+ it "finds the number of deploys made created after it" do
+ expect(subject.second[:value]).to eq(2)
+ end
+
+ context 'with subgroups' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: create(:group, parent: group)))
+ end
+ end
+
+ it "finds deploys from them" do
+ expect(subject.second[:value]).to eq(3)
+ end
+ end
+ end
+
+ context 'with other projects' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: create(:group)))
+ end
+ end
+
+ it "doesn't find deploys from them" do
+ expect(subject.second[:value]).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
index 3b6af9cbaed..ffd0b84cb57 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -4,11 +4,11 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::IssueStage do
let(:stage_name) { :issue }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago )
@@ -24,7 +24,7 @@ describe Gitlab::CycleAnalytics::IssueStage do
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
@@ -36,4 +36,67 @@ describe Gitlab::CycleAnalytics::IssueStage do
expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title)
end
end
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
+ end
+
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
index 506a8160412..3cd1320ca9c 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::CycleAnalytics::PlanStage do
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
@@ -18,22 +18,88 @@ describe Gitlab::CycleAnalytics::PlanStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes issues with metrics' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
+ issue_2_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
+ issue_3_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
+ end
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
index f072a9644e8..6d14973c711 100644
--- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
@@ -4,14 +4,14 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::ReviewStage do
let(:stage_name) { :review }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- 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(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ 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(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
mr_1.metrics.update!(merged_at: 30.minutes.ago)
@@ -24,22 +24,66 @@ describe Gitlab::CycleAnalytics::ReviewStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes merge requests that close issues' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_2_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
+ let!(:mr_2_4) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'C') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ mr_2_1.metrics.update!(merged_at: 30.minutes.ago)
+ mr_2_2.metrics.update!(merged_at: 10.minutes.ago)
+
+ create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
+ create(:merge_requests_closing_issues, merge_request: mr_2_3, issue: issue_2_3)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
index c22d27f60d6..b001a46001e 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
shared_examples 'default query config' do
let(:project) { create(:project) }
- let(:event) { described_class.new(project: project, stage: stage_name, options: { from: 1.day.ago }) }
+ let(:event) { described_class.new(stage: stage_name, options: { from: 1.day.ago, project: project }) }
it 'has the stage attribute' do
expect(event.stage).not_to be_nil
diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
index 1a4b572cc11..c146146723f 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
@@ -3,10 +3,10 @@ require 'spec_helper'
shared_examples 'base stage' do
ISSUES_MEDIAN = 30.minutes.to_i
- let(:stage) { described_class.new(project: double, options: {}) }
+ let(:stage) { described_class.new(options: { project: double }) }
before do
- allow(stage).to receive(:median).and_return(1.12)
+ allow(stage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
index 17d5fbb9733..9ca12cc448c 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -5,16 +5,16 @@ describe Gitlab::CycleAnalytics::StagingStage do
let(:stage_name) { :staging }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- 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(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ 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(:build_1) { create(:ci_build, project: project) }
let(:build_2) { create(:ci_build, project: project) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
@@ -28,22 +28,68 @@ describe Gitlab::CycleAnalytics::StagingStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes builds connected to merge request' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:build_1) { create(:ci_build, project: project_2) }
+ let(:build_2) { create(:ci_build, project: project_3) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
+ mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id)
+ mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project_2).commit_id)
+
+ create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2_2)
+ create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_2_3)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index 8633a63849f..41028c44a00 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -4,7 +4,7 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
let(:project) { create(:project) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
it_behaves_like 'base stage'
@@ -36,7 +36,7 @@ describe Gitlab::CycleAnalytics::TestStage do
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
index 8122e85a981..ad61bdeace7 100644
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::CycleAnalytics::UsageData do
expect(result).to have_key(:avg_cycle_analytics)
- CycleAnalytics::Base::STAGES.each do |stage|
+ CycleAnalytics::LevelBase::STAGES.each do |stage|
expect(result[:avg_cycle_analytics]).to have_key(stage)
stage_values = result[:avg_cycle_analytics][stage]
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index ce15057dd7d..6515be85ae3 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -39,6 +39,26 @@ describe Gitlab::Git do
end
end
+ describe '.commit_id?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:sha, :result) do
+ '' | false
+ 'foobar' | false
+ '4b825dc' | false
+ 'zzz25dc642cb6eb9a060e54bf8d69288fbee4904' | false
+
+ '4b825dc642cb6eb9a060e54bf8d69288fbee4904' | true
+ Gitlab::Git::BLANK_SHA | true
+ end
+
+ with_them do
+ it 'returns the expected result' do
+ expect(described_class.commit_id?(sha)).to eq(result)
+ end
+ end
+ end
+
describe '.shas_eql?' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
index da13b6df53b..61096e6c69e 100644
--- a/spec/lib/peek/views/redis_detailed_spec.rb
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -2,14 +2,8 @@
require 'spec_helper'
-describe Peek::Views::RedisDetailed do
- let(:redis_detailed_class) do
- Class.new do
- include Peek::Views::RedisDetailed
- end
- end
-
- subject { redis_detailed_class.new }
+describe Peek::Views::RedisDetailed, :request_store do
+ subject { described_class.new }
using RSpec::Parameterized::TableSyntax
@@ -22,15 +16,24 @@ describe Peek::Views::RedisDetailed do
end
with_them do
- it 'scrubs Redis commands', :request_store do
+ it 'scrubs Redis commands' do
subject.detail_store << { cmd: cmd, duration: 1.second }
- expect(subject.details.count).to eq(1)
- expect(subject.details.first)
+ expect(subject.results[:details].count).to eq(1)
+ expect(subject.results[:details].first)
.to eq({
cmd: expected,
duration: 1000
})
end
end
+
+ it 'returns aggregated results' do
+ subject.detail_store << { cmd: [:get, 'test'], duration: 0.001 }
+ subject.detail_store << { cmd: [:get, 'test'], duration: 1.second }
+
+ expect(subject.results[:calls]).to eq(2)
+ expect(subject.results[:duration]).to eq('1001.00ms')
+ expect(subject.results[:details].count).to eq(2)
+ end
end
diff --git a/spec/lib/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb
new file mode 100644
index 00000000000..e7d500612b1
--- /dev/null
+++ b/spec/lib/prometheus/pid_provider_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Prometheus::PidProvider do
+ describe '.worker_id' do
+ subject { described_class.worker_id }
+
+ let(:sidekiq_module) { Module.new }
+
+ before do
+ allow(sidekiq_module).to receive(:server?).and_return(false)
+ stub_const('Sidekiq', sidekiq_module)
+ end
+
+ context 'when running in Sidekiq server mode' do
+ before do
+ expect(Sidekiq).to receive(:server?).and_return(true)
+ end
+
+ it { is_expected.to eq 'sidekiq' }
+ end
+
+ context 'when running in Unicorn mode' do
+ before do
+ stub_const('Unicorn::Worker', Class.new)
+ hide_const('Puma')
+ end
+
+ context 'when `Prometheus::Client::Support::Unicorn` provides worker_id' do
+ before do
+ expect(::Prometheus::Client::Support::Unicorn).to receive(:worker_id).and_return(1)
+ end
+
+ it { is_expected.to eq 'unicorn_1' }
+ end
+
+ context 'when no worker_id is provided from `Prometheus::Client::Support::Unicorn`' do
+ before do
+ expect(::Prometheus::Client::Support::Unicorn).to receive(:worker_id).and_return(nil)
+ end
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+ end
+
+ context 'when running in Puma mode' do
+ before do
+ stub_const('Puma', Module.new)
+ hide_const('Unicorn::Worker')
+ end
+
+ context 'when cluster worker id is specified in process name' do
+ before do
+ expect(described_class).to receive(:process_name).and_return('puma: cluster worker 1: 17483 [gitlab-puma-worker]')
+ end
+
+ it { is_expected.to eq 'puma_1' }
+ end
+
+ context 'when no worker id is specified in process name' do
+ before do
+ expect(described_class).to receive(:process_name).and_return('bin/puma')
+ end
+
+ it { is_expected.to eq 'puma_master' }
+ end
+ end
+
+ context 'when running in unknown mode' do
+ before do
+ hide_const('Puma')
+ hide_const('Unicorn::Worker')
+ end
+
+ it { is_expected.to eq "process_#{Process.pid}" }
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index db6e70973ae..808659552ff 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -38,7 +38,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue)
deploy_master(user, project)
- expect(subject[:code].median).to be_nil
+ expect(subject[:code].project_median).to be_nil
end
end
end
@@ -68,7 +68,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:code].median).to be_nil
+ expect(subject[:code].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/group_level_spec.rb b/spec/models/cycle_analytics/group_level_spec.rb
new file mode 100644
index 00000000000..154c1b9c0f8
--- /dev/null
+++ b/spec/models/cycle_analytics/group_level_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe CycleAnalytics::GroupLevel do
+ let(:group) { create(:group)}
+ let(:project) { create(:project, :repository, namespace: group) }
+ let(:from_date) { 10.days.ago }
+ let(:user) { create(:user, :admin) }
+ let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
+ let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
+
+ subject { described_class.new(group: group, options: { from: from_date, current_user: user }) }
+
+ describe '#permissions' do
+ it 'returns true for all stages' do
+ expect(subject.permissions.values.uniq).to eq([true])
+ end
+ end
+
+ describe '#stats' do
+ before do
+ allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
+
+ create_cycle(user, project, issue, mr, milestone, pipeline)
+ deploy_master(user, project)
+ end
+
+ it 'returns medians for each stage for a specific group' do
+ expect(subject.no_stats?).to eq(false)
+ end
+ end
+
+ describe '#summary' do
+ before do
+ create_cycle(user, project, issue, mr, milestone, pipeline)
+ deploy_master(user, project)
+ end
+
+ it 'returns medians for each stage for a specific group' do
+ expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly(1, 1)
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 4ccbdf29df6..8cdf83b1292 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -43,7 +43,7 @@ describe 'CycleAnalytics#issue' do
create_merge_request_closing_issue(user, project, issue)
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:issue].median).to be_nil
+ expect(subject[:issue].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index c99c38e9cf3..28ad9bd194d 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -47,7 +47,7 @@ describe 'CycleAnalytics#plan' do
create_merge_request_closing_issue(user, project, issue, source_branch: branch_name)
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:issue].median).to be_nil
+ expect(subject[:issue].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index ddd199362d1..613c1786540 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -41,7 +41,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project)
- expect(subject[:production].median).to be_nil
+ expect(subject[:production].project_median).to be_nil
end
end
@@ -52,7 +52,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')
- expect(subject[:production].median).to be_nil
+ expect(subject[:production].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/project_level_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index 77bd0bfeb9c..4de01b1c679 100644
--- a/spec/models/cycle_analytics/project_level_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -23,7 +23,7 @@ describe CycleAnalytics::ProjectLevel do
it 'returns every median for each stage for a specific project' do
values = described_class::STAGES.each_with_object({}) do |stage_name, hsh|
- hsh[stage_name] = subject[stage_name].median.presence
+ hsh[stage_name] = subject[stage_name].project_median.presence
end
expect(subject.all_medians_by_stage).to eq(values)
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 63c481ed465..ef88fd86340 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -28,7 +28,7 @@ describe 'CycleAnalytics#review' do
it "returns nil" do
MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
- expect(subject[:review].median).to be_nil
+ expect(subject[:review].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index c134b97553f..571792559d8 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project)
- expect(subject[:staging].median).to be_nil
+ expect(subject[:staging].project_median).to be_nil
end
end
@@ -56,7 +56,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')
- expect(subject[:staging].median).to be_nil
+ expect(subject[:staging].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index a6ea73b2699..7b3001d2bd8 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -36,7 +36,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -47,7 +47,7 @@ describe 'CycleAnalytics#test' do
pipeline.run!
pipeline.succeed!
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -62,7 +62,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -77,7 +77,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 45da5391bc8..e8e17228523 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -320,67 +320,132 @@ describe API::Commits do
end
end
- context 'when the API user is a guest' do
+ context 'when committing to a new branch' do
def last_commit_id(project, branch_name)
project.repository.find_branch(branch_name)&.dereferenced_target&.id
end
- let(:public_project) { create(:project, :public, :repository) }
- let!(:url) { "/projects/#{public_project.id}/repository/commits" }
- let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
+ before do
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
+ end
- it 'returns a 403' do
- post api(url, guest), params: valid_c_params
+ context 'when the API user is a guest' do
+ let(:public_project) { create(:project, :public, :repository) }
+ let(:url) { "/projects/#{public_project.id}/repository/commits" }
+ let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
- expect(response).to have_gitlab_http_status(403)
- end
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
- context 'when start_project is provided' do
- context 'when posting to a forked project the user owns' do
- let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
- let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+ expect(response).to have_gitlab_http_status(403)
+ end
- before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
- end
+ context 'when start_project is provided' do
+ context 'when posting to a forked project the user owns' do
+ let(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
+ let(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ context 'identified by Integer (id)' do
+ before do
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ end
+ end
- context 'identified by Integer (id)' do
- before do
- valid_c_params[:start_project] = public_project.id
+ context 'identified by String (full_path)' do
+ before do
+ valid_c_params[:start_project] = public_project.full_path
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ end
end
- it 'adds a new commit to forked_project and returns a 201' do
- expect { post api(url, guest), params: valid_c_params }
- .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
- .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ context 'when branch already exists' do
+ before do
+ valid_c_params.delete(:start_branch)
+ valid_c_params[:branch] = 'master'
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 400' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("A branch called 'master' already exists. Switch to that branch in order to make changes")
+ end
+
+ context 'when force is set to true' do
+ before do
+ valid_c_params[:force] = true
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:branch]) }
+ end
+ end
+ end
+
+ context 'when start_sha is also provided' do
+ let(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: false) }
+ let(:start_sha) { public_project.repository.commit.parent.sha }
+
+ before do
+ # initialize an empty repository to force fetching from the original project
+ forked_project.repository.create_if_not_exists
- expect(response).to have_gitlab_http_status(201)
+ valid_c_params[:start_project] = public_project.id
+ valid_c_params[:start_sha] = start_sha
+ valid_c_params.delete(:start_branch)
+ end
+
+ it 'fetches the start_sha from the original project to use as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(forked_project, 'master') }
+
+ last_commit = forked_project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
end
end
- context 'identified by String (full_path)' do
+ context 'when the target project is not part of the fork network of start_project' do
+ let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
+ let(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+
before do
- valid_c_params[:start_project] = public_project.full_path
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
+ valid_c_params[:start_project] = public_project.id
end
- it 'adds a new commit to forked_project and returns a 201' do
- expect { post api(url, guest), params: valid_c_params }
- .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
- .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
- expect(response).to have_gitlab_http_status(201)
+ expect(response).to have_gitlab_http_status(403)
end
end
end
- context 'when the target project is not part of the fork network of start_project' do
- let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
- let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+ context 'when posting to a forked project the user does not have write access' do
+ let(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
+ let(:url) { "/projects/#{forked_project.id}/repository/commits" }
before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
valid_c_params[:start_project] = public_project.id
end
@@ -392,20 +457,68 @@ describe API::Commits do
end
end
- context 'when posting to a forked project the user does not have write access' do
- let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
- let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+ context 'when start_sha is provided' do
+ let(:start_sha) { project.repository.commit.parent.sha }
before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
- valid_c_params[:start_project] = public_project.id
+ valid_c_params[:start_sha] = start_sha
+ valid_c_params.delete(:start_branch)
end
- it 'returns a 403' do
- post api(url, guest), params: valid_c_params
+ it 'returns a 400 if start_branch is also provided' do
+ valid_c_params[:start_branch] = 'master'
+ post api(url, user), params: valid_c_params
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('start_branch, start_sha are mutually exclusive')
+ end
+
+ it 'returns a 400 if branch already exists' do
+ valid_c_params[:branch] = 'master'
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("A branch called 'master' already exists. Switch to that branch in order to make changes")
+ end
+
+ it 'returns a 400 if start_sha does not exist' do
+ valid_c_params[:start_sha] = '1' * 40
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("Cannot find start_sha '#{valid_c_params[:start_sha]}'")
+ end
+
+ it 'returns a 400 if start_sha is not a full SHA' do
+ valid_c_params[:start_sha] = start_sha.slice(0, 7)
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("Invalid start_sha '#{valid_c_params[:start_sha]}'")
+ end
+
+ it 'uses the start_sha as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, user), params: valid_c_params }
+ .to change { last_commit_id(project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(project, 'master') }
+
+ last_commit = project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
+
+ context 'when force is set to true and branch already exists' do
+ before do
+ valid_c_params[:force] = true
+ valid_c_params[:branch] = 'master'
+ end
+
+ it 'uses the start_sha as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, user), params: valid_c_params }
+ .to change { last_commit_id(project, valid_c_params[:branch]) }
+
+ last_commit = project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
end
end
end
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 75b22b1879b..851affbcf88 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -3,13 +3,18 @@ require 'spec_helper'
describe 'Request Profiler' do
let(:user) { create(:user) }
- shared_examples 'profiling a request' do
+ shared_examples 'profiling a request' do |profile_type, extension|
before do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
allow(RubyProf::Profile).to receive(:profile) do |&blk|
blk.call
RubyProf::Profile.new
end
+ allow(MemoryProfiler).to receive(:report) do |&blk|
+ blk.call
+ MemoryProfiler.start
+ MemoryProfiler.stop
+ end
end
it 'creates a profile of the request' do
@@ -18,10 +23,11 @@ describe 'Request Profiler' do
path = "/#{project.full_path}"
Timecop.freeze(time) do
- get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token }
+ get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token, 'X-Profile-Mode' => profile_type }
end
- profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}.html"
+ profile_type = 'execution' if profile_type.nil?
+ profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}_#{profile_type}.#{extension}"
expect(File.exist?(profile_path)).to be true
end
@@ -35,10 +41,14 @@ describe 'Request Profiler' do
login_as(user)
end
- include_examples 'profiling a request'
+ include_examples 'profiling a request', 'execution', 'html'
+ include_examples 'profiling a request', nil, 'html'
+ include_examples 'profiling a request', 'memory', 'txt'
end
context "when user is not logged-in" do
- include_examples 'profiling a request'
+ include_examples 'profiling a request', 'execution', 'html'
+ include_examples 'profiling a request', nil, 'html'
+ include_examples 'profiling a request', 'memory', 'txt'
end
end
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 89588b4df2b..dd5e43a4b62 100644
--- a/spec/serializers/analytics_issue_entity_spec.rb
+++ b/spec/serializers/analytics_issue_entity_spec.rb
@@ -9,12 +9,14 @@ describe AnalyticsIssueEntity do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ name: project.name,
+ path: project.namespace
}
end
let(:project) { create(:project) }
- let(:request) { EntityRequest.new(project: project, entity: :merge_request) }
+ let(:request) { EntityRequest.new(entity: :merge_request) }
let(:entity) do
described_class.new(entity_hash, request: request, project: project)
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 5befc28f4fa..c9ffe1c5dad 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe AnalyticsIssueSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
@@ -16,7 +16,9 @@ describe AnalyticsIssueSerializer do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ name: project.name,
+ path: project.namespace
}
end
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index 62067cc0ef2..123d7d795ce 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe AnalyticsMergeRequestSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
@@ -17,7 +17,9 @@ describe AnalyticsMergeRequestSerializer do
id: "1",
state: 'open',
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ name: project.name,
+ path: project.namespace
}
end
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index 5b05c2f2ef3..86a796a2d94 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -6,11 +6,11 @@ describe AnalyticsStageSerializer do
end
let(:resource) do
- Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {})
+ Gitlab::CycleAnalytics::CodeStage.new(options: { project: double })
end
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end
@@ -24,7 +24,7 @@ describe AnalyticsStageSerializer do
context 'when median is equal 0' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0)
end
it 'sets the value to nil' do
@@ -34,7 +34,7 @@ describe AnalyticsStageSerializer do
context 'when median is below 1' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0.12)
end
it 'sets the value to equal to median' do
@@ -44,7 +44,7 @@ describe AnalyticsStageSerializer do
context 'when median is above 1' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(60.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(60.12)
end
it 'sets the value to equal to median' do
diff --git a/spec/services/submodules/update_service_spec.rb b/spec/services/submodules/update_service_spec.rb
index cf92350c1b2..47b31d4bcbf 100644
--- a/spec/services/submodules/update_service_spec.rb
+++ b/spec/services/submodules/update_service_spec.rb
@@ -142,7 +142,7 @@ describe Submodules::UpdateService do
let(:branch_name) { nil }
it_behaves_like 'returns error result' do
- let(:error_message) { 'You can only create or edit files when you are on a branch' }
+ let(:error_message) { 'Invalid parameters' }
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 978158192d9..a6fb172e79b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -104,6 +104,7 @@ RSpec.configure do |config|
config.include Rails.application.routes.url_helpers, type: :routing
config.include PolicyHelpers, type: :policy
config.include MemoryUsageHelper
+ config.include ExpectRequestWithStatus, type: :request
if ENV['CI']
# This includes the first try, i.e. tests will be run 4 times before failing.
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 19b32c84d81..be1c2bc3046 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -50,7 +50,7 @@ module CycleAnalyticsHelpers
end
median_time_difference = time_differences.sort[2]
- expect(subject[phase].median).to be_within(5).of(median_time_difference)
+ expect(subject[phase].project_median).to be_within(5).of(median_time_difference)
end
context "when the data belongs to another project" do
@@ -80,7 +80,7 @@ module CycleAnalyticsHelpers
# Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
@@ -103,7 +103,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -121,7 +121,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -138,7 +138,7 @@ module CycleAnalyticsHelpers
post_fn[self, data] if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -146,7 +146,7 @@ module CycleAnalyticsHelpers
context "when none of the start / end conditions are matched" do
it "returns nil" do
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
diff --git a/spec/support/helpers/expect_request_with_status.rb b/spec/support/helpers/expect_request_with_status.rb
new file mode 100644
index 00000000000..0469a94e336
--- /dev/null
+++ b/spec/support/helpers/expect_request_with_status.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module ExpectRequestWithStatus
+ def expect_request_with_status(status)
+ expect do
+ yield
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index a0fbdfd4541..c0c1887a9d1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -705,21 +705,21 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579"
integrity sha512-hJOmWEs6RkjzyKkb1vc9wwKGZIBIP0coHkxu/KgOoxhBVudpGk4CH7xJ6UuB2TKpb0SEh5CC1CzRZfBYaFhsaA==
-"@gitlab/ui@^5.6.0":
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.6.0.tgz#6b5408050229e2135359f3fce5d0de7718326a0d"
- integrity sha512-EohpACc5OCK8pOWgwB7/QZUcg3tA9k863ku6Ik9NxaRSKt/JIpQ8RI4wCr4UmqhejZLQMD9VZHLUmc9Sf3Mk9w==
+"@gitlab/ui@^5.7.0":
+ version "5.7.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.7.0.tgz#2ce6b431de262f09b2e1dbe77b047d6e8e1ca41d"
+ integrity sha512-zOPFNrCGyZrgqa8OXNhtfRg4aQ6pRCpIV2+alq3/4jllYb3HHHH+jIk/ejLrvNe8+fk7LRNSvJEvuInRLqBbEg==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
bootstrap "4.3.1"
- bootstrap-vue "^2.0.0-rc.24"
+ bootstrap-vue "^2.0.0-rc.26"
copy-to-clipboard "^3.0.8"
core-js "^2.6.9"
echarts "^4.2.0-rc.2"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
- lodash "^4.17.11"
+ lodash "^4.17.14"
url-search-params-polyfill "^5.0.0"
vue "^2.6.10"
vue-loader "^15.4.2"
@@ -1921,14 +1921,13 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
-bootstrap-vue@^2.0.0-rc.24:
- version "2.0.0-rc.24"
- resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.24.tgz#8ea5bbcd19e0f9b4f87ed4d9ba72abaa35231f32"
- integrity sha512-8rA/I9tOvpNVIuMKD3rdlrUqgVdPEw4vPI0X8OeFJcG2hHvCHeZDF7FmWqxSeehIrUHGDV17HlTGSuP/v1Sp5g==
+bootstrap-vue@^2.0.0-rc.26:
+ version "2.0.0-rc.26"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.26.tgz#2b8b9116a452584ae20ba8310d143635bc9e1375"
+ integrity sha512-AzN+IRTmfR9rLFWNGt+v2XPmQjZiAlH4x5z2kStA3UoJ5LR91K34iZ8apaa6isDo+DfWDcwH4q7OwHM+VNxWwg==
dependencies:
"@nuxt/opencollective" "^0.2.2"
bootstrap "^4.3.1"
- core-js ">=2.6.5 <3.0.0"
popper.js "^1.15.0"
portal-vue "^2.1.5"
vue-functional-data-merge "^3.1.0"
@@ -2827,7 +2826,7 @@ core-js@3.0.1:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.1.tgz#1343182634298f7f38622f95e73f54e48ddf4738"
integrity sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==
-"core-js@>=2.6.5 <3.0.0", core-js@^2.2.0, core-js@^2.6.9:
+core-js@^2.2.0, core-js@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
@@ -7016,7 +7015,7 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.5.0, lodash@~4.17.10:
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.5.0, lodash@~4.17.10:
version "4.17.14"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==