Commit 19be1668 authored by Mikołaj Wawrzyniak's avatar Mikołaj Wawrzyniak

Merge branch '290278-allow-extend-or-reactivate-trial-on-gitlab-com' into 'master'

BE: Allow customers to extend or reactivate their trial on gitlab.com [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!56471
parents bd248cbd 7854f0e6
...@@ -8,6 +8,8 @@ class TrialsController < ApplicationController ...@@ -8,6 +8,8 @@ class TrialsController < ApplicationController
before_action :check_if_gl_com_or_dev before_action :check_if_gl_com_or_dev
before_action :authenticate_user! before_action :authenticate_user!
before_action :find_or_create_namespace, only: :apply before_action :find_or_create_namespace, only: :apply
before_action :find_namespace, only: [:extend_reactivate]
before_action :authenticate_namespace_owner!, only: [:extend_reactivate]
feature_category :purchase feature_category :purchase
...@@ -49,6 +51,18 @@ class TrialsController < ApplicationController ...@@ -49,6 +51,18 @@ class TrialsController < ApplicationController
end end
end end
def extend_reactivate
render_404 unless Feature.enabled?(:allow_extend_reactivate_trial)
result = GitlabSubscriptions::ExtendReactivateTrialService.new.execute(extend_reactivate_trial_params) if valid_extension?
if result&.success?
head 200
else
render_403
end
end
protected protected
# override the ConfirmEmailWarning method in order to skip # override the ConfirmEmailWarning method in order to skip
...@@ -64,6 +78,16 @@ class TrialsController < ApplicationController ...@@ -64,6 +78,16 @@ class TrialsController < ApplicationController
redirect_to new_trial_registration_path, alert: I18n.t('devise.failure.unauthenticated') redirect_to new_trial_registration_path, alert: I18n.t('devise.failure.unauthenticated')
end end
def authenticate_namespace_owner!
user_is_namespace_owner = if @namespace.is_a?(Group)
@namespace.owners.include?(current_user)
else
@namespace.owner == current_user
end
render_403 unless user_is_namespace_owner
end
def company_params def company_params
params.permit(:company_name, :company_size, :first_name, :last_name, :phone_number, :number_of_users, :country) params.permit(:company_name, :company_size, :first_name, :last_name, :phone_number, :number_of_users, :country)
.merge(extra_params) .merge(extra_params)
...@@ -90,6 +114,15 @@ class TrialsController < ApplicationController ...@@ -90,6 +114,15 @@ class TrialsController < ApplicationController
} }
end end
def extend_reactivate_trial_params
gl_com_params = { gitlab_com_trial: true }
{
trial_user: params.permit(:namespace_id, :trial_extension_type, :trial_entity, :glm_source, :glm_content).merge(gl_com_params),
uid: current_user.id
}
end
def find_or_create_namespace def find_or_create_namespace
@namespace = if find_namespace? @namespace = if find_namespace?
current_user.namespaces.find_by_id(params[:namespace_id]) current_user.namespaces.find_by_id(params[:namespace_id])
...@@ -100,10 +133,30 @@ class TrialsController < ApplicationController ...@@ -100,10 +133,30 @@ class TrialsController < ApplicationController
render_404 unless @namespace render_404 unless @namespace
end end
def find_namespace
@namespace = if find_namespace?
current_user.namespaces.find_by_id(params[:namespace_id])
end
render_404 unless @namespace
end
def find_namespace? def find_namespace?
params[:namespace_id].present? && params[:namespace_id] != '0' params[:namespace_id].present? && params[:namespace_id] != '0'
end end
def valid_extension?
trial_extension_type = params[:trial_extension_type].to_i
return false unless GitlabSubscription.trial_extension_types.value?(trial_extension_type)
return false if trial_extension_type == GitlabSubscription.trial_extension_types[:extended] && !@namespace.can_extend?
return false if trial_extension_type == GitlabSubscription.trial_extension_types[:reactivated] && !@namespace.can_reactivate?
true
end
def can_create_group? def can_create_group?
params[:new_group_name].present? && can?(current_user, :create_group) params[:new_group_name].present? && can?(current_user, :create_group)
end end
......
...@@ -94,7 +94,7 @@ module EE ...@@ -94,7 +94,7 @@ module EE
less_than: ::Gitlab::Pages::MAX_SIZE / 1.megabyte } less_than: ::Gitlab::Pages::MAX_SIZE / 1.megabyte }
delegate :trial?, :trial_ends_on, :trial_starts_on, :trial_days_remaining, delegate :trial?, :trial_ends_on, :trial_starts_on, :trial_days_remaining,
:trial_percentage_complete, :upgradable?, :trial_percentage_complete, :upgradable?, :trial_extended_or_reactivated?,
to: :gitlab_subscription, allow_nil: true to: :gitlab_subscription, allow_nil: true
before_create :sync_membership_lock_with_parent before_create :sync_membership_lock_with_parent
...@@ -295,6 +295,14 @@ module EE ...@@ -295,6 +295,14 @@ module EE
trial? && trial_ends_on.present? && trial_ends_on >= Date.today trial? && trial_ends_on.present? && trial_ends_on >= Date.today
end end
def can_extend?
trial_active? && !trial_extended_or_reactivated?
end
def can_reactivate?
!trial_active? && !never_had_trial? && !trial_extended_or_reactivated? && free_plan?
end
def never_had_trial? def never_had_trial?
trial_ends_on.nil? trial_ends_on.nil?
end end
......
...@@ -6,6 +6,8 @@ class GitlabSubscription < ApplicationRecord ...@@ -6,6 +6,8 @@ class GitlabSubscription < ApplicationRecord
EOA_ROLLOUT_DATE = '2021-01-26' EOA_ROLLOUT_DATE = '2021-01-26'
enum trial_extension_type: { extended: 1, reactivated: 2 }
default_value_for(:start_date) { Date.today } default_value_for(:start_date) { Date.today }
before_update :log_previous_state_for_update before_update :log_previous_state_for_update
after_commit :index_namespace, on: [:create, :update] after_commit :index_namespace, on: [:create, :update]
...@@ -107,6 +109,10 @@ class GitlabSubscription < ApplicationRecord ...@@ -107,6 +109,10 @@ class GitlabSubscription < ApplicationRecord
seats_in_use_now seats_in_use_now
end end
def trial_extended_or_reactivated?
trial_extension_type.present?
end
def trial_days_remaining def trial_days_remaining
(trial_ends_on - Date.current).to_i (trial_ends_on - Date.current).to_i
end end
......
# frozen_string_literal: true
module GitlabSubscriptions
class ExtendReactivateTrialService
def execute(extend_reactivate_trial_params)
response = client.extend_reactivate_trial(extend_reactivate_trial_params)
if response[:success]
ServiceResponse.success
else
ServiceResponse.error(message: response.dig(:data, :errors))
end
end
private
def client
Gitlab::SubscriptionPortal::Client
end
end
end
---
title: Allow customers to extend or reactivate their trial on gitlab.com
merge_request: 56471
author:
type: added
---
name: allow_extend_reactivate_trial
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56471
rollout_issue_url:
milestone: '13.11'
type: development
group: group::purchase
default_enabled: false
...@@ -5,5 +5,6 @@ resources :trials, only: [:new] do ...@@ -5,5 +5,6 @@ resources :trials, only: [:new] do
post :create_lead post :create_lead
get :select get :select
post :apply post :apply
put :extend_reactivate
end end
end end
...@@ -45,6 +45,7 @@ module EE ...@@ -45,6 +45,7 @@ module EE
optional :trial, type: Grape::API::Boolean, desc: 'Whether the subscription is a trial' optional :trial, type: Grape::API::Boolean, desc: 'Whether the subscription is a trial'
optional :trial_ends_on, type: Date, desc: 'End date of trial' optional :trial_ends_on, type: Date, desc: 'End date of trial'
optional :trial_starts_on, type: Date, desc: 'Start date of trial' optional :trial_starts_on, type: Date, desc: 'Start date of trial'
optional :trial_extension_type, type: Integer, desc: 'Whether subscription is an extended or reactivated trial'
end end
end end
......
...@@ -11,6 +11,10 @@ module Gitlab ...@@ -11,6 +11,10 @@ module Gitlab
http_post("trials", admin_headers, params) http_post("trials", admin_headers, params)
end end
def extend_reactivate_trial(params)
http_put("trials/extend_reactivate_trial", admin_headers, params)
end
def create_customer(params) def create_customer(params)
http_post("api/customers", admin_headers, params) http_post("api/customers", admin_headers, params)
end end
...@@ -48,6 +52,14 @@ module Gitlab ...@@ -48,6 +52,14 @@ module Gitlab
rescue *Gitlab::HTTP::HTTP_ERRORS => e rescue *Gitlab::HTTP::HTTP_ERRORS => e
{ success: false, data: { errors: e.message } } { success: false, data: { errors: e.message } }
end end
def http_put(path, headers, params = {})
response = Gitlab::HTTP.put("#{base_url}/#{path}", body: params.to_json, headers: headers)
parse_response(response)
rescue *Gitlab::HTTP::HTTP_ERRORS => e
{ success: false, data: { errors: e.message } }
end
end end
end end
end end
......
...@@ -171,8 +171,9 @@ RSpec.describe TrialsController do ...@@ -171,8 +171,9 @@ RSpec.describe TrialsController do
before do before do
namespace.add_owner(user) namespace.add_owner(user)
allow_any_instance_of(GitlabSubscriptions::ApplyTrialService).to receive(:execute) do
{ success: apply_trial_result } allow_next_instance_of(GitlabSubscriptions::ApplyTrialService) do |service|
allow(service).to receive(:execute).and_return({ success: apply_trial_result })
end end
end end
...@@ -248,6 +249,115 @@ RSpec.describe TrialsController do ...@@ -248,6 +249,115 @@ RSpec.describe TrialsController do
end end
end end
describe '#extend_reactivate' do
let!(:namespace) { create(:group_with_plan, trial_ends_on: Date.tomorrow, path: 'namespace-test') }
let(:namespace_id) { namespace.id }
let(:trial_extension_type) { GitlabSubscription.trial_extension_types[:extended].to_s }
let(:put_params) { { namespace_id: namespace_id, trial_extension_type: trial_extension_type } }
let(:extend_reactivate_trial_result) { true }
let(:is_owner?) { true }
before do
if is_owner?
namespace.add_owner(user)
else
namespace.add_developer(user)
end
allow_next_instance_of(GitlabSubscriptions::ExtendReactivateTrialService) do |service|
allow(service).to receive(:execute).and_return(extend_reactivate_trial_result ? ServiceResponse.success : ServiceResponse.error(message: 'failed'))
end
end
subject do
put :extend_reactivate, params: put_params
response
end
it_behaves_like 'an authenticated endpoint'
it_behaves_like 'a dot-com only feature'
context 'on success' do
it { is_expected.to have_gitlab_http_status(:ok) }
end
context 'on failure' do
context 'when user is not namespace owner' do
let(:is_owner?) { false }
it 'returns 403' do
is_expected.to have_gitlab_http_status(:forbidden)
end
end
context 'when cannot find the namespace' do
let(:namespace_id) { 'invalid-namespace-id' }
it 'returns 404' do
is_expected.to have_gitlab_http_status(:not_found)
end
end
context 'when trial extension type is neither EXTEND nor REACTIVATE' do
let(:trial_extension_type) { nil }
it 'returns 403' do
is_expected.to have_gitlab_http_status(:forbidden)
end
end
context 'when trial extension type is EXTEND' do
let(:trial_extension_type) { GitlabSubscription.trial_extension_types[:extended].to_s }
it 'returns 403 if the namespace cannot extend' do
namespace.gitlab_subscription.update_column(:trial_extension_type, GitlabSubscription.trial_extension_types[:extended])
is_expected.to have_gitlab_http_status(:forbidden)
end
end
context 'when trial extension type is REACTIVATE' do
let(:trial_extension_type) { GitlabSubscription.trial_extension_types[:reactivated].to_s }
it 'returns 403 if the namespace cannot reactivate' do
namespace.gitlab_subscription.update_column(:trial_extension_type, GitlabSubscription.trial_extension_types[:extended])
is_expected.to have_gitlab_http_status(:forbidden)
end
end
context 'when ExtendReactivateTrialService fails' do
let(:extend_reactivate_trial_result) { false }
it 'returns 403' do
is_expected.to have_gitlab_http_status(:forbidden)
end
end
end
it "calls the ExtendReactivateTrialService with correct parameters" do
gl_com_params = { gitlab_com_trial: true }
put_params = {
namespace_id: namespace.id.to_s,
trial_extension_type: GitlabSubscription.trial_extension_types[:extended].to_s,
trial_entity: 'company',
glm_source: 'source',
glm_content: 'content'
}
extend_reactivate_trial_params = {
uid: user.id,
trial_user: ActionController::Parameters.new(put_params).permit(:namespace_id, :trial_extension_type, :trial_entity, :glm_source, :glm_content).merge(gl_com_params)
}
expect_next_instance_of(GitlabSubscriptions::ExtendReactivateTrialService) do |service|
expect(service).to receive(:execute).with(extend_reactivate_trial_params).and_return(ServiceResponse.success)
end
put :extend_reactivate, params: put_params
end
end
describe 'confirm email warning' do describe 'confirm email warning' do
before do before do
get :new get :new
......
...@@ -50,6 +50,18 @@ RSpec.describe Gitlab::SubscriptionPortal::Clients::REST do ...@@ -50,6 +50,18 @@ RSpec.describe Gitlab::SubscriptionPortal::Clients::REST do
it_behaves_like 'when response code is 500' it_behaves_like 'when response code is 500'
end end
describe '#extend_reactivate_trial' do
let(:http_method) { :put }
subject do
client.extend_reactivate_trial({})
end
it_behaves_like 'when response is successful'
it_behaves_like 'when response code is 422'
it_behaves_like 'when response code is 500'
end
describe '#create_subscription' do describe '#create_subscription' do
subject do subject do
client.create_subscription({}, 'customer@mail.com', 'token') client.create_subscription({}, 'customer@mail.com', 'token')
......
...@@ -26,6 +26,7 @@ RSpec.describe Namespace do ...@@ -26,6 +26,7 @@ RSpec.describe Namespace do
it { is_expected.to delegate_method(:trial_days_remaining).to(:gitlab_subscription) } it { is_expected.to delegate_method(:trial_days_remaining).to(:gitlab_subscription) }
it { is_expected.to delegate_method(:trial_percentage_complete).to(:gitlab_subscription) } it { is_expected.to delegate_method(:trial_percentage_complete).to(:gitlab_subscription) }
it { is_expected.to delegate_method(:upgradable?).to(:gitlab_subscription) } it { is_expected.to delegate_method(:upgradable?).to(:gitlab_subscription) }
it { is_expected.to delegate_method(:trial_extended_or_reactivated?).to(:gitlab_subscription) }
it { is_expected.to delegate_method(:email).to(:owner).with_prefix.allow_nil } it { is_expected.to delegate_method(:email).to(:owner).with_prefix.allow_nil }
it { is_expected.to delegate_method(:additional_purchased_storage_size).to(:namespace_limit) } it { is_expected.to delegate_method(:additional_purchased_storage_size).to(:namespace_limit) }
it { is_expected.to delegate_method(:additional_purchased_storage_size=).to(:namespace_limit).with_arguments(:args) } it { is_expected.to delegate_method(:additional_purchased_storage_size=).to(:namespace_limit).with_arguments(:args) }
...@@ -1246,6 +1247,60 @@ RSpec.describe Namespace do ...@@ -1246,6 +1247,60 @@ RSpec.describe Namespace do
end end
end end
describe '#can_extend?' do
subject { namespace.can_extend? }
where(:trial_active, :trial_extended_or_reactivated, :can_extend) do
false | false | false
false | true | false
true | false | true
true | true | false
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 }
end
end
describe '#can_reactivate?' do
subject { namespace.can_reactivate? }
where(:trial_active, :never_had_trial, :trial_extended_or_reactivated, :free_plan, :can_reactivate) do
false | false | false | false | false
false | false | false | true | true
false | false | true | false | false
false | false | true | true | false
false | true | false | false | false
false | true | false | true | false
false | true | true | false | false
false | true | true | true | false
true | false | false | false | false
true | false | false | true | false
true | false | true | false | false
true | false | true | true | false
true | true | false | false | false
true | true | false | true | false
true | true | true | false | false
true | true | true | true | false
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 }
end
end
describe '#file_template_project_id' do describe '#file_template_project_id' do
it 'is cleared before validation' do it 'is cleared before validation' do
project = create(:project, namespace: namespace) project = create(:project, namespace: namespace)
......
...@@ -476,10 +476,26 @@ RSpec.describe GitlabSubscription do ...@@ -476,10 +476,26 @@ RSpec.describe GitlabSubscription do
let(:start_trial_on) { nil } let(:start_trial_on) { nil }
let(:end_trial_on) { nil } let(:end_trial_on) { nil }
let(:trial_extension_type) { nil }
before do before do
gitlab_subscription.trial_starts_on = start_trial_on if start_trial_on gitlab_subscription.trial_starts_on = start_trial_on if start_trial_on
gitlab_subscription.trial_ends_on = end_trial_on if end_trial_on gitlab_subscription.trial_ends_on = end_trial_on if end_trial_on
gitlab_subscription.trial_extension_type = trial_extension_type
end
describe '#trial_extended_or_reactivated?' do
subject { gitlab_subscription.trial_extended_or_reactivated? }
where(:trial_extension_type, :extended_or_reactivated) do
nil | false
1 | true
2 | true
end
with_them do
it { is_expected.to be(extended_or_reactivated) }
end
end end
describe '#trial_days_remaining' do describe '#trial_days_remaining' do
......
...@@ -290,7 +290,8 @@ RSpec.describe API::Namespaces do ...@@ -290,7 +290,8 @@ RSpec.describe API::Namespaces do
auto_renew: true, auto_renew: true,
trial: true, trial: true,
trial_ends_on: '2019-05-01', trial_ends_on: '2019-05-01',
trial_starts_on: '2019-06-01' trial_starts_on: '2019-06-01',
trial_extension_type: GitlabSubscription.trial_extension_types[:reactivated]
} }
end end
...@@ -310,7 +311,8 @@ RSpec.describe API::Namespaces do ...@@ -310,7 +311,8 @@ RSpec.describe API::Namespaces do
auto_renew: true, auto_renew: true,
trial: true, trial: true,
trial_starts_on: Date.parse(gitlab_subscription[:trial_starts_on]), trial_starts_on: Date.parse(gitlab_subscription[:trial_starts_on]),
trial_ends_on: Date.parse(gitlab_subscription[:trial_ends_on]) trial_ends_on: Date.parse(gitlab_subscription[:trial_ends_on]),
trial_extension_type: 'reactivated'
) )
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSubscriptions::ExtendReactivateTrialService do
subject(:execute) { described_class.new.execute(extend_reactivate_trial_params) }
let_it_be(:namespace) { create(:namespace) }
let(:extend_reactivate_trial_params) do
{
trial_user: {
namespace_id: namespace.id,
trial_extension_type: GitlabSubscription.trial_extension_types[:extended]
}
}
end
describe '#execute' do
before do
allow(Gitlab::SubscriptionPortal::Client).to receive(:extend_reactivate_trial).and_return(response)
end
context 'trial is extended/reactivated successfully' do
let(:response) { { success: true } }
it 'returns success: true' do
result = execute
expect(result.is_a?(ServiceResponse)).to be true
expect(result.success?).to be true
end
end
context 'error while extending/reactivating the trial' do
let(:response) { { success: false, data: { errors: ['some error'] } } }
it 'returns success: false with errors' do
result = execute
expect(result.is_a?(ServiceResponse)).to be true
expect(result.success?).to be false
expect(result.message).to eq(['some error'])
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment