diff --git a/src/sentry/api/endpoints/organization_dashboards.py b/src/sentry/api/endpoints/organization_dashboards.py index baad3e061689a4..4e36754de09fb3 100644 --- a/src/sentry/api/endpoints/organization_dashboards.py +++ b/src/sentry/api/endpoints/organization_dashboards.py @@ -4,6 +4,7 @@ from django.db import IntegrityError, router, transaction from django.db.models import Case, IntegerField, When +from drf_spectacular.utils import extend_schema from rest_framework.request import Request from rest_framework.response import Response @@ -14,8 +15,21 @@ from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission from sentry.api.paginator import ChainPaginator from sentry.api.serializers import serialize -from sentry.api.serializers.models.dashboard import DashboardListSerializer +from sentry.api.serializers.models.dashboard import ( + DashboardDetailsModelSerializer, + DashboardListResponse, + DashboardListSerializer, +) from sentry.api.serializers.rest_framework import DashboardSerializer +from sentry.apidocs.constants import ( + RESPONSE_BAD_REQUEST, + RESPONSE_CONFLICT, + RESPONSE_FORBIDDEN, + RESPONSE_NOT_FOUND, +) +from sentry.apidocs.examples.dashboard_examples import DashboardExamples +from sentry.apidocs.parameters import CursorQueryParam, GlobalParams, VisibilityParams +from sentry.apidocs.utils import inline_sentry_response_serializer from sentry.models.dashboard import Dashboard from sentry.models.organization import Organization @@ -43,28 +57,33 @@ def has_object_permission(self, request: Request, view, obj): return True +@extend_schema(tags=["Dashboards"]) @region_silo_endpoint class OrganizationDashboardsEndpoint(OrganizationEndpoint): publish_status = { - "GET": ApiPublishStatus.UNKNOWN, - "POST": ApiPublishStatus.UNKNOWN, + "GET": ApiPublishStatus.PUBLIC, + "POST": ApiPublishStatus.PUBLIC, } owner = ApiOwner.PERFORMANCE permission_classes = (OrganizationDashboardsPermission,) + @extend_schema( + operation_id="Retrieve an Organization's Custom Dashboards", + parameters=[GlobalParams.ORG_ID_OR_SLUG, VisibilityParams.PER_PAGE, CursorQueryParam], + request=None, + responses={ + 200: inline_sentry_response_serializer( + "DashboardListResponse", list[DashboardListResponse] + ), + 400: RESPONSE_BAD_REQUEST, + 403: RESPONSE_FORBIDDEN, + 404: RESPONSE_NOT_FOUND, + }, + examples=DashboardExamples.DASHBOARDS_QUERY_RESPONSE, + ) def get(self, request: Request, organization) -> Response: """ - Retrieve an Organization's Dashboards - ````````````````````````````````````` - - Retrieve a list of dashboards that are associated with the given organization. - If on the first page, this endpoint will also include any pre-built dashboards - that haven't been replaced or removed. - - :pparam string organization_id_or_slug: the id or slug of the organization the - dashboards belongs to. - :qparam string query: the title of the dashboard being searched for. - :auth: required + Retrieve a list of custom dashboards that are associated with the given organization. """ if not features.has("organizations:dashboards-basic", organization, actor=request.user): return Response(status=404) @@ -148,14 +167,22 @@ def handle_results(results): on_results=handle_results, ) + @extend_schema( + operation_id="Create a New Dashboard for an Organization", + parameters=[GlobalParams.ORG_ID_OR_SLUG], + request=DashboardSerializer, + responses={ + 201: DashboardDetailsModelSerializer, + 400: RESPONSE_BAD_REQUEST, + 403: RESPONSE_FORBIDDEN, + 404: RESPONSE_NOT_FOUND, + 409: RESPONSE_CONFLICT, + }, + examples=DashboardExamples.DASHBOARD_POST_RESPONSE, + ) def post(self, request: Request, organization, retry=0) -> Response: """ - Create a New Dashboard for an Organization - `````````````````````````````````````````` - Create a new dashboard for the given Organization - :pparam string organization_id_or_slug: the id or slug of the organization the - dashboards belongs to. """ if not features.has("organizations:dashboards-edit", organization, actor=request.user): return Response(status=404) diff --git a/src/sentry/api/serializers/models/dashboard.py b/src/sentry/api/serializers/models/dashboard.py index f3f26123b32f7f..7e1458a550d12c 100644 --- a/src/sentry/api/serializers/models/dashboard.py +++ b/src/sentry/api/serializers/models/dashboard.py @@ -167,6 +167,15 @@ def serialize(self, obj, attrs, user, **kwargs) -> DashboardWidgetQueryResponse: } +class DashboardListResponse(TypedDict): + id: str + title: str + dateCreated: str + createdBy: UserSerializerResponse + widgetDisplay: list[str] + widgetPreview: list[dict[str, str]] + + class DashboardListSerializer(Serializer): def get_attrs(self, item_list, user, **kwargs): item_dict = {i.id: i for i in item_list} @@ -215,7 +224,7 @@ def get_attrs(self, item_list, user, **kwargs): return result - def serialize(self, obj, attrs, user, **kwargs): + def serialize(self, obj, attrs, user, **kwargs) -> DashboardListResponse: data = { "id": str(obj.id), "title": obj.title, diff --git a/src/sentry/apidocs/examples/dashboard_examples.py b/src/sentry/apidocs/examples/dashboard_examples.py index 169b3b8320e1e4..07d3d21c790ae1 100644 --- a/src/sentry/apidocs/examples/dashboard_examples.py +++ b/src/sentry/apidocs/examples/dashboard_examples.py @@ -68,6 +68,69 @@ "period": "7d", } +DASHBOARDS_OBJECT = [ + { + "id": "1", + "title": "Dashboard", + "dateCreated": "2024-06-20T14:38:03.498574Z", + "createdBy": { + "id": "1", + "name": "Admin", + "username": "admin", + "email": "admin@sentry.io", + "avatarUrl": "www.example.com", + "isActive": True, + "hasPasswordAuth": True, + "isManaged": False, + "dateJoined": "2021-10-25T17:07:33.190596Z", + "lastLogin": "2024-07-16T15:28:39.261659Z", + "has2fa": True, + "lastActive": "2024-07-16T20:45:49.364197Z", + "isSuperuser": False, + "isStaff": False, + "experiments": {}, + "emails": [{"id": "1", "email": "admin@sentry.io", "is_verified": True}], + "avatar": { + "avatarType": "letter_avatar", + "avatarUuid": None, + "avatarUrl": "www.example.com", + }, + }, + "widgetDisplay": [], + "widgetPreview": [], + }, + { + "id": "2", + "title": "Dashboard", + "dateCreated": "2024-06-20T14:38:03.498574Z", + "createdBy": { + "id": "1", + "name": "Admin", + "username": "admin", + "email": "admin@sentry.io", + "avatarUrl": "www.example.com", + "isActive": True, + "hasPasswordAuth": True, + "isManaged": False, + "dateJoined": "2021-10-25T17:07:33.190596Z", + "lastLogin": "2024-07-16T15:28:39.261659Z", + "has2fa": True, + "lastActive": "2024-07-16T20:45:49.364197Z", + "isSuperuser": False, + "isStaff": False, + "experiments": {}, + "emails": [{"id": "1", "email": "admin@sentry.io", "is_verified": True}], + "avatar": { + "avatarType": "letter_avatar", + "avatarUuid": None, + "avatarUrl": "www.example.com", + }, + }, + "widgetDisplay": [], + "widgetPreview": [], + }, +] + class DashboardExamples: DASHBOARD_GET_RESPONSE = [ @@ -87,3 +150,21 @@ class DashboardExamples: response_only=True, ) ] + + DASHBOARD_POST_RESPONSE = [ + OpenApiExample( + "Create Dashboard", + value=DASHBOARD_OBJECT, + status_codes=["201"], + response_only=True, + ) + ] + + DASHBOARDS_QUERY_RESPONSE = [ + OpenApiExample( + "Query Dashboards", + value=DASHBOARDS_OBJECT, + status_codes=["200"], + response_only=True, + ) + ]