Commit c37228bb authored by Corinna Wiesner's avatar Corinna Wiesner Committed by Dmytro Zaporozhets (DZ)

Use previous license term for seat overage

To get the current seat overage the historical max data is pulled for
the past year. But not every license runs for a year. There was also an
overlap for new licenses to the old one which was removed with
https://gitlab.com/gitlab-org/customers-gitlab-com/-/merge_requests/2635

This change will take a previous license's term to pull the historical
max data and only use the past year as a fallback.
parent 2d42057d
...@@ -8,6 +8,7 @@ module Admin ...@@ -8,6 +8,7 @@ module Admin
@license ||= begin @license ||= begin
License.reset_current License.reset_current
License.reset_future_dated License.reset_future_dated
License.reset_previous
License.current License.current
end end
end end
......
...@@ -18,9 +18,10 @@ class HistoricalData < ApplicationRecord ...@@ -18,9 +18,10 @@ class HistoricalData < ApplicationRecord
def max_historical_user_count(license: nil, from: nil, to: nil) def max_historical_user_count(license: nil, from: nil, to: nil)
license ||= License.current license ||= License.current
starts_at = license&.starts_at || Time.current - 1.year
expires_at = license&.expires_at || Time.current expires_at = license&.expires_at || Time.current
from ||= (expires_at - 1.year).beginning_of_day from ||= starts_at.beginning_of_day
to ||= expires_at.end_of_day to ||= expires_at.end_of_day
HistoricalData.during(from..to).maximum(:active_user_count) || 0 HistoricalData.during(from..to).maximum(:active_user_count) || 0
end end
......
...@@ -241,6 +241,7 @@ class License < ApplicationRecord ...@@ -241,6 +241,7 @@ class License < ApplicationRecord
after_create :reset_current after_create :reset_current
after_destroy :reset_current after_destroy :reset_current
after_commit :reset_future_dated, on: [:create, :destroy] after_commit :reset_future_dated, on: [:create, :destroy]
after_commit :reset_previous, on: [:create, :destroy]
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
scope :last_hundred, -> { recent.limit(100) } scope :last_hundred, -> { recent.limit(100) }
...@@ -303,6 +304,14 @@ class License < ApplicationRecord ...@@ -303,6 +304,14 @@ class License < ApplicationRecord
future_dated.present? future_dated.present?
end end
def previous
Gitlab::SafeRequestStore.fetch(:previous_license) { load_previous }
end
def reset_previous
Gitlab::SafeRequestStore.delete(:previous_license)
end
def global_feature?(feature) def global_feature?(feature)
GLOBAL_FEATURES.include?(feature) GLOBAL_FEATURES.include?(feature)
end end
...@@ -334,6 +343,10 @@ class License < ApplicationRecord ...@@ -334,6 +343,10 @@ class License < ApplicationRecord
def load_future_dated def load_future_dated
self.last_hundred.find { |license| license.valid? && license.future_dated? } self.last_hundred.find { |license| license.valid? && license.future_dated? }
end end
def load_previous
self.last_hundred.find { |license| license.valid? && !license.future_dated? && license != License.current }
end
end end
def data_filename def data_filename
...@@ -488,12 +501,12 @@ class License < ApplicationRecord ...@@ -488,12 +501,12 @@ class License < ApplicationRecord
overage(maximum_user_count) overage(maximum_user_count)
end end
def historical_max(from = nil, to = nil) def historical_max(from: nil, to: nil)
HistoricalData.max_historical_user_count(license: self, from: from, to: to) HistoricalData.max_historical_user_count(license: self, from: from, to: to)
end end
def maximum_user_count def maximum_user_count
[historical_max(starts_at), daily_billable_users_count].max [historical_max(from: starts_at), daily_billable_users_count].max
end end
def update_trial_setting def update_trial_setting
...@@ -561,6 +574,10 @@ class License < ApplicationRecord ...@@ -561,6 +574,10 @@ class License < ApplicationRecord
self.class.reset_future_dated self.class.reset_future_dated
end end
def reset_previous
self.class.reset_previous
end
def reset_license def reset_license
@license = nil @license = nil
end end
...@@ -573,10 +590,7 @@ class License < ApplicationRecord ...@@ -573,10 +590,7 @@ class License < ApplicationRecord
def prior_historical_max def prior_historical_max
@prior_historical_max ||= begin @prior_historical_max ||= begin
from = (starts_at - 1.year).beginning_of_day historical_max(from: previous_started_at, to: previous_expired_at)
to = starts_at.end_of_day
historical_max(from, to)
end end
end end
...@@ -603,9 +617,9 @@ class License < ApplicationRecord ...@@ -603,9 +617,9 @@ class License < ApplicationRecord
def check_trueup def check_trueup
trueup_qty = restrictions[:trueup_quantity] trueup_qty = restrictions[:trueup_quantity]
trueup_from = Date.parse(restrictions[:trueup_from]).beginning_of_day rescue (starts_at - 1.year).beginning_of_day trueup_from = Date.parse(restrictions[:trueup_from]).beginning_of_day rescue previous_started_at
trueup_to = Date.parse(restrictions[:trueup_to]).end_of_day rescue starts_at.end_of_day trueup_to = Date.parse(restrictions[:trueup_to]).end_of_day rescue previous_expired_at
max_historical = historical_max(trueup_from, trueup_to) max_historical = historical_max(from: trueup_from, to: trueup_to)
expected_trueup_qty = if previous_user_count expected_trueup_qty = if previous_user_count
max_historical - previous_user_count max_historical - previous_user_count
else else
...@@ -643,4 +657,12 @@ class License < ApplicationRecord ...@@ -643,4 +657,12 @@ class License < ApplicationRecord
self.errors.add(:base, _('This license has already expired.')) self.errors.add(:base, _('This license has already expired.'))
end end
def previous_started_at
(License.previous&.starts_at || starts_at - 1.year).beginning_of_day
end
def previous_expired_at
(License.previous&.expires_at || starts_at).end_of_day
end
end end
---
title: Use previous license term for seat overage
merge_request: 53878
author:
type: changed
...@@ -40,16 +40,35 @@ RSpec.describe HistoricalData do ...@@ -40,16 +40,35 @@ RSpec.describe HistoricalData do
end end
describe '.max_historical_user_count' do describe '.max_historical_user_count' do
let(:current_license) { create(:license, starts_at: Date.current - 1.month, expires_at: Date.current + 1.month) }
before do
# stub current license to cover a shorter period (one month ago until a date in the future) than the one
# set for the whole test suite (1970-01-01 to a date in the future)
allow(License).to receive(:load_license).and_return(current_license)
allow(License).to receive(:current).and_return(current_license)
end
context 'with multiple historical data points for the current license' do context 'with multiple historical data points for the current license' do
before do before do
(1..3).each do |i| (1..3).each do |i|
described_class.create!(recorded_at: Time.current - i.days, active_user_count: i * 100) described_class.create!(recorded_at: Time.current - i.days, active_user_count: i * 100)
end end
described_class.create!(recorded_at: Time.current - 1.year, active_user_count: 400)
end end
it 'returns max user count for the past year' do it 'returns max user count for the duration of the current license' do
expect(described_class.max_historical_user_count).to eq(300) expect(described_class.max_historical_user_count).to eq(300)
end end
context 'when there is no current license' do
let(:current_license) { nil }
it 'returns max user count for the past year as a fallback' do
expect(described_class.max_historical_user_count).to eq(400)
end
end
end end
context 'using parameters' do context 'using parameters' do
...@@ -79,7 +98,6 @@ RSpec.describe HistoricalData do ...@@ -79,7 +98,6 @@ RSpec.describe HistoricalData do
before do before do
create(:group_member, :guest) create(:group_member, :guest)
create(:group_member, :reporter) create(:group_member, :reporter)
create(:license, plan: plan)
described_class.track! described_class.track!
end end
...@@ -92,6 +110,9 @@ RSpec.describe HistoricalData do ...@@ -92,6 +110,9 @@ RSpec.describe HistoricalData do
with_them do with_them do
let(:plan) { gl_plan } let(:plan) { gl_plan }
let(:current_license) do
create(:license, plan: plan, starts_at: Date.current - 1.month, expires_at: Date.current + 1.month)
end
it 'does not count guest users' do it 'does not count guest users' do
expect(described_class.max_historical_user_count).to eq(expected_count) expect(described_class.max_historical_user_count).to eq(expected_count)
...@@ -100,11 +121,9 @@ RSpec.describe HistoricalData do ...@@ -100,11 +121,9 @@ RSpec.describe HistoricalData do
end end
context 'with data outside of the license period' do context 'with data outside of the license period' do
let!(:license) { create(:license, starts_at: Date.current - 1.month) }
context 'with stats before the license period' do context 'with stats before the license period' do
before do before do
described_class.create!(recorded_at: license.starts_at.ago(2.days), active_user_count: 10) described_class.create!(recorded_at: current_license.starts_at.ago(2.days), active_user_count: 10)
end end
it 'ignore those records' do it 'ignore those records' do
...@@ -114,7 +133,7 @@ RSpec.describe HistoricalData do ...@@ -114,7 +133,7 @@ RSpec.describe HistoricalData do
context 'with stats after the license period' do context 'with stats after the license period' do
before do before do
described_class.create!(recorded_at: license.expires_at.in(2.days), active_user_count: 10) described_class.create!(recorded_at: current_license.expires_at.in(2.days), active_user_count: 10)
end end
it 'ignore those records' do it 'ignore those records' do
...@@ -124,8 +143,8 @@ RSpec.describe HistoricalData do ...@@ -124,8 +143,8 @@ RSpec.describe HistoricalData do
context 'with stats inside license period' do context 'with stats inside license period' do
before do before do
described_class.create!(recorded_at: license.starts_at.in(2.days), active_user_count: 10) described_class.create!(recorded_at: current_license.starts_at.in(2.days), active_user_count: 10)
described_class.create!(recorded_at: license.starts_at.in(5.days), active_user_count: 15) described_class.create!(recorded_at: current_license.starts_at.in(5.days), active_user_count: 15)
end end
it 'returns max value for active_user_count' do it 'returns max value for active_user_count' do
......
...@@ -71,7 +71,7 @@ RSpec.describe License do ...@@ -71,7 +71,7 @@ RSpec.describe License do
let(:new_license) do let(:new_license) do
gl_license = build( gl_license = build(
:gitlab_license, :gitlab_license,
starts_at: Date.today, starts_at: Date.current,
restrictions: { active_user_count: 10, previous_user_count: previous_user_count } restrictions: { active_user_count: 10, previous_user_count: previous_user_count }
) )
...@@ -353,6 +353,36 @@ RSpec.describe License do ...@@ -353,6 +353,36 @@ RSpec.describe License do
end end
end end
end end
describe '#reset_previous', :request_store do
let!(:previous_license) do
create(
:license,
data: create(:gitlab_license, starts_at: Date.new(1969, 1, 1), expires_at: Date.new(1969, 12, 31)).export)
end
before do
described_class.previous
expect(Gitlab::SafeRequestStore.read(:previous_license)).to be_present
end
context 'when a license is created' do
it 'deletes the previous_license value in Gitlab::SafeRequestStore' do
create(:license)
expect(Gitlab::SafeRequestStore.read(:previous_license)).to be_nil
end
end
context 'when a license is destroyed' do
it 'deletes the previous_license value in Gitlab::SafeRequestStore' do
previous_license.destroy
expect(Gitlab::SafeRequestStore.read(:previous_license)).to be_nil
end
end
end
end end
describe "Class methods" do describe "Class methods" do
...@@ -559,6 +589,62 @@ RSpec.describe License do ...@@ -559,6 +589,62 @@ RSpec.describe License do
end end
end end
describe '.previous' do
before do
described_class.reset_previous
end
context 'when there is no license' do
it 'returns nil' do
allow(described_class).to receive(:last_hundred).and_return([])
expect(described_class.previous).to be_nil
end
end
context 'when the license is invalid' do
it 'returns nil' do
license = build(
:license,
data: build(:gitlab_license, starts_at: Date.new(1969, 1, 1), expires_at: Date.new(1969, 12, 31)).export
)
allow(described_class).to receive(:last_hundred).and_return([license])
allow(license).to receive(:valid?).and_return(false)
expect(described_class.previous).to be_nil
end
end
context 'when the license is valid' do
context 'when only a current and a future dated license exist' do
before do
create(:license, data: create(:gitlab_license, starts_at: Date.current + 1.month).export)
end
it 'returns nil' do
expect(described_class.previous).to be_nil
end
end
context 'when license is not a future dated or the current one' do
it 'returns the the previous license' do
previous_license = create(
:license,
data: create(:gitlab_license, starts_at: Date.new(2000, 1, 1), expires_at: Date.new(2000, 12, 31)).export
)
# create another license since the last uploaded license is considered the current one
create(
:license,
data: create(:gitlab_license, starts_at: Date.new(2001, 1, 1), expires_at: Date.new(2001, 12, 31)).export
)
expect(described_class.previous).to eq(previous_license)
end
end
end
end
describe ".block_changes?" do describe ".block_changes?" do
before do before do
allow(License).to receive(:current).and_return(license) allow(License).to receive(:current).and_return(license)
......
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