Commit 57cafd29 authored by Lukas 'Eipi' Eipert's avatar Lukas 'Eipi' Eipert Committed by Peter Hegman

Use custom modal instead of browser confirm

This overwrites `Rails.confirm` from `@rails/ujs` in order to show a
modal instead of a Browser `window.confirm`.
parent 3140c76a
<script>
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
cancelAction: { text: __('Cancel') },
components: {
GlModal,
},
props: {
primaryText: {
type: String,
required: false,
default: __('OK'),
},
primaryVariant: {
type: String,
required: false,
default: 'confirm',
},
},
computed: {
primaryAction() {
return { text: this.primaryText, attributes: { variant: this.primaryVariant } };
},
},
mounted() {
this.$refs.modal.show();
},
};
</script>
<template>
<gl-modal
ref="modal"
size="sm"
modal-id="confirmationModal"
body-class="gl-display-flex"
:action-primary="primaryAction"
:action-cancel="$options.cancelAction"
hide-header
@primary="$emit('confirmed')"
@hidden="$emit('closed')"
>
<div class="gl-align-self-center"><slot></slot></div>
</gl-modal>
</template>
import Vue from 'vue';
export function confirmViaGlModal(message, element) {
return new Promise((resolve) => {
let confirmed = false;
const props = {};
const confirmBtnVariant = element.getAttribute('data-confirm-btn-variant');
if (confirmBtnVariant) {
props.primaryVariant = confirmBtnVariant;
}
const screenReaderText =
element.querySelector('.gl-sr-only')?.textContent ||
element.querySelector('.sr-only')?.textContent ||
element.getAttribute('aria-label');
if (screenReaderText) {
props.primaryText = screenReaderText;
}
const component = new Vue({
components: {
ConfirmModal: () => import('./confirm_modal.vue'),
},
render(h) {
return h(
'confirm-modal',
{
props,
on: {
confirmed() {
confirmed = true;
},
closed() {
component.$destroy();
resolve(confirmed);
},
},
},
[message],
);
},
}).$mount();
});
}
import Rails from '@rails/ujs';
import { confirmViaGlModal } from './confirm_via_gl_modal/confirm_via_gl_modal';
function monkeyPatchConfirmModal() {
/**
* This function is used to replace the `Rails.confirm` which uses `window.confirm`
*
* This function opens a confirmation modal which will resolve in a promise.
* Because the `Rails.confirm` API is synchronous, we go with a little hack here:
*
* 1. User clicks on something with `data-confirm`
* 2. We open the modal and return `false`, ending the "Rails" event chain
* 3. If the modal is closed and the user "confirmed" the action
* 1. replace the `Rails.confirm` with a function that always returns `true`
* 2. click the same element programmatically
*
* @param message {String} Message to be shown in the modal
* @param element {HTMLElement} Element that was clicked on
* @returns {boolean}
*/
function confirmViaModal(message, element) {
confirmViaGlModal(message, element)
.then((confirmed) => {
if (confirmed) {
Rails.confirm = () => true;
element.click();
Rails.confirm = confirmViaModal;
}
})
.catch(() => {});
return false;
}
Rails.confirm = confirmViaModal;
}
if (gon?.features?.bootstrapConfirmationModals) {
monkeyPatchConfirmModal();
}
export const initRails = () => {
// eslint-disable-next-line no-underscore-dangle
......
---
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'
type: development
group: group::foundations
default_enabled: false
......@@ -6,6 +6,7 @@ RSpec.describe 'Admin updates EE-only settings' do
include StubENV
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
admin = create(:admin)
sign_in(admin)
......
......@@ -6,6 +6,7 @@ RSpec.describe 'Admin views Subscription', :js do
let_it_be(:admin) { create(:admin) }
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
end
......
......@@ -115,6 +115,8 @@ RSpec.describe 'epic boards', :js do
context 'lists' do
let_it_be(:label_list2) { create(:epic_list, epic_board: epic_board, label: label2, position: 1) }
stub_feature_flags(bootstrap_confirmation_modals: false)
it 'changes position of list' do
expect(find_board_list(2)).to have_content(label.title)
expect(find_board_list(3)).to have_content(label2.title)
......
......@@ -13,6 +13,7 @@ RSpec.describe 'Groups > Members > Leave group' do
before do
user.update!(provisioned_by_group: group)
sign_in(user)
stub_feature_flags(bootstrap_confirmation_modals: false)
end
context 'with block_password_auth_for_saml_users feature flag switched on' do
......
......@@ -9,6 +9,7 @@ RSpec.describe 'Path Locks', :js do
before do
allow(project).to receive(:feature_available?).with(:file_locks) { true }
stub_feature_flags(bootstrap_confirmation_modals: false)
project.add_maintainer(user)
sign_in(user)
......
......@@ -34,6 +34,7 @@ RSpec.describe 'Protected Environments' do
context 'logged in as a maintainer' do
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
project.add_maintainer(user)
visit project_settings_ci_cd_path(project)
......
......@@ -173,6 +173,7 @@ RSpec.shared_examples "protected branches > access control > EE" do
let!(:protected_branch) { create(:protected_branch, project: project) }
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
stub_licensed_features(unprotection_restrictions: true)
end
......
......@@ -58,6 +58,7 @@ module Gitlab
push_frontend_feature_flag(:new_header_search, default_enabled: :yaml)
push_frontend_feature_flag(:suppress_apollo_errors_during_navigation, current_user, default_enabled: :yaml)
push_frontend_feature_flag(:configure_iac_scanning_via_mr, current_user, default_enabled: :yaml)
push_frontend_feature_flag(:bootstrap_confirmation_modals, default_enabled: :yaml)
end
# Exposes the state of a feature flag to the frontend code.
......
......@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Admin disables 2FA for a user' do
it 'successfully', :js do
stub_feature_flags(bootstrap_confirmation_modals: false)
admin = create(:admin)
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
......
......@@ -252,6 +252,7 @@ RSpec.describe 'Admin Groups' do
describe 'admin remove themself from a group', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/222342' do
it 'removes admin from the group' do
stub_feature_flags(bootstrap_confirmation_modals: false)
group.add_user(current_user, Gitlab::Access::DEVELOPER)
visit group_group_members_path(group)
......
......@@ -79,6 +79,7 @@ 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
......
......@@ -14,6 +14,7 @@ RSpec.describe 'admin issues labels' do
describe 'list' do
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
visit admin_labels_path
end
......
......@@ -74,6 +74,7 @@ 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" }
......
......@@ -8,6 +8,7 @@ RSpec.describe 'Admin uses repository checks', :request_store do
let(:admin) { create(:admin) }
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
end
......
......@@ -9,6 +9,7 @@ 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
......
......@@ -9,6 +9,7 @@ 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
......
......@@ -536,6 +536,7 @@ 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)
......
......@@ -10,6 +10,7 @@ RSpec.describe 'Groups > Members > Leave group' do
let(:group) { create(:group) }
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
sign_in(user)
end
......
......@@ -14,6 +14,7 @@ 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)
......
......@@ -18,6 +18,7 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
project.add_developer(user)
sign_in(user)
stub_feature_flags(bootstrap_confirmation_modals: false)
end
context 'when hovering over a parallel view diff file' do
......
......@@ -18,8 +18,10 @@ 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)
visit project_merge_request_path(project, merge_request)
end
......
......@@ -79,6 +79,7 @@ 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
......
......@@ -110,6 +110,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
let(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
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)
......
......@@ -6,6 +6,7 @@ RSpec.describe 'Profile account page', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
sign_in(user)
end
......@@ -80,6 +81,7 @@ RSpec.describe 'Profile account page', :js do
describe 'when I reset incoming email token' do
before do
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
stub_feature_flags(bootstrap_confirmation_modals: false)
visit profile_personal_access_tokens_path
end
......
......@@ -11,6 +11,10 @@ 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)
......
......@@ -7,6 +7,7 @@ RSpec.describe 'Profile > Applications' do
let(:application) { create(:oauth_application, owner: user) }
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
sign_in(user)
end
......
......@@ -34,6 +34,7 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
end
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
sign_in(user)
end
......
......@@ -35,6 +35,7 @@ RSpec.describe "User deletes branch", :js do
context 'when the feature flag :delete_branch_confirmation_modals is disabled' do
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
stub_feature_flags(delete_branch_confirmation_modals: false)
end
......
......@@ -179,6 +179,7 @@ RSpec.describe 'Branches' do
context 'when the delete_branch_confirmation_modals feature flag is disabled' do
it 'removes branch after confirmation', :js do
stub_feature_flags(delete_branch_confirmation_modals: false)
stub_feature_flags(bootstrap_confirmation_modals: false)
visit project_branches_filtered_path(project, state: 'all')
......
......@@ -11,6 +11,7 @@ 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)
......
......@@ -93,6 +93,8 @@ 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)
......
......@@ -143,6 +143,8 @@ 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)
......
......@@ -12,6 +12,7 @@ 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)
......
......@@ -9,6 +9,7 @@ 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
......
......@@ -11,6 +11,7 @@ RSpec.describe 'Projects > Members > User requests access', :js do
before do
sign_in(user)
visit project_path(project)
stub_feature_flags(bootstrap_confirmation_modals: false)
end
it 'request access feature is disabled' do
......
......@@ -14,6 +14,8 @@ 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
......
......@@ -14,6 +14,7 @@ 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)
......
......@@ -176,6 +176,7 @@ 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
......
......@@ -11,6 +11,7 @@ RSpec.describe 'Pipeline Schedules', :js do
context 'logged in as maintainer' do
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
project.add_maintainer(user)
gitlab_sign_in(user)
end
......
......@@ -317,6 +317,7 @@ RSpec.describe 'Pipelines', :js do
end
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
visit_project_pipelines
end
......
......@@ -13,6 +13,7 @@ RSpec.describe 'Project > Settings > Access Tokens', :js do
end
before do
stub_feature_flags(bootstrap_confirmation_modals: false)
sign_in(user)
end
......
......@@ -7,6 +7,7 @@ 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
......
......@@ -18,6 +18,7 @@ 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)
......
......@@ -16,6 +16,7 @@ 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
......
......@@ -72,6 +72,7 @@ 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
......
import { GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import ConfirmModal from '~/lib/utils/confirm_via_gl_modal/confirm_modal.vue';
describe('Confirm Modal', () => {
let wrapper;
let modal;
const createComponent = ({ primaryText, primaryVariant } = {}) => {
wrapper = mount(ConfirmModal, {
propsData: {
primaryText,
primaryVariant,
},
});
};
afterEach(() => {
wrapper.destroy();
});
const findGlModal = () => wrapper.findComponent(GlModal);
describe('Modal events', () => {
beforeEach(() => {
createComponent();
modal = findGlModal();
});
it('should emit `confirmed` event on `primary` modal event', () => {
findGlModal().vm.$emit('primary');
expect(wrapper.emitted('confirmed')).toBeTruthy();
});
it('should emit closed` event on `hidden` modal event', () => {
modal.vm.$emit('hidden');
expect(wrapper.emitted('closed')).toBeTruthy();
});
});
describe('Custom properties', () => {
it('should pass correct custom primary text & button variant to the modal when provided', () => {
const primaryText = "Let's do it!";
const primaryVariant = 'danger';
createComponent({ primaryText, primaryVariant });
const customProps = findGlModal().props('actionPrimary');
expect(customProps.text).toBe(primaryText);
expect(customProps.attributes.variant).toBe(primaryVariant);
});
it('should pass default primary text & button variant to the modal if no custom values provided', () => {
createComponent();
const customProps = findGlModal().props('actionPrimary');
expect(customProps.text).toBe('OK');
expect(customProps.attributes.variant).toBe('confirm');
});
});
});
......@@ -18,6 +18,7 @@ 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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment