Commit e266a52b authored by Josianne Hyson's avatar Josianne Hyson

Fetch purchase eligibility status from Customers

Rather than re-defining the eligibility logic in the GitLab application,
delegate this decision to the customers app so that this logic is only
defined in one place. This will stop the logic from being different on
each platform.

MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53857
Issue: https://gitlab.com/gitlab-org/customers-gitlab-com/-/issues/2344
parent 3706a222
...@@ -4,6 +4,8 @@ class SubscriptionsController < ApplicationController ...@@ -4,6 +4,8 @@ class SubscriptionsController < ApplicationController
layout 'checkout' layout 'checkout'
skip_before_action :authenticate_user!, only: [:new, :buy_minutes] skip_before_action :authenticate_user!, only: [:new, :buy_minutes]
before_action :load_eligible_groups, only: %i[new buy_minutes]
feature_category :purchase feature_category :purchase
content_security_policy do |p| content_security_policy do |p|
...@@ -44,15 +46,10 @@ class SubscriptionsController < ApplicationController ...@@ -44,15 +46,10 @@ class SubscriptionsController < ApplicationController
def create def create
current_user.update(setup_for_company: true) if params[:setup_for_company] current_user.update(setup_for_company: true) if params[:setup_for_company]
group = params[:selected_group] ? find_group : create_group
if params[:selected_group] return not_found if group.nil?
group = current_user.manageable_groups_eligible_for_subscription.find(params[:selected_group]) return render json: group.errors.to_json unless group.persisted?
else
name = Namespace.clean_name(params[:setup_for_company] ? customer_params[:company] : current_user.name)
path = Namespace.clean_path(name)
group = Groups::CreateService.new(current_user, name: name, path: path).execute
return render json: group.errors.to_json unless group.persisted?
end
response = Subscriptions::CreateService.new( response = Subscriptions::CreateService.new(
current_user, current_user,
...@@ -85,6 +82,23 @@ class SubscriptionsController < ApplicationController ...@@ -85,6 +82,23 @@ class SubscriptionsController < ApplicationController
params.require(:subscription).permit(:plan_id, :payment_method_id, :quantity) params.require(:subscription).permit(:plan_id, :payment_method_id, :quantity)
end end
def find_group
selected_group = current_user.manageable_groups.top_most.find(params[:selected_group])
result = GitlabSubscriptions::FilterPurchaseEligibleNamespacesService
.new(user: current_user, namespaces: Array(selected_group))
.execute
result.success? ? result.payload.first : nil
end
def create_group
name = Namespace.clean_name(params[:setup_for_company] ? customer_params[:company] : current_user.name)
path = Namespace.clean_path(name)
Groups::CreateService.new(current_user, name: name, path: path).execute
end
def client def client
Gitlab::SubscriptionPortal::Client Gitlab::SubscriptionPortal::Client
end end
...@@ -99,4 +113,16 @@ class SubscriptionsController < ApplicationController ...@@ -99,4 +113,16 @@ class SubscriptionsController < ApplicationController
store_location_for :user, request.fullpath store_location_for :user, request.fullpath
redirect_to new_user_registration_path(redirect_from: from) redirect_to new_user_registration_path(redirect_from: from)
end end
def load_eligible_groups
return unless current_user
candidate_groups = current_user.manageable_groups.top_most.with_counts(archived: false)
result = GitlabSubscriptions::FilterPurchaseEligibleNamespacesService
.new(user: current_user, namespaces: candidate_groups)
.execute
@eligible_groups = result.success? ? result.payload : []
end
end end
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module SubscriptionsHelper module SubscriptionsHelper
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
def subscription_data def subscription_data(eligible_groups)
{ {
setup_for_company: (current_user.setup_for_company == true).to_s, setup_for_company: (current_user.setup_for_company == true).to_s,
full_name: current_user.name, full_name: current_user.name,
...@@ -12,7 +12,7 @@ module SubscriptionsHelper ...@@ -12,7 +12,7 @@ module SubscriptionsHelper
plan_id: params[:plan_id], plan_id: params[:plan_id],
namespace_id: params[:namespace_id], namespace_id: params[:namespace_id],
new_user: new_user?.to_s, new_user: new_user?.to_s,
group_data: group_data.to_json group_data: present_groups(eligible_groups).to_json
} }
end end
...@@ -64,8 +64,8 @@ module SubscriptionsHelper ...@@ -64,8 +64,8 @@ module SubscriptionsHelper
end end
end end
def group_data def present_groups(groups)
current_user.manageable_groups_eligible_for_subscription.with_counts(archived: false).map do |namespace| groups.map do |namespace|
{ {
id: namespace.id, id: namespace.id,
name: namespace.name, name: namespace.name,
......
...@@ -47,10 +47,6 @@ module EE ...@@ -47,10 +47,6 @@ module EE
.where(plans: { name: [nil, *::Plan.default_plans] }) .where(plans: { name: [nil, *::Plan.default_plans] })
end end
scope :eligible_for_subscription, -> do
top_most.in_active_trial.or(top_most.in_default_plan)
end
scope :eligible_for_trial, -> do scope :eligible_for_trial, -> do
left_joins(gitlab_subscription: :hosted_plan) left_joins(gitlab_subscription: :hosted_plan)
.where( .where(
......
...@@ -255,10 +255,6 @@ module EE ...@@ -255,10 +255,6 @@ module EE
.any? .any?
end end
def manageable_groups_eligible_for_subscription
manageable_groups.eligible_for_subscription.order(:name)
end
def manageable_groups_eligible_for_trial def manageable_groups_eligible_for_trial
manageable_groups.eligible_for_trial.order(:name) manageable_groups.eligible_for_trial.order(:name)
end end
......
- page_title _('Buy CI Minutes') - page_title _('Buy CI Minutes')
#js-buy-minutes{ data: subscription_data } #js-buy-minutes{ data: subscription_data(@eligible_groups) }
- page_title _('Checkout') - page_title _('Checkout')
#js-new-subscription{ data: subscription_data } #js-new-subscription{ data: subscription_data(@eligible_groups) }
...@@ -20,7 +20,7 @@ RSpec.describe SubscriptionsController do ...@@ -20,7 +20,7 @@ RSpec.describe SubscriptionsController do
end end
describe 'GET #new' do describe 'GET #new' do
subject { get :new, params: { plan_id: 'bronze_id' } } subject(:get_new) { get :new, params: { plan_id: 'bronze_id' } }
it_behaves_like 'unauthenticated subscription request', 'checkout' it_behaves_like 'unauthenticated subscription request', 'checkout'
...@@ -31,11 +31,41 @@ RSpec.describe SubscriptionsController do ...@@ -31,11 +31,41 @@ RSpec.describe SubscriptionsController do
it { is_expected.to render_template 'layouts/checkout' } it { is_expected.to render_template 'layouts/checkout' }
it { is_expected.to render_template :new } it { is_expected.to render_template :new }
context 'when there are groups eligible for the subscription' do
let_it_be(:group) { create(:group) }
before do
group.add_owner(user)
allow_next_instance_of(GitlabSubscriptions::FilterPurchaseEligibleNamespacesService, user: user, namespaces: [group]) do |instance|
allow(instance).to receive(:execute).and_return(instance_double(ServiceResponse, success?: true, payload: [group]))
end
end
it 'assigns the eligible groups for the subscription' do
get_new
expect(assigns(:eligible_groups)).to eq [group]
end
end
context 'when there are no eligible groups for the subscription' do
it 'assigns eligible groups as an empty array' do
allow_next_instance_of(GitlabSubscriptions::FilterPurchaseEligibleNamespacesService, user: user, namespaces: []) do |instance|
allow(instance).to receive(:execute).and_return(instance_double(ServiceResponse, success?: true, payload: []))
end
get_new
expect(assigns(:eligible_groups)).to eq []
end
end
end end
end end
describe 'GET #buy_minutes' do describe 'GET #buy_minutes' do
subject { get :buy_minutes, params: { plan_id: 'bronze_id' } } subject(:buy_minutes) { get :buy_minutes, params: { plan_id: 'bronze_id' } }
it_behaves_like 'unauthenticated subscription request', 'buy_minutes' it_behaves_like 'unauthenticated subscription request', 'buy_minutes'
...@@ -46,6 +76,36 @@ RSpec.describe SubscriptionsController do ...@@ -46,6 +76,36 @@ RSpec.describe SubscriptionsController do
it { is_expected.to render_template 'layouts/checkout' } it { is_expected.to render_template 'layouts/checkout' }
it { is_expected.to render_template :buy_minutes } it { is_expected.to render_template :buy_minutes }
context 'when there are groups eligible for the subscription' do
let_it_be(:group) { create(:group) }
before do
group.add_owner(user)
allow_next_instance_of(GitlabSubscriptions::FilterPurchaseEligibleNamespacesService, user: user, namespaces: [group]) do |instance|
allow(instance).to receive(:execute).and_return(instance_double(ServiceResponse, success?: true, payload: [group]))
end
end
it 'assigns the eligible groups for the subscription' do
buy_minutes
expect(assigns(:eligible_groups)).to eq [group]
end
end
context 'when there are no eligible groups for the subscription' do
it 'assigns eligible groups as an empty array' do
allow_next_instance_of(GitlabSubscriptions::FilterPurchaseEligibleNamespacesService, user: user, namespaces: []) do |instance|
allow(instance).to receive(:execute).and_return(instance_double(ServiceResponse, success?: true, payload: []))
end
buy_minutes
expect(assigns(:eligible_groups)).to eq []
end
end
end end
context 'with :new_route_ci_minutes_purchase disabled' do context 'with :new_route_ci_minutes_purchase disabled' do
...@@ -218,7 +278,6 @@ RSpec.describe SubscriptionsController do ...@@ -218,7 +278,6 @@ RSpec.describe SubscriptionsController do
end end
context 'when selecting an existing group' do context 'when selecting an existing group' do
let_it_be(:selected_group) { create(:group) }
let(:params) do let(:params) do
{ {
selected_group: selected_group.id, selected_group: selected_group.id,
...@@ -227,21 +286,63 @@ RSpec.describe SubscriptionsController do ...@@ -227,21 +286,63 @@ RSpec.describe SubscriptionsController do
} }
end end
before do context 'when the selected group is eligible for a new subscription' do
selected_group.add_owner(user) let_it_be(:selected_group) { create(:group) }
end
before do
selected_group.add_owner(user)
allow_next_instance_of(
GitlabSubscriptions::FilterPurchaseEligibleNamespacesService,
user: user,
namespaces: [selected_group]
) do |instance|
allow(instance)
.to receive(:execute)
.and_return(instance_double(ServiceResponse, success?: true, payload: [selected_group]))
end
end
it 'does not create a group' do it 'does not create a group' do
expect { subject }.to not_change { Group.count } expect { subject }.to not_change { Group.count }
end
it 'returns the selected group location in JSON format' do
subject
plan_id = params[:subscription][:plan_id]
quantity = params[:subscription][:quantity]
expect(response.body).to eq({ location: "/#{selected_group.path}?plan_id=#{plan_id}&purchased_quantity=#{quantity}" }.to_json)
end
end end
it 'returns the selected group location in JSON format' do context 'when the selected group is ineligible for a new subscription' do
subject let_it_be(:selected_group) { create(:group) }
before do
selected_group.add_owner(user)
allow_next_instance_of(
GitlabSubscriptions::FilterPurchaseEligibleNamespacesService,
user: user,
namespaces: [selected_group]
) do |instance|
allow(instance)
.to receive(:execute)
.and_return(instance_double(ServiceResponse, success?: true, payload: []))
end
end
plan_id = params[:subscription][:plan_id] it 'does not create a group' do
quantity = params[:subscription][:quantity] expect { subject }.to not_change { Group.count }
end
expect(response.body).to eq({ location: "/#{selected_group.path}?plan_id=#{plan_id}&purchased_quantity=#{quantity}" }.to_json) it 'returns a 404 not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end end
context 'when selected group is a sub group' do context 'when selected group is a sub group' do
......
...@@ -46,7 +46,7 @@ RSpec.describe SubscriptionsHelper do ...@@ -46,7 +46,7 @@ RSpec.describe SubscriptionsHelper do
group.add_owner(user) group.add_owner(user)
end end
subject { helper.subscription_data } subject { helper.subscription_data([group]) }
it { is_expected.to include(setup_for_company: 'false') } it { is_expected.to include(setup_for_company: 'false') }
it { is_expected.to include(full_name: 'First Last') } it { is_expected.to include(full_name: 'First Last') }
......
...@@ -221,92 +221,6 @@ RSpec.describe Namespace do ...@@ -221,92 +221,6 @@ RSpec.describe Namespace do
end end
end end
describe '.eligible_for_subscription' do
let_it_be(:namespace) { create :namespace }
let_it_be(:group) { create :group }
let_it_be(:subgroup) { create(:group, parent: group) }
subject { described_class.eligible_for_subscription.ids }
context 'when there is no subscription' do
it { is_expected.to contain_exactly(group.id, namespace.id) }
end
context 'when there is a subscription' do
context 'with a plan that is eligible for a trial' do
where(plan: ::Plan::PLANS_ELIGIBLE_FOR_TRIAL)
with_them do
context 'and has not yet been trialed' do
before do
create :gitlab_subscription, plan, namespace: namespace
create :gitlab_subscription, plan, namespace: group
create :gitlab_subscription, plan, namespace: subgroup
end
it { is_expected.to contain_exactly(group.id, namespace.id) }
end
context 'but has already had a trial' do
before do
create :gitlab_subscription, plan, namespace: namespace
create :gitlab_subscription, plan, :expired_trial, namespace: group
create :gitlab_subscription, plan, :expired_trial, namespace: subgroup
end
it { is_expected.to contain_exactly(group.id, namespace.id) }
end
context 'but is currently being trialed' do
before do
create :gitlab_subscription, plan, namespace: namespace
create :gitlab_subscription, plan, :active_trial, namespace: group
create :gitlab_subscription, plan, :active_trial, namespace: subgroup
end
it { is_expected.to contain_exactly(group.id, namespace.id) }
end
end
end
context 'in active trial ultimate plan' do
using RSpec::Parameterized::TableSyntax
where(:plan_name) do
[
[::Plan::GOLD],
[::Plan::ULTIMATE]
]
end
with_them do
before do
create :gitlab_subscription, plan_name, :active_trial, namespace: namespace
create :gitlab_subscription, plan_name, :active_trial, namespace: group
create :gitlab_subscription, plan_name, :active_trial, namespace: subgroup
end
it { is_expected.to contain_exactly(group.id, namespace.id) }
end
end
context 'with a paid plan and not in trial' do
where(plan: ::Plan::PAID_HOSTED_PLANS)
with_them do
context 'and has not yet been trialed' do
before do
create :gitlab_subscription, plan, namespace: namespace
create :gitlab_subscription, plan, namespace: group
end
it { is_expected.to be_empty }
end
end
end
end
end
describe '.eligible_for_trial' do describe '.eligible_for_trial' do
let_it_be(:namespace) { create :namespace } let_it_be(:namespace) { create :namespace }
......
...@@ -1009,88 +1009,6 @@ RSpec.describe User do ...@@ -1009,88 +1009,6 @@ RSpec.describe User do
end end
end end
describe '#manageable_groups_eligible_for_subscription' do
let_it_be(:user) { create(:user) }
let_it_be(:licensed_group) { create(:group_with_plan, plan: :bronze_plan) }
let_it_be(:free_group_z) { create(:group_with_plan, plan: :free_plan, name: 'AZ') }
let_it_be(:free_group_a) { create(:group_with_plan, plan: :free_plan, name: 'AA') }
let_it_be(:sub_group) { create(:group, name: 'SubGroup', parent: free_group_a) }
let_it_be(:trial_group) { create(:group_with_plan, plan: :ultimate_plan, trial_ends_on: Date.current + 1.day, name: 'AB') }
subject { user.manageable_groups_eligible_for_subscription }
context 'user with no groups' do
it { is_expected.to eq [] }
end
context 'owner of a licensed group' do
before do
licensed_group.add_owner(user)
end
it { is_expected.not_to include licensed_group }
end
context 'guest of a free group' do
before do
free_group_a.add_guest(user)
end
it { is_expected.not_to include free_group_a }
end
context 'developer of a free group' do
before do
free_group_a.add_developer(user)
end
it { is_expected.not_to include free_group_a }
end
context 'maintainer of a free group' do
before do
free_group_a.add_maintainer(user)
end
it { is_expected.to include free_group_a }
end
context 'owner of 2 free groups' do
before do
free_group_a.add_owner(user)
free_group_z.add_owner(user)
end
it { is_expected.to eq [free_group_a, free_group_z] }
it { is_expected.not_to include(sub_group) }
end
context 'developer of a trial group' do
before do
trial_group.add_developer(user)
end
it { is_expected.not_to include(trial_group) }
end
context 'owner of a trial group' do
before do
trial_group.add_owner(user)
end
it { is_expected.to include(trial_group) }
end
context 'maintainer of a trial group' do
before do
trial_group.add_maintainer(user)
end
it { is_expected.to include(trial_group) }
end
end
describe '#manageable_groups_eligible_for_trial' do describe '#manageable_groups_eligible_for_trial' do
let_it_be(:user) { create :user } let_it_be(:user) { create :user }
let_it_be(:non_trialed_group_z) { create :group_with_plan, name: 'Zeta', plan: :free_plan } let_it_be(:non_trialed_group_z) { create :group_with_plan, name: 'Zeta', plan: :free_plan }
......
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