Просмотр исходного кода

feat(apis): Update documentation for project PUT (#57407)

"Update Project" endpoint is not using our standard documentation, for
example use of options is deprecated and filters are not moving to
filters endpoint. Updating to the standard one as well as documenting
some of the features that missed documentation.

Before:

![Screenshot 2023-10-03 at 2 36 37
PM](https://github.com/getsentry/sentry/assets/132939361/d704a688-0c69-46e2-bf1e-a16c5b1d331c)

After:

![Screenshot 2023-10-10 at 1 14 31
PM](https://github.com/getsentry/sentry/assets/132939361/288b9831-402c-48ce-82ec-7d79a37716eb)


Removing from update filter api:

![Screenshot 2023-10-10 at 11 30 54
AM](https://github.com/getsentry/sentry/assets/132939361/7c4e5a04-3f6b-4c57-956b-fb566138f6c3)
Athena Moghaddam 1 год назад
Родитель
Сommit
2eb054549d

+ 60 - 61
src/sentry/api/endpoints/project_details.py

@@ -1,12 +1,11 @@
 import math
 import time
 from datetime import timedelta
-from itertools import chain
 from uuid import uuid4
 
 from django.db import IntegrityError, router, transaction
 from django.utils import timezone
-from drf_spectacular.utils import extend_schema, inline_serializer
+from drf_spectacular.utils import extend_schema, extend_schema_serializer
 from rest_framework import serializers, status
 from rest_framework.request import Request
 from rest_framework.response import Response
@@ -66,24 +65,6 @@ from sentry.utils import json
 #: Limit determined experimentally here: https://github.com/getsentry/relay/blob/3105d8544daca3a102c74cefcd77db980306de71/relay-general/src/pii/convert.rs#L289
 MAX_SENSITIVE_FIELD_CHARS = 4000
 
-_options_description = """
-Configure various project filters:
-- `Hydration Errors` - Filter out react hydration errors that are often unactionable
-- `IP Addresses` - Filter events from these IP addresses separated with newlines.
-- `Releases` - Filter events from these releases separated with newlines. Allows [glob pattern matching](https://docs.sentry.io/product/data-management-settings/filtering/#glob-matching).
-- `Error Message` - Filter events by error messages separated with newlines. Allows [glob pattern matching](https://docs.sentry.io/product/data-management-settings/filtering/#glob-matching).
-```json
-{
-    options: {
-        filters:react-hydration-errors: true,
-        filters:blacklisted_ips: "127.0.0.1\\n192.168. 0.1"
-        filters:releases: "[!3]\\n4"
-        filters:error_messages: "TypeError*\\n*ConnectionError*"
-    }
-}
-```
-"""
-
 
 def clean_newline_inputs(value, case_insensitive=True):
     result = []
@@ -109,22 +90,63 @@ class DynamicSamplingBiasSerializer(serializers.Serializer):
 
 
 class ProjectMemberSerializer(serializers.Serializer):
-    isBookmarked = serializers.BooleanField()
-    isSubscribed = serializers.BooleanField()
+    isBookmarked = serializers.BooleanField(
+        help_text="Enables starring the project within the projects tab. Can be updated with **`project:read`** permission.",
+        required=False,
+    )
+    isSubscribed = serializers.BooleanField(
+        help_text="Subscribes the member for notifications related to the project. Can be updated with **`project:read`** permission.",
+        required=False,
+    )
 
 
+@extend_schema_serializer(exclude_fields=["options"])
 class ProjectAdminSerializer(ProjectMemberSerializer, PreventNumericSlugMixin):
-    name = serializers.CharField(max_length=200)
+    name = serializers.CharField(
+        help_text="The name for the project",
+        max_length=200,
+        required=False,
+    )
     slug = serializers.RegexField(
         DEFAULT_SLUG_PATTERN,
         max_length=50,
         error_messages={"invalid": DEFAULT_SLUG_ERROR_MESSAGE},
+        help_text="Uniquely identifies a project and is used for the interface.",
+        required=False,
+    )
+    platform = serializers.CharField(
+        help_text="The platform for the project",
+        required=False,
+        allow_null=True,
+        allow_blank=True,
+    )
+
+    subjectPrefix = serializers.CharField(
+        help_text="Custom prefix for emails from this project.",
+        max_length=200,
+        allow_blank=True,
+        required=False,
     )
+    subjectTemplate = serializers.CharField(
+        help_text="""The email subject to use (excluding the prefix) for individual alerts. Here are the list of variables you can use:
+- `$title`
+- `$shortID`
+- `$projectID`
+- `$orgID`
+- `${tag:key}` - such as `${tag:environment}` or `${tag:release}`.""",
+        max_length=200,
+        required=False,
+    )
+    resolveAge = EmptyIntegerField(
+        required=False,
+        allow_null=True,
+        help_text="Automatically resolve an issue if it hasn't been seen for this many hours. Set to `0` to disable auto-resolve.",
+    )
+
+    # TODO: Add help_text to all the fields for public documentation
     team = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50)
     digestsMinDelay = serializers.IntegerField(min_value=60, max_value=3600)
     digestsMaxDelay = serializers.IntegerField(min_value=60, max_value=3600)
-    subjectPrefix = serializers.CharField(max_length=200, allow_blank=True)
-    subjectTemplate = serializers.CharField(max_length=200)
     securityToken = serializers.RegexField(
         r"^[-a-zA-Z0-9+/=\s]+$", max_length=255, allow_blank=True
     )
@@ -155,8 +177,7 @@ class ProjectAdminSerializer(ProjectMemberSerializer, PreventNumericSlugMixin):
     groupingAutoUpdate = serializers.BooleanField(required=False)
     scrapeJavaScript = serializers.BooleanField(required=False)
     allowedDomains = EmptyListField(child=OriginField(allow_blank=True), required=False)
-    resolveAge = EmptyIntegerField(required=False, allow_null=True)
-    platform = serializers.CharField(required=False, allow_null=True, allow_blank=True)
+
     copy_from_project = serializers.IntegerField(required=False)
     dynamicSamplingBiases = DynamicSamplingBiasSerializer(required=False, many=True)
     performanceIssueCreationRate = serializers.FloatField(required=False, min_value=0, max_value=1)
@@ -165,6 +186,13 @@ class ProjectAdminSerializer(ProjectMemberSerializer, PreventNumericSlugMixin):
     recapServerUrl = serializers.URLField(required=False, allow_blank=True, allow_null=True)
     recapServerToken = serializers.CharField(required=False, allow_blank=True, allow_null=True)
 
+    # DO NOT ADD MORE TO OPTIONS
+    # Each param should be a field in the serializer like above.
+    # Keeping options here for backward compatibility but removing it from documentation.
+    options = serializers.DictField(
+        required=False,
+    )
+
     def validate(self, data):
         max_delay = (
             data["digestsMaxDelay"]
@@ -473,34 +501,7 @@ class ProjectDetailsEndpoint(ProjectEndpoint):
             GlobalParams.ORG_SLUG,
             GlobalParams.PROJECT_SLUG,
         ],
-        request=inline_serializer(
-            "UpdateProject",
-            fields={
-                "slug": serializers.RegexField(
-                    DEFAULT_SLUG_PATTERN,
-                    help_text="Uniquely identifies a project and is used for the interface.",
-                    required=False,
-                    max_length=50,
-                    error_messages={"invalid": DEFAULT_SLUG_ERROR_MESSAGE},
-                ),
-                "name": serializers.CharField(
-                    help_text="The name for the project.",
-                    required=False,
-                    max_length=200,
-                ),
-                "platform": serializers.CharField(
-                    help_text="The platform for the project.",
-                    required=False,
-                    allow_null=True,
-                    allow_blank=True,
-                ),
-                "isBookmarked": serializers.BooleanField(
-                    help_text="Enables starring the project within the projects tab.",
-                    required=False,
-                ),
-                "options": serializers.DictField(help_text=_options_description, required=False),
-            },
-        ),
+        request=ProjectAdminSerializer,
         responses={
             200: DetailedProjectSerializer,
             403: RESPONSE_FORBIDDEN,
@@ -513,7 +514,7 @@ class ProjectDetailsEndpoint(ProjectEndpoint):
         Update various attributes and configurable settings for the given project.
 
         Note that solely having the **`project:read`** scope restricts updatable settings to
-        `isBookmarked` only.
+        `isBookmarked` and `isSubscribed`.
         """
 
         old_data = serialize(project, request.user, DetailedProjectSerializer())
@@ -546,14 +547,12 @@ class ProjectDetailsEndpoint(ProjectEndpoint):
             return Response(serializer.errors, status=400)
 
         if not has_elevated_scopes:
-            # options isn't part of the serializer, but should not be editable by members
-            for key in chain(ProjectAdminSerializer().fields.keys(), ["options"]):
+            for key in ProjectAdminSerializer().fields.keys():
                 if request.data.get(key) and not result.get(key):
                     return Response(
                         {"detail": "You do not have permission to perform this action."},
                         status=403,
                     )
-
         changed = False
         changed_proj_settings = {}
 
@@ -726,9 +725,9 @@ class ProjectDetailsEndpoint(ProjectEndpoint):
                 changed_proj_settings["sentry:dynamic_sampling_biases"] = result[
                     "dynamicSamplingBiases"
                 ]
-        # TODO(dcramer): rewrite options to use standard API config
+
         if has_elevated_scopes:
-            options = request.data.get("options", {})
+            options = result.get("options", {})
             if "sentry:origins" in options:
                 project.update_option(
                     "sentry:origins", clean_newline_inputs(options["sentry:origins"])

+ 0 - 4
src/sentry/api/endpoints/project_filter_details.py

@@ -46,10 +46,6 @@ class ProjectFilterDetailsEndpoint(ProjectEndpoint):
     def put(self, request: Request, project, filter_id) -> Response:
         """
         Update various inbound data filters for a project.
-
-        Note that the hydration filter and custom inbound
-        filters must be updated using the [Update a
-        Project](https://docs.sentry.io/api/projects/update-a-project/) endpoint.
         """
         for flt in inbound_filters.get_all_filter_specs():
             if flt.id == filter_id: