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
layout 'checkout'
skip_before_action :authenticate_user!, only: [:new, :buy_minutes]
before_action :load_eligible_groups, only: %i[new buy_minutes]
feature_category :purchase
content_security_policy do |p|
......@@ -44,15 +46,10 @@ class SubscriptionsController < ApplicationController
def create
current_user.update(setup_for_company: true) if params[:setup_for_company]
group = params[:selected_group] ? find_group : create_group
if params[:selected_group]
group = current_user.manageable_groups_eligible_for_subscription.find(params[:selected_group])
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
return not_found if group.nil?
return render json: group.errors.to_json unless group.persisted?
response = Subscriptions::CreateService.new(
current_user,
......@@ -85,6 +82,23 @@ class SubscriptionsController < ApplicationController
params.require(:subscription).permit(:plan_id, :payment_method_id, :quantity)
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
Gitlab::SubscriptionPortal::Client
end
......@@ -99,4 +113,16 @@ class SubscriptionsController < ApplicationController
store_location_for :user, request.fullpath
redirect_to new_user_registration_path(redirect_from: from)
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
......@@ -3,7 +3,7 @@
module SubscriptionsHelper
include ::Gitlab::Utils::StrongMemoize
def subscription_data
def subscription_data(eligible_groups)
{
setup_for_company: (current_user.setup_for_company == true).to_s,
full_name: current_user.name,
......@@ -12,7 +12,7 @@ module SubscriptionsHelper
plan_id: params[:plan_id],
namespace_id: params[:namespace_id],
new_user: new_user?.to_s,
group_data: group_data.to_json
group_data: present_groups(eligible_groups).to_json
}
end
......@@ -64,8 +64,8 @@ module SubscriptionsHelper
end
end
def group_data
current_user.manageable_groups_eligible_for_subscription.with_counts(archived: false).map do |namespace|
def present_groups(groups)
groups.map do |namespace|
{
id: namespace.id,
name: namespace.name,
......
......@@ -47,10 +47,6 @@ module EE
.where(plans: { name: [nil, *::Plan.default_plans] })
end
scope :eligible_for_subscription, -> do
top_most.in_active_trial.or(top_most.in_default_plan)
end
scope :eligible_for_trial, -> do
left_joins(gitlab_subscription: :hosted_plan)
.where(
......
......@@ -255,10 +255,6 @@ module EE
.any?
end
def manageable_groups_eligible_for_subscription
manageable_groups.eligible_for_subscription.order(:name)
end
def manageable_groups_eligible_for_trial
manageable_groups.eligible_for_trial.order(:name)
end
......
- page_title _('Buy CI Minutes')
#js-buy-minutes{ data: subscription_data }
#js-buy-minutes{ data: subscription_data(@eligible_groups) }
- 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
end
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'
......@@ -31,11 +31,41 @@ RSpec.describe SubscriptionsController do
it { is_expected.to render_template 'layouts/checkout' }
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
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'
......@@ -46,6 +76,36 @@ RSpec.describe SubscriptionsController do
it { is_expected.to render_template 'layouts/checkout' }
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
context 'with :new_route_ci_minutes_purchase disabled' do
......@@ -218,7 +278,6 @@ RSpec.describe SubscriptionsController do
end
context 'when selecting an existing group' do
let_it_be(:selected_group) { create(:group) }
let(:params) do
{
selected_group: selected_group.id,
......@@ -227,21 +286,63 @@ RSpec.describe SubscriptionsController do
}
end
before do
selected_group.add_owner(user)
end
context 'when the selected group is eligible for a new subscription' do
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: [selected_group]))
end
end
it 'does not create a group' do
expect { subject }.to not_change { Group.count }
it 'does not create a group' do
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
it 'returns the selected group location in JSON format' do
subject
context 'when the selected group is ineligible for a new subscription' do
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]
quantity = params[:subscription][:quantity]
it 'does not create a group' do
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
context 'when selected group is a sub group' do
......
......@@ -46,7 +46,7 @@ RSpec.describe SubscriptionsHelper do
group.add_owner(user)
end
subject { helper.subscription_data }
subject { helper.subscription_data([group]) }
it { is_expected.to include(setup_for_company: 'false') }
it { is_expected.to include(full_name: 'First Last') }
......
......@@ -221,92 +221,6 @@ RSpec.describe Namespace do
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
let_it_be(:namespace) { create :namespace }
......
......@@ -1009,88 +1009,6 @@ RSpec.describe User do
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
let_it_be(:user) { create :user }
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