Commit 8964f64d authored by Francisco Javier López's avatar Francisco Javier López Committed by Adam Hegyi

Introduce new Group Feature model and database structure

In this commit we're starting the foundations for group features.
It introduces a really small Groups::Feature model with the most
basic functionality plus all the necessary database structure
and backfilling migration.

Changelog: added
parent 79e6b4db
...@@ -96,6 +96,8 @@ class Group < Namespace ...@@ -96,6 +96,8 @@ class Group < Namespace
has_many :group_callouts, class_name: 'Users::GroupCallout', foreign_key: :group_id has_many :group_callouts, class_name: 'Users::GroupCallout', foreign_key: :group_id
has_one :group_feature, inverse_of: :group, class_name: 'Groups::FeatureSetting'
delegate :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap, :setup_for_company, :jobs_to_be_done, to: :namespace_settings delegate :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap, :setup_for_company, :jobs_to_be_done, to: :namespace_settings
delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
delegate :subgroup_runner_token_expiration_interval, :subgroup_runner_token_expiration_interval=, :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true delegate :subgroup_runner_token_expiration_interval, :subgroup_runner_token_expiration_interval=, :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
...@@ -119,6 +121,8 @@ class Group < Namespace ...@@ -119,6 +121,8 @@ class Group < Namespace
message: Gitlab::Regex.group_name_regex_message }, message: Gitlab::Regex.group_name_regex_message },
if: :name_changed? if: :name_changed?
validates :group_feature, presence: true
add_authentication_token_field :runners_token, add_authentication_token_field :runners_token,
encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required },
prefix: RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX prefix: RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
...@@ -127,6 +131,7 @@ class Group < Namespace ...@@ -127,6 +131,7 @@ class Group < Namespace
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
after_save :update_two_factor_requirement after_save :update_two_factor_requirement
after_update :path_changed_hook, if: :saved_change_to_path? after_update :path_changed_hook, if: :saved_change_to_path?
after_create -> { create_or_load_association(:group_feature) }
scope :with_users, -> { includes(:users) } scope :with_users, -> { includes(:users) }
...@@ -796,6 +801,10 @@ class Group < Namespace ...@@ -796,6 +801,10 @@ class Group < Namespace
super || build_dependency_proxy_setting super || build_dependency_proxy_setting
end end
def group_feature
super || build_group_feature
end
def crm_enabled? def crm_enabled?
crm_settings&.enabled? crm_settings&.enabled?
end end
......
# frozen_string_literal: true
module Groups
class FeatureSetting < ApplicationRecord
self.primary_key = :group_id
self.table_name = 'group_features'
belongs_to :group
validates :group, presence: true
end
end
# frozen_string_literal: true
class AddGroupFeaturesTable < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def up
create_table :group_features, id: false do |t|
t.references :group, index: false, foreign_key: { to_table: :namespaces, on_delete: :cascade }, null: false
t.timestamps_with_timezone null: false
t.integer :wiki_access_level, default: Featurable::ENABLED, null: false, limit: 2
end
execute('ALTER TABLE group_features ADD PRIMARY KEY (group_id)')
end
def down
drop_table :group_features
end
end
# frozen_string_literal: true
class BackfillGroupFeatures < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
MIGRATION = 'BackfillGroupFeatures'
INTERVAL = 2.minutes
BATCH_SIZE = 10_000
SUB_BATCH_SIZE = 1_000
def up
queue_batched_background_migration(
MIGRATION,
:namespaces,
:id,
BATCH_SIZE,
job_interval: INTERVAL,
batch_size: BATCH_SIZE,
max_batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
Gitlab::Database::BackgroundMigration::BatchedMigration
.for_configuration(MIGRATION, :namespaces, :id, [BATCH_SIZE])
.delete_all
end
end
4f565a313c37d12f24afe26a9e344d4273c54d0f080b3108d56ed98bf7299ecc
\ No newline at end of file
4e2de14559b47a9bf3fa8910fdb84ff62cb18442620f4147e40e8026bf4bcf1b
\ No newline at end of file
...@@ -15679,6 +15679,13 @@ CREATE SEQUENCE group_deploy_tokens_id_seq ...@@ -15679,6 +15679,13 @@ CREATE SEQUENCE group_deploy_tokens_id_seq
ALTER SEQUENCE group_deploy_tokens_id_seq OWNED BY group_deploy_tokens.id; ALTER SEQUENCE group_deploy_tokens_id_seq OWNED BY group_deploy_tokens.id;
CREATE TABLE group_features (
group_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
wiki_access_level smallint DEFAULT 20 NOT NULL
);
CREATE TABLE group_group_links ( CREATE TABLE group_group_links (
id bigint NOT NULL, id bigint NOT NULL,
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
...@@ -24529,6 +24536,9 @@ ALTER TABLE ONLY group_deploy_keys ...@@ -24529,6 +24536,9 @@ ALTER TABLE ONLY group_deploy_keys
ALTER TABLE ONLY group_deploy_tokens ALTER TABLE ONLY group_deploy_tokens
ADD CONSTRAINT group_deploy_tokens_pkey PRIMARY KEY (id); ADD CONSTRAINT group_deploy_tokens_pkey PRIMARY KEY (id);
ALTER TABLE ONLY group_features
ADD CONSTRAINT group_features_pkey PRIMARY KEY (group_id);
ALTER TABLE ONLY group_group_links ALTER TABLE ONLY group_group_links
ADD CONSTRAINT group_group_links_pkey PRIMARY KEY (id); ADD CONSTRAINT group_group_links_pkey PRIMARY KEY (id);
...@@ -32199,6 +32209,9 @@ ALTER TABLE ONLY requirements ...@@ -32199,6 +32209,9 @@ ALTER TABLE ONLY requirements
ALTER TABLE ONLY metrics_dashboard_annotations ALTER TABLE ONLY metrics_dashboard_annotations
ADD CONSTRAINT fk_rails_345ab51043 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_345ab51043 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE;
ALTER TABLE ONLY group_features
ADD CONSTRAINT fk_rails_356514082b FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY wiki_page_slugs ALTER TABLE ONLY wiki_page_slugs
ADD CONSTRAINT fk_rails_358b46be14 FOREIGN KEY (wiki_page_meta_id) REFERENCES wiki_page_meta(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_358b46be14 FOREIGN KEY (wiki_page_meta_id) REFERENCES wiki_page_meta(id) ON DELETE CASCADE;
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Backfill group_features for an array of groups
class BackfillGroupFeatures < ::Gitlab::BackgroundMigration::BaseJob
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, end_id, batch_table, batch_column, sub_batch_size, pause_ms, batch_size)
pause_ms = 0 if pause_ms < 0
parent_batch_relation = relation_scoped_to_range(batch_table, batch_column, start_id, end_id)
parent_batch_relation.each_batch(column: batch_column, of: sub_batch_size, order_hint: :type) do |sub_batch|
batch_metrics.time_operation(:upsert_group_features) do
upsert_group_features(sub_batch, batch_size)
end
sleep(pause_ms * 0.001)
end
end
def batch_metrics
@batch_metrics ||= Gitlab::Database::BackgroundMigration::BatchMetrics.new
end
private
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
define_batchable_model(source_table, connection: connection)
.where(source_key_column => start_id..stop_id)
.where(type: 'Group')
end
def upsert_group_features(relation, batch_size)
connection.execute(
<<~SQL
INSERT INTO group_features (group_id, created_at, updated_at)
SELECT namespaces.id as group_id, now(), now()
FROM namespaces
WHERE namespaces.type = 'Group' AND namespaces.id IN(#{relation.select(:id).limit(batch_size).to_sql})
ON CONFLICT (group_id) DO NOTHING;
SQL
)
end
end
end
end
...@@ -240,6 +240,7 @@ group_deletion_schedules: :gitlab_main ...@@ -240,6 +240,7 @@ group_deletion_schedules: :gitlab_main
group_deploy_keys: :gitlab_main group_deploy_keys: :gitlab_main
group_deploy_keys_groups: :gitlab_main group_deploy_keys_groups: :gitlab_main
group_deploy_tokens: :gitlab_main group_deploy_tokens: :gitlab_main
group_features: :gitlab_main
group_group_links: :gitlab_main group_group_links: :gitlab_main
group_import_states: :gitlab_main group_import_states: :gitlab_main
group_merge_request_approval_settings: :gitlab_main group_merge_request_approval_settings: :gitlab_main
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillGroupFeatures, :migration, schema: 20220302114046 do
let(:group_features) { table(:group_features) }
let(:namespaces) { table(:namespaces) }
subject { described_class.new(connection: ActiveRecord::Base.connection) }
describe '#perform' do
it 'creates settings for all group namespaces in range' do
namespaces.create!(id: 1, name: 'group1', path: 'group1', type: 'Group')
namespaces.create!(id: 2, name: 'user', path: 'user')
namespaces.create!(id: 3, name: 'group2', path: 'group2', type: 'Group')
# Checking that no error is raised if the group_feature for a group already exists
namespaces.create!(id: 4, name: 'group3', path: 'group3', type: 'Group')
group_features.create!(id: 1, group_id: 4)
expect(group_features.count).to eq 1
expect { subject.perform(1, 4, :namespaces, :id, 10, 0, 4) }.to change { group_features.count }.by(2)
expect(group_features.count).to eq 3
expect(group_features.all.pluck(:group_id)).to contain_exactly(1, 3, 4)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe BackfillGroupFeatures, :migration do
let(:migration) { described_class::MIGRATION }
describe '#up' do
it 'schedules background jobs for each batch of namespaces' do
migrate!
expect(migration).to have_scheduled_batched_migration(
table_name: :namespaces,
column_name: :id,
job_arguments: [described_class::BATCH_SIZE],
interval: described_class::INTERVAL,
batch_size: described_class::BATCH_SIZE
)
end
end
describe '#down' do
it 'deletes all batched migration records' do
migrate!
schema_migrate_down!
expect(migration).not_to have_scheduled_batched_migration
end
end
end
...@@ -41,6 +41,7 @@ RSpec.describe Group do ...@@ -41,6 +41,7 @@ RSpec.describe Group do
it { is_expected.to have_many(:contacts).class_name('CustomerRelations::Contact') } it { is_expected.to have_many(:contacts).class_name('CustomerRelations::Contact') }
it { is_expected.to have_many(:organizations).class_name('CustomerRelations::Organization') } it { is_expected.to have_many(:organizations).class_name('CustomerRelations::Organization') }
it { is_expected.to have_one(:crm_settings) } it { is_expected.to have_one(:crm_settings) }
it { is_expected.to have_one(:group_feature) }
describe '#members & #requesters' do describe '#members & #requesters' do
let(:requester) { create(:user) } let(:requester) { create(:user) }
...@@ -295,6 +296,21 @@ RSpec.describe Group do ...@@ -295,6 +296,21 @@ RSpec.describe Group do
it_behaves_like 'a BulkUsersByEmailLoad model' it_behaves_like 'a BulkUsersByEmailLoad model'
context 'after initialized' do
it 'has a group_feature' do
expect(described_class.new.group_feature).to be_present
end
end
context 'when creating a new project' do
let_it_be(:group) { create(:group) }
it 'automatically creates the groups feature for the group' do
expect(group.group_feature).to be_an_instance_of(Groups::FeatureSetting)
expect(group.group_feature).to be_persisted
end
end
context 'traversal_ids on create' do context 'traversal_ids on create' do
context 'default traversal_ids' do context 'default traversal_ids' do
let(:group) { build(:group) } let(:group) { build(:group) }
......
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