From 4d4b91419253ff6b6f4f406200fc76efce6515e3 Mon Sep 17 00:00:00 2001 From: betegon Date: Wed, 10 Jun 2026 20:00:39 +0200 Subject: [PATCH 1/7] ref(api): use token operationIds + summary for core + api endpoints operationId->token migration, batch 1 of 3 (core + api endpoints, 83 ops). Move the human sentence into summary= and set operation_id to a short camelCase REST token for the @sentry/api SDK; docs title/slug stay identical (derived from summary). Ad-hoc overrides applied for path-driven awkwardness/consistency: - SCIM provision matches the CRUD scim names (provisionOrganizationScimV2User/Group) - symbol source detail ops singular (delete/updateProjectSymbolSource) - listOrganizationTraceItemAttributes (singular Item), listProjectDebugFiles --- src/sentry/api/endpoints/debug_files.py | 3 ++- .../api/endpoints/event_attachment_details.py | 3 ++- src/sentry/api/endpoints/event_attachments.py | 3 ++- .../api/endpoints/organization_events.py | 3 ++- .../organization_events_timeseries.py | 3 ++- .../organization_profiling_profiles.py | 6 ++++-- .../api/endpoints/organization_project_keys.py | 3 ++- .../api/endpoints/organization_relay_usage.py | 3 ++- .../api/endpoints/organization_releases.py | 6 ++++-- .../api/endpoints/organization_sessions.py | 3 ++- .../endpoints/organization_stats_summary.py | 3 ++- .../api/endpoints/organization_stats_v2.py | 3 ++- src/sentry/api/endpoints/organization_tags.py | 3 ++- src/sentry/api/endpoints/organization_trace.py | 3 ++- .../organization_trace_item_attributes.py | 3 ++- .../api/endpoints/organization_trace_meta.py | 3 ++- .../api/endpoints/project_filter_details.py | 3 ++- src/sentry/api/endpoints/project_filters.py | 3 ++- .../api/endpoints/project_member_index.py | 3 ++- .../api/endpoints/project_profiling_profile.py | 3 ++- src/sentry/api/endpoints/project_repo.py | 3 ++- .../api/endpoints/project_servicehooks.py | 6 ++++-- .../api/endpoints/project_symbol_sources.py | 12 ++++++++---- .../api/endpoints/project_tagkey_values.py | 3 ++- .../release_threshold_status_index.py | 3 ++- src/sentry/api/endpoints/seer_models.py | 3 ++- src/sentry/api/endpoints/source_map_debug.py | 3 ++- .../core/endpoints/organization_details.py | 6 ++++-- .../endpoints/organization_environments.py | 3 ++- .../core/endpoints/organization_index.py | 3 ++- .../endpoints/organization_member_details.py | 9 ++++++--- .../endpoints/organization_member_index.py | 6 ++++-- .../organization_member_team_details.py | 9 ++++++--- .../core/endpoints/organization_projects.py | 6 ++++-- .../core/endpoints/organization_teams.py | 6 ++++-- .../core/endpoints/organization_user_teams.py | 3 ++- src/sentry/core/endpoints/project_details.py | 9 ++++++--- .../endpoints/project_environment_details.py | 6 ++++-- .../core/endpoints/project_environments.py | 3 ++- .../core/endpoints/project_key_details.py | 9 ++++++--- src/sentry/core/endpoints/project_keys.py | 6 ++++-- src/sentry/core/endpoints/project_stats.py | 3 ++- .../core/endpoints/project_team_details.py | 6 ++++-- src/sentry/core/endpoints/project_teams.py | 3 ++- src/sentry/core/endpoints/project_users.py | 3 ++- src/sentry/core/endpoints/scim/members.py | 18 ++++++++++++------ src/sentry/core/endpoints/scim/teams.py | 15 ++++++++++----- src/sentry/core/endpoints/team_details.py | 9 ++++++--- src/sentry/core/endpoints/team_members.py | 3 ++- src/sentry/core/endpoints/team_projects.py | 6 ++++-- 50 files changed, 166 insertions(+), 83 deletions(-) diff --git a/src/sentry/api/endpoints/debug_files.py b/src/sentry/api/endpoints/debug_files.py index d166be11d35e15..648709acddbdbf 100644 --- a/src/sentry/api/endpoints/debug_files.py +++ b/src/sentry/api/endpoints/debug_files.py @@ -285,7 +285,8 @@ def download(self, debug_file_id, project: Project): raise Http404 @extend_schema( - operation_id="List a Project's Debug Information Files", + operation_id="listProjectDebugFiles", + summary="List a Project's Debug Information Files", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/event_attachment_details.py b/src/sentry/api/endpoints/event_attachment_details.py index dce0aee42aef84..b6eac73e103e9d 100644 --- a/src/sentry/api/endpoints/event_attachment_details.py +++ b/src/sentry/api/endpoints/event_attachment_details.py @@ -93,7 +93,8 @@ def stream_attachment(): return response @extend_schema( - operation_id="Retrieve an Event Attachment", + operation_id="getProjectEventAttachment", + summary="Retrieve an Event Attachment", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/event_attachments.py b/src/sentry/api/endpoints/event_attachments.py index 0df10eb6e5fb92..a9e359405cc09e 100644 --- a/src/sentry/api/endpoints/event_attachments.py +++ b/src/sentry/api/endpoints/event_attachments.py @@ -39,7 +39,8 @@ class EventAttachmentsEndpoint(ProjectEndpoint): } @extend_schema( - operation_id="List an Event's Attachments", + operation_id="listProjectEventAttachments", + summary="List an Event's Attachments", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/organization_events.py b/src/sentry/api/endpoints/organization_events.py index 3da22a2e52c648..c4915ad1c3c6cc 100644 --- a/src/sentry/api/endpoints/organization_events.py +++ b/src/sentry/api/endpoints/organization_events.py @@ -155,7 +155,8 @@ def get_features(self, organization: Organization, request: Request) -> Mapping[ return all_features @extend_schema( - operation_id="Query Explore Events in Table Format", + operation_id="listOrganizationEvents", + summary="Query Explore Events in Table Format", parameters=[ GlobalParams.END, GlobalParams.ENVIRONMENT, diff --git a/src/sentry/api/endpoints/organization_events_timeseries.py b/src/sentry/api/endpoints/organization_events_timeseries.py index 33cdf500675243..148f9a875bc8ad 100644 --- a/src/sentry/api/endpoints/organization_events_timeseries.py +++ b/src/sentry/api/endpoints/organization_events_timeseries.py @@ -129,7 +129,8 @@ def get_comparison_delta(self, request: Request) -> timedelta | None: return None @extend_schema( - operation_id="Query Explore Events in Timeseries Format", + operation_id="listOrganizationEventsTimeseries", + summary="Query Explore Events in Timeseries Format", parameters=[ GlobalParams.END, GlobalParams.ENVIRONMENT, diff --git a/src/sentry/api/endpoints/organization_profiling_profiles.py b/src/sentry/api/endpoints/organization_profiling_profiles.py index 7c5aef607bcdad..d37cd4058d8886 100644 --- a/src/sentry/api/endpoints/organization_profiling_profiles.py +++ b/src/sentry/api/endpoints/organization_profiling_profiles.py @@ -116,7 +116,8 @@ class OrganizationProfilingFlamegraphEndpoint(OrganizationProfilingBaseEndpoint) } @extend_schema( - operation_id="Retrieve a Flamegraph for an Organization", + operation_id="getOrganizationProfilingFlamegraph", + summary="Retrieve a Flamegraph for an Organization", parameters=[ GlobalParams.ORG_ID_OR_SLUG, OrganizationParams.PROJECT, @@ -212,7 +213,8 @@ class OrganizationProfilingChunksEndpoint(OrganizationProfilingBaseEndpoint): } @extend_schema( - operation_id="Retrieve Profile Chunks for an Organization", + operation_id="listOrganizationProfilingChunks", + summary="Retrieve Profile Chunks for an Organization", parameters=[ GlobalParams.ORG_ID_OR_SLUG, CHUNKS_PROJECT_PARAM, diff --git a/src/sentry/api/endpoints/organization_project_keys.py b/src/sentry/api/endpoints/organization_project_keys.py index 6f35048ed61282..7ad38e70b34877 100644 --- a/src/sentry/api/endpoints/organization_project_keys.py +++ b/src/sentry/api/endpoints/organization_project_keys.py @@ -41,7 +41,8 @@ class OrganizationProjectKeysEndpoint(OrganizationEndpoint): ) @extend_schema( - operation_id="List an Organization's Client Keys", + operation_id="listOrganizationProjectKeys", + summary="List an Organization's Client Keys", parameters=[ GlobalParams.ORG_ID_OR_SLUG, CursorQueryParam, diff --git a/src/sentry/api/endpoints/organization_relay_usage.py b/src/sentry/api/endpoints/organization_relay_usage.py index de3c4f5e7fffdc..8332a9dbdb836a 100644 --- a/src/sentry/api/endpoints/organization_relay_usage.py +++ b/src/sentry/api/endpoints/organization_relay_usage.py @@ -29,7 +29,8 @@ class OrganizationRelayUsage(OrganizationEndpoint): permission_classes = (OrganizationPermission,) @extend_schema( - operation_id="List an Organization's trusted Relays", + operation_id="listOrganizationRelayUsage", + summary="List an Organization's trusted Relays", parameters=[GlobalParams.ORG_ID_OR_SLUG], request=None, responses={ diff --git a/src/sentry/api/endpoints/organization_releases.py b/src/sentry/api/endpoints/organization_releases.py index 50a3ce0b258d96..fad09fdc7f68fb 100644 --- a/src/sentry/api/endpoints/organization_releases.py +++ b/src/sentry/api/endpoints/organization_releases.py @@ -357,7 +357,8 @@ def get_projects( ) @extend_schema( - operation_id="List an Organization's Releases", + operation_id="listOrganizationReleases", + summary="List an Organization's Releases", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.ENVIRONMENT, @@ -737,7 +738,8 @@ def qs_load_func(queryset, total_offset, qs_offset, limit): ) @extend_schema( - operation_id="Create a New Release for an Organization", + operation_id="createOrganizationRelease", + summary="Create a New Release for an Organization", parameters=[GlobalParams.ORG_ID_OR_SLUG], request=ReleaseSerializerWithProjects, responses={ diff --git a/src/sentry/api/endpoints/organization_sessions.py b/src/sentry/api/endpoints/organization_sessions.py index 10a31e290cdc94..cd06d6530a4eaf 100644 --- a/src/sentry/api/endpoints/organization_sessions.py +++ b/src/sentry/api/endpoints/organization_sessions.py @@ -39,7 +39,8 @@ class OrganizationSessionsEndpoint(OrganizationEndpoint): owner = ApiOwner.TELEMETRY_EXPERIENCE @extend_schema( - operation_id="Retrieve Release Health Session Statistics", + operation_id="getOrganizationSessions", + summary="Retrieve Release Health Session Statistics", parameters=[ GlobalParams.START, GlobalParams.END, diff --git a/src/sentry/api/endpoints/organization_stats_summary.py b/src/sentry/api/endpoints/organization_stats_summary.py index aa6da1d54b34ab..58391b84d48fbd 100644 --- a/src/sentry/api/endpoints/organization_stats_summary.py +++ b/src/sentry/api/endpoints/organization_stats_summary.py @@ -130,7 +130,8 @@ class OrganizationStatsSummaryEndpoint(OrganizationEndpoint): owner = ApiOwner.DASHBOARDS @extend_schema( - operation_id="Retrieve an Organization's Events Count by Project", + operation_id="getOrganizationStatsSummary", + summary="Retrieve an Organization's Events Count by Project", parameters=[GlobalParams.ORG_ID_OR_SLUG, OrgStatsSummaryQueryParamsSerializer], request=None, responses={ diff --git a/src/sentry/api/endpoints/organization_stats_v2.py b/src/sentry/api/endpoints/organization_stats_v2.py index 7ca56189dc9b4c..f0c5a3800b5149 100644 --- a/src/sentry/api/endpoints/organization_stats_v2.py +++ b/src/sentry/api/endpoints/organization_stats_v2.py @@ -159,7 +159,8 @@ class OrganizationStatsEndpointV2(OrganizationEndpoint): permission_classes = (OrganizationAndStaffPermission,) @extend_schema( - operation_id="Retrieve Event Counts for an Organization (v2)", + operation_id="listOrganizationStatsV2", + summary="Retrieve Event Counts for an Organization (v2)", parameters=[GlobalParams.ORG_ID_OR_SLUG, OrgStatsQueryParamsSerializer], request=None, responses={ diff --git a/src/sentry/api/endpoints/organization_tags.py b/src/sentry/api/endpoints/organization_tags.py index 4ff19824b9f691..b5e475642d6237 100644 --- a/src/sentry/api/endpoints/organization_tags.py +++ b/src/sentry/api/endpoints/organization_tags.py @@ -38,7 +38,8 @@ class OrganizationTagsEndpoint(OrganizationEndpoint): owner = ApiOwner.DATA_BROWSING @extend_schema( - operation_id="List an Organization's Tags", + operation_id="listOrganizationTags", + summary="List an Organization's Tags", parameters=[ GlobalParams.ORG_ID_OR_SLUG, OrganizationParams.PROJECT, diff --git a/src/sentry/api/endpoints/organization_trace.py b/src/sentry/api/endpoints/organization_trace.py index 9fdc3b27f4dc3c..2346928b4f7b09 100644 --- a/src/sentry/api/endpoints/organization_trace.py +++ b/src/sentry/api/endpoints/organization_trace.py @@ -125,7 +125,8 @@ def query_trace_data( ) @extend_schema( - operation_id="Retrieve a Trace", + operation_id="getOrganizationTrace", + summary="Retrieve a Trace", parameters=[ GlobalParams.ORG_ID_OR_SLUG, TRACE_ID_PATH_PARAM, diff --git a/src/sentry/api/endpoints/organization_trace_item_attributes.py b/src/sentry/api/endpoints/organization_trace_item_attributes.py index 6950125c6d1438..6734b27e7788ec 100644 --- a/src/sentry/api/endpoints/organization_trace_item_attributes.py +++ b/src/sentry/api/endpoints/organization_trace_item_attributes.py @@ -356,7 +356,8 @@ class OrganizationTraceItemAttributesEndpoint(OrganizationTraceItemAttributesEnd } @extend_schema( - operation_id="List Trace Item Attributes", + operation_id="listOrganizationTraceItemAttributes", + summary="List Trace Item Attributes", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.STATS_PERIOD, diff --git a/src/sentry/api/endpoints/organization_trace_meta.py b/src/sentry/api/endpoints/organization_trace_meta.py index 302f6bb15e1ac0..0a669b14a0864e 100644 --- a/src/sentry/api/endpoints/organization_trace_meta.py +++ b/src/sentry/api/endpoints/organization_trace_meta.py @@ -221,7 +221,8 @@ def query_meta_data( ) @extend_schema( - operation_id="Retrieve Trace Metadata", + operation_id="getOrganizationTraceMeta", + summary="Retrieve Trace Metadata", parameters=[ GlobalParams.ORG_ID_OR_SLUG, TRACE_ID_PATH_PARAM, diff --git a/src/sentry/api/endpoints/project_filter_details.py b/src/sentry/api/endpoints/project_filter_details.py index 7654bbdf7e4c66..c46e3a6370c75e 100644 --- a/src/sentry/api/endpoints/project_filter_details.py +++ b/src/sentry/api/endpoints/project_filter_details.py @@ -29,7 +29,8 @@ class ProjectFilterDetailsEndpoint(ProjectEndpoint): } @extend_schema( - operation_id="Update an Inbound Data Filter", + operation_id="updateProjectFilter", + summary="Update an Inbound Data Filter", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/project_filters.py b/src/sentry/api/endpoints/project_filters.py index 197613aaa4c361..c678db1232e713 100644 --- a/src/sentry/api/endpoints/project_filters.py +++ b/src/sentry/api/endpoints/project_filters.py @@ -29,7 +29,8 @@ class ProjectFiltersEndpoint(ProjectEndpoint): } @extend_schema( - operation_id="List a Project's Data Filters", + operation_id="listProjectFilters", + summary="List a Project's Data Filters", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/project_member_index.py b/src/sentry/api/endpoints/project_member_index.py index ebf617942dc83f..27a2e112126032 100644 --- a/src/sentry/api/endpoints/project_member_index.py +++ b/src/sentry/api/endpoints/project_member_index.py @@ -25,7 +25,8 @@ class ProjectMemberIndexEndpoint(ProjectEndpoint): owner = ApiOwner.FOUNDATIONS @extend_schema( - operation_id="List a Project's Organization Members", + operation_id="listProjectMembers", + summary="List a Project's Organization Members", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG], request=None, responses={ diff --git a/src/sentry/api/endpoints/project_profiling_profile.py b/src/sentry/api/endpoints/project_profiling_profile.py index 03009b426fab46..33ba5a7c279ff2 100644 --- a/src/sentry/api/endpoints/project_profiling_profile.py +++ b/src/sentry/api/endpoints/project_profiling_profile.py @@ -44,7 +44,8 @@ class ProjectProfilingProfileEndpoint(ProjectProfilingBaseEndpoint): } @extend_schema( - operation_id="Retrieve a Profile", + operation_id="getProjectProfilingProfile", + summary="Retrieve a Profile", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/project_repo.py b/src/sentry/api/endpoints/project_repo.py index 86589a0d1bba06..4be1cdf629837d 100644 --- a/src/sentry/api/endpoints/project_repo.py +++ b/src/sentry/api/endpoints/project_repo.py @@ -68,7 +68,8 @@ class ProjectRepoEndpoint(ProjectEndpoint): permission_classes = (ProjectPermission,) @extend_schema( - operation_id="Link a Repository to a Project", + operation_id="linkProjectRepository", + summary="Link a Repository to a Project", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/project_servicehooks.py b/src/sentry/api/endpoints/project_servicehooks.py index 7e5d007d0e60cc..044ed446f10dd7 100644 --- a/src/sentry/api/endpoints/project_servicehooks.py +++ b/src/sentry/api/endpoints/project_servicehooks.py @@ -42,7 +42,8 @@ def has_feature(self, request: Request, project): return features.has("projects:servicehooks", project=project, actor=request.user) @extend_schema( - operation_id="List a Project's Service Hooks", + operation_id="listProjectHooks", + summary="List a Project's Service Hooks", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -89,7 +90,8 @@ def get(self, request: Request, project) -> Response[list[ServiceHookSerializerR ) @extend_schema( - operation_id="Register a New Service Hook", + operation_id="registerProjectServiceHook", + summary="Register a New Service Hook", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG], request=ServiceHookValidator, responses={ diff --git a/src/sentry/api/endpoints/project_symbol_sources.py b/src/sentry/api/endpoints/project_symbol_sources.py index 2cc048ce3de2b4..95de33e3cf4079 100644 --- a/src/sentry/api/endpoints/project_symbol_sources.py +++ b/src/sentry/api/endpoints/project_symbol_sources.py @@ -258,7 +258,8 @@ class ProjectSymbolSourcesEndpoint(ProjectEndpoint): } @extend_schema( - operation_id="Retrieve a Project's Symbol Sources", + operation_id="listProjectSymbolSources", + summary="Retrieve a Project's Symbol Sources", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -294,7 +295,8 @@ def get( return Response(redacted) @extend_schema( - operation_id="Delete a Symbol Source from a Project", + operation_id="deleteProjectSymbolSource", + summary="Delete a Symbol Source from a Project", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -330,7 +332,8 @@ def delete( return Response(data={"error": "Missing source id"}, status=404) @extend_schema( - operation_id="Add a Symbol Source to a Project", + operation_id="addProjectSymbolSource", + summary="Add a Symbol Source to a Project", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG], request=SourceSerializer, responses={ @@ -369,7 +372,8 @@ def post(self, request: Request, project: Project) -> Response[_SymbolSource] | return Response(data=redacted_single[0], status=201) @extend_schema( - operation_id="Update a Project's Symbol Source", + operation_id="updateProjectSymbolSource", + summary="Update a Project's Symbol Source", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/project_tagkey_values.py b/src/sentry/api/endpoints/project_tagkey_values.py index 5031d37f5792a0..34b9e82b6bc7d9 100644 --- a/src/sentry/api/endpoints/project_tagkey_values.py +++ b/src/sentry/api/endpoints/project_tagkey_values.py @@ -40,7 +40,8 @@ class ProjectTagKeyValuesEndpoint(ProjectEndpoint): ) @extend_schema( - operation_id="List a Tag's Values", + operation_id="listProjectTagValues", + summary="List a Tag's Values", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/api/endpoints/release_thresholds/release_threshold_status_index.py b/src/sentry/api/endpoints/release_thresholds/release_threshold_status_index.py index 7b4a47f02d3267..2057687656694c 100644 --- a/src/sentry/api/endpoints/release_thresholds/release_threshold_status_index.py +++ b/src/sentry/api/endpoints/release_thresholds/release_threshold_status_index.py @@ -107,7 +107,8 @@ class ReleaseThresholdStatusIndexEndpoint(OrganizationReleasesBaseEndpoint): } @extend_schema( - operation_id="Retrieve Statuses of Release Thresholds (Alpha)", + operation_id="listOrganizationReleaseThresholdStatuses", + summary="Retrieve Statuses of Release Thresholds (Alpha)", parameters=[GlobalParams.ORG_ID_OR_SLUG, ReleaseThresholdStatusIndexSerializer], request=None, responses={ diff --git a/src/sentry/api/endpoints/seer_models.py b/src/sentry/api/endpoints/seer_models.py index a2258a528a6433..9ced0a63761dd1 100644 --- a/src/sentry/api/endpoints/seer_models.py +++ b/src/sentry/api/endpoints/seer_models.py @@ -60,7 +60,8 @@ class SeerModelsEndpoint(Endpoint): ) @extend_schema( - operation_id="List Seer AI Models", + operation_id="listSeerModels", + summary="List Seer AI Models", responses={ 200: inline_sentry_response_serializer("SeerModelsResponse", SeerModelsResponse), }, diff --git a/src/sentry/api/endpoints/source_map_debug.py b/src/sentry/api/endpoints/source_map_debug.py index a5c02110e8c32b..7794d3e8e3f143 100644 --- a/src/sentry/api/endpoints/source_map_debug.py +++ b/src/sentry/api/endpoints/source_map_debug.py @@ -137,7 +137,8 @@ class SourceMapDebugEndpoint(ProjectEndpoint): owner = ApiOwner.WEB_FRONTEND_SDKS @extend_schema( - operation_id="Get Debug Information Related to Source Maps for a Given Event", + operation_id="getProjectEventSourceMapDebug", + summary="Get Debug Information Related to Source Maps for a Given Event", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/organization_details.py b/src/sentry/core/endpoints/organization_details.py index 3202eb95ee49e4..84b2147445d253 100644 --- a/src/sentry/core/endpoints/organization_details.py +++ b/src/sentry/core/endpoints/organization_details.py @@ -1081,7 +1081,8 @@ class OrganizationDetailsEndpoint(OrganizationEndpoint): } @extend_schema( - operation_id="Retrieve an Organization", + operation_id="getOrganization", + summary="Retrieve an Organization", parameters=[GlobalParams.ORG_ID_OR_SLUG, OrganizationParams.DETAILED], request=None, responses={ @@ -1126,7 +1127,8 @@ def get( return self.respond(context) @extend_schema( - operation_id="Update an Organization", + operation_id="updateOrganization", + summary="Update an Organization", parameters=[ GlobalParams.ORG_ID_OR_SLUG, ], diff --git a/src/sentry/core/endpoints/organization_environments.py b/src/sentry/core/endpoints/organization_environments.py index aea0a1c19cae2f..5a563bbdc1bcec 100644 --- a/src/sentry/core/endpoints/organization_environments.py +++ b/src/sentry/core/endpoints/organization_environments.py @@ -30,7 +30,8 @@ class OrganizationEnvironmentsEndpoint(OrganizationEndpoint): } @extend_schema( - operation_id="List an Organization's Environments", + operation_id="listOrganizationEnvironments", + summary="List an Organization's Environments", parameters=[GlobalParams.ORG_ID_OR_SLUG, EnvironmentParams.VISIBILITY], responses={ 200: inline_sentry_response_serializer( diff --git a/src/sentry/core/endpoints/organization_index.py b/src/sentry/core/endpoints/organization_index.py index 19614cc56d044b..da3cdc88b17028 100644 --- a/src/sentry/core/endpoints/organization_index.py +++ b/src/sentry/core/endpoints/organization_index.py @@ -125,7 +125,8 @@ class OrganizationIndexEndpoint(Endpoint): permission_classes = (OrganizationPermission,) @extend_schema( - operation_id="List Your Organizations", + operation_id="listOrganizations", + summary="List Your Organizations", parameters=[ OrganizationParams.OWNER, CursorQueryParam, diff --git a/src/sentry/core/endpoints/organization_member_details.py b/src/sentry/core/endpoints/organization_member_details.py index d59fa694299450..4ee3651f75f240 100644 --- a/src/sentry/core/endpoints/organization_member_details.py +++ b/src/sentry/core/endpoints/organization_member_details.py @@ -111,7 +111,8 @@ def _get_member( raise OrganizationMember.DoesNotExist() @extend_schema( - operation_id="Retrieve an Organization Member", + operation_id="getOrganizationMember", + summary="Retrieve an Organization Member", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the organization member."), @@ -144,7 +145,8 @@ def get( return Response(body) @extend_schema( - operation_id="Update an Organization Member's Roles", + operation_id="updateOrganizationMember", + summary="Update an Organization Member's Roles", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the member to update."), @@ -456,7 +458,8 @@ def _handle_deletion_by_member( return Response(status=204) @extend_schema( - operation_id="Delete an Organization Member", + operation_id="deleteOrganizationMember", + summary="Delete an Organization Member", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the member to delete."), diff --git a/src/sentry/core/endpoints/organization_member_index.py b/src/sentry/core/endpoints/organization_member_index.py index 006f58a17f3990..7fb80050246436 100644 --- a/src/sentry/core/endpoints/organization_member_index.py +++ b/src/sentry/core/endpoints/organization_member_index.py @@ -193,7 +193,8 @@ class OrganizationMemberIndexEndpoint(OrganizationEndpoint): owner = ApiOwner.FOUNDATIONS @extend_schema( - operation_id="List an Organization's Members", + operation_id="listOrganizationMembers", + summary="List an Organization's Members", parameters=[ GlobalParams.ORG_ID_OR_SLUG, CursorQueryParam, @@ -318,7 +319,8 @@ def get( ) @extend_schema( - operation_id="Add a Member to an Organization", + operation_id="addOrganizationMember", + summary="Add a Member to an Organization", parameters=[ GlobalParams.ORG_ID_OR_SLUG, ], diff --git a/src/sentry/core/endpoints/organization_member_team_details.py b/src/sentry/core/endpoints/organization_member_team_details.py index f420685e0a87b5..bea03a133f1368 100644 --- a/src/sentry/core/endpoints/organization_member_team_details.py +++ b/src/sentry/core/endpoints/organization_member_team_details.py @@ -234,7 +234,8 @@ def get( ) @extend_schema( - operation_id="Add an Organization Member to a Team", + operation_id="addOrganizationMemberTeam", + summary="Add an Organization Member to a Team", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the organization member to add to the team"), @@ -368,7 +369,8 @@ def post( return Response(body, status=201) @extend_schema( - operation_id="Update an Organization Member's Team Role", + operation_id="updateOrganizationMemberTeam", + summary="Update an Organization Member's Team Role", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the organization member to change"), @@ -475,7 +477,8 @@ def _change_team_member_role( ) @extend_schema( - operation_id="Delete an Organization Member from a Team", + operation_id="deleteOrganizationMemberTeam", + summary="Delete an Organization Member from a Team", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the organization member to delete from the team"), diff --git a/src/sentry/core/endpoints/organization_projects.py b/src/sentry/core/endpoints/organization_projects.py index 8a267d615aeb79..e0f0b62022f42c 100644 --- a/src/sentry/core/endpoints/organization_projects.py +++ b/src/sentry/core/endpoints/organization_projects.py @@ -130,7 +130,8 @@ class OrganizationProjectsEndpoint(OrganizationEndpoint): logger = logging.getLogger("team-project.create") @extend_schema( - operation_id="List an Organization's Projects", + operation_id="listOrganizationProjects", + summary="List an Organization's Projects", parameters=[ GlobalParams.ORG_ID_OR_SLUG, CursorQueryParam, @@ -291,7 +292,8 @@ def should_add_creator_to_team(self, user: User | AnonymousUser) -> TypeIs[User] @extend_schema( tags=["Projects"], - operation_id="Create a Project for an Organization", + operation_id="createOrganizationProject", + summary="Create a Project for an Organization", parameters=[GlobalParams.ORG_ID_OR_SLUG], request=ProjectPostSerializer, responses={ diff --git a/src/sentry/core/endpoints/organization_teams.py b/src/sentry/core/endpoints/organization_teams.py index f60da6e42932fc..ce8c74bc4c55d5 100644 --- a/src/sentry/core/endpoints/organization_teams.py +++ b/src/sentry/core/endpoints/organization_teams.py @@ -86,7 +86,8 @@ def team_serializer_for_post(self): return TeamSerializer() @extend_schema( - operation_id="List an Organization's Teams", + operation_id="listOrganizationTeams", + summary="List an Organization's Teams", parameters=[ GlobalParams.ORG_ID_OR_SLUG, TeamParams.DETAILED, @@ -173,7 +174,8 @@ def should_add_creator_to_team(self, request: Request): return request.user.is_authenticated @extend_schema( - operation_id="Create a New Team", + operation_id="createOrganizationTeam", + summary="Create a New Team", parameters=[ GlobalParams.ORG_ID_OR_SLUG, ], diff --git a/src/sentry/core/endpoints/organization_user_teams.py b/src/sentry/core/endpoints/organization_user_teams.py index 7215ff87cce737..719d7c0d8859f0 100644 --- a/src/sentry/core/endpoints/organization_user_teams.py +++ b/src/sentry/core/endpoints/organization_user_teams.py @@ -29,7 +29,8 @@ class OrganizationUserTeamsEndpoint(OrganizationEndpoint): owner = ApiOwner.FOUNDATIONS @extend_schema( - operation_id="List a User's Teams for an Organization", + operation_id="listOrganizationUserTeams", + summary="List a User's Teams for an Organization", parameters=[GlobalParams.ORG_ID_OR_SLUG], request=None, responses={ diff --git a/src/sentry/core/endpoints/project_details.py b/src/sentry/core/endpoints/project_details.py index aea9709f20ec37..2a4584af83d8ca 100644 --- a/src/sentry/core/endpoints/project_details.py +++ b/src/sentry/core/endpoints/project_details.py @@ -547,7 +547,8 @@ def _get_unresolved_count(self, project): return queryset.count() @extend_schema( - operation_id="Retrieve a Project", + operation_id="getProject", + summary="Retrieve a Project", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG], request=None, responses={ @@ -596,7 +597,8 @@ def get( return Response(data) @extend_schema( - operation_id="Update a Project", + operation_id="updateProject", + summary="Update a Project", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -1188,7 +1190,8 @@ def put( return Response(body) @extend_schema( - operation_id="Delete a Project", + operation_id="deleteProject", + summary="Delete a Project", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG], responses={ 204: RESPONSE_NO_CONTENT, diff --git a/src/sentry/core/endpoints/project_environment_details.py b/src/sentry/core/endpoints/project_environment_details.py index 62d9f1ceb91f8c..f615de123b46a4 100644 --- a/src/sentry/core/endpoints/project_environment_details.py +++ b/src/sentry/core/endpoints/project_environment_details.py @@ -33,7 +33,8 @@ class ProjectEnvironmentDetailsEndpoint(ProjectEndpoint): } @extend_schema( - operation_id="Retrieve a Project Environment", + operation_id="getProjectEnvironment", + summary="Retrieve a Project Environment", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -65,7 +66,8 @@ def get( return Response(body) @extend_schema( - operation_id="Update a Project Environment", + operation_id="updateProjectEnvironment", + summary="Update a Project Environment", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/project_environments.py b/src/sentry/core/endpoints/project_environments.py index 574654c8e618f6..a386ca15f506eb 100644 --- a/src/sentry/core/endpoints/project_environments.py +++ b/src/sentry/core/endpoints/project_environments.py @@ -40,7 +40,8 @@ class ProjectEnvironmentsEndpoint(ProjectEndpoint): } @extend_schema( - operation_id="List a Project's Environments", + operation_id="listProjectEnvironments", + summary="List a Project's Environments", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/project_key_details.py b/src/sentry/core/endpoints/project_key_details.py index b259202d6d1c1e..95cf6fb6956916 100644 --- a/src/sentry/core/endpoints/project_key_details.py +++ b/src/sentry/core/endpoints/project_key_details.py @@ -41,7 +41,8 @@ class ProjectKeyDetailsEndpoint(ProjectKeyEndpoint): } @extend_schema( - operation_id="Retrieve a Client Key", + operation_id="getProjectKey", + summary="Retrieve a Client Key", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -65,7 +66,8 @@ def get( return Response(body, status=200) @extend_schema( - operation_id="Update a Client Key", + operation_id="updateProjectKey", + summary="Update a Client Key", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -174,7 +176,8 @@ def put( return Response(body, status=200) @extend_schema( - operation_id="Delete a Client Key", + operation_id="deleteProjectKey", + summary="Delete a Client Key", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/project_keys.py b/src/sentry/core/endpoints/project_keys.py index aa868ec73a0659..d9d9d673b4a28b 100644 --- a/src/sentry/core/endpoints/project_keys.py +++ b/src/sentry/core/endpoints/project_keys.py @@ -50,7 +50,8 @@ class ProjectKeysEndpoint(ProjectEndpoint): ) @extend_schema( - operation_id="List a Project's Client Keys", + operation_id="listProjectKeys", + summary="List a Project's Client Keys", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -90,7 +91,8 @@ def get(self, request: Request, project) -> Response[list[ProjectKeySerializerRe ) @extend_schema( - operation_id="Create a New Client Key", + operation_id="createProjectKey", + summary="Create a New Client Key", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/project_stats.py b/src/sentry/core/endpoints/project_stats.py index 55e270ac8560b9..d669313ad1006e 100644 --- a/src/sentry/core/endpoints/project_stats.py +++ b/src/sentry/core/endpoints/project_stats.py @@ -67,7 +67,8 @@ class ProjectStatsEndpoint(ProjectEndpoint, StatsMixin): ) @extend_schema( - operation_id="Retrieve Event Counts for a Project", + operation_id="listProjectStats", + summary="Retrieve Event Counts for a Project", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/project_team_details.py b/src/sentry/core/endpoints/project_team_details.py index 659cae43962524..4c95c99a2fad51 100644 --- a/src/sentry/core/endpoints/project_team_details.py +++ b/src/sentry/core/endpoints/project_team_details.py @@ -66,7 +66,8 @@ def convert_args( return (args, kwargs) @extend_schema( - operation_id="Add a Team to a Project", + operation_id="addProjectTeam", + summary="Add a Team to a Project", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, @@ -100,7 +101,8 @@ def post(self, request: Request, project, team: Team) -> Response[ProjectWithTea return Response(body, status=201) @extend_schema( - operation_id="Delete a Team from a Project", + operation_id="deleteProjectTeam", + summary="Delete a Team from a Project", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/project_teams.py b/src/sentry/core/endpoints/project_teams.py index 4a9f49bb41f125..485e4691bdcca9 100644 --- a/src/sentry/core/endpoints/project_teams.py +++ b/src/sentry/core/endpoints/project_teams.py @@ -25,7 +25,8 @@ class ProjectTeamsEndpoint(ProjectEndpoint): owner = ApiOwner.FOUNDATIONS @extend_schema( - operation_id="List a Project's Teams", + operation_id="listProjectTeams", + summary="List a Project's Teams", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, CursorQueryParam], request=None, responses={ diff --git a/src/sentry/core/endpoints/project_users.py b/src/sentry/core/endpoints/project_users.py index dad08ea834f7d6..3e14793583571c 100644 --- a/src/sentry/core/endpoints/project_users.py +++ b/src/sentry/core/endpoints/project_users.py @@ -51,7 +51,8 @@ class ProjectUsersEndpoint(ProjectEndpoint): permission_classes = (ProjectAndStaffPermission,) @extend_schema( - operation_id="List a Project's Users", + operation_id="listProjectUsers", + summary="List a Project's Users", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.PROJECT_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/scim/members.py b/src/sentry/core/endpoints/scim/members.py index c9601ff95b0ad4..800bbf584fa083 100644 --- a/src/sentry/core/endpoints/scim/members.py +++ b/src/sentry/core/endpoints/scim/members.py @@ -247,7 +247,8 @@ def _should_delete_member(self, operation): return False @extend_schema( - operation_id="Query an Individual Organization Member", + operation_id="getOrganizationScimV2User", + summary="Query an Individual Organization Member", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the member to query."), @@ -276,7 +277,8 @@ def get( return Response(body) @extend_schema( - operation_id="Update an Organization Member's Attributes", + operation_id="updateOrganizationScimV2User", + summary="Update an Organization Member's Attributes", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the member to update."), @@ -334,7 +336,8 @@ def patch( return Response(body) @extend_schema( - operation_id="Delete an Organization Member via SCIM", + operation_id="deleteOrganizationScimV2User", + summary="Delete an Organization Member via SCIM", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the member to delete."), @@ -365,7 +368,8 @@ def delete( return Response(status=204) @extend_schema( - operation_id="Update an Organization Member's Attributes", + operation_id="updateOrganizationScimV2User", + summary="Update an Organization Member's Attributes", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the member to update."), @@ -485,7 +489,8 @@ class OrganizationSCIMMemberIndex(SCIMEndpoint): permission_classes = (OrganizationSCIMMemberPermission,) @extend_schema( - operation_id="List an Organization's SCIM Members", + operation_id="listOrganizationScimV2Users", + summary="List an Organization's SCIM Members", parameters=[GlobalParams.ORG_ID_OR_SLUG, SCIMQueryParamSerializer], responses={ 200: inline_sentry_response_serializer( @@ -544,7 +549,8 @@ def on_results(results): ) @extend_schema( - operation_id="Provision a New Organization Member", + operation_id="provisionOrganizationScimV2User", + summary="Provision a New Organization Member", parameters=[GlobalParams.ORG_ID_OR_SLUG], request=inline_serializer( name="SCIMMemberProvision", diff --git a/src/sentry/core/endpoints/scim/teams.py b/src/sentry/core/endpoints/scim/teams.py index 6ec1bb974121da..626425fee884b6 100644 --- a/src/sentry/core/endpoints/scim/teams.py +++ b/src/sentry/core/endpoints/scim/teams.py @@ -186,7 +186,8 @@ class OrganizationSCIMTeamIndex(SCIMEndpoint): permission_classes = (OrganizationSCIMTeamPermission,) @extend_schema( - operation_id="List an Organization's Paginated Teams", + operation_id="listOrganizationScimV2Groups", + summary="List an Organization's Paginated Teams", parameters=[GlobalParams.ORG_ID_OR_SLUG, SCIMQueryParamSerializer], request=None, responses={ @@ -235,7 +236,8 @@ def on_results(results): ) @extend_schema( - operation_id="Provision a New Team", + operation_id="provisionOrganizationScimV2Group", + summary="Provision a New Team", parameters=[GlobalParams.ORG_ID_OR_SLUG], request=inline_serializer( name="SCIMTeamRequestBody", @@ -350,7 +352,8 @@ def _get_team(self, organization, team_id_or_slug): return team @extend_schema( - operation_id="Query an Individual Team", + operation_id="getOrganizationScimV2Group", + summary="Query an Individual Team", parameters=[GlobalParams.TEAM_ID_OR_SLUG, GlobalParams.ORG_ID_OR_SLUG], request=None, responses={ @@ -548,7 +551,8 @@ def _handle_replace_patch_op( return None @extend_schema( - operation_id="Update a Team's Attributes", + operation_id="updateOrganizationScimV2Group", + summary="Update a Team's Attributes", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG], request=SCIMTeamPatchRequestSerializer, responses={ @@ -641,7 +645,8 @@ def patch( return self.respond(status=204) @extend_schema( - operation_id="Delete an Individual Team", + operation_id="deleteOrganizationScimV2Group", + summary="Delete an Individual Team", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG], responses={ 204: RESPONSE_SUCCESS, diff --git a/src/sentry/core/endpoints/team_details.py b/src/sentry/core/endpoints/team_details.py index 1c491166a0efc1..bc2743aadc2152 100644 --- a/src/sentry/core/endpoints/team_details.py +++ b/src/sentry/core/endpoints/team_details.py @@ -72,7 +72,8 @@ def can_modify_idp_team(self, team: Team): return self._allow_idp_changes @extend_schema( - operation_id="Retrieve a Team", + operation_id="getTeam", + summary="Retrieve a Team", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG, @@ -106,7 +107,8 @@ def get(self, request: Request, team) -> Response[TeamSerializerResponse]: return Response(body) @extend_schema( - operation_id="Update a Team", + operation_id="updateTeam", + summary="Update a Team", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG], request=TeamDetailsSerializer, responses={ @@ -154,7 +156,8 @@ def put( return Response(as_validation_errors(serializer), status=status.HTTP_400_BAD_REQUEST) @extend_schema( - operation_id="Delete a Team", + operation_id="deleteTeam", + summary="Delete a Team", parameters=[GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG], responses={ 204: RESPONSE_NO_CONTENT, diff --git a/src/sentry/core/endpoints/team_members.py b/src/sentry/core/endpoints/team_members.py index 4f1f9c0b4e058c..2bf36f69885d37 100644 --- a/src/sentry/core/endpoints/team_members.py +++ b/src/sentry/core/endpoints/team_members.py @@ -68,7 +68,8 @@ class TeamMembersEndpoint(TeamEndpoint): owner = ApiOwner.FOUNDATIONS @extend_schema( - operation_id="List a Team's Members", + operation_id="listTeamMembers", + summary="List a Team's Members", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG, diff --git a/src/sentry/core/endpoints/team_projects.py b/src/sentry/core/endpoints/team_projects.py index 0107e0c95c5abe..1159f991a6e534 100644 --- a/src/sentry/core/endpoints/team_projects.py +++ b/src/sentry/core/endpoints/team_projects.py @@ -152,7 +152,8 @@ class TeamProjectsEndpoint(TeamEndpoint): owner = ApiOwner.FOUNDATIONS @extend_schema( - operation_id="List a Team's Projects", + operation_id="listTeamProjects", + summary="List a Team's Projects", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG, @@ -211,7 +212,8 @@ def get( @extend_schema( # Ensure POST is in the projects tab tags=["Projects"], - operation_id="Create a New Project", + operation_id="createTeamProject", + summary="Create a New Project", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.TEAM_ID_OR_SLUG, From 40646957b933542c5fe48488e34966a63fe86a41 Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 16 Jun 2026 12:29:22 +0200 Subject: [PATCH 2/7] fix(api): unique operationId for SCIM member PUT + add uniqueness guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SCIM member PUT and PATCH both had operation_id="updateOrganizationScimV2User" (pre-existing: they shared the sentence operationId). PATCH is PUBLIC, PUT is EXPERIMENTAL, so the public api-docs build never caught it — but it's an invalid duplicate in the full schema. Give the experimental PUT a distinct id+summary (replaceOrganizationScimV2User / "Replace an Organization Member's Attributes"; PUT = SCIM full replace). Add tests/apidocs/test_operation_id_uniqueness.py: AST-scan every @extend_schema and assert operation_id and summary are each unique across src/sentry (duplicate summary = docs URL collision, since the reference slug derives from summary). Catches the recurrence regardless of publish status; pure file scan, no spec generation. --- src/sentry/core/endpoints/scim/members.py | 4 +- tests/apidocs/test_operation_id_uniqueness.py | 73 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 tests/apidocs/test_operation_id_uniqueness.py diff --git a/src/sentry/core/endpoints/scim/members.py b/src/sentry/core/endpoints/scim/members.py index b9cfb570d4e132..f1a34fca8904ab 100644 --- a/src/sentry/core/endpoints/scim/members.py +++ b/src/sentry/core/endpoints/scim/members.py @@ -368,8 +368,8 @@ def delete( return Response(status=204) @extend_schema( - operation_id="updateOrganizationScimV2User", - summary="Update an Organization Member's Attributes", + operation_id="replaceOrganizationScimV2User", + summary="Replace an Organization Member's Attributes", parameters=[ GlobalParams.ORG_ID_OR_SLUG, GlobalParams.member_id("The ID of the member to update."), diff --git a/tests/apidocs/test_operation_id_uniqueness.py b/tests/apidocs/test_operation_id_uniqueness.py new file mode 100644 index 00000000000000..021e33737b2cc1 --- /dev/null +++ b/tests/apidocs/test_operation_id_uniqueness.py @@ -0,0 +1,73 @@ +"""Guards against duplicate ``operation_id`` / ``summary`` values in +``@extend_schema`` decorators. + +Two operations sharing an ``operation_id`` produce an invalid OpenAPI document and +duplicate SDK function names; two sharing a ``summary`` collide on the same docs URL +(the API reference derives the page slug from ``summary``). Both are easy to introduce +by accident — especially PUT/PATCH pairs on the same endpoint — and the drf-spectacular +``--fail-on-warn`` build only sees PUBLIC operations, so it never catches a clash that +involves a non-public method. This test scans the source instead, so it covers every +``@extend_schema`` regardless of publish status. +""" + +from __future__ import annotations + +import ast +import os +from collections import defaultdict + +SENTRY_SRC = os.path.join(os.path.dirname(__file__), "..", "..", "src", "sentry") + + +def _iter_extend_schema_kwargs() -> list[tuple[str, str, str]]: + """Yield (kwarg_name, value, "path:line") for every string-literal ``operation_id`` + and ``summary`` kwarg passed to an ``extend_schema(...)`` call under src/sentry.""" + found: list[tuple[str, str, str]] = [] + for root, _dirs, files in os.walk(SENTRY_SRC): + for name in files: + if not name.endswith(".py"): + continue + path = os.path.join(root, name) + try: + tree = ast.parse(open(path, encoding="utf-8").read(), filename=path) + except (SyntaxError, UnicodeDecodeError): + continue + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + func = node.func + fname = func.attr if isinstance(func, ast.Attribute) else getattr(func, "id", None) + if fname != "extend_schema": + continue + for kw in node.keywords: + if ( + kw.arg in ("operation_id", "summary") + and isinstance(kw.value, ast.Constant) + and isinstance(kw.value.value, str) + ): + rel = os.path.relpath(path, SENTRY_SRC) + found.append((kw.arg, kw.value.value, f"src/sentry/{rel}:{kw.lineno}")) + return found + + +def _duplicates(kwarg: str) -> dict[str, list[str]]: + locations: dict[str, list[str]] = defaultdict(list) + for name, value, loc in _iter_extend_schema_kwargs(): + if name == kwarg: + locations[value].append(loc) + return {value: locs for value, locs in locations.items() if len(locs) > 1} + + +def test_operation_ids_are_unique() -> None: + dups = _duplicates("operation_id") + assert not dups, "Duplicate @extend_schema operation_id values:\n" + "\n".join( + f" {value!r}: {', '.join(locs)}" for value, locs in sorted(dups.items()) + ) + + +def test_summaries_are_unique() -> None: + # The docs page slug is slugify(summary); duplicates collide on the same URL. + dups = _duplicates("summary") + assert not dups, "Duplicate @extend_schema summary values (docs URL collision):\n" + "\n".join( + f" {value!r}: {', '.join(locs)}" for value, locs in sorted(dups.items()) + ) From 1bedb46b31759d7e9d074b88ab3b77f3a3ed9b0c Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 16 Jun 2026 14:06:38 +0200 Subject: [PATCH 3/7] fix(api): close file handles in operation_id uniqueness scan Used a context manager when reading source files; the bare open().read() leaked handles, which pytest surfaced as unraisable ResourceWarnings and flagged the test as flaky in CI. --- tests/apidocs/test_operation_id_uniqueness.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/apidocs/test_operation_id_uniqueness.py b/tests/apidocs/test_operation_id_uniqueness.py index 021e33737b2cc1..745f089a93ef65 100644 --- a/tests/apidocs/test_operation_id_uniqueness.py +++ b/tests/apidocs/test_operation_id_uniqueness.py @@ -29,7 +29,8 @@ def _iter_extend_schema_kwargs() -> list[tuple[str, str, str]]: continue path = os.path.join(root, name) try: - tree = ast.parse(open(path, encoding="utf-8").read(), filename=path) + with open(path, encoding="utf-8") as f: + tree = ast.parse(f.read(), filename=path) except (SyntaxError, UnicodeDecodeError): continue for node in ast.walk(tree): From 10edd359125ea7d754eb682c0112b4247ee521c1 Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 16 Jun 2026 15:09:34 +0200 Subject: [PATCH 4/7] ref(api): simplify operationId guard; move summary check to build hook The operationId uniqueness test was AST-based solely to scope the summary check away from OpenApiExample(summary=...). Split the two concerns: - operation_id uniqueness: a plain regex scan (operation_id= appears only as an @extend_schema kwarg). Handles both quote styles so apostrophes in un-migrated sentence-style values aren't truncated. - summary uniqueness: enforced in custom_postprocessing_hook, where each operation's real summary is directly available (no example nesting) and the check is correctly scoped to public/docs operations. Guards against docs-URL slug collisions; fails the api docs build. --- src/sentry/apidocs/hooks.py | 14 ++++ tests/apidocs/test_hooks.py | 39 ++++++++++ tests/apidocs/test_operation_id_uniqueness.py | 74 ++++++------------- 3 files changed, 75 insertions(+), 52 deletions(-) diff --git a/src/sentry/apidocs/hooks.py b/src/sentry/apidocs/hooks.py index 123023581ddf46..9397d7105af70d 100644 --- a/src/sentry/apidocs/hooks.py +++ b/src/sentry/apidocs/hooks.py @@ -232,10 +232,24 @@ def custom_postprocessing_hook(result: Any, generator: Any, **kwargs: Any) -> An # Fetch schema component references schema_components = result["components"]["schemas"] + # The API docs derive each page's URL slug from its summary, so two public + # operations sharing a summary would collide on the same docs URL. + summaries_seen: dict[str, str] = {} + for path, endpoints in result["paths"].items(): for method_info in endpoints.values(): endpoint_name = f"'{method_info['operationId']}'" + summary = method_info.get("summary") + if summary is not None: + if summary in summaries_seen: + raise SentryApiBuildError( + f"Duplicate summary {summary!r} on endpoints {summaries_seen[summary]} " + f"and {endpoint_name}. Summaries must be unique because the API docs " + "derive each page's URL from them." + ) + summaries_seen[summary] = endpoint_name + _check_tag(method_info, endpoint_name) _check_description( method_info, diff --git a/tests/apidocs/test_hooks.py b/tests/apidocs/test_hooks.py index f6e288a3507488..e624ca7eddf2ba 100644 --- a/tests/apidocs/test_hooks.py +++ b/tests/apidocs/test_hooks.py @@ -1,6 +1,9 @@ from unittest import TestCase +import pytest + from sentry.apidocs.hooks import _ENDPOINT_SERVERS, custom_postprocessing_hook +from sentry.apidocs.utils import SentryApiBuildError class EndpointServersTest(TestCase): @@ -46,6 +49,42 @@ def test_servers_applied_to_endpoint(self) -> None: assert "servers" not in processed["paths"]["/api/0/other/endpoint/"]["get"] +class SummaryUniquenessTest(TestCase): + def setUp(self) -> None: + _ENDPOINT_SERVERS.clear() + + def _op(self, summary: str) -> dict: + return { + "tags": ["Events"], + "description": "An endpoint", + "operationId": summary.lower().replace(" ", "-"), + "summary": summary, + "parameters": [], + } + + def test_duplicate_summary_raises(self) -> None: + result = { + "components": {"schemas": {}}, + "paths": { + "/api/0/foo/": {"get": self._op("List Foos")}, + "/api/0/bar/": {"get": self._op("List Foos")}, + }, + } + with pytest.raises(SentryApiBuildError): + custom_postprocessing_hook(result, None) + + def test_unique_summaries_pass(self) -> None: + result = { + "components": {"schemas": {}}, + "paths": { + "/api/0/foo/": {"get": self._op("List Foos")}, + "/api/0/bar/": {"get": self._op("List Bars")}, + }, + } + # Should not raise. + custom_postprocessing_hook(result, None) + + class FixIssueRoutesTest(TestCase): def test_issue_route_fixes(self) -> None: BEFORE = { diff --git a/tests/apidocs/test_operation_id_uniqueness.py b/tests/apidocs/test_operation_id_uniqueness.py index 745f089a93ef65..722e2f0d7afa2b 100644 --- a/tests/apidocs/test_operation_id_uniqueness.py +++ b/tests/apidocs/test_operation_id_uniqueness.py @@ -1,74 +1,44 @@ -"""Guards against duplicate ``operation_id`` / ``summary`` values in -``@extend_schema`` decorators. +"""Guards against duplicate ``operation_id`` values across ``@extend_schema`` decorators. Two operations sharing an ``operation_id`` produce an invalid OpenAPI document and -duplicate SDK function names; two sharing a ``summary`` collide on the same docs URL -(the API reference derives the page slug from ``summary``). Both are easy to introduce -by accident — especially PUT/PATCH pairs on the same endpoint — and the drf-spectacular -``--fail-on-warn`` build only sees PUBLIC operations, so it never catches a clash that -involves a non-public method. This test scans the source instead, so it covers every -``@extend_schema`` regardless of publish status. +duplicate SDK function names. drf-spectacular's ``--fail-on-warn`` build only sees PUBLIC +operations, so it never catches a clash that involves a non-public method (e.g. a PUT/PATCH +pair on the same endpoint where only one is public). This test scans the source instead, so +it covers every ``@extend_schema`` regardless of publish status. + +(Summary uniqueness — which guards against docs-URL collisions — is enforced separately in +``custom_postprocessing_hook``, where each operation's real summary is directly available.) """ from __future__ import annotations -import ast import os +import re from collections import defaultdict SENTRY_SRC = os.path.join(os.path.dirname(__file__), "..", "..", "src", "sentry") +# operation_id="..." appears only as an @extend_schema kwarg, so a literal scan is safe. +# Match either quote style so apostrophes inside double-quoted, sentence-style values +# (e.g. "List a Project's Tags") aren't truncated. +_OPERATION_ID = re.compile(r"""operation_id=(?:"([^"]*)"|'([^']*)')""") + -def _iter_extend_schema_kwargs() -> list[tuple[str, str, str]]: - """Yield (kwarg_name, value, "path:line") for every string-literal ``operation_id`` - and ``summary`` kwarg passed to an ``extend_schema(...)`` call under src/sentry.""" - found: list[tuple[str, str, str]] = [] +def test_operation_ids_are_unique() -> None: + locations: dict[str, list[str]] = defaultdict(list) for root, _dirs, files in os.walk(SENTRY_SRC): for name in files: if not name.endswith(".py"): continue path = os.path.join(root, name) - try: - with open(path, encoding="utf-8") as f: - tree = ast.parse(f.read(), filename=path) - except (SyntaxError, UnicodeDecodeError): - continue - for node in ast.walk(tree): - if not isinstance(node, ast.Call): - continue - func = node.func - fname = func.attr if isinstance(func, ast.Attribute) else getattr(func, "id", None) - if fname != "extend_schema": - continue - for kw in node.keywords: - if ( - kw.arg in ("operation_id", "summary") - and isinstance(kw.value, ast.Constant) - and isinstance(kw.value.value, str) - ): + with open(path, encoding="utf-8") as f: + for lineno, line in enumerate(f, start=1): + for double, single in _OPERATION_ID.findall(line): + value = double or single rel = os.path.relpath(path, SENTRY_SRC) - found.append((kw.arg, kw.value.value, f"src/sentry/{rel}:{kw.lineno}")) - return found - - -def _duplicates(kwarg: str) -> dict[str, list[str]]: - locations: dict[str, list[str]] = defaultdict(list) - for name, value, loc in _iter_extend_schema_kwargs(): - if name == kwarg: - locations[value].append(loc) - return {value: locs for value, locs in locations.items() if len(locs) > 1} - + locations[value].append(f"src/sentry/{rel}:{lineno}") -def test_operation_ids_are_unique() -> None: - dups = _duplicates("operation_id") + dups = {value: locs for value, locs in locations.items() if len(locs) > 1} assert not dups, "Duplicate @extend_schema operation_id values:\n" + "\n".join( f" {value!r}: {', '.join(locs)}" for value, locs in sorted(dups.items()) ) - - -def test_summaries_are_unique() -> None: - # The docs page slug is slugify(summary); duplicates collide on the same URL. - dups = _duplicates("summary") - assert not dups, "Duplicate @extend_schema summary values (docs URL collision):\n" + "\n".join( - f" {value!r}: {', '.join(locs)}" for value, locs in sorted(dups.items()) - ) From 57e8fb4fa16c6d867768660553c47bef19201015 Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 16 Jun 2026 15:42:07 +0200 Subject: [PATCH 5/7] fix(api): add type args to test helper return annotation --- tests/apidocs/test_hooks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/apidocs/test_hooks.py b/tests/apidocs/test_hooks.py index e624ca7eddf2ba..18deb0eb836211 100644 --- a/tests/apidocs/test_hooks.py +++ b/tests/apidocs/test_hooks.py @@ -1,3 +1,4 @@ +from typing import Any from unittest import TestCase import pytest @@ -53,7 +54,7 @@ class SummaryUniquenessTest(TestCase): def setUp(self) -> None: _ENDPOINT_SERVERS.clear() - def _op(self, summary: str) -> dict: + def _op(self, summary: str) -> dict[str, Any]: return { "tags": ["Events"], "description": "An endpoint", From 34208fd3fd2838576e1f57f2653f697e20657c1d Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 16 Jun 2026 16:31:04 +0200 Subject: [PATCH 6/7] ref(api): align apidocs guard tests with repo conventions - test_hooks.py: use module-level pytest functions for the summary checks (per tests/AGENTS.md 'use pytest, not unittest'); drop the dead setUp. - test_operation_id_uniqueness.py: scan via pathlib, matching the canonical source-scan precedent (tests/sentry/test_no_create_or_update_usage.py). --- tests/apidocs/test_hooks.py | 62 +++++++++---------- tests/apidocs/test_operation_id_uniqueness.py | 22 +++---- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/tests/apidocs/test_hooks.py b/tests/apidocs/test_hooks.py index 18deb0eb836211..118bad4c59c092 100644 --- a/tests/apidocs/test_hooks.py +++ b/tests/apidocs/test_hooks.py @@ -50,40 +50,38 @@ def test_servers_applied_to_endpoint(self) -> None: assert "servers" not in processed["paths"]["/api/0/other/endpoint/"]["get"] -class SummaryUniquenessTest(TestCase): - def setUp(self) -> None: - _ENDPOINT_SERVERS.clear() - - def _op(self, summary: str) -> dict[str, Any]: - return { - "tags": ["Events"], - "description": "An endpoint", - "operationId": summary.lower().replace(" ", "-"), - "summary": summary, - "parameters": [], - } +def _operation(summary: str) -> dict[str, Any]: + return { + "tags": ["Events"], + "description": "An endpoint", + "operationId": summary.lower().replace(" ", "-"), + "summary": summary, + "parameters": [], + } + + +def test_duplicate_summary_raises() -> None: + result = { + "components": {"schemas": {}}, + "paths": { + "/api/0/foo/": {"get": _operation("List Foos")}, + "/api/0/bar/": {"get": _operation("List Foos")}, + }, + } + with pytest.raises(SentryApiBuildError): + custom_postprocessing_hook(result, None) - def test_duplicate_summary_raises(self) -> None: - result = { - "components": {"schemas": {}}, - "paths": { - "/api/0/foo/": {"get": self._op("List Foos")}, - "/api/0/bar/": {"get": self._op("List Foos")}, - }, - } - with pytest.raises(SentryApiBuildError): - custom_postprocessing_hook(result, None) - def test_unique_summaries_pass(self) -> None: - result = { - "components": {"schemas": {}}, - "paths": { - "/api/0/foo/": {"get": self._op("List Foos")}, - "/api/0/bar/": {"get": self._op("List Bars")}, - }, - } - # Should not raise. - custom_postprocessing_hook(result, None) +def test_unique_summaries_pass() -> None: + result = { + "components": {"schemas": {}}, + "paths": { + "/api/0/foo/": {"get": _operation("List Foos")}, + "/api/0/bar/": {"get": _operation("List Bars")}, + }, + } + # Should not raise. + custom_postprocessing_hook(result, None) class FixIssueRoutesTest(TestCase): diff --git a/tests/apidocs/test_operation_id_uniqueness.py b/tests/apidocs/test_operation_id_uniqueness.py index 722e2f0d7afa2b..02953ffac27d0c 100644 --- a/tests/apidocs/test_operation_id_uniqueness.py +++ b/tests/apidocs/test_operation_id_uniqueness.py @@ -12,11 +12,11 @@ from __future__ import annotations -import os import re from collections import defaultdict +from pathlib import Path -SENTRY_SRC = os.path.join(os.path.dirname(__file__), "..", "..", "src", "sentry") +SENTRY_SRC = Path(__file__).resolve().parents[2] / "src" / "sentry" # operation_id="..." appears only as an @extend_schema kwarg, so a literal scan is safe. # Match either quote style so apostrophes inside double-quoted, sentence-style values @@ -26,17 +26,13 @@ def test_operation_ids_are_unique() -> None: locations: dict[str, list[str]] = defaultdict(list) - for root, _dirs, files in os.walk(SENTRY_SRC): - for name in files: - if not name.endswith(".py"): - continue - path = os.path.join(root, name) - with open(path, encoding="utf-8") as f: - for lineno, line in enumerate(f, start=1): - for double, single in _OPERATION_ID.findall(line): - value = double or single - rel = os.path.relpath(path, SENTRY_SRC) - locations[value].append(f"src/sentry/{rel}:{lineno}") + for path in SENTRY_SRC.rglob("*.py"): + with path.open(encoding="utf-8") as f: + for lineno, line in enumerate(f, start=1): + for double, single in _OPERATION_ID.findall(line): + value = double or single + rel = path.relative_to(SENTRY_SRC) + locations[value].append(f"src/sentry/{rel}:{lineno}") dups = {value: locs for value, locs in locations.items() if len(locs) > 1} assert not dups, "Duplicate @extend_schema operation_id values:\n" + "\n".join( From a1d5ae62753f174d3c788737cac934df113d6fcc Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 16 Jun 2026 17:09:20 +0200 Subject: [PATCH 7/7] ref(api): group summary uniqueness tests in a class Match the surrounding test_hooks.py structure (EndpointServersTest, FixIssueRoutesTest) instead of bare module-level functions. Body keeps pytest.raises + plain asserts like its siblings. --- tests/apidocs/test_hooks.py | 59 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/tests/apidocs/test_hooks.py b/tests/apidocs/test_hooks.py index 118bad4c59c092..93c1afa7dee81a 100644 --- a/tests/apidocs/test_hooks.py +++ b/tests/apidocs/test_hooks.py @@ -50,38 +50,37 @@ def test_servers_applied_to_endpoint(self) -> None: assert "servers" not in processed["paths"]["/api/0/other/endpoint/"]["get"] -def _operation(summary: str) -> dict[str, Any]: - return { - "tags": ["Events"], - "description": "An endpoint", - "operationId": summary.lower().replace(" ", "-"), - "summary": summary, - "parameters": [], - } - - -def test_duplicate_summary_raises() -> None: - result = { - "components": {"schemas": {}}, - "paths": { - "/api/0/foo/": {"get": _operation("List Foos")}, - "/api/0/bar/": {"get": _operation("List Foos")}, - }, - } - with pytest.raises(SentryApiBuildError): - custom_postprocessing_hook(result, None) +class SummaryUniquenessTest(TestCase): + def _operation(self, summary: str) -> dict[str, Any]: + return { + "tags": ["Events"], + "description": "An endpoint", + "operationId": summary.lower().replace(" ", "-"), + "summary": summary, + "parameters": [], + } + def test_duplicate_summary_raises(self) -> None: + result = { + "components": {"schemas": {}}, + "paths": { + "/api/0/foo/": {"get": self._operation("List Foos")}, + "/api/0/bar/": {"get": self._operation("List Foos")}, + }, + } + with pytest.raises(SentryApiBuildError): + custom_postprocessing_hook(result, None) -def test_unique_summaries_pass() -> None: - result = { - "components": {"schemas": {}}, - "paths": { - "/api/0/foo/": {"get": _operation("List Foos")}, - "/api/0/bar/": {"get": _operation("List Bars")}, - }, - } - # Should not raise. - custom_postprocessing_hook(result, None) + def test_unique_summaries_pass(self) -> None: + result = { + "components": {"schemas": {}}, + "paths": { + "/api/0/foo/": {"get": self._operation("List Foos")}, + "/api/0/bar/": {"get": self._operation("List Bars")}, + }, + } + # Should not raise. + custom_postprocessing_hook(result, None) class FixIssueRoutesTest(TestCase):