diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md index d5087c20e6f11a813a3ee83022507420ed49e051..c3f0c94db218ecd466bd4856723918112078850e 100644 --- a/doc/user/admin_area/license.md +++ b/doc/user/admin_area/license.md @@ -94,7 +94,7 @@ a license, upload the license in the **Admin Area** in the web user interface. ## What happens when your license expires -One month before the license expires, a message with the upcoming expiration +Fifteen days before the license expires, a notification banner with the upcoming expiration date displays to GitLab administrators. When your license expires, GitLab locks features, like Git pushes diff --git a/ee/app/models/license.rb b/ee/app/models/license.rb index 309c5a017480f36b22de6978aa44ef538861b45c..a3e205d03c9aafeaf927a8a8c06ef1f3a526da17 100644 --- a/ee/app/models/license.rb +++ b/ee/app/models/license.rb @@ -11,6 +11,9 @@ class License < ApplicationRecord LICENSE_FILE_TYPE = 'license_file' ALLOWED_PERCENTAGE_OF_USERS_OVERAGE = (10 / 100.0) + NOTIFICATION_DAYS_BEFORE_TRIAL_EXPIRY = 1.week + ADMIN_NOTIFICATION_DAYS_BEFORE_EXPIRY = 15.days + EE_ALL_PLANS = [STARTER_PLAN, PREMIUM_PLAN, ULTIMATE_PLAN].freeze EES_FEATURES_WITH_USAGE_PING = %i[ @@ -623,6 +626,25 @@ class License < ApplicationRecord super || created_at end + # Overrides method from Gitlab::License which will be removed in a future version + def notify_admins? + return false if expires_at.blank? + return true if expired? + + notification_days = trial? ? NOTIFICATION_DAYS_BEFORE_TRIAL_EXPIRY : ADMIN_NOTIFICATION_DAYS_BEFORE_EXPIRY + + Date.current >= (expires_at - notification_days) + end + + # Overrides method from Gitlab::License which will be removed in a future version + def notify_users? + return false if expires_at.blank? + + notification_start_date = trial? ? expires_at - NOTIFICATION_DAYS_BEFORE_TRIAL_EXPIRY : block_changes_at + + Date.current >= notification_start_date + end + private def restricted_attr(name, default = nil) diff --git a/ee/spec/features/subscriptions/expiring_subscription_message_spec.rb b/ee/spec/features/subscriptions/expiring_subscription_message_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..274989e7f23f19e048fa71cc87c50d32aaa98f18 --- /dev/null +++ b/ee/spec/features/subscriptions/expiring_subscription_message_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Expiring Subscription Message', :js, :freeze_time do + shared_examples 'no expiration notification' do + it 'loads the page without any subscription expiration notifications' do + expect(page).not_to have_content('Your subscription expired!') + expect(page).not_to have_content('Your subscription will expire') + end + end + + context 'for self-managed subscriptions' do + context 'when signed in user is an admin' do + let_it_be(:admin) { create(:admin) } + + before do + create_current_license(plan: License::ULTIMATE_PLAN, expires_at: expires_at) + + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + end + + context 'with a license with no expiration' do + let(:expires_at) { nil } + + include_examples 'no expiration notification' + end + + context 'with an expired license' do + let(:expires_at) { Date.current - 1.day } + + it 'notifies the admin of the expired subscription' do + expect(page).to have_content('Your subscription expired!') + end + end + + context 'with a license expiring in 15 days' do + let(:expires_at) { Date.current + 15.days } + + it 'notifies the admin of a soon expiring subscription' do + expect(page).to have_content('Your subscription will expire in 15 days') + end + end + + context 'with a license expiring in more than 15 days' do + let(:expires_at) { Date.current + 16.days } + + include_examples 'no expiration notification' + end + end + + context 'when signed in user is not an admin' do + let_it_be(:user) { create(:user) } + + before do + create_current_license(plan: License::ULTIMATE_PLAN, expires_at: expires_at, block_changes_at: block_changes_at) + + sign_in(user) + visit root_path + end + + context 'with a license with no expiration' do + let(:expires_at) { nil } + let(:block_changes_at) { nil } + + include_examples 'no expiration notification' + end + + context 'with an expired license in the grace period' do + let(:expires_at) { Date.current - 1.day } + let(:block_changes_at) { Date.current + 13.days } + + include_examples 'no expiration notification' + end + + context 'with an expired license beyond the grace period' do + let(:expires_at) { Date.current - 15.days } + let(:block_changes_at) { Date.current - 1.day } + + it 'notifies the admin of the expired subscription' do + expect(page).to have_content('Your subscription expired!') + end + end + end + end + + context 'for namespace subscriptions', :saas do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + + before do + enable_namespace_license_check! + + create(:gitlab_subscription, namespace: group, end_date: end_date, auto_renew: false) + + allow_next_instance_of(GitlabSubscriptions::CheckFutureRenewalService, namespace: group) do |service| + allow(service).to receive(:execute).and_return(false) + end + end + + context 'when signed in user is a group owner' do + before do + group.add_owner(user) + + sign_in(user) + visit group_path(group) + end + + context 'with an expired license' do + let(:end_date) { Date.current - 1.day } + + it 'notifies the group owner of the expired subscription' do + expect(page).to have_content('Your subscription expired!') + end + end + + context 'with a license expiring in less than 30 days' do + let(:end_date) { Date.current + 29.days } + + it 'notifies the group owner of a soon expiring subscription' do + expect(page).to have_content('Your subscription will expire in 29 days') + end + end + + context 'with a license expiring in 30 or more days' do + let(:end_date) { Date.current + 30.days } + + include_examples 'no expiration notification' + end + end + + context 'when signed in user is not a group owner' do + before do + group.add_developer(user) + + sign_in(user) + visit group_path(group) + end + + context 'with an expired license' do + let(:end_date) { Date.current - 1.day } + + include_examples 'no expiration notification' + end + + context 'with a license expiring in less than 30 days' do + let(:end_date) { Date.current + 29.days } + + include_examples 'no expiration notification' + end + end + end +end diff --git a/ee/spec/models/license_spec.rb b/ee/spec/models/license_spec.rb index bde0e16b5203b2e591307046a4e6f05b24c1f4af..08146a968d6fb31d8c32f0085026abf6123857f5 100644 --- a/ee/spec/models/license_spec.rb +++ b/ee/spec/models/license_spec.rb @@ -1623,4 +1623,151 @@ RSpec.describe License do it { is_expected.to eq(license.created_at) } end end + + describe '#notify_admins?', :freeze_time do + subject(:notify_admins?) { license.notify_admins? } + + context 'when license has expired' do + before do + gl_license.expires_at = Date.yesterday + end + + it { is_expected.to eq(true) } + end + + context 'when license has no expiration' do + before do + gl_license.expires_at = nil + end + + it { is_expected.to eq(false) } + end + + context 'when license has not expired' do + context 'when license is a trial' do + before do + gl_license.restrictions = { trial: true } + end + + context 'when license expiration is more than a week from today' do + before do + gl_license.expires_at = Date.current + 8.days + end + + it { is_expected.to eq(false) } + end + + context 'when license expiration is a week from today' do + before do + gl_license.expires_at = Date.current + 7.days + end + + it { is_expected.to eq(true) } + end + + context 'when license expiration is less than a week from today' do + before do + gl_license.expires_at = Date.current + 6.days + end + + it { is_expected.to eq(true) } + end + end + + context 'when license is not a trial' do + context 'when license expiration is more than 15 days from today' do + before do + gl_license.expires_at = Date.current + 16.days + end + + it { is_expected.to eq(false) } + end + + context 'when license expiration is 15 days from today' do + before do + gl_license.expires_at = Date.current + 15.days + end + + it { is_expected.to eq(true) } + end + + context 'when license expiration is less than 15 days from today' do + before do + gl_license.expires_at = Date.current + 14.days + end + + it { is_expected.to eq(true) } + end + end + end + end + + describe '#notify_users?', :freeze_time do + subject(:notify_users?) { license.notify_users? } + + context 'when license has no expiration' do + before do + gl_license.expires_at = nil + gl_license.block_changes_at = nil + end + + it { is_expected.to eq(false) } + end + + context 'when license is a trial' do + before do + gl_license.restrictions = { trial: true } + end + + context 'when license expiration is more than a week from today' do + before do + gl_license.expires_at = Date.current + 8.days + end + + it { is_expected.to eq(false) } + end + + context 'when license expiration is a week from today' do + before do + gl_license.expires_at = Date.current + 7.days + end + + it { is_expected.to eq(true) } + end + + context 'when license expiration is less than a week from today' do + before do + gl_license.expires_at = Date.current + 6.days + end + + it { is_expected.to eq(true) } + end + end + + context 'when license is not a trial' do + context 'when license block changes date is before today' do + before do + gl_license.block_changes_at = Date.current - 1.day + end + + it { is_expected.to eq(true) } + end + + context 'when license block changes date is today' do + before do + gl_license.block_changes_at = Date.current + end + + it { is_expected.to eq(true) } + end + + context 'when license block changes date is after today' do + before do + gl_license.block_changes_at = Date.current + 1.day + end + + it { is_expected.to eq(false) } + end + end + end end