diff options
137 files changed, 1655 insertions, 454 deletions
@@ -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 %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 Binary files differindex 1b06e240fa0..3b34f207974 100644 --- a/doc/administration/monitoring/performance/img/request_profile_result.png +++ b/doc/administration/monitoring/performance/img/request_profile_result.png 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 Binary files differindex 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 diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png Binary files differindex 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 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 Binary files differnew file mode 100644 index 00000000000..7c24226a6c4 --- /dev/null +++ b/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png 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 Binary files differnew file mode 100644 index 00000000000..d6d391c6dd9 --- /dev/null +++ b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png 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== |