diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 030a622cc9fb30..6525680e05a34d 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -93,9 +93,6 @@ "/api/0/teams/{organization_slug}/{team_slug}/": { "$ref": "paths/teams/by-slug.json" }, - "/api/0/teams/{organization_slug}/{team_slug}/projects/": { - "$ref": "paths/teams/projects.json" - }, "/api/0/teams/{organization_slug}/{team_slug}/stats/": { "$ref": "paths/teams/stats.json" }, diff --git a/api-docs/paths/teams/projects.json b/api-docs/paths/teams/projects.json deleted file mode 100644 index 952fe5e4d4a838..00000000000000 --- a/api-docs/paths/teams/projects.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "get": { - "tags": ["Teams"], - "description": "Return a list of projects bound to a team.", - "operationId": "List a Team's Projects", - "parameters": [ - { - "name": "organization_slug", - "in": "path", - "description": "The slug of the organization the team belongs to.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "team_slug", - "in": "path", - "description": "The slug of the team to get.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "$ref": "../../components/parameters/pagination-cursor.json#/PaginationCursor" - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "../../components/schemas/project.json#/TeamProjects" - } - }, - "example": [ - { - "slug": "the-spoiled-yoghurt", - "name": "The Spoiled Yoghurt", - "hasAccess": true, - "teams": [ - { - "id": "542609", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - } - ], - "platform": null, - "firstEvent": null, - "isMember": false, - "team": { - "id": "542609", - "name": "Powerful Abolitionist", - "slug": "powerful-abolitionist" - }, - "dateCreated": "2020-08-20T14:36:34.171255Z", - "isBookmarked": false, - "id": "5398494", - "latestDeploys": null, - "features": [ - "custom-inbound-filters", - "discard-groups", - "rate-limits", - "data-forwarding", - "similarity-view", - "issue-alerts-targeting", - "servicehooks", - "minidump", - "similarity-indexing" - ] - } - ] - } - } - }, - "403": { - "description": "Forbidden" - }, - "404": { - "description": "Team not found" - } - }, - "security": [ - { - "auth_token": ["project:read"] - } - ] - } -} diff --git a/src/sentry/api/endpoints/team_projects.py b/src/sentry/api/endpoints/team_projects.py index 3e5e1b48755547..7cbf753dc98782 100644 --- a/src/sentry/api/endpoints/team_projects.py +++ b/src/sentry/api/endpoints/team_projects.py @@ -1,3 +1,5 @@ +from typing import List + from django.db import IntegrityError, transaction from drf_spectacular.utils import OpenApiResponse, extend_schema from rest_framework import serializers, status @@ -9,12 +11,15 @@ from sentry.api.bases.team import TeamEndpoint, TeamPermission from sentry.api.paginator import OffsetPaginator from sentry.api.serializers import ProjectSummarySerializer, serialize +from sentry.api.serializers.models.project import OrganizationProjectResponse from sentry.api.serializers.models.project import ( ProjectSerializer as SentryProjectResponseSerializer, ) from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_FORBIDDEN from sentry.apidocs.examples.project_examples import ProjectExamples -from sentry.apidocs.parameters import GLOBAL_PARAMS, PROJECT_PARAMS +from sentry.apidocs.examples.team_examples import TeamExamples +from sentry.apidocs.parameters import CURSOR_QUERY_PARAM, GLOBAL_PARAMS, PROJECT_PARAMS +from sentry.apidocs.utils import inline_sentry_response_serializer from sentry.constants import ObjectStatus from sentry.models import Project from sentry.signals import project_created @@ -52,22 +57,32 @@ class TeamProjectPermission(TeamPermission): } +@extend_schema(tags=["Teams"]) @region_silo_endpoint class TeamProjectsEndpoint(TeamEndpoint, EnvironmentMixin): - public = {"POST"} + public = {"GET", "POST"} permission_classes = (TeamProjectPermission,) + @extend_schema( + operation_id="List a Team's Projects", + parameters=[ + GLOBAL_PARAMS.ORG_SLUG, + GLOBAL_PARAMS.TEAM_SLUG, + CURSOR_QUERY_PARAM, + ], + request=None, + responses={ + 200: inline_sentry_response_serializer( + "ListTeamProjectResponse", List[OrganizationProjectResponse] + ), + 403: RESPONSE_FORBIDDEN, + 404: OpenApiResponse(description="Team not found."), + }, + examples=TeamExamples.LIST_TEAM_PROJECTS, + ) def get(self, request: Request, team) -> Response: """ - List a Team's Projects - `````````````````````` - Return a list of projects bound to a team. - - :pparam string organization_slug: the slug of the organization the - team belongs to. - :pparam string team_slug: the slug of the team to list the projects of. - :auth: required """ if request.auth and hasattr(request.auth, "project"): queryset = Project.objects.filter(id=request.auth.project.id) diff --git a/src/sentry/api/serializers/models/project.py b/src/sentry/api/serializers/models/project.py index 6738031badb2b8..1d06f0889c2d4a 100644 --- a/src/sentry/api/serializers/models/project.py +++ b/src/sentry/api/serializers/models/project.py @@ -231,6 +231,7 @@ class ProjectSerializerBaseResponse(_ProjectSerializerOptionalBaseResponse): firstTransactionEvent: bool access: List[str] hasAccess: bool + hasMinifiedStackTrace: bool hasMonitors: bool hasProfiles: bool hasReplays: bool diff --git a/src/sentry/apidocs/examples/discover_performance_examples.py b/src/sentry/apidocs/examples/discover_performance_examples.py index 98c410a61b7113..790cad4e0fe246 100644 --- a/src/sentry/apidocs/examples/discover_performance_examples.py +++ b/src/sentry/apidocs/examples/discover_performance_examples.py @@ -35,5 +35,7 @@ class DiscoverAndPerformanceExamples: }, }, }, + status_codes=["200"], + response_only=True, ) ] diff --git a/src/sentry/apidocs/examples/issue_alert_examples.py b/src/sentry/apidocs/examples/issue_alert_examples.py index 650cfdd915f2ce..6b9fe0494421f4 100644 --- a/src/sentry/apidocs/examples/issue_alert_examples.py +++ b/src/sentry/apidocs/examples/issue_alert_examples.py @@ -7,5 +7,6 @@ class IssueAlertExamples: "Successful response", value={}, status_codes=["200"], + response_only=True, ) ] diff --git a/src/sentry/apidocs/examples/organization_examples.py b/src/sentry/apidocs/examples/organization_examples.py index 920e54ac77b649..13c026aa987f30 100644 --- a/src/sentry/apidocs/examples/organization_examples.py +++ b/src/sentry/apidocs/examples/organization_examples.py @@ -37,11 +37,14 @@ class OrganizationExamples: "hasSessions": True, "hasProfiles": True, "hasReplays": True, + "hasMinifiedStackTrace": False, "hasMonitors": True, "hasUserReports": False, "latestRelease": None, } ], + status_codes=["200"], + response_only=True, ) ] @@ -61,5 +64,6 @@ class OrganizationExamples: ], }, status_codes=["200"], + response_only=True, ), ] diff --git a/src/sentry/apidocs/examples/project_examples.py b/src/sentry/apidocs/examples/project_examples.py index f478993f4f59f9..55c41c164b2134 100644 --- a/src/sentry/apidocs/examples/project_examples.py +++ b/src/sentry/apidocs/examples/project_examples.py @@ -5,7 +5,7 @@ class ProjectExamples: CREATE_PROJECT = [ OpenApiExample( "Project successfully created", - { + value={ "id": "4505321021243392", "slug": "the-spoiled-yoghurt", "name": "The Spoiled Yoghurt", @@ -59,5 +59,6 @@ class ProjectExamples: "status": "active", }, status_codes=["201"], + response_only=True, ), ] diff --git a/src/sentry/apidocs/examples/scim_examples.py b/src/sentry/apidocs/examples/scim_examples.py index 19baf8c05a8c5d..78116e1babe5f9 100644 --- a/src/sentry/apidocs/examples/scim_examples.py +++ b/src/sentry/apidocs/examples/scim_examples.py @@ -26,6 +26,7 @@ class SCIMExamples: ], }, status_codes=["200"], + response_only=True, ), ] @@ -48,6 +49,7 @@ class SCIMExamples: ], }, status_codes=["200"], + response_only=True, ), ] @@ -94,6 +96,8 @@ class SCIMExamples: "members": [], "meta": {"resourceType": "Group"}, }, + status_codes=["200"], + response_only=True, ), ] @@ -111,6 +115,7 @@ class SCIMExamples: "sentryOrgRole": "member", }, status_codes=["200"], + response_only=True, ), ] @@ -122,6 +127,7 @@ class SCIMExamples: "Operations": [{"op": "replace", "value": {"active": False}}], }, status_codes=["204"], + response_only=True, ), ] diff --git a/src/sentry/apidocs/examples/team_examples.py b/src/sentry/apidocs/examples/team_examples.py new file mode 100644 index 00000000000000..cb8e519b96a3a3 --- /dev/null +++ b/src/sentry/apidocs/examples/team_examples.py @@ -0,0 +1,136 @@ +from drf_spectacular.utils import OpenApiExample + + +class TeamExamples: + LIST_TEAM_PROJECTS = [ + OpenApiExample( + "Get list of team's projects", + value=[ + { + "team": { + "id": "2349234102", + "name": "Prime Mover", + "slug": "prime-mover", + }, + "teams": [ + { + "id": "2349234102", + "name": "Prime Mover", + "slug": "prime-mover", + }, + { + "id": "47584447", + "name": "Powerful Abolitionist", + "slug": "powerful-abolitionist", + }, + ], + "id": "6758470122493650", + "name": "the-spoiled-yoghurt", + "slug": "The Spoiled Yoghurt", + "isBookmarked": False, + "isMember": True, + "access": [ + "project:read", + "event:read", + "team:read", + "alerts:read", + "org:read", + "event:write", + "project:releases", + "member:read", + ], + "hasAccess": True, + "dateCreated": "2023-03-29T15:25:21.344565Z", + "environments": ["production"], + "eventProcessing": {"symbolicationDegraded": False}, + "features": [ + "alert-filters", + "custom-inbound-filters", + "data-forwarding", + "discard-groups", + "minidump", + "race-free-group-creation", + "rate-limits", + "servicehooks", + "similarity-indexing", + "similarity-indexing-v2", + "similarity-view", + "similarity-view-v2", + ], + "firstEvent": None, + "firstTransactionEvent": True, + "hasSessions": False, + "hasProfiles": False, + "hasReplays": False, + "hasMonitors": False, + "hasMinifiedStackTrace": False, + "platform": "node-express", + "platforms": [], + "latestRelease": None, + "hasUserReports": False, + "latestDeploys": None, + }, + { + "team": { + "id": "2349234102", + "name": "Prime Mover", + "slug": "prime-mover", + }, + "teams": [ + { + "id": "2349234102", + "name": "Prime Mover", + "slug": "prime-mover", + } + ], + "id": "1829334501859481", + "name": "Pump Station", + "slug": "pump-station", + "isBookmarked": False, + "isMember": True, + "access": [ + "project:read", + "event:read", + "team:read", + "alerts:read", + "org:read", + "event:write", + "project:releases", + "member:read", + ], + "hasAccess": True, + "dateCreated": "2023-03-29T15:21:49.943746Z", + "environments": ["production"], + "eventProcessing": {"symbolicationDegraded": False}, + "features": [ + "alert-filters", + "custom-inbound-filters", + "data-forwarding", + "discard-groups", + "minidump", + "race-free-group-creation", + "rate-limits", + "servicehooks", + "similarity-indexing", + "similarity-indexing-v2", + "similarity-view", + "similarity-view-v2", + ], + "firstEvent": "2023-04-05T21:02:08.054000Z", + "firstTransactionEvent": False, + "hasSessions": False, + "hasProfiles": False, + "hasReplays": False, + "hasMonitors": False, + "hasMinifiedStackTrace": True, + "platform": "javascript", + "platforms": ["javascript"], + "latestRelease": None, + "hasUserReports": False, + "latestDeploys": None, + }, + ], + status_codes=["200"], + response_only=True, + ) + ] diff --git a/tests/apidocs/endpoints/events/test_project_events.py b/tests/apidocs/endpoints/events/test_project_events.py deleted file mode 100644 index 090990287247d8..00000000000000 --- a/tests/apidocs/endpoints/events/test_project_events.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.test.client import RequestFactory -from django.urls import reverse - -from fixtures.apidocs_test_case import APIDocsTestCase -from sentry.testutils.silo import region_silo_test - - -@region_silo_test -class ProjectEventsDocs(APIDocsTestCase): - endpoint = "sentry-api-0-project-events" - - def setUp(self): - self.create_event("a") - self.create_event("b") - - self.url = reverse( - self.endpoint, - kwargs={ - "organization_slug": self.organization.slug, - "project_slug": self.project.slug, - }, - ) - - self.login_as(user=self.user) - - def test_get(self): - response = self.client.get(self.url) - request = RequestFactory().get(self.url) - - self.validate_schema(request, response)