feat: add configurable view-aware dashboards
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal, get_api_principal
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_dashboard.backend.db.models import DashboardLayout
|
||||
from govoplan_dashboard.backend.schemas import (
|
||||
DashboardLayoutResponse,
|
||||
DashboardLayoutUpdateRequest,
|
||||
)
|
||||
from govoplan_dashboard.backend.service import (
|
||||
DashboardLayoutConflict,
|
||||
DashboardLayoutLimitExceeded,
|
||||
delete_dashboard_layout,
|
||||
get_dashboard_layout,
|
||||
normalize_view_id,
|
||||
save_dashboard_layout,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
|
||||
|
||||
|
||||
def _response(
|
||||
layout: DashboardLayout | None,
|
||||
*,
|
||||
view_id: str | None,
|
||||
) -> DashboardLayoutResponse:
|
||||
if layout is None:
|
||||
return DashboardLayoutResponse(
|
||||
exists=False,
|
||||
view_id=normalize_view_id(view_id),
|
||||
)
|
||||
return DashboardLayoutResponse(
|
||||
exists=True,
|
||||
view_id=layout.view_id,
|
||||
layout_version=layout.layout_version,
|
||||
revision=layout.revision,
|
||||
placements=layout.placements,
|
||||
known_widget_ids=layout.known_widget_ids,
|
||||
updated_at=layout.updated_at,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/layout", response_model=DashboardLayoutResponse)
|
||||
def api_get_dashboard_layout(
|
||||
view_id: str | None = Query(default=None, max_length=255),
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> DashboardLayoutResponse:
|
||||
return _response(
|
||||
get_dashboard_layout(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
account_id=principal.account_id,
|
||||
view_id=view_id,
|
||||
),
|
||||
view_id=view_id,
|
||||
)
|
||||
|
||||
|
||||
@router.put("/layout", response_model=DashboardLayoutResponse)
|
||||
def api_save_dashboard_layout(
|
||||
payload: DashboardLayoutUpdateRequest,
|
||||
view_id: str | None = Query(default=None, max_length=255),
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> DashboardLayoutResponse:
|
||||
try:
|
||||
layout = save_dashboard_layout(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
account_id=principal.account_id,
|
||||
view_id=view_id,
|
||||
expected_revision=payload.expected_revision,
|
||||
layout_version=payload.layout_version,
|
||||
placements=payload.placements,
|
||||
known_widget_ids=payload.known_widget_ids,
|
||||
)
|
||||
session.commit()
|
||||
except DashboardLayoutConflict as exc:
|
||||
session.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except DashboardLayoutLimitExceeded as exc:
|
||||
session.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except IntegrityError as exc:
|
||||
session.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=(
|
||||
"The dashboard layout changed in another session. "
|
||||
"Reload it before saving."
|
||||
),
|
||||
) from exc
|
||||
return _response(layout, view_id=view_id)
|
||||
|
||||
|
||||
@router.delete("/layout", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def api_delete_dashboard_layout(
|
||||
view_id: str | None = Query(default=None, max_length=255),
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> None:
|
||||
delete_dashboard_layout(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
account_id=principal.account_id,
|
||||
view_id=view_id,
|
||||
)
|
||||
session.commit()
|
||||
|
||||
|
||||
__all__ = ["router"]
|
||||
Reference in New Issue
Block a user