Skip to content

Commit

Permalink
feat(browser-starfish): Add project settings endpoint for images (#62025
Browse files Browse the repository at this point in the history
)

Work for #60482 

This PR adds a new endpoint to enable/disable images for the resource
module as a project settings.
Complements #62131 , this PR shall be merged and deployed first.

TODO: 
- [x] Add some more error handling
- [x] Add tests
<img width="767" alt="image"
src="https://github.com/getsentry/sentry/assets/44422760/84196e45-3311-459f-b16b-c7693a659063">
  • Loading branch information
DominikB2014 authored and trillville committed Jan 19, 2024
1 parent f7c08bb commit ae2ccdf
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 0 deletions.
75 changes: 75 additions & 0 deletions src/sentry/api/endpoints/project_performance_general_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from rest_framework import serializers, status
from rest_framework.request import Request
from rest_framework.response import Response

from sentry import features
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.project import ProjectEndpoint, ProjectSettingPermission
from sentry.api.permissions import SuperuserPermission
from sentry.models.project import Project
from sentry.projectoptions.defaults import DEFAULT_PROJECT_PERFORMANCE_GENERAL_SETTINGS

SETTINGS_PROJECT_OPTION_KEY = "sentry:performance_general_settings"


class ProjectPerformanceGeneralSettingsSerializer(serializers.Serializer):
enable_images = serializers.BooleanField(required=False)


class ProjectOwnerOrSuperUserPermissions(ProjectSettingPermission):
def has_object_permission(self, request: Request, view, project):
return super().has_object_permission(
request, view, project
) or SuperuserPermission().has_permission(request, view)


@region_silo_endpoint
class ProjectPerformanceGeneralSettingsEndpoint(ProjectEndpoint):
owner = ApiOwner.PERFORMANCE
publish_status = {
"DELETE": ApiPublishStatus.PRIVATE,
"GET": ApiPublishStatus.PRIVATE,
"POST": ApiPublishStatus.PRIVATE,
}
permission_classes = (ProjectOwnerOrSuperUserPermissions,)

def get(self, request: Request, project) -> Response:
if not self.has_feature(project, request):
return self.respond(status=status.HTTP_404_NOT_FOUND)

if not project:
return Response(status=status.HTTP_404_NOT_FOUND)

project_option_settings = self.get_current_settings(project)
return Response(project_option_settings)

def post(self, request: Request, project: Project) -> Response:
if not self.has_feature(project, request):
return self.respond(status=status.HTTP_404_NOT_FOUND)

if not project:
return Response(status=status.HTTP_404_NOT_FOUND)

serializer = ProjectPerformanceGeneralSettingsSerializer(data=request.data)

if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

self.update_settings(project, request.data)
return Response(status=status.HTTP_204_NO_CONTENT)

def has_feature(self, project, request) -> bool:
return features.has(
"organizations:performance-view", project.organization, actor=request.user
)

def get_current_settings(self, project: Project):
return project.get_option(
SETTINGS_PROJECT_OPTION_KEY, DEFAULT_PROJECT_PERFORMANCE_GENERAL_SETTINGS
)

def update_settings(self, project: Project, new_settings: dict):
current_settings = self.get_current_settings(project)
project.update_option(SETTINGS_PROJECT_OPTION_KEY, {**current_settings, **new_settings})
8 changes: 8 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,9 @@
from .endpoints.project_keys import ProjectKeysEndpoint
from .endpoints.project_member_index import ProjectMemberIndexEndpoint
from .endpoints.project_ownership import ProjectOwnershipEndpoint
from .endpoints.project_performance_general_settings import (
ProjectPerformanceGeneralSettingsEndpoint,
)
from .endpoints.project_performance_issue_settings import ProjectPerformanceIssueSettingsEndpoint
from .endpoints.project_platforms import ProjectPlatformsEndpoint
from .endpoints.project_plugin_details import ProjectPluginDetailsEndpoint
Expand Down Expand Up @@ -2484,6 +2487,11 @@
ProjectPerformanceIssueSettingsEndpoint.as_view(),
name="sentry-api-0-project-performance-issue-settings",
),
re_path(
r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/performance/configure/$",
ProjectPerformanceGeneralSettingsEndpoint.as_view(),
name="sentry-api-0-project-performance-general-settings",
),
# Load plugin project urls
re_path(
r"^(?P<organization_slug>[^\/]+)/(?P<project_slug>[^\/]+)/plugins/$",
Expand Down
10 changes: 10 additions & 0 deletions src/sentry/projectoptions/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,22 @@
"transaction_duration_regression_detection_enabled": True,
}

DEFAULT_PROJECT_PERFORMANCE_GENERAL_SETTINGS = {
"enable_images": False,
}

# A dict containing all the specific detection thresholds and rates.
register(
key="sentry:performance_issue_settings",
default=DEFAULT_PROJECT_PERFORMANCE_DETECTION_SETTINGS,
)

register(
key="sentry:performance_general_settings",
default=DEFAULT_PROJECT_PERFORMANCE_GENERAL_SETTINGS,
)


# Replacement rules for transaction names discovered by the transaction clusterer.
# Contains a mapping from rule to last seen timestamp,
# for example `{"/organizations/*/**": 1334318402}`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from django.urls import reverse
from rest_framework.exceptions import ErrorDetail

from sentry.testutils.cases import APITestCase
from sentry.testutils.silo import region_silo_test

PERFORMANCE_SETTINGS_FEATURES = {
"organizations:performance-view": True,
}


@region_silo_test
class ProjectPerformanceGeneralSettingsTest(APITestCase):
endpoint = "sentry-api-0-project-performance-general-settings"

def setUp(self) -> None:
super().setUp()
self.login_as(user=self.user, superuser=True)
self.project = self.create_project()

self.url = reverse(
self.endpoint,
kwargs={
"organization_slug": self.project.organization.slug,
"project_slug": self.project.slug,
},
)

def test_get_project_general_settings_defaults(self):
with self.feature(PERFORMANCE_SETTINGS_FEATURES):
response = self.client.get(self.url, format="json")

assert response.status_code == 200, response.content

assert response.data["enable_images"] is False

def test_get_returns_error_without_feature_enabled(self):
with self.feature({}):
response = self.client.get(self.url, format="json")
assert response.status_code == 404

def test_updates_to_new_value(self):
with self.feature(PERFORMANCE_SETTINGS_FEATURES):
response = self.client.post(
self.url,
data={
"enable_images": True,
},
)
response = self.client.get(self.url, format="json")
assert response.data["enable_images"] is True

response = self.client.post(
self.url,
data={
"enable_images": False,
},
)
response = self.client.get(self.url, format="json")
assert response.data["enable_images"] is False

def test_update_project_setting_check_validation(self):
with self.feature(PERFORMANCE_SETTINGS_FEATURES):
response = self.client.post(
self.url,
data={
"enable_images": -1,
},
)

assert response.status_code == 400, response.content
assert response.data == {
"enable_images": [ErrorDetail(string="Must be a valid boolean.", code="invalid")]
}

0 comments on commit ae2ccdf

Please sign in to comment.