diff --git a/Gemfile b/Gemfile index 87d970ababb2e3dd95b891f450071eddf3b164d9..f0b33c5dd0e775d28955a77b4c6a6e442c11affb 100644 --- a/Gemfile +++ b/Gemfile @@ -566,6 +566,7 @@ gem 'lockbox', '~> 1.1.1' gem 'valid_email', '~> 0.1' # JSON +gem 'jsonb_accessor', '~> 1.3.10' gem 'json', '~> 2.6.3' gem 'json_schemer', '~> 0.2.18' gem 'oj', '~> 3.13.21' diff --git a/Gemfile.checksum b/Gemfile.checksum index db8b20f6e49398ef0d5cf91e107ebda561b1980a..ea6d7716c1e3fd23211797d08234d39fa674fc4b 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -316,6 +316,8 @@ {"name":"json","version":"2.6.3","platform":"ruby","checksum":"86aaea16adf346a2b22743d88f8dcceeb1038843989ab93cda44b5176c845459"}, {"name":"json-jwt","version":"1.15.3","platform":"ruby","checksum":"66db4f14e538a774c15502a5b5b26b1f3e7585481bbb96df490aa74b5c2d6110"}, {"name":"json_schemer","version":"0.2.18","platform":"ruby","checksum":"3362c21efbefdd12ce994e541a1e7fdb86fd267a6541dd8715e8a580fe3b6be6"}, +{"name":"jsonb_accessor","version":"1.3.10","platform":"java","checksum":"6630ac69dac46457b03e1352178ed3e2d7ba2d8edb99f2e9b64a0e60cda9ed26"}, +{"name":"jsonb_accessor","version":"1.3.10","platform":"ruby","checksum":"670f80a257ae39e3be9233c6a8ef3b03517e06687affe510dfe61237454c58e0"}, {"name":"jsonpath","version":"1.1.2","platform":"ruby","checksum":"6804124c244d04418218acb85b15c7caa79c592d7d6970195300428458946d3a"}, {"name":"jwt","version":"2.5.0","platform":"ruby","checksum":"b835fe55287572e1f65128d6c12d3ff7402bb4652c4565bf3ecdcb974db7954d"}, {"name":"kaminari","version":"1.2.2","platform":"ruby","checksum":"c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e"}, diff --git a/Gemfile.lock b/Gemfile.lock index 6a704144a5575991beaa317c0821646c2a9d1673..3a10ac441fb117f62dae5dd6b1042b9ee090aecc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -863,6 +863,10 @@ GEM hana (~> 1.3) regexp_parser (~> 2.0) uri_template (~> 0.7) + jsonb_accessor (1.3.10) + activerecord (>= 5.0) + activesupport (>= 5.0) + pg (>= 0.18.1) jsonpath (1.1.2) multi_json jwt (2.5.0) @@ -1822,6 +1826,7 @@ DEPENDENCIES js_regex (~> 3.8) json (~> 2.6.3) json_schemer (~> 0.2.18) + jsonb_accessor (~> 1.3.10) jwt (~> 2.5) kaminari (~> 1.2.2) kas-grpc (~> 0.2.0) diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb index ce89f57a73bf9a6e5354657972a1dce13f4d89e4..7c3ba4bf50cb30f2cec0dfd9bf8e9182ea4b3578 100644 --- a/app/models/organizations/organization.rb +++ b/app/models/organizations/organization.rb @@ -8,6 +8,8 @@ class Organization < ApplicationRecord before_destroy :check_if_default_organization + has_one :settings, class_name: "OrganizationSetting" + validates :name, presence: true, length: { maximum: 255 } diff --git a/app/models/organizations/organization_setting.rb b/app/models/organizations/organization_setting.rb new file mode 100644 index 0000000000000000000000000000000000000000..5a5ace0cc061af30de1eea92235f8b261e6a4170 --- /dev/null +++ b/app/models/organizations/organization_setting.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Organizations + class OrganizationSetting < ApplicationRecord + belongs_to :organization + + jsonb_accessor :settings, + restricted_visibility_levels: [:integer, { array: true }] + + validates_each :restricted_visibility_levels do |record, attr, value| + value&.each do |level| + unless Gitlab::VisibilityLevel.options.value?(level) + record.errors.add(attr, format(_("'%{level}' is not a valid visibility level"), level: level)) + end + end + end + end +end diff --git a/db/docs/organization_settings.yml b/db/docs/organization_settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..669aafc9ed7bf1a83ca492a0bfe470abef4d6bae --- /dev/null +++ b/db/docs/organization_settings.yml @@ -0,0 +1,10 @@ +--- +table_name: organization_settings +classes: +- Organizations::OrganizationSetting +feature_categories: +- cell +description: Settings related to Organizations +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123380 +milestone: '16.2' +gitlab_schema: gitlab_main diff --git a/db/migrate/20230607124754_create_organization_settings.rb b/db/migrate/20230607124754_create_organization_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..15d3fa3159fbd9e06416f9dd0502b1b6ccc8fa56 --- /dev/null +++ b/db/migrate/20230607124754_create_organization_settings.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class CreateOrganizationSettings < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def change + create_table :organization_settings, id: false do |t| + t.references :organization, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade } + t.timestamps_with_timezone null: false + t.jsonb :settings, default: {}, null: false + end + end +end diff --git a/db/schema_migrations/20230607124754 b/db/schema_migrations/20230607124754 new file mode 100644 index 0000000000000000000000000000000000000000..2c49744ec26ac7a2e0502105b7ea9a99d4ba0756 --- /dev/null +++ b/db/schema_migrations/20230607124754 @@ -0,0 +1 @@ +3aca70a09ce278454f38740817bba4e88501b8e68d719b9ef3f922cb09b7c7d3 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index ebd2c9a0424b4f952bb09d788a69c59d8d63de8c..600e2bfb47539363f787615d87689485696ddde5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19235,6 +19235,13 @@ CREATE SEQUENCE operations_user_lists_id_seq ALTER SEQUENCE operations_user_lists_id_seq OWNED BY operations_user_lists.id; +CREATE TABLE organization_settings ( + organization_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + settings jsonb DEFAULT '{}'::jsonb NOT NULL +); + CREATE TABLE organizations ( id bigint NOT NULL, created_at timestamp with time zone NOT NULL, @@ -27788,6 +27795,9 @@ ALTER TABLE ONLY operations_strategies_user_lists ALTER TABLE ONLY operations_user_lists ADD CONSTRAINT operations_user_lists_pkey PRIMARY KEY (id); +ALTER TABLE ONLY organization_settings + ADD CONSTRAINT organization_settings_pkey PRIMARY KEY (organization_id); + ALTER TABLE ONLY organizations ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); @@ -37516,6 +37526,9 @@ ALTER TABLE ONLY boards_epic_board_recent_visits ALTER TABLE ONLY ci_job_artifacts ADD CONSTRAINT fk_rails_c5137cb2c1_p FOREIGN KEY (partition_id, job_id) REFERENCES p_ci_builds(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE; +ALTER TABLE ONLY organization_settings + ADD CONSTRAINT fk_rails_c56e4690c0 FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ALTER TABLE ONLY project_settings ADD CONSTRAINT fk_rails_c6df6e6328 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 4ec6d3ad4f573d711b07219d6c3b8e6c9399c8fe..5716b9fd012e14cd0a9c0de094e61734a6de44b7 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -244,6 +244,7 @@ "GeoNodeStatus" => %w[status], "Operations::FeatureFlagScope" => %w[strategies], "Operations::FeatureFlags::Strategy" => %w[parameters], + "Organizations::OrganizationSetting" => %w[settings], # Custom validations "Packages::Composer::Metadatum" => %w[composer_json], "RawUsageData" => %w[payload], # Usage data payload changes often, we cannot use one schema "Releases::Evidence" => %w[summary], diff --git a/spec/factories/organizations/organization_settings.rb b/spec/factories/organizations/organization_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..ad4715ee653ff68b520c84f5597b6ba0b47935a5 --- /dev/null +++ b/spec/factories/organizations/organization_settings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :organization_setting, class: 'Organizations::OrganizationSetting' do + organization { association(:organization) } + end +end diff --git a/spec/models/organizations/organization_setting_spec.rb b/spec/models/organizations/organization_setting_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0dd379743f4a09d91826249cc288822154b9e295 --- /dev/null +++ b/spec/models/organizations/organization_setting_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Organizations::OrganizationSetting, type: :model, feature_category: :cell do + let_it_be(:organization) { create(:organization) } + + describe 'associations' do + it { is_expected.to belong_to :organization } + end + + describe 'validations' do + context 'when setting restricted_visibility_levels' do + it 'is one or more of Gitlab::VisibilityLevel constants' do + setting = build(:organization_setting) + + setting.restricted_visibility_levels = [123] + + expect(setting.valid?).to be false + expect(setting.errors.full_messages).to include( + "Restricted visibility levels '123' is not a valid visibility level" + ) + + setting.restricted_visibility_levels = [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL] + expect(setting.valid?).to be true + end + end + end +end