From d5b79c4f1ce28ae06cfebeb4fe1fdc912b762e02 Mon Sep 17 00:00:00 2001 From: Bishwa Hang Rai <bhrai@gitlab.com> Date: Mon, 26 Jun 2023 13:49:45 +0000 Subject: [PATCH] Create table subscription_users_add_on_assignments - create model 'GitlabSubscriptions::UserAddOnAssignment' - add valdiation and specs Changelog: added --- .../subscription_user_add_on_assignments.yml | 10 ++++++ ...te_subscription_user_add_on_assignments.rb | 17 ++++++++++ ...on_subscription_user_add_on_assignments.rb | 16 ++++++++++ ...on_subscription_user_add_on_assignments.rb | 15 +++++++++ db/schema_migrations/20230616164309 | 1 + db/schema_migrations/20230616164705 | 1 + db/schema_migrations/20230616164731 | 1 + db/structure.sql | 32 +++++++++++++++++++ ee/app/models/ee/user.rb | 2 ++ ee/app/models/gitlab_subscriptions.rb | 7 ++++ ee/app/models/gitlab_subscriptions/add_on.rb | 2 -- .../gitlab_subscriptions/add_on_purchase.rb | 4 +-- .../upcoming_reconciliation.rb | 2 ++ .../user_add_on_assignment.rb | 11 +++++++ .../user_add_on_assignment.rb | 8 +++++ ee/spec/models/ee/user_spec.rb | 1 + .../add_on_purchase_spec.rb | 5 +++ .../user_add_on_assignment_spec.rb | 21 ++++++++++++ 18 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 db/docs/subscription_user_add_on_assignments.yml create mode 100644 db/migrate/20230616164309_create_subscription_user_add_on_assignments.rb create mode 100644 db/migrate/20230616164705_add_foreign_key_add_on_purchase_id_on_subscription_user_add_on_assignments.rb create mode 100644 db/migrate/20230616164731_add_foreign_key_user_id_on_subscription_user_add_on_assignments.rb create mode 100644 db/schema_migrations/20230616164309 create mode 100644 db/schema_migrations/20230616164705 create mode 100644 db/schema_migrations/20230616164731 create mode 100644 ee/app/models/gitlab_subscriptions.rb create mode 100644 ee/app/models/gitlab_subscriptions/user_add_on_assignment.rb create mode 100644 ee/spec/factories/gitlab_subscriptions/user_add_on_assignment.rb create mode 100644 ee/spec/models/gitlab_subscriptions/user_add_on_assignment_spec.rb diff --git a/db/docs/subscription_user_add_on_assignments.yml b/db/docs/subscription_user_add_on_assignments.yml new file mode 100644 index 0000000000000..acd9b821115f6 --- /dev/null +++ b/db/docs/subscription_user_add_on_assignments.yml @@ -0,0 +1,10 @@ +--- +table_name: subscription_user_add_on_assignments +description: Tracks the assignment of an add-on to a user within a namespace +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123967 +milestone: '16.2' +feature_categories: +- seat_cost_management +classes: +- GitlabSubscriptions::UserAddOnAssignment +gitlab_schema: gitlab_main diff --git a/db/migrate/20230616164309_create_subscription_user_add_on_assignments.rb b/db/migrate/20230616164309_create_subscription_user_add_on_assignments.rb new file mode 100644 index 0000000000000..cb184cd1987d2 --- /dev/null +++ b/db/migrate/20230616164309_create_subscription_user_add_on_assignments.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class CreateSubscriptionUserAddOnAssignments < Gitlab::Database::Migration[2.1] + UNIQUE_INDEX_NAME = 'uniq_idx_user_add_on_assignments_on_add_on_purchase_and_user' + + def change + create_table :subscription_user_add_on_assignments do |t| + t.bigint :add_on_purchase_id, null: false + t.bigint :user_id, null: false + + t.timestamps_with_timezone null: false + + t.index [:add_on_purchase_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME + t.index :user_id + end + end +end diff --git a/db/migrate/20230616164705_add_foreign_key_add_on_purchase_id_on_subscription_user_add_on_assignments.rb b/db/migrate/20230616164705_add_foreign_key_add_on_purchase_id_on_subscription_user_add_on_assignments.rb new file mode 100644 index 0000000000000..d0d89bd5027ca --- /dev/null +++ b/db/migrate/20230616164705_add_foreign_key_add_on_purchase_id_on_subscription_user_add_on_assignments.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddForeignKeyAddOnPurchaseIdOnSubscriptionUserAddOnAssignments < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :subscription_user_add_on_assignments, :subscription_add_on_purchases, + column: :add_on_purchase_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :subscription_user_add_on_assignments, column: :add_on_purchase_id + end + end +end diff --git a/db/migrate/20230616164731_add_foreign_key_user_id_on_subscription_user_add_on_assignments.rb b/db/migrate/20230616164731_add_foreign_key_user_id_on_subscription_user_add_on_assignments.rb new file mode 100644 index 0000000000000..a28c798deec4e --- /dev/null +++ b/db/migrate/20230616164731_add_foreign_key_user_id_on_subscription_user_add_on_assignments.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddForeignKeyUserIdOnSubscriptionUserAddOnAssignments < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :subscription_user_add_on_assignments, :users, column: :user_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :subscription_user_add_on_assignments, column: :user_id + end + end +end diff --git a/db/schema_migrations/20230616164309 b/db/schema_migrations/20230616164309 new file mode 100644 index 0000000000000..b9fbdc7d33eef --- /dev/null +++ b/db/schema_migrations/20230616164309 @@ -0,0 +1 @@ +f3b14748f1702972e7f5069edd9ed25d9896dfb11f4fc4a4386ca9c94533e10a \ No newline at end of file diff --git a/db/schema_migrations/20230616164705 b/db/schema_migrations/20230616164705 new file mode 100644 index 0000000000000..1bcb723524bd1 --- /dev/null +++ b/db/schema_migrations/20230616164705 @@ -0,0 +1 @@ +75310614bb98a598b8425aa87a0a4a6561fa1b166461d55329c21aff849d71fc \ No newline at end of file diff --git a/db/schema_migrations/20230616164731 b/db/schema_migrations/20230616164731 new file mode 100644 index 0000000000000..2588271d9acd7 --- /dev/null +++ b/db/schema_migrations/20230616164731 @@ -0,0 +1 @@ +e6308ee437b6e57da16e1b8aff1d6a571ef849c4c7cccafe940710c799fa6eea \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 35c119628ce21..5cff7ab4337bc 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -23057,6 +23057,23 @@ CREATE SEQUENCE subscription_add_ons_id_seq ALTER SEQUENCE subscription_add_ons_id_seq OWNED BY subscription_add_ons.id; +CREATE TABLE subscription_user_add_on_assignments ( + id bigint NOT NULL, + add_on_purchase_id bigint NOT NULL, + user_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL +); + +CREATE SEQUENCE subscription_user_add_on_assignments_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE subscription_user_add_on_assignments_id_seq OWNED BY subscription_user_add_on_assignments.id; + CREATE TABLE subscriptions ( id integer NOT NULL, user_id integer, @@ -25862,6 +25879,8 @@ ALTER TABLE ONLY subscription_add_on_purchases ALTER COLUMN id SET DEFAULT nextv ALTER TABLE ONLY subscription_add_ons ALTER COLUMN id SET DEFAULT nextval('subscription_add_ons_id_seq'::regclass); +ALTER TABLE ONLY subscription_user_add_on_assignments ALTER COLUMN id SET DEFAULT nextval('subscription_user_add_on_assignments_id_seq'::regclass); + ALTER TABLE ONLY subscriptions ALTER COLUMN id SET DEFAULT nextval('subscriptions_id_seq'::regclass); ALTER TABLE ONLY suggestions ALTER COLUMN id SET DEFAULT nextval('suggestions_id_seq'::regclass); @@ -28351,6 +28370,9 @@ ALTER TABLE ONLY subscription_add_on_purchases ALTER TABLE ONLY subscription_add_ons ADD CONSTRAINT subscription_add_ons_pkey PRIMARY KEY (id); +ALTER TABLE ONLY subscription_user_add_on_assignments + ADD CONSTRAINT subscription_user_add_on_assignments_pkey PRIMARY KEY (id); + ALTER TABLE ONLY subscriptions ADD CONSTRAINT subscriptions_pkey PRIMARY KEY (id); @@ -32910,6 +32932,8 @@ CREATE INDEX index_subscription_add_on_purchases_on_subscription_add_on_id ON su CREATE UNIQUE INDEX index_subscription_add_ons_on_name ON subscription_add_ons USING btree (name); +CREATE INDEX index_subscription_user_add_on_assignments_on_user_id ON subscription_user_add_on_assignments USING btree (user_id); + CREATE INDEX index_subscriptions_on_project_id ON subscriptions USING btree (project_id); CREATE UNIQUE INDEX index_subscriptions_on_subscribable_and_user_id_and_project_id ON subscriptions USING btree (subscribable_id, subscribable_type, user_id, project_id); @@ -33556,6 +33580,8 @@ CREATE UNIQUE INDEX u_project_compliance_standards_adherence_for_reporting ON pr CREATE UNIQUE INDEX uniq_idx_packages_packages_on_project_id_name_version_ml_model ON packages_packages USING btree (project_id, name, version) WHERE (package_type = 14); +CREATE UNIQUE INDEX uniq_idx_user_add_on_assignments_on_add_on_purchase_and_user ON subscription_user_add_on_assignments USING btree (add_on_purchase_id, user_id); + CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name); CREATE UNIQUE INDEX uniq_pkgs_deb_grp_components_on_distribution_id_and_name ON packages_debian_group_components USING btree (distribution_id, name); @@ -35227,6 +35253,9 @@ ALTER TABLE ONLY notification_settings ALTER TABLE ONLY lists ADD CONSTRAINT fk_0d3f677137 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE; +ALTER TABLE ONLY subscription_user_add_on_assignments + ADD CONSTRAINT fk_0d89020c49 FOREIGN KEY (add_on_purchase_id) REFERENCES subscription_add_on_purchases(id) ON DELETE CASCADE; + ALTER TABLE ONLY deployment_approvals ADD CONSTRAINT fk_0f58311058 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; @@ -35596,6 +35625,9 @@ ALTER TABLE ONLY integrations ALTER TABLE ONLY user_interacted_projects ADD CONSTRAINT fk_722ceba4f7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY subscription_user_add_on_assignments + ADD CONSTRAINT fk_724c2df9a8 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ALTER TABLE ONLY vulnerabilities ADD CONSTRAINT fk_725465b774 FOREIGN KEY (dismissed_by_id) REFERENCES users(id) ON DELETE SET NULL; diff --git a/ee/app/models/ee/user.rb b/ee/app/models/ee/user.rb index eb12df91d3bbc..3be56ce669ebd 100644 --- a/ee/app/models/ee/user.rb +++ b/ee/app/models/ee/user.rb @@ -103,6 +103,8 @@ module User has_many :dependency_list_exports, class_name: 'Dependencies::DependencyListExport', inverse_of: :author + has_many :assigned_add_ons, class_name: 'GitlabSubscriptions::UserAddOnAssignment', inverse_of: :user + scope :not_managed, ->(group: nil) { scope = where(managing_group_id: nil) scope = scope.or(where.not(managing_group_id: group.id)) if group diff --git a/ee/app/models/gitlab_subscriptions.rb b/ee/app/models/gitlab_subscriptions.rb new file mode 100644 index 0000000000000..2ee838aeceb65 --- /dev/null +++ b/ee/app/models/gitlab_subscriptions.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module GitlabSubscriptions + def self.table_name_prefix + 'subscription_' + end +end diff --git a/ee/app/models/gitlab_subscriptions/add_on.rb b/ee/app/models/gitlab_subscriptions/add_on.rb index ba8d85c5af3d4..9416eac2c61ab 100644 --- a/ee/app/models/gitlab_subscriptions/add_on.rb +++ b/ee/app/models/gitlab_subscriptions/add_on.rb @@ -2,8 +2,6 @@ module GitlabSubscriptions class AddOn < ApplicationRecord - self.table_name = 'subscription_add_ons' - has_many :add_on_purchases, foreign_key: :subscription_add_on_id, inverse_of: :add_on validates :name, diff --git a/ee/app/models/gitlab_subscriptions/add_on_purchase.rb b/ee/app/models/gitlab_subscriptions/add_on_purchase.rb index 246052cdce7b5..9496b935845ec 100644 --- a/ee/app/models/gitlab_subscriptions/add_on_purchase.rb +++ b/ee/app/models/gitlab_subscriptions/add_on_purchase.rb @@ -2,11 +2,11 @@ module GitlabSubscriptions class AddOnPurchase < ApplicationRecord - self.table_name = 'subscription_add_on_purchases' - belongs_to :add_on, foreign_key: :subscription_add_on_id, inverse_of: :add_on_purchases belongs_to :namespace + has_many :assigned_users, class_name: 'GitlabSubscriptions::UserAddOnAssignment', inverse_of: :add_on_purchase + validates :add_on, :namespace, :expires_on, presence: true validates :subscription_add_on_id, uniqueness: { scope: :namespace_id } validates :quantity, diff --git a/ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb b/ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb index 599b22924835e..2f6983660a6bf 100644 --- a/ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb +++ b/ee/app/models/gitlab_subscriptions/upcoming_reconciliation.rb @@ -4,6 +4,8 @@ module GitlabSubscriptions class UpcomingReconciliation < ApplicationRecord include BulkInsertSafe + self.table_name = 'upcoming_reconciliations' + belongs_to :namespace, inverse_of: :upcoming_reconciliation, optional: true # Validate presence of namespace_id if this is running on a GitLab instance diff --git a/ee/app/models/gitlab_subscriptions/user_add_on_assignment.rb b/ee/app/models/gitlab_subscriptions/user_add_on_assignment.rb new file mode 100644 index 0000000000000..c3b11329c1594 --- /dev/null +++ b/ee/app/models/gitlab_subscriptions/user_add_on_assignment.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module GitlabSubscriptions + class UserAddOnAssignment < ApplicationRecord + belongs_to :user, inverse_of: :assigned_add_ons + belongs_to :add_on_purchase, class_name: 'GitlabSubscriptions::AddOnPurchase', inverse_of: :assigned_users + + validates :user, :add_on_purchase, presence: true + validates :add_on_purchase_id, uniqueness: { scope: :user_id } + end +end diff --git a/ee/spec/factories/gitlab_subscriptions/user_add_on_assignment.rb b/ee/spec/factories/gitlab_subscriptions/user_add_on_assignment.rb new file mode 100644 index 0000000000000..1b4eb4d640352 --- /dev/null +++ b/ee/spec/factories/gitlab_subscriptions/user_add_on_assignment.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :gitlab_subscription_user_add_on_assignment, class: 'GitlabSubscriptions::UserAddOnAssignment' do + user + add_on_purchase { association(:gitlab_subscription_add_on_purchase) } + end +end diff --git a/ee/spec/models/ee/user_spec.rb b/ee/spec/models/ee/user_spec.rb index 4ecce8d03e947..b9e513870d111 100644 --- a/ee/spec/models/ee/user_spec.rb +++ b/ee/spec/models/ee/user_spec.rb @@ -43,6 +43,7 @@ it { is_expected.to have_many(:namespace_bans).class_name('Namespaces::NamespaceBan') } it { is_expected.to have_many(:dependency_list_exports).class_name('Dependencies::DependencyListExport') } it { is_expected.to have_many(:elevated_members).class_name('Member') } + it { is_expected.to have_many(:assigned_add_ons).class_name('GitlabSubscriptions::UserAddOnAssignment').inverse_of(:user) } end describe 'nested attributes' do diff --git a/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb b/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb index 4f92584cc3a0a..f7969eb17e786 100644 --- a/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb +++ b/ee/spec/models/gitlab_subscriptions/add_on_purchase_spec.rb @@ -8,6 +8,11 @@ describe 'associations' do it { is_expected.to belong_to(:add_on).with_foreign_key(:subscription_add_on_id).inverse_of(:add_on_purchases) } it { is_expected.to belong_to(:namespace) } + + it do + is_expected.to have_many(:assigned_users) + .class_name('GitlabSubscriptions::UserAddOnAssignment').inverse_of(:add_on_purchase) + end end describe 'validations' do diff --git a/ee/spec/models/gitlab_subscriptions/user_add_on_assignment_spec.rb b/ee/spec/models/gitlab_subscriptions/user_add_on_assignment_spec.rb new file mode 100644 index 0000000000000..f9760cf36cf6e --- /dev/null +++ b/ee/spec/models/gitlab_subscriptions/user_add_on_assignment_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSubscriptions::UserAddOnAssignment, feature_category: :seat_cost_management do + describe 'associations' do + it { is_expected.to belong_to(:user).inverse_of(:assigned_add_ons) } + it { is_expected.to belong_to(:add_on_purchase).inverse_of(:assigned_users) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:add_on_purchase) } + + context 'for uniqueness' do + subject { build(:gitlab_subscription_user_add_on_assignment) } + + it { is_expected.to validate_uniqueness_of(:add_on_purchase_id).scoped_to(:user_id) } + end + end +end -- GitLab