Commit 6ddf2ab9 authored by Stan Hu's avatar Stan Hu

Merge branch 'astoicescu-implement-offer-eligible-and-ineligible' into 'master'

Add "free upgrade" marketing UX

See merge request gitlab-org/gitlab!51702
parents 0fc3b41c 0c6d62df
...@@ -19,21 +19,81 @@ ...@@ -19,21 +19,81 @@
} }
} }
$gutter-small: $gl-spacing-scale-6;
$gutter: $gl-spacing-scale-7;
$badge-height: $gl-spacing-scale-7;
.billing-plans { .billing-plans {
// This color is not part of the GitLab-UI/Pajamas specifications.
// We're using it only for marketing purposes
$highlight-color: #6e49cb;
margin-bottom: $gutter-small;
> * + * {
margin-top: $gutter-small;
}
.card-wrapper-has-badge {
.card {
@include gl-border-1;
@include gl-border-solid;
@include gl-rounded-top-left-none;
@include gl-rounded-top-right-none;
border-color: $highlight-color;
}
}
.card-badge {
@include gl-rounded-top-left-base;
@include gl-rounded-top-right-base;
@include gl-font-weight-bold;
@include gl-px-5;
@include gl-text-white;
background-color: $highlight-color;
// These border radii values are not defined in gitlab-ui,
// but they are consistent with the startup-*.scss .card overrides
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
line-height: $badge-height;
&-text {
@include gl-display-block;
@include gl-text-truncate;
}
}
.card { .card {
@include gl-mb-0;
&-active { &-active {
background-color: $gray-light; background-color: $gray-light;
} }
.card-body { .card-body {
.price-per-month { .price-description {
align-items: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
color: $blue-500; color: $blue-500;
font-size: 48px; font-size: 45px;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
line-height: 1; line-height: 1;
.price-rebate {
color: $blue-400;
font-size: 20px;
text-decoration: line-through;
}
.price-cut {
text-decoration: line-through;
}
.conditions { .conditions {
list-style: none; list-style: none;
font-size: $gl-font-size-large; font-size: $gl-font-size-large;
...@@ -42,15 +102,62 @@ ...@@ -42,15 +102,62 @@
} }
} }
.price-per-year { .price-conclusion {
@include gl-font-base;
color: $blue-500; color: $blue-500;
font-size: $gl-font-size-small;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
} }
} }
} }
@media (min-width: $breakpoint-md) {
.billing-plans {
@include gl-display-flex;
@include gl-flex-wrap;
@include gl-justify-content-space-between;
> * + * {
@include gl-mt-0;
}
.card-wrapper {
margin-bottom: $gutter-small;
padding-top: $badge-height;
width: calc(50% - #{$gutter-small} / 2);
&-has-badge {
@include gl-pt-0;
.card {
height: calc(100% - #{$badge-height});
}
}
}
.card {
@include gl-h-full;
}
}
}
@media (min-width: $breakpoint-lg) {
.billing-plans {
flex-wrap: nowrap;
> * + * {
margin-left: $gutter;
}
.card-wrapper {
@include gl-flex-fill-1;
@include gl-mb-0;
@include gl-overflow-hidden;
@include gl-w-auto;
}
}
}
.subscription-table { .subscription-table {
.flex-grid { .flex-grid {
.grid-cell { .grid-cell {
......
# frozen_string_literal: true # frozen_string_literal: true
module BillingPlansHelper module BillingPlansHelper
include Gitlab::Utils::StrongMemoize
def subscription_plan_info(plans_data, current_plan_code) def subscription_plan_info(plans_data, current_plan_code)
current_plan = plans_data.find { |plan| plan.code == current_plan_code && plan.current_subscription_plan? } current_plan = plans_data.find { |plan| plan.code == current_plan_code && plan.current_subscription_plan? }
current_plan || plans_data.find { |plan| plan.code == current_plan_code } current_plan || plans_data.find { |plan| plan.code == current_plan_code }
...@@ -10,6 +12,28 @@ module BillingPlansHelper ...@@ -10,6 +12,28 @@ module BillingPlansHelper
number_to_currency(value, unit: '$', strip_insignificant_zeros: true, format: "%u%n") number_to_currency(value, unit: '$', strip_insignificant_zeros: true, format: "%u%n")
end end
def upgrade_offer_type(namespace, plan)
return :no_offer if namespace.actual_plan_name != Plan::BRONZE || !offer_from_previous_tier?(namespace.id, plan.id)
upgrade_for_free?(namespace.id) ? :upgrade_for_free : :upgrade_for_offer
end
def has_upgrade?(upgrade_offer)
upgrade_offer == :upgrade_for_free || upgrade_offer == :upgrade_for_offer
end
def show_contact_sales_button?(purchase_link_action, upgrade_offer)
return false unless purchase_link_action == 'upgrade'
upgrade_offer == :upgrade_for_offer ||
(experiment_enabled?(:contact_sales_btn_in_app) && upgrade_offer == :no_offer)
end
def show_upgrade_button?(purchase_link_action, upgrade_offer)
purchase_link_action == 'upgrade' &&
(upgrade_offer == :no_offer || upgrade_offer == :upgrade_for_free)
end
def subscription_plan_data_attributes(namespace, plan) def subscription_plan_data_attributes(namespace, plan)
return {} unless namespace return {} unless namespace
...@@ -34,11 +58,6 @@ module BillingPlansHelper ...@@ -34,11 +58,6 @@ module BillingPlansHelper
namespace.group? && (namespace.actual_plan_name == Plan::FREE || namespace.trial_active?) namespace.group? && (namespace.actual_plan_name == Plan::FREE || namespace.trial_active?)
end end
def show_contact_sales_button?(purchase_link_action)
experiment_enabled?(:contact_sales_btn_in_app) &&
purchase_link_action == 'upgrade'
end
def experiment_tracking_data_for_button_click(button_label) def experiment_tracking_data_for_button_click(button_label)
return {} unless Gitlab::Experimentation.active?(:contact_sales_btn_in_app) return {} unless Gitlab::Experimentation.active?(:contact_sales_btn_in_app)
...@@ -139,4 +158,24 @@ module BillingPlansHelper ...@@ -139,4 +158,24 @@ module BillingPlansHelper
def billable_seats_href(group) def billable_seats_href(group)
group_seat_usage_path(group) group_seat_usage_path(group)
end end
def offer_from_previous_tier?(namespace_id, plan_id)
upgrade_plan_id = upgrade_plan_data(namespace_id)[:upgrade_plan_id]
return false unless upgrade_plan_id
upgrade_plan_id == plan_id
end
def upgrade_for_free?(namespace_id)
!!upgrade_plan_data(namespace_id)[:upgrade_for_free]
end
def upgrade_plan_data(namespace_id)
strong_memoize(:upgrade_plan_data) do
GitlabSubscriptions::PlanUpgradeService
.new(namespace_id: namespace_id)
.execute
end
end
end end
# frozen_string_literal: true
module GitlabSubscriptions
class PlanUpgradeService
def initialize(namespace_id:)
@namespace_id = namespace_id
end
def execute
result = client.plan_upgrade_offer(@namespace_id)
plan_id = result[:assisted_upgrade_plan_id] || result[:free_upgrade_plan_id] unless result[:eligible_for_free_upgrade].nil?
{
upgrade_for_free: result[:eligible_for_free_upgrade],
upgrade_plan_id: plan_id
}
end
private
def client
Gitlab::SubscriptionPortal::Client
end
end
end
- purchase_link = plan.purchase_link - purchase_link = plan.purchase_link
- plan_name = plan.name - plan_name = plan.name
- show_deprecated_plan = ::Feature.enabled?(:hide_deprecated_billing_plans) && plan.deprecated? - show_deprecated_plan = ::Feature.enabled?(:hide_deprecated_billing_plans) && plan.deprecated?
- has_upgrade = has_upgrade?(plan_offer_type)
- if show_deprecated_plan - if show_deprecated_plan
- plan_name += ' (Legacy)' - plan_name += ' (Legacy)'
- faq_link_url = 'https://about.gitlab.com/gitlab-com/#faq' - faq_link_url = 'https://about.gitlab.com/gitlab-com/#faq'
- faq_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: faq_link_url } - faq_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: faq_link_url }
.card.h-100{ class: ("card-active" if is_current || show_deprecated_plan) } .card-wrapper{ class: ("card-wrapper-has-badge" if has_upgrade) }
.card-header.gl-font-weight-bold.d-flex.flex-row.justify-content-between.flex-wrap - if has_upgrade
%div .card-badge
= plan_name %span.card-badge-text
- if is_current - case plan_offer_type
.text-muted - when :upgrade_for_free
= _("Current Plan") = s_("BillingPlans|Free upgrade!")
- else
= _("Upgrade offers available!")
.card{ class: ("card-active" if is_current || show_deprecated_plan) }
.card-header.gl-font-weight-bold.d-flex.flex-row.justify-content-between.flex-wrap
%div
= plan_name
- if is_current
.text-muted
= s_("BillingPlans|Current Plan")
.card-body .card-body
- if show_deprecated_plan - if show_deprecated_plan
= s_("The %{plan_name} is no longer available to purchase. For more information about how this will impact you, check our %{faq_link_start}frequently asked questions%{faq_link_end}.").html_safe % { plan_name: plan.name, faq_link_start: faq_link_start, faq_link_end: '</a>'.html_safe } = s_("The %{plan_name} is no longer available to purchase. For more information about how this will impact you, check our %{faq_link_start}frequently asked questions%{faq_link_end}.").html_safe % { plan_name: plan.name, faq_link_start: faq_link_start, faq_link_end: '</a>'.html_safe }
- else - else
.price-per-month .price-description
.gl-mr-2 .gl-mr-2.gl-display-flex.gl-align-items-center
= number_to_plan_currency(plan.price_per_month) - case plan_offer_type
- when :upgrade_for_free
%span.gl-mr-3.price-rebate
= number_to_plan_currency(plan.price_per_month)
%span
= number_to_plan_currency(plan.upgrade_price_per_month)
- when :upgrade_for_offer
%span.price-cut
= number_to_plan_currency(plan.price_per_month)
- else
%span
= number_to_plan_currency(plan.price_per_month)
%ul.conditions.gl-p-0.gl-my-auto %ul.conditions.gl-p-0.gl-my-auto
%li= s_("BillingPlans|per user") %li= s_("BillingPlans|per user")
%li= s_("BillingPlans|monthly") %li= s_("BillingPlans|monthly")
.price-per-year.text-left{ class: ("invisible" unless plan.price_per_year > 0) } .price-conclusion{ class: ("invisible" unless plan.price_per_year > 0) }
- price_per_year = number_to_plan_currency(plan.price_per_year) - case plan_offer_type
= s_("BillingPlans|billed annually at %{price_per_year}") % { price_per_year: price_per_year } - when :upgrade_for_free
= s_("BillingPlans|for the remainder of your subscription")
- else
- price_per_year = number_to_plan_currency(plan.price_per_year)
= s_("BillingPlans|billed annually at %{price_per_year}") % { price_per_year: price_per_year }
%hr.gl-my-3
%hr.gl-my-3 %ul.unstyled-list
- plan_feature_list(plan).each do |feature|
- feature_class = "gl-p-0!"
- if feature.highlight
- feature_class += " gl-font-weight-bold"
%li{ class: "#{feature_class}" }
= feature.title
%li.gl-p-0.gl-pt-3
- if plan.about_page_href
= link_to s_("BillingPlans|See all %{plan_name} features") % { plan_name: plan.name }, EE::SUBSCRIPTIONS_COMPARISON_URL
%ul.unstyled-list
- plan_feature_list(plan).each do |feature|
- feature_class = "gl-p-0!"
- if feature.highlight
- feature_class += " gl-font-weight-bold"
%li{ class: "#{feature_class}" }
= feature.title
%li.gl-p-0.gl-pt-3
- if plan.about_page_href
= link_to s_("BillingPlans|See all %{plan_name} features") % { plan_name: plan.name }, EE::SUBSCRIPTIONS_COMPARISON_URL
- if purchase_link
.card-footer .card-footer
.float-right{ class: ("invisible" unless purchase_link.action == 'upgrade' || is_current) } - cta_class = '-new' if use_new_purchase_flow?(namespace)
- if show_contact_sales_button?(purchase_link.action) - upgrade_button_classes = upgrade_button_css_classes(namespace, plan, is_current)
= link_to s_('BillingPlan|Contact sales'), "#{contact_sales_url}?test=inappcontactsales#{plan.code}", class: "btn btn-success-secondary gl-button", data: { **experiment_tracking_data_for_button_click('contact_sales') } - upgrade_button_text = plan_offer_type === :upgrade_for_free ? s_('BillingPlan|Upgrade for free') : s_('BillingPlan|Upgrade')
- cta_class = '-new' if use_new_purchase_flow?(namespace) - show_upgrade_button = show_upgrade_button?(purchase_link.action, plan_offer_type)
- upgrade_button_classes = upgrade_button_css_classes(namespace, plan, is_current) - show_contact_sales_button = show_contact_sales_button?(purchase_link.action, plan_offer_type)
= link_to s_('BillingPlan|Upgrade'), plan_purchase_or_upgrade_url(namespace, plan), class: "#{upgrade_button_classes} billing-cta-purchase#{cta_class}", data: { **experiment_tracking_data_for_button_click('upgrade') } .gl-min-h-7.gl-display-flex.gl-flex-wrap.gl-justify-content-end
- if show_contact_sales_button
= link_to s_('BillingPlan|Contact sales'), "#{contact_sales_url}?test=inappcontactsales#{plan.code}", class: [("btn gl-button"), (show_upgrade_button ? "btn-success-secondary" : "btn-success")], data: { **experiment_tracking_data_for_button_click('contact_sales') }
- if show_upgrade_button
= link_to upgrade_button_text, plan_purchase_or_upgrade_url(namespace, plan), class: "#{upgrade_button_classes} billing-cta-purchase#{cta_class} gl-ml-3", data: { **experiment_tracking_data_for_button_click('upgrade') }
...@@ -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 .billing-plan-header.content-block.center.gl-mb-5
.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
......
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
- if show_plans?(namespace) - if show_plans?(namespace)
- plans = billing_available_plans(plans_data, current_plan) - plans = billing_available_plans(plans_data, current_plan)
.billing-plans.gl-mt-5.row.justify-content-center .billing-plans
- plans.each do |plan| - plans.each do |plan|
- is_default_plan = current_plan.nil? && plan.default? - is_default_plan = current_plan.nil? && plan.default?
- is_current = plan.code == current_plan&.code || is_default_plan - is_current = plan.code == current_plan&.code || is_default_plan
.col-md-6.col-lg-3.gl-mb-5 = render 'shared/billings/billing_plan', namespace: namespace, plan: plan, is_current: is_current,
= render 'shared/billings/billing_plan', namespace: namespace, plan: plan, is_current: is_current plan_offer_type: upgrade_offer_type(namespace, plan)
- if namespace.gitlab_subscription&.has_a_paid_hosted_plan? - if namespace.gitlab_subscription&.has_a_paid_hosted_plan?
.center.gl-mb-7 .center.gl-mb-7
......
...@@ -45,6 +45,35 @@ module Gitlab ...@@ -45,6 +45,35 @@ module Gitlab
end end
end end
def plan_upgrade_offer(namespace_id)
query = <<~GQL
{
subscription(namespaceId: "#{namespace_id}") {
eoaStarterBronzeEligible
assistedUpgradePlanId
freeUpgradePlanId
}
}
GQL
response = http_post("graphql", admin_headers, { query: query }).dig(:data)
if response['errors'].blank?
eligible = response.dig('data', 'subscription', 'eoaStarterBronzeEligible')
assisted_upgrade = response.dig('data', 'subscription', 'assistedUpgradePlanId')
free_upgrade = response.dig('data', 'subscription', 'freeUpgradePlanId')
{
success: true,
eligible_for_free_upgrade: eligible,
assisted_upgrade_plan_id: assisted_upgrade,
free_upgrade_plan_id: free_upgrade
}
else
{ success: false }
end
end
private private
def http_get(path, headers) def http_get(path, headers)
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Billing plan pages', :feature do RSpec.describe 'Billing plan pages', :feature do
include StubRequests include StubRequests
include SubscriptionPortalHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:namespace) { user.namespace } let(:namespace) { user.namespace }
...@@ -22,6 +23,7 @@ RSpec.describe 'Billing plan pages', :feature do ...@@ -22,6 +23,7 @@ RSpec.describe 'Billing plan pages', :feature do
stub_experiment_for_subject(contact_sales_btn_in_app: true) stub_experiment_for_subject(contact_sales_btn_in_app: true)
stub_full_request("#{EE::SUBSCRIPTIONS_URL}/gitlab_plans?plan=#{plan.name}&namespace_id=#{namespace.id}") stub_full_request("#{EE::SUBSCRIPTIONS_URL}/gitlab_plans?plan=#{plan.name}&namespace_id=#{namespace.id}")
.to_return(status: 200, body: plans_data.to_json) .to_return(status: 200, body: plans_data.to_json)
stub_eoa_eligibility_request(namespace.id)
stub_application_setting(check_namespace_plan: true) stub_application_setting(check_namespace_plan: true)
allow(Gitlab).to receive(:com?) { true } allow(Gitlab).to receive(:com?) { true }
gitlab_sign_in(user) gitlab_sign_in(user)
...@@ -159,8 +161,7 @@ RSpec.describe 'Billing plan pages', :feature do ...@@ -159,8 +161,7 @@ RSpec.describe 'Billing plan pages', :feature do
expect(action).not_to have_link('Upgrade') expect(action).not_to have_link('Upgrade')
expect(action).not_to have_css('.disabled') expect(action).not_to have_css('.disabled')
when 'current_plan' when 'current_plan'
expect(action).to have_link('Upgrade') expect(action).not_to have_link('Upgrade')
expect(action).to have_css('.disabled')
when 'upgrade' when 'upgrade'
expect(action).to have_link('Upgrade') expect(action).to have_link('Upgrade')
expect(action).not_to have_css('.disabled') expect(action).not_to have_css('.disabled')
...@@ -254,7 +255,6 @@ RSpec.describe 'Billing plan pages', :feature do ...@@ -254,7 +255,6 @@ RSpec.describe 'Billing plan pages', :feature do
expect(action).not_to have_link('Upgrade') expect(action).not_to have_link('Upgrade')
expect(action).not_to have_css('.disabled') expect(action).not_to have_css('.disabled')
when 'current_plan' when 'current_plan'
expect(action).to have_link('Upgrade')
expect(action).not_to have_css('.disabled') expect(action).not_to have_css('.disabled')
when 'upgrade' when 'upgrade'
expect(action).to have_link('Upgrade') expect(action).to have_link('Upgrade')
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Groups > Billing', :js do RSpec.describe 'Groups > Billing', :js do
include StubRequests include StubRequests
include SubscriptionPortalHelpers
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
...@@ -18,6 +19,7 @@ RSpec.describe 'Groups > Billing', :js do ...@@ -18,6 +19,7 @@ RSpec.describe 'Groups > Billing', :js do
end end
before do before do
stub_eoa_eligibility_request(group.id)
stub_full_request("#{EE::SUBSCRIPTIONS_URL}/gitlab_plans?plan=#{plan}&namespace_id=#{group.id}") stub_full_request("#{EE::SUBSCRIPTIONS_URL}/gitlab_plans?plan=#{plan}&namespace_id=#{group.id}")
.with(headers: { 'Accept' => 'application/json' }) .with(headers: { 'Accept' => 'application/json' })
.to_return(status: 200, body: File.new(Rails.root.join('ee/spec/fixtures/gitlab_com_plans.json'))) .to_return(status: 200, body: File.new(Rails.root.join('ee/spec/fixtures/gitlab_com_plans.json')))
......
...@@ -8,7 +8,7 @@ RSpec.describe BillingPlansHelper do ...@@ -8,7 +8,7 @@ RSpec.describe BillingPlansHelper do
let(:group) { build(:group) } let(:group) { build(:group) }
let(:plan) do let(:plan) do
Hashie::Mash.new(id: 'external-paid-plan-hash-code', name: 'Bronze Plan') OpenStruct.new(id: 'external-paid-plan-hash-code', name: 'Bronze Plan')
end end
context 'when group and plan with ID present' do context 'when group and plan with ID present' do
...@@ -61,7 +61,7 @@ RSpec.describe BillingPlansHelper do ...@@ -61,7 +61,7 @@ RSpec.describe BillingPlansHelper do
end end
context 'when plan with ID not present' do context 'when plan with ID not present' do
let(:plan) { Hashie::Mash.new(id: nil, name: 'Bronze Plan') } let(:plan) { OpenStruct.new(id: nil, name: 'Bronze Plan') }
it 'returns data attributes without upgrade href' do it 'returns data attributes without upgrade href' do
add_seats_href = "#{EE::SUBSCRIPTIONS_URL}/gitlab/namespaces/#{group.id}/extra_seats" add_seats_href = "#{EE::SUBSCRIPTIONS_URL}/gitlab/namespaces/#{group.id}/extra_seats"
...@@ -149,16 +149,77 @@ RSpec.describe BillingPlansHelper do ...@@ -149,16 +149,77 @@ RSpec.describe BillingPlansHelper do
end end
end end
describe '#upgrade_offer_type' do
using RSpec::Parameterized::TableSyntax
let(:plan) { OpenStruct.new({ id: '123456789' }) }
context 'when plan has a valid property' do
where(:plan_name, :for_free, :plan_id, :result) do
Plan::BRONZE | true | '123456789' | :upgrade_for_free
Plan::BRONZE | true | '987654321' | :no_offer
Plan::BRONZE | true | nil | :no_offer
Plan::BRONZE | false | '123456789' | :upgrade_for_offer
Plan::BRONZE | false | nil | :no_offer
Plan::BRONZE | nil | nil | :no_offer
Plan::SILVER | nil | nil | :no_offer
nil | true | nil | :no_offer
end
with_them do
let(:namespace) do
OpenStruct.new(
{
actual_plan_name: plan_name,
id: '000000000'
}
)
end
before do
allow_next_instance_of(GitlabSubscriptions::PlanUpgradeService) do |instance|
expect(instance).to receive(:execute).once.and_return({
upgrade_for_free: for_free,
upgrade_plan_id: plan_id
})
end
end
subject { helper.upgrade_offer_type(namespace, plan) }
it { is_expected.to eq(result) }
end
end
end
describe '#has_upgrade?' do
using RSpec::Parameterized::TableSyntax
where(:offer_type, :result) do
:no_offer | false
:upgrade_for_free | true
:upgrade_for_offer | true
end
with_them do
subject { helper.has_upgrade?(offer_type) }
it { is_expected.to eq(result) }
end
end
describe '#show_contact_sales_button?' do describe '#show_contact_sales_button?' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
where(:experiment_enabled, :link_action, :result) do where(:experiment_enabled, :link_action, :upgrade_offer, :result) do
true | 'downgrade' | false true | 'upgrade' | :no_offer | true
true | 'current' | false true | 'upgrade' | :upgrade_for_offer | true
true | 'upgrade' | true true | 'no_upgrade' | :no_offer | false
false | 'downgrade' | false true | 'no_upgrade' | :upgrade_for_offer | false
false | 'current' | false false | 'upgrade' | :no_offer | false
false | 'upgrade' | false false | 'upgrade' | :upgrade_for_offer | true
false | 'no_upgrade' | :no_offer | false
false | 'no_upgrade' | :upgrade_for_offer | false
end end
with_them do with_them do
...@@ -166,7 +227,26 @@ RSpec.describe BillingPlansHelper do ...@@ -166,7 +227,26 @@ RSpec.describe BillingPlansHelper do
allow(helper).to receive(:experiment_enabled?).with(:contact_sales_btn_in_app).and_return(experiment_enabled) allow(helper).to receive(:experiment_enabled?).with(:contact_sales_btn_in_app).and_return(experiment_enabled)
end end
subject { helper.show_contact_sales_button?(link_action) } subject { helper.show_contact_sales_button?(link_action, upgrade_offer) }
it { is_expected.to eq(result) }
end
end
describe '#show_upgrade_button?' do
using RSpec::Parameterized::TableSyntax
where(:link_action, :upgrade_offer, :result) do
'upgrade' | :no_offer | true
'upgrade' | :upgrade_for_free | true
'upgrade' | :upgrade_for_offer | false
'no_upgrade' | :no_offer | false
'no_upgrade' | :upgrade_for_free | false
'no_upgrade' | :upgrade_for_offer | false
end
with_them do
subject { helper.show_upgrade_button?(link_action, upgrade_offer) }
it { is_expected.to eq(result) } it { is_expected.to eq(result) }
end end
...@@ -277,7 +357,7 @@ RSpec.describe BillingPlansHelper do ...@@ -277,7 +357,7 @@ RSpec.describe BillingPlansHelper do
end end
with_them do with_them do
let(:namespace) { Hashie::Mash.new(trial_active: trial_active) } let(:namespace) { OpenStruct.new(trial_active: trial_active) }
subject { helper.upgrade_button_css_classes(namespace, plan, is_current_plan) } subject { helper.upgrade_button_css_classes(namespace, plan, is_current_plan) }
...@@ -305,7 +385,7 @@ RSpec.describe BillingPlansHelper do ...@@ -305,7 +385,7 @@ RSpec.describe BillingPlansHelper do
end end
context 'when namespace is on an active plan' do context 'when namespace is on an active plan' do
let(:current_plan) { Hashie::Mash.new(code: 'silver') } let(:current_plan) { OpenStruct.new(code: 'silver') }
it 'returns plans without deprecated' do it 'returns plans without deprecated' do
expect(helper.billing_available_plans(plans_data, nil)).to eq([plan]) expect(helper.billing_available_plans(plans_data, nil)).to eq([plan])
...@@ -313,7 +393,7 @@ RSpec.describe BillingPlansHelper do ...@@ -313,7 +393,7 @@ RSpec.describe BillingPlansHelper do
end end
context 'when namespace is on a deprecated plan' do context 'when namespace is on a deprecated plan' do
let(:current_plan) { Hashie::Mash.new(code: 'bronze') } let(:current_plan) { OpenStruct.new(code: 'bronze') }
it 'returns plans with a deprecated plan' do it 'returns plans with a deprecated plan' do
expect(helper.billing_available_plans(plans_data, current_plan)).to eq(plans_data) expect(helper.billing_available_plans(plans_data, current_plan)).to eq(plans_data)
...@@ -321,7 +401,7 @@ RSpec.describe BillingPlansHelper do ...@@ -321,7 +401,7 @@ RSpec.describe BillingPlansHelper do
end end
context 'when namespace is on a deprecated plan that has hide_deprecated_card set to true' do context 'when namespace is on a deprecated plan that has hide_deprecated_card set to true' do
let(:current_plan) { Hashie::Mash.new(code: 'bronze') } let(:current_plan) { OpenStruct.new(code: 'bronze') }
let(:deprecated_plan) { double('Plan', deprecated?: true, code: 'bronze', hide_deprecated_card?: true) } let(:deprecated_plan) { double('Plan', deprecated?: true, code: 'bronze', hide_deprecated_card?: true) }
it 'returns plans without the deprecated plan' do it 'returns plans without the deprecated plan' do
...@@ -330,7 +410,7 @@ RSpec.describe BillingPlansHelper do ...@@ -330,7 +410,7 @@ RSpec.describe BillingPlansHelper do
end end
context 'when namespace is on a plan that has hide_deprecated_card set to true, but deprecated? is false' do context 'when namespace is on a plan that has hide_deprecated_card set to true, but deprecated? is false' do
let(:current_plan) { Hashie::Mash.new(code: 'silver') } let(:current_plan) { OpenStruct.new(code: 'silver') }
let(:plan) { double('Plan', deprecated?: false, code: 'silver', hide_deprecated_card?: true) } let(:plan) { double('Plan', deprecated?: false, code: 'silver', hide_deprecated_card?: true) }
it 'returns plans with the deprecated plan' do it 'returns plans with the deprecated plan' do
......
...@@ -137,4 +137,100 @@ RSpec.describe Gitlab::SubscriptionPortal::Client do ...@@ -137,4 +137,100 @@ RSpec.describe Gitlab::SubscriptionPortal::Client do
expect(result).to eq({ errors: ["invalid activation code"], success: false }) expect(result).to eq({ errors: ["invalid activation code"], success: false })
end end
end end
describe '#plan_upgrade_offer' do
let(:namespace_id) { 111 }
let(:headers) do
{
"Accept" => "application/json",
"Content-Type" => "application/json",
"X-Admin-Email" => "gl_com_api@gitlab.com",
"X-Admin-Token" => "customer_admin_token"
}
end
let(:params) do
{ query: <<~GQL
{
subscription(namespaceId: "{:namespace_id=>#{namespace_id}}") {
eoaStarterBronzeEligible
assistedUpgradePlanId
freeUpgradePlanId
}
}
GQL
}
end
subject(:plan_upgrade_offer) { described_class.plan_upgrade_offer(namespace_id: namespace_id) }
context 'when the response contains errors' do
before do
expect(described_class).to receive(:http_post).with('graphql', headers, params).and_return(response)
end
let(:response) do
{
success: true,
data: {
'errors' => [{ 'message' => 'this will be ignored' }]
}
}
end
it 'returns a failure' do
expect(plan_upgrade_offer).to eq({ success: false })
end
end
context 'when the response does not contain errors' do
using RSpec::Parameterized::TableSyntax
where(:eligible, :assisted_plan_id, :free_plan_id) do
true | '111111' | '111111'
true | '111111' | nil
true | nil | '111111'
end
with_them do
before do
allow(described_class).to receive(:http_post).and_return({
success: true,
data: { "data" => { "subscription" => {
"eoaStarterBronzeEligible" => eligible,
"assistedUpgradePlanId" => assisted_plan_id,
"freeUpgradePlanId" => free_plan_id
} } }
})
end
it 'returns the correct response' do
expect(plan_upgrade_offer).to eq({
success: true,
eligible_for_free_upgrade: eligible,
assisted_upgrade_plan_id: assisted_plan_id,
free_upgrade_plan_id: free_plan_id
})
end
end
context 'when subscription is nil' do
before do
allow(described_class).to receive(:http_post).and_return({
success: true,
data: { "data" => { "subscription" => nil } }
})
end
it 'returns the correct response' do
expect(plan_upgrade_offer).to eq({
success: true,
eligible_for_free_upgrade: nil,
assisted_upgrade_plan_id: nil,
free_upgrade_plan_id: nil
})
end
end
end
end
end end
...@@ -10,7 +10,7 @@ RSpec.describe FetchSubscriptionPlansService do ...@@ -10,7 +10,7 @@ RSpec.describe FetchSubscriptionPlansService do
let(:plan) { 'bronze' } let(:plan) { 'bronze' }
let(:response_mock) { double(body: [{ 'foo' => 'bar' }].to_json) } let(:response_mock) { double(body: [{ 'foo' => 'bar' }].to_json) }
context 'when successully fetching plans data' do context 'when successfully fetching plans data' do
it 'returns parsed JSON' do it 'returns parsed JSON' do
expect(Gitlab::HTTP).to receive(:get) expect(Gitlab::HTTP).to receive(:get)
.with( .with(
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSubscriptions::PlanUpgradeService do
subject(:execute) { described_class.new(namespace_id: namespace_id).execute }
let(:namespace_id) { '111' }
describe '#execute' do
using RSpec::Parameterized::TableSyntax
before do
allow(Gitlab::SubscriptionPortal::Client).to receive(:plan_upgrade_offer).and_return(response)
end
context 'when the response is a failure' do
let(:response) { { success: false } }
it 'returns nil values' do
expect(execute).to eq({
upgrade_for_free: nil,
upgrade_plan_id: nil
})
end
end
context 'when the response is successful' do
where(:eligible, :assisted_id, :free_id, :plan_id) do
true | '111' | '222' | '111'
true | nil | '222' | '222'
true | '111' | nil | '111'
true | nil | nil | nil
false | '111' | '222' | '111'
false | '111' | nil | '111'
false | nil | '222' | '222'
nil | '111' | '222' | nil
end
with_them do
let(:response) do
{
success: true,
eligible_for_free_upgrade: eligible,
assisted_upgrade_plan_id: assisted_id,
free_upgrade_plan_id: free_id
}
end
before do
expect(Gitlab::SubscriptionPortal::Client).to receive(:plan_upgrade_offer).once.and_return(response)
end
it 'returns the correct values' do
expect(execute).to eq({
upgrade_for_free: eligible,
upgrade_plan_id: plan_id
})
end
end
end
end
end
# frozen_string_literal: true
module SubscriptionPortalHelpers
include StubRequests
def stub_eoa_eligibility_request(namespace_id)
stub_full_request("#{EE::SUBSCRIPTIONS_URL}/graphql", method: :post)
.with(
body: "{\"query\":\"{\\n subscription(namespaceId: \\\"#{namespace_id}\\\") {\\n eoaStarterBronzeEligible\\n assistedUpgradePlanId\\n freeUpgradePlanId\\n }\\n}\\n\"}",
headers: {
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Admin-Email' => EE::SUBSCRIPTION_PORTAL_ADMIN_EMAIL,
'X-Admin-Token' => EE::SUBSCRIPTION_PORTAL_ADMIN_TOKEN
}
)
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: stubbed_eoa_eligibility_response_body
)
end
private
def stubbed_eoa_eligibility_response_body
{
"data": {
"subscription": {
"eoaStarterBronzeEligible": false,
"assistedUpgradePlanId": nil,
"freeUpgradePlanId": nil
}
}
}.to_json
end
end
...@@ -4457,6 +4457,12 @@ msgstr "" ...@@ -4457,6 +4457,12 @@ msgstr ""
msgid "BillingPlans|Congratulations, your free trial is activated." msgid "BillingPlans|Congratulations, your free trial is activated."
msgstr "" msgstr ""
msgid "BillingPlans|Current Plan"
msgstr ""
msgid "BillingPlans|Free upgrade!"
msgstr ""
msgid "BillingPlans|If you would like to downgrade your plan please contact %{support_link_start}Customer Support%{support_link_end}." msgid "BillingPlans|If you would like to downgrade your plan please contact %{support_link_start}Customer Support%{support_link_end}."
msgstr "" msgstr ""
...@@ -4490,6 +4496,9 @@ msgstr "" ...@@ -4490,6 +4496,9 @@ msgstr ""
msgid "BillingPlans|billed annually at %{price_per_year}" msgid "BillingPlans|billed annually at %{price_per_year}"
msgstr "" msgstr ""
msgid "BillingPlans|for the remainder of your subscription"
msgstr ""
msgid "BillingPlans|frequently asked questions" msgid "BillingPlans|frequently asked questions"
msgstr "" msgstr ""
...@@ -4505,6 +4514,9 @@ msgstr "" ...@@ -4505,6 +4514,9 @@ msgstr ""
msgid "BillingPlan|Upgrade" msgid "BillingPlan|Upgrade"
msgstr "" msgstr ""
msgid "BillingPlan|Upgrade for free"
msgstr ""
msgid "Billing|An email address is only visible for users with public emails." msgid "Billing|An email address is only visible for users with public emails."
msgstr "" msgstr ""
...@@ -8510,9 +8522,6 @@ msgstr "" ...@@ -8510,9 +8522,6 @@ msgstr ""
msgid "Current Branch" msgid "Current Branch"
msgstr "" msgstr ""
msgid "Current Plan"
msgstr ""
msgid "Current Project" msgid "Current Project"
msgstr "" msgstr ""
...@@ -30638,6 +30647,9 @@ msgstr "" ...@@ -30638,6 +30647,9 @@ msgstr ""
msgid "Updating" msgid "Updating"
msgstr "" msgstr ""
msgid "Upgrade offers available!"
msgstr ""
msgid "Upgrade your plan" msgid "Upgrade your plan"
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