Commit 0e605964 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'sh-validate-account-modal' into 'master'

[RUN-AS-IF-FOSS] Add validate account modal from CI/CD shared runners page

See merge request gitlab-org/gitlab!64861
parents 33cf859a f470fe4c
<script>
import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
const DEFAULT_ERROR_MESSAGE = __('An error occurred while updating the configuration.');
const REQUIRES_VALIDATION_TEXT = s__(
`Billings|Shared runners cannot be enabled until a valid credit card is on file.`,
);
export default {
i18n: {
REQUIRES_VALIDATION_TEXT,
},
components: {
GlAlert,
GlToggle,
GlTooltip,
CcValidationRequiredAlert: () =>
import('ee_component/billings/components/cc_validation_required_alert.vue'),
},
props: {
isDisabledAndUnoverridable: {
......@@ -20,6 +28,10 @@ export default {
type: Boolean,
required: true,
},
isCreditCardValidationRequired: {
type: Boolean,
required: false,
},
updatePath: {
type: String,
required: true,
......@@ -30,12 +42,17 @@ export default {
isLoading: false,
isSharedRunnerEnabled: false,
errorMessage: null,
isCcValidationRequired: false,
};
},
created() {
this.isSharedRunnerEnabled = this.isEnabled;
this.isCcValidationRequired = this.isCreditCardValidationRequired;
},
methods: {
creditCardValidated() {
this.isCcValidationRequired = false;
},
toggleSharedRunners() {
this.isLoading = true;
this.errorMessage = null;
......@@ -45,6 +62,7 @@ export default {
.then(() => {
this.isLoading = false;
this.isSharedRunnerEnabled = !this.isSharedRunnerEnabled;
this.isCcValidationRequired = this.isCreditCardValidationRequired;
})
.catch((error) => {
this.isLoading = false;
......@@ -61,16 +79,25 @@ export default {
<gl-alert v-if="errorMessage" class="gl-mb-3" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
<div ref="sharedRunnersToggle">
<gl-toggle
:disabled="isDisabledAndUnoverridable"
:is-loading="isLoading"
:label="__('Enable shared runners for this project')"
:value="isSharedRunnerEnabled"
data-testid="toggle-shared-runners"
@change="toggleSharedRunners"
/>
</div>
<cc-validation-required-alert
v-if="isCcValidationRequired && !isSharedRunnerEnabled"
class="gl-pb-5"
:custom-message="$options.i18n.REQUIRES_VALIDATION_TEXT"
@verifiedCreditCard="creditCardValidated"
/>
<gl-toggle
v-else
ref="sharedRunnersToggle"
:disabled="isDisabledAndUnoverridable"
:is-loading="isLoading"
:label="__('Enable shared runners for this project')"
:value="isSharedRunnerEnabled"
data-testid="toggle-shared-runners"
@change="toggleSharedRunners"
/>
<gl-tooltip v-if="isDisabledAndUnoverridable" :target="() => $refs.sharedRunnersToggle">
{{ __('Shared runners are disabled on group level') }}
</gl-tooltip>
......
......@@ -4,7 +4,12 @@ import SharedRunnersToggle from '~/projects/settings/components/shared_runners_t
export default (containerId = 'toggle-shared-runners-form') => {
const containerEl = document.getElementById(containerId);
const { isDisabledAndUnoverridable, isEnabled, updatePath } = containerEl.dataset;
const {
isDisabledAndUnoverridable,
isEnabled,
updatePath,
isCreditCardValidationRequired,
} = containerEl.dataset;
return new Vue({
el: containerEl,
......@@ -13,6 +18,7 @@ export default (containerId = 'toggle-shared-runners-form') => {
props: {
isDisabledAndUnoverridable: parseBoolean(isDisabledAndUnoverridable),
isEnabled: parseBoolean(isEnabled),
isCreditCardValidationRequired: parseBoolean(isCreditCardValidationRequired),
updatePath,
},
});
......
......@@ -28,6 +28,13 @@ export default {
GlLink,
AccountVerificationModal,
},
props: {
customMessage: {
type: String,
default: null,
required: false,
},
},
data() {
return {
shouldRenderSuccess: false,
......@@ -48,6 +55,7 @@ export default {
handleSuccessfulVerification() {
this.$refs.modal.hide();
this.shouldRenderSuccess = true;
this.$emit('verifiedCreditCard');
},
},
i18n,
......@@ -72,7 +80,10 @@ export default {
:primary-button-text="$options.i18n.dangerAlert.primaryButtonText"
@primaryAction="showModal"
>
<gl-sprintf :message="$options.i18n.dangerAlert.text">
<template v-if="customMessage">
{{ customMessage }}
</template>
<gl-sprintf v-else :message="$options.i18n.dangerAlert.text">
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
......
......@@ -3,9 +3,19 @@ module EE
module Ci
module RunnersHelper
include ::Gitlab::Utils::StrongMemoize
extend ::Gitlab::Utils::Override
BUY_PIPELINE_MINUTES_NOTIFICATION_DOT = 'buy_pipeline_minutes_notification_dot'
override :toggle_shared_runners_settings_data
def toggle_shared_runners_settings_data(project)
super.merge(is_credit_card_validation_required: "#{validate_credit_card?(project)}")
end
def validate_credit_card?(project)
!current_user.has_required_credit_card_to_enable_shared_runners?(project)
end
def show_buy_pipeline_minutes?(project, namespace)
return false unless ::Gitlab.dev_env_or_com?
......
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
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';
......@@ -26,6 +27,7 @@ describe('CreditCardValidationRequiredAlert', () => {
};
wrapper = createComponent();
wrapper.vm.$refs.modal.hide = jest.fn();
});
afterEach(() => {
......@@ -49,4 +51,11 @@ describe('CreditCardValidationRequiredAlert', () => {
expect(findGlAlert().attributes('variant')).toBe('success');
});
it('hides the modal and emits a verifiedCreditCard event upon success', () => {
wrapper.findComponent(AccountVerificationModal).vm.$emit('success');
expect(wrapper.vm.$refs.modal.hide).toHaveBeenCalled();
expect(wrapper.emitted('verifiedCreditCard')).toBeDefined();
});
});
......@@ -81,6 +81,30 @@ RSpec.describe EE::Ci::RunnersHelper do
end
end
describe '#toggle_shared_runners_settings_data' do
let(:valid_card) { true }
subject { helper.toggle_shared_runners_settings_data(project) }
before do
expect(user).to receive(:has_required_credit_card_to_enable_shared_runners?).with(project).and_return(valid_card)
end
context 'when user has a valid credit card' do
it 'return is_credit_card_validation_required as "false"' do
expect(subject[:is_credit_card_validation_required]).to eq('false')
end
end
context 'when user does not have a valid credit card' do
let(:valid_card) { false }
it 'return is_credit_card_validation_required as "true"' do
expect(subject[:is_credit_card_validation_required]).to eq('true')
end
end
end
context 'with notifications' do
let(:dev_env_or_com) { true }
......
......@@ -5117,6 +5117,9 @@ msgstr ""
msgid "BillingPlan|Upgrade for free"
msgstr ""
msgid "Billings|Shared runners cannot be enabled until a valid credit card is on file."
msgstr ""
msgid "Billings|To use free pipeline minutes on shared runners, you’ll need to validate your account with a credit or debit card. If you prefer not to provide one, you can run pipelines by bringing your own runners and disabling shared runners for your project. This is required to discourage and reduce abuse on GitLab infrastructure. %{strongStart}GitLab will not charge or store your card, it will only be used for validation.%{strongEnd} %{linkStart}Learn more%{linkEnd}."
msgstr ""
......
import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAxiosAdapter from 'axios-mock-adapter';
import CcValidationRequiredAlert from 'ee_component/billings/components/cc_validation_required_alert.vue';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import SharedRunnersToggleComponent from '~/projects/settings/components/shared_runners_toggle.vue';
......@@ -20,6 +22,7 @@ describe('projects/settings/components/shared_runners', () => {
isDisabledAndUnoverridable: false,
isLoading: false,
updatePath: TEST_UPDATE_PATH,
isCreditCardValidationRequired: false,
...props,
},
});
......@@ -28,6 +31,7 @@ describe('projects/settings/components/shared_runners', () => {
const findErrorAlert = () => wrapper.find(GlAlert);
const findSharedRunnersToggle = () => wrapper.find(GlToggle);
const findToggleTooltip = () => wrapper.find(GlTooltip);
const findCcValidationRequiredAlert = () => wrapper.findComponent(CcValidationRequiredAlert);
const getToggleValue = () => findSharedRunnersToggle().props('value');
const isToggleLoading = () => findSharedRunnersToggle().props('isLoading');
const isToggleDisabled = () => findSharedRunnersToggle().props('disabled');
......@@ -154,4 +158,40 @@ describe('projects/settings/components/shared_runners', () => {
});
});
});
describe('with credit card validation required and shared runners DISABLED', () => {
beforeEach(() => {
window.gon = {
subscriptions_url: TEST_HOST,
payment_form_url: TEST_HOST,
};
createComponent({
isCreditCardValidationRequired: true,
isEnabled: false,
});
});
it('toggle should not be visible', () => {
expect(findSharedRunnersToggle().exists()).toBe(false);
});
it('credit card validation component should exist', () => {
expect(findCcValidationRequiredAlert().exists()).toBe(true);
expect(findCcValidationRequiredAlert().text()).toBe(
SharedRunnersToggleComponent.i18n.REQUIRES_VALIDATION_TEXT,
);
});
describe('when credit card is validated', () => {
it('should show the toggle button', async () => {
findCcValidationRequiredAlert().vm.$emit('verifiedCreditCard');
await waitForPromises();
expect(findSharedRunnersToggle().exists()).toBe(true);
expect(getToggleValue()).toBe(false);
expect(isToggleDisabled()).toBe(false);
});
});
});
});
......@@ -3,6 +3,12 @@
require 'spec_helper'
RSpec.describe Ci::RunnersHelper do
let_it_be(:user, refind: true) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
describe '#runner_status_icon', :clean_gitlab_redis_cache do
it "returns - not contacted yet" do
runner = create(:ci_runner)
......@@ -90,28 +96,28 @@ RSpec.describe Ci::RunnersHelper do
context 'when project has runners' do
it 'returns the correct value for is_enabled' do
data = toggle_shared_runners_settings_data(project_with_runners)
data = helper.toggle_shared_runners_settings_data(project_with_runners)
expect(data[:is_enabled]).to eq("true")
end
end
context 'when project does not have runners' do
it 'returns the correct value for is_enabled' do
data = toggle_shared_runners_settings_data(project_without_runners)
data = helper.toggle_shared_runners_settings_data(project_without_runners)
expect(data[:is_enabled]).to eq("false")
end
end
context 'for all projects' do
it 'returns the update path for toggling the shared runners setting' do
data = toggle_shared_runners_settings_data(project_with_runners)
data = helper.toggle_shared_runners_settings_data(project_with_runners)
expect(data[:update_path]).to eq(toggle_shared_runners_project_runners_path(project_with_runners))
end
it 'returns false for is_disabled_and_unoverridable when project has no group' do
project = create(:project)
data = toggle_shared_runners_settings_data(project)
data = helper.toggle_shared_runners_settings_data(project)
expect(data[:is_disabled_and_unoverridable]).to eq("false")
end
......@@ -129,7 +135,7 @@ RSpec.describe Ci::RunnersHelper do
project = create(:project, group: group)
allow(group).to receive(:shared_runners_setting).and_return(shared_runners_setting)
data = toggle_shared_runners_settings_data(project)
data = helper.toggle_shared_runners_settings_data(project)
expect(data[:is_disabled_and_unoverridable]).to eq(is_disabled_and_unoverridable)
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