summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-15 15:09:20 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-15 15:09:20 +0000
commit9440c17f554424cc77aff8afadc61adbe23524d1 (patch)
treebc2b4213d845e5fd6d43eb71d21cfce28c81e440
parent3e0c035fe3a10436be36b4e22a4986479821b8e4 (diff)
downloadgitlab-ce-9440c17f554424cc77aff8afadc61adbe23524d1.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml11
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml11
-rw-r--r--.gitlab/ci/review-apps/main.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/workhorse.gitlab-ci.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/environments/components/environment_folder.vue4
-rw-r--r--app/assets/javascripts/lib/utils/rails_ujs.js4
-rw-r--r--app/assets/javascripts/work_items/components/work_item_assignees.vue108
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue6
-rw-r--r--app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql9
-rw-r--r--app/assets/javascripts/work_items/graphql/provider.js97
-rw-r--r--app/assets/javascripts/work_items/graphql/typedefs.graphql14
-rw-r--r--app/models/error_tracking/error_event.rb54
-rw-r--r--app/views/projects/blob/_upload.html.haml30
-rw-r--r--app/views/projects/blob/show.html.haml3
-rw-r--r--app/views/projects/empty.html.haml3
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml2
-rw-r--r--app/views/shared/_label.html.haml5
-rw-r--r--app/views/shared/promotions/_promote_servicedesk.html.haml19
-rw-r--r--config/feature_flags/development/active_support_hash_digest_sha256.yml (renamed from config/feature_flags/development/bootstrap_confirmation_modals.yml)10
-rw-r--r--config/feature_flags/development/rename_integrations_workers.yml2
-rw-r--r--config/feature_flags/development/update_vuln_identifiers_flag.yml8
-rw-r--r--config/initializers/set_active_support_hash_digest_class.rb11
-rw-r--r--db/fixtures/development/12_snippets.rb3
-rw-r--r--doc/administration/audit_events.md6
-rw-r--r--doc/development/pipelines.md6
-rw-r--r--lib/api/ci/job_artifacts.rb4
-rw-r--r--lib/api/ci/runner.rb2
-rw-r--r--lib/api/helpers.rb18
-rw-r--r--lib/error_tracking/stacktrace_builder.rb61
-rw-r--r--lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml2
-rw-r--r--lib/gitlab/diff/rendered/notebook/diff_file.rb36
-rw-r--r--lib/gitlab/diff/rendered/notebook/diff_file_helper.rb135
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/hash_digest/facade.rb29
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb7
-rw-r--r--spec/features/admin/admin_hooks_spec.rb7
-rw-r--r--spec/features/admin/admin_labels_spec.rb9
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb5
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb6
-rw-r--r--spec/features/admin/users/user_spec.rb3
-rw-r--r--spec/features/admin/users/users_spec.rb6
-rw-r--r--spec/features/boards/boards_spec.rb1
-rw-r--r--spec/features/groups/members/leave_group_spec.rb4
-rw-r--r--spec/features/groups/settings/access_tokens_spec.rb3
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb1
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb1
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb1
-rw-r--r--spec/features/profile_spec.rb19
-rw-r--r--spec/features/profiles/active_sessions_spec.rb10
-rw-r--r--spec/features/profiles/oauth_applications_spec.rb11
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb7
-rw-r--r--spec/features/projects/branches/user_deletes_branch_spec.rb6
-rw-r--r--spec/features/projects/commit/comments/user_deletes_comments_spec.rb6
-rw-r--r--spec/features/projects/commit/user_comments_on_commit_spec.rb7
-rw-r--r--spec/features/projects/environments/environments_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb9
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb4
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb5
-rw-r--r--spec/features/projects/pages/user_adds_domain_spec.rb5
-rw-r--r--spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb5
-rw-r--r--spec/features/projects/pages/user_edits_settings_spec.rb5
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb8
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb1
-rw-r--r--spec/features/projects/settings/access_tokens_spec.rb3
-rw-r--r--spec/features/projects/settings/user_searches_in_settings_spec.rb1
-rw-r--r--spec/features/promotion_spec.rb2
-rw-r--r--spec/features/refactor_blob_viewer_disabled/projects/files/user_replaces_files_spec.rb93
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb6
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb1
-rw-r--r--spec/features/triggers_spec.rb5
-rw-r--r--spec/frontend/blob/blob_file_dropzone_spec.js49
-rw-r--r--spec/frontend/environments/environment_folder_spec.js4
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_assignees_spec.js82
-rw-r--r--spec/initializers/set_active_support_hash_digest_class_spec.rb9
-rw-r--r--spec/lib/error_tracking/stacktrace_builder_spec.rb95
-rw-r--r--spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb132
-rw-r--r--spec/lib/gitlab/hash_digest/facade_spec.rb36
-rw-r--r--spec/lib/gitlab/usage_data_metrics_spec.rb5
-rw-r--r--spec/models/error_tracking/error_event_spec.rb44
-rw-r--r--spec/services/ci/after_requeue_job_service_spec.rb45
-rw-r--r--spec/support/shared_examples/features/2fa_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/access_tokens_shared_examples.rb4
-rw-r--r--tooling/quality/test_level.rb2
94 files changed, 922 insertions, 626 deletions
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 @@
<script>
-import { GlAvatar, GlLink } from '@gitlab/ui';
+import { GlTokenSelector, GlIcon, GlAvatar, GlLink } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import localUpdateWorkItemMutation from '../graphql/local_update_work_item.mutation.graphql';
+
+function isClosingIcon(el) {
+ return el?.classList.contains('gl-token-close');
+}
export default {
components: {
+ GlTokenSelector,
+ GlIcon,
GlAvatar,
GlLink,
},
props: {
+ workItemId: {
+ type: String,
+ required: true,
+ },
assignees: {
type: Array,
required: true,
},
},
+ data() {
+ return {
+ isEditing: false,
+ localAssignees: this.assignees.map((assignee) => ({
+ ...assignee,
+ class: 'gl-bg-transparent!',
+ })),
+ };
+ },
+ computed: {
+ assigneeIds() {
+ return this.localAssignees.map((assignee) => assignee.id);
+ },
+ assigneeListEmpty() {
+ return this.assignees.length === 0;
+ },
+ containerClass() {
+ return !this.isEditing ? 'gl-shadow-none! gl-bg-transparent!' : '';
+ },
+ },
methods: {
getUserId(id) {
return getIdFromGraphQLId(id);
},
+ setAssignees(e) {
+ if (isClosingIcon(e.relatedTarget) || !this.isEditing) return;
+ this.isEditing = false;
+ this.$apollo.mutate({
+ mutation: localUpdateWorkItemMutation,
+ variables: {
+ input: {
+ id: this.workItemId,
+ assigneeIds: this.assigneeIds,
+ },
+ },
+ });
+ },
+ async focusTokenSelector() {
+ this.isEditing = true;
+ await this.$nextTick();
+ this.$refs.tokenSelector.focusTextInput();
+ },
},
};
</script>
<template>
- <div class="gl-display-flex">
- <span class="gl-font-weight-bold gl-w-15 gl-pt-1">{{ __('Assignee(s)') }}</span>
- <div class="gl-mb-4">
- <gl-link
- v-for="user in assignees"
- :key="user.id"
- :href="user.webUrl"
- :title="`test`"
- :data-user-id="getUserId(user.id)"
- data-placement="top"
- class="gl-text-decoration-none! gl-text-black-normal! gl-display-flex gl-md-display-inline-flex! gl-align-items-center gl-mb-4 gl-md-mb-0 gl-mr-4 js-user-link"
- >
- <gl-avatar :size="24" :src="user.avatarUrl" />
- <span class="gl-pl-2">{{ user.name }}</span>
- </gl-link>
+ <div class="gl-display-flex gl-mb-4 work-item-assignees gl-relative">
+ <span class="gl-font-weight-bold gl-w-15 gl-pt-2" data-testid="assignees-title">{{
+ __('Assignee(s)')
+ }}</span>
+ <!-- TODO: Remove this div when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2872 is merged -->
+ <div
+ v-if="assigneeListEmpty && !isEditing"
+ class="add-assignees gl-min-w-fit-content gl-absolute gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2 gl-z-index-0"
+ data-testid="empty-state"
+ >
+ <gl-icon name="profile" />
+ <span class="gl-ml-2">{{ __('Add assignees') }}</span>
</div>
+ <gl-token-selector
+ ref="tokenSelector"
+ v-model="localAssignees"
+ hide-dropdown-with-no-items
+ :container-class="containerClass"
+ class="gl-w-full gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base gl-z-index-1 gl-bg-transparent!"
+ @token-remove="focusTokenSelector"
+ @focus="isEditing = true"
+ @blur="setAssignees"
+ >
+ <template #token-content="{ token }">
+ <gl-link
+ :href="token.webUrl"
+ :title="token.name"
+ :data-user-id="getUserId(token.id)"
+ data-placement="top"
+ class="gl-text-decoration-none! gl-text-body! gl-display-flex gl-md-display-inline-flex! gl-align-items-center js-user-link"
+ >
+ <gl-avatar :size="24" :src="token.avatarUrl" />
+ <span class="gl-pl-2">{{ token.name }}</span>
+ </gl-link>
+ </template>
+ </gl-token-selector>
</div>
</template>
+
+<style lang="scss">
+/* TODO: Remove style block when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2872 is merged */
+.work-item-assignees .add-assignees {
+ left: 7.5rem;
+}
+</style>
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 {
/>
</div>
<template v-if="workItemsMvc2Enabled">
- <work-item-assignees v-if="workItemAssignees" :assignees="workItemAssignees.nodes" />
+ <work-item-assignees
+ v-if="workItemAssignees"
+ :work-item-id="workItem.id"
+ :assignees="workItemAssignees.nodes"
+ />
<work-item-weight v-if="workItemWeight" :weight="workItemWeight.weight" />
</template>
<work-item-state
diff --git a/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql
new file mode 100644
index 00000000000..0d31ecef6f8
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql
@@ -0,0 +1,9 @@
+#import "./work_item.fragment.graphql"
+
+mutation localUpdateWorkItem($input: LocalWorkItemAssigneesInput) {
+ localUpdateWorkItem(input: $input) @client {
+ workItem {
+ ...WorkItem
+ }
+ }
+}
diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js
index e393354705b..09d929faae2 100644
--- a/app/assets/javascripts/work_items/graphql/provider.js
+++ b/app/assets/javascripts/work_items/graphql/provider.js
@@ -1,7 +1,10 @@
+import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { WIDGET_TYPE_ASSIGNEE } from '../constants';
import typeDefs from './typedefs.graphql';
+import workItemQuery from './work_item.query.graphql';
export const temporaryConfig = {
typeDefs,
@@ -13,38 +16,40 @@ export const temporaryConfig = {
WorkItem: {
fields: {
mockWidgets: {
- read() {
- return [
- {
- __typename: 'LocalWorkItemAssignees',
- type: 'ASSIGNEES',
- nodes: [
- {
- __typename: 'UserCore',
- id: 'gid://gitlab/User/1',
- avatarUrl: '',
- webUrl: '',
- // eslint-disable-next-line @gitlab/require-i18n-strings
- name: 'John Doe',
- username: 'doe_I',
- },
- {
- __typename: 'UserCore',
- id: 'gid://gitlab/User/2',
- avatarUrl: '',
- webUrl: '',
- // eslint-disable-next-line @gitlab/require-i18n-strings
- name: 'Marcus Rutherford',
- username: 'ruthfull',
- },
- ],
- },
- {
- __typename: 'LocalWorkItemWeight',
- type: 'WEIGHT',
- weight: 0,
- },
- ];
+ read(widgets) {
+ return (
+ widgets || [
+ {
+ __typename: 'LocalWorkItemAssignees',
+ type: 'ASSIGNEES',
+ nodes: [
+ {
+ __typename: 'UserCore',
+ id: 'gid://gitlab/User/1',
+ avatarUrl: '',
+ webUrl: '',
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ name: 'John Doe',
+ username: 'doe_I',
+ },
+ {
+ __typename: 'UserCore',
+ id: 'gid://gitlab/User/2',
+ avatarUrl: '',
+ webUrl: '',
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ name: 'Marcus Rutherford',
+ username: 'ruthfull',
+ },
+ ],
+ },
+ {
+ __typename: 'LocalWorkItemWeight',
+ type: 'WEIGHT',
+ weight: 0,
+ },
+ ]
+ );
},
},
},
@@ -53,10 +58,36 @@ export const temporaryConfig = {
},
};
+export const resolvers = {
+ Mutation: {
+ localUpdateWorkItem(_, { input }, { cache }) {
+ const sourceData = cache.readQuery({
+ query: workItemQuery,
+ variables: { id: input.id },
+ });
+
+ const data = produce(sourceData, (draftData) => {
+ 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" } &times;
- .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 &amp; 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
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
- 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
+ # line_positions_at_source_diff: given the transformed lines,
+ # what are the correct values for old_pos and new_pos?
#
- # 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:
#
- # Example: only additions
- # Old: New:
- # A A
- # F B
- # C
- # F
+ # Original
+ # from | to
+ # A | A
+ # B | D
+ # C | E
+ # F | 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
+ # Original Diff
+ # A A
+ # - B
+ # - C
+ # + D
+ # + E
+ # F F
#
- # Example: only removals
- # Old: New:
- # A A
- # B F
- # C
- # F
+ # Transformed
+ # from | to
+ # A | A
+ # C | D
+ # B | J
+ # L | E
+ # K | K
+ # F | 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 = {}
-
- 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
-
- [removals, additions]
+ # 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
+
+ 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
+
+ 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?
+
+ 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 &amp; 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)