From 9002e0098682766c1f745f08aa695b43d3798ebb Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Mon, 24 Jun 2024 19:13:24 -0400 Subject: [PATCH 01/10] chore(apidocs): Rewrite org details docs --- api-docs/openapi.json | 3 - api-docs/paths/organizations/details.json | 367 ------------------ .../api/endpoints/organization_details.py | 233 +++++++++-- .../api/serializers/models/organization.py | 18 +- src/sentry/api/serializers/types.py | 3 + .../apidocs/examples/organization_examples.py | 55 ++- src/sentry/apidocs/parameters.py | 9 + 7 files changed, 281 insertions(+), 407 deletions(-) delete mode 100644 api-docs/paths/organizations/details.json diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 775d31d81d4032..bb50e3c6837bba 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -93,9 +93,6 @@ "/api/0/organizations/{organization_id_or_slug}/eventids/{event_id}/": { "$ref": "paths/organizations/event-id-lookup.json" }, - "/api/0/organizations/{organization_id_or_slug}/": { - "$ref": "paths/organizations/details.json" - }, "/api/0/organizations/{organization_id_or_slug}/repos/": { "$ref": "paths/organizations/repos.json" }, diff --git a/api-docs/paths/organizations/details.json b/api-docs/paths/organizations/details.json deleted file mode 100644 index 2bd77703c4cde8..00000000000000 --- a/api-docs/paths/organizations/details.json +++ /dev/null @@ -1,367 +0,0 @@ -{ - "get": { - "tags": ["Organizations"], - "description": "Return details on an individual organization including various details such as membership access, features, and teams.", - "operationId": "Retrieve an Organization", - "parameters": [ - { - "name": "organization_id_or_slug", - "in": "path", - "description": "The id or slug of the organization to look up.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "../../components/schemas/organization-details.json#/OrganizationDetails" - }, - "example": { - "access": [], - "allowSharedIssues": true, - "availableRoles": [ - { - "id": "member", - "name": "Member" - }, - { - "id": "admin", - "name": "Admin" - }, - { - "id": "manager", - "name": "Manager" - }, - { - "id": "owner", - "name": "Owner" - } - ], - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null - }, - "dataScrubber": false, - "dataScrubberDefaults": false, - "dateCreated": "2018-11-06T21:19:55.101Z", - "defaultRole": "member", - "enhancedPrivacy": false, - "experiments": {}, - "features": [ - "new-teams", - "shared-issues", - "new-issue-ui", - "repos", - "open-membership", - "invite-members", - "sso-saml2", - "sso-basic", - "suggested-commits" - ], - "id": "2", - "isDefault": false, - "isEarlyAdopter": false, - "name": "The Interstellar Jurisdiction", - "onboardingTasks": [ - { - "data": {}, - "dateCompleted": "2018-11-06T21:20:08.089Z", - "status": "complete", - "task": 1, - "user": "" - } - ], - "openMembership": true, - "pendingAccessRequests": 0, - "projects": [ - { - "dateCreated": "2018-11-06T21:19:58.536Z", - "firstEvent": null, - "hasAccess": true, - "id": "3", - "isBookmarked": false, - "isMember": true, - "latestDeploys": null, - "name": "Prime Mover", - "platform": null, - "platforms": [], - "slug": "prime-mover", - "team": { - "id": "2", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - }, - "teams": [ - { - "id": "2", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - } - ] - }, - { - "dateCreated": "2018-11-06T21:19:55.121Z", - "firstEvent": null, - "hasAccess": true, - "id": "2", - "isBookmarked": false, - "isMember": true, - "latestDeploys": null, - "name": "Pump Station", - "platform": null, - "platforms": [], - "slug": "pump-station", - "team": { - "id": "2", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - }, - "teams": [ - { - "id": "2", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - } - ] - }, - { - "dateCreated": "2018-11-06T21:20:08.064Z", - "firstEvent": null, - "hasAccess": true, - "id": "4", - "isBookmarked": false, - "isMember": true, - "latestDeploys": null, - "name": "The Spoiled Yoghurt", - "platform": null, - "platforms": [], - "slug": "the-spoiled-yoghurt", - "team": { - "id": "2", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - }, - "teams": [ - { - "id": "2", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - } - ] - } - ], - "quota": { - "accountLimit": 0, - "maxRate": 0, - "maxRateInterval": 60, - "projectLimit": 100 - }, - "require2FA": false, - "safeFields": [], - "scrapeJavaScript": true, - "scrubIPAddresses": false, - "sensitiveFields": [], - "slug": "the-interstellar-jurisdiction", - "status": { - "id": "active", - "name": "active" - }, - "storeCrashReports": 0, - "teams": [ - { - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null - }, - "dateCreated": "2018-11-06T21:20:08.115Z", - "hasAccess": true, - "id": "3", - "isMember": true, - "isPending": false, - "memberCount": 1, - "name": "Ancient Gabelers", - "slug": "ancient-gabelers" - }, - { - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null - }, - "dateCreated": "2018-11-06T21:19:55.114Z", - "hasAccess": true, - "id": "2", - "isMember": true, - "isPending": false, - "memberCount": 1, - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - } - ], - "trustedRelays": [] - } - } - } - }, - "403": { - "description": "Forbidden" - }, - "404": { - "description": "The requested resource does not exist" - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "auth_token": ["org: read"] - } - ] - }, - "put": { - "tags": ["Organizations"], - "description": "Update various attributes and configurable settings for the given organization.", - "operationId": "Update an Organization", - "parameters": [ - { - "name": "organization_id_or_slug", - "in": "path", - "description": "The id or slug of the organization to update.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "required": ["name"], - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "An optional new name for the organization." - }, - "slug": { - "type": "string", - "description": "An optional new slug for the organization. Needs to be available and unique." - } - } - }, - "example": { - "name": "Impeccably Designated", - "slug": "impeccably-designated" - } - } - }, - "required": false - }, - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "../../components/schemas/organization-details.json#/OrganizationDetails" - }, - "example": { - "access": [], - "allowSharedIssues": true, - "availableRoles": [ - { - "id": "member", - "name": "Member" - }, - { - "id": "admin", - "name": "Admin" - }, - { - "id": "manager", - "name": "Manager" - }, - { - "id": "owner", - "name": "Owner" - } - ], - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null - }, - "dataScrubber": false, - "dataScrubberDefaults": false, - "dateCreated": "2018-11-06T21:20:19.548Z", - "defaultRole": "member", - "enhancedPrivacy": false, - "experiments": {}, - "features": [ - "new-teams", - "shared-issues", - "new-issue-ui", - "repos", - "open-membership", - "invite-members", - "sso-saml2", - "sso-basic", - "suggested-commits" - ], - "id": "3", - "isDefault": false, - "isEarlyAdopter": false, - "name": "Impeccably Designated", - "onboardingTasks": [], - "openMembership": true, - "pendingAccessRequests": 0, - "projects": [], - "quota": { - "accountLimit": 0, - "maxRate": 0, - "maxRateInterval": 60, - "projectLimit": 100 - }, - "require2FA": false, - "safeFields": [], - "scrapeJavaScript": true, - "scrubIPAddresses": false, - "sensitiveFields": [], - "slug": "impeccably-designated", - "status": { - "id": "active", - "name": "active" - }, - "storeCrashReports": 0, - "teams": [], - "trustedRelays": [] - } - } - } - }, - "403": { - "description": "Forbidden" - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Not Found" - }, - "400": { - "description": "Bad Input" - } - }, - "security": [ - { - "auth_token": ["org:write"] - } - ] - } -} diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 7488f2fb8cc2fc..ce232d3aa2cd80 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -10,6 +10,7 @@ from django.urls import reverse from django.utils import timezone as django_timezone from django.utils.functional import cached_property +from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_serializer from rest_framework import serializers, status from bitfield.types import BitHandler @@ -25,8 +26,17 @@ from sentry.api.serializers.models import organization as org_serializers from sentry.api.serializers.models.organization import ( BaseOrganizationSerializer, + DetailedOrganizationSerializerWithProjectsAndTeams, TrustedRelaySerializer, ) +from sentry.apidocs.constants import ( + RESPONSE_CONFLICT, + RESPONSE_FORBIDDEN, + RESPONSE_NOT_FOUND, + RESPONSE_UNAUTHORIZED, +) +from sentry.apidocs.examples.organization_examples import OrganizationExamples +from sentry.apidocs.parameters import GlobalParams, OrganizationParams from sentry.auth.staff import is_active_staff from sentry.constants import ( ACCOUNT_RATE_LIMIT_DEFAULT, @@ -570,28 +580,196 @@ def post_org_pending_deletion( send_delete_confirmation(delete_confirmation_args) +@extend_schema_serializer( + exclude_fields=[ + "accountRateLimit", + "projectRateLimit", + "requireEmailVerification", + "relayPiiConfig", + "trustedRelays", + "apdexThreshold", + "githubPRBot", + "githubOpenPRBot", + "githubNudgeInvite", + "aggregatedDataConsent", + "genAIConsent", + "issueAlertsThreadFlag", + "metricAlertsThreadFlag", + "metricsActivatePercentiles", + "metricsActivateLastForGauges", + ] +) +class OrganizationDetailsPutSerializer(serializers.Serializer): + # general + slug = serializers.CharField( + max_length=50, + help_text="The new slug for the organization, which needs to be unique.", + required=False, + ) + name = serializers.CharField( + max_length=64, help_text="The new name for the organization.", required=False + ) + isEarlyAdopter = serializers.BooleanField( + help_text="Specify `true` to opt-in to new features before they're released to the public.", + required=False, + ) + aiSuggestedSolution = serializers.BooleanField( + help_text="Specify `true` to opt-in to [AI Suggested Solution](/product/issues/issue-details/ai-suggested-solution/) to get AI help on how to solve an issue.", + required=False, + ) + codecovAccess = serializers.BooleanField( + help_text="Specify `true` to enable Code Coverage Insights. Learn more about Codecov [here](/product/codecov/).", + required=False, + ) + + # membership + defaultRole = serializers.ChoiceField( + choices=roles.get_choices(), + help_text="The default role new members will receive.", + required=False, + ) + openMembership = serializers.BooleanField( + help_text="Specify `true` to allow organization members to freely join any team.", + required=False, + ) + eventsMemberAdmin = serializers.BooleanField( + help_text="Specify `true` to allow members to delete events (including the delete & discard action) by granting them the `event:admin` scope.", + required=False, + ) + alertsMemberWrite = serializers.BooleanField( + help_text="Specify `true` to allow members to create, edit, and delete alert rules by granting them the `alerts:write` scope.", + required=False, + ) + attachmentsRole = serializers.ChoiceField( + choices=roles.get_choices(), + help_text="The role required to download event attachments, such as native crash reports or log files.", + required=False, + ) + debugFilesRole = serializers.ChoiceField( + choices=roles.get_choices(), + help_text="The role required tto download debug information files, proguard mappings and source maps.", + required=False, + ) + + # avatar + avatarType = serializers.ChoiceField( + choices=(("letter_avatar", "Use initials"), ("upload", "Upload an image")), + help_text="The type of display picture for the organization", + required=False, + ) + # avatar = AvatarField(help_text="The file to upload as the organization avatar. Required if `avatarType` is `upload`.", required=False) + + # organization deletion TODO(isabella): check that customers can do this through UI + cancelDeletion = serializers.BooleanField( + help_text="Specify `true` to cancel the organization's pending deletion.", required=False + ) + + # security & privacy + require2FA = serializers.BooleanField( + help_text="Specify `true` to require and enforce two-factor authentication for all members.", + required=False, + ) + allowSharedIssues = serializers.BooleanField( + help_text="Specify `true` to allow sharing of limited details on issues to anonymous users.", + required=False, + ) + enhancedPrivacy = serializers.BooleanField( + help_text="Specify `true` to enable enhanced privacy controls to limit personally identifiable information (PII) as well as source code in things like notifications.", + required=False, + ) + scrapeJavaScript = serializers.BooleanField( + help_text="Specify `true` to allow Sentry to scrape missing JavaScript source context when possible.", + required=False, + ) + storeCrashReports = serializers.ChoiceField( + choices=( + (0, "Disabled"), + (1, "1 per issue"), + (5, "5 per issue"), + (10, "10 per issue"), + (20, "20 per issue"), + (50, "50 per issue"), + (100, "100 per issue"), + (-1, "Unlimited"), + ), + required=False, + ) + allowJoinRequests = serializers.BooleanField( + help_text="Specify `true` to allow users to request to join your organization.", + required=False, + ) + + # data scrubbing + dataScrubber = serializers.BooleanField( + help_text="Specify `true` to require server-side data scrubbing be enabled for all projects.", + required=False, + ) + dataScrubberDefaults = serializers.BooleanField( + help_text="Specify `true` to require the default scrubbers be applied to prevent things like passwords and credit cards from being stored for all projects.", + required=False, + ) + sensitiveFields = serializers.CharField( + help_text="Additional global field names to match against when scrubbing data for all projects. Separate multiple entries with a newline.", + required=False, + ) + safeFields = serializers.CharField( + help_text="Global field names which data scrubbers should ignore. Separate multiple entries with a newline.", + required=False, + ) + scrubIPAddresses = serializers.BooleanField( + help_text="Specify `true` to prevent IP addresses from being stored for new events on all projects.", + required=False, + ) + + # private attributes # TODO(isabella): confirm these are all inaccessible on the UI + accountRateLimit = serializers.IntegerField( + min_value=ACCOUNT_RATE_LIMIT_DEFAULT, required=False + ) + projectRateLimit = serializers.IntegerField( + min_value=PROJECT_RATE_LIMIT_DEFAULT, required=False + ) + requireEmailVerification = serializers.BooleanField(required=False) + relayPiiConfig = serializers.CharField(required=False) + trustedRelays = serializers.ListField(child=TrustedRelaySerializer(), required=False) + apdexThreshold = serializers.IntegerField(required=False) + githubPRBot = serializers.BooleanField(required=False) + githubOpenPRBot = serializers.BooleanField(required=False) + githubNudgeInvite = serializers.BooleanField(required=False) + aggregatedDataConsent = serializers.BooleanField(required=False) + genAIConsent = serializers.BooleanField(required=False) + issueAlertsThreadFlag = serializers.BooleanField(required=False) + metricAlertsThreadFlag = serializers.BooleanField(required=False) + metricsActivatePercentiles = serializers.BooleanField(required=False) + metricsActivateLastForGauges = serializers.BooleanField(required=False) + + # NOTE: We override the permission class of this endpoint in getsentry with the OrganizationDetailsPermission class +@extend_schema(tags=["Organizations"]) @region_silo_endpoint class OrganizationDetailsEndpoint(OrganizationEndpoint): publish_status = { - "DELETE": ApiPublishStatus.UNKNOWN, - "GET": ApiPublishStatus.UNKNOWN, - "PUT": ApiPublishStatus.UNKNOWN, + "DELETE": ApiPublishStatus.PRIVATE, + "GET": ApiPublishStatus.PUBLIC, + "PUT": ApiPublishStatus.PUBLIC, } + @extend_schema( + operation_id="Retrieve an Organization", + parameters=[GlobalParams.ORG_ID_OR_SLUG, OrganizationParams.DETAILED], + request=None, + responses={ + 200: org_serializers.OrganizationSerializer, + 401: RESPONSE_UNAUTHORIZED, + 403: RESPONSE_FORBIDDEN, + 404: RESPONSE_NOT_FOUND, + 413: OpenApiResponse(description="Image too large."), + }, + examples=OrganizationExamples.RETRIEVE_ORGANIZATION, + ) def get(self, request: Request, organization) -> Response: """ - Retrieve an Organization - ```````````````````````` - Return details on an individual organization including various details - such as membership access, features, and teams. - - :pparam string organization_id_or_slug: the id or slug of the organization the - team should be created for. - :param string detailed: Specify '0' to retrieve details without projects and teams. - :qparam string include_feature_flags: whether or not to include feature flags in the response - :auth: required + such as membership access, and teams. """ # This param will be used to determine if we should include feature flags in the response include_feature_flags = request.GET.get("include_feature_flags", "0") != "0" @@ -615,21 +793,24 @@ def get(self, request: Request, organization) -> Response: return self.respond(context) + @extend_schema( + operation_id="Update an Organization", + parameters=[ + GlobalParams.ORG_ID_OR_SLUG, + ], + request=OrganizationDetailsPutSerializer, + responses={ + 200: DetailedOrganizationSerializerWithProjectsAndTeams, + 401: RESPONSE_UNAUTHORIZED, + 403: RESPONSE_FORBIDDEN, + 404: RESPONSE_NOT_FOUND, + 409: RESPONSE_CONFLICT, + }, + examples=OrganizationExamples.UPDATE_ORGANIZATION, + ) def put(self, request: Request, organization) -> Response: """ - Update an Organization - `````````````````````` - - Update various attributes and configurable settings for the given - organization. - - :pparam string organization_id_or_slug: the id or slug of the organization the - team should be created for. - :param string name: an optional new name for the organization. - :param string slug: an optional new slug for the organization. Needs - to be available and unique. - :qparam string include_feature_flags: whether or not to include feature flags in the response - :auth: required + Update various attributes and configurable settings for the given organization. """ from sentry import features diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 8854193e99e5ad..49b5f80eb2147d 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, TypedDict, cast import sentry_sdk +from drf_spectacular.utils import extend_schema_serializer from rest_framework import serializers from sentry_relay.auth import PublicKey from sentry_relay.exceptions import RelayError @@ -126,6 +127,14 @@ def validate_slug(self, value: str) -> str: return value +class TrustedRelaySerializerResponse(TypedDict): + name: str + description: str + publicKey: str + created: datetime + lastModified: datetime + + class TrustedRelaySerializer(serializers.Serializer): internal_external = ( ("name", "name"), @@ -422,6 +431,7 @@ class _DetailedOrganizationSerializerResponseOptional(OrganizationSerializerResp orgRole: str +@extend_schema_serializer(exclude_fields=["availableRoles"]) class DetailedOrganizationSerializerResponse(_DetailedOrganizationSerializerResponseOptional): experiments: Any quota: Any @@ -435,9 +445,9 @@ class DetailedOrganizationSerializerResponse(_DetailedOrganizationSerializerResp enhancedPrivacy: bool dataScrubber: bool dataScrubberDefaults: bool - sensitiveFields: list[Any] # TODO - safeFields: list[Any] - storeCrashReports: Any # TODO + sensitiveFields: list[str] + safeFields: list[str] + storeCrashReports: int attachmentsRole: Any # TODO debugFilesRole: str eventsMemberAdmin: bool @@ -446,7 +456,7 @@ class DetailedOrganizationSerializerResponse(_DetailedOrganizationSerializerResp scrapeJavaScript: bool allowJoinRequests: bool relayPiiConfig: str | None - trustedRelays: Any # TODO + trustedRelays: list[TrustedRelaySerializerResponse] access: frozenset[str] pendingAccessRequests: int onboardingTasks: OnboardingTasksSerializerResponse diff --git a/src/sentry/api/serializers/types.py b/src/sentry/api/serializers/types.py index 5e956de1c6e8ac..983e01ca555aea 100644 --- a/src/sentry/api/serializers/types.py +++ b/src/sentry/api/serializers/types.py @@ -1,6 +1,8 @@ from datetime import datetime from typing import Any, TypedDict +from drf_spectacular.utils import extend_schema_serializer + from sentry.api.serializers.release_details_types import Author, LastDeploy, Project, VersionInfo @@ -22,6 +24,7 @@ class _Links(TypedDict): # Moved from serializers/models/organization.py to avoid a circular import between project and # organization serializers +@extend_schema_serializer(exclude_fields=["features"]) class OrganizationSerializerResponse(TypedDict): id: str slug: str diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index 145966ac0c5e4f..2a381a13e36c28 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -2,6 +2,54 @@ class OrganizationExamples: + RETRIEVE_ORGANIZATION = [ + OpenApiExample( + "Retrieve an organization", + value={ + "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, + "dateCreated": "2018-11-06T21:19:55.101Z", + "hasAuthProvider": False, + "id": "2", + "isEarlyAdopter": False, + "links": { + "organizationUrl": "https://the-interstellar-jurisdiction.sentry.io", + "regionUrl": "https://us.sentry.io", + }, + "name": "The Interstellar Jurisdiction", + "require2FA": False, + "requireEmailVerification": False, + "slug": "the-interstellar-jurisdiction", + "status": {"id": "active", "name": "active"}, + }, + status_codes=["200"], + response_only=True, + ) + ] + + UPDATE_ORGANIZATION = [ + OpenApiExample( + "Update an organization", + value={ + "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, + "dateCreated": "2018-11-06T21:19:55.101Z", + "hasAuthProvider": False, + "id": "2", + "isEarlyAdopter": False, + "links": { + "organizationUrl": "https://the-interstellar-jurisdiction.sentry.io", + "regionUrl": "https://us.sentry.io", + }, + "name": "The Interstellar Jurisdiction", + "require2FA": False, + "requireEmailVerification": False, + "slug": "the-interstellar-jurisdiction", + "status": {"id": "active", "name": "active"}, + }, + status_codes=["200"], + response_only=True, + ) + ] + LIST_ORGANIZATIONS = [ OpenApiExample( "List your organizations", @@ -9,13 +57,6 @@ class OrganizationExamples: { "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, "dateCreated": "2018-11-06T21:19:55.101Z", - "features": [ - "session-replay-video", - "onboarding", - "advanced-search", - "monitor-seat-billing", - "issue-platform", - ], "hasAuthProvider": False, "id": "2", "isEarlyAdopter": False, diff --git a/src/sentry/apidocs/parameters.py b/src/sentry/apidocs/parameters.py index b9c87f79609f81..979b7f81681409 100644 --- a/src/sentry/apidocs/parameters.py +++ b/src/sentry/apidocs/parameters.py @@ -121,6 +121,15 @@ class OrganizationParams: For example the following are valid parameters: - `/?project=1234&project=56789` - `/?project=-1` +""", + ) + DETAILED = OpenApiParameter( + name="detailed", + location="query", + required=False, + type=str, + description=""" +Specify `"0"` to return organization details that do not include projects or teams. """, ) OWNER = OpenApiParameter( From 4a1545b53224f2b4a0ff91c68b8ed699afca9f50 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Tue, 25 Jun 2024 11:38:51 -0400 Subject: [PATCH 02/10] update serializers + examples --- .../api/endpoints/organization_details.py | 115 ++++-- .../api/serializers/models/organization.py | 11 + src/sentry/api/serializers/types.py | 2 +- .../apidocs/examples/organization_examples.py | 373 +++++++++++++++++- 4 files changed, 461 insertions(+), 40 deletions(-) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index ce232d3aa2cd80..6c708af154e1d6 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -585,16 +585,8 @@ def post_org_pending_deletion( "accountRateLimit", "projectRateLimit", "requireEmailVerification", - "relayPiiConfig", - "trustedRelays", "apdexThreshold", - "githubPRBot", - "githubOpenPRBot", - "githubNudgeInvite", - "aggregatedDataConsent", "genAIConsent", - "issueAlertsThreadFlag", - "metricAlertsThreadFlag", "metricsActivatePercentiles", "metricsActivateLastForGauges", ] @@ -618,7 +610,7 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): required=False, ) codecovAccess = serializers.BooleanField( - help_text="Specify `true` to enable Code Coverage Insights. Learn more about Codecov [here](/product/codecov/).", + help_text="Specify `true` to enable Code Coverage Insights. This feature is only available for organizations on the Team plan and above. Learn more about Codecov [here](/product/codecov/).", required=False, ) @@ -657,11 +649,9 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): help_text="The type of display picture for the organization", required=False, ) - # avatar = AvatarField(help_text="The file to upload as the organization avatar. Required if `avatarType` is `upload`.", required=False) - - # organization deletion TODO(isabella): check that customers can do this through UI - cancelDeletion = serializers.BooleanField( - help_text="Specify `true` to cancel the organization's pending deletion.", required=False + avatar = serializers.CharField( + help_text="The image to upload as the organization avatar, in base64. Required if `avatarType` is `upload`.", + required=False, ) # security & privacy @@ -692,6 +682,7 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): (100, "100 per issue"), (-1, "Unlimited"), ), + help_text="How many native crash reports (such as Minidumps for improved processing and download in issue details) to store per issue.", required=False, ) allowJoinRequests = serializers.BooleanField( @@ -708,20 +699,94 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): help_text="Specify `true` to require the default scrubbers be applied to prevent things like passwords and credit cards from being stored for all projects.", required=False, ) - sensitiveFields = serializers.CharField( - help_text="Additional global field names to match against when scrubbing data for all projects. Separate multiple entries with a newline.", + sensitiveFields = serializers.ListField( + child=serializers.CharField(), + help_text="A list of additional global field names to match against when scrubbing data for all projects.", required=False, ) - safeFields = serializers.CharField( - help_text="Global field names which data scrubbers should ignore. Separate multiple entries with a newline.", + safeFields = serializers.ListField( + child=serializers.CharField(), + help_text="A list of global field names which data scrubbers should ignore.", required=False, ) scrubIPAddresses = serializers.BooleanField( help_text="Specify `true` to prevent IP addresses from being stored for new events on all projects.", required=False, ) + relayPiiConfig = serializers.CharField( + help_text="""Advanced data scrubbing rules that can be configured for each project as a JSON string. The new rules will only apply to upcoming events. For more details on advanced data scrubbing, see our [full documentation](/security-legal-pii/scrubbing/advanced-datascrubbing/). + +> Warning: Calling this endpoint with this field fully overwrites the advanced data scrubbing rules. + +Below is an example of a payload for a set of advanced data scrubbing rules for masking credit card numbers from the log message (equivalent to `[Mask] [Credit card numbers] from [$message]` in the Sentry app) and removing a specific key called `foo` (equivalent to `[Remove] [Anything] from [extra.foo]` in the Sentry app): +```json +{ + relayPiiConfig: "{\\"rules\":{\\"0\\":{\\"type\\":\\"creditcard\\",\\"redaction\\":{\\"method\\":\\"mask\\"}},\\"1\\":{\\"type\\":\\"anything\\",\\"redaction\\":{\\"method\\":\\"remove\\"}}},\\"applications\\":{\\"$message\\":[\\"0\\"],\\"extra.foo\\":[\\"1\\"]}}" +} +``` + """, + required=False, + ) + + # relay + trustedRelays = serializers.ListField( + child=serializers.JSONField(), + help_text="""A list of local Relays (the name, public key, and description as a JSON) registered for the organization. This feature is only available for organizations on the Business and Enterprise plans. Read more about Relay [here](/product/relay/). + + Below is an example of a list containing a single local Relay registered for the organization: + ```json + { + trustedRelays: [ + { + name: "my-relay", + publicKey: "eiwr9fdruw4erfh892qy4493reyf89ur34wefd90h", + description: "Configuration for my-relay." + } + ] + } + ``` + """, + required=False, + ) + + # github features + githubPRBot = serializers.BooleanField( + help_text="Specify `true` to allow Sentry to comment on recent pull requests suspected of causing issues. Requires a GitHub integration.", + required=False, + ) + githubOpenPRBot = serializers.BooleanField( + help_text="Specify `true` to allow Sentry to comment on open pull requests to show recent error issues for the code being changed. Requires a GitHub integration.", + required=False, + ) + githubNudgeInvite = serializers.BooleanField( + help_text="Specify `true` to allow Sentry to detect users committing to your GitHub repositories that are not part of your Sentry organization. Requires a GitHub integration.", + required=False, + ) + + # slack features + issueAlertsThreadFlag = serializers.BooleanField( + help_text="Specify `true` to allow the Sentry Slack integration to post replies in threads for an Issue Alert notification. Requires a Slack integration.", + required=False, + ) + metricAlertsThreadFlag = serializers.BooleanField( + help_text="Specify `true` to allow the Sentry Slack integration to post replies in threads for an Metric Alert notification. Requires a Slack integration.", + required=False, + ) + + # legal and compliance + aggregatedDataConsent = serializers.BooleanField( + help_text="Specify `true` to let Sentry use your error messages, stack traces, spans, and DOM interactions data for issue workflow and other product improvements.", + required=False, + ) + + # restore org + cancelDeletion = serializers.BooleanField( + help_text="Specify `true` to restore an organization that is pending deletion.", + required=False, + ) - # private attributes # TODO(isabella): confirm these are all inaccessible on the UI + # private attributes + # legacy features accountRateLimit = serializers.IntegerField( min_value=ACCOUNT_RATE_LIMIT_DEFAULT, required=False ) @@ -729,16 +794,10 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): min_value=PROJECT_RATE_LIMIT_DEFAULT, required=False ) requireEmailVerification = serializers.BooleanField(required=False) - relayPiiConfig = serializers.CharField(required=False) - trustedRelays = serializers.ListField(child=TrustedRelaySerializer(), required=False) apdexThreshold = serializers.IntegerField(required=False) - githubPRBot = serializers.BooleanField(required=False) - githubOpenPRBot = serializers.BooleanField(required=False) - githubNudgeInvite = serializers.BooleanField(required=False) - aggregatedDataConsent = serializers.BooleanField(required=False) + + # TODO: publish when GA'd genAIConsent = serializers.BooleanField(required=False) - issueAlertsThreadFlag = serializers.BooleanField(required=False) - metricAlertsThreadFlag = serializers.BooleanField(required=False) metricsActivatePercentiles = serializers.BooleanField(required=False) metricsActivateLastForGauges = serializers.BooleanField(required=False) @@ -762,7 +821,6 @@ class OrganizationDetailsEndpoint(OrganizationEndpoint): 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOT_FOUND, - 413: OpenApiResponse(description="Image too large."), }, examples=OrganizationExamples.RETRIEVE_ORGANIZATION, ) @@ -805,6 +863,7 @@ def get(self, request: Request, organization) -> Response: 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOT_FOUND, 409: RESPONSE_CONFLICT, + 413: OpenApiResponse(description="Image too large."), }, examples=OrganizationExamples.UPDATE_ORGANIZATION, ) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 49b5f80eb2147d..2a3cecc18fe374 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -647,6 +647,17 @@ def serialize( # type: ignore[explicit-override, override] return context +@extend_schema_serializer( + exclude_fields=[ + "availableRoles", + "requireEmailVerification", + "genAIConsent", + "metricsActivatePercentiles", + "metricsActivateLastForGauges", + "features", + "quota", + ] +) class DetailedOrganizationSerializerWithProjectsAndTeamsResponse( DetailedOrganizationSerializerResponse ): diff --git a/src/sentry/api/serializers/types.py b/src/sentry/api/serializers/types.py index 983e01ca555aea..45fd58fbb56b67 100644 --- a/src/sentry/api/serializers/types.py +++ b/src/sentry/api/serializers/types.py @@ -24,7 +24,7 @@ class _Links(TypedDict): # Moved from serializers/models/organization.py to avoid a circular import between project and # organization serializers -@extend_schema_serializer(exclude_fields=["features"]) +@extend_schema_serializer(exclude_fields=["features", "requireEmailVerification"]) class OrganizationSerializerResponse(TypedDict): id: str slug: str diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index 2a381a13e36c28..cea2be27145e09 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -17,7 +17,6 @@ class OrganizationExamples: }, "name": "The Interstellar Jurisdiction", "require2FA": False, - "requireEmailVerification": False, "slug": "the-interstellar-jurisdiction", "status": {"id": "active", "name": "active"}, }, @@ -28,22 +27,375 @@ class OrganizationExamples: UPDATE_ORGANIZATION = [ OpenApiExample( - "Update an organization", + "Update an organdization", value={ - "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, - "dateCreated": "2018-11-06T21:19:55.101Z", - "hasAuthProvider": False, "id": "2", + "slug": "the-interstellar-jurisdiction", + "status": {"id": "active", "name": "active"}, + "name": "The Interstellar Jurisdiction", + "dateCreated": "2018-11-06T21:19:55.101Z", "isEarlyAdopter": False, + "require2FA": False, + "avatar": {"avatarType": "letter_avatar", "avatarUuid": None, "avatarUrl": None}, "links": { "organizationUrl": "https://the-interstellar-jurisdiction.sentry.io", "regionUrl": "https://us.sentry.io", }, - "name": "The Interstellar Jurisdiction", - "require2FA": False, - "requireEmailVerification": False, - "slug": "the-interstellar-jurisdiction", - "status": {"id": "active", "name": "active"}, + "hasAuthProvider": False, + "access": [ + "org:integrations", + "member:admin", + "alerts:write", + "team:write", + "team:admin", + "project:write", + "org:read", + "member:write", + "project:read", + "member:read", + "event:admin", + "event:write", + "org:write", + "org:admin", + "project:releases", + "org:billing", + "project:admin", + "team:read", + "event:read", + "alerts:read", + ], + "onboardingTasks": [ + { + "task": "create_project", + "status": "complete", + "user": { + "id": "1", + "name": "Stella R", + "username": "stella@the-interstellar-jurisdiction.io", + "email": "stella@the-interstellar-jurisdiction.io", + "avatarUrl": "https://gravatar.com/avatar/wufeousrfdiohfwea8sfhawesdhiu", + "isActive": True, + "hasPasswordAuth": True, + "isManaged": False, + "dateJoined": "2019-06-05T17:43:29.556793Z", + "lastLogin": "2019-06-25T13:53:44.524478Z", + "has2fa": False, + "lastActive": "2024-06-25T14:43:41.678886Z", + "isSuperuser": True, + "isStaff": False, + "experiments": {}, + "emails": [ + { + "id": "1", + "email": "stella@the-interstellar-jurisdiction.io", + "is_verified": False, + } + ], + "avatar": { + "avatarType": "letter_avatar", + "avatarUuid": None, + "avatarUrl": None, + }, + }, + "completionSeen": None, + "dateCompleted": "2019-06-17T18:56:25.856360Z", + "data": {}, + } + ], + "experiments": {}, + "isDefault": False, + "defaultRole": "member", + "orgRoleList": [ + { + "id": "billing", + "name": "Billing", + "desc": "Can manage subscription and billing details.", + "scopes": ["org:billing"], + "allowed": False, + "isAllowed": False, + "isRetired": False, + "isTeamRolesAllowed": False, + "is_global": False, + "isGlobal": False, + "minimumTeamRole": "contributor", + }, + { + "id": "member", + "name": "Member", + "desc": "Members can view and act on events, as well as view most other data within the organization.", + "scopes": [ + "project:read", + "project:releases", + "member:read", + "event:admin", + "team:read", + "alerts:write", + "event:read", + "alerts:read", + "event:write", + "org:read", + ], + "allowed": False, + "isAllowed": False, + "isRetired": False, + "isTeamRolesAllowed": True, + "is_global": False, + "isGlobal": False, + "minimumTeamRole": "contributor", + }, + { + "id": "admin", + "name": "Admin", + "desc": "Admin privileges on any teams of which they're a member. They can create new teams and projects, as well as remove teams and projects on which they already hold membership (or all teams, if open membership is enabled). Additionally, they can manage memberships of teams that they are members of. They cannot invite members to the organization.", + "scopes": [ + "project:read", + "project:releases", + "member:read", + "event:admin", + "team:write", + "project:admin", + "team:read", + "org:integrations", + "alerts:write", + "team:admin", + "project:write", + "event:read", + "alerts:read", + "event:write", + "org:read", + ], + "allowed": False, + "isAllowed": False, + "isRetired": False, + "isTeamRolesAllowed": True, + "is_global": False, + "isGlobal": False, + "minimumTeamRole": "admin", + }, + { + "id": "manager", + "name": "Manager", + "desc": "Gains admin access on all teams as well as the ability to add and remove members.", + "scopes": [ + "member:write", + "project:read", + "member:read", + "event:admin", + "org:integrations", + "member:admin", + "alerts:write", + "event:write", + "org:write", + "project:releases", + "team:write", + "project:admin", + "team:read", + "team:admin", + "project:write", + "event:read", + "alerts:read", + "org:read", + ], + "allowed": False, + "isAllowed": False, + "isRetired": False, + "isTeamRolesAllowed": True, + "is_global": True, + "isGlobal": True, + "minimumTeamRole": "admin", + }, + { + "id": "owner", + "name": "Owner", + "desc": "Unrestricted access to the organization, its data, and its settings. Can add, modify, and delete projects and members, as well as make billing and plan changes.", + "scopes": [ + "member:write", + "project:read", + "member:read", + "event:admin", + "org:integrations", + "member:admin", + "alerts:write", + "event:write", + "org:write", + "org:admin", + "project:releases", + "team:write", + "project:admin", + "team:read", + "org:billing", + "team:admin", + "project:write", + "event:read", + "alerts:read", + "org:read", + ], + "allowed": False, + "isAllowed": False, + "isRetired": False, + "isTeamRolesAllowed": True, + "is_global": True, + "isGlobal": True, + "minimumTeamRole": "admin", + }, + ], + "teamRoleList": [ + { + "id": "contributor", + "name": "Contributor", + "desc": "Contributors can view and act on events, as well as view most other data within the team's projects.", + "scopes": [ + "project:read", + "project:releases", + "member:read", + "team:read", + "event:read", + "alerts:read", + "event:write", + "org:read", + ], + "allowed": False, + "isAllowed": False, + "isRetired": False, + "isTeamRolesAllowed": True, + "isMinimumRoleFor": None, + }, + { + "id": "admin", + "name": "Team Admin", + "desc": "Admin privileges on the team. They can create and remove projects, and can manage the team's memberships.", + "scopes": [ + "project:read", + "project:releases", + "member:read", + "event:admin", + "team:write", + "project:admin", + "team:read", + "org:integrations", + "alerts:write", + "team:admin", + "project:write", + "event:read", + "alerts:read", + "event:write", + "org:read", + ], + "allowed": False, + "isAllowed": False, + "isRetired": False, + "isTeamRolesAllowed": True, + "isMinimumRoleFor": "admin", + }, + ], + "openMembership": True, + "allowSharedIssues": True, + "enhancedPrivacy": False, + "dataScrubber": False, + "dataScrubberDefaults": False, + "sensitiveFields": [], + "safeFields": [], + "storeCrashReports": 0, + "attachmentsRole": "member", + "debugFilesRole": "admin", + "eventsMemberAdmin": True, + "alertsMemberWrite": True, + "scrubIPAddresses": False, + "scrapeJavaScript": True, + "allowJoinRequests": True, + "relayPiiConfig": None, + "codecovAccess": False, + "aiSuggestedSolution": True, + "githubPRBot": True, + "githubOpenPRBot": True, + "githubNudgeInvite": True, + "aggregatedDataConsent": False, + "issueAlertsThreadFlag": True, + "metricAlertsThreadFlag": True, + "trustedRelays": [], + "role": "owner", + "orgRole": "owner", + "pendingAccessRequests": 0, + "isDynamicallySampled": False, + "teams": [ + { + "id": "1", + "slug": "my-team", + "name": "my-team", + "dateCreated": "2019-06-17T18:56:19.729172Z", + "isMember": True, + "teamRole": "admin", + "flags": {"idp:provisioned": False}, + "access": [ + "project:read", + "project:releases", + "member:read", + "event:admin", + "team:write", + "project:admin", + "team:read", + "org:integrations", + "alerts:write", + "team:admin", + "project:write", + "event:read", + "alerts:read", + "event:write", + "org:read", + ], + "hasAccess": True, + "isPending": False, + "memberCount": 1, + "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, + } + ], + "projects": [ + { + "team": {"id": "1", "slug": "my-team", "name": "my-team"}, + "teams": [{"id": "1", "slug": "my-team", "name": "my-team"}], + "id": "1", + "name": "node", + "slug": "node", + "isBookmarked": False, + "isMember": True, + "access": [ + "project:read", + "project:releases", + "member:read", + "event:admin", + "team:write", + "project:admin", + "team:read", + "org:integrations", + "alerts:write", + "team:admin", + "project:write", + "event:read", + "alerts:read", + "event:write", + "org:read", + ], + "hasAccess": True, + "dateCreated": "2019-06-17T18:56:25.777769Z", + "environments": [], + "eventProcessing": {"symbolicationDegraded": False}, + "firstEvent": None, + "firstTransactionEvent": False, + "hasSessions": False, + "hasProfiles": False, + "hasReplays": False, + "hasFeedbacks": False, + "hasNewFeedbacks": False, + "hasCustomMetrics": False, + "hasMonitors": False, + "hasMinifiedStackTrace": False, + "platform": "node", + "platforms": [], + "latestRelease": None, + "hasUserReports": False, + "latestDeploys": None, + } + ], }, status_codes=["200"], response_only=True, @@ -66,7 +418,6 @@ class OrganizationExamples: }, "name": "The Interstellar Jurisdiction", "require2FA": False, - "requireEmailVerification": False, "slug": "the-interstellar-jurisdiction", "status": {"id": "active", "name": "active"}, } From 7614fee3895e6797ee15cc68f1b9f471fccbb1d8 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Tue, 25 Jun 2024 12:21:56 -0400 Subject: [PATCH 03/10] fix examples and schemas --- .../api/serializers/models/organization.py | 10 +- src/sentry/api/serializers/models/project.py | 2 + src/sentry/api/serializers/models/role.py | 1 + .../apidocs/examples/organization_examples.py | 12 +- .../apidocs/examples/project_examples.py | 124 +----------------- src/sentry/apidocs/examples/team_examples.py | 10 +- 6 files changed, 29 insertions(+), 130 deletions(-) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 2a3cecc18fe374..c59e3d61baac1b 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -14,7 +14,7 @@ from sentry import features, onboarding_tasks, options, quotas, roles from sentry.api.fields.sentry_slug import SentrySerializerSlugField from sentry.api.serializers import Serializer, register, serialize -from sentry.api.serializers.models.project import ProjectSerializerResponse +from sentry.api.serializers.models.project import OrganizationProjectResponse from sentry.api.serializers.models.role import ( OrganizationRoleSerializer, OrganizationRoleSerializerResponse, @@ -393,7 +393,7 @@ class OnboardingTasksSerializerResponse(TypedDict): task: str # TODO: literal/enum status: str # TODO: literal/enum user: UserSerializerResponse | UserSerializerResponseSelf | None - completionSeen: datetime + completionSeen: datetime | None dateCompleted: datetime data: Any # JSON object @@ -436,7 +436,7 @@ class DetailedOrganizationSerializerResponse(_DetailedOrganizationSerializerResp experiments: Any quota: Any isDefault: bool - defaultRole: bool + defaultRole: str # TODO: replace with enum/literal availableRoles: list[Any] # TODO: deprecated, use orgRoleList orgRoleList: list[OrganizationRoleSerializerResponse] teamRoleList: list[TeamRoleSerializerResponse] @@ -459,7 +459,7 @@ class DetailedOrganizationSerializerResponse(_DetailedOrganizationSerializerResp trustedRelays: list[TrustedRelaySerializerResponse] access: frozenset[str] pendingAccessRequests: int - onboardingTasks: OnboardingTasksSerializerResponse + onboardingTasks: list[OnboardingTasksSerializerResponse] codecovAccess: bool aiSuggestedSolution: bool githubPRBot: bool @@ -662,7 +662,7 @@ class DetailedOrganizationSerializerWithProjectsAndTeamsResponse( DetailedOrganizationSerializerResponse ): teams: list[TeamSerializerResponse] - projects: list[ProjectSerializerResponse] + projects: list[OrganizationProjectResponse] class DetailedOrganizationSerializerWithProjectsAndTeams(DetailedOrganizationSerializer): diff --git a/src/sentry/api/serializers/models/project.py b/src/sentry/api/serializers/models/project.py index 7ea107389de45f..c794eb7e274d84 100644 --- a/src/sentry/api/serializers/models/project.py +++ b/src/sentry/api/serializers/models/project.py @@ -253,8 +253,10 @@ class ProjectSerializerBaseResponse(_ProjectSerializerOptionalBaseResponse): access: list[str] hasAccess: bool hasCustomMetrics: bool + hasFeedbacks: bool hasMinifiedStackTrace: bool hasMonitors: bool + hasNewFeedbacks: bool hasProfiles: bool hasReplays: bool hasSessions: bool diff --git a/src/sentry/api/serializers/models/role.py b/src/sentry/api/serializers/models/role.py index 4577b8f337e782..0b651a3016cde3 100644 --- a/src/sentry/api/serializers/models/role.py +++ b/src/sentry/api/serializers/models/role.py @@ -16,6 +16,7 @@ class BaseRoleSerializerResponse(TypedDict): allowed: bool isAllowed: bool isRetired: bool + isTeamRolesAllowed: bool class OrganizationRoleSerializerResponse(BaseRoleSerializerResponse): diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index cea2be27145e09..d3912c9dfd97a4 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -27,7 +27,7 @@ class OrganizationExamples: UPDATE_ORGANIZATION = [ OpenApiExample( - "Update an organdization", + "Update an organization", value={ "id": "2", "slug": "the-interstellar-jurisdiction", @@ -379,6 +379,7 @@ class OrganizationExamples: "dateCreated": "2019-06-17T18:56:25.777769Z", "environments": [], "eventProcessing": {"symbolicationDegraded": False}, + "features": ["releases"], "firstEvent": None, "firstTransactionEvent": False, "hasSessions": False, @@ -464,6 +465,8 @@ class OrganizationExamples: "hasReplays": True, "hasMinifiedStackTrace": False, "hasMonitors": True, + "hasFeedbacks": False, + "hasNewFeedbacks": False, "hasCustomMetrics": False, "hasUserReports": False, "latestRelease": None, @@ -608,6 +611,7 @@ class OrganizationExamples: "isRetired": False, "is_global": False, "isGlobal": False, + "isTeamRolesAllowed": False, "minimumTeamRole": "contributor", }, { @@ -631,6 +635,7 @@ class OrganizationExamples: "isRetired": False, "is_global": False, "isGlobal": False, + "isTeamRolesAllowed": True, "minimumTeamRole": "contributor", }, { @@ -659,6 +664,7 @@ class OrganizationExamples: "isRetired": True, "is_global": False, "isGlobal": False, + "isTeamRolesAllowed": True, "minimumTeamRole": "admin", }, { @@ -690,6 +696,7 @@ class OrganizationExamples: "isRetired": False, "is_global": True, "isGlobal": True, + "isTeamRolesAllowed": True, "minimumTeamRole": "admin", }, { @@ -723,6 +730,7 @@ class OrganizationExamples: "isRetired": False, "is_global": True, "isGlobal": True, + "isTeamRolesAllowed": True, "minimumTeamRole": "admin", }, ], @@ -745,6 +753,7 @@ class OrganizationExamples: "isAllowed": False, "isRetired": False, "isMinimumRoleFor": None, + "isTeamRolesAllowed": True, }, { "id": "admin", @@ -771,6 +780,7 @@ class OrganizationExamples: "isAllowed": False, "isRetired": False, "isMinimumRoleFor": "admin", + "isTeamRolesAllowed": True, }, ], }, diff --git a/src/sentry/apidocs/examples/project_examples.py b/src/sentry/apidocs/examples/project_examples.py index 52320609609cbb..d293b623e815a7 100644 --- a/src/sentry/apidocs/examples/project_examples.py +++ b/src/sentry/apidocs/examples/project_examples.py @@ -84,7 +84,9 @@ "hasAccess": True, "hasMinifiedStackTrace": False, "hasCustomMetrics": False, + "hasFeedbacks": False, "hasMonitors": False, + "hasNewFeedbacks": False, "hasProfiles": False, "hasReplays": False, "hasSessions": False, @@ -172,129 +174,7 @@ "dateCreated": "2014-12-15T04:06:24.263571Z", "isEarlyAdopter": True, "require2FA": False, - "requireEmailVerification": False, "avatar": {"avatarType": "upload", "avatarUuid": "24f6f762f7a7473888b259c566da5adb"}, - "features": [ - "performance-uncompressed-assets-ingest", - "dashboards-rh-widget", - "performance-db-main-thread-visible", - "transaction-name-mark-scrubbed-as-sanitized", - "sentry-pride-logo-footer", - "issue-list-prefetch-issue-on-hover", - "mep-rollout-flag", - "performance-issues-m-n-plus-one-db-detector", - "session-replay-ui", - "alert-crash-free-metrics", - "performance-n-plus-one-db-queries-visible", - "session-replay-recording-scrubbing", - "profile-file-io-main-thread-visible", - "performance-consecutive-http-detector", - "profiling", - "symbol-sources", - "advanced-search", - "performance-n-plus-one-db-queries-post-process-group", - "minute-resolution-sessions", - "performance-transaction-name-only-search-indexed", - "performance-large-http-payload-detector", - "performance-consecutive-db-queries-ingest", - "business-to-team-promotion", - "shared-issues", - "performance-large-http-payload-post-process-group", - "promotion-be-adoption-enabled", - "monitors", - "am2-billing", - "profiling-ga", - "performance-new-trends", - "performance-n-plus-one-api-calls-post-process-group", - "performance-db-main-thread-post-process-group", - "performance-metrics-backed-transaction-summary", - "performance-db-main-thread-detector", - "issue-platform", - "performance-consecutive-db-issue", - "performance-consecutive-http-post-process-group", - "performance-n-plus-one-api-calls-detector", - "performance-render-blocking-asset-span-post-process-group", - "performance-uncompressed-assets-post-process-group", - "performance-issues-search", - "performance-slow-db-issue", - "performance-db-main-thread-ingest", - "session-replay", - "sql-format", - "performance-consecutive-db-queries-visible", - "user-spend-notifications-settings", - "performance-m-n-plus-one-db-queries-post-process-group", - "transaction-metrics-extraction", - "performance-consecutive-db-queries-post-process-group", - "performance-slow-db-query-post-process-group", - "session-replay-sdk-errors-only", - "performance-n-plus-one-db-queries-ingest", - "profile-image-decode-main-thread-visible", - "performance-issues-render-blocking-assets-detector", - "performance-m-n-plus-one-db-queries-ingest", - "anr-rate", - "auto-enable-codecov", - "ondemand-budgets", - "profile-file-io-main-thread-post-process-group", - "performance-render-blocking-asset-span-ingest", - "profile-json-decode-main-thread-post-process-group", - "onboarding-project-deletion-on-back-click", - "invite-members-rate-limits", - "transaction-name-normalize", - "performance-file-io-main-thread-visible", - "onboarding-sdk-selection", - "performance-span-histogram-view", - "performance-file-io-main-thread-ingest", - "metrics-extraction", - "profile-json-decode-main-thread-ingest", - "onboarding", - "promotion-mobperf-gift50kerr", - "device-classification", - "transaction-name-normalize-legacy", - "performance-slow-db-query-ingest", - "bundle-plan-checkout", - "metric-alert-chartcuterie", - "performance-issues-all-events-tab", - "discover-events-rate-limit", - "india-promotion", - "track-button-click-events", - "performance-issues-compressed-assets-detector", - "device-class-synthesis", - "profiling-billing", - "performance-file-io-main-thread-detector", - "integrations-deployment", - "performance-m-n-plus-one-db-queries-visible", - "mobile-cpu-memory-in-transactions", - "derive-code-mappings", - "performance-onboarding-checklist", - "performance-consecutive-http-visible", - "performance-n-plus-one-api-calls-ingest", - "performance-landing-page-stats-period", - "dynamic-sampling", - "performance-slow-db-query-visible", - "performance-n-plus-one-api-calls-visible", - "profile-image-decode-main-thread-ingest", - "set-grouping-config", - "event-attachments", - "open-membership", - "new-spike-protection", - "source-maps-debug-ids", - "paid-to-free-promotion", - "performance-large-http-payload-ingest", - "crons-issue-platform", - "profile-file-io-main-thread-ingest", - "performance-file-io-main-thread-post-process-group", - "performance-render-blocking-asset-span-visible", - "ds-sliding-window-org", - "performance-consecutive-http-ingest", - "profile-image-decode-main-thread-post-process-group", - "performance-mep-bannerless-ui", - "performance-uncompressed-assets-visible", - "performance-large-http-payload-visible", - "performance-view", - "promotion-mobperf-discount20", - "performance-new-widget-designs", - "profile-json-decode-main-thread-visible", - ], "links": { "organizationUrl": "https://sentry.sentry.io", "regionUrl": "https://us.sentry.io", diff --git a/src/sentry/apidocs/examples/team_examples.py b/src/sentry/apidocs/examples/team_examples.py index 806c53876a8cde..a4e5ab663f1b29 100644 --- a/src/sentry/apidocs/examples/team_examples.py +++ b/src/sentry/apidocs/examples/team_examples.py @@ -193,6 +193,8 @@ class TeamExamples: "hasMonitors": True, "hasProfiles": False, "hasReplays": False, + "hasFeedbacks": False, + "hasNewFeedbacks": False, "hasSessions": True, "isInternal": False, "isPublic": False, @@ -240,6 +242,8 @@ class TeamExamples: "hasMonitors": True, "hasProfiles": False, "hasReplays": False, + "hasFeedbacks": False, + "hasNewFeedbacks": False, "hasSessions": False, "isInternal": False, "isPublic": False, @@ -316,6 +320,8 @@ class TeamExamples: "hasProfiles": False, "hasReplays": False, "hasMonitors": False, + "hasFeedbacks": False, + "hasNewFeedbacks": False, "hasMinifiedStackTrace": False, "hasCustomMetrics": False, "platform": "node-express", @@ -376,6 +382,8 @@ class TeamExamples: "hasProfiles": False, "hasReplays": False, "hasMonitors": False, + "hasFeedbacks": False, + "hasNewFeedbacks": False, "hasMinifiedStackTrace": True, "hasCustomMetrics": False, "platform": "javascript", @@ -411,8 +419,6 @@ class TeamExamples: "require2FA": False, "slug": "the-interstellar-jurisdiction", "status": {"id": "active", "name": "active"}, - "requireEmailVerification": False, - "features": ["session-replay-videos"], "hasAuthProvider": True, "links": { "organizationUrl": "https://philosophers.sentry.io", From 393d766c66b4b816d47cb76515b04a3d21d27edf Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Tue, 25 Jun 2024 12:41:22 -0400 Subject: [PATCH 04/10] wait don't make this private --- .../api/serializers/models/organization.py | 1 - src/sentry/api/serializers/types.py | 2 +- .../apidocs/examples/organization_examples.py | 21 +++ .../apidocs/examples/project_examples.py | 121 ++++++++++++++++++ src/sentry/apidocs/examples/team_examples.py | 1 + 5 files changed, 144 insertions(+), 2 deletions(-) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index c59e3d61baac1b..90efacf863ea5c 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -654,7 +654,6 @@ def serialize( # type: ignore[explicit-override, override] "genAIConsent", "metricsActivatePercentiles", "metricsActivateLastForGauges", - "features", "quota", ] ) diff --git a/src/sentry/api/serializers/types.py b/src/sentry/api/serializers/types.py index 45fd58fbb56b67..dfda4cf94d1b39 100644 --- a/src/sentry/api/serializers/types.py +++ b/src/sentry/api/serializers/types.py @@ -24,7 +24,7 @@ class _Links(TypedDict): # Moved from serializers/models/organization.py to avoid a circular import between project and # organization serializers -@extend_schema_serializer(exclude_fields=["features", "requireEmailVerification"]) +@extend_schema_serializer(exclude_fields=["requireEmailVerification"]) class OrganizationSerializerResponse(TypedDict): id: str slug: str diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index d3912c9dfd97a4..16bff69757b22d 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -8,6 +8,13 @@ class OrganizationExamples: value={ "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, "dateCreated": "2018-11-06T21:19:55.101Z", + "features": [ + "session-replay-video", + "onboarding", + "advanced-search", + "monitor-seat-billing", + "issue-platform", + ], "hasAuthProvider": False, "id": "2", "isEarlyAdopter": False, @@ -41,6 +48,13 @@ class OrganizationExamples: "organizationUrl": "https://the-interstellar-jurisdiction.sentry.io", "regionUrl": "https://us.sentry.io", }, + "features": [ + "session-replay-video", + "onboarding", + "advanced-search", + "monitor-seat-billing", + "issue-platform", + ], "hasAuthProvider": False, "access": [ "org:integrations", @@ -410,6 +424,13 @@ class OrganizationExamples: { "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, "dateCreated": "2018-11-06T21:19:55.101Z", + "features": [ + "session-replay-video", + "onboarding", + "advanced-search", + "monitor-seat-billing", + "issue-platform", + ], "hasAuthProvider": False, "id": "2", "isEarlyAdopter": False, diff --git a/src/sentry/apidocs/examples/project_examples.py b/src/sentry/apidocs/examples/project_examples.py index d293b623e815a7..ca583eefc70d15 100644 --- a/src/sentry/apidocs/examples/project_examples.py +++ b/src/sentry/apidocs/examples/project_examples.py @@ -175,6 +175,127 @@ "isEarlyAdopter": True, "require2FA": False, "avatar": {"avatarType": "upload", "avatarUuid": "24f6f762f7a7473888b259c566da5adb"}, + "features": [ + "performance-uncompressed-assets-ingest", + "dashboards-rh-widget", + "performance-db-main-thread-visible", + "transaction-name-mark-scrubbed-as-sanitized", + "sentry-pride-logo-footer", + "issue-list-prefetch-issue-on-hover", + "mep-rollout-flag", + "performance-issues-m-n-plus-one-db-detector", + "session-replay-ui", + "alert-crash-free-metrics", + "performance-n-plus-one-db-queries-visible", + "session-replay-recording-scrubbing", + "profile-file-io-main-thread-visible", + "performance-consecutive-http-detector", + "profiling", + "symbol-sources", + "advanced-search", + "performance-n-plus-one-db-queries-post-process-group", + "minute-resolution-sessions", + "performance-transaction-name-only-search-indexed", + "performance-large-http-payload-detector", + "performance-consecutive-db-queries-ingest", + "business-to-team-promotion", + "shared-issues", + "performance-large-http-payload-post-process-group", + "promotion-be-adoption-enabled", + "monitors", + "am2-billing", + "profiling-ga", + "performance-new-trends", + "performance-n-plus-one-api-calls-post-process-group", + "performance-db-main-thread-post-process-group", + "performance-metrics-backed-transaction-summary", + "performance-db-main-thread-detector", + "issue-platform", + "performance-consecutive-db-issue", + "performance-consecutive-http-post-process-group", + "performance-n-plus-one-api-calls-detector", + "performance-render-blocking-asset-span-post-process-group", + "performance-uncompressed-assets-post-process-group", + "performance-issues-search", + "performance-slow-db-issue", + "performance-db-main-thread-ingest", + "session-replay", + "sql-format", + "performance-consecutive-db-queries-visible", + "user-spend-notifications-settings", + "performance-m-n-plus-one-db-queries-post-process-group", + "transaction-metrics-extraction", + "performance-consecutive-db-queries-post-process-group", + "performance-slow-db-query-post-process-group", + "session-replay-sdk-errors-only", + "performance-n-plus-one-db-queries-ingest", + "profile-image-decode-main-thread-visible", + "performance-issues-render-blocking-assets-detector", + "performance-m-n-plus-one-db-queries-ingest", + "anr-rate", + "auto-enable-codecov", + "ondemand-budgets", + "profile-file-io-main-thread-post-process-group", + "performance-render-blocking-asset-span-ingest", + "profile-json-decode-main-thread-post-process-group", + "onboarding-project-deletion-on-back-click", + "invite-members-rate-limits", + "transaction-name-normalize", + "performance-file-io-main-thread-visible", + "onboarding-sdk-selection", + "performance-span-histogram-view", + "performance-file-io-main-thread-ingest", + "metrics-extraction", + "profile-json-decode-main-thread-ingest", + "onboarding", + "promotion-mobperf-gift50kerr", + "device-classification", + "transaction-name-normalize-legacy", + "performance-slow-db-query-ingest", + "bundle-plan-checkout", + "metric-alert-chartcuterie", + "performance-issues-all-events-tab", + "discover-events-rate-limit", + "india-promotion", + "track-button-click-events", + "performance-issues-compressed-assets-detector", + "device-class-synthesis", + "profiling-billing", + "performance-file-io-main-thread-detector", + "integrations-deployment", + "performance-m-n-plus-one-db-queries-visible", + "mobile-cpu-memory-in-transactions", + "derive-code-mappings", + "performance-onboarding-checklist", + "performance-consecutive-http-visible", + "performance-n-plus-one-api-calls-ingest", + "performance-landing-page-stats-period", + "dynamic-sampling", + "performance-slow-db-query-visible", + "performance-n-plus-one-api-calls-visible", + "profile-image-decode-main-thread-ingest", + "set-grouping-config", + "event-attachments", + "open-membership", + "new-spike-protection", + "source-maps-debug-ids", + "paid-to-free-promotion", + "performance-large-http-payload-ingest", + "crons-issue-platform", + "profile-file-io-main-thread-ingest", + "performance-file-io-main-thread-post-process-group", + "performance-render-blocking-asset-span-visible", + "ds-sliding-window-org", + "performance-consecutive-http-ingest", + "profile-image-decode-main-thread-post-process-group", + "performance-mep-bannerless-ui", + "performance-uncompressed-assets-visible", + "performance-large-http-payload-visible", + "performance-view", + "promotion-mobperf-discount20", + "performance-new-widget-designs", + "profile-json-decode-main-thread-visible", + ], "links": { "organizationUrl": "https://sentry.sentry.io", "regionUrl": "https://us.sentry.io", diff --git a/src/sentry/apidocs/examples/team_examples.py b/src/sentry/apidocs/examples/team_examples.py index a4e5ab663f1b29..97877ad9454ddb 100644 --- a/src/sentry/apidocs/examples/team_examples.py +++ b/src/sentry/apidocs/examples/team_examples.py @@ -419,6 +419,7 @@ class TeamExamples: "require2FA": False, "slug": "the-interstellar-jurisdiction", "status": {"id": "active", "name": "active"}, + "features": ["session-replay-videos"], "hasAuthProvider": True, "links": { "organizationUrl": "https://philosophers.sentry.io", From e8ab73862333e73fd36b46bfcc073f1080d9b481 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Tue, 25 Jun 2024 12:45:53 -0400 Subject: [PATCH 05/10] little more typing --- src/sentry/api/serializers/models/organization.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 90efacf863ea5c..1ad5103eedae14 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -448,8 +448,8 @@ class DetailedOrganizationSerializerResponse(_DetailedOrganizationSerializerResp sensitiveFields: list[str] safeFields: list[str] storeCrashReports: int - attachmentsRole: Any # TODO - debugFilesRole: str + attachmentsRole: str # TODO: replace with enum/literal + debugFilesRole: str # TODO: replace with enum/literal eventsMemberAdmin: bool alertsMemberWrite: bool scrubIPAddresses: bool From ea4bfbd60d7f1daf27f6e165e093b3392f9eb70f Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Tue, 25 Jun 2024 13:00:55 -0400 Subject: [PATCH 06/10] make features optional --- src/sentry/api/serializers/types.py | 7 +++++-- .../apidocs/examples/organization_examples.py | 14 -------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/sentry/api/serializers/types.py b/src/sentry/api/serializers/types.py index dfda4cf94d1b39..460477b2a11b77 100644 --- a/src/sentry/api/serializers/types.py +++ b/src/sentry/api/serializers/types.py @@ -22,10 +22,14 @@ class _Links(TypedDict): regionUrl: str +class OrganizationSerializerResponseOptional(TypedDict, total=False): + features: list[str] # Only included if include_feature_flags is True + + # Moved from serializers/models/organization.py to avoid a circular import between project and # organization serializers @extend_schema_serializer(exclude_fields=["requireEmailVerification"]) -class OrganizationSerializerResponse(TypedDict): +class OrganizationSerializerResponse(OrganizationSerializerResponseOptional): id: str slug: str status: _Status @@ -35,7 +39,6 @@ class OrganizationSerializerResponse(TypedDict): require2FA: bool requireEmailVerification: bool avatar: SerializedAvatarFields - features: list[str] links: _Links hasAuthProvider: bool diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index 16bff69757b22d..e112e8bfe8f5f1 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -8,13 +8,6 @@ class OrganizationExamples: value={ "avatar": {"avatarType": "letter_avatar", "avatarUuid": None}, "dateCreated": "2018-11-06T21:19:55.101Z", - "features": [ - "session-replay-video", - "onboarding", - "advanced-search", - "monitor-seat-billing", - "issue-platform", - ], "hasAuthProvider": False, "id": "2", "isEarlyAdopter": False, @@ -48,13 +41,6 @@ class OrganizationExamples: "organizationUrl": "https://the-interstellar-jurisdiction.sentry.io", "regionUrl": "https://us.sentry.io", }, - "features": [ - "session-replay-video", - "onboarding", - "advanced-search", - "monitor-seat-billing", - "issue-platform", - ], "hasAuthProvider": False, "access": [ "org:integrations", From 068c053f504cbd5df45f41464106072533869206 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Tue, 25 Jun 2024 17:19:21 -0400 Subject: [PATCH 07/10] copyedits --- src/sentry/api/endpoints/organization_details.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 6c708af154e1d6..80291ddb577a3c 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -639,14 +639,14 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): ) debugFilesRole = serializers.ChoiceField( choices=roles.get_choices(), - help_text="The role required tto download debug information files, proguard mappings and source maps.", + help_text="The role required to download debug information files, ProGuard mappings and source maps.", required=False, ) # avatar avatarType = serializers.ChoiceField( choices=(("letter_avatar", "Use initials"), ("upload", "Upload an image")), - help_text="The type of display picture for the organization", + help_text="The type of display picture for the organization.", required=False, ) avatar = serializers.CharField( @@ -692,11 +692,11 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): # data scrubbing dataScrubber = serializers.BooleanField( - help_text="Specify `true` to require server-side data scrubbing be enabled for all projects.", + help_text="Specify `true` to require server-side data scrubbing for all projects.", required=False, ) dataScrubberDefaults = serializers.BooleanField( - help_text="Specify `true` to require the default scrubbers be applied to prevent things like passwords and credit cards from being stored for all projects.", + help_text="Specify `true` to applt the default scrubbers to prevent things like passwords and credit cards from being stored for all projects.", required=False, ) sensitiveFields = serializers.ListField( @@ -714,7 +714,7 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): required=False, ) relayPiiConfig = serializers.CharField( - help_text="""Advanced data scrubbing rules that can be configured for each project as a JSON string. The new rules will only apply to upcoming events. For more details on advanced data scrubbing, see our [full documentation](/security-legal-pii/scrubbing/advanced-datascrubbing/). + help_text="""Advanced data scrubbing rules that can be configured for each project as a JSON string. The new rules will only apply to new incoming events. For more details on advanced data scrubbing, see our [full documentation](/security-legal-pii/scrubbing/advanced-datascrubbing/). > Warning: Calling this endpoint with this field fully overwrites the advanced data scrubbing rules. @@ -769,7 +769,7 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): required=False, ) metricAlertsThreadFlag = serializers.BooleanField( - help_text="Specify `true` to allow the Sentry Slack integration to post replies in threads for an Metric Alert notification. Requires a Slack integration.", + help_text="Specify `true` to allow the Sentry Slack integration to post replies in threads for a Metric Alert notification. Requires a Slack integration.", required=False, ) @@ -826,8 +826,8 @@ class OrganizationDetailsEndpoint(OrganizationEndpoint): ) def get(self, request: Request, organization) -> Response: """ - Return details on an individual organization including various details - such as membership access, and teams. + Return details on an individual organization, including various details + such as membership access and teams. """ # This param will be used to determine if we should include feature flags in the response include_feature_flags = request.GET.get("include_feature_flags", "0") != "0" From 93cc6bb730ec47856b77684bb0d46e64c606bd64 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Tue, 25 Jun 2024 17:20:26 -0400 Subject: [PATCH 08/10] typo --- src/sentry/api/endpoints/organization_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 80291ddb577a3c..d48332e9753dd0 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -696,7 +696,7 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): required=False, ) dataScrubberDefaults = serializers.BooleanField( - help_text="Specify `true` to applt the default scrubbers to prevent things like passwords and credit cards from being stored for all projects.", + help_text="Specify `true` to apply the default scrubbers to prevent things like passwords and credit cards from being stored for all projects.", required=False, ) sensitiveFields = serializers.ListField( From d0b03251311ef23172dd8ceb635445d166cbce3b Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Mon, 1 Jul 2024 10:52:56 -0400 Subject: [PATCH 09/10] new request and response fields --- src/sentry/api/endpoints/organization_details.py | 4 ++++ src/sentry/api/serializers/models/organization.py | 1 + src/sentry/apidocs/examples/organization_examples.py | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 49b43042742ef5..5da08890076b2c 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -30,6 +30,7 @@ TrustedRelaySerializer, ) from sentry.apidocs.constants import ( + RESPONSE_BAD_REQUEST, RESPONSE_CONFLICT, RESPONSE_FORBIDDEN, RESPONSE_NOT_FOUND, @@ -592,6 +593,7 @@ def post_org_pending_deletion( "genAIConsent", "metricsActivatePercentiles", "metricsActivateLastForGauges", + "extrapolateMetrics", ] ) class OrganizationDetailsPutSerializer(serializers.Serializer): @@ -803,6 +805,7 @@ class OrganizationDetailsPutSerializer(serializers.Serializer): genAIConsent = serializers.BooleanField(required=False) metricsActivatePercentiles = serializers.BooleanField(required=False) metricsActivateLastForGauges = serializers.BooleanField(required=False) + extrapolateMetrics = serializers.BooleanField(required=False) # NOTE: We override the permission class of this endpoint in getsentry with the OrganizationDetailsPermission class @@ -862,6 +865,7 @@ def get(self, request: Request, organization) -> Response: request=OrganizationDetailsPutSerializer, responses={ 200: DetailedOrganizationSerializerWithProjectsAndTeams, + 400: RESPONSE_BAD_REQUEST, 401: RESPONSE_UNAUTHORIZED, 403: RESPONSE_FORBIDDEN, 404: RESPONSE_NOT_FOUND, diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 0145f0e376e922..2be189c71bedc5 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -642,6 +642,7 @@ def serialize( # type: ignore[explicit-override, override] "genAIConsent", "metricsActivatePercentiles", "metricsActivateLastForGauges", + "extrapolateMetrics", "quota", ] ) diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index ad34bd68041743..2348e898c38beb 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -390,6 +390,15 @@ class OrganizationExamples: "hasCustomMetrics": False, "hasMonitors": False, "hasMinifiedStackTrace": False, + "hasInsightsHttp": True, + "hasInsightsDb": False, + "hasInsightsAssets": True, + "hasInsightsAppStart": False, + "hasInsightsScreenLoad": False, + "hasInsightsVitals": False, + "hasInsightsCaches": False, + "hasInsightsQueues": False, + "hasInsightsLlmMonitoring": False, "platform": "node", "platforms": [], "latestRelease": None, From 05332df0d6811d70fde003a73d3072eb033fb709 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Wed, 10 Jul 2024 15:33:52 -0400 Subject: [PATCH 10/10] update example --- src/sentry/apidocs/examples/organization_examples.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index 2348e898c38beb..b08e37061a0d53 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -36,6 +36,7 @@ class OrganizationExamples: "dateCreated": "2018-11-06T21:19:55.101Z", "isEarlyAdopter": False, "require2FA": False, + "requiresSso": False, "avatar": {"avatarType": "letter_avatar", "avatarUuid": None, "avatarUrl": None}, "links": { "organizationUrl": "https://the-interstellar-jurisdiction.sentry.io",