From d7774ee304809f81347ef814328a9d620fb5d1a5 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 1 Feb 2021 18:09:17 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/rules.gitlab-ci.yml | 1 + .../jobs/components/erased_block.vue | 20 ++-- app/assets/javascripts/lib/graphql.js | 17 +++- .../javascripts/lib/utils/datetime_utility.js | 36 +++++++ app/helpers/avatars_helper.rb | 9 +- .../ci/codequality_mr_diff_entity.rb | 7 ++ .../codequality_mr_diff_report_serializer.rb | 7 ++ .../create_quality_report_service.rb | 8 +- app/views/layouts/_page.html.haml | 2 +- app/views/layouts/nav/_breadcrumbs.html.haml | 2 +- .../layouts/nav/sidebar/_admin.html.haml | 2 +- .../layouts/nav/sidebar/_group.html.haml | 14 +-- .../layouts/nav/sidebar/_profile.html.haml | 2 +- .../layouts/nav/sidebar/_project.html.haml | 2 +- .../boards/components/_sidebar.html.haml | 2 +- .../issuable/_bulk_update_sidebar.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- .../shared/milestones/_sidebar.html.haml | 2 +- app/views/shared/wikis/_sidebar.html.haml | 2 +- .../239031-yo-remove-avatar-blocked-user.yml | 5 - .../cngo-add-landmarks-and-provide-names.yml | 5 + ...ate-tz-sensitivity-ci-analytics-charts.yml | 5 + .../pb-use-gl-alert-erased-block.yml | 5 + .../development/import_requirements_csv.yml | 8 -- doc/administration/auditor_users.md | 2 + doc/api/geo_nodes.md | 60 ++++++------ .../documentation/styleguide/index.md | 2 +- doc/development/documentation/testing.md | 54 +++++----- doc/integration/saml.md | 14 +-- doc/user/infrastructure/index.md | 11 +++ doc/user/permissions.md | 3 + .../reviewing_and_managing_merge_requests.md | 2 +- doc/user/project/protected_branches.md | 10 +- .../metric_definition.yml | 14 +++ .../usage_metric_definition_generator.rb | 78 +++++++++++++++ .../user_mentions/models/namespace.rb | 1 + lib/gitlab/ci/charts.rb | 8 +- lib/gitlab/ci/reports/codequality_mr_diff.rb | 39 ++++++++ lib/rouge/formatters/html_gitlab.rb | 4 +- locale/gitlab.pot | 34 ++++++- .../ci/reports/codequality_degradations.rb | 98 +++++++++++++++++++ spec/features/admin/admin_groups_spec.rb | 2 +- .../entities/codequality_mr_diff_report.json | 21 ++++ .../jobs/components/erased_block_spec.js | 4 +- .../lib/utils/datetime_utility_spec.js | 69 +++++++++++-- spec/helpers/avatars_helper_spec.rb | 9 -- spec/lib/gitlab/ci/charts_spec.rb | 82 ++++++++++++++++ .../ci/reports/codequality_mr_diff_spec.rb | 58 +++++++++++ spec/lib/gitlab/import_export/all_models.yml | 2 +- .../ci/codequality_mr_diff_entity_spec.rb | 27 +++++ ...equality_mr_diff_report_serializer_spec.rb | 32 ++++++ spec/support/helpers/wait_for_requests.rb | 2 +- 52 files changed, 769 insertions(+), 140 deletions(-) create mode 100644 app/serializers/ci/codequality_mr_diff_entity.rb create mode 100644 app/serializers/ci/codequality_mr_diff_report_serializer.rb delete mode 100644 changelogs/unreleased/239031-yo-remove-avatar-blocked-user.yml create mode 100644 changelogs/unreleased/cngo-add-landmarks-and-provide-names.yml create mode 100644 changelogs/unreleased/eliminate-tz-sensitivity-ci-analytics-charts.yml create mode 100644 changelogs/unreleased/pb-use-gl-alert-erased-block.yml delete mode 100644 config/feature_flags/development/import_requirements_csv.yml create mode 100644 generator_templates/usage_metric_definition/metric_definition.yml create mode 100644 lib/generators/gitlab/usage_metric_definition_generator.rb create mode 100644 lib/gitlab/ci/reports/codequality_mr_diff.rb create mode 100644 spec/factories/ci/reports/codequality_degradations.rb create mode 100644 spec/fixtures/api/schemas/entities/codequality_mr_diff_report.json create mode 100644 spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb create mode 100644 spec/serializers/ci/codequality_mr_diff_entity_spec.rb create mode 100644 spec/serializers/ci/codequality_mr_diff_report_serializer_spec.rb diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index f9382637610..875a4557d42 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -123,6 +123,7 @@ - ".gitlab/route-map.yml" - "doc/**/*" - ".markdownlint.json" + - "scripts/lint-doc.sh" .frontend-dependency-patterns: &frontend-dependency-patterns - "{package.json,yarn.lock}" diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue index a6d1b41c275..91c36f38447 100644 --- a/app/assets/javascripts/jobs/components/erased_block.vue +++ b/app/assets/javascripts/jobs/components/erased_block.vue @@ -1,12 +1,14 @@ diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index 5c4bb5ea01f..d974e36f6f0 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -35,6 +35,16 @@ export default (resolvers = {}, config = {}) => { batchMax: config.batchMax || 10, }; + const requestCounterLink = new ApolloLink((operation, forward) => { + window.pendingApolloRequests = window.pendingApolloRequests || 0; + window.pendingApolloRequests += 1; + + return forward(operation).map((response) => { + window.pendingApolloRequests -= 1; + return response; + }); + }); + const uploadsLink = ApolloLink.split( (operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest, createUploadLink(httpOptions), @@ -63,7 +73,12 @@ export default (resolvers = {}, config = {}) => { return new ApolloClient({ typeDefs: config.typeDefs, - link: ApolloLink.from([performanceBarLink, new StartupJSLink(), uploadsLink]), + link: ApolloLink.from([ + requestCounterLink, + performanceBarLink, + new StartupJSLink(), + uploadsLink, + ]), cache: new InMemoryCache({ ...config.cacheConfig, freezeResults: config.assumeImmutableResults, diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index d56281d057b..d290c747ad6 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -6,6 +6,7 @@ import { languageCode, s__, __, n__ } from '../../locale'; const MILLISECONDS_IN_HOUR = 60 * 60 * 1000; const MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR; +const DAYS_IN_WEEK = 7; window.timeago = timeago; @@ -693,6 +694,25 @@ export const nDaysAfter = (date, numberOfDays) => */ export const nDaysBefore = (date, numberOfDays) => nDaysAfter(date, -numberOfDays); +/** + * Returns the date n weeks after the date provided + * + * @param {Date} date the initial date + * @param {Number} numberOfWeeks number of weeks after + * @return {Date} the date following the date provided + */ +export const nWeeksAfter = (date, numberOfWeeks) => + new Date(newDate(date)).setDate(date.getDate() + DAYS_IN_WEEK * numberOfWeeks); + +/** + * Returns the date n weeks before the date provided + * + * @param {Date} date the initial date + * @param {Number} numberOfWeeks number of weeks before + * @return {Date} the date following the date provided + */ +export const nWeeksBefore = (date, numberOfWeeks) => nWeeksAfter(date, -numberOfWeeks); + /** * Returns the date n months after the date provided * @@ -897,3 +917,19 @@ export const getOverlapDateInPeriods = (givenPeriodLeft = {}, givenPeriodRight = overlapEndDate, }; }; + +/** + * A utility function that checks that the date is today + * + * @param {Date} date + * + * @return {Boolean} true if provided date is today + */ +export const isToday = (date) => { + const today = new Date(); + return ( + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() + ); +}; diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index eae65f04cbf..5457f96d506 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -31,10 +31,11 @@ module AvatarsHelper end def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true) - return gravatar_icon(nil, size, scale) unless user - return default_avatar if user.blocked? - - user.avatar_url(size: size, only_path: only_path) || default_avatar + if user + user.avatar_url(size: size, only_path: only_path) || default_avatar + else + gravatar_icon(nil, size, scale) + end end def gravatar_icon(user_email = '', size = nil, scale = 2) diff --git a/app/serializers/ci/codequality_mr_diff_entity.rb b/app/serializers/ci/codequality_mr_diff_entity.rb new file mode 100644 index 00000000000..99e7cc54017 --- /dev/null +++ b/app/serializers/ci/codequality_mr_diff_entity.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Ci + class CodequalityMrDiffEntity < Grape::Entity + expose :files + end +end diff --git a/app/serializers/ci/codequality_mr_diff_report_serializer.rb b/app/serializers/ci/codequality_mr_diff_report_serializer.rb new file mode 100644 index 00000000000..e9b51930b99 --- /dev/null +++ b/app/serializers/ci/codequality_mr_diff_report_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Ci + class CodequalityMrDiffReportSerializer < BaseSerializer + entity CodequalityMrDiffEntity + end +end diff --git a/app/services/ci/pipeline_artifacts/create_quality_report_service.rb b/app/services/ci/pipeline_artifacts/create_quality_report_service.rb index 6bc17ff69c0..c24eb5f8d40 100644 --- a/app/services/ci/pipeline_artifacts/create_quality_report_service.rb +++ b/app/services/ci/pipeline_artifacts/create_quality_report_service.rb @@ -22,11 +22,17 @@ module Ci def build_carrierwave_file(pipeline) CarrierWaveStringFile.new_file( - file_content: pipeline.codequality_reports.to_json, + file_content: build_quality_mr_diff_report(pipeline), filename: Ci::PipelineArtifact::DEFAULT_FILE_NAMES.fetch(:code_quality), content_type: 'application/json' ) end + + def build_quality_mr_diff_report(pipeline) + mr_diff_report = Gitlab::Ci::Reports::CodequalityMrDiff.new(pipeline.codequality_reports) + + Ci::CodequalityMrDiffReportSerializer.new.represent(mr_diff_report).to_json # rubocop: disable CodeReuse/Serializer + end end end end diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index c552454caa7..b28bd3295a0 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -22,6 +22,6 @@ - unless @hide_breadcrumbs = render "layouts/nav/breadcrumbs" %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } - .content{ id: "content-body", **page_itemtype } + %main.content{ id: "content-body", **page_itemtype } = render "layouts/flash", extra_flash_class: 'limit-container-width' = yield diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index dd2c5e2a19e..aeeffb6f4b6 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -3,7 +3,7 @@ - unless @skip_current_level_breadcrumb - push_to_schema_breadcrumb(@breadcrumb_title, breadcrumb_title_link) -%nav.breadcrumbs{ role: "navigation", class: [container, @content_class] } +%nav.breadcrumbs{ class: [container, @content_class], 'aria-label': _('Breadcrumbs') } .breadcrumbs-container{ class: ("border-bottom-0" if @no_breadcrumb_border) } - if defined?(@left_sidebar) = button_tag class: 'toggle-mobile-nav', type: 'button' do diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index ca2d3828c14..3d4a00b01a2 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar.qa-admin-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } +%aside.nav-sidebar.qa-admin-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), 'aria-label': _('Admin navigation') } .nav-sidebar-inner-scroll .context-header = link_to admin_root_path, title: _('Admin Overview') do diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 52d3c8d133a..8401111c86c 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -1,7 +1,9 @@ - issues_count = group_open_issues_count(@group) - merge_requests_count = group_merge_requests_count(state: 'opened') +- aside_title = @group.subgroup? ? _('Subgroup navigation') : _('Group navigation') +- overview_title = @group.subgroup? ? _('Subgroup overview') : _('Group overview') -.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('groups_side_navigation', 'render', 'groups_side_navigation') } +%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('groups_side_navigation', 'render', 'groups_side_navigation'), 'aria-label': aside_title } .nav-sidebar-inner-scroll .context-header = link_to group_path(@group), title: @group.name do @@ -19,19 +21,13 @@ .nav-icon-container = sprite_icon('home') %span.nav-item-name - - if @group.subgroup? - = _('Subgroup overview') - - else - = _('Group overview') + = overview_title %ul.sidebar-sub-level-items = nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do = link_to group_path(@group) do %strong.fly-out-top-item-name - - if @group.subgroup? - = _('Subgroup overview') - - else - = _('Group overview') + = overview_title %li.divider.fly-out-top-item = nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml index dadab554c02..a66110f28e8 100644 --- a/app/views/layouts/nav/sidebar/_profile.html.haml +++ b/app/views/layouts/nav/sidebar/_profile.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('user_side_navigation', 'render', 'user_side_navigation') } +%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('user_side_navigation', 'render', 'user_side_navigation'), 'aria-label': _('User settings') } .nav-sidebar-inner-scroll .context-header = link_to profile_path, title: _('Profile Settings') do diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index d516484c4b8..20d9cbcb7e9 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -1,4 +1,4 @@ -.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('projects_side_navigation', 'render', 'projects_side_navigation') } +%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('projects_side_navigation', 'render', 'projects_side_navigation'), 'aria-label': _('Project navigation') } .nav-sidebar-inner-scroll .context-header = link_to project_path(@project), title: @project.name do diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml index b4f75967a67..3daa13fb488 100644 --- a/app/views/shared/boards/components/_sidebar.html.haml +++ b/app/views/shared/boards/components/_sidebar.html.haml @@ -1,6 +1,6 @@ %board-sidebar{ "inline-template" => true, ":current-user" => (UserSerializer.new.represent(current_user) || {}).to_json } %transition{ name: "boards-sidebar-slide" } - %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" } + %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar", 'aria-label': s_('Boards|Board') } .issuable-sidebar .block.issuable-sidebar-header.position-relative %span.issuable-header-text.hide-collapsed.float-left diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml index b83724f450a..214651c276e 100644 --- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml +++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml @@ -3,7 +3,7 @@ - epic_bulk_edit_flag = @project&.group&.feature_available?(:epics) && type == :issues - bulk_iterations_flag = @project&.group&.feature_available?(:iterations) && type == :issues -%aside.issues-bulk-update.js-right-sidebar.right-sidebar{ "aria-live" => "polite", data: { 'signed-in': current_user.present? } } +%aside.issues-bulk-update.js-right-sidebar.right-sidebar{ "aria-live" => "polite", data: { 'signed-in': current_user.present? }, 'aria-label': _('Bulk update') } .issuable-sidebar.hidden = form_tag [:bulk_update, @project, type], method: :post, class: "bulk-update" do .block.issuable-sidebar-header diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 911bef482dd..a1150fbfe1b 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -8,7 +8,7 @@ - add_page_startup_api_call "#{issuable_sidebar[:issuable_json_path]}?serializer=sidebar_extras" - reviewers = local_assigns.fetch(:reviewers, nil) -%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in }, issuable_type: issuable_type }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } +%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in }, issuable_type: issuable_type }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite', 'aria-label': issuable_type } .issuable-sidebar .block.issuable-sidebar-header - if signed_in diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index d9d7d18c732..661ace8feaa 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -1,7 +1,7 @@ - affix_offset = local_assigns.fetch(:affix_offset, "50") - project = local_assigns[:project] -%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix", "always-show-toggle" => true }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } +%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix", "always-show-toggle" => true }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite', 'aria-label': _('Milestone') } .issuable-sidebar.milestone-sidebar .block.milestone-progress.issuable-sidebar-header %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => s_('MilestoneSidebar|Toggle sidebar'), title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } diff --git a/app/views/shared/wikis/_sidebar.html.haml b/app/views/shared/wikis/_sidebar.html.haml index 4e9fdc8b95a..5f181371663 100644 --- a/app/views/shared/wikis/_sidebar.html.haml +++ b/app/views/shared/wikis/_sidebar.html.haml @@ -1,6 +1,6 @@ - editing ||= false -%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } } +%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" }, 'aria-label': _('Wiki') } .sidebar-container .block.wiki-sidebar-header.gl-mb-3.w-100 %a.gutter-toggle.float-right.d-block.d-md-none.js-sidebar-wiki-toggle{ href: "#" } diff --git a/changelogs/unreleased/239031-yo-remove-avatar-blocked-user.yml b/changelogs/unreleased/239031-yo-remove-avatar-blocked-user.yml deleted file mode 100644 index 888e3bd28c3..00000000000 --- a/changelogs/unreleased/239031-yo-remove-avatar-blocked-user.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove avatar of the blocked user -merge_request: 52051 -author: Yogi (@yo) -type: fixed diff --git a/changelogs/unreleased/cngo-add-landmarks-and-provide-names.yml b/changelogs/unreleased/cngo-add-landmarks-and-provide-names.yml new file mode 100644 index 00000000000..7c734797ecd --- /dev/null +++ b/changelogs/unreleased/cngo-add-landmarks-and-provide-names.yml @@ -0,0 +1,5 @@ +--- +title: Add site landmarks for screen readers +merge_request: 52514 +author: +type: added diff --git a/changelogs/unreleased/eliminate-tz-sensitivity-ci-analytics-charts.yml b/changelogs/unreleased/eliminate-tz-sensitivity-ci-analytics-charts.yml new file mode 100644 index 00000000000..c6b2df789e7 --- /dev/null +++ b/changelogs/unreleased/eliminate-tz-sensitivity-ci-analytics-charts.yml @@ -0,0 +1,5 @@ +--- +title: Fix empty pipeline analytics charts when time_zone is non-UTC +merge_request: 52971 +author: +type: fixed diff --git a/changelogs/unreleased/pb-use-gl-alert-erased-block.yml b/changelogs/unreleased/pb-use-gl-alert-erased-block.yml new file mode 100644 index 00000000000..4f40e54f5ba --- /dev/null +++ b/changelogs/unreleased/pb-use-gl-alert-erased-block.yml @@ -0,0 +1,5 @@ +--- +title: Replace erase job alert background color with color consistent with UI +merge_request: 52810 +author: +type: changed diff --git a/config/feature_flags/development/import_requirements_csv.yml b/config/feature_flags/development/import_requirements_csv.yml deleted file mode 100644 index 736d2204b44..00000000000 --- a/config/feature_flags/development/import_requirements_csv.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: import_requirements_csv -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48060 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284846 -milestone: '13.7' -type: development -group: group::product planning -default_enabled: true diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md index d4b0fd69b05..b04ceebf645 100644 --- a/doc/administration/auditor_users.md +++ b/doc/administration/auditor_users.md @@ -68,6 +68,8 @@ To create a new Auditor user: To revoke Auditor permissions from a user, make them a regular user by following the previous steps. +Additionally users can be set as an Auditor using [SAML groups](../integration/saml.md#auditor-groups). + ## Permissions and restrictions of an Auditor user An Auditor user should be able to access all projects and groups of a GitLab diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md index f9c00347e4f..386472a23f6 100644 --- a/doc/api/geo_nodes.md +++ b/doc/api/geo_nodes.md @@ -307,37 +307,37 @@ Example response: "health_status": "Healthy", "missing_oauth_application": false, "attachments_count": 1, - "attachments_synced_count": nil, - "attachments_failed_count": nil, + "attachments_synced_count": null, + "attachments_failed_count": null, "attachments_synced_missing_on_primary_count": 0, "attachments_synced_in_percentage": "0.00%", - "db_replication_lag_seconds": nil, + "db_replication_lag_seconds": null, "lfs_objects_count": 0, - "lfs_objects_synced_count": nil, - "lfs_objects_failed_count": nil, + "lfs_objects_synced_count": null, + "lfs_objects_failed_count": null, "lfs_objects_synced_missing_on_primary_count": 0, "lfs_objects_synced_in_percentage": "0.00%", "job_artifacts_count": 2, - "job_artifacts_synced_count": nil, - "job_artifacts_failed_count": nil, + "job_artifacts_synced_count": null, + "job_artifacts_failed_count": null, "job_artifacts_synced_missing_on_primary_count": 0, "job_artifacts_synced_in_percentage": "0.00%", "container_repositories_count": 3, - "container_repositories_synced_count": nil, - "container_repositories_failed_count": nil, + "container_repositories_synced_count": null, + "container_repositories_failed_count": null, "container_repositories_synced_in_percentage": "0.00%", "design_repositories_count": 3, - "design_repositories_synced_count": nil, - "design_repositories_failed_count": nil, + "design_repositories_synced_count": null, + "design_repositories_failed_count": null, "design_repositories_synced_in_percentage": "0.00%", "projects_count": 41, "repositories_count": 41, - "repositories_failed_count": nil, - "repositories_synced_count": nil, + "repositories_failed_count": null, + "repositories_synced_count": null, "repositories_synced_in_percentage": "0.00%", "wikis_count": 41, - "wikis_failed_count": nil, - "wikis_synced_count": nil, + "wikis_failed_count": null, + "wikis_synced_count": null, "wikis_synced_in_percentage": "0.00%", "replication_slots_count": 1, "replication_slots_used_count": 1, @@ -367,7 +367,7 @@ Example response: "repositories_checked_in_percentage": "17.07%", "last_event_id": 23, "last_event_timestamp": 1509681166, - "cursor_last_event_id": nil, + "cursor_last_event_id": null, "cursor_last_event_timestamp": 0, "last_successful_status_check_timestamp": 1510125024, "version": "10.3.0", @@ -408,12 +408,12 @@ Example response: "job_artifacts_synced_missing_on_primary_count": 0, "job_artifacts_synced_in_percentage": "50.00%", "container_repositories_count": 3, - "container_repositories_synced_count": nil, - "container_repositories_failed_count": nil, + "container_repositories_synced_count": null, + "container_repositories_failed_count": null, "container_repositories_synced_in_percentage": "0.00%", "design_repositories_count": 3, - "design_repositories_synced_count": nil, - "design_repositories_failed_count": nil, + "design_repositories_synced_count": null, + "design_repositories_failed_count": null, "design_repositories_synced_in_percentage": "0.00%", "projects_count": 41, "repositories_count": 41, @@ -424,10 +424,10 @@ Example response: "wikis_failed_count": 0, "wikis_synced_count": 41, "wikis_synced_in_percentage": "100.00%", - "replication_slots_count": nil, - "replication_slots_used_count": nil, + "replication_slots_count": null, + "replication_slots_used_count": null, "replication_slots_used_in_percentage": "0.00%", - "replication_slots_max_retained_wal_bytes": nil, + "replication_slots_max_retained_wal_bytes": null, "repositories_checksummed_count": 20, "repositories_checksum_failed_count": 5, "repositories_checksummed_in_percentage": "48.78%", @@ -518,12 +518,12 @@ Example response: "job_artifacts_synced_missing_on_primary_count": 0, "job_artifacts_synced_in_percentage": "50.00%", "container_repositories_count": 3, - "container_repositories_synced_count": nil, - "container_repositories_failed_count": nil, + "container_repositories_synced_count": null, + "container_repositories_failed_count": null, "container_repositories_synced_in_percentage": "0.00%", "design_repositories_count": 3, - "design_repositories_synced_count": nil, - "design_repositories_failed_count": nil, + "design_repositories_synced_count": null, + "design_repositories_failed_count": null, "design_repositories_synced_in_percentage": "0.00%", "projects_count": 41, "repositories_count": 41, @@ -534,10 +534,10 @@ Example response: "wikis_failed_count": 0, "wikis_synced_count": 41, "wikis_synced_in_percentage": "100.00%", - "replication_slots_count": nil, - "replication_slots_used_count": nil, + "replication_slots_count": null, + "replication_slots_used_count": null, "replication_slots_used_in_percentage": "0.00%", - "replication_slots_max_retained_wal_bytes": nil, + "replication_slots_max_retained_wal_bytes": null, "last_event_id": 23, "last_event_timestamp": 1509681166, "cursor_last_event_id": 23, diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 54aab25418d..0a2ee52f00d 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -1779,7 +1779,7 @@ To add a tier badge to a heading, add the relevant [tier badge](#available-produ after the heading text. For example: ```markdown -# Heading title `**(FREE)**` +# Heading title **(FREE)** ``` #### Product tier badges on other content diff --git a/doc/development/documentation/testing.md b/doc/development/documentation/testing.md index 95fcebab81d..f3d6e0a5c71 100644 --- a/doc/development/documentation/testing.md +++ b/doc/development/documentation/testing.md @@ -93,8 +93,8 @@ To execute documentation link tests locally: ### UI link tests -The `ui-docs-links lint` job uses `haml-lint` to test that all links to docs from -UI elements (`app/views` files, for example) are linking to valid docs and anchors. +The `ui-docs-links lint` job uses `haml-lint` to test that all documentation links from +UI elements (`app/views` files, for example) are linking to valid pages and anchors. To run the `ui-docs-links` test locally: @@ -156,12 +156,12 @@ markdownlint configuration is found in the following projects: - [`charts`](https://gitlab.com/gitlab-org/charts/gitlab/-/blob/master/.markdownlint.json) - [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/master/.markdownlint.json) -This configuration is also used within build pipelines. +This configuration is also used in build pipelines. You can use markdownlint: - [On the command line](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli--). -- [Within a code editor](#configure-editors). +- [In a code editor](#configure-editors). - [In a `pre-push` hook](#configure-pre-push-hooks). ### Vale @@ -172,10 +172,10 @@ English language. Vale's configuration is stored in the directory of projects. Vale supports creating [custom tests](https://errata-ai.github.io/vale/styles/) that extend any of -several types of checks, which we store in the `.linting/vale/styles/gitlab` directory within the +several types of checks, which we store in the `.linting/vale/styles/gitlab` directory in the documentation directory of projects. -Vale configuration is found in the following projects: +You can find Vale configuration in the following projects: - [`gitlab`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/doc/.vale/gitlab) - [`gitlab-runner`](https://gitlab.com/gitlab-org/gitlab-runner/-/tree/master/docs/.vale/gitlab) @@ -183,13 +183,13 @@ Vale configuration is found in the following projects: - [`charts`](https://gitlab.com/gitlab-org/charts/gitlab/-/tree/master/doc/.vale/gitlab) - [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/master/doc/.vale/gitlab) -This configuration is also used within build pipelines, where +This configuration is also used in build pipelines, where [error-level rules](#vale-result-types) are enforced. You can use Vale: - [On the command line](https://errata-ai.gitbook.io/vale/getting-started/usage). -- [Within a code editor](#configure-editors). +- [In a code editor](#configure-editors). - [In a Git hook](#configure-pre-push-hooks). Vale only reports errors in the Git hook (the same configuration as the CI/CD pipelines), and does not report suggestions or warnings. @@ -243,32 +243,40 @@ To match the versions of `markdownlint-cli` and `vale` used in the GitLab projec [versions used](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/master/.gitlab-ci.yml#L447) when building the `image:docs-lint-markdown` Docker image containing these tools for CI/CD. -| Tool | Version | Command | Additional information | -|--------------------|----------|-------------------------------------------|------------------------| -| `markdownlint-cli` | Latest | `yarn global add markdownlint-cli` | n/a | +| Tool | Version | Command | Additional information | +|--------------------|-----------|-------------------------------------------|------------------------| +| `markdownlint-cli` | Latest | `yarn global add markdownlint-cli` | n/a | | `markdownlint-cli` | Specific | `yarn global add markdownlint-cli@0.23.2` | The `@` indicates a specific version, and this example updates the tool to version `0.23.2`. | -| Vale | Latest | `brew update && brew upgrade vale` | This command is for macOS only. | -| Vale | Specific | n/a | Not possible using `brew`, but can be [directly downloaded](https://github.com/errata-ai/vale/releases). | +| Vale | Latest | `brew update && brew upgrade vale` | This command is for macOS only. | +| Vale | Specific | n/a | Not possible using `brew`, but can be [directly downloaded](https://github.com/errata-ai/vale/releases). | ### Configure editors Using linters in your editor is more convenient than having to run the commands from the command line. -To configure markdownlint within your editor, install one of the following as appropriate: +To configure markdownlint in your editor, install one of the following as appropriate: -- [Sublime Text](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint) -- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) -- [Atom](https://atom.io/packages/linter-node-markdownlint) -- [Vim](https://github.com/dense-analysis/ale) +- Sublime Text [`SublimeLinter-contrib-markdownlint` package](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint). +- Visual Studio Code [`DavidAnson.vscode-markdownlint` extension](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint). +- Atom [`linter-node-markdownlint` package](https://atom.io/packages/linter-node-markdownlint). +- Vim [ALE plugin](https://github.com/dense-analysis/ale). -To configure Vale within your editor, install one of the following as appropriate: +To configure Vale in your editor, install one of the following as appropriate: -- The Sublime Text [`SublimeLinter-contrib-vale` plugin](https://packagecontrol.io/packages/SublimeLinter-contrib-vale). -- The Visual Studio Code [`errata-ai.vale-server` extension](https://marketplace.visualstudio.com/items?itemName=errata-ai.vale-server). - You don't need Vale Server to use the plugin. You can configure the plugin to +- Sublime Text [`SublimeLinter-contrib-vale` package](https://packagecontrol.io/packages/SublimeLinter-contrib-vale). +- Visual Studio Code [`errata-ai.vale-server` extension](https://marketplace.visualstudio.com/items?itemName=errata-ai.vale-server). + You can configure the plugin to [display only a subset of alerts](#show-subset-of-vale-alerts). -- [Vim](https://github.com/dense-analysis/ale). + + In the extension's settings: + + - Select the **Use CLI** checkbox. + - In the **Config** setting, enter an absolute path to [`.vale.ini`](https://gitlab.com/gitlab-org/gitlab/blob/master/.vale.ini) in one of the cloned GitLab repositories on your computer. + - In the **Path** setting, enter the absolute path to the Vale binary. In most + cases, `vale` should work. To find the location, run `which vale` in a terminal. + +- Vim [ALE plugin](https://github.com/dense-analysis/ale). We don't use [Vale Server](https://errata-ai.github.io/vale/#using-vale-with-a-text-editor-or-another-third-party-application). diff --git a/doc/integration/saml.md b/doc/integration/saml.md index c2da839d547..2164bc3dc5f 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -163,7 +163,9 @@ will be returned to GitLab and will be signed in. ## SAML Groups -You can require users to be members of a certain group, or assign users `external`, `admin` or `auditor` roles based on group membership. This feature **does not** allow you to +You can require users to be members of a certain group, or assign users [external](../user/permissions.md#external-users), admin or [auditor](../user/permissions.md#auditor-users) roles based on group membership. +These groups are checked on each SAML login and user attributes updated as necessary. +This feature **does not** allow you to automatically add users to GitLab [Groups](../user/group/index.md). ### Requirements @@ -215,7 +217,7 @@ Example: ### External groups **(PREMIUM SELF)** -SAML login supports automatic identification on whether a user should be considered an [external](../user/permissions.md) user. This is based on the user's group membership in the SAML identity provider. +SAML login supports automatic identification on whether a user should be considered an [external user](../user/permissions.md#external-users). This is based on the user's group membership in the SAML identity provider. ```yaml { name: 'saml', @@ -257,7 +259,7 @@ considered admin users. The requirements are the same as the previous settings, your IdP needs to pass Group information to GitLab, you need to tell GitLab where to look for the groups in the SAML response, and which group(s) should be -considered auditor users. +considered [auditor users](../user/permissions.md#auditor-users). ```yaml { name: 'saml', @@ -385,7 +387,7 @@ This setting should be used only to map attributes that are part of the OmniAuth `attribute_statements` is used to map Attribute Names in a SAMLResponse to entries in the OmniAuth [`info` hash](https://github.com/omniauth/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later). -For example, if your SAMLResponse contains an Attribute called 'EmailAddress', +For example, if your SAMLResponse contains an Attribute called `EmailAddress`, specify `{ email: ['EmailAddress'] }` to map the Attribute to the corresponding key in the `info` hash. URI-named Attributes are also supported, e.g. `{ email: ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] }`. @@ -582,8 +584,8 @@ GitLab will sign the request with the provided private key. GitLab will include Avoid user control of the following attributes: -- [`*NameID*`](../user/group/saml_sso/index.md#nameid) -- *Email* when used with `omniauth_auto_link_saml_user` +- [`NameID`](../user/group/saml_sso/index.md#nameid) +- `Email` when used with `omniauth_auto_link_saml_user` These attributes define the SAML user. If users can change these attributes, they can impersonate others. diff --git a/doc/user/infrastructure/index.md b/doc/user/infrastructure/index.md index 83e19751f8c..bab61402909 100644 --- a/doc/user/infrastructure/index.md +++ b/doc/user/infrastructure/index.md @@ -26,6 +26,8 @@ variables: # If not using GitLab's HTTP backend, remove this line and specify TF_HTTP_* variables TF_STATE_NAME: default TF_CACHE_KEY: default + # If your terraform files are in a subdirectory, set TF_ROOT accordingly + # TF_ROOT: terraform/production ``` This template uses `.latest.`, instead of stable, and may include breaking changes. @@ -39,6 +41,15 @@ This template also includes some opinionated decisions, which you can override: [run the Terraform commands](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform/Base.latest.gitlab-ci.yml) `init`, `validate`, `plan`, `plan-json`, and `apply`. The `apply` command only runs on `master`. +This video from January 2021 walks you through all the GitLab Terraform integration features: + +
+ See the video: Terraform with GitLab. +
+
+ +
+ ## GitLab Managed Terraform state [Terraform remote backends](https://www.terraform.io/docs/backends/index.html) diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 90e41a7b99b..cc1106fe84f 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -352,6 +352,9 @@ An administrator can flag a user as external by either of the following methods: or edit an existing one. There, you can find the option to flag the user as external. +Additionally users can be set as external users using [SAML groups](../integration/saml.md#external-groups) +and [LDAP groups](../administration/auth/ldap/index.md#external-groups). + ### Setting new users to external By default, new users are not set as external users. This behavior can be changed diff --git a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md index b611a4f850c..4efeb3b7938 100644 --- a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md +++ b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md @@ -145,7 +145,7 @@ whitespace changes. ## Mark files as viewed -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51513) in GitLab 13.8. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51513) in GitLab 13.9. > - It's deployed behind a feature flag, enabled by default. > - It's enabled on GitLab.com. > - It's recommended for production use. diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index ede130513bc..4f6a2c1562f 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -21,13 +21,17 @@ By default, a protected branch does these things: - It prevents **anyone** from force pushing to the branch. - It prevents **anyone** from deleting the branch. -NOTE: -A GitLab administrator is allowed to push to the protected branches. +**Permissions:** -See the [Changelog](#changelog) section for changes over time. +- GitLab administrators are allowed to push to the protected branches. +- Users with [Developer permissions](../permissions.md) are allowed to + create a project in a group, but might not be allowed to initially + push to the [default branch](repository/branches/index.md#default-branch). The default branch protection level is set in the [Admin Area](../admin_area/settings/visibility_and_access_controls.md#default-branch-protection). +See the [Changelog](#changelog) section for changes over time. + ## Configuring protected branches To protect a branch, you need to have at least Maintainer permission level. diff --git a/generator_templates/usage_metric_definition/metric_definition.yml b/generator_templates/usage_metric_definition/metric_definition.yml new file mode 100644 index 00000000000..38968355171 --- /dev/null +++ b/generator_templates/usage_metric_definition/metric_definition.yml @@ -0,0 +1,14 @@ +# See Usage Ping metrics dictionary docs https://docs.gitlab.com/ee/development/usage_ping/metrics_dictionary.html +key_path: <%= key_path %> +value_type: +product_category: +stage: +status: +milestone: +introduced_by_url: +group: +time_frame: <%= time_frame %> +data_source: +distribution: <%= distribution %> +# tier: ['free', 'starter', 'premium', 'ultimate', 'bronze', 'silver', 'gold'] +tier: diff --git a/lib/generators/gitlab/usage_metric_definition_generator.rb b/lib/generators/gitlab/usage_metric_definition_generator.rb new file mode 100644 index 00000000000..20e0cb333dc --- /dev/null +++ b/lib/generators/gitlab/usage_metric_definition_generator.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'rails/generators' + +module Gitlab + class UsageMetricDefinitionGenerator < Rails::Generators::Base + Directory = Struct.new(:name, :time_frame) do + def match?(str) + (name == str || time_frame == str) && str != 'none' + end + end + + TIME_FRAME_DIRS = [ + Directory.new('counts_7d', '7d'), + Directory.new('counts_28d', '28d'), + Directory.new('counts_all', 'all'), + Directory.new('settings', 'none'), + Directory.new('license', 'none') + ].freeze + + VALID_INPUT_DIRS = (TIME_FRAME_DIRS.flat_map { |d| [d.name, d.time_frame] } - %w(none)).freeze + + source_root File.expand_path('../../../generator_templates/usage_metric_definition', __dir__) + + desc 'Generates a metric definition yml file' + + class_option :ee, type: :boolean, optional: true, default: false, desc: 'Indicates if metric is for ee' + class_option :dir, + type: :string, desc: "Indicates the metric location. It must be one of: #{VALID_INPUT_DIRS.join(', ')}" + + argument :key_path, type: :string, desc: 'Unique JSON key path for the metric' + + def create_metric_file + validate! + + template "metric_definition.yml", file_path + end + + def time_frame + directory&.time_frame + end + + def distribution + value = ['ce'] + value << 'ee' if ee? + value + end + + private + + def file_path + path = File.join('config', 'metrics', directory&.name, "#{file_name}.yml") + path = File.join('ee', path) if ee? + path + end + + def validate! + raise "--dir option is required" unless input_dir.present? + raise "Invalid dir #{input_dir}, allowed options are #{VALID_INPUT_DIRS.join(', ')}" unless directory.present? + end + + def ee? + options[:ee] + end + + def input_dir + options[:dir] + end + + def file_name + key_path.split('.').last + end + + def directory + @directory ||= TIME_FRAME_DIRS.find { |d| d.match?(input_dir) } + end + end +end diff --git a/lib/gitlab/background_migration/user_mentions/models/namespace.rb b/lib/gitlab/background_migration/user_mentions/models/namespace.rb index 6d7b9a86e69..8fa0db5fd4b 100644 --- a/lib/gitlab/background_migration/user_mentions/models/namespace.rb +++ b/lib/gitlab/background_migration/user_mentions/models/namespace.rb @@ -6,6 +6,7 @@ module Gitlab module Models # isolated Namespace model class Namespace < ApplicationRecord + include FeatureGate include ::Gitlab::VisibilityLevel include ::Gitlab::Utils::StrongMemoize include Gitlab::BackgroundMigration::UserMentions::Models::Concerns::Namespace::RecursiveTraversal diff --git a/lib/gitlab/ci/charts.rb b/lib/gitlab/ci/charts.rb index 25fb9c0ca97..797193a6be5 100644 --- a/lib/gitlab/ci/charts.rb +++ b/lib/gitlab/ci/charts.rb @@ -31,9 +31,10 @@ module Gitlab current = @from while current <= @to - @labels << current.strftime(@format) - @total << (totals_count[current] || 0) - @success << (success_count[current] || 0) + label = current.strftime(@format) + @labels << label + @total << (totals_count[label] || 0) + @success << (success_count[label] || 0) current += interval_step end @@ -45,6 +46,7 @@ module Gitlab query .group("date_trunc('#{interval}', #{::Ci::Pipeline.table_name}.created_at)") .count(:created_at) + .transform_keys { |date| date.strftime(@format) } end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/gitlab/ci/reports/codequality_mr_diff.rb b/lib/gitlab/ci/reports/codequality_mr_diff.rb new file mode 100644 index 00000000000..e60a075e3f5 --- /dev/null +++ b/lib/gitlab/ci/reports/codequality_mr_diff.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Reports + class CodequalityMrDiff + attr_reader :files + + def initialize(raw_report) + @raw_report = raw_report + @files = {} + build_report! + end + + private + + def build_report! + codequality_files = @raw_report.all_degradations.each_with_object({}) do |degradation, codequality_files| + unless codequality_files[degradation.dig(:location, :path)].present? + codequality_files[degradation.dig(:location, :path)] = [] + end + + build_mr_diff_payload(codequality_files, degradation) + end + + @files = codequality_files + end + + def build_mr_diff_payload(codequality_files, degradation) + codequality_files[degradation.dig(:location, :path)] << { + line: degradation.dig(:location, :lines, :begin) || degradation.dig(:location, :positions, :begin, :line), + description: degradation[:description], + severity: degradation[:severity] + } + end + end + end + end +end diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index e7e0d4e471f..8f18d6433e0 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -8,9 +8,9 @@ module Rouge # Creates a new Rouge::Formatter::HTMLGitlab instance. # # [+tag+] The tag (language) of the lexer used to generate the formatted tokens - def initialize(tag: nil) + def initialize(options = {}) @line_number = 1 - @tag = tag + @tag = options[:tag] end def stream(tokens) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 074ebe57f0a..e539c44e4e9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1905,6 +1905,9 @@ msgstr "" msgid "Admin mode enabled" msgstr "" +msgid "Admin navigation" +msgstr "" + msgid "Admin notes" msgstr "" @@ -4656,6 +4659,9 @@ msgstr "" msgid "Boards|An error occurred while updating the list. Please try again." msgstr "" +msgid "Boards|Board" +msgstr "" + msgid "Boards|Collapse" msgstr "" @@ -4851,6 +4857,9 @@ msgstr "" msgid "Branches|protected" msgstr "" +msgid "Breadcrumbs" +msgstr "" + msgid "Brief title about the change" msgstr "" @@ -4890,6 +4899,9 @@ msgstr "" msgid "Bulk request concurrency" msgstr "" +msgid "Bulk update" +msgstr "" + msgid "BulkImport|From source group" msgstr "" @@ -8904,7 +8916,7 @@ msgstr "" msgid "Dashboard|%{firstProject}, %{rest}, and %{secondProject}" msgstr "" -msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Silver plan." +msgid "Dashboard|Unable to add %{invalidProjects}. This dashboard is available for public projects, and private projects in groups with a Premium plan." msgstr "" msgid "DastProfiles|A passive scan monitors all HTTP messages (requests and responses) sent to the target. An active scan attacks the target to find potential vulnerabilities." @@ -13876,6 +13888,9 @@ msgstr "" msgid "Group name (your organization)" msgstr "" +msgid "Group navigation" +msgstr "" + msgid "Group overview" msgstr "" @@ -16723,7 +16738,7 @@ msgstr "" msgid "Job|Job has been erased" msgstr "" -msgid "Job|Job has been erased by" +msgid "Job|Job has been erased by %{userLink}" msgstr "" msgid "Job|Keep" @@ -20507,7 +20522,7 @@ msgstr "" msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less." msgstr "" -msgid "Only 'Reporter' roles and above on tiers Premium / Silver and above can see Value Stream Analytics." +msgid "Only 'Reporter' roles and above on tiers Premium and above can see Value Stream Analytics." msgstr "" msgid "Only 1 appearances row can exist" @@ -20543,7 +20558,7 @@ msgstr "" msgid "Only verified users with an email address in any of these domains can be added to the group." msgstr "" -msgid "Only ‘Reporter’ roles and above on tiers Premium / Silver and above can see Productivity Analytics." +msgid "Only ‘Reporter’ roles and above on tiers Premium and above can see Productivity Analytics." msgstr "" msgid "Oops, are you sure?" @@ -22613,6 +22628,9 @@ msgstr "" msgid "Project name suffix" msgstr "" +msgid "Project navigation" +msgstr "" + msgid "Project order will not be saved as local storage is not available." msgstr "" @@ -25595,6 +25613,9 @@ msgstr "" msgid "Security dashboard" msgstr "" +msgid "Security navigation" +msgstr "" + msgid "Security report is out of date. Please update your branch with the latest changes from the target branch (%{targetBranchName})" msgstr "" @@ -27710,6 +27731,9 @@ msgstr "" msgid "Subgroup milestone" msgstr "" +msgid "Subgroup navigation" +msgstr "" + msgid "Subgroup overview" msgstr "" @@ -30322,7 +30346,7 @@ msgstr "" msgid "To see all the user's personal access tokens you must impersonate them first." msgstr "" -msgid "To see this project's operational details, %{linkStart}upgrade its group plan to Silver%{linkEnd}. You can also remove the project from the dashboard." +msgid "To see this project's operational details, %{linkStart}upgrade its group plan to Premium%{linkEnd}. You can also remove the project from the dashboard." msgstr "" msgid "To see this project's operational details, contact an owner of group %{groupName} to upgrade the plan. You can also remove the project from the dashboard." diff --git a/spec/factories/ci/reports/codequality_degradations.rb b/spec/factories/ci/reports/codequality_degradations.rb new file mode 100644 index 00000000000..d82157b457a --- /dev/null +++ b/spec/factories/ci/reports/codequality_degradations.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :codequality_degradation_1, class: Hash do + skip_create + + initialize_with do + { + "categories": [ + "Complexity" + ], + "check_name": "argument_count", + "content": { + "body": "" + }, + "description": "Avoid parameter lists longer than 5 parameters. [12/5]", + "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547", + "location": { + "path": "file_a.rb", + "lines": { + "begin": 10, + "end": 10 + } + }, + "other_locations": [], + "remediation_points": 900000, + "severity": "major", + "type": "issue", + "engine_name": "structure" + }.with_indifferent_access + end + end + + factory :codequality_degradation_2, class: Hash do + skip_create + + initialize_with do + { + "categories": [ + "Complexity" + ], + "check_name": "argument_count", + "content": { + "body": "" + }, + "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", + "fingerprint": "f3bdc1e8c102ba5fbd9e7f6cda51c95e", + "location": { + "path": "file_a.rb", + "lines": { + "begin": 10, + "end": 10 + } + }, + "other_locations": [], + "remediation_points": 900000, + "severity": "major", + "type": "issue", + "engine_name": "structure" + }.with_indifferent_access + end + end + + factory :codequality_degradation_3, class: Hash do + skip_create + + initialize_with do + { + "type": "Issue", + "check_name": "Rubocop/Metrics/ParameterLists", + "description": "Avoid parameter lists longer than 5 parameters. [12/5]", + "categories": [ + "Complexity" + ], + "remediation_points": 550000, + "location": { + "path": "file_b.rb", + "positions": { + "begin": { + "column": 14, + "line": 10 + }, + "end": { + "column": 39, + "line": 10 + } + } + }, + "content": { + "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count." + }, + "engine_name": "rubocop", + "fingerprint": "ab5f8b935886b942d621399f5a2ca16e", + "severity": "minor" + }.with_indifferent_access + end + end +end diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index a8e18385bd2..bbdf2f7f4a9 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -54,7 +54,7 @@ RSpec.describe 'Admin Groups' do click_button "Create group" expect(current_path).to eq admin_group_path(Group.find_by(path: path_component)) - content = page.find('div#content-body') + content = page.find('#content-body') h3_texts = content.all('h3').collect(&:text).join("\n") expect(h3_texts).to match group_name li_texts = content.all('li').collect(&:text).join("\n") diff --git a/spec/fixtures/api/schemas/entities/codequality_mr_diff_report.json b/spec/fixtures/api/schemas/entities/codequality_mr_diff_report.json new file mode 100644 index 00000000000..63e0c68e9cd --- /dev/null +++ b/spec/fixtures/api/schemas/entities/codequality_mr_diff_report.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "description": "The schema used to display codequality report in mr diff", + "required": ["files"], + "properties": { + "patternProperties": { + ".*.": { + "type": "array", + "items": { + "required": ["line", "description", "severity"], + "properties": { + "line": { "type": "integer" }, + "description": { "type": "string" }, + "severity": { "type": "string" } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/spec/frontend/jobs/components/erased_block_spec.js b/spec/frontend/jobs/components/erased_block_spec.js index b3e1d28eb16..9557ecdbb91 100644 --- a/spec/frontend/jobs/components/erased_block_spec.js +++ b/spec/frontend/jobs/components/erased_block_spec.js @@ -10,6 +10,8 @@ describe('Erased block', () => { const timeago = getTimeago(); const formattedDate = timeago.format(erasedAt); + const findLink = () => wrapper.find(GlLink); + const createComponent = (props) => { wrapper = mount(ErasedBlock, { propsData: props, @@ -32,7 +34,7 @@ describe('Erased block', () => { }); it('renders username and link', () => { - expect(wrapper.find(GlLink).attributes('href')).toEqual('gitlab.com/root'); + expect(findLink().attributes('href')).toEqual('gitlab.com/root'); expect(wrapper.text().trim()).toContain('Job has been erased by'); expect(wrapper.text().trim()).toContain('root'); diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js index ac3421c186f..e673d7dfd6b 100644 --- a/spec/frontend/lib/utils/datetime_utility_spec.js +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -618,9 +618,12 @@ describe('nDaysAfter', () => { ${-1} | ${new Date('2019-07-15T00:00:00.000Z').valueOf()} ${0} | ${date.valueOf()} ${0.9} | ${date.valueOf()} - `('returns $numberOfDays day(s) after the provided date', ({ numberOfDays, expectedResult }) => { - expect(datetimeUtility.nDaysAfter(date, numberOfDays)).toBe(expectedResult); - }); + `( + 'returns the date $numberOfDays day(s) after the provided date', + ({ numberOfDays, expectedResult }) => { + expect(datetimeUtility.nDaysAfter(date, numberOfDays)).toBe(expectedResult); + }, + ); }); describe('nDaysBefore', () => { @@ -633,9 +636,48 @@ describe('nDaysBefore', () => { ${-1} | ${new Date('2019-07-17T00:00:00.000Z').valueOf()} ${0} | ${date.valueOf()} ${0.9} | ${new Date('2019-07-15T00:00:00.000Z').valueOf()} - `('returns $numberOfDays day(s) before the provided date', ({ numberOfDays, expectedResult }) => { - expect(datetimeUtility.nDaysBefore(date, numberOfDays)).toBe(expectedResult); - }); + `( + 'returns the date $numberOfDays day(s) before the provided date', + ({ numberOfDays, expectedResult }) => { + expect(datetimeUtility.nDaysBefore(date, numberOfDays)).toBe(expectedResult); + }, + ); +}); + +describe('nWeeksAfter', () => { + const date = new Date('2021-07-16T00:00:00.000Z'); + + it.each` + numberOfWeeks | expectedResult + ${1} | ${new Date('2021-07-23T00:00:00.000Z').valueOf()} + ${3} | ${new Date('2021-08-06T00:00:00.000Z').valueOf()} + ${-1} | ${new Date('2021-07-09T00:00:00.000Z').valueOf()} + ${0} | ${date.valueOf()} + ${0.6} | ${new Date('2021-07-20T00:00:00.000Z').valueOf()} + `( + 'returns the date $numberOfWeeks week(s) after the provided date', + ({ numberOfWeeks, expectedResult }) => { + expect(datetimeUtility.nWeeksAfter(date, numberOfWeeks)).toBe(expectedResult); + }, + ); +}); + +describe('nWeeksBefore', () => { + const date = new Date('2021-07-16T00:00:00.000Z'); + + it.each` + numberOfWeeks | expectedResult + ${1} | ${new Date('2021-07-09T00:00:00.000Z').valueOf()} + ${3} | ${new Date('2021-06-25T00:00:00.000Z').valueOf()} + ${-1} | ${new Date('2021-07-23T00:00:00.000Z').valueOf()} + ${0} | ${date.valueOf()} + ${0.6} | ${new Date('2021-07-11T00:00:00.000Z').valueOf()} + `( + 'returns the date $numberOfWeeks week(s) before the provided date', + ({ numberOfWeeks, expectedResult }) => { + expect(datetimeUtility.nWeeksBefore(date, numberOfWeeks)).toBe(expectedResult); + }, + ); }); describe('nMonthsAfter', () => { @@ -659,7 +701,7 @@ describe('nMonthsAfter', () => { ${may2020} | ${0} | ${may2020.valueOf()} ${may2020} | ${0.9} | ${may2020.valueOf()} `( - 'returns $numberOfMonths month(s) after the provided date', + 'returns the date $numberOfMonths month(s) after the provided date', ({ date, numberOfMonths, expectedResult }) => { expect(datetimeUtility.nMonthsAfter(date, numberOfMonths)).toBe(expectedResult); }, @@ -687,7 +729,7 @@ describe('nMonthsBefore', () => { ${june2020} | ${0} | ${june2020.valueOf()} ${june2020} | ${0.9} | ${new Date('2020-05-15T00:00:00.000Z').valueOf()} `( - 'returns $numberOfMonths month(s) before the provided date', + 'returns the date $numberOfMonths month(s) before the provided date', ({ date, numberOfMonths, expectedResult }) => { expect(datetimeUtility.nMonthsBefore(date, numberOfMonths)).toBe(expectedResult); }, @@ -898,3 +940,14 @@ describe('getOverlapDateInPeriods', () => { }); }); }); + +describe('isToday', () => { + const today = new Date(); + it.each` + date | expected | negation + ${today} | ${true} | ${'is'} + ${new Date('2021-01-21T12:00:00.000Z')} | ${false} | ${'is NOT'} + `('returns $expected as $date $negation today', ({ date, expected }) => { + expect(datetimeUtility.isToday(date)).toBe(expected); + }); +}); diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index ac325bd4b29..9e18ab34c1f 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -135,15 +135,6 @@ RSpec.describe AvatarsHelper do helper.avatar_icon_for_user(nil, 20, 2) end end - - context 'for a blocked user' do - let(:user) { create(:user, :blocked) } - - it 'returns the default avatar' do - expect(helper.avatar_icon_for_user(user).to_s) - .to eq(helper.default_avatar) - end - end end describe '#gravatar_icon' do diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb index cfc2019a89b..46d7d4a58f0 100644 --- a/spec/lib/gitlab/ci/charts_spec.rb +++ b/spec/lib/gitlab/ci/charts_spec.rb @@ -9,6 +9,10 @@ RSpec.describe Gitlab::Ci::Charts do subject { chart.to } + before do + create(:ci_empty_pipeline, project: project, duration: 120) + end + it 'goes until the end of the current month (including the whole last day of the month)' do is_expected.to eq(Date.today.end_of_month.end_of_day) end @@ -20,6 +24,10 @@ RSpec.describe Gitlab::Ci::Charts do it 'uses %B %Y as labels format' do expect(chart.labels).to include(chart.from.strftime('%B %Y')) end + + it 'returns count of pipelines run each day in the current year' do + expect(chart.total.sum).to eq(1) + end end context 'monthchart' do @@ -28,6 +36,10 @@ RSpec.describe Gitlab::Ci::Charts do subject { chart.to } + before do + create(:ci_empty_pipeline, project: project, duration: 120) + end + it 'includes the whole current day' do is_expected.to eq(Date.today.end_of_day) end @@ -39,6 +51,10 @@ RSpec.describe Gitlab::Ci::Charts do it 'uses %d %B as labels format' do expect(chart.labels).to include(chart.from.strftime('%d %B')) end + + it 'returns count of pipelines run each day in the current month' do + expect(chart.total.sum).to eq(1) + end end context 'weekchart' do @@ -47,6 +63,10 @@ RSpec.describe Gitlab::Ci::Charts do subject { chart.to } + before do + create(:ci_empty_pipeline, project: project, duration: 120) + end + it 'includes the whole current day' do is_expected.to eq(Date.today.end_of_day) end @@ -58,6 +78,68 @@ RSpec.describe Gitlab::Ci::Charts do it 'uses %d %B as labels format' do expect(chart.labels).to include(chart.from.strftime('%d %B')) end + + it 'returns count of pipelines run each day in the current week' do + expect(chart.total.sum).to eq(1) + end + end + + context 'weekchart_utc' do + today = Date.today + end_of_today = Time.use_zone(Time.find_zone('UTC')) { today.end_of_day } + + let(:project) { create(:project) } + let(:chart) do + allow(Date).to receive(:today).and_return(today) + allow(today).to receive(:end_of_day).and_return(end_of_today) + Gitlab::Ci::Charts::WeekChart.new(project) + end + + subject { chart.total } + + before do + create(:ci_empty_pipeline, project: project, duration: 120) + end + + it 'uses a utc time zone for range times' do + expect(chart.to.zone).to eq(end_of_today.zone) + expect(chart.from.zone).to eq(end_of_today.zone) + end + + it 'returns count of pipelines run each day in the current week' do + expect(chart.total.sum).to eq(1) + end + end + + context 'weekchart_non_utc' do + today = Date.today + end_of_today = Time.use_zone(Time.find_zone('Asia/Dubai')) { today.end_of_day } + + let(:project) { create(:project) } + let(:chart) do + allow(Date).to receive(:today).and_return(today) + allow(today).to receive(:end_of_day).and_return(end_of_today) + Gitlab::Ci::Charts::WeekChart.new(project) + end + + subject { chart.total } + + before do + # The DB uses UTC always, so our use of a Time Zone in the application + # can cause the creation date of the pipeline to go unmatched depending + # on the offset. We can work around this by requesting the pipeline be + # created a with the `created_at` field set to a day ago in the same week. + create(:ci_empty_pipeline, project: project, duration: 120, created_at: today - 1.day) + end + + it 'uses a non-utc time zone for range times' do + expect(chart.to.zone).to eq(end_of_today.zone) + expect(chart.from.zone).to eq(end_of_today.zone) + end + + it 'returns count of pipelines run each day in the current week' do + expect(chart.total.sum).to eq(1) + end end context 'pipeline_times' do diff --git a/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb b/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb new file mode 100644 index 00000000000..8b177fa7fc1 --- /dev/null +++ b/spec/lib/gitlab/ci/reports/codequality_mr_diff_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Reports::CodequalityMrDiff do + let(:codequality_report) { Gitlab::Ci::Reports::CodequalityReports.new } + let(:degradation_1) { build(:codequality_degradation_1) } + let(:degradation_2) { build(:codequality_degradation_2) } + let(:degradation_3) { build(:codequality_degradation_3) } + + describe '#initialize!' do + subject(:report) { described_class.new(codequality_report) } + + context 'when quality has degradations' do + context 'with several degradations on the same line' do + before do + codequality_report.add_degradation(degradation_1) + codequality_report.add_degradation(degradation_2) + end + + it 'generates quality report for mr diff' do + expect(report.files).to match( + "file_a.rb" => [ + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" }, + { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "major" } + ] + ) + end + end + + context 'with several degradations on several files' do + before do + codequality_report.add_degradation(degradation_1) + codequality_report.add_degradation(degradation_2) + codequality_report.add_degradation(degradation_3) + end + + it 'returns quality report for mr diff' do + expect(report.files).to match( + "file_a.rb" => [ + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "major" }, + { line: 10, description: "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.", severity: "major" } + ], + "file_b.rb" => [ + { line: 10, description: "Avoid parameter lists longer than 5 parameters. [12/5]", severity: "minor" } + ] + ) + end + end + end + + context 'when quality has no degradation' do + it 'returns an empty hash' do + expect(report.files).to match({}) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 3f40e0d228b..2d616ec8862 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -545,7 +545,7 @@ project: - daily_build_group_report_results - jira_imports - compliance_framework_setting -- compliance_management_frameworks +- compliance_management_framework - metrics_users_starred_dashboards - alert_management_alerts - repository_storage_moves diff --git a/spec/serializers/ci/codequality_mr_diff_entity_spec.rb b/spec/serializers/ci/codequality_mr_diff_entity_spec.rb new file mode 100644 index 00000000000..82708908d95 --- /dev/null +++ b/spec/serializers/ci/codequality_mr_diff_entity_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::CodequalityMrDiffEntity do + let(:entity) { described_class.new(mr_diff_report) } + let(:mr_diff_report) { Gitlab::Ci::Reports::CodequalityMrDiff.new(codequality_report) } + let(:codequality_report) { Gitlab::Ci::Reports::CodequalityReports.new } + let(:degradation_1) { build(:codequality_degradation_1) } + let(:degradation_2) { build(:codequality_degradation_2) } + + describe '#as_json' do + subject(:report) { entity.as_json } + + context 'when quality report has degradations' do + before do + codequality_report.add_degradation(degradation_1) + codequality_report.add_degradation(degradation_2) + end + + it 'contains correct codequality mr diff report', :aggregate_failures do + expect(report[:files].keys).to eq(["file_a.rb"]) + expect(report[:files]["file_a.rb"].first).to include(:line, :description, :severity) + end + end + end +end diff --git a/spec/serializers/ci/codequality_mr_diff_report_serializer_spec.rb b/spec/serializers/ci/codequality_mr_diff_report_serializer_spec.rb new file mode 100644 index 00000000000..906ca36041f --- /dev/null +++ b/spec/serializers/ci/codequality_mr_diff_report_serializer_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::CodequalityMrDiffReportSerializer do + let(:serializer) { described_class.new.represent(mr_diff_report) } + let(:mr_diff_report) { Gitlab::Ci::Reports::CodequalityMrDiff.new(codequality_report) } + let(:codequality_report) { Gitlab::Ci::Reports::CodequalityReports.new } + let(:degradation_1) { build(:codequality_degradation_1) } + let(:degradation_2) { build(:codequality_degradation_2) } + + describe '#to_json' do + subject { serializer.as_json } + + context 'when quality report has degradations' do + before do + codequality_report.add_degradation(degradation_1) + codequality_report.add_degradation(degradation_2) + end + + it 'matches the schema' do + expect(subject).to match_schema('entities/codequality_mr_diff_report') + end + end + + context 'when quality report has no degradations' do + it 'matches the schema' do + expect(subject).to match_schema('entities/codequality_mr_diff_report') + end + end + end +end diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb index 43060e571a9..8fd9bb47053 100644 --- a/spec/support/helpers/wait_for_requests.rb +++ b/spec/support/helpers/wait_for_requests.rb @@ -52,6 +52,6 @@ module WaitForRequests end def finished_all_ajax_requests? - Capybara.page.evaluate_script('window.pendingRequests || window.pendingRailsUJSRequests || 0').zero? # rubocop:disable Style/NumericPredicate + Capybara.page.evaluate_script('window.pendingRequests || window.pendingApolloRequests || window.pendingRailsUJSRequests || 0').zero? # rubocop:disable Style/NumericPredicate end end