diff --git a/app/models/project.rb b/app/models/project.rb index 35e38cd37d1ef85e9e1a3c9a25b8e929b3e9ad53..26d5ca6c69b47caa265518208b25138854645ce4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -551,6 +551,7 @@ def self.integration_association_name(name) delegate :show_default_award_emojis, :show_default_award_emojis= delegate :enforce_auth_checks_on_uploads, :enforce_auth_checks_on_uploads= delegate :warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters= + delegate :code_suggestions, :code_suggestions= end end @@ -3199,6 +3200,11 @@ def instance_runner_running_jobs_count end strong_memoize_attr :instance_runner_running_jobs_count + def code_suggestions_enabled? + code_suggestions && (group.nil? || group.code_suggestions) + end + strong_memoize_attr :code_suggestions_enabled? + private # overridden in EE diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb index f684feacace1420b84e692602d93ea1ae4d04508..e3ffe2347d868dd40514305cbf622fbdd6be5966 100644 --- a/app/models/project_setting.rb +++ b/app/models/project_setting.rb @@ -39,6 +39,7 @@ class ProjectSetting < ApplicationRecord validates :issue_branch_template, length: { maximum: Issue::MAX_BRANCH_TEMPLATE } validates :target_platforms, inclusion: { in: ALLOWED_TARGET_PLATFORMS } validates :suggested_reviewers_enabled, inclusion: { in: [true, false] } + validates :code_suggestions, allow_nil: false, inclusion: { in: [true, false] } validates :pages_unique_domain, uniqueness: { if: -> { pages_unique_domain.present? } }, diff --git a/db/migrate/20231205163658_add_code_suggestions_to_project_setting.rb b/db/migrate/20231205163658_add_code_suggestions_to_project_setting.rb new file mode 100644 index 0000000000000000000000000000000000000000..53c303963a55c7030b703b0981d3a4bdaf3c52db --- /dev/null +++ b/db/migrate/20231205163658_add_code_suggestions_to_project_setting.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddCodeSuggestionsToProjectSetting < Gitlab::Database::Migration[2.2] + enable_lock_retries! + milestone '16.7' + + def change + add_column :project_settings, :code_suggestions, :boolean, default: true, null: false + end +end diff --git a/db/schema_migrations/20231205163658 b/db/schema_migrations/20231205163658 new file mode 100644 index 0000000000000000000000000000000000000000..c2ecfa8486bcc1e1d10c36d71b6a5c2f5a5fee4c --- /dev/null +++ b/db/schema_migrations/20231205163658 @@ -0,0 +1 @@ +94118057fe8e0d4ed9ac6590e3aa48088f26524f02dead72f338ff58c078ef33 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index dc6e8c4cef9a6e3e81452d48b8eb509b0057d620..c1ef64251e1dce60e5e53afab904349b9c3b13d0 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -22195,6 +22195,7 @@ CREATE TABLE project_settings ( encrypted_product_analytics_configurator_connection_string_iv bytea, pages_multiple_versions_enabled boolean DEFAULT false NOT NULL, allow_merge_without_pipeline boolean DEFAULT false NOT NULL, + code_suggestions boolean DEFAULT true NOT NULL, CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)), CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)), CONSTRAINT check_3ca5cbffe6 CHECK ((char_length(issue_branch_template) <= 255)), diff --git a/ee/lib/api/code_suggestions.rb b/ee/lib/api/code_suggestions.rb index 69e2a29e68ee7f7231fdf8e526f4f70760f7b152..e6fb68c3d3ad06ff054c69a5b0f47bca14b3a468 100644 --- a/ee/lib/api/code_suggestions.rb +++ b/ee/lib/api/code_suggestions.rb @@ -173,6 +173,35 @@ def gitlab_realm body '' end end + + resources :enabled do + desc 'Code suggestions enabled for a project' do + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: '403 Code Suggestions Disabled' }, + { code: 404, message: 'Not found' } + ] + end + params do + requires :project_path, type: String, desc: 'The path of the project', + documentation: { example: 'namespace/project' } + end + + post do + path = declared_params[:project_path] + + not_found! if path.empty? + + projects = ::ProjectsFinder.new(params: { full_paths: [path] }, current_user: current_user).execute + + not_found! if projects.none? + + forbidden! unless projects.first.code_suggestions_enabled? + + status :ok + end + end end end end diff --git a/ee/spec/requests/api/code_suggestions_spec.rb b/ee/spec/requests/api/code_suggestions_spec.rb index b8a76901a6b43f63a1d4f0726defb6e33d61d96e..253a0dca3dd33992cdea22b2de58c0c96b19c335 100644 --- a/ee/spec/requests/api/code_suggestions_spec.rb +++ b/ee/spec/requests/api/code_suggestions_spec.rb @@ -725,4 +725,51 @@ def is_even(n: int) -> end end end + + context 'when checking in project has code suggestions enabled' do + let_it_be(:enabled_project) { create(:project, :with_code_suggestions_enabled) } + let(:current_user) { authorized_user } + let_it_be(:disabled_project) { create(:project, :with_code_suggestions_disabled) } + let_it_be(:secret_project) { create(:project, :with_code_suggestions_enabled) } + + before_all do + enabled_project.add_maintainer(authorized_user) + disabled_project.add_maintainer(authorized_user) + end + + subject { post api("/code_suggestions/enabled", current_user), params: { project_path: project_path } } + + context 'when not logged in' do + let(:current_user) { nil } + let(:project_path) { enabled_project.full_path } + + it { is_expected.to eq(401) } + end + + context 'when authorized' do + context 'when enabled' do + let(:project_path) { enabled_project.full_path } + + it { is_expected.to eq(200) } + end + + context 'when disabled' do + let(:project_path) { disabled_project.full_path } + + it { is_expected.to eq(403) } + end + + context 'when user cannot access project' do + let(:project_path) { secret_project.full_path } + + it { is_expected.to eq(404) } + end + + context 'when does not exist' do + let(:project_path) { 'not_a_real_project' } + + it { is_expected.to eq(404) } + end + end + end end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 12e022bfb2044f5ac43ea5c4ef166c32d7b9104e..b8e489c9d5192b48884cdbaa76c0b1131e3e9a9e 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -41,6 +41,7 @@ class Project < BasicProjectDetails end end + expose :code_suggestions, documentation: { type: 'boolean' } expose :packages_enabled, documentation: { type: 'boolean' } expose :empty_repo?, as: :empty_repo, documentation: { type: 'boolean' } expose :archived?, as: :archived, documentation: { type: 'boolean' } diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 4da7dcba7635dab605b531463157c63f8a7f98a1..a2848bd025613647183c93009bc44237373121cf 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -608,4 +608,16 @@ path { 'gitlab-profile' } files { { 'README.md' => 'Hello World' } } end + + trait :with_code_suggestions_enabled do + after(:create) do |project| + project.project_setting.update!(code_suggestions: true) + end + end + + trait :with_code_suggestions_disabled do + after(:create) do |project| + project.project_setting.update!(code_suggestions: false) + end + end end