Browse Source

ref: Make recap server feature org-wide and move config to different page (#53596)

Instead of using the main project config page, options have been moved
to "Security & Privacy" page for now, so its less visible for
not-interested people.

Org-wide feature allows us to configure early adopters directly in the
production repo, instead of relying on flagr.

Replaces https://github.com/getsentry/sentry/pull/53259
Kamil Ogórek 1 year ago
parent
commit
c1eec3a6d3

+ 8 - 8
src/sentry/api/endpoints/project_details.py

@@ -335,10 +335,10 @@ class ProjectAdminSerializer(ProjectMemberSerializer):
     def validate_recapServerUrl(self, value):
         from sentry import features
 
-        project = self.context["project"]
-
-        # Adding recapServerUrl is only allowed if recap server polling is enabled.
-        has_recap_server_enabled = features.has("projects:recap-server", project)
+        # Adding recapServerUrl is only allowed if recap server polling is enabled for given organization.
+        has_recap_server_enabled = features.has(
+            "organizations:recap-server", self.context["project"].organization
+        )
 
         if not has_recap_server_enabled:
             raise serializers.ValidationError("Project is not allowed to set recap server url")
@@ -348,10 +348,10 @@ class ProjectAdminSerializer(ProjectMemberSerializer):
     def validate_recapServerToken(self, value):
         from sentry import features
 
-        project = self.context["project"]
-
-        # Adding recapServerToken is only allowed if recap server polling is enabled.
-        has_recap_server_enabled = features.has("projects:recap-server", project)
+        # Adding recapServerUrl is only allowed if recap server polling is enabled for given organization.
+        has_recap_server_enabled = features.has(
+            "organizations:recap-server", self.context["project"].organization
+        )
 
         if not has_recap_server_enabled:
             raise serializers.ValidationError("Project is not allowed to set recap server token")

+ 2 - 2
src/sentry/conf/server.py

@@ -1669,6 +1669,8 @@ SENTRY_FEATURES = {
     "organizations:pr-comment-bot": False,
     # Enables slack channel lookup via schedule message
     "organizations:slack-use-new-lookup": False,
+    # Enable functionality for recap server polling.
+    "organizations:recap-server": False,
     # Adds additional filters and a new section to issue alert rules.
     "projects:alert-filters": True,
     # Enable functionality to specify custom inbound filters on events.
@@ -1688,8 +1690,6 @@ SENTRY_FEATURES = {
     "projects:race-free-group-creation": True,
     # Enable functionality for rate-limiting events on projects.
     "projects:rate-limits": True,
-    # Enable functionality for recap server polling.
-    "projects:recap-server": False,
     # Enable functionality to trigger service hooks upon event ingestion.
     "projects:servicehooks": False,
     # Enable suspect resolutions feature

+ 1 - 1
src/sentry/features/__init__.py

@@ -260,6 +260,7 @@ default_manager.add("organizations:pr-comment-bot", OrganizationFeature, Feature
 default_manager.add("organizations:ds-org-recalibration", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("organizations:slack-use-new-lookup", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
 default_manager.add("organizations:sourcemaps-bundle-flat-file-indexing", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
+default_manager.add("organizations:recap-server", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
 
 # Project scoped features
 default_manager.add("projects:alert-filters", ProjectFeature, FeatureHandlerStrategy.INTERNAL)
@@ -269,7 +270,6 @@ default_manager.add("projects:discard-groups", ProjectFeature, FeatureHandlerStr
 default_manager.add("projects:minidump", ProjectFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("projects:race-free-group-creation", ProjectFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("projects:rate-limits", ProjectFeature, FeatureHandlerStrategy.INTERNAL)
-default_manager.add("projects:recap-server", ProjectFeature, FeatureHandlerStrategy.REMOTE)
 default_manager.add("projects:servicehooks", ProjectFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("projects:similarity-indexing", ProjectFeature, FeatureHandlerStrategy.INTERNAL)
 default_manager.add("projects:similarity-view", ProjectFeature, FeatureHandlerStrategy.INTERNAL)

+ 3 - 3
src/sentry/tasks/recap_servers.py

@@ -58,10 +58,10 @@ def poll_project_recap_server(project_id: int, **kwargs) -> None:
         logger.warning("Polled project do not exist", extra={"project_id": project_id})
         return
 
-    if not features.has("projects:recap-server", project):
+    if not features.has("organizations:recap-server", project.organization):
         logger.info(
-            "Recap server polling feature is not enabled for a given project",
-            extra={"project_id": project_id},
+            "Recap server polling feature is not enabled for a given organization",
+            extra={"organization": project.organization},
         )
         return
 

+ 0 - 17
static/app/data/forms/projectGeneralSettings.tsx

@@ -90,23 +90,6 @@ export const fields: Record<string, Field> = {
     }),
   },
 
-  // TODO(recap): Move this to a separate page or debug files one, not general settings
-  recapServerUrl: {
-    name: 'recapServerUrl',
-    type: 'string',
-    placeholder: t('URL'),
-    label: t('Recap Server URL'),
-    help: t('URL to the Recap Server events should be polled from'),
-  },
-
-  recapServerToken: {
-    name: 'recapServerToken',
-    type: 'string',
-    placeholder: t('Token'),
-    label: t('Recap Server Token'),
-    help: t('Auth Token to the configured Recap Server'),
-  },
-
   subjectPrefix: {
     name: 'subjectPrefix',
     type: 'string',

+ 16 - 0
static/app/data/forms/projectSecurityAndPrivacyGroups.tsx

@@ -54,6 +54,22 @@ const formGroups: JsonFormObject[] = [
             formatStoreCrashReports(value, organization.storeCrashReports),
           ]),
       },
+      {
+        name: 'recapServerUrl',
+        type: 'string',
+        placeholder: t('URL'),
+        label: t('Recap Server URL'),
+        help: t('URL to the Recap Server events should be polled from'),
+        visible: ({features}) => features.has('recap-server'),
+      },
+      {
+        name: 'recapServerToken',
+        type: 'string',
+        placeholder: t('Token'),
+        label: t('Recap Server Token'),
+        help: t('Auth Token to the configured Recap Server'),
+        visible: ({features}) => features.has('recap-server'),
+      },
     ],
   },
   {

+ 1 - 15
static/app/views/settings/projectGeneralSettings/index.tsx

@@ -299,8 +299,6 @@ class ProjectGeneralSettings extends DeprecatedAsyncView<Props, State> {
       },
     };
 
-    const hasRecapServerFeature = project.features.includes('recap-server');
-
     return (
       <div>
         <SettingsPageHeader title={t('Project Settings')} />
@@ -310,19 +308,7 @@ class ProjectGeneralSettings extends DeprecatedAsyncView<Props, State> {
           <JsonForm
             {...jsonFormProps}
             title={t('Project Details')}
-            // TODO(recap): Move this to a separate page or debug files one, not general settings
-            fields={[
-              fields.name,
-              fields.platform,
-              {
-                ...fields.recapServerUrl,
-                visible: hasRecapServerFeature,
-              },
-              {
-                ...fields.recapServerToken,
-                visible: hasRecapServerFeature,
-              },
-            ]}
+            fields={[fields.name, fields.platform]}
           />
 
           <JsonForm

+ 4 - 4
tests/sentry/api/endpoints/test_project_details.py

@@ -1048,7 +1048,7 @@ class ProjectUpdateTest(APITestCase):
     @mock.patch("sentry.tasks.recap_servers.poll_project_recap_server.delay")
     def test_recap_server(self, poll_project_recap_server):
         project = Project.objects.get(id=self.project.id)
-        with Feature({"projects:recap-server": True}):
+        with Feature({"organizations:recap-server": True}):
             resp = self.get_response(
                 self.org_slug, self.proj_slug, recapServerUrl="http://example.com"
             )
@@ -1066,7 +1066,7 @@ class ProjectUpdateTest(APITestCase):
     @mock.patch("sentry.tasks.recap_servers.poll_project_recap_server.delay")
     def test_recap_server_no_feature(self, poll_project_recap_server):
         project = Project.objects.get(id=self.project.id)
-        with Feature({"projects:recap-server": False}):
+        with Feature({"organizations:recap-server": False}):
             resp = self.get_response(
                 self.org_slug, self.proj_slug, recapServerUrl="http://example.com"
             )
@@ -1084,7 +1084,7 @@ class ProjectUpdateTest(APITestCase):
         project = Project.objects.get(id=self.project.id)
         project.update_option("sentry:recap_server_url", "http://example.com")
         project.update_option("sentry:recap_server_token", "wat")
-        with Feature({"projects:recap-server": True}):
+        with Feature({"organizations:recap-server": True}):
             resp = self.get_response(
                 self.org_slug, self.proj_slug, recapServerUrl="http://example.com"
             )
@@ -1104,7 +1104,7 @@ class ProjectUpdateTest(APITestCase):
         project = Project.objects.get(id=self.project.id)
         project.update_option("sentry:recap_server_url", "http://example.com")
         project.update_option("sentry:recap_server_token", "wat")
-        with Feature({"projects:recap-server": True}):
+        with Feature({"organizations:recap-server": True}):
             resp = self.get_response(self.org_slug, self.proj_slug, recapServerUrl="")
 
             assert resp.status_code == 200

+ 2 - 2
tests/sentry/tasks/test_recap_servers.py

@@ -96,7 +96,7 @@ class PollRecapServersTest(TestCase):
 class PollProjectRecapServerTest(TestCase):
     @pytest.fixture(autouse=True)
     def initialize(self):
-        with Feature({"projects:recap-server": True}):
+        with Feature({"organizations:recap-server": True}):
             yield  # Run test case
 
     def setUp(self):
@@ -115,7 +115,7 @@ class PollProjectRecapServerTest(TestCase):
         poll_project_recap_server(self.project.id)  # should not error
 
     def test_poll_project_recap_server_disabled_feature(self):
-        with Feature({"projects:recap-server": False}):
+        with Feature({"organizations:recap-server": False}):
             self.project.update_option(RECAP_SERVER_URL_OPTION, "http://example.com")
             poll_project_recap_server(self.project.id)  # should not error