Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions src/sentry/api/endpoints/organization_dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out a way to extend DashboardListSerializer to a list, so I created DashboardListResponse instead, and also typed DashboardListSerializer to use DashboardListResponse.

"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)
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 10 additions & 1 deletion src/sentry/api/serializers/models/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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,
Expand Down
81 changes: 81 additions & 0 deletions src/sentry/apidocs/examples/dashboard_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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,
)
]