203 lines
8.2 KiB
Python
203 lines
8.2 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
|
|
from govoplan_core.core.module_guards import (
|
|
drop_table_retirement_provider,
|
|
persistent_table_uninstall_guard,
|
|
)
|
|
from govoplan_core.core.modules import (
|
|
DocumentationTopic,
|
|
FrontendModule,
|
|
FrontendRoute,
|
|
MigrationSpec,
|
|
ModuleManifest,
|
|
NavItem,
|
|
)
|
|
from govoplan_core.core.provider_governance import declared_module_architecture
|
|
from govoplan_core.core.views import ViewSurface
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_dashboard.backend.db.models import DashboardLayout
|
|
|
|
|
|
def _dashboard_router(_context):
|
|
from govoplan_dashboard.backend.router import router
|
|
|
|
return router
|
|
|
|
|
|
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
|
return {
|
|
"dashboard_layouts": (
|
|
session.query(DashboardLayout)
|
|
.filter(DashboardLayout.tenant_id == tenant_id)
|
|
.count()
|
|
)
|
|
}
|
|
|
|
|
|
manifest = ModuleManifest(
|
|
id="dashboard",
|
|
name="Dashboard",
|
|
version="0.1.15",
|
|
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
|
optional_dependencies=("ops", "campaigns", "files", "mail", "tasks", "notifications", "reporting"),
|
|
route_factory=_dashboard_router,
|
|
tenant_summary_providers=(_tenant_summary,),
|
|
migration_spec=MigrationSpec(
|
|
module_id="dashboard",
|
|
metadata=Base.metadata,
|
|
script_location=str(Path(__file__).with_name("migrations") / "versions"),
|
|
retirement_supported=True,
|
|
retirement_provider=drop_table_retirement_provider(
|
|
DashboardLayout,
|
|
label="Dashboard",
|
|
),
|
|
retirement_notes=(
|
|
"Destructive retirement removes personal Dashboard layouts after "
|
|
"the installer captures a database snapshot."
|
|
),
|
|
),
|
|
uninstall_guard_providers=(
|
|
persistent_table_uninstall_guard(
|
|
DashboardLayout,
|
|
label="Dashboard",
|
|
),
|
|
),
|
|
nav_items=(NavItem(path="/dashboard", label="Dashboard", icon="dashboard", order=10),),
|
|
frontend=FrontendModule(
|
|
module_id="dashboard",
|
|
package_name="@govoplan/dashboard-webui",
|
|
routes=(FrontendRoute(path="/dashboard", component="DashboardPage", order=10),),
|
|
nav_items=(NavItem(path="/dashboard", label="Dashboard", icon="dashboard", order=10),),
|
|
view_surfaces=(
|
|
ViewSurface(
|
|
id="dashboard.page",
|
|
module_id="dashboard",
|
|
kind="route",
|
|
label="Dashboard",
|
|
order=10,
|
|
),
|
|
ViewSurface(
|
|
id="dashboard.summary",
|
|
module_id="dashboard",
|
|
kind="section",
|
|
label="Dashboard summary",
|
|
parent_id="dashboard.page",
|
|
order=10,
|
|
),
|
|
ViewSurface(
|
|
id="dashboard.library",
|
|
module_id="dashboard",
|
|
kind="section",
|
|
label="Widget library",
|
|
parent_id="dashboard.page",
|
|
order=20,
|
|
),
|
|
ViewSurface(
|
|
id="dashboard.grid",
|
|
module_id="dashboard",
|
|
kind="section",
|
|
label="Dashboard grid",
|
|
parent_id="dashboard.page",
|
|
order=30,
|
|
),
|
|
ViewSurface(
|
|
id="dashboard.widget-settings",
|
|
module_id="dashboard",
|
|
kind="action",
|
|
label="Widget settings",
|
|
parent_id="dashboard.grid",
|
|
order=40,
|
|
),
|
|
ViewSurface(
|
|
id="dashboard.widget.installed-modules",
|
|
module_id="dashboard",
|
|
kind="section",
|
|
label="Installed modules widget",
|
|
parent_id="dashboard.grid",
|
|
order=10,
|
|
),
|
|
),
|
|
),
|
|
documentation=(
|
|
DocumentationTopic(
|
|
id="dashboard.configurable-home",
|
|
title="Configurable user dashboard",
|
|
summary="The dashboard module owns the configurable home surface. Feature modules expose widgets through a narrow dashboard.widgets capability.",
|
|
body=(
|
|
"Core only provides a minimal fallback home when the dashboard module is absent. "
|
|
"Dashboard widgets must be contributed through core contracts, not by importing sibling module components directly. "
|
|
"Personal layouts are stored per tenant, account, and active View."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("user", "tenant_admin", "operator"),
|
|
related_modules=("core", "ops"),
|
|
order=20,
|
|
metadata={
|
|
"help_contexts": [
|
|
"dashboard.page",
|
|
"dashboard.summary",
|
|
"dashboard.library",
|
|
"dashboard.grid",
|
|
"dashboard.state.browser-fallback",
|
|
"dashboard.state.view-specific",
|
|
],
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="dashboard.reference.layout-and-widgets",
|
|
title="Dashboard layouts and widget consequences",
|
|
summary="Per-user, per-tenant, and per-View widget placement, sizing, configuration, availability, and fallback semantics.",
|
|
body=(
|
|
"A Dashboard layout belongs to the active tenant, account, and focused View. "
|
|
"Configuring changes only a local draft until Save layout is selected; Cancel or "
|
|
"Discard restores the last saved arrangement. Widget removal removes only the placement, "
|
|
"not the module data represented by the widget. Reset restores the defaults announced by "
|
|
"currently active modules and remains reversible until save. A widget is offered only when "
|
|
"its module, focused View surface, and permission contract are available. Widgets never grant "
|
|
"access and providers must authorize every data request. If the server layout is unreachable, "
|
|
"the browser or module default can be displayed but is identified as a fallback until saved. "
|
|
"Concurrent saves use the layout revision and reject stale updates instead of overwriting them."
|
|
),
|
|
layer="available",
|
|
documentation_types=("admin", "user"),
|
|
audience=("user", "tenant_admin", "operator", "module_admin"),
|
|
related_modules=("core", "views", "access", "ops"),
|
|
order=21,
|
|
metadata={
|
|
"help_contexts": [
|
|
"dashboard.action.configure",
|
|
"dashboard.action.save",
|
|
"dashboard.action.reset",
|
|
"dashboard.action.remove-widget",
|
|
"dashboard.field.widget-size",
|
|
"dashboard.field.widget-configuration",
|
|
],
|
|
"consequence_classes": {
|
|
"save_layout": "Persists the complete layout for the active tenant, account, and View revision context.",
|
|
"reset_layout": "Replaces the draft with current module defaults and remains reversible until save.",
|
|
"remove_widget": "Removes only the placement from the draft; provider data is unchanged.",
|
|
"configure_widget": "Changes presentation and query preferences for one widget placement.",
|
|
},
|
|
},
|
|
),
|
|
),
|
|
architecture=declared_module_architecture(
|
|
layer="data_reporting_integration",
|
|
kind="presentation",
|
|
maturity="vertical_slice",
|
|
documentation_ref="README.md",
|
|
test_ref="tests/test_dashboard_layouts.py",
|
|
known_limits=("Widget configuration depth and target accessibility evidence vary by contributing module.",),
|
|
owned_concepts=("dashboard layout", "widget placement", "view-specific dashboard"),
|
|
non_owned_concepts=("widget domain data", "report definition", "view policy"),
|
|
),
|
|
)
|
|
|
|
|
|
def get_manifest() -> ModuleManifest:
|
|
return manifest
|