Commit 24f68da2 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'vs/reactivate-extend-trial-button' into 'master'

Add Vue.js-based button for extending/reactivating trial

See merge request gitlab-org/gitlab!66049
parents fdb0a34b 3bbd0dcb
...@@ -2,6 +2,12 @@ import { buildApiUrl } from '~/api/api_utils'; ...@@ -2,6 +2,12 @@ import { buildApiUrl } from '~/api/api_utils';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
const SUBSCRIPTIONS_PATH = '/api/:version/subscriptions'; const SUBSCRIPTIONS_PATH = '/api/:version/subscriptions';
const EXTEND_REACTIVATE_TRIAL_PATH = '/-/trials/extend_reactivate';
const TRIAL_EXTENSION_TYPE = Object.freeze({
extended: 1,
reactivated: 2,
});
export function createSubscription(groupId, customer, subscription) { export function createSubscription(groupId, customer, subscription) {
const url = buildApiUrl(SUBSCRIPTIONS_PATH); const url = buildApiUrl(SUBSCRIPTIONS_PATH);
...@@ -13,3 +19,25 @@ export function createSubscription(groupId, customer, subscription) { ...@@ -13,3 +19,25 @@ export function createSubscription(groupId, customer, subscription) {
return axios.post(url, { params }); return axios.post(url, { params });
} }
const updateTrial = async (namespaceId, trialExtensionType) => {
if (!Object.values(TRIAL_EXTENSION_TYPE).includes(trialExtensionType)) {
throw new TypeError('The "trialExtensionType" argument is invalid.');
}
const url = buildApiUrl(EXTEND_REACTIVATE_TRIAL_PATH);
const params = {
namespace_id: namespaceId,
trial_extension_type: trialExtensionType,
};
return axios.put(url, params);
};
export const extendTrial = async (namespaceId) => {
return updateTrial(namespaceId, TRIAL_EXTENSION_TYPE.extended);
};
export const reactivateTrial = async (namespaceId) => {
return updateTrial(namespaceId, TRIAL_EXTENSION_TYPE.reactivated);
};
...@@ -9,7 +9,7 @@ export default { ...@@ -9,7 +9,7 @@ export default {
}, },
inject: { inject: {
namespaceId: { namespaceId: {
default: '', default: null,
}, },
}, },
created() { created() {
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
DAYS_FOR_RENEWAL, DAYS_FOR_RENEWAL,
PLAN_TITLE_TRIAL_TEXT, PLAN_TITLE_TRIAL_TEXT,
} from 'ee/billings/constants'; } from 'ee/billings/constants';
import ExtendReactivateTrialButton from 'ee/trials/extend_reactivate_trial/components/extend_reactivate_trial_button.vue';
import createFlash from '~/flash'; import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { getDayDifference } from '~/lib/utils/datetime/date_calculation_utility'; import { getDayDifference } from '~/lib/utils/datetime/date_calculation_utility';
...@@ -24,6 +25,7 @@ export default { ...@@ -24,6 +25,7 @@ export default {
GlButton, GlButton,
GlLoadingIcon, GlLoadingIcon,
SubscriptionTableRow, SubscriptionTableRow,
ExtendReactivateTrialButton,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
inject: { inject: {
...@@ -34,7 +36,7 @@ export default { ...@@ -34,7 +36,7 @@ export default {
default: '', default: '',
}, },
namespaceId: { namespaceId: {
default: '', default: null,
}, },
customerPortalUrl: { customerPortalUrl: {
default: '', default: '',
...@@ -54,6 +56,9 @@ export default { ...@@ -54,6 +56,9 @@ export default {
refreshSeatsHref: { refreshSeatsHref: {
default: '', default: '',
}, },
availableTrialAction: {
default: null,
},
}, },
computed: { computed: {
...mapState([ ...mapState([
...@@ -182,7 +187,14 @@ export default { ...@@ -182,7 +187,14 @@ export default {
data-testid="subscription-header" data-testid="subscription-header"
> >
<strong>{{ subscriptionHeader }}</strong> <strong>{{ subscriptionHeader }}</strong>
<div class="controls"> <div class="gl-display-flex">
<extend-reactivate-trial-button
v-if="availableTrialAction"
:namespace-id="namespaceId"
:action="availableTrialAction"
:plan-name="planName"
class="gl-mr-3"
/>
<gl-button <gl-button
v-for="(button, index) in buttons" v-for="(button, index) in buttons"
:key="button.text" :key="button.text"
......
...@@ -24,13 +24,14 @@ export default (containerId = 'js-billing-plans') => { ...@@ -24,13 +24,14 @@ export default (containerId = 'js-billing-plans') => {
planName, planName,
freePersonalNamespace, freePersonalNamespace,
refreshSeatsHref, refreshSeatsHref,
action,
} = containerEl.dataset; } = containerEl.dataset;
return new Vue({ return new Vue({
el: containerEl, el: containerEl,
store: new Vuex.Store(initialStore()), store: new Vuex.Store(initialStore()),
provide: { provide: {
namespaceId, namespaceId: Number(namespaceId),
namespaceName, namespaceName,
addSeatsHref, addSeatsHref,
planUpgradeHref, planUpgradeHref,
...@@ -40,6 +41,7 @@ export default (containerId = 'js-billing-plans') => { ...@@ -40,6 +41,7 @@ export default (containerId = 'js-billing-plans') => {
planName, planName,
freePersonalNamespace: parseBoolean(freePersonalNamespace), freePersonalNamespace: parseBoolean(freePersonalNamespace),
refreshSeatsHref, refreshSeatsHref,
availableTrialAction: action,
}, },
render(createElement) { render(createElement) {
return createElement(SubscriptionApp); return createElement(SubscriptionApp);
......
import { shouldQrtlyReconciliationMount } from 'ee/billings/qrtly_reconciliation'; import { shouldQrtlyReconciliationMount } from 'ee/billings/qrtly_reconciliation';
import initSubscriptions from 'ee/billings/subscriptions'; import initSubscriptions from 'ee/billings/subscriptions';
import { shouldExtendReactivateTrialButtonMount } from 'ee/trials/extend_reactivate_trial';
import PersistentUserCallout from '~/persistent_user_callout'; import PersistentUserCallout from '~/persistent_user_callout';
PersistentUserCallout.factory(document.querySelector('.js-gold-trial-callout')); PersistentUserCallout.factory(document.querySelector('.js-gold-trial-callout'));
initSubscriptions(); initSubscriptions();
shouldExtendReactivateTrialButtonMount();
shouldQrtlyReconciliationMount(); shouldQrtlyReconciliationMount();
import initSubscriptions from 'ee/billings/subscriptions'; import initSubscriptions from 'ee/billings/subscriptions';
import { shouldExtendReactivateTrialButtonMount } from 'ee/trials/extend_reactivate_trial';
import PersistentUserCallout from '~/persistent_user_callout'; import PersistentUserCallout from '~/persistent_user_callout';
PersistentUserCallout.factory(document.querySelector('.js-gold-trial-callout')); PersistentUserCallout.factory(document.querySelector('.js-gold-trial-callout'));
shouldExtendReactivateTrialButtonMount();
initSubscriptions(); initSubscriptions();
<script>
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { extendTrial, reactivateTrial } from 'ee/api/subscriptions_api';
import createFlash from '~/flash';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { sprintf, __ } from '~/locale';
import { i18n, TRIAL_ACTION_EXTEND, TRIAL_ACTIONS } from '../constants';
export default {
name: 'ExtendReactivateTrialButton',
components: { GlButton, GlModal },
directives: {
GlModal: GlModalDirective,
},
props: {
namespaceId: {
type: Number,
required: true,
},
action: {
type: String,
required: true,
default: TRIAL_ACTION_EXTEND,
validator: (value) => TRIAL_ACTIONS.includes(value),
},
planName: {
type: String,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
computed: {
i18nContext() {
return this.action === TRIAL_ACTION_EXTEND
? this.$options.i18n.extend
: this.$options.i18n.reactivate;
},
modalText() {
return sprintf(this.i18nContext.modalText, {
action: this.actionName,
planName: sprintf(this.$options.i18n.planName, { planName: this.planName }),
});
},
actionPrimary() {
return {
text: this.i18nContext.buttonText,
};
},
actionSecondary() {
return {
text: __('Cancel'),
};
},
},
methods: {
async submit() {
this.isLoading = true;
this.$refs.modal.hide();
const action = this.action === TRIAL_ACTION_EXTEND ? extendTrial : reactivateTrial;
await action(this.namespaceId)
.then(() => {
refreshCurrentPage();
})
.catch((error) => {
createFlash({
message: this.i18nContext.trialActionError,
captureError: true,
error,
});
})
.finally(() => {
this.isLoading = false;
});
},
},
i18n,
};
</script>
<template>
<div>
<gl-button v-gl-modal.extend-trial :loading="isLoading" category="primary" variant="info">
{{ i18nContext.buttonText }}
</gl-button>
<gl-modal
ref="modal"
modal-id="extend-trial"
:title="i18nContext.buttonText"
:action-primary="actionPrimary"
:action-secondary="actionSecondary"
data-testid="extend-reactivate-trial-modal"
@primary="submit"
>
{{ modalText }}
</gl-modal>
</div>
</template>
import { s__ } from '~/locale';
export const TRIAL_ACTION_EXTEND = 'extend';
export const TRIAL_ACTION_REACTIVATE = 'reactivate';
export const TRIAL_ACTIONS = [TRIAL_ACTION_EXTEND, TRIAL_ACTION_REACTIVATE];
export const i18n = Object.freeze({
planName: s__('Billings|%{planName} plan'),
extend: {
buttonText: s__('Billings|Extend trial'),
modalText: s__(
'Billings|By extending your trial, you will receive an additional 30 days of %{planName}. Your trial can be only extended once.',
),
trialActionError: s__('Billings|An error occurred while extending your trial.'),
},
reactivate: {
buttonText: s__('Billings|Reactivate trial'),
modalText: s__(
'Billings|By reactivating your trial, you will receive an additional 30 days of %{planName}. Your trial can be only reactivated once.',
),
trialActionError: s__('Billings|An error occurred while reactivating your trial.'),
},
});
export const shouldExtendReactivateTrialButtonMount = async () => {
const el = document.querySelector('.js-extend-reactivate-trial-button');
if (el) {
const { initExtendReactivateTrialButton } = await import(
/* webpackChunkName: 'init_extend_reactivate_trial_button' */ './init_extend_reactivate_trial_button'
);
initExtendReactivateTrialButton(el);
}
};
import Vue from 'vue';
import ExtendReactivateTrialButton from 'ee/trials/extend_reactivate_trial/components/extend_reactivate_trial_button.vue';
export const initExtendReactivateTrialButton = (el) => {
const { namespaceId, action, planName } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(ExtendReactivateTrialButton, {
props: {
namespaceId: Number(namespaceId),
planName,
action,
},
});
},
});
};
...@@ -123,7 +123,6 @@ $badge-height: $gl-spacing-scale-7; ...@@ -123,7 +123,6 @@ $badge-height: $gl-spacing-scale-7;
.card-wrapper { .card-wrapper {
margin-bottom: $gutter-small; margin-bottom: $gutter-small;
padding-top: $badge-height;
width: calc(50% - #{$gutter-small} / 2); width: calc(50% - #{$gutter-small} / 2);
&-has-badge { &-has-badge {
......
...@@ -69,7 +69,7 @@ module EE ...@@ -69,7 +69,7 @@ module EE
{ {
namespace_id: namespace.id, namespace_id: namespace.id,
plan_name: namespace.actual_plan_name.titleize, plan_name: ::Plan::ULTIMATE.titleize,
action: action action: action
} }
end end
......
...@@ -9,4 +9,5 @@ ...@@ -9,4 +9,5 @@
- else - else
= render 'shared/billings/billing_plans', plans_data: @plans_data, namespace: @group, current_plan: current_plan = render 'shared/billings/billing_plans', plans_data: @plans_data, namespace: @group, current_plan: current_plan
#js-billing-plans{ data: subscription_plan_data_attributes(@group, current_plan) } - data_attributes = subscription_plan_data_attributes(@group, current_plan).merge(extend_reactivate_trial_button_data(@group))
#js-billing-plans{ data: data_attributes }
- page_title _('Billing') - page_title _('Billing')
- current_plan = subscription_plan_info(@plans_data, current_user.namespace.actual_plan_name) - namespace = current_user.namespace
- current_plan = subscription_plan_info(@plans_data, namespace.actual_plan_name)
- data_attributes = subscription_plan_data_attributes(namespace, current_plan).merge(extend_reactivate_trial_button_data(namespace))
= render 'shared/billings/billing_plans', plans_data: @plans_data, namespace: current_user.namespace, current_plan: current_plan = render 'shared/billings/billing_plans', plans_data: @plans_data, namespace: current_user.namespace, current_plan: current_plan
#js-billing-plans{ data: subscription_plan_data_attributes(current_user.namespace, current_plan) } #js-billing-plans{ data: data_attributes }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- if namespace_for_user - if namespace_for_user
= render_if_exists 'trials/banner', namespace: namespace = render_if_exists 'trials/banner', namespace: namespace
.billing-plan-header.content-block.center.gl-mb-5 .billing-plan-header.content-block.center.gl-mb-3
.billing-plan-logo .billing-plan-logo
- if namespace_for_user - if namespace_for_user
.avatar-container.s96.home-panel-avatar.gl-mr-3.float-none.mx-auto.mb-4.mt-1 .avatar-container.s96.home-panel-avatar.gl-mr-3.float-none.mx-auto.mb-4.mt-1
...@@ -34,3 +34,7 @@ ...@@ -34,3 +34,7 @@
- if show_start_free_trial_messages?(namespace) - if show_start_free_trial_messages?(namespace)
- glm_content = namespace_for_user ? 'user-billing' : 'group-billing' - glm_content = namespace_for_user ? 'user-billing' : 'group-billing'
%p= link_to 'Start your free trial', new_trial_registration_path(glm_source: 'gitlab.com', glm_content: glm_content), class: 'btn btn-confirm gl-button', data: { qa_selector: 'start_your_free_trial' } %p= link_to 'Start your free trial', new_trial_registration_path(glm_source: 'gitlab.com', glm_content: glm_content), class: 'btn btn-confirm gl-button', data: { qa_selector: 'start_your_free_trial' }
- if show_extend_reactivate_trial_button?(namespace)
.gl-mt-3
.js-extend-reactivate-trial-button.gl-mt-3{ data: extend_reactivate_trial_button_data(namespace) }
...@@ -20,12 +20,22 @@ FactoryBot.define do ...@@ -20,12 +20,22 @@ FactoryBot.define do
trial_ends_on { Date.current.advance(days: 15) } trial_ends_on { Date.current.advance(days: 15) }
end end
trait :extended_trial do
active_trial
trial_extension_type { GitlabSubscription.trial_extension_types[:extended] }
end
trait :expired_trial do trait :expired_trial do
trial { true } trial { true }
trial_starts_on { Date.current.advance(days: -31) } trial_starts_on { Date.current.advance(days: -31) }
trial_ends_on { Date.current.advance(days: -1) } trial_ends_on { Date.current.advance(days: -1) }
end end
trait :reactivated_trial do
expired_trial
trial_extension_type { GitlabSubscription.trial_extension_types[:reactivated] }
end
trait :default do trait :default do
association :hosted_plan, factory: :default_plan association :hosted_plan, factory: :default_plan
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Billings > Extend / Reactivate Trial', :js do
include SubscriptionPortalHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:plan) { create(:free_plan) }
let_it_be(:plans_data) do
Gitlab::Json.parse(File.read(Rails.root.join('ee/spec/fixtures/gitlab_com_plans.json'))).map do |data|
data.deep_symbolize_keys
end
end
let(:initial_trial_end_date) { Date.current }
let(:extended_or_reactivated_trial_end_date) { initial_trial_end_date + 30.days }
before do
group.add_owner(user)
allow(Gitlab).to receive(:com?).and_return(true)
stub_ee_application_setting(should_check_namespace_plan: true)
stub_feature_flags(allow_extend_reactivate_trial: true)
stub_full_request("#{EE::SUBSCRIPTIONS_URL}/gitlab_plans?plan=#{plan.name}&namespace_id=#{group.id}")
.to_return(status: 200, body: plans_data.to_json)
stub_full_request("#{EE::SUBSCRIPTIONS_URL}/trials/extend_reactivate_trial", method: :put)
.to_return(status: 200)
sign_in(user)
end
shared_examples 'a non-reactivatable trial' do
before do
visit group_billings_path(group)
end
it 'does not display the "Reactivate trial" button' do
expect(page).not_to have_button('Reactivate trial')
end
end
shared_examples 'a non-extendable trial' do
before do
visit group_billings_path(group)
end
it 'does not display the "Extend trial" button' do
expect(page).not_to have_button('Extend trial')
end
end
shared_examples 'a reactivatable trial' do
before do
allow_next_instance_of(GitlabSubscriptions::ExtendReactivateTrialService) do |service|
group.gitlab_subscription.update!(trial_extension_type: GitlabSubscription.trial_extension_types[:reactivated],
end_date: extended_or_reactivated_trial_end_date,
trial_ends_on: extended_or_reactivated_trial_end_date)
end
visit group_billings_path(group)
end
it 'reactivates trial' do
expect(page).to have_content("trial expired on #{initial_trial_end_date}")
within '.billing-plan-header' do
click_button('Reactivate trial')
end
within '[data-testid="extend-reactivate-trial-modal"]' do
click_button('Reactivate trial')
end
wait_for_requests
expect(page).to have_content("trial will expire after #{extended_or_reactivated_trial_end_date}")
expect(page).not_to have_button('Reactivate trial')
end
end
shared_examples 'an extendable trial' do
before do
allow_next_instance_of(GitlabSubscriptions::ExtendReactivateTrialService) do |service|
group.gitlab_subscription.update!(trial_extension_type: GitlabSubscription.trial_extension_types[:extended],
end_date: initial_trial_end_date,
trial_ends_on: extended_or_reactivated_trial_end_date)
end
visit group_billings_path(group)
end
it 'extends the trial' do
expect(page).to have_content("trial will expire after #{initial_trial_end_date}")
within '.billing-plan-header' do
click_button('Extend trial')
end
within '[data-testid="extend-reactivate-trial-modal"]' do
click_button('Extend trial')
end
wait_for_requests
expect(page).to have_content("trial will expire after #{extended_or_reactivated_trial_end_date}")
expect(page).not_to have_button('Extend trial')
end
end
context 'with paid subscription' do
context 'when expired' do
let_it_be(:subscription) { create(:gitlab_subscription, :expired, hosted_plan: plan, namespace: group) }
it_behaves_like 'a non-reactivatable trial'
it_behaves_like 'a non-extendable trial'
context 'when the feature flag is disabled' do
before do
stub_feature_flags(allow_extend_reactivate_trial: false)
end
it_behaves_like 'a non-reactivatable trial'
it_behaves_like 'a non-extendable trial'
end
end
context 'when not expired' do
let_it_be(:subscription) { create(:gitlab_subscription, hosted_plan: plan, namespace: group) }
it_behaves_like 'a non-reactivatable trial'
it_behaves_like 'a non-extendable trial'
end
end
context 'without a subscription' do
it_behaves_like 'a non-reactivatable trial'
it_behaves_like 'a non-extendable trial'
end
context 'with active trial near the expiration date' do
let(:initial_trial_end_date) { Date.tomorrow }
let_it_be(:subscription) { create(:gitlab_subscription, :active_trial, trial_ends_on: Date.tomorrow, hosted_plan: plan, namespace: group) }
it_behaves_like 'an extendable trial'
it_behaves_like 'a non-reactivatable trial'
end
context 'with extended trial' do
let_it_be(:subscription) { create(:gitlab_subscription, :extended_trial, hosted_plan: plan, namespace: group) }
it_behaves_like 'a non-extendable trial'
it_behaves_like 'a non-reactivatable trial'
end
context 'with reactivated trial' do
let_it_be(:subscription) { create(:gitlab_subscription, :reactivated_trial, hosted_plan: plan, namespace: group) }
it_behaves_like 'a non-extendable trial'
it_behaves_like 'a non-reactivatable trial'
end
context 'with expired trial' do
let(:initial_trial_end_date) { Date.current.advance(days: -1) }
let_it_be(:subscription) { create(:gitlab_subscription, :expired_trial, hosted_plan: plan, namespace: group) }
it_behaves_like 'a reactivatable trial'
it_behaves_like 'a non-extendable trial'
end
end
...@@ -6,6 +6,7 @@ import SubscriptionTable from 'ee/billings/subscriptions/components/subscription ...@@ -6,6 +6,7 @@ import SubscriptionTable from 'ee/billings/subscriptions/components/subscription
import SubscriptionTableRow from 'ee/billings/subscriptions/components/subscription_table_row.vue'; import SubscriptionTableRow from 'ee/billings/subscriptions/components/subscription_table_row.vue';
import initialStore from 'ee/billings/subscriptions/store'; import initialStore from 'ee/billings/subscriptions/store';
import * as types from 'ee/billings/subscriptions/store/mutation_types'; import * as types from 'ee/billings/subscriptions/store/mutation_types';
import ExtendReactivateTrialButton from 'ee/trials/extend_reactivate_trial/components/extend_reactivate_trial_button.vue';
import { mockDataSubscription } from 'ee_jest/billings/mock_data'; import { mockDataSubscription } from 'ee_jest/billings/mock_data';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
...@@ -361,4 +362,33 @@ describe('SubscriptionTable component', () => { ...@@ -361,4 +362,33 @@ describe('SubscriptionTable component', () => {
expect(findRefreshSeatsButton().exists()).toBe(false); expect(findRefreshSeatsButton().exists()).toBe(false);
}); });
}); });
describe.each`
availableTrialAction | buttonVisible
${null} | ${false}
${'extend'} | ${true}
${'reactivate'} | ${true}
`(
'with availableTrialAction=$availableTrialAction',
({ availableTrialAction, buttonVisible }) => {
beforeEach(() => {
createComponentWithStore({
provide: {
namespaceId: 1,
availableTrialAction,
},
});
});
if (buttonVisible) {
it('renders the trial button', () => {
expect(wrapper.findComponent(ExtendReactivateTrialButton).isVisible()).toBe(true);
});
} else {
it('does not render the trial button', () => {
expect(wrapper.findComponent(ExtendReactivateTrialButton).exists()).toBe(false);
});
}
},
);
}); });
import { GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ExtendReactivateTrialButton from 'ee/trials/extend_reactivate_trial/components/extend_reactivate_trial_button.vue';
import {
i18n,
TRIAL_ACTION_EXTEND,
TRIAL_ACTION_REACTIVATE,
} from 'ee/trials/extend_reactivate_trial/constants';
import { sprintf } from '~/locale';
describe('ExtendReactivateTrialButton', () => {
let wrapper;
const createComponent = (props = {}) => {
return shallowMount(ExtendReactivateTrialButton, {
propsData: {
...props,
},
});
};
const findButton = () => wrapper.findComponent(GlButton);
const findModal = () => wrapper.findComponent(GlModal);
afterEach(() => {
wrapper.destroy();
});
describe('rendering', () => {
beforeEach(() => {
wrapper = createComponent({
namespaceId: 1,
action: TRIAL_ACTION_EXTEND,
planName: 'Ultimate',
});
});
it('does not have loading icon', () => {
expect(findButton().props('loading')).toBe(false);
});
});
describe('when extending trial', () => {
beforeEach(() => {
wrapper = createComponent({
namespaceId: 1,
action: TRIAL_ACTION_EXTEND,
planName: 'Ultimate',
});
});
it('has the "Extend trial" text on the button', () => {
expect(findButton().text()).toBe(i18n.extend.buttonText);
});
it('has the correct text in the modal', () => {
expect(findModal().text()).toBe(
sprintf(i18n.extend.modalText, { planName: 'Ultimate plan' }),
);
});
});
describe('when reactivating trial', () => {
beforeEach(() => {
wrapper = createComponent({
namespaceId: 1,
action: TRIAL_ACTION_REACTIVATE,
planName: 'Ultimate',
});
});
it('has the "Reactivate trial" text on the button', () => {
expect(findButton().text()).toBe(i18n.reactivate.buttonText);
});
it('has the correct text in the modal', () => {
expect(findModal().text()).toBe(
sprintf(i18n.reactivate.modalText, { planName: 'Ultimate plan' }),
);
});
});
});
...@@ -197,24 +197,20 @@ RSpec.describe EE::TrialHelper do ...@@ -197,24 +197,20 @@ RSpec.describe EE::TrialHelper do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when feature flag is enabled' do where(:can_extend_trial, :can_reactivate_trial, :result) do
where(:can_extend_trial, :can_reactivate_trial, :result) do false | false | false
false | false | false true | false | true
true | false | true false | true | true
false | true | true true | true | true
true | true | true end
end
with_them do
before do
stub_feature_flags(allow_extend_reactivate_trial: true)
allow(namespace).to receive(:can_extend_trial?).and_return(can_extend_trial)
allow(namespace).to receive(:can_reactivate_trial?).and_return(can_reactivate_trial)
end
it { is_expected.to eq(result) } with_them do
before do
allow(namespace).to receive(:can_extend_trial?).and_return(can_extend_trial)
allow(namespace).to receive(:can_reactivate_trial?).and_return(can_reactivate_trial)
end end
it { is_expected.to eq(result) }
end end
end end
...@@ -253,22 +249,20 @@ RSpec.describe EE::TrialHelper do ...@@ -253,22 +249,20 @@ RSpec.describe EE::TrialHelper do
end end
end end
context 'when feature flag is enabled' do context 'when trial can be extended' do
context 'when trial can be extended' do before do
before do allow(namespace).to receive(:can_extend_trial?).and_return(true)
allow(namespace).to receive(:can_extend_trial?).and_return(true)
end
it { is_expected.to eq({ namespace_id: 1, plan_name: 'Ultimate', action: 'extend' }) }
end end
context 'when trial can be reactivated' do it { is_expected.to eq({ namespace_id: 1, plan_name: 'Ultimate', action: 'extend' }) }
before do end
allow(namespace).to receive(:can_reactivate_trial?).and_return(true)
end
it { is_expected.to eq({ namespace_id: 1, plan_name: 'Ultimate', action: 'reactivate' }) } context 'when trial can be reactivated' do
before do
allow(namespace).to receive(:can_reactivate_trial?).and_return(true)
end end
it { is_expected.to eq({ namespace_id: 1, plan_name: 'Ultimate', action: 'reactivate' }) }
end end
end end
end end
...@@ -1261,22 +1261,20 @@ RSpec.describe Namespace do ...@@ -1261,22 +1261,20 @@ RSpec.describe Namespace do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when feature flag is enabled' do where(:trial_active, :trial_extended_or_reactivated, :can_extend_trial) do
where(:trial_active, :trial_extended_or_reactivated, :can_extend_trial) do false | false | false
false | false | false false | true | false
false | true | false true | false | true
true | false | true true | true | false
true | true | false end
end
with_them do
before do
allow(namespace).to receive(:trial_active?).and_return(trial_active)
allow(namespace).to receive(:trial_extended_or_reactivated?).and_return(trial_extended_or_reactivated)
end
it { is_expected.to be can_extend_trial } with_them do
before do
allow(namespace).to receive(:trial_active?).and_return(trial_active)
allow(namespace).to receive(:trial_extended_or_reactivated?).and_return(trial_extended_or_reactivated)
end end
it { is_expected.to be can_extend_trial }
end end
end end
...@@ -1296,36 +1294,34 @@ RSpec.describe Namespace do ...@@ -1296,36 +1294,34 @@ RSpec.describe Namespace do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
context 'when feature flag is enabled' do where(:trial_active, :never_had_trial, :trial_extended_or_reactivated, :free_plan, :can_reactivate_trial) do
where(:trial_active, :never_had_trial, :trial_extended_or_reactivated, :free_plan, :can_reactivate_trial) do false | false | false | false | false
false | false | false | false | false false | false | false | true | true
false | false | false | true | true false | false | true | false | false
false | false | true | false | false false | false | true | true | false
false | false | true | true | false false | true | false | false | false
false | true | false | false | false false | true | false | true | false
false | true | false | true | false false | true | true | false | false
false | true | true | false | false false | true | true | true | false
false | true | true | true | false true | false | false | false | false
true | false | false | false | false true | false | false | true | false
true | false | false | true | false true | false | true | false | false
true | false | true | false | false true | false | true | true | false
true | false | true | true | false true | true | false | false | false
true | true | false | false | false true | true | false | true | false
true | true | false | true | false true | true | true | false | false
true | true | true | false | false true | true | true | true | false
true | true | true | true | false end
end
with_them do
before do
allow(namespace).to receive(:trial_active?).and_return(trial_active)
allow(namespace).to receive(:never_had_trial?).and_return(never_had_trial)
allow(namespace).to receive(:trial_extended_or_reactivated?).and_return(trial_extended_or_reactivated)
allow(namespace).to receive(:free_plan?).and_return(free_plan)
end
it { is_expected.to be can_reactivate_trial } with_them do
before do
allow(namespace).to receive(:trial_active?).and_return(trial_active)
allow(namespace).to receive(:never_had_trial?).and_return(never_had_trial)
allow(namespace).to receive(:trial_extended_or_reactivated?).and_return(trial_extended_or_reactivated)
allow(namespace).to receive(:free_plan?).and_return(free_plan)
end end
it { is_expected.to be can_reactivate_trial }
end end
end end
......
...@@ -5178,6 +5178,27 @@ msgstr "" ...@@ -5178,6 +5178,27 @@ msgstr ""
msgid "BillingPlan|Upgrade for free" msgid "BillingPlan|Upgrade for free"
msgstr "" msgstr ""
msgid "Billings|%{planName} plan"
msgstr ""
msgid "Billings|An error occurred while extending your trial."
msgstr ""
msgid "Billings|An error occurred while reactivating your trial."
msgstr ""
msgid "Billings|By extending your trial, you will receive an additional 30 days of %{planName}. Your trial can be only extended once."
msgstr ""
msgid "Billings|By reactivating your trial, you will receive an additional 30 days of %{planName}. Your trial can be only reactivated once."
msgstr ""
msgid "Billings|Extend trial"
msgstr ""
msgid "Billings|Reactivate trial"
msgstr ""
msgid "Billings|Shared runners cannot be enabled until a valid credit card is on file." msgid "Billings|Shared runners cannot be enabled until a valid credit card is on file."
msgstr "" msgstr ""
......
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