-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
ref: Create a prevent config model, no longer use organization options #101831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 13 commits
2c472c4
d00ee36
c79771e
95f1d6b
03dc73b
4c72266
a266d31
1a463a9
b872e4a
c1d2f28
03560b1
160fbd6
42d0454
d9ce127
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| from copy import deepcopy | ||
| from typing import Any | ||
|
|
||
| from jsonschema import validate | ||
| from rest_framework.request import Request | ||
| from rest_framework.response import Response | ||
|
|
||
| from sentry import audit_log | ||
| from sentry.api.api_owners import ApiOwner | ||
| from sentry.api.api_publish_status import ApiPublishStatus | ||
| from sentry.api.base import region_silo_endpoint | ||
| from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission | ||
| from sentry.models.organization import Organization | ||
| from sentry.prevent.models import PreventAIConfiguration | ||
| from sentry.prevent.types.config import ORG_CONFIG_SCHEMA, PREVENT_AI_CONFIG_GITHUB_DEFAULT | ||
|
|
||
|
|
||
| class PreventAIConfigPermission(OrganizationPermission): | ||
| scope_map = { | ||
| "GET": ["org:read", "org:write", "org:admin"], | ||
| # allow any organization member to update the PR review config | ||
| "PUT": ["org:read", "org:write", "org:admin"], | ||
| } | ||
|
|
||
|
|
||
| @region_silo_endpoint | ||
| class OrganizationPreventGitHubConfigEndpoint(OrganizationEndpoint): | ||
| """ | ||
| Get and set the GitHub PR review config for a Sentry organization | ||
| """ | ||
|
|
||
| owner = ApiOwner.CODECOV | ||
| publish_status = { | ||
| "GET": ApiPublishStatus.EXPERIMENTAL, | ||
| "PUT": ApiPublishStatus.EXPERIMENTAL, | ||
| } | ||
| permission_classes = (PreventAIConfigPermission,) | ||
|
|
||
| def get( | ||
| self, request: Request, organization: Organization, git_organization_id: str | ||
| ) -> Response: | ||
| """ | ||
| Get the Prevent AI GitHub configuration for a specific git organization. | ||
| """ | ||
| response_data: dict[str, Any] = deepcopy(PREVENT_AI_CONFIG_GITHUB_DEFAULT) | ||
|
|
||
| config = PreventAIConfiguration.objects.filter( | ||
| organization_id=organization.id, | ||
| provider="github", | ||
| git_organization_id=git_organization_id, | ||
| ).first() | ||
|
|
||
| if config: | ||
| response_data["github_organization"][git_organization_id] = config.data | ||
|
|
||
| return Response(response_data, status=200) | ||
|
|
||
| def put( | ||
| self, request: Request, organization: Organization, git_organization_id: str | ||
| ) -> Response: | ||
| """ | ||
| Update the Prevent AI GitHub configuration for an organization. | ||
| """ | ||
| try: | ||
| validate(request.data, ORG_CONFIG_SCHEMA) | ||
| except Exception: | ||
| return Response({"detail": "Invalid config"}, status=400) | ||
|
|
||
| PreventAIConfiguration.objects.update_or_create( | ||
| organization_id=organization.id, | ||
| provider="github", | ||
| git_organization_id=git_organization_id, | ||
| defaults={"data": request.data}, | ||
| ) | ||
|
|
||
| self.create_audit_entry( | ||
| request=request, | ||
| organization=organization, | ||
| target_object=organization.id, | ||
| event=audit_log.get_event_id("PREVENT_CONFIG_EDIT"), | ||
| data={ | ||
| "git_organization": git_organization_id, | ||
| "provider": "github", | ||
| }, | ||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Audit Log Template Mismatch Causes Incorrect RenderingThe Additional Locations (1) |
||
|
|
||
| response_data: dict[str, Any] = deepcopy(PREVENT_AI_CONFIG_GITHUB_DEFAULT) | ||
| response_data["github_organization"][git_organization_id] = request.data | ||
|
|
||
| return Response(response_data, status=200) | ||
|
Comment on lines
+87
to
+90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: A key in the default config object was renamed from 🔍 Detailed AnalysisThe default configuration object, 💡 Suggested FixUpdate all frontend references from 🤖 Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Frontend code is not live yet, it will be updated next. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Generated by Django 5.2.1 on 2025-10-22 21:03 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
| import sentry.db.models.fields.bounded | ||
| from sentry.new_migrations.migrations import CheckedMigration | ||
|
|
||
|
|
||
| class Migration(CheckedMigration): | ||
| # This flag is used to mark that a migration shouldn't be automatically run in production. | ||
| # This should only be used for operations where it's safe to run the migration after your | ||
| # code has deployed. So this should not be used for most operations that alter the schema | ||
| # of a table. | ||
| # Here are some things that make sense to mark as post deployment: | ||
| # - Large data migrations. Typically we want these to be run manually so that they can be | ||
| # monitored and not block the deploy for a long period of time while they run. | ||
| # - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to | ||
| # run this outside deployments so that we don't block them. Note that while adding an index | ||
| # is a schema change, it's completely safe to run the operation after the code has deployed. | ||
| # Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment | ||
|
|
||
| is_post_deployment = False | ||
|
|
||
| initial = True | ||
|
|
||
| dependencies = [] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="PreventAIConfiguration", | ||
| fields=[ | ||
| ( | ||
| "id", | ||
| sentry.db.models.fields.bounded.BoundedBigAutoField( | ||
| primary_key=True, serialize=False | ||
| ), | ||
| ), | ||
| ("date_updated", models.DateTimeField(auto_now=True)), | ||
| ("date_added", models.DateTimeField(auto_now_add=True)), | ||
| ( | ||
| "organization_id", | ||
| sentry.db.models.fields.bounded.BoundedBigIntegerField(db_index=True), | ||
| ), | ||
| ("provider", models.CharField(max_length=64)), | ||
| ("git_organization_id", models.CharField(max_length=255)), | ||
| ("data", models.JSONField(default=dict)), | ||
| ], | ||
| options={ | ||
| "db_table": "sentry_preventaiconfiguration", | ||
| "unique_together": {("organization_id", "provider", "git_organization_id")}, | ||
| }, | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from django.db import models | ||
|
|
||
| from sentry.backup.scopes import RelocationScope | ||
| from sentry.db.models.base import DefaultFieldsModel, region_silo_model | ||
| from sentry.db.models.fields.bounded import BoundedBigIntegerField | ||
|
|
||
|
|
||
| @region_silo_model | ||
| class PreventAIConfiguration(DefaultFieldsModel): | ||
| """ | ||
| Configuration for Prevent AI features for git organizations per Sentry organization. | ||
| This model stores configurations for Prevent AI functionality, | ||
| allowing different settings for different git organizations and repos. | ||
| """ | ||
|
|
||
| __relocation_scope__ = RelocationScope.Excluded | ||
|
|
||
| organization_id = BoundedBigIntegerField(db_index=True) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use a proper foreign key here
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On a similar note, should we consider which silo this data should live in? I could see a case being made for either region or control, depending on where this is used the most. |
||
| provider = models.CharField(max_length=64) | ||
| git_organization_id = models.CharField(max_length=255) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we link to the integration instead? |
||
| data = models.JSONField(default=dict) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use more explicit fields here, instead of a json blob? What kind of data are we putting here? |
||
|
|
||
| class Meta: | ||
| app_label = "prevent" | ||
| db_table = "sentry_preventaiconfiguration" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is in your own app, you can feel free to call this |
||
| unique_together = (("organization_id", "provider", "git_organization_id"),) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: The RPC endpoint reads config from the old
organization_options, while the new UI endpoint writes to the newPreventAIConfigurationmodel, with no data migration.(Severity: Critical 0.85 | Confidence: 1.00)
🔍 Detailed Analysis
The
PreventPrReviewResolvedConfigsEndpointinoverwatch_rpc.pyreads configuration from the oldorganization.get_optionstorage. However, the newOrganizationPreventGitHubConfigEndpointwrites settings to the newPreventAIConfigurationmodel. There is no data migration to move existing settings from the old storage to the new model, nor is there any fallback logic for the read endpoint to check the new model. As a result, any configuration changes made through the new UI will be saved to the new model but will be completely ignored by the Overwatch service, which will continue to use old or default settings from the previous storage location.💡 Suggested Fix
Update the
PreventPrReviewResolvedConfigsEndpointto read from the newPreventAIConfigurationmodel. Additionally, create a data migration to transfer existing configurations fromorganization_optionsto thePreventAIConfigurationtable to ensure continuity for existing users.🤖 Prompt for AI Agent
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feature is not live, don't need to make migration.