diff --git a/app/assets/javascripts/pipelines/components/parsing_utils.js b/app/assets/javascripts/pipelines/components/parsing_utils.js index 9d886e0e379..f4b18897002 100644 --- a/app/assets/javascripts/pipelines/components/parsing_utils.js +++ b/app/assets/javascripts/pipelines/components/parsing_utils.js @@ -55,21 +55,25 @@ export const createNodeDict = (nodes) => { export const makeLinksFromNodes = (nodes, nodeDict) => { const constantLinkValue = 10; // all links are the same weight return nodes - .map((group) => { - return group.jobs.map((job) => { - if (!job.needs) { - return []; - } + .map(({ jobs, name: groupName }) => + jobs.map(({ needs = [] }) => + needs.reduce((acc, needed) => { + // It's possible that we have an optional job, which + // is being needed by another job. In that scenario, + // the needed job doesn't exist, so we don't want to + // create link for it. + if (nodeDict[needed]?.name) { + acc.push({ + source: nodeDict[needed].name, + target: groupName, + value: constantLinkValue, + }); + } - return job.needs.map((needed) => { - return { - source: nodeDict[needed]?.name, - target: group.name, - value: constantLinkValue, - }; - }); - }); - }) + return acc; + }, []), + ), + ) .flat(2); }; diff --git a/app/assets/javascripts/pipelines/utils.js b/app/assets/javascripts/pipelines/utils.js index 800a363cada..02a9e5b7fc6 100644 --- a/app/assets/javascripts/pipelines/utils.js +++ b/app/assets/javascripts/pipelines/utils.js @@ -39,7 +39,13 @@ export const generateJobNeedsDict = (jobs = {}) => { } return jobs[jobName].needs - .map((job) => { + .reduce((needsAcc, job) => { + // It's possible that a needs refer to an optional job + // that is not defined in which case we don't add that entry + if (!jobs[job]) { + return needsAcc; + } + // If we already have the needs of a job in the accumulator, // then we use the memoized data instead of the recursive call // to save some performance. @@ -50,11 +56,11 @@ export const generateJobNeedsDict = (jobs = {}) => { // to the list of `needs` to ensure we can properly reference it. const group = jobs[job]; if (group.size > 1) { - return [job, group.name, newNeeds]; + return [...needsAcc, job, group.name, newNeeds]; } - return [job, newNeeds]; - }) + return [...needsAcc, job, newNeeds]; + }, []) .flat(Infinity); }; diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss index 14d1a0663d0..95b8298c59b 100644 --- a/app/assets/stylesheets/framework/contextual_sidebar.scss +++ b/app/assets/stylesheets/framework/contextual_sidebar.scss @@ -363,21 +363,8 @@ // Collapsed nav .toggle-sidebar-button, -.close-nav-button, -.toggle-right-sidebar-button { - transition: width $sidebar-transition-duration; - height: $toggle-sidebar-height; - padding: 0 $gl-padding; - background-color: $gray-light; - border: 0; - color: $gl-text-color-secondary; - display: flex; - align-items: center; - - &:hover { - background-color: $border-color; - color: $gl-text-color; - } +.close-nav-button { + @include side-panel-toggle; } .toggle-sidebar-button, @@ -396,10 +383,6 @@ } } -.toggle-right-sidebar-button { - border-bottom: 1px solid $border-color; -} - .collapse-text { white-space: nowrap; overflow: hidden; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1e2fc1445e8..fcf86680bb3 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -446,3 +446,19 @@ } } } + +@mixin side-panel-toggle { + transition: width $sidebar-transition-duration; + height: $toggle-sidebar-height; + padding: 0 $gl-padding; + background-color: $gray-light; + border: 0; + color: $gl-text-color-secondary; + display: flex; + align-items: center; + + &:hover { + background-color: $border-color; + color: $gl-text-color; + } +} diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index cb8a0c40f7f..e35feb8c62d 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -232,3 +232,8 @@ } } } + +.toggle-right-sidebar-button { + @include side-panel-toggle; + border-bottom: 1px solid $border-color; +} diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 6a2fa2ee7a1..b52a3c445b5 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -5,6 +5,12 @@ } .avatar-image { + margin-bottom: $grid-size; + + .avatar { + float: none; + } + @include media-breakpoint-up(sm) { float: left; margin-bottom: 0; diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb index 011ac9a42f8..a8038878504 100644 --- a/app/controllers/projects/merge_requests/conflicts_controller.rb +++ b/app/controllers/projects/merge_requests/conflicts_controller.rb @@ -9,6 +9,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap respond_to do |format| format.html do @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar') + Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_loading_conflict_ui_action(user: current_user) end format.json do @@ -42,6 +43,8 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap def resolve_conflicts return render_404 unless @conflicts_list.can_be_resolved_in_ui? + Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_resolve_conflict_action(user: current_user) + if @merge_request.can_be_merged? render status: :bad_request, json: { message: _('The merge conflicts for this merge request have already been resolved.') } return diff --git a/app/models/issue.rb b/app/models/issue.rb index 2077f9bfdbb..5b65d059ee6 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -524,7 +524,7 @@ class Issue < ApplicationRecord def could_not_move(exception) # Symptom of running out of space - schedule rebalancing - IssueRebalancingWorker.perform_async(nil, project_id) + IssueRebalancingWorker.perform_async(nil, *project.self_or_root_group_ids) end end diff --git a/app/models/project.rb b/app/models/project.rb index 238be813c4b..7eaf9cba85f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2561,6 +2561,17 @@ class Project < ApplicationRecord end end + # for projects that are part of user namespace, return project. + def self_or_root_group_ids + if group + root_group = root_namespace + else + project = self + end + + [project&.id, root_group&.id] + end + def package_already_taken?(package_name) namespace.root_ancestor.all_projects .joins(:packages) diff --git a/app/services/issue_rebalancing_service.rb b/app/services/issue_rebalancing_service.rb index 6a8d45b92b2..142d287370f 100644 --- a/app/services/issue_rebalancing_service.rb +++ b/app/services/issue_rebalancing_service.rb @@ -15,14 +15,13 @@ class IssueRebalancingService [5.seconds, 1.second] ].freeze - def initialize(issue) - @issue = issue - @base = Issue.relative_positioning_query_base(issue) + def initialize(projects_collection) + @root_namespace = projects_collection.take.root_namespace # rubocop:disable CodeReuse/ActiveRecord + @base = Issue.in_projects(projects_collection) end def execute - gates = [issue.project, issue.project.group].compact - return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) } + return unless Feature.enabled?(:rebalance_issues, root_namespace) raise TooManyIssues, "#{issue_count} issues" if issue_count > MAX_ISSUE_COUNT @@ -57,7 +56,7 @@ class IssueRebalancingService private - attr_reader :issue, :base + attr_reader :root_namespace, :base # rubocop: disable CodeReuse/ActiveRecord def indexed_ids diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 72e906e20f1..8fce346d988 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -29,7 +29,7 @@ module Issues gates = [issue.project, issue.project.group].compact return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) } - IssueRebalancingWorker.perform_async(nil, issue.project_id) + IssueRebalancingWorker.perform_async(nil, *issue.project.self_or_root_group_ids) end private diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 654d9356739..c24ab10ad86 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -69,8 +69,7 @@ class WebHookService http_status: response.code, message: response.to_s } - rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, - Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep, + rescue *Gitlab::HTTP::HTTP_ERRORS, Gitlab::Json::LimitedEncoder::LimitExceeded, URI::InvalidURIError => e execution_duration = Gitlab::Metrics::System.monotonic_time - start_time log_execution( diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index c3ec2f7bab3..ef4a40eb69b 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -11,7 +11,7 @@ .row.js-search-settings-section .col-lg-4.profile-settings-sidebar %h4.gl-mt-0 - = s_("Profiles|Public Avatar") + = s_("Profiles|Public avatar") %p - if @user.avatar? - if gravatar_enabled? @@ -27,18 +27,17 @@ .md = brand_profile_image_guidelines .col-lg-8 - .clearfix.avatar-image.gl-mb-3 + .avatar-image = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do - = image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160' + = image_tag avatar_icon_for_user(@user, 96), alt: '', class: 'avatar s96' %h5.gl-mt-0= s_("Profiles|Upload new avatar") - .gl-mt-2.gl-mb-3 - %button.gl-button.btn.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...") + .gl-my-3 + %button.gl-button.btn.btn-default.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...") %span.avatar-file-name.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen.") = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' - .form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.") + .gl-text-gray-500= s_("Profiles|The maximum file size allowed is 200KB.") - if @user.avatar? - %hr - = link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'gl-button btn btn-danger btn-inverted' + = link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'gl-button btn btn-danger-secondary btn-sm gl-mt-5' .col-lg-12 %hr .row.js-search-settings-section @@ -124,10 +123,10 @@ = f.check_box :include_private_contributions, label: s_('Profiles|Include private contributions on my profile'), wrapper_class: 'mb-2', inline: true .help-block = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information") - .row.gl-mt-3.gl-mb-3.gl-justify-content-end - .col-lg-8 - = f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm' - = link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-cancel' + .row.gl-justify-content-end.gl-mt-5 + .col-lg-8.gl-display-flex + = f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm gl-mr-3' + = link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-default btn-cancel' .modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } } .modal-dialog diff --git a/app/views/shared/file_hooks/_index.html.haml b/app/views/shared/file_hooks/_index.html.haml index cab0adf159b..d48e9f3d02e 100644 --- a/app/views/shared/file_hooks/_index.html.haml +++ b/app/views/shared/file_hooks/_index.html.haml @@ -19,9 +19,6 @@ %li .monospace = File.basename(file) - - if File.dirname(file).ends_with?('plugins') - .text-warning - = _('Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory.') - else .card.bg-light.text-center diff --git a/app/workers/issue_placement_worker.rb b/app/workers/issue_placement_worker.rb index dba791c3f05..8166dda135e 100644 --- a/app/workers/issue_placement_worker.rb +++ b/app/workers/issue_placement_worker.rb @@ -41,7 +41,7 @@ class IssuePlacementWorker IssuePlacementWorker.perform_async(nil, leftover.project_id) if leftover.present? rescue RelativePositioning::NoSpaceLeft => e Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id, project_id: project_id) - IssueRebalancingWorker.perform_async(nil, project_id.presence || issue.project_id) + IssueRebalancingWorker.perform_async(nil, *root_namespace_id_to_rebalance(issue, project_id)) end def find_issue(issue_id, project_id) @@ -53,4 +53,11 @@ class IssuePlacementWorker project.issues.take end # rubocop: enable CodeReuse/ActiveRecord + + private + + def root_namespace_id_to_rebalance(issue, project_id) + project_id = project_id.presence || issue.project_id + Project.find(project_id)&.self_or_root_group_ids + end end diff --git a/app/workers/issue_rebalancing_worker.rb b/app/workers/issue_rebalancing_worker.rb index 9eac451f107..66ef7dd3152 100644 --- a/app/workers/issue_rebalancing_worker.rb +++ b/app/workers/issue_rebalancing_worker.rb @@ -9,21 +9,44 @@ class IssueRebalancingWorker urgency :low feature_category :issue_tracking tags :exclude_from_kubernetes + deduplicate :until_executed, including_scheduled: true - def perform(ignore = nil, project_id = nil) - return if project_id.nil? + def perform(ignore = nil, project_id = nil, root_namespace_id = nil) + # we need to have exactly one of the project_id and root_namespace_id params be non-nil + raise ArgumentError, "Expected only one of the params project_id: #{project_id} and root_namespace_id: #{root_namespace_id}" if project_id && root_namespace_id + return if project_id.nil? && root_namespace_id.nil? - project = Project.find(project_id) + # pull the projects collection to be rebalanced either the project if namespace is not a group(i.e. user namesapce) + # or the root namespace, this also makes the worker backward compatible with previous version where a project_id was + # passed as the param + projects_to_rebalance = projects_collection(project_id, root_namespace_id) - # Temporary disable reabalancing for performance reasons + # something might have happened with the namespace between scheduling the worker and actually running it, + # maybe it was removed. + if projects_to_rebalance.blank? + Gitlab::ErrorTracking.log_exception( + ArgumentError.new("Projects to be rebalanced not found for arguments: project_id #{project_id}, root_namespace_id: #{root_namespace_id}"), + { project_id: project_id, root_namespace_id: root_namespace_id }) + + return + end + + # Temporary disable rebalancing for performance reasons # For more information check https://gitlab.com/gitlab-com/gl-infra/production/-/issues/4321 - return if project.root_namespace&.issue_repositioning_disabled? + return if projects_to_rebalance.take&.root_namespace&.issue_repositioning_disabled? # rubocop:disable CodeReuse/ActiveRecord - # All issues are equivalent as far as we are concerned - issue = project.issues.take # rubocop: disable CodeReuse/ActiveRecord + IssueRebalancingService.new(projects_to_rebalance).execute + rescue IssueRebalancingService::TooManyIssues => e + Gitlab::ErrorTracking.log_exception(e, root_namespace_id: root_namespace_id, project_id: project_id) + end - IssueRebalancingService.new(issue).execute - rescue ActiveRecord::RecordNotFound, IssueRebalancingService::TooManyIssues => e - Gitlab::ErrorTracking.log_exception(e, project_id: project_id) + private + + def projects_collection(project_id, root_namespace_id) + # we can have either project_id(older version) or project_id if project is part of a user namespace and not a group + # or root_namespace_id(newer version) never both. + return Project.id_in([project_id]) if project_id + + Namespace.find_by_id(root_namespace_id)&.all_projects end end diff --git a/bin/pkgr_before_precompile.sh b/bin/pkgr_before_precompile.sh deleted file mode 100755 index 54ff32c711b..00000000000 --- a/bin/pkgr_before_precompile.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -set -e - -for file in config/*.yml.example; do - cp ${file} config/$(basename ${file} .example) -done - -# Allow to override the GitLab URL from an environment variable, as this will avoid having to change the configuration file for simple deployments. -config=$(echo '<% gitlab_url = URI(ENV["GITLAB_URL"] || "http://localhost:80") %>' | cat - config/gitlab.yml) -echo "$config" > config/gitlab.yml -sed -i "s/host: localhost/host: <%= gitlab_url.host %>/" config/gitlab.yml -sed -i "s/port: 80/port: <%= gitlab_url.port %>/" config/gitlab.yml -sed -i "s/https: false/https: <%= gitlab_url.scheme == 'https' %>/" config/gitlab.yml - -# No need for config file. Will be taken care of by REDIS_URL env variable -rm config/resque.yml - -# Set default unicorn.rb file -echo "" > config/unicorn.rb diff --git a/changelogs/unreleased/273315-fy21q4-foundations-kr2-audit-and-update-buttons-on-profilescontrol.yml b/changelogs/unreleased/273315-fy21q4-foundations-kr2-audit-and-update-buttons-on-profilescontrol.yml new file mode 100644 index 00000000000..0e24d15ce04 --- /dev/null +++ b/changelogs/unreleased/273315-fy21q4-foundations-kr2-audit-and-update-buttons-on-profilescontrol.yml @@ -0,0 +1,6 @@ +--- +title: Update button variants and alignment to align with the Pajamas Design System + and modify the avatar layout to have better flow. +merge_request: 61504 +author: +type: other diff --git a/changelogs/unreleased/292833-track-rebase-and-resolve-conflicts-metrics-for-a-merge-request-2.yml b/changelogs/unreleased/292833-track-rebase-and-resolve-conflicts-metrics-for-a-merge-request-2.yml new file mode 100644 index 00000000000..43de5751286 --- /dev/null +++ b/changelogs/unreleased/292833-track-rebase-and-resolve-conflicts-metrics-for-a-merge-request-2.yml @@ -0,0 +1,5 @@ +--- +title: Track usage of the resolve conflict UI +merge_request: 61654 +author: +type: other diff --git a/changelogs/unreleased/329895-fix-needs-is-undefined-in-pipeline-graph.yml b/changelogs/unreleased/329895-fix-needs-is-undefined-in-pipeline-graph.yml new file mode 100644 index 00000000000..4d9c105f3ed --- /dev/null +++ b/changelogs/unreleased/329895-fix-needs-is-undefined-in-pipeline-graph.yml @@ -0,0 +1,5 @@ +--- +title: Fix pipeline graph undefined needs error +merge_request: 62027 +author: +type: fixed diff --git a/changelogs/unreleased/dz-drop-plugins-dir.yml b/changelogs/unreleased/dz-drop-plugins-dir.yml new file mode 100644 index 00000000000..685c1610097 --- /dev/null +++ b/changelogs/unreleased/dz-drop-plugins-dir.yml @@ -0,0 +1,5 @@ +--- +title: Drop plugins directory support +merge_request: 55168 +author: +type: removed diff --git a/changelogs/unreleased/issue-324766-kroki-filter-all-markup.yml b/changelogs/unreleased/issue-324766-kroki-filter-all-markup.yml new file mode 100644 index 00000000000..01e87a4a1e2 --- /dev/null +++ b/changelogs/unreleased/issue-324766-kroki-filter-all-markup.yml @@ -0,0 +1,5 @@ +--- +title: Enable Kroki on reStructuredText and Textile documents +merge_request: 60780 +author: Guillaume Grossetie +type: added diff --git a/config/feature_flags/development/create_vulnerability_jira_issue_via_graphql.yml b/config/feature_flags/development/create_vulnerability_jira_issue_via_graphql.yml new file mode 100644 index 00000000000..9aa5807789d --- /dev/null +++ b/config/feature_flags/development/create_vulnerability_jira_issue_via_graphql.yml @@ -0,0 +1,8 @@ +--- +name: create_vulnerability_jira_issue_via_graphql +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60593 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329780 +milestone: '13.12' +type: development +group: group::protect +default_enabled: false diff --git a/config/metrics/aggregates/code_review.yml b/config/metrics/aggregates/code_review.yml index e1f30777612..31037e116a7 100644 --- a/config/metrics/aggregates/code_review.yml +++ b/config/metrics/aggregates/code_review.yml @@ -63,6 +63,8 @@ - 'i_code_review_diff_hide_whitespace' - 'i_code_review_diff_single_file' - 'i_code_review_diff_multiple_files' + - 'i_code_review_user_load_conflict_ui' + - 'i_code_review_user_resolve_conflict' - name: code_review_category_monthly_active_users operator: OR feature_flag: usage_data_code_review_aggregation @@ -118,6 +120,8 @@ - 'i_code_review_diff_hide_whitespace' - 'i_code_review_diff_single_file' - 'i_code_review_diff_multiple_files' + - 'i_code_review_user_load_conflict_ui' + - 'i_code_review_user_resolve_conflict' - name: code_review_extension_category_monthly_active_users operator: OR feature_flag: usage_data_code_review_aggregation diff --git a/config/metrics/counts_28d/20210514013545_i_code_review_user_resolve_conflict_monthly.yml b/config/metrics/counts_28d/20210514013545_i_code_review_user_resolve_conflict_monthly.yml new file mode 100644 index 00000000000..2a39317f76c --- /dev/null +++ b/config/metrics/counts_28d/20210514013545_i_code_review_user_resolve_conflict_monthly.yml @@ -0,0 +1,21 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_user_resolve_conflict_monthly +name: resolve_conflict +description: Count of unique users per week who attempt to resolve a conflict through the ui +product_section: +product_stage: create +product_group: group::code review +product_category: code_review +value_type: number +status: implemented +milestone: "13.12" +time_frame: 28d +data_source: redis_hll +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654 +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_28d/20210514013549_i_code_review_user_load_conflict_ui_monthly.yml b/config/metrics/counts_28d/20210514013549_i_code_review_user_load_conflict_ui_monthly.yml new file mode 100644 index 00000000000..f031fb33c88 --- /dev/null +++ b/config/metrics/counts_28d/20210514013549_i_code_review_user_load_conflict_ui_monthly.yml @@ -0,0 +1,21 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_monthly +name: load_conflict_ui +description: Count of unique users per week who load the conflict resolution page +product_section: +product_stage: create +product_group: group::code review +product_category: code_review +value_type: number +status: implemented +milestone: "13.12" +time_frame: 28d +data_source: redis_hll +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654 +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20210514013544_i_code_review_user_load_conflict_ui_weekly.yml b/config/metrics/counts_7d/20210514013544_i_code_review_user_load_conflict_ui_weekly.yml new file mode 100644 index 00000000000..0a2410b9ed5 --- /dev/null +++ b/config/metrics/counts_7d/20210514013544_i_code_review_user_load_conflict_ui_weekly.yml @@ -0,0 +1,21 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_weekly +name: load_conflict_ui +description: Count of unique users per week who load the conflict resolution page +product_section: +product_stage: create +product_group: group::code review +product_category: code_review +value_type: number +status: implemented +milestone: "13.12" +time_frame: 7d +data_source: redis_hll +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654 +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_7d/20210514013545_i_code_review_user_resolve_conflict_weekly.yml b/config/metrics/counts_7d/20210514013545_i_code_review_user_resolve_conflict_weekly.yml new file mode 100644 index 00000000000..4ea6c847c49 --- /dev/null +++ b/config/metrics/counts_7d/20210514013545_i_code_review_user_resolve_conflict_weekly.yml @@ -0,0 +1,21 @@ +--- +key_path: redis_hll_counters.code_review.i_code_review_user_resolve_conflict_weekly +name: resolve_conflict +description: Count of unique users per week who attempt to resolve a conflict through the ui +product_section: +product_stage: create +product_group: group::code review +product_category: code_review +value_type: number +status: implemented +milestone: "13.12" +time_frame: 28d +data_source: redis_hll +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654 +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/doc/administration/geo/replication/version_specific_updates.md b/doc/administration/geo/replication/version_specific_updates.md index 4a101c52325..20e32dae00f 100644 --- a/doc/administration/geo/replication/version_specific_updates.md +++ b/doc/administration/geo/replication/version_specific_updates.md @@ -11,6 +11,10 @@ Review this page for update instructions for your version. These steps accompany the [general steps](updating_the_geo_nodes.md#general-update-steps) for updating Geo nodes. +## Updating to GitLab 13.11 + +We found an [issue with Git clone/pull through HTTP(s)](https://gitlab.com/gitlab-org/gitlab/-/issues/330787) on Geo secondaries and on any GitLab instance if maintenance mode is enabled. This was caused by a regression in GitLab Workhorse. This is fixed in the [GitLab 13.11.4 patch release](https://about.gitlab.com/releases/2021/05/14/gitlab-13-11-4-released/). To avoid this issue, upgrade to GitLab 13.11.4 or later. + ## Updating to GitLab 13.9 We've detected an issue [with a column rename](https://gitlab.com/gitlab-org/gitlab/-/issues/324160) diff --git a/doc/administration/index.md b/doc/administration/index.md index 1bc2084a23a..557d4b73f20 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -54,7 +54,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Environment variables](environment_variables.md): Supported environment variables that can be used to override their default values to configure GitLab. -- [Plugins](file_hooks.md): With custom plugins, GitLab administrators can +- [File hooks](file_hooks.md): With custom file hooks, GitLab administrators can introduce custom integrations without modifying GitLab source code. - [Enforcing Terms of Service](../user/admin_area/settings/terms.md) - [Third party offers](../user/admin_area/settings/third_party_offers.md) diff --git a/doc/administration/integration/kroki.md b/doc/administration/integration/kroki.md index 9e9ea62c44e..702e3837c89 100644 --- a/doc/administration/integration/kroki.md +++ b/doc/administration/integration/kroki.md @@ -6,10 +6,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Kroki diagrams **(FREE SELF)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241744) in GitLab 13.7. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241744) in GitLab 13.7. +> - Support for reStructuredText and Textile documents [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/324766) in GitLab 13.12. When [Kroki](https://kroki.io) integration is enabled and configured in -GitLab you can use it to create diagrams in AsciiDoc and Markdown documents. +GitLab you can use it to create diagrams in AsciiDoc, Markdown, reStructuredText, and Textile documents. ## Kroki Server @@ -85,13 +86,29 @@ your AsciiDoc or Markdown documentation using delimited blocks: .... ``` +- **reStructuredText** + + ```plaintext + .. code-block:: plantuml + + Bob->Alice : hello + Alice -> Bob : hi + ``` + +- **Textile** + + ```plaintext + bc[plantuml]. Bob->Alice : hello + Alice -> Bob : hi + ``` + The above blocks are converted to an HTML image tag with source pointing to the Kroki instance. If the Kroki server is correctly configured, this should render a nice diagram instead of the block: ![PlantUML diagram](../img/kroki_plantuml_diagram.png) -Kroki supports more than a dozen diagram libraries. Here's a few examples: +Kroki supports more than a dozen diagram libraries. Here's a few examples for AsciiDoc: **GraphViz** diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index 1ac53275ffc..2f0b46c4c4d 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -9346,6 +9346,30 @@ Status: `data_available` Tiers: `free`, `premium`, `ultimate` +### `redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_monthly` + +Count of unique users per week who load the conflict resolution page + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210514013549_i_code_review_user_load_conflict_ui_monthly.yml) + +Group: `group::code review` + +Status: `implemented` + +Tiers: `free`, `premium`, `ultimate` + +### `redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_weekly` + +Count of unique users per week who load the conflict resolution page + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210514013544_i_code_review_user_load_conflict_ui_weekly.yml) + +Group: `group::code review` + +Status: `implemented` + +Tiers: `free`, `premium`, `ultimate` + ### `redis_hll_counters.code_review.i_code_review_user_marked_as_draft_monthly` Count of unique users per month who mark a merge request as a draft @@ -9562,6 +9586,30 @@ Status: `data_available` Tiers: `free`, `premium`, `ultimate` +### `redis_hll_counters.code_review.i_code_review_user_resolve_conflict_monthly` + +Count of unique users per week who attempt to resolve a conflict through the ui + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210514013545_i_code_review_user_resolve_conflict_monthly.yml) + +Group: `group::code review` + +Status: `implemented` + +Tiers: `free`, `premium`, `ultimate` + +### `redis_hll_counters.code_review.i_code_review_user_resolve_conflict_weekly` + +Count of unique users per week who attempt to resolve a conflict through the ui + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210514013545_i_code_review_user_resolve_conflict_weekly.yml) + +Group: `group::code review` + +Status: `implemented` + +Tiers: `free`, `premium`, `ultimate` + ### `redis_hll_counters.code_review.i_code_review_user_resolve_thread_monthly` Count of unique users per month who resolve a thread in a merge request diff --git a/doc/operations/index.md b/doc/operations/index.md index 934634562fc..c2f3ce0a427 100644 --- a/doc/operations/index.md +++ b/doc/operations/index.md @@ -4,10 +4,10 @@ group: Monitor info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Project operations **(FREE)** +# Monitor application performance **(FREE)** GitLab provides a variety of tools to help operate and maintain -your applications: +your applications. ## Measure reliability and stability with metrics diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index c2d06e0a22c..341723a0abb 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -351,16 +351,17 @@ You can customize the deployment namespace in a few ways: When you customize the namespace, existing environments remain linked to their current namespaces until you [clear the cluster cache](#clearing-the-cluster-cache). -WARNING: +#### Protecting credentials + By default, anyone who can create a deployment job can access any CI/CD variable in an environment's deployment job. This includes `KUBECONFIG`, which gives access to any secret available to the associated service account in your cluster. To keep your production credentials safe, consider using [protected environments](../../../ci/environments/protected_environments.md), -combined with either +combined with *one* of the following: -- a GitLab-managed cluster and namespace per environment, -- *or*, an environment-scoped cluster per protected environment. The same cluster +- A GitLab-managed cluster and namespace per environment. +- An environment-scoped cluster per protected environment. The same cluster can be added multiple times with multiple restricted service accounts. ### Integrations diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb index c86d5f08ded..17a73f29afb 100644 --- a/lib/banzai/pipeline/markup_pipeline.rb +++ b/lib/banzai/pipeline/markup_pipeline.rb @@ -9,7 +9,8 @@ module Banzai Filter::AssetProxyFilter, Filter::ExternalLinkFilter, Filter::PlantumlFilter, - Filter::SyntaxHighlightFilter + Filter::SyntaxHighlightFilter, + Filter::KrokiFilter ] end diff --git a/lib/gitlab/file_hook.rb b/lib/gitlab/file_hook.rb index e398a3f9585..a8719761278 100644 --- a/lib/gitlab/file_hook.rb +++ b/lib/gitlab/file_hook.rb @@ -11,7 +11,7 @@ module Gitlab end def self.dir_glob - Dir.glob([Rails.root.join('file_hooks/*'), Rails.root.join('plugins/*')]) + Dir.glob(Rails.root.join('file_hooks/*')) end private_class_method :dir_glob diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml index cc89fbd5caf..038189f9835 100644 --- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml +++ b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml @@ -219,3 +219,11 @@ category: code_review aggregation: weekly feature_flag: diff_settings_usage_data +- name: i_code_review_user_load_conflict_ui + redis_slot: code_review + category: code_review + aggregation: weekly +- name: i_code_review_user_resolve_conflict + redis_slot: code_review + category: code_review + aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb index eb28a387a97..bdda064de20 100644 --- a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb +++ b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb @@ -44,6 +44,8 @@ module Gitlab MR_INCLUDING_CI_CONFIG_ACTION = 'o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile' MR_MILESTONE_CHANGED_ACTION = 'i_code_review_user_milestone_changed' MR_LABELS_CHANGED_ACTION = 'i_code_review_user_labels_changed' + MR_LOAD_CONFLICT_UI_ACTION = 'i_code_review_user_load_conflict_ui' + MR_RESOLVE_CONFLICT_ACTION = 'i_code_review_user_resolve_conflict' class << self def track_mr_diffs_action(merge_request:) @@ -201,6 +203,14 @@ module Gitlab track_unique_action_by_user(MR_LABELS_CHANGED_ACTION, user) end + def track_loading_conflict_ui_action(user:) + track_unique_action_by_user(MR_LOAD_CONFLICT_UI_ACTION, user) + end + + def track_resolve_conflict_action(user:) + track_unique_action_by_user(MR_RESOLVE_CONFLICT_ACTION, user) + end + private def track_unique_action_by_merge_request(action, merge_request) diff --git a/lib/tasks/file_hooks.rake b/lib/tasks/file_hooks.rake index a892d36b48e..5eb49808eff 100644 --- a/lib/tasks/file_hooks.rake +++ b/lib/tasks/file_hooks.rake @@ -3,14 +3,9 @@ namespace :file_hooks do desc 'Validate existing file hooks' task validate: :environment do - puts 'Validating file hooks from /file_hooks and /plugins directories' + puts 'Validating file hooks from /file_hooks directories' Gitlab::FileHook.files.each do |file| - if File.dirname(file).ends_with?('plugins') - puts 'DEPRECATED: /plugins directory is deprecated and will be removed in 14.0. ' \ - 'Please move your files into /file_hooks directory.' - end - success, message = Gitlab::FileHook.execute(file, Gitlab::DataBuilder::Push::SAMPLE_DATA) if success diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a1379ad2761..a996a273c62 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24711,9 +24711,6 @@ msgstr "" msgid "Please wait while we import the repository for you. Refresh at will." msgstr "" -msgid "Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory." -msgstr "" - msgid "Pod does not exist" msgstr "" @@ -25254,7 +25251,7 @@ msgstr "" msgid "Profiles|Profile was successfully updated" msgstr "" -msgid "Profiles|Public Avatar" +msgid "Profiles|Public avatar" msgstr "" msgid "Profiles|Public email" diff --git a/plugins/.gitignore b/plugins/.gitignore deleted file mode 100644 index e4ccdc9e2ec..00000000000 --- a/plugins/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -* -!*/ -!.gitignore -!.gitkeep -!examples/* diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb index c2cc3d10ea0..e07b7e4586a 100644 --- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -17,8 +17,31 @@ RSpec.describe Projects::MergeRequests::ConflictsController do end describe 'GET show' do + context 'when the request is html' do + before do + allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_loading_conflict_ui_action) + + get :show, + params: { + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project, + id: merge_request_with_conflicts.iid + }, + format: 'html' + end + + it 'does tracks the resolve call' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to have_received(:track_loading_conflict_ui_action).with(user: user) + end + end + context 'when the conflicts cannot be resolved in the UI' do before do + allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_loading_conflict_ui_action) + allow(Gitlab::Git::Conflict::Parser).to receive(:parse) .and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile) @@ -38,6 +61,11 @@ RSpec.describe Projects::MergeRequests::ConflictsController do it 'returns JSON with a message' do expect(json_response.keys).to contain_exactly('message', 'type') end + + it 'does not track the resolve call' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to have_received(:track_loading_conflict_ui_action).with(user: user) + end end context 'with valid conflicts' do @@ -145,20 +173,19 @@ RSpec.describe Projects::MergeRequests::ConflictsController do conflict_for_path(path) end - it 'returns a 200 status code' do - expect(response).to have_gitlab_http_status(:ok) - end - - it 'returns the file in JSON format' do + it 'returns a 200 and the file in JSON format' do content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts) .file_for_path(path, path) .content - expect(json_response).to include('old_path' => path, - 'new_path' => path, - 'blob_icon' => 'doc-text', - 'blob_path' => a_string_ending_with(path), - 'content' => content) + aggregate_failures do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to include('old_path' => path, + 'new_path' => path, + 'blob_icon' => 'doc-text', + 'blob_path' => a_string_ending_with(path), + 'content' => content) + end end end end @@ -166,6 +193,11 @@ RSpec.describe Projects::MergeRequests::ConflictsController do context 'POST resolve_conflicts' do let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } + before do + allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_resolve_conflict_action) + end + def resolve_conflicts(files) post :resolve_conflicts, params: { @@ -201,13 +233,16 @@ RSpec.describe Projects::MergeRequests::ConflictsController do resolve_conflicts(resolved_files) end - it 'creates a new commit on the branch' do - expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha) - expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message') - end + it 'handles the success case' do + aggregate_failures do + # creates a new commit on the branch + expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha) + expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message') - it 'returns an OK response' do - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to have_received(:track_resolve_conflict_action).with(user: user) + end end end @@ -232,16 +267,17 @@ RSpec.describe Projects::MergeRequests::ConflictsController do resolve_conflicts(resolved_files) end - it 'returns a 400 error' do - expect(response).to have_gitlab_http_status(:bad_request) - end + it 'handles the error case' do + aggregate_failures do + # has a message with the name of the first missing section + expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') + # does not create a new commit + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - it 'has a message with the name of the first missing section' do - expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + expect(response).to have_gitlab_http_status(:bad_request) + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to have_received(:track_resolve_conflict_action).with(user: user) + end end end @@ -262,16 +298,17 @@ RSpec.describe Projects::MergeRequests::ConflictsController do resolve_conflicts(resolved_files) end - it 'returns a 400 error' do - expect(response).to have_gitlab_http_status(:bad_request) - end + it 'handles the error case' do + aggregate_failures do + # has a message with the name of the missing file + expect(json_response['message']).to include('files/ruby/popen.rb') + # does not create a new commit + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - it 'has a message with the name of the missing file' do - expect(json_response['message']).to include('files/ruby/popen.rb') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + expect(response).to have_gitlab_http_status(:bad_request) + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to have_received(:track_resolve_conflict_action).with(user: user) + end end end @@ -300,16 +337,17 @@ RSpec.describe Projects::MergeRequests::ConflictsController do resolve_conflicts(resolved_files) end - it 'returns a 400 error' do - expect(response).to have_gitlab_http_status(:bad_request) - end + it 'handles the error case' do + aggregate_failures do + # has a message with the path of the problem file + expect(json_response['message']).to include('files/ruby/popen.rb') + # does not create a new commit + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) - it 'has a message with the path of the problem file' do - expect(json_response['message']).to include('files/ruby/popen.rb') - end - - it 'does not create a new commit' do - expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + expect(response).to have_gitlab_http_status(:bad_request) + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to have_received(:track_resolve_conflict_action).with(user: user) + end end end end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 3fed402267c..a501efd82ed 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -37,24 +37,6 @@ RSpec.describe 'Admin::Hooks' do expect(page).to have_content('foo.rb') expect(page).to have_content('bar.clj') end - - context 'deprecation warning' do - it 'shows warning for plugins directory' do - allow(Gitlab::FileHook).to receive(:files).and_return(['plugins/foo.rb']) - - visit admin_hooks_path - - expect(page).to have_content('Plugins directory is deprecated and will be removed in 14.0') - end - - it 'does not show warning for file_hooks directory' do - allow(Gitlab::FileHook).to receive(:files).and_return(['file_hooks/foo.rb']) - - visit admin_hooks_path - - expect(page).not_to have_content('Plugins directory is deprecated and will be removed in 14.0') - end - end end describe 'New Hook' do diff --git a/spec/features/profiles/user_search_settings_spec.rb b/spec/features/profiles/user_search_settings_spec.rb index 64a8556e349..0b05e6c9489 100644 --- a/spec/features/profiles/user_search_settings_spec.rb +++ b/spec/features/profiles/user_search_settings_spec.rb @@ -14,7 +14,7 @@ RSpec.describe 'User searches their settings', :js do visit profile_path end - it_behaves_like 'can search settings', 'Public Avatar', 'Main settings' + it_behaves_like 'can search settings', 'Public avatar', 'Main settings' end context 'in preferences page' do diff --git a/spec/frontend/pipelines/components/dag/mock_data.js b/spec/frontend/pipelines/components/dag/mock_data.js index e7e93804195..f27e7cf3d6b 100644 --- a/spec/frontend/pipelines/components/dag/mock_data.js +++ b/spec/frontend/pipelines/components/dag/mock_data.js @@ -398,6 +398,8 @@ export const multiNote = { }, }; +export const missingJob = 'missing_job'; + /* It is important that the base include parallel jobs as well as non-parallel jobs with spaces in the name to prevent @@ -657,4 +659,16 @@ export const mockParsedGraphQLNodes = [ ], __typename: 'CiGroup', }, + { + category: 'production', + name: 'production_e', + size: 1, + jobs: [ + { + name: 'production_e', + needs: [missingJob], + }, + ], + __typename: 'CiGroup', + }, ]; diff --git a/spec/frontend/pipelines/parsing_utils_spec.js b/spec/frontend/pipelines/parsing_utils_spec.js index 96748ae9e5c..074009ae056 100644 --- a/spec/frontend/pipelines/parsing_utils_spec.js +++ b/spec/frontend/pipelines/parsing_utils_spec.js @@ -10,7 +10,7 @@ import { getMaxNodes, } from '~/pipelines/components/parsing_utils'; -import { mockParsedGraphQLNodes } from './components/dag/mock_data'; +import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data'; import { generateResponse, mockPipelineResponse } from './graph/mock_data'; describe('DAG visualization parsing utilities', () => { @@ -24,6 +24,12 @@ describe('DAG visualization parsing utilities', () => { expect(unfilteredLinks[0]).toHaveProperty('target', 'test_a'); expect(unfilteredLinks[0]).toHaveProperty('value', 10); }); + + it('does not generate a link for non-existing jobs', () => { + const sources = unfilteredLinks.map(({ source }) => source); + + expect(sources.includes(missingJob)).toBe(false); + }); }); describe('filterByAncestors', () => { @@ -88,7 +94,7 @@ describe('DAG visualization parsing utilities', () => { These lengths are determined by the mock data. If the data changes, the numbers may also change. */ - expect(parsed.nodes).toHaveLength(21); + expect(parsed.nodes).toHaveLength(mockParsedGraphQLNodes.length); expect(cleanedNodes).toHaveLength(12); }); }); diff --git a/spec/frontend/pipelines/pipeline_graph/utils_spec.js b/spec/frontend/pipelines/pipeline_graph/utils_spec.js index 070d3bf7dac..5816bc06fe3 100644 --- a/spec/frontend/pipelines/pipeline_graph/utils_spec.js +++ b/spec/frontend/pipelines/pipeline_graph/utils_spec.js @@ -111,6 +111,28 @@ describe('utils functions', () => { }); }); + it('removes needs which are not in the data', () => { + const inexistantJobName = 'job5'; + const jobsWithNeeds = { + [jobName1]: job1, + [jobName2]: job2, + [jobName3]: job3, + [jobName4]: { + name: jobName4, + script: 'echo deploy', + stage: 'deploy', + needs: [inexistantJobName], + }, + }; + + expect(generateJobNeedsDict(jobsWithNeeds)).toEqual({ + [jobName1]: [], + [jobName2]: [], + [jobName3]: [jobName1, jobName2], + [jobName4]: [], + }); + }); + it('handles parallel jobs by adding the group name as a need', () => { const size = 3; const jobOptimize1 = 'optimize_1'; diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb index 6486a5a22ba..79622b731ab 100644 --- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb @@ -386,4 +386,20 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl let(:action) { described_class::MR_LABELS_CHANGED_ACTION } end end + + describe '.track_loading_conflict_ui_action' do + subject { described_class.track_loading_conflict_ui_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_LOAD_CONFLICT_UI_ACTION } + end + end + + describe '.track_resolve_conflict_action' do + subject { described_class.track_resolve_conflict_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_RESOLVE_CONFLICT_ACTION } + end + end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 884c476932e..e3b26b9c1e8 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1287,15 +1287,33 @@ RSpec.describe Issue do end end - let(:project) { build_stubbed(:project_empty_repo) } - let(:issue) { build_stubbed(:issue, relative_position: 100, project: project) } + shared_examples 'schedules issues rebalancing' do + let(:issue) { build_stubbed(:issue, relative_position: 100, project: project) } - it 'schedules rebalancing if we time-out when moving' do - lhs = build_stubbed(:issue, relative_position: 99, project: project) - to_move = build(:issue, project: project) - expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id) + it 'schedules rebalancing if we time-out when moving' do + lhs = build_stubbed(:issue, relative_position: 99, project: project) + to_move = build(:issue, project: project) + expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project_id, namespace_id) - expect { to_move.move_between(lhs, issue) }.to raise_error(ActiveRecord::QueryCanceled) + expect { to_move.move_between(lhs, issue) }.to raise_error(ActiveRecord::QueryCanceled) + end + end + + context 'when project in user namespace' do + let(:project) { build_stubbed(:project_empty_repo) } + let(:project_id) { project.id } + let(:namespace_id) { nil } + + it_behaves_like 'schedules issues rebalancing' + end + + context 'when project in a group namespace' do + let(:group) { create(:group) } + let(:project) { build_stubbed(:project_empty_repo, group: group) } + let(:project_id) { nil } + let(:namespace_id) { group.id } + + it_behaves_like 'schedules issues rebalancing' end end diff --git a/spec/services/issue_rebalancing_service_spec.rb b/spec/services/issue_rebalancing_service_spec.rb index 1c7f74264b7..76ccb6d89ea 100644 --- a/spec/services/issue_rebalancing_service_spec.rb +++ b/spec/services/issue_rebalancing_service_spec.rb @@ -39,7 +39,7 @@ RSpec.describe IssueRebalancingService do shared_examples 'IssueRebalancingService shared examples' do it 'rebalances a set of issues with clumps at the end and start' do all_issues = start_clump + unclumped + end_clump.reverse - service = described_class.new(project.issues.first) + service = described_class.new(Project.id_in([project.id])) expect { service.execute }.not_to change { issues_in_position_order.map(&:id) } @@ -55,7 +55,7 @@ RSpec.describe IssueRebalancingService do end it 'is idempotent' do - service = described_class.new(project.issues.first) + service = described_class.new(Project.id_in(project)) expect do service.execute @@ -70,17 +70,17 @@ RSpec.describe IssueRebalancingService do issue.project.group old_pos = issue.relative_position - service = described_class.new(issue) + service = described_class.new(Project.id_in(project)) expect { service.execute }.not_to exceed_query_limit(0) expect(old_pos).to eq(issue.reload.relative_position) end - it 'acts if the flag is enabled for the project' do + it 'acts if the flag is enabled for the root namespace' do issue = create(:issue, project: project, author: user, relative_position: max_pos) - stub_feature_flags(rebalance_issues: issue.project) + stub_feature_flags(rebalance_issues: project.root_namespace) - service = described_class.new(issue) + service = described_class.new(Project.id_in(project)) expect { service.execute }.to change { issue.reload.relative_position } end @@ -90,23 +90,22 @@ RSpec.describe IssueRebalancingService do project.update!(group: create(:group)) stub_feature_flags(rebalance_issues: issue.project.group) - service = described_class.new(issue) + service = described_class.new(Project.id_in(project)) expect { service.execute }.to change { issue.reload.relative_position } end it 'aborts if there are too many issues' do - issue = project.issues.first base = double(count: 10_001) - allow(Issue).to receive(:relative_positioning_query_base).with(issue).and_return(base) + allow(Issue).to receive(:in_projects).and_return(base) - expect { described_class.new(issue).execute }.to raise_error(described_class::TooManyIssues) + expect { described_class.new(Project.id_in(project)).execute }.to raise_error(described_class::TooManyIssues) end end shared_examples 'rebalancing is retried on statement timeout exceptions' do - subject { described_class.new(project.issues.first) } + subject { described_class.new(Project.id_in(project)) } it 'retries update statement' do call_count = 0 diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 8c97dd95ced..efddf28e41b 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -225,7 +225,7 @@ RSpec.describe Issues::UpdateService, :mailer do opts[:move_between_ids] = [issue1.id, issue2.id] - expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id) + expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id) update_issue(opts) expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) @@ -239,7 +239,7 @@ RSpec.describe Issues::UpdateService, :mailer do opts[:move_between_ids] = [issue1.id, issue2.id] - expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id) + expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id) update_issue(opts) expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) @@ -253,7 +253,7 @@ RSpec.describe Issues::UpdateService, :mailer do opts[:move_between_ids] = [issue1.id, issue2.id] - expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id) + expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id) update_issue(opts) expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index b3fd4e33640..e1954c32227 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -128,11 +128,10 @@ RSpec.describe WebHookService do end it 'handles exceptions' do - exceptions = [ - SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, - Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, - Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep + exceptions = Gitlab::HTTP::HTTP_ERRORS + [ + Gitlab::Json::LimitedEncoder::LimitExceeded, URI::InvalidURIError ] + exceptions.each do |exception_class| exception = exception_class.new('Exception message') project_hook.enable! diff --git a/spec/tooling/danger/project_helper_spec.rb b/spec/tooling/danger/project_helper_spec.rb index 1d2ea0f5ba3..e07d9a8a6f2 100644 --- a/spec/tooling/danger/project_helper_spec.rb +++ b/spec/tooling/danger/project_helper_spec.rb @@ -139,18 +139,18 @@ RSpec.describe Tooling::Danger::ProjectHelper do 'db/post_migrate/foo' | [:database, :migration] 'ee/db/geo/migrate/foo' | [:database, :migration] 'ee/db/geo/post_migrate/foo' | [:database, :migration] - 'app/models/project_authorization.rb' | [:database] - 'app/services/users/refresh_authorized_projects_service.rb' | [:database] - 'app/services/authorized_project_update/find_records_due_for_refresh_service.rb' | [:database] - 'lib/gitlab/background_migration.rb' | [:database] - 'lib/gitlab/background_migration/foo' | [:database] - 'ee/lib/gitlab/background_migration/foo' | [:database] - 'lib/gitlab/database.rb' | [:database] - 'lib/gitlab/database/foo' | [:database] - 'ee/lib/gitlab/database/foo' | [:database] - 'lib/gitlab/github_import.rb' | [:database] - 'lib/gitlab/github_import/foo' | [:database] - 'lib/gitlab/sql/foo' | [:database] + 'app/models/project_authorization.rb' | [:database, :backend] + 'app/services/users/refresh_authorized_projects_service.rb' | [:database, :backend] + 'app/services/authorized_project_update/find_records_due_for_refresh_service.rb' | [:database, :backend] + 'lib/gitlab/background_migration.rb' | [:database, :backend] + 'lib/gitlab/background_migration/foo' | [:database, :backend] + 'ee/lib/gitlab/background_migration/foo' | [:database, :backend] + 'lib/gitlab/database.rb' | [:database, :backend] + 'lib/gitlab/database/foo' | [:database, :backend] + 'ee/lib/gitlab/database/foo' | [:database, :backend] + 'lib/gitlab/github_import.rb' | [:database, :backend] + 'lib/gitlab/github_import/foo' | [:database, :backend] + 'lib/gitlab/sql/foo' | [:database, :backend] 'rubocop/cop/migration/foo' | [:database] 'db/fixtures/foo.rb' | [:backend] diff --git a/spec/workers/issue_placement_worker_spec.rb b/spec/workers/issue_placement_worker_spec.rb index e0c17bfadee..780790dbb1b 100644 --- a/spec/workers/issue_placement_worker_spec.rb +++ b/spec/workers/issue_placement_worker_spec.rb @@ -35,7 +35,7 @@ RSpec.describe IssuePlacementWorker do it 'schedules rebalancing if needed' do issue_a.update!(relative_position: RelativePositioning::MAX_POSITION) - expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id) + expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id) run_worker end @@ -101,7 +101,7 @@ RSpec.describe IssuePlacementWorker do it 'anticipates the failure to place the issues, and schedules rebalancing' do allow(Issue).to receive(:move_nulls_to_end) { raise RelativePositioning::NoSpaceLeft } - expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id) + expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id) expect(Gitlab::ErrorTracking) .to receive(:log_exception) .with(RelativePositioning::NoSpaceLeft, worker_arguments) diff --git a/spec/workers/issue_rebalancing_worker_spec.rb b/spec/workers/issue_rebalancing_worker_spec.rb index e5c6ac3f854..b6e9429d78e 100644 --- a/spec/workers/issue_rebalancing_worker_spec.rb +++ b/spec/workers/issue_rebalancing_worker_spec.rb @@ -20,34 +20,83 @@ RSpec.describe IssueRebalancingWorker do end end - it 'runs an instance of IssueRebalancingService' do - service = double(execute: nil) - expect(IssueRebalancingService).to receive(:new).with(issue).and_return(service) + shared_examples 'running the worker' do + it 'runs an instance of IssueRebalancingService' do + service = double(execute: nil) + service_param = arguments.second.present? ? kind_of(Project.id_in([project]).class) : kind_of(group&.all_projects.class) - described_class.new.perform(nil, issue.project_id) + expect(IssueRebalancingService).to receive(:new).with(service_param).and_return(service) + + described_class.new.perform(*arguments) + end + + it 'anticipates there being too many issues' do + service = double + service_param = arguments.second.present? ? kind_of(Project.id_in([project]).class) : kind_of(group&.all_projects.class) + + allow(service).to receive(:execute).and_raise(IssueRebalancingService::TooManyIssues) + expect(IssueRebalancingService).to receive(:new).with(service_param).and_return(service) + expect(Gitlab::ErrorTracking).to receive(:log_exception).with(IssueRebalancingService::TooManyIssues, include(project_id: arguments.second, root_namespace_id: arguments.third)) + + described_class.new.perform(*arguments) + end + + it 'takes no action if the value is nil' do + expect(IssueRebalancingService).not_to receive(:new) + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + described_class.new.perform # all arguments are nil + end end - it 'anticipates the inability to find the issue' do - expect(Gitlab::ErrorTracking).to receive(:log_exception).with(ActiveRecord::RecordNotFound, include(project_id: -1)) - expect(IssueRebalancingService).not_to receive(:new) + shared_examples 'safely handles non-existent ids' do + it 'anticipates the inability to find the issue' do + expect(Gitlab::ErrorTracking).to receive(:log_exception).with(ArgumentError, include(project_id: arguments.second, root_namespace_id: arguments.third)) + expect(IssueRebalancingService).not_to receive(:new) - described_class.new.perform(nil, -1) + described_class.new.perform(*arguments) + end end - it 'anticipates there being too many issues' do - service = double - allow(service).to receive(:execute) { raise IssueRebalancingService::TooManyIssues } - expect(IssueRebalancingService).to receive(:new).with(issue).and_return(service) - expect(Gitlab::ErrorTracking).to receive(:log_exception).with(IssueRebalancingService::TooManyIssues, include(project_id: issue.project_id)) + context 'without root_namespace param' do + it_behaves_like 'running the worker' do + let(:arguments) { [-1, project.id] } + end - described_class.new.perform(nil, issue.project_id) + it_behaves_like 'safely handles non-existent ids' do + let(:arguments) { [nil, -1] } + end + + include_examples 'an idempotent worker' do + let(:job_args) { [-1, project.id] } + end + + include_examples 'an idempotent worker' do + let(:job_args) { [nil, -1] } + end end - it 'takes no action if the value is nil' do - expect(IssueRebalancingService).not_to receive(:new) - expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + context 'with root_namespace param' do + it_behaves_like 'running the worker' do + let(:arguments) { [nil, nil, group.id] } + end - described_class.new.perform(nil, nil) + it_behaves_like 'safely handles non-existent ids' do + let(:arguments) { [nil, nil, -1] } + end + + include_examples 'an idempotent worker' do + let(:job_args) { [nil, nil, group.id] } + end + + include_examples 'an idempotent worker' do + let(:job_args) { [nil, nil, -1] } + end end end + + it 'has the `until_executed` deduplicate strategy' do + expect(described_class.get_deduplicate_strategy).to eq(:until_executed) + expect(described_class.get_deduplication_options).to include({ including_scheduled: true }) + end end diff --git a/tooling/danger/project_helper.rb b/tooling/danger/project_helper.rb index c6aaf82ef35..c965c02378c 100644 --- a/tooling/danger/project_helper.rb +++ b/tooling/danger/project_helper.rb @@ -76,11 +76,11 @@ module Tooling )\z}x => %i[frontend engineering_productivity], %r{\A(ee/)?db/(geo/)?(migrate|post_migrate)/} => [:database, :migration], - %r{\A(ee/)?db/(?!fixtures)[^/]+} => :database, - %r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database, - %r{\A(app/services/authorized_project_update/find_records_due_for_refresh_service)(/|\.rb)} => :database, - %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database, - %r{\A(ee/)?app/finders/} => :database, + %r{\A(ee/)?db/(?!fixtures)[^/]+} => [:database], + %r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => [:database, :backend], + %r{\A(app/services/authorized_project_update/find_records_due_for_refresh_service)(/|\.rb)} => [:database, :backend], + %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => [:database, :backend], + %r{\A(ee/)?app/finders/} => [:database, :backend], %r{\Arubocop/cop/migration(/|\.rb)} => :database, %r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,