Skip to content

Commit 76a7e5d

Browse files
feat(dashboards): Adds public api docs for post and get /dashboards (#74404)
Documents the dashboards endpoint and makes GET and POST methods public API.
1 parent eb193e0 commit 76a7e5d

File tree

3 files changed

+137
-20
lines changed

3 files changed

+137
-20
lines changed

src/sentry/api/endpoints/organization_dashboards.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from django.db import IntegrityError, router, transaction
66
from django.db.models import Case, IntegerField, When
7+
from drf_spectacular.utils import extend_schema
78
from rest_framework.request import Request
89
from rest_framework.response import Response
910

@@ -14,8 +15,21 @@
1415
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
1516
from sentry.api.paginator import ChainPaginator
1617
from sentry.api.serializers import serialize
17-
from sentry.api.serializers.models.dashboard import DashboardListSerializer
18+
from sentry.api.serializers.models.dashboard import (
19+
DashboardDetailsModelSerializer,
20+
DashboardListResponse,
21+
DashboardListSerializer,
22+
)
1823
from sentry.api.serializers.rest_framework import DashboardSerializer
24+
from sentry.apidocs.constants import (
25+
RESPONSE_BAD_REQUEST,
26+
RESPONSE_CONFLICT,
27+
RESPONSE_FORBIDDEN,
28+
RESPONSE_NOT_FOUND,
29+
)
30+
from sentry.apidocs.examples.dashboard_examples import DashboardExamples
31+
from sentry.apidocs.parameters import CursorQueryParam, GlobalParams, VisibilityParams
32+
from sentry.apidocs.utils import inline_sentry_response_serializer
1933
from sentry.models.dashboard import Dashboard
2034
from sentry.models.organization import Organization
2135

@@ -43,28 +57,33 @@ def has_object_permission(self, request: Request, view, obj):
4357
return True
4458

4559

60+
@extend_schema(tags=["Dashboards"])
4661
@region_silo_endpoint
4762
class OrganizationDashboardsEndpoint(OrganizationEndpoint):
4863
publish_status = {
49-
"GET": ApiPublishStatus.UNKNOWN,
50-
"POST": ApiPublishStatus.UNKNOWN,
64+
"GET": ApiPublishStatus.PUBLIC,
65+
"POST": ApiPublishStatus.PUBLIC,
5166
}
5267
owner = ApiOwner.PERFORMANCE
5368
permission_classes = (OrganizationDashboardsPermission,)
5469

70+
@extend_schema(
71+
operation_id="Retrieve an Organization's Custom Dashboards",
72+
parameters=[GlobalParams.ORG_ID_OR_SLUG, VisibilityParams.PER_PAGE, CursorQueryParam],
73+
request=None,
74+
responses={
75+
200: inline_sentry_response_serializer(
76+
"DashboardListResponse", list[DashboardListResponse]
77+
),
78+
400: RESPONSE_BAD_REQUEST,
79+
403: RESPONSE_FORBIDDEN,
80+
404: RESPONSE_NOT_FOUND,
81+
},
82+
examples=DashboardExamples.DASHBOARDS_QUERY_RESPONSE,
83+
)
5584
def get(self, request: Request, organization) -> Response:
5685
"""
57-
Retrieve an Organization's Dashboards
58-
`````````````````````````````````````
59-
60-
Retrieve a list of dashboards that are associated with the given organization.
61-
If on the first page, this endpoint will also include any pre-built dashboards
62-
that haven't been replaced or removed.
63-
64-
:pparam string organization_id_or_slug: the id or slug of the organization the
65-
dashboards belongs to.
66-
:qparam string query: the title of the dashboard being searched for.
67-
:auth: required
86+
Retrieve a list of custom dashboards that are associated with the given organization.
6887
"""
6988
if not features.has("organizations:dashboards-basic", organization, actor=request.user):
7089
return Response(status=404)
@@ -148,14 +167,22 @@ def handle_results(results):
148167
on_results=handle_results,
149168
)
150169

170+
@extend_schema(
171+
operation_id="Create a New Dashboard for an Organization",
172+
parameters=[GlobalParams.ORG_ID_OR_SLUG],
173+
request=DashboardSerializer,
174+
responses={
175+
201: DashboardDetailsModelSerializer,
176+
400: RESPONSE_BAD_REQUEST,
177+
403: RESPONSE_FORBIDDEN,
178+
404: RESPONSE_NOT_FOUND,
179+
409: RESPONSE_CONFLICT,
180+
},
181+
examples=DashboardExamples.DASHBOARD_POST_RESPONSE,
182+
)
151183
def post(self, request: Request, organization, retry=0) -> Response:
152184
"""
153-
Create a New Dashboard for an Organization
154-
``````````````````````````````````````````
155-
156185
Create a new dashboard for the given Organization
157-
:pparam string organization_id_or_slug: the id or slug of the organization the
158-
dashboards belongs to.
159186
"""
160187
if not features.has("organizations:dashboards-edit", organization, actor=request.user):
161188
return Response(status=404)

src/sentry/api/serializers/models/dashboard.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ def serialize(self, obj, attrs, user, **kwargs) -> DashboardWidgetQueryResponse:
167167
}
168168

169169

170+
class DashboardListResponse(TypedDict):
171+
id: str
172+
title: str
173+
dateCreated: str
174+
createdBy: UserSerializerResponse
175+
widgetDisplay: list[str]
176+
widgetPreview: list[dict[str, str]]
177+
178+
170179
class DashboardListSerializer(Serializer):
171180
def get_attrs(self, item_list, user, **kwargs):
172181
item_dict = {i.id: i for i in item_list}
@@ -215,7 +224,7 @@ def get_attrs(self, item_list, user, **kwargs):
215224

216225
return result
217226

218-
def serialize(self, obj, attrs, user, **kwargs):
227+
def serialize(self, obj, attrs, user, **kwargs) -> DashboardListResponse:
219228
data = {
220229
"id": str(obj.id),
221230
"title": obj.title,

src/sentry/apidocs/examples/dashboard_examples.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,69 @@
6868
"period": "7d",
6969
}
7070

71+
DASHBOARDS_OBJECT = [
72+
{
73+
"id": "1",
74+
"title": "Dashboard",
75+
"dateCreated": "2024-06-20T14:38:03.498574Z",
76+
"createdBy": {
77+
"id": "1",
78+
"name": "Admin",
79+
"username": "admin",
80+
"email": "admin@sentry.io",
81+
"avatarUrl": "www.example.com",
82+
"isActive": True,
83+
"hasPasswordAuth": True,
84+
"isManaged": False,
85+
"dateJoined": "2021-10-25T17:07:33.190596Z",
86+
"lastLogin": "2024-07-16T15:28:39.261659Z",
87+
"has2fa": True,
88+
"lastActive": "2024-07-16T20:45:49.364197Z",
89+
"isSuperuser": False,
90+
"isStaff": False,
91+
"experiments": {},
92+
"emails": [{"id": "1", "email": "admin@sentry.io", "is_verified": True}],
93+
"avatar": {
94+
"avatarType": "letter_avatar",
95+
"avatarUuid": None,
96+
"avatarUrl": "www.example.com",
97+
},
98+
},
99+
"widgetDisplay": [],
100+
"widgetPreview": [],
101+
},
102+
{
103+
"id": "2",
104+
"title": "Dashboard",
105+
"dateCreated": "2024-06-20T14:38:03.498574Z",
106+
"createdBy": {
107+
"id": "1",
108+
"name": "Admin",
109+
"username": "admin",
110+
"email": "admin@sentry.io",
111+
"avatarUrl": "www.example.com",
112+
"isActive": True,
113+
"hasPasswordAuth": True,
114+
"isManaged": False,
115+
"dateJoined": "2021-10-25T17:07:33.190596Z",
116+
"lastLogin": "2024-07-16T15:28:39.261659Z",
117+
"has2fa": True,
118+
"lastActive": "2024-07-16T20:45:49.364197Z",
119+
"isSuperuser": False,
120+
"isStaff": False,
121+
"experiments": {},
122+
"emails": [{"id": "1", "email": "admin@sentry.io", "is_verified": True}],
123+
"avatar": {
124+
"avatarType": "letter_avatar",
125+
"avatarUuid": None,
126+
"avatarUrl": "www.example.com",
127+
},
128+
},
129+
"widgetDisplay": [],
130+
"widgetPreview": [],
131+
},
132+
]
133+
71134

72135
class DashboardExamples:
73136
DASHBOARD_GET_RESPONSE = [
@@ -87,3 +150,21 @@ class DashboardExamples:
87150
response_only=True,
88151
)
89152
]
153+
154+
DASHBOARD_POST_RESPONSE = [
155+
OpenApiExample(
156+
"Create Dashboard",
157+
value=DASHBOARD_OBJECT,
158+
status_codes=["201"],
159+
response_only=True,
160+
)
161+
]
162+
163+
DASHBOARDS_QUERY_RESPONSE = [
164+
OpenApiExample(
165+
"Query Dashboards",
166+
value=DASHBOARDS_OBJECT,
167+
status_codes=["200"],
168+
response_only=True,
169+
)
170+
]

0 commit comments

Comments
 (0)