Commit cf07b5d1 authored by Eugie Limpin's avatar Eugie Limpin Committed by Mayra Cabrera

EE-only: Open account validation modal

parent bb074096
......@@ -10,8 +10,7 @@
.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } }
#js-pipeline-header-vue.pipeline-header-container{ data: { full_path: @project.full_path, pipeline_iid: @pipeline.iid, pipeline_id: @pipeline.id, pipelines_path: project_pipelines_path(@project) } }
- if Gitlab.com? && show_cc_validation_alert?(@pipeline)
#js-cc-validation-required-alert
= render_if_exists 'projects/pipelines/cc_validation_required_alert', pipeline: @pipeline
- if @pipeline.commit.present?
= render "projects/pipelines/info", commit: @pipeline.commit
......
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
import AccountVerificationModal from './account_verification_modal.vue';
const i18n = {
......@@ -28,12 +29,18 @@ export default {
GlLink,
AccountVerificationModal,
},
mixins: [Tracking.mixin()],
props: {
customMessage: {
type: String,
default: null,
required: false,
},
isFromAccountValidationEmail: {
type: Boolean,
default: false,
required: false,
},
},
data() {
return {
......@@ -48,11 +55,20 @@ export default {
return gon.subscriptions_url;
},
},
mounted() {
if (this.isFromAccountValidationEmail) {
this.showModal();
}
},
methods: {
showModal() {
this.$refs.modal.show();
},
handleSuccessfulVerification() {
if (this.isFromAccountValidationEmail) {
this.track('successful_validation', { label: 'account_validation_email' });
}
this.$refs.modal.hide();
this.shouldRenderSuccess = true;
this.$emit('verifiedCreditCard');
......
......@@ -11,7 +11,11 @@ export default (containerId = 'js-cc-validation-required-alert') => {
return new Vue({
el,
render(createElement) {
return createElement(CreditCardValidationRequiredAlert);
return createElement(CreditCardValidationRequiredAlert, {
props: {
isFromAccountValidationEmail: 'openValidateAccountModal' in el.dataset,
},
});
},
});
};
# frozen_string_literal: true
module Projects
module Pipelines
class EmailCampaignsController < Projects::Pipelines::ApplicationController
before_action :check_if_gl_com_or_dev
feature_category :navigation
def validate_account
track_email_cta_click
session[:start_account_validation] = true
redirect_to project_pipeline_path(project, pipeline)
end
private
def track_email_cta_click
::Gitlab::Tracking.event(
self.class.name,
'cta_clicked',
label: 'account_validation_email',
project: project,
user: current_user,
namespace: project.root_namespace
)
end
end
end
end
......@@ -4,6 +4,7 @@ module EE
module Ci
module PipelinesHelper
def show_cc_validation_alert?(pipeline)
return false unless ::Gitlab.dev_env_or_com?
return false if pipeline.user.blank? || current_user != pipeline.user
pipeline.user_not_verified? && !pipeline.user.has_required_credit_card_to_run_pipelines?(pipeline.project)
......
- return unless show_cc_validation_alert?(pipeline)
- open_validate_account_modal = session.delete(:start_account_validation)
#js-cc-validation-required-alert{ data: { open_validate_account_modal: open_validate_account_modal } }
......@@ -6,4 +6,6 @@ resources :pipelines, only: [] do
get :licenses
get :codequality_report
end
get 'validate_account', action: :validate_account, controller: 'pipelines/email_campaigns'
end
......@@ -44,7 +44,7 @@ module Gitlab
end
def cta_link
url = project_pipeline_url(pipeline.project, pipeline)
url = project_pipeline_validate_account_url(pipeline.project, pipeline)
case format
when :html
......
......@@ -316,6 +316,31 @@ RSpec.describe 'Pipeline', :js do
end
end
describe 'GET /:project/-/pipelines/:id/validate_account' do
let(:pipeline) { create(:ci_pipeline, :failed, project: project, user: user, failure_reason: 'user_not_verified') }
let(:ultimate_plan) { create(:ultimate_plan) }
before do
allow(Gitlab).to receive(:com?) { true }
create(:gitlab_subscription, :active_trial, namespace: namespace, hosted_plan: ultimate_plan)
end
it 'redirects to pipeline page with account validation modal opened' do
visit project_pipeline_validate_account_path(project, pipeline)
expect(page).to have_current_path(pipeline_path(pipeline))
account_validation_alert_content = 'User validation required'
expect(page).to have_content(account_validation_alert_content)
expect(page).to have_selector("#credit-card-verification-modal")
# ensure account validation modal is only opened when redirected from /validate_account
visit current_path
expect(page).not_to have_selector("#credit-card-verification-modal")
end
end
private
def create_link(source_pipeline, pipeline)
......
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import AccountVerificationModal from 'ee/billings/components/account_verification_modal.vue';
import CreditCardValidationRequiredAlert from 'ee/billings/components/cc_validation_required_alert.vue';
import { TEST_HOST } from 'helpers/test_constants';
import { stubComponent } from 'helpers/stub_component';
describe('CreditCardValidationRequiredAlert', () => {
let showSpy;
let trackingSpy;
let wrapper;
const createComponent = (data = {}) => {
const createComponent = ({ data = {}, props = {} } = {}) => {
showSpy = jest.fn();
return shallowMount(CreditCardValidationRequiredAlert, {
propsData: {
...props,
},
stubs: {
GlSprintf,
AccountVerificationModal: stubComponent(AccountVerificationModal, {
methods: { show: showSpy, hide: jest.fn() },
}),
},
data() {
return data;
......@@ -21,6 +33,8 @@ describe('CreditCardValidationRequiredAlert', () => {
const findGlAlert = () => wrapper.findComponent(GlAlert);
beforeEach(() => {
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
window.gon = {
subscriptions_url: TEST_HOST,
payment_form_url: TEST_HOST,
......@@ -31,6 +45,7 @@ describe('CreditCardValidationRequiredAlert', () => {
});
afterEach(() => {
unmockTracking();
wrapper.destroy();
});
......@@ -47,7 +62,7 @@ describe('CreditCardValidationRequiredAlert', () => {
});
it('renders the success alert instead of danger', () => {
wrapper = createComponent({ shouldRenderSuccess: true });
wrapper = createComponent({ data: { shouldRenderSuccess: true } });
expect(findGlAlert().attributes('variant')).toBe('success');
});
......@@ -64,4 +79,26 @@ describe('CreditCardValidationRequiredAlert', () => {
expect(wrapper.emitted('dismiss')).toBeDefined();
});
it('does not open the modal on mount', () => {
expect(showSpy).not.toHaveBeenCalled();
});
describe('when isFromAccountValidationEmail prop is true', () => {
beforeEach(() => {
wrapper = createComponent({ props: { isFromAccountValidationEmail: true } });
});
it('opens the modal on mount', () => {
expect(showSpy).toHaveBeenCalled();
});
it('sends successful verification event', () => {
wrapper.findComponent(AccountVerificationModal).vm.$emit('success');
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'successful_validation', {
label: 'account_validation_email',
});
});
});
});
......@@ -40,5 +40,15 @@ RSpec.describe EE::Ci::PipelinesHelper do
it { is_expected.to be_falsy }
end
context 'when not in dev env or com' do
let(:pipeline) { instance_double(Ci::Pipeline) }
before do
allow(Gitlab).to receive(:dev_env_or_com?) { false }
end
it { is_expected.to be_falsy }
end
end
end
......@@ -22,7 +22,7 @@ RSpec.describe Emails::InProductMarketing do
it 'has the correct subject and content' do
message = Gitlab::Email::Message::AccountValidation.new(pipeline)
cta_url = project_pipeline_url(pipeline.project, pipeline)
cta_url = project_pipeline_validate_account_url(pipeline.project, pipeline)
cta2_url = 'https://docs.gitlab.com/runner/install/'
aggregate_failures do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Pipelines::EmailCampaignsController do
let_it_be(:user) { create(:user) }
let(:dev_env_or_com) { true }
subject(:request) do
get project_pipeline_validate_account_path(project, pipeline)
end
before do
allow(Gitlab).to receive(:dev_env_or_com?) { dev_env_or_com }
end
describe 'GET #validate_account', :snowplow do
context 'when user has access to the pipeline' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
before do
group.add_developer(user)
sign_in(user)
request
end
it 'emits a snowplow event' do
expect_snowplow_event(
category: described_class.name,
action: 'cta_clicked',
label: 'account_validation_email',
project: project,
user: user,
namespace: group
)
end
it 'sets session[:start_account_validation] to true' do
expect(session[:start_account_validation]).to eq(true)
end
it 'redirects to the pipeline show page' do
expect(response).to redirect_to(project_pipeline_path(project, pipeline))
end
context 'when not in .com or dev env' do
let(:dev_env_or_com) { false }
it 'returns 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when user does not have access to the pipeline' do
let_it_be(:project) { create(:project, :private) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
before do
sign_in(user)
request
end
it 'returns :not_found' do
expect(response).to have_gitlab_http_status(:not_found)
end
it 'does not set session[:start_account_validation]' do
expect(session[:start_account_validation]).to be_nil
end
end
end
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