Commit b28478bd authored by Reuben Pereira's avatar Reuben Pereira

Add upcoming_reconciliations table and model

Add the upcoming_reconciliations table and UpcomingReconciliation
model.

This table is to be used for displaying alerts to customers when
their subscription will be undergoing the quarterly reconciliation
process.

The table will hold the date when the next reconciliation is to
occur, and a date when an alert should start being displayed to the
customer. This information will be received from CustomersDot.

The table will hold a row for each relevant namespace in SaaS,
which should be about 9,000 - 10,000 rows (at time of commit). This
is because customers buy subscriptions for top-level namespaces in
SaaS.

In self-managed, the table should hold one row only, because the
entire self-managed instance uses one subscription/license.

MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63054

Changelog: added
parent 490f67ca
# frozen_string_literal: true
class AddUpcomingReconciliations < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
create_table :upcoming_reconciliations do |t|
t.references :namespace, index: { unique: true }, null: true, foreign_key: { on_delete: :cascade }
t.date :next_reconciliation_date, null: false
t.date :display_alert_from, null: false
t.timestamps_with_timezone
end
end
end
def down
with_lock_retries do
drop_table :upcoming_reconciliations
end
end
end
66e50071130c2bd64be2f52d5c5f348a91883b2e9a9f4241175d1d2ad2a74434
\ No newline at end of file
......@@ -18413,6 +18413,24 @@ CREATE SEQUENCE u2f_registrations_id_seq
ALTER SEQUENCE u2f_registrations_id_seq OWNED BY u2f_registrations.id;
CREATE TABLE upcoming_reconciliations (
id bigint NOT NULL,
namespace_id bigint,
next_reconciliation_date date NOT NULL,
display_alert_from date NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE upcoming_reconciliations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE upcoming_reconciliations_id_seq OWNED BY upcoming_reconciliations.id;
CREATE TABLE uploads (
id integer NOT NULL,
size bigint NOT NULL,
......@@ -20279,6 +20297,8 @@ ALTER TABLE ONLY trending_projects ALTER COLUMN id SET DEFAULT nextval('trending
ALTER TABLE ONLY u2f_registrations ALTER COLUMN id SET DEFAULT nextval('u2f_registrations_id_seq'::regclass);
ALTER TABLE ONLY upcoming_reconciliations ALTER COLUMN id SET DEFAULT nextval('upcoming_reconciliations_id_seq'::regclass);
ALTER TABLE ONLY uploads ALTER COLUMN id SET DEFAULT nextval('uploads_id_seq'::regclass);
ALTER TABLE ONLY user_agent_details ALTER COLUMN id SET DEFAULT nextval('user_agent_details_id_seq'::regclass);
......@@ -21927,6 +21947,9 @@ ALTER TABLE ONLY trending_projects
ALTER TABLE ONLY u2f_registrations
ADD CONSTRAINT u2f_registrations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY upcoming_reconciliations
ADD CONSTRAINT upcoming_reconciliations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY uploads
ADD CONSTRAINT uploads_pkey PRIMARY KEY (id);
......@@ -24675,6 +24698,8 @@ CREATE INDEX index_unit_test_failures_failed_at ON ci_unit_test_failures USING b
CREATE UNIQUE INDEX index_unit_test_failures_unique_columns ON ci_unit_test_failures USING btree (unit_test_id, failed_at DESC, build_id);
CREATE UNIQUE INDEX index_upcoming_reconciliations_on_namespace_id ON upcoming_reconciliations USING btree (namespace_id);
CREATE INDEX index_uploads_on_checksum ON uploads USING btree (checksum);
CREATE INDEX index_uploads_on_model_id_and_model_type ON uploads USING btree (model_id, model_type);
......@@ -26519,6 +26544,9 @@ ALTER TABLE ONLY user_custom_attributes
ALTER TABLE ONLY ci_pending_builds
ADD CONSTRAINT fk_rails_480669c3b3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY upcoming_reconciliations
ADD CONSTRAINT fk_rails_497b4938ac FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_artifacts
ADD CONSTRAINT fk_rails_4a70390ca6 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
......@@ -27,6 +27,7 @@ module EE
has_one :namespace_limit, inverse_of: :namespace
has_one :gitlab_subscription
has_one :elasticsearch_indexed_namespace
has_one :upcoming_reconciliation, inverse_of: :namespace, class_name: "GitlabSubscriptions::UpcomingReconciliation"
has_many :compliance_management_frameworks, class_name: "ComplianceManagement::Framework"
......
# frozen_string_literal: true
module GitlabSubscriptions
class UpcomingReconciliation < ApplicationRecord
belongs_to :namespace, inverse_of: :upcoming_reconciliation, optional: true
validates :namespace, uniqueness: true, presence: { if: proc { ::Gitlab.com? } }
def self.for_self_managed
self.find_by(namespace_id: nil)
end
def display_alert?
next_reconciliation_date >= Date.current && display_alert_from <= Date.current
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :upcoming_reconciliation, class: 'GitlabSubscriptions::UpcomingReconciliation' do
next_reconciliation_date { Date.current + 7.days }
display_alert_from { Date.current.beginning_of_day }
trait :self_managed do
namespace { nil }
end
trait :saas do
namespace
end
end
end
......@@ -17,6 +17,7 @@ RSpec.describe Namespace do
it { is_expected.to have_one(:namespace_statistics) }
it { is_expected.to have_one(:namespace_limit) }
it { is_expected.to have_one(:elasticsearch_indexed_namespace) }
it { is_expected.to have_one :upcoming_reconciliation }
it { is_expected.to delegate_method(:shared_runners_seconds).to(:namespace_statistics) }
it { is_expected.to delegate_method(:shared_runners_seconds_last_reset).to(:namespace_statistics) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSubscriptions::UpcomingReconciliation do
describe 'associations' do
it { is_expected.to belong_to(:namespace).optional }
end
describe 'validations' do
# This is needed for the validate_uniqueness_of expectation.
let_it_be(:upcoming_reconciliation) { create(:upcoming_reconciliation, :saas) }
it { is_expected.to validate_uniqueness_of(:namespace) }
it 'does not allow multiple rows with namespace_id nil' do
create(:upcoming_reconciliation, :self_managed)
expect { create(:upcoming_reconciliation, :self_managed) }.to raise_error(
ActiveRecord::RecordInvalid,
'Validation failed: Namespace has already been taken'
)
end
context 'when gitlab.com' do
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
it { is_expected.to validate_presence_of(:namespace) }
end
context 'when not gitlab.com' do
it { is_expected.not_to validate_presence_of(:namespace) }
end
end
describe '#display_alert?' do
let(:upcoming_reconciliation) { build(:upcoming_reconciliation, :saas) }
subject(:display_alert?) { upcoming_reconciliation.display_alert? }
context 'with next_reconciliation_date in future' do
it { is_expected.to eq(true) }
end
context 'with next_reconciliation_date in past' do
before do
upcoming_reconciliation.next_reconciliation_date = Date.yesterday
end
it { is_expected.to eq(false) }
end
context 'with display_alert_from in future' do
before do
upcoming_reconciliation.display_alert_from = 2.days.from_now
end
it { is_expected.to eq(false) }
end
context 'with display_alert_from in past' do
it { is_expected.to eq(true) }
end
end
describe '.for_self_managed' do
it 'returns row where namespace_id is nil' do
upcoming_reconciliation = create(:upcoming_reconciliation, :self_managed)
expect(described_class.for_self_managed).to eq(upcoming_reconciliation)
end
it 'returns nil when there is no row with namespace_id nil' do
expect(described_class.for_self_managed).to eq(nil)
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