From c71db28d3fa1dee67c4c2608f2169cb50b8d1517 Mon Sep 17 00:00:00 2001 From: ievgenii vdovenko Date: Wed, 21 Jan 2026 11:59:03 +0100 Subject: [PATCH] feature: implements AI code review PR checkbox; adds AI PR settings --- rhodecode/apps/admin/views/ai.py | 2 + .../repository/views/repo_pull_requests.py | 89 +++++++++---------- .../repository/views/repo_settings_vcs.py | 11 +++ rhodecode/model/forms.py | 3 + rhodecode/model/settings.py | 1 + .../templates/admin/settings/settings_ai.mako | 20 ++++- rhodecode/templates/base/vcs_settings.mako | 28 ++++++ .../templates/pullrequests/pullrequest.mako | 25 ++++++ .../models/settings/test_vcs_settings.py | 1 + 9 files changed, 132 insertions(+), 48 deletions(-) diff --git a/rhodecode/apps/admin/views/ai.py b/rhodecode/apps/admin/views/ai.py index 59cc1832..9a31b055 100644 --- a/rhodecode/apps/admin/views/ai.py +++ b/rhodecode/apps/admin/views/ai.py @@ -36,6 +36,7 @@ class AdminAiView(BaseAppView): c.selected_ai_model_version = app_settings.get("rhodecode_ai_model_version") c.api_key = app_settings.get("rhodecode_ai_api_key") c.ai_features_enabled = app_settings.get("rhodecode_ai_features_enabled", False) + c.ai_default_code_review = app_settings.get("rhodecode_ai_default_code_review", False) c.ai_instructions = app_settings.get("rhodecode_ai_code_review_instructions") if not c.ai_instructions: @@ -112,6 +113,7 @@ class AdminAiView(BaseAppView): try: settings = [ ("ai_features_enabled", "rhodecode_ai_features_enabled", "bool"), + ("ai_default_code_review", "rhodecode_ai_default_code_review", "bool"), ("ai_model", "rhodecode_ai_model", "unicode"), ("ai_model_version", "rhodecode_ai_model_version", "unicode"), ("ai_api_key", "rhodecode_ai_api_key", "unicode"), diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py index 8c71c007..1d966843 100644 --- a/rhodecode/apps/repository/views/repo_pull_requests.py +++ b/rhodecode/apps/repository/views/repo_pull_requests.py @@ -913,6 +913,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): self.assure_not_empty_repo() source_repo = self.db_repo + app_settings = c.rc_config + c.ai_enabled = app_settings.get("rhodecode_ai_features_enabled", False) + commit_id = self.request.GET.get("commit") branch_ref = self.request.GET.get("branch") bookmark_ref = self.request.GET.get("bookmark") @@ -971,6 +974,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): # Get security scan setting for pre-checking the checkbox c.pr_security_scan_enabled = self._get_repo_setting(source_repo, "rhodecode_pr_security_scan_enabled", False) + c.ai_pr_review = self._get_repo_setting( + source_repo, "rhodecode_ai_default_code_review", app_settings.get("rhodecode_ai_default_code_review", False) + ) return self._get_template_context(c) @LoginRequired() @@ -1311,6 +1317,11 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): except ImportError: log.warning("EE security audit tasks not available, skipping") + rc_settings = SettingsModel() + ai_enabled = rc_settings.get_setting_by_name("ai_features_enabled") + if ai_enabled and _form.get("run_ai_codereview", False): + self._try_start_ai_code_review(_, pull_request, add_existing_users=True) + Session().commit() h.flash(_("Successfully opened new pull request"), category="success") @@ -1373,62 +1384,46 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView): log.warning("Code review already finished.") return False - admin_permissions = ( - "repository.admin", - "hg.admin", - ) - is_admin = h.HasRepoPermissionAny(*admin_permissions)( - user=self._rhodecode_user, repo_name=pull_request.target_repo.repo_name - ) + ai_user = User.get_ai_user() + log.debug("Initiating code review for pull request id: %s", pull_request.pull_request_id) - if is_admin: - ai_user = User.get_ai_user() - log.debug("Initiating code review for pull request id: %s", pull_request.pull_request_id) + reviewers_data = [] - reviewers_data = [] - - if add_existing_users: - current_reviewers = PullRequestReviewers.get_pull_request_reviewers( - pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER - ) - for existing_reviewer in current_reviewers: - reviewers_data.append( - ( - existing_reviewer.user_id, - existing_reviewer.reasons, - existing_reviewer.mandatory, - existing_reviewer.role, - existing_reviewer.rule_data, - ) + if add_existing_users: + current_reviewers = PullRequestReviewers.get_pull_request_reviewers( + pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER + ) + for existing_reviewer in current_reviewers: + reviewers_data.append( + ( + existing_reviewer.user_id, + existing_reviewer.reasons, + existing_reviewer.mandatory, + existing_reviewer.role, + existing_reviewer.rule_data, ) - - reviewers_data.append( - ( - ai_user.user_id, - ["adds system ai user for review"], - False, - PullRequestReviewers.ROLE_REVIEWER, - pull_request.reviewer_data, ) + + reviewers_data.append( + ( + ai_user.user_id, + ["adds system ai user for review"], + False, + PullRequestReviewers.ROLE_REVIEWER, + pull_request.reviewer_data, ) + ) - PullRequestModel().update_reviewers(pull_request, reviewers_data, self._rhodecode_db_user) + PullRequestModel().update_reviewers(pull_request, reviewers_data, self._rhodecode_db_user) - Session().commit() + Session().commit() - run_task(tasks.start_ai_code_review, pull_request.pull_request_id) - self._set_ai_pr_state_pending(pull_request) + run_task(tasks.start_ai_code_review, pull_request.pull_request_id) + self._set_ai_pr_state_pending(pull_request) + + msg = _("AI code review has started. The review will be ready in about 1–3 minutes.") + h.flash(msg, category="success") - msg = _("AI code review has started. The review will be ready in about 1–3 minutes.") - h.flash(msg, category="success") - else: - log.debug("AI code review canceled, only admin can initiate AI code review.") - msg = _("AI code review is not available. This feature is in BETA and can only be initiated by an admin.") - h.flash( - msg, - category="warning", - ) - return False return True @LoginRequired() diff --git a/rhodecode/apps/repository/views/repo_settings_vcs.py b/rhodecode/apps/repository/views/repo_settings_vcs.py index 25cd59bd..b6a9279c 100644 --- a/rhodecode/apps/repository/views/repo_settings_vcs.py +++ b/rhodecode/apps/repository/views/repo_settings_vcs.py @@ -71,7 +71,18 @@ class RepoSettingsVcsView(RepoAppView): c = self.load_default_context() model = VcsSettingsModel(repo=self.db_repo_name) + app_settings = c.rc_config + c.active = "vcs" + c.repo_vcs_settings = True + + c.ai_features_enabled = app_settings.get("rhodecode_ai_features_enabled", False) + c.ai_default_code_review = self._get_repo_setting( + self.db_repo, + "rhodecode_ai_default_code_review", + app_settings.get("rhodecode_ai_default_code_review", False), + ) + c.global_svn_branch_patterns = model.get_global_svn_branch_patterns() c.global_svn_tag_patterns = model.get_global_svn_tag_patterns() c.svn_branch_patterns = model.get_repo_svn_branch_patterns() diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py index 69915d53..31cb9681 100644 --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -444,6 +444,7 @@ class _BaseVcsSettingsForm(formencode.Schema): rhodecode_auto_merge_enabled = v.StringBoolean(if_missing=False) rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False) rhodecode_pr_security_scan_enabled = v.StringBoolean(if_missing=False) + rhodecode_ai_default_code_review = v.StringBoolean(if_missing=False) # hg extensions_largefiles = v.StringBoolean(if_missing=False) @@ -673,6 +674,7 @@ def PullRequestForm(localizer, repo_id): description_renderer = v.UnicodeString(strip=True, required=False) close_branch_before_merging = v.StringBoolean(if_missing=False) run_security_audit = v.StringBoolean(if_missing=False) + run_ai_codereview = v.StringBoolean(if_missing=False) return _PullRequestForm @@ -692,6 +694,7 @@ def AiSettingsForm(): class _AiSettingsForm(formencode.Schema): allow_extra_fields = True rhodecode_ai_features_enabled = v.StringBoolean(if_missing=False) + rhodecode_ai_default_code_review = v.StringBoolean(if_missing=False) rhodecode_ai_model = v.UnicodeString(strip=True, required=True) rhodecode_ai_model_version = v.UnicodeString(strip=True, required=True) rhodecode_ai_api_key = v.UnicodeString(strip=True) diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py index 59863017..37efe92a 100644 --- a/rhodecode/model/settings.py +++ b/rhodecode/model/settings.py @@ -457,6 +457,7 @@ class VcsSettingsModel: "pr_merge_enabled", "auto_merge_enabled", "pr_security_scan_enabled", + "ai_default_code_review", "hg_use_rebase_for_merging", "hg_close_branch_before_merging", "hg_merge_strategy_selector", diff --git a/rhodecode/templates/admin/settings/settings_ai.mako b/rhodecode/templates/admin/settings/settings_ai.mako index 4c68763b..38b39ae7 100644 --- a/rhodecode/templates/admin/settings/settings_ai.mako +++ b/rhodecode/templates/admin/settings/settings_ai.mako @@ -1,3 +1,9 @@ + +

${_('Ai Configuration')}

@@ -15,6 +21,16 @@
+
+
+ + Warning: can potentially spend a lot of tokens +
+
+ ${h.checkbox('rhodecode_ai_default_code_review',value=True, checked=c.ai_default_code_review==True)} +
+
+
@@ -79,6 +95,7 @@ import json const $model = $('#rhodecode_ai_model'); const $slot = $('#model_version_slot'); const $enable = $('#rhodecode_ai_features_enabled'); + const $codeReview = $('#rhodecode_ai_default_code_review'); const $form = $('#ai_features_form'); const $updateModelsBtn = $('#model_version_button_update'); @@ -181,7 +198,8 @@ import json data: { 'rhodecode_ai_model': $model.val(), 'rhodecode_ai_api_key': $apiKey.val(), - 'rhodecode_ai_features_enabled': $enable.is(':checked') + 'rhodecode_ai_features_enabled': $enable.is(':checked'), + 'rhodecode_ai_default_code_review': $codeReview.is(':checked') }, success: function (response) { $.Topic('/notifications').publish({ diff --git a/rhodecode/templates/base/vcs_settings.mako b/rhodecode/templates/base/vcs_settings.mako index 8a6d671c..d821b465 100644 --- a/rhodecode/templates/base/vcs_settings.mako +++ b/rhodecode/templates/base/vcs_settings.mako @@ -279,6 +279,34 @@ ${_('When enabled, the security scan checkbox will be pre-selected when creating pull requests. This scans the PR diff for exposed secrets and credentials.')}
%endif + + %if hasattr(c, 'repo_vcs_settings') and c.repo_vcs_settings: + + %if c.rhodecode_edition_id != 'EE': +
+ + +
+
+ ${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='sales@rhodecode.com')|n} +
+ %else: +
+ %if c.ai_features_enabled: + ${h.checkbox('rhodecode_ai_default_code_review' + suffix, 'True', checked=c.ai_default_code_review, **kwargs)} + + %else: + + ${h.checkbox('rhodecode_ai_default_code_review' + suffix, 'True', checked=c.ai_default_code_review, disabled=True, **kwargs)} + + + %endif +
+
+ ${_('When enabled, the AI code review checkbox will be pre-selected when creating pull requests. This will schedule AI code review for the PR.')} +
+ %endif + %endif
% endif diff --git a/rhodecode/templates/pullrequests/pullrequest.mako b/rhodecode/templates/pullrequests/pullrequest.mako index 1ea1b7db..c3884932 100644 --- a/rhodecode/templates/pullrequests/pullrequest.mako +++ b/rhodecode/templates/pullrequests/pullrequest.mako @@ -247,6 +247,31 @@ % endif + + % if c.ai_enabled and c.rhodecode_edition_id == 'EE': +
+ ${h.checkbox('run_ai_codereview', checked=c.ai_pr_review, value=True)} + +
+ % elif not c.ai_enabled: +
+ ${h.checkbox('run_ai_codereview', checked=False, value=True, disabled=True, **{'class': 'disabled-checkbox'})} + +
+ % elif c.rhodecode_edition_id != 'EE': +
+ ${h.checkbox('run_ai_codereview', checked=False, value=True, disabled=True, **{'class': 'disabled-checkbox'})} + +
+ % endif diff --git a/rhodecode/tests/models/settings/test_vcs_settings.py b/rhodecode/tests/models/settings/test_vcs_settings.py index d3ace4f5..5986333d 100644 --- a/rhodecode/tests/models/settings/test_vcs_settings.py +++ b/rhodecode/tests/models/settings/test_vcs_settings.py @@ -37,6 +37,7 @@ GENERAL_FORM_DATA = { "rhodecode_auto_merge_enabled": True, "rhodecode_use_outdated_comments": True, "rhodecode_pr_security_scan_enabled": True, + "rhodecode_ai_default_code_review": True, "rhodecode_hg_use_rebase_for_merging": True, "rhodecode_hg_close_branch_before_merging": True, "rhodecode_hg_merge_strategy_selector": True,