From 9440c17f554424cc77aff8afadc61adbe23524d1 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 15 Jun 2022 15:09:20 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab-ci.yml | 4 +- .gitlab/ci/docs.gitlab-ci.yml | 2 +- .gitlab/ci/frontend.gitlab-ci.yml | 2 +- .gitlab/ci/global.gitlab-ci.yml | 11 +- .gitlab/ci/qa.gitlab-ci.yml | 2 +- .gitlab/ci/rails.gitlab-ci.yml | 11 +- .gitlab/ci/review-apps/main.gitlab-ci.yml | 2 +- .gitlab/ci/setup.gitlab-ci.yml | 6 +- .gitlab/ci/test-metadata.gitlab-ci.yml | 2 +- .gitlab/ci/workhorse.gitlab-ci.yml | 2 +- GITALY_SERVER_VERSION | 2 +- .../components/environment_folder.vue | 4 +- app/assets/javascripts/lib/utils/rails_ujs.js | 4 +- .../components/work_item_assignees.vue | 108 ++++++++++++-- .../components/work_item_detail.vue | 6 +- .../local_update_work_item.mutation.graphql | 9 ++ .../work_items/graphql/provider.js | 97 ++++++++----- .../work_items/graphql/typedefs.graphql | 14 ++ app/models/error_tracking/error_event.rb | 54 +------ app/views/projects/blob/_upload.html.haml | 30 ---- app/views/projects/blob/show.html.haml | 3 - app/views/projects/empty.html.haml | 3 - .../pipeline_schedules/_form.html.haml | 2 +- app/views/shared/_label.html.haml | 5 +- .../promotions/_promote_servicedesk.html.haml | 19 +-- ... => active_support_hash_digest_sha256.yml} | 10 +- .../rename_integrations_workers.yml | 2 +- .../update_vuln_identifiers_flag.yml | 8 + .../set_active_support_hash_digest_class.rb | 11 ++ db/fixtures/development/12_snippets.rb | 3 +- doc/administration/audit_events.md | 6 + doc/development/pipelines.md | 6 + lib/api/ci/job_artifacts.rb | 4 +- lib/api/ci/runner.rb | 2 +- lib/api/helpers.rb | 18 ++- lib/error_tracking/stacktrace_builder.rb | 61 ++++++++ .../ci/templates/Terraform/Base.gitlab-ci.yml | 2 +- .../diff/rendered/notebook/diff_file.rb | 36 +++-- .../rendered/notebook/diff_file_helper.rb | 137 ++++++++++-------- lib/gitlab/gon_helper.rb | 1 - lib/gitlab/hash_digest/facade.rb | 29 ++++ locale/gitlab.pot | 18 +-- .../admin/admin_disables_two_factor_spec.rb | 7 +- spec/features/admin/admin_hooks_spec.rb | 7 +- spec/features/admin/admin_labels_spec.rb | 9 +- .../admin_users_impersonation_tokens_spec.rb | 5 +- .../admin_uses_repository_checks_spec.rb | 6 +- spec/features/admin/users/user_spec.rb | 3 +- spec/features/admin/users/users_spec.rb | 6 +- spec/features/boards/boards_spec.rb | 1 - .../groups/members/leave_group_spec.rb | 4 +- .../groups/settings/access_tokens_spec.rb | 3 +- .../user_comments_on_diff_spec.rb | 1 - .../user_posts_diff_notes_spec.rb | 1 - .../merge_request/user_posts_notes_spec.rb | 1 - .../user_sees_avatar_on_diff_notes_spec.rb | 1 - .../user_sees_deployment_widget_spec.rb | 1 - spec/features/profile_spec.rb | 19 ++- .../features/profiles/active_sessions_spec.rb | 10 +- .../profiles/oauth_applications_spec.rb | 11 +- .../profiles/personal_access_tokens_spec.rb | 7 +- .../branches/user_deletes_branch_spec.rb | 6 +- .../comments/user_deletes_comments_spec.rb | 6 +- .../commit/user_comments_on_commit_spec.rb | 7 +- .../environments/environments_spec.rb | 2 - .../projects/jobs/user_browses_job_spec.rb | 9 +- .../members/member_leaves_project_spec.rb | 4 +- .../members/user_requests_access_spec.rb | 5 +- .../projects/pages/user_adds_domain_spec.rb | 5 +- .../user_edits_lets_encrypt_settings_spec.rb | 5 +- .../pages/user_edits_settings_spec.rb | 5 +- .../projects/pipeline_schedules_spec.rb | 8 +- .../projects/pipelines/pipelines_spec.rb | 1 - .../projects/settings/access_tokens_spec.rb | 3 +- .../user_searches_in_settings_spec.rb | 1 - spec/features/promotion_spec.rb | 2 +- .../files/user_replaces_files_spec.rb | 93 ------------ .../notes_on_personal_snippets_spec.rb | 6 +- .../snippets/user_creates_snippet_spec.rb | 1 - spec/features/triggers_spec.rb | 5 +- spec/frontend/blob/blob_file_dropzone_spec.js | 49 ------- .../environments/environment_folder_spec.js | 4 +- .../environments/new_environment_item_spec.js | 4 +- .../components/work_item_assignees_spec.js | 82 ++++++++++- ...t_active_support_hash_digest_class_spec.rb | 9 ++ .../error_tracking/stacktrace_builder_spec.rb | 95 ++++++++++++ .../notebook/diff_file_helper_spec.rb | 132 ++++++++--------- spec/lib/gitlab/hash_digest/facade_spec.rb | 36 +++++ spec/lib/gitlab/usage_data_metrics_spec.rb | 5 +- .../models/error_tracking/error_event_spec.rb | 44 +----- .../ci/after_requeue_job_service_spec.rb | 45 ++++++ .../features/2fa_shared_examples.rb | 4 +- .../features/access_tokens_shared_examples.rb | 4 +- tooling/quality/test_level.rb | 2 +- 94 files changed, 923 insertions(+), 627 deletions(-) create mode 100644 app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql delete mode 100644 app/views/projects/blob/_upload.html.haml rename config/feature_flags/development/{bootstrap_confirmation_modals.yml => active_support_hash_digest_sha256.yml} (62%) create mode 100644 config/feature_flags/development/update_vuln_identifiers_flag.yml create mode 100644 config/initializers/set_active_support_hash_digest_class.rb create mode 100644 lib/error_tracking/stacktrace_builder.rb create mode 100644 lib/gitlab/hash_digest/facade.rb delete mode 100644 spec/features/refactor_blob_viewer_disabled/projects/files/user_replaces_files_spec.rb delete mode 100644 spec/frontend/blob/blob_file_dropzone_spec.js create mode 100644 spec/initializers/set_active_support_hash_digest_class_spec.rb create mode 100644 spec/lib/error_tracking/stacktrace_builder_spec.rb create mode 100644 spec/lib/gitlab/hash_digest/facade_spec.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f6910a067dd..7605f10b14e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -44,6 +44,7 @@ workflow: # For the 2-hourly scheduled pipelines, we set specific variables. - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"' variables: + RUBY_VERSION: "3.0" CRYSTALBALL: "true" # For `$CI_DEFAULT_BRANCH` branch, create a pipeline (this includes on schedules, pushes, merges, etc.). - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' @@ -59,7 +60,7 @@ workflow: variables: PG_VERSION: "12" - DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-2.7.patched-golang-1.17-node-16.14-postgresql-${PG_VERSION}:git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36" + DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-1.17-node-16.14-postgresql-${PG_VERSION}:git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36" RAILS_ENV: "test" NODE_ENV: "test" BUNDLE_WITHOUT: "production:development" @@ -75,6 +76,7 @@ variables: DEBIAN_VERSION: "bullseye" CHROME_VERSION: "101" DOCKER_VERSION: "20.10.14" + RUBY_VERSION: "2.7" TMP_TEST_FOLDER: "${CI_PROJECT_DIR}/tmp/tests" GITLAB_WORKHORSE_FOLDER: "gitlab-workhorse" diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml index 8eefb9ad569..9dcc305d32d 100644 --- a/.gitlab/ci/docs.gitlab-ci.yml +++ b/.gitlab/ci/docs.gitlab-ci.yml @@ -2,7 +2,7 @@ extends: - .default-retry - .docs:rules:review-docs - image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine + image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION}-alpine stage: review needs: [] variables: diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 4b1194d0fbd..8bfda0e6684 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -11,7 +11,7 @@ - .default-retry - .default-before_script - .assets-compile-cache - image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:debian-${DEBIAN_VERSION}-ruby-2.7-git-2.33-lfs-2.9-node-16.14-yarn-1.22-graphicsmagick-1.3.36 + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-git-2.33-lfs-2.9-node-16.14-yarn-1.22-graphicsmagick-1.3.36 variables: SETUP_DB: "false" WEBPACK_VENDOR_DLL: "true" diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index 6a2c27a1c22..344a31b28d8 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -18,7 +18,7 @@ - source scripts/prepare_build.sh .ruby-gems-cache: &ruby-gems-cache - key: "ruby-gems-${DEBIAN_VERSION}" + key: "ruby-gems-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}" paths: - vendor/ruby/ policy: pull @@ -28,7 +28,7 @@ policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up. .gitaly-ruby-gems-cache: &gitaly-ruby-gems-cache - key: "gitaly-ruby-gems-${DEBIAN_VERSION}" + key: "gitaly-ruby-gems-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}" paths: - vendor/gitaly-ruby/ policy: pull @@ -42,7 +42,7 @@ files: - GITALY_SERVER_VERSION - lib/gitlab/setup_helper.rb - prefix: "gitaly-binaries-${DEBIAN-VERSION}" + prefix: "gitaly-binaries-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}" paths: - ${TMP_TEST_FOLDER}/gitaly/_build/bin/ - ${TMP_TEST_FOLDER}/gitaly/_build/deps/git/install/ @@ -79,7 +79,7 @@ policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up. .assets-cache: &assets-cache - key: "assets-${DEBIAN_VERSION}-${NODE_ENV}" + key: "assets-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-${NODE_ENV}" paths: - assets-hash.txt - public/assets/webpack/ @@ -103,7 +103,7 @@ policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up. .rubocop-cache: &rubocop-cache - key: "rubocop-${DEBIAN_VERSION}" + key: "rubocop-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}" paths: - tmp/rubocop_cache/ policy: pull @@ -116,6 +116,7 @@ .qa-ruby-gems-cache: &qa-ruby-gems-cache key: + prefix: "qa-ruby-gems-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}" files: - qa/Gemfile.lock paths: diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index cdead1fb491..5ca70da352a 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -92,7 +92,7 @@ populate-qa-tests-var: - detect-tests .package-and-qa-base: - image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine + image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION}-alpine stage: qa retry: 0 before_script: diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 77bdfda3eac..0358fe8ec49 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -395,15 +395,15 @@ db:migrate-from-previous-major-version: USE_BUNDLE_INSTALL: "false" SETUP_DB: "false" PROJECT_TO_CHECKOUT: "gitlab-foss" - TAG_TO_CHECKOUT: "v13.12.9" + TAG_TO_CHECKOUT: "v14.10.2" before_script: - !reference [.default-before_script, before_script] - '[[ -d "ee/" ]] || export PROJECT_TO_CHECKOUT="gitlab"' - '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="${TAG_TO_CHECKOUT}-ee"' - retry 'git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT' - git checkout -f FETCH_HEAD - - SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh - - run_timed_command "bundle exec rake db:drop db:create db:structure:load db:migrate db:seed_fu" + - SETUP_DB=false USE_BUNDLE_INSTALL=true ENABLE_BOOTSNAP=false bash scripts/prepare_build.sh + - run_timed_command "ENABLE_BOOTSNAP=false bundle exec rake db:drop db:create db:structure:load db:migrate db:seed_fu" - git checkout -f $CI_COMMIT_SHA - SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh script: @@ -419,7 +419,7 @@ db:migrate-from-previous-major-version-single-db: extends: - .rails:rules:ee-mr-and-default-branch-only variables: - TAG_TO_CHECKOUT: "v14.4.0" + TAG_TO_CHECKOUT: "v14.7.0" # this version updated grpc to 1.42.0, which supports Ruby 2 & 3 script: - run_timed_command "scripts/db_tasks db:migrate" - scripts/schema_changed.sh @@ -460,7 +460,7 @@ db:migrate-non-superuser: db:gitlabcom-database-testing: extends: .rails:rules:db:gitlabcom-database-testing stage: test - image: ruby:2.7-alpine + image: ruby:${RUBY_VERSION}-alpine needs: [] allow_failure: true script: @@ -976,7 +976,6 @@ rspec system pg13: - .rspec-base-pg13 - .rails:rules:default-branch-schedule-nightly--code-backstage - .rspec-system-parallel - # EE/FOSS: default branch nightly scheduled jobs # ########################################## diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 22fdce71243..f3cde5d7318 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -20,7 +20,7 @@ review-build-cng-env: extends: - .default-retry - .review:rules:review-build-cng - image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine3.13 + image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION}-alpine3.13 stage: prepare needs: [] before_script: diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml index eeaa9ddb41a..505caeec837 100644 --- a/.gitlab/ci/setup.gitlab-ci.yml +++ b/.gitlab/ci/setup.gitlab-ci.yml @@ -60,7 +60,7 @@ no-jh-check: verify-tests-yml: extends: - .setup:rules:verify-tests-yml - image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine3.13 + image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION}-alpine3.13 stage: test needs: [] script: @@ -96,7 +96,7 @@ generate-frontend-fixtures-mapping: - ${FRONTEND_FIXTURES_MAPPING_PATH} .detect-test-base: - image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7 + image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION} needs: [] stage: prepare script: @@ -160,7 +160,7 @@ detect-previous-failed-tests: add-jh-folder: extends: .setup:rules:add-jh-folder - image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7 + image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION} stage: prepare before_script: - source ./scripts/utils.sh diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml index 79fea15690c..f4fa39300b6 100644 --- a/.gitlab/ci/test-metadata.gitlab-ci.yml +++ b/.gitlab/ci/test-metadata.gitlab-ci.yml @@ -1,5 +1,5 @@ .tests-metadata-state: - image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7 + image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION} before_script: - source scripts/utils.sh artifacts: diff --git a/.gitlab/ci/workhorse.gitlab-ci.yml b/.gitlab/ci/workhorse.gitlab-ci.yml index 15be0158da7..6db3582bdab 100644 --- a/.gitlab/ci/workhorse.gitlab-ci.yml +++ b/.gitlab/ci/workhorse.gitlab-ci.yml @@ -22,4 +22,4 @@ workhorse:verify: workhorse:test using go 1.17: extends: .workhorse:test - image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:debian-${DEBIAN_VERSION}-ruby-2.7-golang-1.17-git-2.31 + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-golang-1.17-git-2.31 diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 0c336b78456..2f000905d26 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -9f87cdc737619e33cddc3ceddc2103dc11dd2577 +13086e25394b65e4c17eca8484890f62bb2f0b92 diff --git a/app/assets/javascripts/environments/components/environment_folder.vue b/app/assets/javascripts/environments/components/environment_folder.vue index 788c3ba6fed..881f404340d 100644 --- a/app/assets/javascripts/environments/components/environment_folder.vue +++ b/app/assets/javascripts/environments/components/environment_folder.vue @@ -47,8 +47,8 @@ export default { computed: { icons() { return this.visible - ? { caret: 'angle-down', folder: 'folder-open' } - : { caret: 'angle-right', folder: 'folder-o' }; + ? { caret: 'chevron-lg-down', folder: 'folder-open' } + : { caret: 'chevron-lg-right', folder: 'folder-o' }; }, label() { return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand; diff --git a/app/assets/javascripts/lib/utils/rails_ujs.js b/app/assets/javascripts/lib/utils/rails_ujs.js index b4f425da871..48f1b32526f 100644 --- a/app/assets/javascripts/lib/utils/rails_ujs.js +++ b/app/assets/javascripts/lib/utils/rails_ujs.js @@ -37,9 +37,7 @@ function monkeyPatchConfirmModal() { Rails.confirm = confirmViaModal; } -if (gon?.features?.bootstrapConfirmationModals) { - monkeyPatchConfirmModal(); -} +monkeyPatchConfirmModal(); export const initRails = () => { // eslint-disable-next-line no-underscore-dangle diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue index 8089af72322..1c89476ea34 100644 --- a/app/assets/javascripts/work_items/components/work_item_assignees.vue +++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue @@ -1,42 +1,118 @@ + + diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 02bd1cbb1e8..afcf28416e9 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -133,7 +133,11 @@ export default { /> { + const assigneesWidget = draftData.workItem.mockWidgets.find( + (widget) => widget.type === WIDGET_TYPE_ASSIGNEE, + ); + assigneesWidget.nodes = assigneesWidget.nodes.filter((assignee) => + input.assigneeIds.includes(assignee.id), + ); + }); + + cache.writeQuery({ + query: workItemQuery, + variables: { id: input.id }, + data, + }); + }, + }, +}; + export function createApolloProvider() { Vue.use(VueApollo); - const defaultClient = createDefaultClient({}, temporaryConfig); + const defaultClient = createDefaultClient(resolvers, temporaryConfig); return new VueApollo({ defaultClient, diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql index 68a0986f411..bfe2f0fe0ce 100644 --- a/app/assets/javascripts/work_items/graphql/typedefs.graphql +++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql @@ -20,3 +20,17 @@ type LocalWorkItemWeight implements LocalWorkItemWidget { extend type WorkItem { mockWidgets: [LocalWorkItemWidget] } + +type LocalWorkItemAssigneesInput { + id: WorkItemID! + assigneeIds: [ID!] +} + +type LocalWorkItemPayload { + workItem: WorkItem! + errors: [String!] +} + +extend type Mutation { + localUpdateWorkItem(input: LocalWorkItemAssigneesInput!): LocalWorkItemPayload +} diff --git a/app/models/error_tracking/error_event.rb b/app/models/error_tracking/error_event.rb index 18c1467e6f6..3ee82b219dc 100644 --- a/app/models/error_tracking/error_event.rb +++ b/app/models/error_tracking/error_event.rb @@ -15,7 +15,7 @@ class ErrorTracking::ErrorEvent < ApplicationRecord validates :occurred_at, presence: true def stacktrace - @stacktrace ||= build_stacktrace + @stacktrace ||= ErrorTracking::StacktraceBuilder.new(payload).stacktrace end # For compatibility with sentry integration @@ -30,56 +30,4 @@ class ErrorTracking::ErrorEvent < ApplicationRecord def release payload.dig('release') end - - private - - def build_stacktrace - raw_stacktrace = find_stacktrace_from_payload - - return [] unless raw_stacktrace - - raw_stacktrace.map do |entry| - { - 'lineNo' => entry['lineno'], - 'context' => build_stacktrace_context(entry), - 'filename' => entry['filename'], - 'function' => entry['function'], - 'colNo' => 0 # we don't support colNo yet. - } - end - end - - def find_stacktrace_from_payload - exception_entry = payload.dig('exception') - - if exception_entry - exception_values = exception_entry.dig('values') - stack_trace_entry = exception_values&.detect { |h| h['stacktrace'].present? } - stack_trace_entry&.dig('stacktrace', 'frames') - end - end - - def build_stacktrace_context(entry) - context = [] - error_line = entry['context_line'] - error_line_no = entry['lineno'] - pre_context = entry['pre_context'] - post_context = entry['post_context'] - - context += lines_with_position(pre_context, error_line_no - pre_context.size) if pre_context - context += lines_with_position([error_line], error_line_no) - context += lines_with_position(post_context, error_line_no + 1) if post_context - - context.reject(&:blank?) - end - - def lines_with_position(lines, position) - return [] if lines.blank? - - lines.map.with_index do |line, index| - next unless line - - [position + index, line] - end - end end diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml deleted file mode 100644 index 70c02ed4e99..00000000000 --- a/app/views/projects/blob/_upload.html.haml +++ /dev/null @@ -1,30 +0,0 @@ -#modal-upload-blob.modal - .modal-dialog.modal-lg - .modal-content - .modal-header - %h1.page-title.gl-font-size-h-display= title - %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') } - %span{ "aria-hidden": "true" } × - .modal-body - = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form', data: { method: method } do - .dropzone - .dropzone-previews.blob-upload-dropzone-previews - %p.dz-message.light - - upload_link = link_to s_('UploadLink|click to upload'), '#', class: "markdown-selector" - - dropzone_text = _('Attach a file by drag & drop or %{upload_link}') % { upload_link: upload_link } - #{ dropzone_text.html_safe } - - %br - = render Pajamas::AlertComponent.new(variant: :danger, - alert_options: { class: 'dropzone-alerts gl-alert gl-alert-danger gl-mb-5 data gl-display-none' }, - dismissible: false) - - = render 'shared/new_commit_form', placeholder: placeholder, ref: local_assigns[:ref] - - .form-actions - = button_tag class: 'btn gl-button btn-confirm btn-upload-file gl-mr-2', id: 'submit-all', type: 'button' do - = gl_loading_icon(inline: true, css_class: 'gl-mr-2 js-loading-icon hidden') - = button_title - = link_to _("Cancel"), '#', class: "btn gl-button btn-default btn-cancel", "data-dismiss" => "modal" - - = render 'shared/projects/edit_information' diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index d4e7ee90a84..a91c0d63b00 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -14,8 +14,5 @@ - if can_modify_blob?(@blob) = render 'projects/blob/remove' - - title = _("Replace %{blob_name}") % { blob_name: @blob.name } - = render 'projects/blob/upload', title: title, placeholder: title, button_title: _('Replace file'), form_path: project_update_blob_path(@project, @id), method: :put - = render partial: 'pipeline_tour_success' if show_suggest_pipeline_creation_celebration? = render 'shared/web_ide_path' diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 47d2b5980af..ce6d021ce2f 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -76,6 +76,3 @@ %span>< git push -u origin --all git push -u origin --tags - -- if @project.upload_anchor_data.present? - = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, default_branch_name), ref: default_branch_name, method: :post diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml index d3951c91329..d29030f992f 100644 --- a/app/views/projects/pipeline_schedules/_form.html.haml +++ b/app/views/projects/pipeline_schedules/_form.html.haml @@ -28,7 +28,7 @@ = render 'ci/variables/variable_row', form_field: 'schedule', variable: variable = render 'ci/variables/variable_row', form_field: 'schedule' - if @schedule.variables.size > 0 - %button.gl-button.btn.btn-confirm-secondary.gl-mt-3.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@schedule.variables.size == 0}" } } + = render Pajamas::ButtonComponent.new(category: :secondary, variant: :confirm, button_options: { class: 'gl-mt-3 js-secret-value-reveal-button', data: { secret_reveal_status: "#{@schedule.variables.size == 0}" }}) do - if @schedule.variables.size == 0 = n_('Hide value', 'Hide values', @schedule.variables.size) - else diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 4d49aa2c344..af5657e0e14 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -12,11 +12,12 @@ - if can?(current_user, :admin_label, @project) %li.gl-display-inline-block.js-toggle-priority.gl-ml-3{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } - %button.add-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip{ title: _('Prioritize'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Prioritize label') } - = sprite_icon('star-o') = render Pajamas::ButtonComponent.new(category: :tertiary, icon: 'star', button_options: { class: 'remove-priority has-tooltip', 'title': _('Remove priority'), 'aria_label': _('Deprioritize label'), data: { placement: 'bottom' } }) + = render Pajamas::ButtonComponent.new(category: :tertiary, + icon: 'star-o', + button_options: { class: 'add-priority has-tooltip', title: _('Prioritize'), aria_label: _('Prioritize label'), data: { placement: 'bottom' } }) - if can?(current_user, :admin_label, label) %li.gl-display-inline-block = render Pajamas::ButtonComponent.new(href: label.edit_path, category: :tertiary, icon: 'pencil', button_options: { class: 'edit has-tooltip', 'title': _('Edit'), 'aria_label': _('Edit'), data: { placement: 'bottom' } }) diff --git a/app/views/shared/promotions/_promote_servicedesk.html.haml b/app/views/shared/promotions/_promote_servicedesk.html.haml index a2da23de2b9..57ac1370f8d 100644 --- a/app/views/shared/promotions/_promote_servicedesk.html.haml +++ b/app/views/shared/promotions/_promote_servicedesk.html.haml @@ -1,11 +1,8 @@ -.user-callout.promotion-callout.js-service-desk-callout#promote_service_desk{ data: { uid: 'promote_service_desk_dismissed' } } - .bordered-box.content-block - %button.gl-button.btn.btn-default.close.js-close-callout{ type: 'button', 'aria-label' => 'Dismiss Service Desk promotion' } - = sprite_icon('close', size: 16, css_class: 'dismiss-icon') - .svg-container - = custom_icon('icon_service_desk') - .user-callout-copy - %h4 - = _("Improve customer support with Service Desk") - %p - = _("Service Desk allows people to create issues in your GitLab instance without their own user account. It provides a unique email address for end users to create issues in a project. Replies can be sent either through the GitLab interface or by email. End users only see threads through email.") += render Pajamas::BannerComponent.new(banner_options: {class: 'js-service-desk-callout', data: {uid: 'promote_service_desk_dismissed'}, id: 'promote_service_desk'}, + close_options: {'aria-label' => s_('Promotions|Dismiss Service Desk promotion'), class: 'js-close-callout'}, + svg_path: 'illustrations/service_desk_callout.svg', + button_text: s_('Promotions|Configure Service Desk'), button_link: help_page_path('user/project/service_desk.html', anchor: 'configuring-service-desk')) do |c| + - c.title do + = _('Improve customer support with Service Desk') + %p + = _('Service Desk allows people to create issues in your GitLab instance without their own user account. It provides a unique email address for end users to create issues in a project. Replies can be sent either through the GitLab interface or by email. End users only see threads through email.') diff --git a/config/feature_flags/development/bootstrap_confirmation_modals.yml b/config/feature_flags/development/active_support_hash_digest_sha256.yml similarity index 62% rename from config/feature_flags/development/bootstrap_confirmation_modals.yml rename to config/feature_flags/development/active_support_hash_digest_sha256.yml index e67fd03fea6..147b84bf112 100644 --- a/config/feature_flags/development/bootstrap_confirmation_modals.yml +++ b/config/feature_flags/development/active_support_hash_digest_sha256.yml @@ -1,8 +1,8 @@ --- -name: bootstrap_confirmation_modals -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73167 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344658 -milestone: '14.5' +name: active_support_hash_digest_sha256 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90098 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365314 +milestone: '15.1' type: development -group: group::foundations +group: group::sharding default_enabled: false diff --git a/config/feature_flags/development/rename_integrations_workers.yml b/config/feature_flags/development/rename_integrations_workers.yml index 91f8f7f7166..307b21c0545 100644 --- a/config/feature_flags/development/rename_integrations_workers.yml +++ b/config/feature_flags/development/rename_integrations_workers.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364112 milestone: '15.1' type: development group: group::integrations -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/update_vuln_identifiers_flag.yml b/config/feature_flags/development/update_vuln_identifiers_flag.yml new file mode 100644 index 00000000000..62fdc08ce34 --- /dev/null +++ b/config/feature_flags/development/update_vuln_identifiers_flag.yml @@ -0,0 +1,8 @@ +--- +name: update_vuln_identifiers_flag +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82538 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362179 +milestone: '15.1' +type: development +group: group::static analysis +default_enabled: false diff --git a/config/initializers/set_active_support_hash_digest_class.rb b/config/initializers/set_active_support_hash_digest_class.rb new file mode 100644 index 00000000000..743b45eed34 --- /dev/null +++ b/config/initializers/set_active_support_hash_digest_class.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +Rails.application.configure do + # We set ActiveSupport::Digest.hash_digest_class directly copying + # See https://github.com/rails/rails/blob/6-1-stable/activesupport/lib/active_support/railtie.rb#L96-L98 + # + # Note that is the only usage of config.active_support.hash_digest_class + config.after_initialize do + ActiveSupport::Digest.hash_digest_class = Gitlab::HashDigest::Facade + end +end diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb index 6d31007b320..24500aa3e7d 100644 --- a/db/fixtures/development/12_snippets.rb +++ b/db/fixtures/development/12_snippets.rb @@ -35,6 +35,8 @@ Gitlab::Seeder.quiet do visibility_level: Gitlab::VisibilityLevel.values.sample, content: 'foo' }).tap do |snippet| + snippet.repository.expire_exists_cache + unless snippet.repository_exists? Gitlab::Seeder::SnippetRepository.new(snippet).import end @@ -48,4 +50,3 @@ Gitlab::Seeder.quiet do Gitlab::Seeder::SnippetRepository.cleanup end - diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index 6d7121d32fe..442f743f2c7 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -117,6 +117,12 @@ From there, you can see the following actions: - Failed attempt to create a group deploy token. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353452) in GitLab 14.9. - [IP restrictions](../user/group/index.md#group-access-restriction-by-ip-address) changed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/358986) in GitLab 15.0. - Changes to push rules. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227629) in GitLab 15.0. +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356152) in GitLab 15.1, changes to the following merge request approvals settings: + - Prevent approval by author. + - Prevent approvals by users who add commits. + - Prevent editing approval rules in projects and merge requests. + - Require user password to approve. + - Remove all approvals when commits are added to the source branch. Group events can also be accessed via the [Group Audit Events API](../api/audit_events.md#group-audit-events) diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index 8cf148e095d..ae837a00633 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -277,6 +277,12 @@ In the event of an emergency, or false positive from this job, add the `pipeline:skip-undercoverage` label to the merge request to allow this job to fail. +## Ruby versions testing + +Our test suite runs against Ruby 2 in merge requests and default branch pipelines. + +We do run our test suite against Ruby 3 on 2-hourly scheduled pipelines, as GitLab.com will soon run on Ruby 3. + ## PostgreSQL versions testing Our test suite runs against PG12 as GitLab.com runs on PG12 and diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index 657ceb44596..8b332f96be0 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -37,7 +37,7 @@ module API latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name]) authorize_read_job_artifacts!(latest_build) - present_carrierwave_file!(latest_build.artifacts_file) + present_artifacts_file!(latest_build.artifacts_file) end desc 'Download a specific file from artifacts archive from a ref' do @@ -78,7 +78,7 @@ module API build = find_build!(params[:job_id]) authorize_read_job_artifacts!(build) - present_carrierwave_file!(build.artifacts_file) + present_artifacts_file!(build.artifacts_file) end desc 'Download a specific file from artifacts archive' do diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 4381309fb9e..65dc002e67d 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -330,7 +330,7 @@ module API authenticate_job!(require_running: false) end - present_carrierwave_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download]) + present_artifacts_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download]) end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index e31748a993e..fc1037131d8 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -570,11 +570,19 @@ module API end end + def log_artifact_size(file) + Gitlab::ApplicationContext.push(artifact: file.model) + end + + def present_artifacts_file!(file, **args) + log_artifact_size(file) if file + + present_carrierwave_file!(file, **args) + end + def present_carrierwave_file!(file, supports_direct_download: true) return not_found! unless file&.exists? - log_artifact_size(file) if file.is_a?(JobArtifactUploader) - if file.file_storage? present_disk_file!(file.path, file.filename) elsif supports_direct_download && file.class.direct_download_enabled? @@ -725,7 +733,6 @@ module API # Deprecated. Use `send_artifacts_entry` instead. def legacy_send_artifacts_entry(file, entry) header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) - log_artifact_size(file) body '' end @@ -733,15 +740,10 @@ module API def send_artifacts_entry(file, entry) header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) header(*Gitlab::Workhorse.detect_content_type) - log_artifact_size(file) body '' end - def log_artifact_size(file) - Gitlab::ApplicationContext.push(artifact: file.model) - end - # The Grape Error Middleware only has access to `env` but not `params` nor # `request`. We workaround this by defining methods that returns the right # values. diff --git a/lib/error_tracking/stacktrace_builder.rb b/lib/error_tracking/stacktrace_builder.rb new file mode 100644 index 00000000000..4f331bc4e06 --- /dev/null +++ b/lib/error_tracking/stacktrace_builder.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ErrorTracking + class StacktraceBuilder + attr_reader :stacktrace + + def initialize(payload) + @stacktrace = build_stacktrace(payload) + end + + private + + def build_stacktrace(payload) + raw_stacktrace = raw_stacktrace_from_payload(payload) + return [] unless raw_stacktrace + + raw_stacktrace.map do |entry| + { + 'lineNo' => entry['lineno'], + 'context' => build_stacktrace_context(entry), + 'filename' => entry['filename'], + 'function' => entry['function'], + 'colNo' => 0 # we don't support colNo yet. + } + end + end + + def raw_stacktrace_from_payload(payload) + exception_entry = payload['exception'] + return unless exception_entry + + exception_values = exception_entry['values'] + stack_trace_entry = exception_values&.detect { |h| h['stacktrace'].present? } + stack_trace_entry&.dig('stacktrace', 'frames') + end + + def build_stacktrace_context(entry) + error_line = entry['context_line'] + error_line_no = entry['lineno'] + pre_context = entry['pre_context'] + post_context = entry['post_context'] + + context = [] + context.concat lines_with_position(pre_context, error_line_no - pre_context.size) if pre_context + context.concat lines_with_position([error_line], error_line_no) + context.concat lines_with_position(post_context, error_line_no + 1) if post_context + + context.reject(&:blank?) + end + + def lines_with_position(lines, position) + return [] if lines.blank? + + lines.map.with_index do |line, index| + next unless line + + [position + index, line] + end + end + end +end diff --git a/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml index 49bdd4b7713..6f9a9c5133c 100644 --- a/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml @@ -4,7 +4,7 @@ # they are able to only include the jobs that they find interesting. # # Therefore, this template is not supposed to run any jobs. The idea is to only -# create hidden jobs. See: https://docs.gitlab.com/ee/ci/yaml/#hide-jobs +# create hidden jobs. See: https://docs.gitlab.com/ee/ci/jobs/#hide-jobs # # There is a more opinionated template which we suggest the users to abide, # which is the lib/gitlab/ci/templates/Terraform.gitlab-ci.yml diff --git a/lib/gitlab/diff/rendered/notebook/diff_file.rb b/lib/gitlab/diff/rendered/notebook/diff_file.rb index 99631d90ce6..0795e0acebb 100644 --- a/lib/gitlab/diff/rendered/notebook/diff_file.rb +++ b/lib/gitlab/diff/rendered/notebook/diff_file.rb @@ -50,12 +50,14 @@ module Gitlab end def highlighted_diff_lines - @highlighted_diff_lines ||= begin - removal_line_maps, addition_line_maps = map_diff_block_to_source_line( - source_diff.highlighted_diff_lines, source_diff.new_file?, source_diff.deleted_file?) - Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight.map do |line| - mutate_line(line, addition_line_maps, removal_line_maps) - end + strong_memoize(:highlighted_diff_lines) do + lines = Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight + lines_in_source = lines_in_source_diff( + source_diff.highlighted_diff_lines, source_diff.deleted_file?, source_diff.new_file? + ) + + lines.zip(line_positions_at_source_diff(lines, transformed_blocks)) + .map { |line, positions| mutate_line(line, positions, lines_in_source)} end end @@ -91,6 +93,10 @@ module Gitlab diff end + def transformed_blocks + { from: notebook_diff.from.blocks, to: notebook_diff.to.blocks } + end + def rendered_timeout @rendered_timeout ||= Gitlab::Metrics.counter( :ipynb_semantic_diff_timeouts_total, @@ -108,21 +114,13 @@ module Gitlab nil end - def compute_line_numbers(transformed_old_pos, transformed_new_pos, addition_line_maps, removal_line_maps) - new_pos = map_transformed_line_to_source(transformed_new_pos, notebook_diff.to.blocks) - old_pos = map_transformed_line_to_source(transformed_old_pos, notebook_diff.from.blocks) - - old_pos = addition_line_maps[new_pos] if old_pos == 0 && new_pos != 0 - new_pos = removal_line_maps[old_pos] if new_pos == 0 && old_pos != 0 - - [old_pos, new_pos] - end - - def mutate_line(line, addition_line_maps, removal_line_maps) - line.old_pos, line.new_pos = compute_line_numbers(line.old_pos, line.new_pos, addition_line_maps, removal_line_maps) + def mutate_line(line, mapped_positions, source_diff_lines) + line.old_pos, line.new_pos = mapped_positions # Lines that do not appear on the original diff should not be commentable - line.type = "#{line.type || 'unchanged'}-nomappinginraw" unless addition_line_maps[line.new_pos] || removal_line_maps[line.old_pos] + unless source_diff_lines[:to].include?(line.new_pos) || source_diff_lines[:from].include?(line.old_pos) + line.type = "#{line.type || 'unchanged'}-nomappinginraw" + end line.line_code = line_code(line) diff --git a/lib/gitlab/diff/rendered/notebook/diff_file_helper.rb b/lib/gitlab/diff/rendered/notebook/diff_file_helper.rb index 822b3771276..2e1b5ea301d 100644 --- a/lib/gitlab/diff/rendered/notebook/diff_file_helper.rb +++ b/lib/gitlab/diff/rendered/notebook/diff_file_helper.rb @@ -4,75 +4,94 @@ module Gitlab module Rendered module Notebook module DiffFileHelper + require 'set' + EMBEDDED_IMAGE_PATTERN = ' ![](data:image' def strip_diff_frontmatter(diff_content) diff_content.scan(/.*\n/)[2..]&.join('') if diff_content.present? end - def map_transformed_line_to_source(transformed_line, transformed_blocks) - transformed_blocks.empty? ? 0 : ( transformed_blocks[transformed_line - 1][:source_line] || -1 ) + 1 + # line_positions_at_source_diff: given the transformed lines, + # what are the correct values for old_pos and new_pos? + # + # Example: + # + # Original + # from | to + # A | A + # B | D + # C | E + # F | F + # + # Original Diff + # A A + # - B + # - C + # + D + # + E + # F F + # + # Transformed + # from | to + # A | A + # C | D + # B | J + # L | E + # K | K + # F | F + # + # Transformed diff | transf old, new | OG old_pos, new_pos | + # A A | 1, 1 | 1, 1 | + # -C | 2, 2 | 3, 2 | + # -B | 3, 2 | 2, 2 | + # -L | 4, 2 | 0, 0 | + # + D | 5, 2 | 4, 2 | + # + J | 5, 3 | 0, 0 | + # + E | 5, 4 | 4, 3 | + # K K | 5, 5 | 0, 0 | + # F F | 6, 6 | 4, 4 | + def line_positions_at_source_diff(lines, blocks) + last_mapped_old_pos = 0 + last_mapped_new_pos = 0 + + lines.reverse_each.map do |line| + old_pos = source_line_from_block(line.old_pos, blocks[:from]) + new_pos = source_line_from_block(line.new_pos, blocks[:to]) + + old_has_no_mapping = old_pos == 0 + new_has_no_mapping = new_pos == 0 + + next [0, 0] if old_has_no_mapping && (new_has_no_mapping || line.type == 'old') + next [0, 0] if new_has_no_mapping && line.type == 'new' + + new_pos = last_mapped_new_pos if new_has_no_mapping && line.type == 'old' + old_pos = last_mapped_old_pos if old_has_no_mapping && line.type == 'new' + + last_mapped_old_pos = old_pos + last_mapped_new_pos = new_pos + + [old_pos, new_pos] + end.reverse end - # line_codes are used for assigning notes to diffs, and these depend on the line on the new version and the - # line that would have been that one in the previous version. However, since we do a transformation on the - # file, that mapping gets lost. To overcome this, we look at the original source lines and build two maps: - # - For additions, we look at the latest line change for that line and pick the old line for that id - # - For removals, we look at the first line in the old version, and pick the first line on the new version - # - # Note: ipynb files never change the first or last line (open and closure of the - # json object), unless the file is removed or deleted - # - # Example: Additions and removals - # Old: New: - # A A - # B D - # C E - # F F - # - # Diff: - # 1 A A 1 | line code: 1_1 - # 2 -B | line code: 2_2 -> new line is what it is after been without the removal, 2 - # 3 -C | line code: 3_2 - # + D 2 | line code: 4_2 -> old line is what would have been before the addition, 4 - # + E 3 | line code: 4_3 - # 4 F F 4 | line code: 4_4 - # - # Example: only additions - # Old: New: - # A A - # F B - # C - # F - # - # Diff: - # A A | line code: 1_1 - # + B | line code: 2_2 -> old line is the next after the additions, 2 - # + C | line code: 2_3 - # F F | line code: 2_4 - # - # Example: only removals - # Old: New: - # A A - # B F - # C - # F - # - # Diff: - # A A | line code: 1_1 - # -B | line code: 2_2 -> new line is what it is after been without the removal, 2 - # -C | line code: 3_2 - # F F | line code: 4_2 - def map_diff_block_to_source_line(lines, file_added, file_deleted) - removals = {} - additions = {} + def lines_in_source_diff(source_diff_lines, is_deleted_file, is_added_file) + { + from: is_added_file ? Set[] : source_diff_lines.map {|l| l.old_pos}.to_set, + to: is_deleted_file ? Set[] : source_diff_lines.map {|l| l.new_pos}.to_set + } + end - lines.each do |line| - removals[line.old_pos] = line.new_pos unless file_added - additions[line.new_pos] = line.old_pos unless file_deleted - end + def source_line_from_block(transformed_line, transformed_blocks) + # Blocks are the lines returned from the library and are a hash with {text:, source_line:} + # Blocks source_line are 0 indexed + return 0 if transformed_blocks.empty? - [removals, additions] + line_in_source = transformed_blocks[transformed_line - 1][:source_line] + + return 0 unless line_in_source.present? + + line_in_source + 1 end def image_as_rich_text(line_text) diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 13f1937a937..5f1802e323c 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -54,7 +54,6 @@ module Gitlab push_frontend_feature_flag(:usage_data_api, type: :ops) push_frontend_feature_flag(:security_auto_fix) push_frontend_feature_flag(:new_header_search) - push_frontend_feature_flag(:bootstrap_confirmation_modals) push_frontend_feature_flag(:source_editor_toolbar) push_frontend_feature_flag(:gl_avatar_for_all_user_avatars) push_frontend_feature_flag(:mr_attention_requests, current_user) diff --git a/lib/gitlab/hash_digest/facade.rb b/lib/gitlab/hash_digest/facade.rb new file mode 100644 index 00000000000..d8efef02893 --- /dev/null +++ b/lib/gitlab/hash_digest/facade.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Gitlab + module HashDigest + # Used for rolling out to use OpenSSL::Digest::SHA256 + # for ActiveSupport::Digest + class Facade + class << self + def hexdigest(...) + hash_digest_class.hexdigest(...) + end + + def hash_digest_class + if use_sha256? + ::OpenSSL::Digest::SHA256 + else + ::Digest::MD5 # rubocop:disable Fips/MD5 + end + end + + def use_sha256? + return false unless Feature.feature_flags_available? + + Feature.enabled?(:active_support_hash_digest_sha256) + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2dec849750a..973bf879c29 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2154,6 +2154,9 @@ msgstr "" msgid "Add approvers" msgstr "" +msgid "Add assignees" +msgstr "" + msgid "Add attention request" msgstr "" @@ -5159,9 +5162,6 @@ msgstr "" msgid "Attach a file" msgstr "" -msgid "Attach a file by drag & drop or %{upload_link}" -msgstr "" - msgid "Attaching File - %{progress}" msgstr "" @@ -30620,6 +30620,9 @@ msgstr "" msgid "Promotions|Buy GitLab Enterprise Edition" msgstr "" +msgid "Promotions|Configure Service Desk" +msgstr "" + msgid "Promotions|Contact an owner of group %{namespace_name} to upgrade the plan." msgstr "" @@ -30632,6 +30635,9 @@ msgstr "" msgid "Promotions|Description templates allow you to define context-specific templates for issue and merge request description fields for your project." msgstr "" +msgid "Promotions|Dismiss Service Desk promotion" +msgstr "" + msgid "Promotions|Dismiss burndown charts promotion" msgstr "" @@ -31908,9 +31914,6 @@ msgstr "" msgid "Replace" msgstr "" -msgid "Replace %{blob_name}" -msgstr "" - msgid "Replace %{name}" msgstr "" @@ -40852,9 +40855,6 @@ msgstr "" msgid "Upload object map" msgstr "" -msgid "UploadLink|click to upload" -msgstr "" - msgid "Uploaded" msgstr "" diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb index f65e85b4cb6..4463dbb1eb0 100644 --- a/spec/features/admin/admin_disables_two_factor_spec.rb +++ b/spec/features/admin/admin_disables_two_factor_spec.rb @@ -3,8 +3,9 @@ require 'spec_helper' RSpec.describe 'Admin disables 2FA for a user' do + include Spec::Support::Helpers::ModalHelpers + it 'successfully', :js do - stub_feature_flags(bootstrap_confirmation_modals: false) admin = create(:admin) sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) @@ -12,9 +13,11 @@ RSpec.describe 'Admin disables 2FA for a user' do edit_user(user) page.within('.two-factor-status') do - accept_confirm { click_link 'Disable' } + click_link 'Disable' end + accept_gl_confirm(button_text: 'Disable') + page.within('.two-factor-status') do expect(page).to have_content 'Disabled' expect(page).not_to have_button 'Disable' diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 388ab02d8e8..901315752d6 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Admin::Hooks' do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:admin) } before do @@ -79,7 +81,6 @@ RSpec.describe 'Admin::Hooks' do let(:hook_url) { generate(:url) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) create(:system_hook, url: hook_url) end @@ -87,7 +88,7 @@ RSpec.describe 'Admin::Hooks' do it 'from hooks list page' do visit admin_hooks_path - accept_confirm { click_link 'Delete' } + accept_gl_confirm(button_text: 'Delete webhook') { click_link 'Delete' } expect(page).not_to have_content(hook_url) end @@ -95,7 +96,7 @@ RSpec.describe 'Admin::Hooks' do visit admin_hooks_path click_link 'Edit' - accept_confirm { click_link 'Delete' } + accept_gl_confirm(button_text: 'Delete webhook') { click_link 'Delete' } expect(page).not_to have_content(hook_url) end end diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb index ba0870a53ae..fa5c94aa66e 100644 --- a/spec/features/admin/admin_labels_spec.rb +++ b/spec/features/admin/admin_labels_spec.rb @@ -16,7 +16,6 @@ RSpec.describe 'admin issues labels' do describe 'list' do before do - stub_feature_flags(bootstrap_confirmation_modals: false) visit admin_labels_path end @@ -38,11 +37,9 @@ RSpec.describe 'admin issues labels' do end it 'deletes all labels', :js do - page.within '.labels' do - page.all('.js-remove-label').each do |remove| - accept_confirm { remove.click } - wait_for_requests - end + page.all('.labels .js-remove-label').each do |remove| + accept_gl_confirm(button_text: 'Delete label') { remove.click } + wait_for_requests end wait_for_requests diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb index 15bc2318022..7e57cffc791 100644 --- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb +++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Admin > Users > Impersonation Tokens', :js do + include Spec::Support::Helpers::ModalHelpers + let(:admin) { create(:admin) } let!(:user) { create(:user) } @@ -74,10 +76,9 @@ RSpec.describe 'Admin > Users > Impersonation Tokens', :js do let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) } it "allows revocation of an active impersonation token" do - stub_feature_flags(bootstrap_confirmation_modals: false) visit admin_user_impersonation_tokens_path(user_id: user.username) - accept_confirm { click_on "Revoke" } + accept_gl_confirm(button_text: 'Revoke') { click_on "Revoke" } expect(page).to have_selector(".settings-message") expect(no_personal_access_tokens_message).to have_text("This user has no active impersonation tokens.") diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 4e6aae7c46f..2dffef93600 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -4,11 +4,11 @@ require 'spec_helper' RSpec.describe 'Admin uses repository checks', :request_store do include StubENV + include Spec::Support::Helpers::ModalHelpers let(:admin) { create(:admin) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') sign_in(admin) end @@ -57,7 +57,9 @@ RSpec.describe 'Admin uses repository checks', :request_store do expect(RepositoryCheck::ClearWorker).to receive(:perform_async) - accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) } + accept_gl_confirm(button_text: 'Clear repository checks') do + find(:link, 'Clear all repository checks').send_keys(:return) + end expect(page).to have_content('Started asynchronous removal of all repository check states.') end diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb index 7e8dee9cc0b..18bb03f4617 100644 --- a/spec/features/admin/users/user_spec.rb +++ b/spec/features/admin/users/user_spec.rb @@ -10,7 +10,6 @@ RSpec.describe 'Admin::Users::User' do let_it_be(:current_user) { create(:admin) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(current_user) gitlab_enable_admin_mode_sign_in(current_user) end @@ -354,7 +353,7 @@ RSpec.describe 'Admin::Users::User' do expect(page).to have_content("Secondary email: #{secondary_email.email}") - accept_confirm { find("#remove_email_#{secondary_email.id}").click } + accept_gl_confirm { find("#remove_email_#{secondary_email.id}").click } expect(page).not_to have_content(secondary_email.email) end diff --git a/spec/features/admin/users/users_spec.rb b/spec/features/admin/users/users_spec.rb index a59e3330628..e5df6cc0fd3 100644 --- a/spec/features/admin/users/users_spec.rb +++ b/spec/features/admin/users/users_spec.rb @@ -10,7 +10,6 @@ RSpec.describe 'Admin::Users' do let_it_be(:current_user) { create(:admin) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(current_user) gitlab_enable_admin_mode_sign_in(current_user) end @@ -504,8 +503,11 @@ RSpec.describe 'Admin::Users' do it 'allows group membership to be revoked', :js do page.within(first('.group_member')) do - accept_confirm { find('.btn[data-testid="remove-user"]').click } + find('.btn[data-testid="remove-user"]').click end + + accept_gl_confirm(button_text: 'Remove') + wait_for_requests expect(page).not_to have_selector('.group_member') diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index bf976168bbe..e8321adeb42 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -521,7 +521,6 @@ RSpec.describe 'Project issue boards', :js do let_it_be(:user_guest) { create(:user) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) project.add_guest(user_guest) sign_in(user_guest) visit project_board_path(project, board) diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index 50d5db46cee..66f251c859a 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -4,13 +4,13 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Leave group' do include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::ModalHelpers let(:user) { create(:user) } let(:other_user) { create(:user) } let(:group) { create(:group) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end @@ -32,7 +32,7 @@ RSpec.describe 'Groups > Members > Leave group' do visit group_path(group, leave: 1) - page.accept_confirm + accept_gl_confirm(button_text: 'Leave group') wait_for_all_requests expect(page).to have_current_path(dashboard_groups_path, ignore_query: true) diff --git a/spec/features/groups/settings/access_tokens_spec.rb b/spec/features/groups/settings/access_tokens_spec.rb index 20787c4c2f5..198d3a40df2 100644 --- a/spec/features/groups/settings/access_tokens_spec.rb +++ b/spec/features/groups/settings/access_tokens_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Group > Settings > Access Tokens', :js do + include Spec::Support::Helpers::ModalHelpers + let_it_be(:user) { create(:user) } let_it_be(:bot_user) { create(:user, :project_bot) } let_it_be(:group) { create(:group) } @@ -13,7 +15,6 @@ RSpec.describe 'Group > Settings > Access Tokens', :js do end before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb index 99756da51e4..06b29969775 100644 --- a/spec/features/merge_request/user_comments_on_diff_spec.rb +++ b/spec/features/merge_request/user_comments_on_diff_spec.rb @@ -14,7 +14,6 @@ RSpec.describe 'User comments on a diff', :js do let(:user) { create(:user) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb index 54eba13da49..8a310aba77b 100644 --- a/spec/features/merge_request/user_posts_diff_notes_spec.rb +++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb @@ -19,7 +19,6 @@ RSpec.describe 'Merge request > User posts diff notes', :js do project.add_developer(user) sign_in(user) - stub_feature_flags(bootstrap_confirmation_modals: false) stub_const('Gitlab::QueryLimiting::Transaction::THRESHOLD', 104) end diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index b0c52463b22..844ef6133c8 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -18,7 +18,6 @@ RSpec.describe 'Merge request > User posts notes', :js do end before do - stub_feature_flags(bootstrap_confirmation_modals: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb index fca40dc7edc..0e9ff98c3e1 100644 --- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb +++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb @@ -83,7 +83,6 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do %w(parallel).each do |view| context "#{view} view" do before do - stub_feature_flags(bootstrap_confirmation_modals: false) visit diffs_project_merge_request_path(project, merge_request, view: view) wait_for_requests diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb index 9f1f0d97b09..81034caaee2 100644 --- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb +++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb @@ -115,7 +115,6 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do end before do - stub_feature_flags(bootstrap_confirmation_modals: false) build.success! deployment.update!(on_stop: manual.name) visit project_merge_request_path(project, merge_request) diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 36657406303..1013937ebb9 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -3,10 +3,11 @@ require 'spec_helper' RSpec.describe 'Profile account page', :js do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:user) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end @@ -65,11 +66,17 @@ RSpec.describe 'Profile account page', :js do it 'allows resetting of feed token' do visit profile_personal_access_tokens_path + previous_token = '' + within('[data-testid="feed-token-container"]') do previous_token = find_field('Feed token').value - accept_confirm { click_link('reset this token') } + click_link('reset this token') + end + accept_gl_confirm + + within('[data-testid="feed-token-container"]') do click_button('Click to reveal') expect(find_field('Feed token').value).not_to eq(previous_token) @@ -81,11 +88,17 @@ RSpec.describe 'Profile account page', :js do visit profile_personal_access_tokens_path + previous_token = '' + within('[data-testid="incoming-email-token-container"]') do previous_token = find_field('Incoming email token').value - accept_confirm { click_link('reset this token') } + click_link('reset this token') + end + accept_gl_confirm + + within('[data-testid="incoming-email-token-container"]') do click_button('Click to reveal') expect(find_field('Incoming email token').value).not_to eq(previous_token) diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb index a515c7b1c1f..24c9225532b 100644 --- a/spec/features/profiles/active_sessions_spec.rb +++ b/spec/features/profiles/active_sessions_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do + include Spec::Support::Helpers::ModalHelpers + let(:user) do create(:user).tap do |user| user.current_sign_in_at = Time.current @@ -11,10 +13,6 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do let(:admin) { create(:admin) } - before do - stub_feature_flags(bootstrap_confirmation_modals: false) - end - it 'user sees their active sessions' do travel_to(Time.zone.parse('2018-03-12 09:06')) do Capybara::Session.new(:session1) @@ -101,7 +99,9 @@ RSpec.describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do expect(page).to have_link('Revoke', count: 1) - accept_confirm { click_on 'Revoke' } + accept_gl_confirm(button_text: 'Revoke') do + click_on 'Revoke' + end expect(page).not_to have_link('Revoke') end diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb index 9d79041dc9d..ee1daf69f62 100644 --- a/spec/features/profiles/oauth_applications_spec.rb +++ b/spec/features/profiles/oauth_applications_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' RSpec.describe 'Profile > Applications' do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:user) } let(:application) { create(:oauth_application, owner: user) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end @@ -25,9 +26,11 @@ RSpec.describe 'Profile > Applications' do page.within('.oauth-applications') do expect(page).to have_content('Your applications (1)') - accept_confirm { click_button 'Destroy' } + click_button 'Destroy' end + accept_gl_confirm(button_text: 'Destroy') + expect(page).to have_content('The application was deleted successfully') expect(page).to have_content('Your applications (0)') expect(page).to have_content('Authorized applications (0)') @@ -39,9 +42,11 @@ RSpec.describe 'Profile > Applications' do page.within('.oauth-authorized-applications') do expect(page).to have_content('Authorized applications (1)') - accept_confirm { click_button 'Revoke' } + click_button 'Revoke' end + accept_gl_confirm(button_text: 'Revoke application') + expect(page).to have_content('The application was revoked access.') expect(page).to have_content('Your applications (0)') expect(page).to have_content('Authorized applications (0)') diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb index 80b828313e3..bca1bc4df4d 100644 --- a/spec/features/profiles/personal_access_tokens_spec.rb +++ b/spec/features/profiles/personal_access_tokens_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Profile > Personal Access Tokens', :js do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:user) } let(:pat_create_service) { double('PersonalAccessTokens::CreateService', execute: ServiceResponse.error(message: 'error', payload: { personal_access_token: PersonalAccessToken.new })) } @@ -19,7 +21,6 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do end before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end @@ -94,7 +95,7 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do it "allows revocation of an active token" do visit profile_personal_access_tokens_path - accept_confirm { click_on "Revoke" } + accept_gl_confirm(button_text: 'Revoke') { click_on "Revoke" } expect(active_personal_access_tokens).to have_text("This user has no active personal access tokens.") end @@ -113,7 +114,7 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do end visit profile_personal_access_tokens_path - accept_confirm { click_on "Revoke" } + accept_gl_confirm(button_text: "Revoke") { click_on "Revoke" } expect(active_personal_access_tokens).to have_text(personal_access_token.name) end end diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb index 0d08e7ea10d..a89fed3a78a 100644 --- a/spec/features/projects/branches/user_deletes_branch_spec.rb +++ b/spec/features/projects/branches/user_deletes_branch_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe "User deletes branch", :js do + include Spec::Support::Helpers::ModalHelpers + let_it_be(:user) { create(:user) } let(:project) { create(:project, :repository) } @@ -24,9 +26,7 @@ RSpec.describe "User deletes branch", :js do find('.js-delete-branch-button').click end - page.within '.modal-footer' do - click_button 'Yes, delete branch' - end + accept_gl_confirm(button_text: 'Yes, delete branch') wait_for_requests diff --git a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb index 67d3276fc14..9059f9e4857 100644 --- a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb +++ b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb @@ -4,6 +4,7 @@ require "spec_helper" RSpec.describe "User deletes comments on a commit", :js do include Spec::Support::Helpers::Features::NotesHelpers + include Spec::Support::Helpers::ModalHelpers include RepoHelpers let(:comment_text) { "XML attached" } @@ -11,7 +12,6 @@ RSpec.describe "User deletes comments on a commit", :js do let(:user) { create(:user) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) project.add_developer(user) @@ -32,9 +32,11 @@ RSpec.describe "User deletes comments on a commit", :js do find(".more-actions").click find(".more-actions .dropdown-menu li", match: :first) - accept_confirm { find(".js-note-delete").click } + find(".js-note-delete").click end + accept_gl_confirm(button_text: 'Delete comment') + expect(page).not_to have_css(".note") end end diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb index b0be6edb245..a7f23f093a3 100644 --- a/spec/features/projects/commit/user_comments_on_commit_spec.rb +++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb @@ -4,6 +4,7 @@ require "spec_helper" RSpec.describe "User comments on commit", :js do include Spec::Support::Helpers::Features::NotesHelpers + include Spec::Support::Helpers::ModalHelpers include RepoHelpers let_it_be(:project) { create(:project, :repository) } @@ -93,8 +94,6 @@ RSpec.describe "User comments on commit", :js do context "when deleting comment" do before do - stub_feature_flags(bootstrap_confirmation_modals: false) - visit(project_commit_path(project, sample_commit.id)) add_note(comment_text) @@ -112,9 +111,11 @@ RSpec.describe "User comments on commit", :js do find(".more-actions").click find(".more-actions .dropdown-menu li", match: :first) - accept_confirm { find(".js-note-delete").click } + find(".js-note-delete").click end + accept_gl_confirm(button_text: 'Delete comment') + expect(page).not_to have_css(".note") end end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 6cf59394af7..9ec41cd8f8d 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -132,8 +132,6 @@ RSpec.describe 'Environments page', :js do create(:environment, project: project, state: :available) end - stub_feature_flags(bootstrap_confirmation_modals: false) - context 'when there are no deployments' do before do visit_environments(project) diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index e2dc760beda..6a2d2c36521 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'User browses a job', :js do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:user) } let(:user_access_level) { :developer } let(:project) { create(:project, :repository, namespace: user.namespace) } @@ -12,7 +14,6 @@ RSpec.describe 'User browses a job', :js do before do project.add_maintainer(user) project.enable_ci - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end @@ -26,7 +27,11 @@ RSpec.describe 'User browses a job', :js do # scroll to the top of the page first execute_script "window.scrollTo(0,0)" - accept_confirm { find('[data-testid="job-log-erase-link"]').click } + accept_gl_confirm(button_text: 'Erase job log') do + find('[data-testid="job-log-erase-link"]').click + end + + wait_for_requests expect(page).to have_no_css('.artifacts') expect(build).not_to have_trace diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 67c40c1dbee..db227f3701d 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Projects > Members > Member leaves project' do include Spec::Support::Helpers::Features::MembersHelpers + include Spec::Support::Helpers::ModalHelpers let(:user) { create(:user) } let(:project) { create(:project, :repository, :with_namespace_settings) } @@ -11,7 +12,6 @@ RSpec.describe 'Projects > Members > Member leaves project' do before do project.add_developer(user) sign_in(user) - stub_feature_flags(bootstrap_confirmation_modals: false) end it 'user leaves project' do @@ -26,7 +26,7 @@ RSpec.describe 'Projects > Members > Member leaves project' do it 'user leaves project by url param', :js do visit project_path(project, leave: 1) - page.accept_confirm + accept_gl_confirm(button_text: 'Leave project') wait_for_all_requests expect(page).to have_current_path(dashboard_projects_path, ignore_query: true) diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index 370d7b49832..be124502c32 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Projects > Members > User requests access', :js do + include Spec::Support::Helpers::ModalHelpers + let_it_be(:user) { create(:user) } let_it_be(:maintainer) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } @@ -13,7 +15,6 @@ RSpec.describe 'Projects > Members > User requests access', :js do sign_in(user) project.add_maintainer(maintainer) visit project_path(project) - stub_feature_flags(bootstrap_confirmation_modals: false) end it 'request access feature is disabled' do @@ -67,7 +68,7 @@ RSpec.describe 'Projects > Members > User requests access', :js do expect(project.requesters.exists?(user_id: user)).to be_truthy - accept_confirm { click_link 'Withdraw Access Request' } + accept_gl_confirm { click_link 'Withdraw Access Request' } expect(page).not_to have_content 'Withdraw Access Request' expect(page).to have_content 'Request Access' diff --git a/spec/features/projects/pages/user_adds_domain_spec.rb b/spec/features/projects/pages/user_adds_domain_spec.rb index dfa6571545c..afa3f29ce0d 100644 --- a/spec/features/projects/pages/user_adds_domain_spec.rb +++ b/spec/features/projects/pages/user_adds_domain_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe 'User adds pages domain', :js do include LetsEncryptHelpers + include Spec::Support::Helpers::ModalHelpers let_it_be(:project) { create(:project, pages_https_only: false) } @@ -14,8 +15,6 @@ RSpec.describe 'User adds pages domain', :js do project.add_maintainer(user) sign_in(user) - - stub_feature_flags(bootstrap_confirmation_modals: false) end context 'when pages are exposed on external HTTP address', :http_pages_enabled do @@ -168,7 +167,7 @@ RSpec.describe 'User adds pages domain', :js do within('#content-body') { click_link 'Edit' } - accept_confirm { click_link 'Remove' } + accept_gl_confirm(button_text: 'Remove certificate') { click_link 'Remove' } expect(page).to have_field('Certificate (PEM)', with: '') expect(page).to have_field('Key (PEM)', with: '') diff --git a/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb b/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb index 156545ba78f..4c633bea64e 100644 --- a/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb +++ b/spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe "Pages with Let's Encrypt", :https_pages_enabled do include LetsEncryptHelpers + include Spec::Support::Helpers::ModalHelpers let(:project) { create(:project, pages_https_only: false) } let(:user) { create(:user) } @@ -14,7 +15,6 @@ RSpec.describe "Pages with Let's Encrypt", :https_pages_enabled do before do allow(Gitlab.config.pages).to receive(:enabled).and_return(true) stub_lets_encrypt_settings - stub_feature_flags(bootstrap_confirmation_modals: false) project.add_role(user, role) sign_in(user) @@ -139,7 +139,8 @@ RSpec.describe "Pages with Let's Encrypt", :https_pages_enabled do expect(page).to have_selector '.card-header', text: 'Certificate' expect(page).to have_text domain.subject - within('.card') { accept_confirm { click_on 'Remove' } } + within('.card') { click_on 'Remove' } + accept_gl_confirm(button_text: 'Remove certificate') expect(page).to have_field 'Certificate (PEM)', with: '' expect(page).to have_field 'Key (PEM)', with: '' end diff --git a/spec/features/projects/pages/user_edits_settings_spec.rb b/spec/features/projects/pages/user_edits_settings_spec.rb index 1226e1dc2ed..bd163f4a109 100644 --- a/spec/features/projects/pages/user_edits_settings_spec.rb +++ b/spec/features/projects/pages/user_edits_settings_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' RSpec.describe 'Pages edits pages settings', :js do + include Spec::Support::Helpers::ModalHelpers + let(:project) { create(:project, pages_https_only: false) } let(:user) { create(:user) } @@ -176,7 +178,6 @@ RSpec.describe 'Pages edits pages settings', :js do describe 'Remove page' do context 'when pages are deployed' do before do - stub_feature_flags(bootstrap_confirmation_modals: false) project.mark_pages_as_deployed end @@ -185,7 +186,7 @@ RSpec.describe 'Pages edits pages settings', :js do expect(page).to have_link('Remove pages') - accept_confirm { click_link 'Remove pages' } + accept_gl_confirm(button_text: 'Remove pages') { click_link 'Remove pages' } expect(page).to have_content('Pages were scheduled for removal') expect(project.reload.pages_deployed?).to be_falsey diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index 022fbcd7af4..8cf6d5bd29b 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Pipeline Schedules', :js do + include Spec::Support::Helpers::ModalHelpers + let!(:project) { create(:project, :repository) } let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) } let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) } @@ -11,7 +13,6 @@ RSpec.describe 'Pipeline Schedules', :js do context 'logged in as the pipeline schedule owner' do before do - stub_feature_flags(bootstrap_confirmation_modals: false) project.add_developer(user) pipeline_schedule.update!(owner: user) gitlab_sign_in(user) @@ -81,7 +82,6 @@ RSpec.describe 'Pipeline Schedules', :js do context 'logged in as a project maintainer' do before do - stub_feature_flags(bootstrap_confirmation_modals: false) project.add_maintainer(user) gitlab_sign_in(user) end @@ -117,7 +117,9 @@ RSpec.describe 'Pipeline Schedules', :js do end it 'deletes the pipeline' do - accept_confirm { click_link 'Delete' } + click_link 'Delete' + + accept_gl_confirm(button_text: 'Delete pipeline schedule') expect(page).not_to have_css(".pipeline-schedule-table-row") end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index a18bf7c5caf..785edc69623 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -311,7 +311,6 @@ RSpec.describe 'Pipelines', :js do end before do - stub_feature_flags(bootstrap_confirmation_modals: false) visit_project_pipelines end diff --git a/spec/features/projects/settings/access_tokens_spec.rb b/spec/features/projects/settings/access_tokens_spec.rb index 8bf228ddc69..88f9a50b093 100644 --- a/spec/features/projects/settings/access_tokens_spec.rb +++ b/spec/features/projects/settings/access_tokens_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Project > Settings > Access Tokens', :js do + include Spec::Support::Helpers::ModalHelpers + let_it_be(:user) { create(:user) } let_it_be(:bot_user) { create(:user, :project_bot) } let_it_be(:group) { create(:group) } @@ -14,7 +16,6 @@ RSpec.describe 'Project > Settings > Access Tokens', :js do end before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end diff --git a/spec/features/projects/settings/user_searches_in_settings_spec.rb b/spec/features/projects/settings/user_searches_in_settings_spec.rb index 44b5464a1b0..7ed96d01189 100644 --- a/spec/features/projects/settings/user_searches_in_settings_spec.rb +++ b/spec/features/projects/settings/user_searches_in_settings_spec.rb @@ -7,7 +7,6 @@ RSpec.describe 'User searches project settings', :js do let_it_be(:project) { create(:project, :repository, namespace: user.namespace, pages_https_only: false) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) end diff --git a/spec/features/promotion_spec.rb b/spec/features/promotion_spec.rb index 8692930376f..903d6244a4c 100644 --- a/spec/features/promotion_spec.rb +++ b/spec/features/promotion_spec.rb @@ -27,7 +27,7 @@ RSpec.describe 'Promotions', :js do visit edit_project_path(project) within('#promote_service_desk') do - find('.close').click + find('.js-close').click end wait_for_requests diff --git a/spec/features/refactor_blob_viewer_disabled/projects/files/user_replaces_files_spec.rb b/spec/features/refactor_blob_viewer_disabled/projects/files/user_replaces_files_spec.rb deleted file mode 100644 index 5561cf15a66..00000000000 --- a/spec/features/refactor_blob_viewer_disabled/projects/files/user_replaces_files_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Projects > Files > User replaces files', :js do - include DropzoneHelper - - let(:fork_message) do - "You're not allowed to make changes to this project directly. "\ - "A fork of this project has been created that you can make changes in, so you can submit a merge request." - end - - let(:project) { create(:project, :repository, name: 'Shop') } - let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } - let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } - let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } - let(:user) { create(:user) } - - before do - stub_feature_flags(refactor_blob_viewer: false) - sign_in(user) - end - - context 'when an user has write access' do - before do - project.add_maintainer(user) - visit(project_tree_path_root_ref) - wait_for_requests - end - - it 'replaces an existed file with a new one' do - click_link('.gitignore') - - expect(page).to have_content('.gitignore') - - click_on('Replace') - drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) - - page.within('#modal-upload-blob') do - fill_in(:commit_message, with: 'Replacement file commit message') - end - - click_button('Replace file') - - expect(page).to have_content('Lorem ipsum dolor sit amet') - expect(page).to have_content('Sed ut perspiciatis unde omnis') - expect(page).to have_content('Replacement file commit message') - end - end - - context 'when an user does not have write access' do - before do - project2.add_reporter(user) - visit(project2_tree_path_root_ref) - wait_for_requests - end - - it 'replaces an existed file with a new one in a forked project', :sidekiq_might_not_need_inline do - click_link('.gitignore') - - expect(page).to have_content('.gitignore') - - click_on('Replace') - - expect(page).to have_link('Fork') - expect(page).to have_button('Cancel') - - click_link('Fork') - - expect(page).to have_content(fork_message) - - click_on('Replace') - drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) - - page.within('#modal-upload-blob') do - fill_in(:commit_message, with: 'Replacement file commit message') - end - - click_button('Replace file') - - expect(page).to have_content('Replacement file commit message') - - fork = user.fork_of(project2.reload) - - expect(page).to have_current_path(project_new_merge_request_path(fork), ignore_query: true) - - click_link('Changes') - - expect(page).to have_content('Lorem ipsum dolor sit amet') - expect(page).to have_content('Sed ut perspiciatis unde omnis') - end - end -end diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index 97bb4c23d25..8d55a7a64f4 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Comments on personal snippets', :js do include NoteInteractionHelpers + include Spec::Support::Helpers::ModalHelpers let_it_be(:snippet) { create(:personal_snippet, :public) } let_it_be(:other_note) { create(:note_on_personal_snippet) } @@ -18,7 +19,6 @@ RSpec.describe 'Comments on personal snippets', :js do end before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in user visit snippet_path(snippet) @@ -142,9 +142,11 @@ RSpec.describe 'Comments on personal snippets', :js do open_more_actions_dropdown(snippet_notes[0]) page.within("#notes-list li#note_#{snippet_notes[0].id}") do - accept_confirm { click_on 'Delete comment' } + click_on 'Delete comment' end + accept_gl_confirm(button_text: 'Delete comment') + wait_for_requests expect(page).not_to have_selector("#notes-list li#note_#{snippet_notes[0].id}") diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index c682ad06977..628468a2abe 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -16,7 +16,6 @@ RSpec.describe 'User creates snippet', :js do let(:snippet_title_field) { 'snippet-title' } before do - stub_feature_flags(bootstrap_confirmation_modals: false) sign_in(user) visit new_snippet_path diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 7f5cf2359a3..eb497715df7 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Triggers', :js do + include Spec::Support::Helpers::ModalHelpers + let(:trigger_title) { 'trigger desc' } let(:user) { create(:user) } let(:user2) { create(:user) } @@ -74,7 +76,6 @@ RSpec.describe 'Triggers', :js do describe 'trigger "Revoke" workflow' do before do - stub_feature_flags(bootstrap_confirmation_modals: false) create(:ci_trigger, owner: user2, project: @project, description: trigger_title) visit project_settings_ci_cd_path(@project) end @@ -86,7 +87,7 @@ RSpec.describe 'Triggers', :js do it 'revoke trigger' do # See if "Revoke" on trigger works post trigger creation - page.accept_confirm do + accept_gl_confirm(button_text: 'Revoke') do find('[data-testid="trigger_revoke_button"]').send_keys(:return) end diff --git a/spec/frontend/blob/blob_file_dropzone_spec.js b/spec/frontend/blob/blob_file_dropzone_spec.js deleted file mode 100644 index d6fc824258b..00000000000 --- a/spec/frontend/blob/blob_file_dropzone_spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import $ from 'jquery'; -import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; -import BlobFileDropzone from '~/blob/blob_file_dropzone'; - -describe('BlobFileDropzone', () => { - let dropzone; - let replaceFileButton; - - beforeEach(() => { - loadHTMLFixture('blob/show.html'); - const form = $('.js-upload-blob-form'); - // eslint-disable-next-line no-new - new BlobFileDropzone(form, 'POST'); - dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone; - dropzone.processQueue = jest.fn(); - replaceFileButton = $('#submit-all'); - }); - - afterEach(() => { - resetHTMLFixture(); - }); - - describe('submit button', () => { - it('requires file', () => { - jest.spyOn(window, 'alert').mockImplementation(() => {}); - - replaceFileButton.click(); - - expect(window.alert).toHaveBeenCalled(); - }); - - it('is disabled while uploading', () => { - jest.spyOn(window, 'alert').mockImplementation(() => {}); - - const file = new File([], 'some-file.jpg'); - const fakeEvent = $.Event('drop', { - dataTransfer: { files: [file] }, - }); - - dropzone.listeners[0].events.drop(fakeEvent); - - replaceFileButton.click(); - - expect(window.alert).not.toHaveBeenCalled(); - expect(replaceFileButton.is(':disabled')).toEqual(true); - expect(dropzone.processQueue).toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/frontend/environments/environment_folder_spec.js b/spec/frontend/environments/environment_folder_spec.js index 37b897bf65d..48624f2324b 100644 --- a/spec/frontend/environments/environment_folder_spec.js +++ b/spec/frontend/environments/environment_folder_spec.js @@ -82,7 +82,7 @@ describe('~/environments/components/environments_folder.vue', () => { expect(collapse.attributes('visible')).toBeUndefined(); const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2); - expect(iconNames).toEqual(['angle-right', 'folder-o']); + expect(iconNames).toEqual(['chevron-lg-right', 'folder-o']); expect(folderName.classes('gl-font-weight-bold')).toBe(false); expect(link.exists()).toBe(false); }); @@ -95,7 +95,7 @@ describe('~/environments/components/environments_folder.vue', () => { expect(button.attributes('aria-label')).toBe(__('Collapse')); expect(collapse.attributes('visible')).toBe('visible'); const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2); - expect(iconNames).toEqual(['angle-down', 'folder-open']); + expect(iconNames).toEqual(['chevron-lg-down', 'folder-open']); expect(folderName.classes('gl-font-weight-bold')).toBe(true); expect(link.attributes('href')).toBe(nestedEnvironment.latest.folderPath); diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index 09fc2c6825f..a151595bf64 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -374,7 +374,7 @@ describe('~/environments/components/new_environment_item.vue', () => { it('is collapsed by default', () => { expect(collapse.attributes('visible')).toBeUndefined(); - expect(icon.props('name')).toEqual('chevron-lg-right'); + expect(icon.props('name')).toBe('chevron-lg-right'); expect(environmentName.classes('gl-font-weight-bold')).toBe(false); }); @@ -385,7 +385,7 @@ describe('~/environments/components/new_environment_item.vue', () => { expect(button.attributes('aria-label')).toBe(__('Collapse')); expect(collapse.attributes('visible')).toBe('visible'); - expect(icon.props('name')).toEqual('chevron-lg-down'); + expect(icon.props('name')).toBe('chevron-lg-down'); expect(environmentName.classes('gl-font-weight-bold')).toBe(true); expect(findDeployment().isVisible()).toBe(true); }); diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js index 58aaa2ef49c..c73b433c0bb 100644 --- a/spec/frontend/work_items/components/work_item_assignees_spec.js +++ b/spec/frontend/work_items/components/work_item_assignees_spec.js @@ -1,6 +1,8 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlLink } from '@gitlab/ui'; +import { GlLink, GlTokenSelector } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue'; +import localUpdateWorkItemMutation from '~/work_items/graphql/local_update_work_item.mutation.graphql'; const mockAssignees = [ { @@ -21,22 +23,92 @@ const mockAssignees = [ }, ]; +const workItemId = 'gid://gitlab/WorkItem/1'; + +const mutate = jest.fn(); + describe('WorkItemAssignees component', () => { let wrapper; const findAssigneeLinks = () => wrapper.findAllComponents(GlLink); + const findTokenSelector = () => wrapper.findComponent(GlTokenSelector); - const createComponent = () => { - wrapper = shallowMount(WorkItemAssignees, { + const findEmptyState = () => wrapper.findByTestId('empty-state'); + + const createComponent = ({ assignees = mockAssignees } = {}) => { + wrapper = mountExtended(WorkItemAssignees, { propsData: { - assignees: mockAssignees, + assignees, + workItemId, }, + mocks: { + $apollo: { + mutate, + }, + }, + attachTo: document.body, }); }; + afterEach(() => { + wrapper.destroy(); + }); + it('should pass the correct data-user-id attribute', () => { createComponent(); expect(findAssigneeLinks().at(0).attributes('data-user-id')).toBe('1'); }); + + describe('when there are no assignees', () => { + beforeEach(() => { + createComponent({ assignees: [] }); + }); + + it('should render empty state placeholder', () => { + expect(findEmptyState().exists()).toBe(true); + }); + + it('should hide empty state placeholder on focusing token selector', async () => { + findTokenSelector().vm.$emit('focus'); + await nextTick(); + + expect(findEmptyState().exists()).toBe(false); + }); + }); + + describe('when there are assignees', () => { + beforeEach(() => { + createComponent(); + }); + + it('should not render empty state placeholder', () => { + expect(findEmptyState().exists()).toBe(false); + }); + + it('should focus token selector on token removal', async () => { + findTokenSelector().vm.$emit('token-remove', mockAssignees[0].id); + await nextTick(); + + expect(findEmptyState().exists()).toBe(false); + expect(findTokenSelector().element.contains(document.activeElement)).toBe(true); + }); + + it('should call a mutation on clicking outside the token selector', async () => { + findTokenSelector().vm.$emit('input', [mockAssignees[0]]); + findTokenSelector().vm.$emit('token-remove'); + await nextTick(); + expect(mutate).not.toHaveBeenCalled(); + + findTokenSelector().vm.$emit('blur', new FocusEvent({ relatedTarget: null })); + await nextTick(); + + expect(mutate).toHaveBeenCalledWith({ + mutation: localUpdateWorkItemMutation, + variables: { + input: { id: workItemId, assigneeIds: [mockAssignees[0].id] }, + }, + }); + }); + }); }); diff --git a/spec/initializers/set_active_support_hash_digest_class_spec.rb b/spec/initializers/set_active_support_hash_digest_class_spec.rb new file mode 100644 index 00000000000..256e8a1f218 --- /dev/null +++ b/spec/initializers/set_active_support_hash_digest_class_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'setting ActiveSupport::Digest.hash_digest_class' do + it 'sets overrides config.active_support.hash_digest_class' do + expect(ActiveSupport::Digest.hash_digest_class).to eq(Gitlab::HashDigest::Facade) + end +end diff --git a/spec/lib/error_tracking/stacktrace_builder_spec.rb b/spec/lib/error_tracking/stacktrace_builder_spec.rb new file mode 100644 index 00000000000..46d0bde8122 --- /dev/null +++ b/spec/lib/error_tracking/stacktrace_builder_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'support/helpers/fast_rails_root' +require 'oj' + +RSpec.describe ErrorTracking::StacktraceBuilder do + include FastRailsRoot + + describe '#stacktrace' do + let(:original_payload) { Gitlab::Json.parse(File.read(rails_root_join('spec/fixtures', payload_file))) } + let(:payload) { original_payload } + let(:payload_file) { 'error_tracking/parsed_event.json' } + + subject(:stacktrace) { described_class.new(payload).stacktrace } + + context 'with full error context' do + it 'generates a correct stacktrace in expected format' do + expected_context = [ + [132, " end\n"], + [133, "\n"], + [134, " begin\n"], + [135, " block.call(work, *extra)\n"], + [136, " rescue Exception => e\n"], + [137, " STDERR.puts \"Error reached top of thread-pool: #\{e.message\} (#\{e.class\})\"\n"], + [138, " end\n"] + ] + + expected_entry = { + 'lineNo' => 135, + 'context' => expected_context, + 'filename' => 'puma/thread_pool.rb', + 'function' => 'block in spawn_thread', + 'colNo' => 0 + } + + expect(stacktrace).to be_kind_of(Array) + expect(stacktrace.first).to eq(expected_entry) + end + end + + context 'when error context is missing' do + let(:payload_file) { 'error_tracking/browser_event.json' } + + it 'generates a stacktrace without context' do + expected_entry = { + 'lineNo' => 6395, + 'context' => [], + 'filename' => 'webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js', + 'function' => 'hydrate', + 'colNo' => 0 + } + + expect(stacktrace).to be_kind_of(Array) + expect(stacktrace.first).to eq(expected_entry) + end + end + + context 'with empty payload' do + let(:payload) { {} } + + it { is_expected.to eq([]) } + end + + context 'without exception field' do + let(:payload) { original_payload.except('exception') } + + it { is_expected.to eq([]) } + end + + context 'without exception.values field' do + before do + original_payload['exception'].delete('values') + end + + it { is_expected.to eq([]) } + end + + context 'without any exception.values[].stacktrace fields' do + before do + original_payload.dig('exception', 'values').each { |value| value['stacktrace'] = '' } + end + + it { is_expected.to eq([]) } + end + + context 'without any exception.values[].stacktrace.frame fields' do + before do + original_payload.dig('exception', 'values').each { |value| value['stacktrace'].delete('frames') } + end + + it { is_expected.to eq([]) } + end + end +end diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb index 4810d55b541..cb046548880 100644 --- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb +++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb @@ -2,6 +2,15 @@ require 'fast_spec_helper' require 'rspec-parameterized' +require 'set' + +MOCK_LINE = Struct.new(:text, :type, :index, :old_pos, :new_pos) + +def make_lines(old_lines, new_lines, texts = nil, types = nil) + old_lines.each_with_index.map do |old, i| + MOCK_LINE.new(texts ? texts[i] : '', types ? types[i] : nil, i, old, new_lines[i]) + end +end RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFileHelper do let(:dummy) { Class.new { include Gitlab::Diff::Rendered::Notebook::DiffFileHelper }.new } @@ -25,7 +34,7 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFileHelper do describe '#map_transformed_line_to_source' do using RSpec::Parameterized::TableSyntax - subject { dummy.map_transformed_line_to_source(1, transformed_blocks) } + subject { dummy.source_line_from_block(1, transformed_blocks) } where(:case, :transformed_blocks, :result) do 'if transformed diff is empty' | [] | 0 @@ -38,71 +47,6 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFileHelper do end end - describe '#map_diff_block_to_source_line' do - let(:file_added) { false } - let(:file_deleted) { false } - let(:old_positions) { [1] } - let(:new_positions) { [1] } - let(:lines) { old_positions.zip(new_positions).map { |old, new| Gitlab::Diff::Line.new("", "", 0, old, new) } } - - subject { dummy.map_diff_block_to_source_line(lines, file_added, file_deleted)} - - context 'only additions' do - let(:old_positions) { [1, 2, 2, 2] } - let(:new_positions) { [1, 2, 3, 4] } - - it 'computes the removals correctly' do - expect(subject[0]).to eq({ 1 => 1, 2 => 4 }) - end - - it 'computes the additions correctly' do - expect(subject[1]).to eq({ 1 => 1, 2 => 2, 3 => 2, 4 => 2 }) - end - end - - context 'only additions' do - let(:old_positions) { [1, 2, 3, 4] } - let(:new_positions) { [1, 2, 2, 2] } - - it 'computes the removals correctly' do - expect(subject[0]).to eq({ 1 => 1, 2 => 2, 3 => 2, 4 => 2 }) - end - - it 'computes the additions correctly' do - expect(subject[1]).to eq({ 1 => 1, 2 => 4 }) - end - end - - context 'with additions and removals' do - let(:old_positions) { [1, 2, 3, 4, 4, 4] } - let(:new_positions) { [1, 2, 2, 2, 3, 4] } - - it 'computes the removals correctly' do - expect(subject[0]).to eq({ 1 => 1, 2 => 2, 3 => 2, 4 => 4 }) - end - - it 'computes the additions correctly' do - expect(subject[1]).to eq({ 1 => 1, 2 => 4, 3 => 4, 4 => 4 }) - end - end - - context 'is new file' do - let(:file_added) { true } - - it 'removals is empty' do - expect(subject[0]).to be_empty - end - end - - context 'is deleted file' do - let(:file_deleted) { true } - - it 'additions is empty' do - expect(subject[1]).to be_empty - end - end - end - describe '#image_as_rich_text' do let(:img) { 'data:image/png;base64,some_image_here' } let(:line_text) { " ![](#{img})"} @@ -131,4 +75,60 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFileHelper do end end end + + describe '#line_positions_at_source_diff' do + using RSpec::Parameterized::TableSyntax + + let(:blocks) do + { + from: [0, 2, 1, nil, nil, 3].map { |i| { source_line: i } }, + to: [0, 1, nil, 2, nil, 3].map { |i| { source_line: i } } + } + end + + let(:lines) do + make_lines( + [1, 2, 3, 4, 5, 5, 5, 5, 6], + [1, 2, 2, 2, 2, 3, 4, 5, 6], + 'ACBLDJEKF'.split(""), + [nil, 'old', 'old', 'old', 'new', 'new', 'new', nil, nil] + ) + end + + subject { dummy.line_positions_at_source_diff(lines, blocks)[index] } + + where(:case, :index, :transformed_positions, :mapped_positions) do + " A A" | 0 | [1, 1] | [1, 1] # No change, old_pos and new_pos have mappings + "- C " | 1 | [2, 2] | [3, 2] # A removal, both old_pos and new_pos have valid mappings + "- B " | 2 | [3, 2] | [2, 2] # A removal, both old_pos and new_pos have valid mappings + "- L " | 3 | [4, 2] | [0, 0] # A removal, but old_pos has no mapping + "+ D" | 4 | [5, 2] | [4, 2] # An addition, new_pos has mapping but old_pos does not, so old_pos is remapped + "+ J" | 5 | [5, 3] | [0, 0] # An addition, but new_pos has no mapping, so neither are remapped + "+ E" | 6 | [5, 4] | [4, 3] # An addition, new_pos has mapping but old_pos does not, so old_pos is remapped + " K K" | 7 | [5, 5] | [0, 0] # This has no mapping + " F F" | 8 | [6, 6] | [4, 4] # No change, old_pos and new_pos have mappings + end + + with_them do + it { is_expected.to eq(mapped_positions) } + end + end + + describe '#lines_in_source_diff' do + using RSpec::Parameterized::TableSyntax + + let(:lines) { make_lines(old_lines, new_lines) } + + subject { dummy.lines_in_source_diff(lines, is_deleted, is_new) } + + where(:old_lines, :new_lines, :is_deleted, :is_new, :existing_lines) do + [1, 2, 2] | [1, 1, 4] | false | false | { from: Set[1, 2], to: Set[1, 4] } + [1, 2, 2] | [1, 1, 4] | true | false | { from: Set[1, 2], to: Set[] } + [1, 2, 2] | [1, 1, 4] | false | true | { from: Set[], to: Set[1, 4] } + end + + with_them do + it { is_expected.to eq(existing_lines) } + end + end end diff --git a/spec/lib/gitlab/hash_digest/facade_spec.rb b/spec/lib/gitlab/hash_digest/facade_spec.rb new file mode 100644 index 00000000000..b352744513e --- /dev/null +++ b/spec/lib/gitlab/hash_digest/facade_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::HashDigest::Facade do + describe '.hexdigest' do + let(:plaintext) { 'something that is plaintext' } + + let(:sha256_hash) { OpenSSL::Digest::SHA256.hexdigest(plaintext) } + let(:md5_hash) { Digest::MD5.hexdigest(plaintext) } # rubocop:disable Fips/MD5 + + it 'uses SHA256' do + expect(described_class.hexdigest(plaintext)).to eq(sha256_hash) + end + + context 'when feature flags is not available' do + before do + allow(Feature).to receive(:feature_flags_available?).and_return(false) + end + + it 'uses MD5' do + expect(described_class.hexdigest(plaintext)).to eq(md5_hash) + end + end + + context 'when active_support_hash_digest_sha256 FF is disabled' do + before do + stub_feature_flags(active_support_hash_digest_sha256: false) + end + + it 'uses MD5' do + expect(described_class.hexdigest(plaintext)).to eq(md5_hash) + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_metrics_spec.rb b/spec/lib/gitlab/usage_data_metrics_spec.rb index 563eed75c38..485f2131d87 100644 --- a/spec/lib/gitlab/usage_data_metrics_spec.rb +++ b/spec/lib/gitlab/usage_data_metrics_spec.rb @@ -24,11 +24,8 @@ RSpec.describe Gitlab::UsageDataMetrics do expect(subject).to include(:hostname) end - it 'includes counts keys' do + it 'includes counts keys', :aggregate_failures do expect(subject[:counts]).to include(:boards) - end - - it 'includes counts keys' do expect(subject[:counts]).to include(:issues) end diff --git a/spec/models/error_tracking/error_event_spec.rb b/spec/models/error_tracking/error_event_spec.rb index 9cf5a405e74..6dab8fbf757 100644 --- a/spec/models/error_tracking/error_event_spec.rb +++ b/spec/models/error_tracking/error_event_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe ErrorTracking::ErrorEvent, type: :model do +RSpec.describe ErrorTracking::ErrorEvent do + include AfterNextHelpers + let_it_be(:event) { create(:error_tracking_error_event) } describe 'relationships' do @@ -18,44 +20,12 @@ RSpec.describe ErrorTracking::ErrorEvent, type: :model do end describe '#stacktrace' do - it 'generates a correct stacktrace in expected format' do - expected_context = [ - [132, " end\n"], - [133, "\n"], - [134, " begin\n"], - [135, " block.call(work, *extra)\n"], - [136, " rescue Exception => e\n"], - [137, " STDERR.puts \"Error reached top of thread-pool: #\{e.message\} (#\{e.class\})\"\n"], - [138, " end\n"] - ] - - expected_entry = { - 'lineNo' => 135, - 'context' => expected_context, - 'filename' => 'puma/thread_pool.rb', - 'function' => 'block in spawn_thread', - 'colNo' => 0 - } + it 'builds a stacktrace' do + expect_next(ErrorTracking::StacktraceBuilder, event.payload) + .to receive(:stacktrace).and_call_original expect(event.stacktrace).to be_kind_of(Array) - expect(event.stacktrace.first).to eq(expected_entry) - end - - context 'error context is missing' do - let(:event) { create(:error_tracking_error_event, :browser) } - - it 'generates a stacktrace without context' do - expected_entry = { - 'lineNo' => 6395, - 'context' => [], - 'filename' => 'webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js', - 'function' => 'hydrate', - 'colNo' => 0 - } - - expect(event.stacktrace).to be_kind_of(Array) - expect(event.stacktrace.first).to eq(expected_entry) - end + expect(event.stacktrace).not_to be_empty end end diff --git a/spec/services/ci/after_requeue_job_service_spec.rb b/spec/services/ci/after_requeue_job_service_spec.rb index c9bd44f78e2..fb67ee18fb2 100644 --- a/spec/services/ci/after_requeue_job_service_spec.rb +++ b/spec/services/ci/after_requeue_job_service_spec.rb @@ -26,6 +26,11 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do script: exit 0 needs: [a1] + a3: + stage: a + script: exit 0 + needs: [a2] + b1: stage: b script: exit 0 @@ -59,6 +64,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do check_jobs_statuses( a1: 'pending', a2: 'created', + a3: 'created', b1: 'pending', b2: 'created', c1: 'created', @@ -69,6 +75,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do check_jobs_statuses( a1: 'pending', a2: 'created', + a3: 'created', b1: 'success', b2: 'created', c1: 'created', @@ -79,6 +86,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do check_jobs_statuses( a1: 'failed', a2: 'skipped', + a3: 'skipped', b1: 'success', b2: 'skipped', c1: 'skipped', @@ -90,6 +98,7 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do check_jobs_statuses( a1: 'pending', a2: 'skipped', + a3: 'skipped', b1: 'success', b2: 'skipped', c1: 'skipped', @@ -103,12 +112,42 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do check_jobs_statuses( a1: 'pending', a2: 'created', + a3: 'skipped', b1: 'success', b2: 'created', c1: 'created', c2: 'created' ) end + + context 'when executed by a different user than the original owner' do + let(:retryer) { create(:user).tap { |u| project.add_maintainer(u) } } + let(:service) { described_class.new(project, retryer) } + + it 'reassigns jobs with updated statuses to the retryer' do + expect(jobs_name_status_owner_needs).to contain_exactly( + { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'a2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a1'] }, + { 'name' => 'a3', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] }, + { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'b2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] }, + { 'name' => 'c1', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['b2'] }, + { 'name' => 'c2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => [] } + ) + + execute_after_requeue_service(a1) + + expect(jobs_name_status_owner_needs).to contain_exactly( + { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'a2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a1'] }, + { 'name' => 'a3', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] }, + { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'b2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a2'] }, + { 'name' => 'c1', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['b2'] }, + { 'name' => 'c2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => [] } + ) + end + end end context 'stage-dag mixed pipeline with some same-stage needs' do @@ -212,6 +251,12 @@ RSpec.describe Ci::AfterRequeueJobService, :sidekiq_inline do pipeline.processables.latest end + def jobs_name_status_owner_needs + processables.reload.map do |job| + job.attributes.slice('name', 'status', 'user_id').merge('needs' => job.needs.map(&:name)) + end + end + def execute_after_requeue_service(processable) service.execute(processable) end diff --git a/spec/support/shared_examples/features/2fa_shared_examples.rb b/spec/support/shared_examples/features/2fa_shared_examples.rb index 94c91556ea7..44f30c32472 100644 --- a/spec/support/shared_examples/features/2fa_shared_examples.rb +++ b/spec/support/shared_examples/features/2fa_shared_examples.rb @@ -2,6 +2,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type| include Spec::Support::Helpers::Features::TwoFactorHelpers + include Spec::Support::Helpers::ModalHelpers def register_device(device_type, **kwargs) case device_type.downcase @@ -18,7 +19,6 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type| let(:user) { create(:user) } before do - stub_feature_flags(bootstrap_confirmation_modals: false) gitlab_sign_in(user) user.update_attribute(:otp_required_for_login, true) end @@ -59,7 +59,7 @@ RSpec.shared_examples 'hardware device for 2fa' do |device_type| expect(page).to have_content(first_device.name) expect(page).to have_content(second_device.name) - accept_confirm { click_on 'Delete', match: :first } + accept_gl_confirm(button_text: 'Delete') { click_on 'Delete', match: :first } expect(page).to have_content('Successfully deleted') expect(page.body).not_to have_content(first_device.name) diff --git a/spec/support/shared_examples/features/access_tokens_shared_examples.rb b/spec/support/shared_examples/features/access_tokens_shared_examples.rb index 5fa96443608..c162ed36881 100644 --- a/spec/support/shared_examples/features/access_tokens_shared_examples.rb +++ b/spec/support/shared_examples/features/access_tokens_shared_examples.rb @@ -135,7 +135,7 @@ RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_tex it 'allows revocation of an active token' do visit resource_settings_access_tokens_path - accept_confirm { click_on 'Revoke' } + accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' } expect(page).to have_selector('.settings-message') expect(no_resource_access_tokens_message).to have_text(no_active_tokens_text) @@ -156,7 +156,7 @@ RSpec.shared_examples 'inactive resource access tokens' do |no_active_tokens_tex it 'allows revocation of an active token' do visit resource_settings_access_tokens_path - accept_confirm { click_on 'Revoke' } + accept_gl_confirm(button_text: 'Revoke') { click_on 'Revoke' } expect(page).to have_selector('.settings-message') expect(no_resource_access_tokens_message).to have_text(no_active_tokens_text) diff --git a/tooling/quality/test_level.rb b/tooling/quality/test_level.rb index d0f2bd0107b..82da0121e31 100644 --- a/tooling/quality/test_level.rb +++ b/tooling/quality/test_level.rb @@ -74,7 +74,7 @@ module Quality end def pattern(level) - @patterns[level] ||= "#{prefixes_for_pattern}spec/#{folders_pattern(level)}{,/**/}*#{suffix(level)}" + @patterns[level] ||= "#{prefixes_for_pattern}spec/#{folders_pattern(level)}{,/**/}*#{suffix(level)}".freeze # rubocop:disable Style/RedundantFreeze end def regexp(level)