Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3593dc5ee | ||
|
|
8253bd0dd6 | ||
|
|
89755e1924 | ||
|
|
f2e42cecfe |
@@ -67,3 +67,21 @@ are covered by [docs/USER_GUIDE.md](docs/USER_GUIDE.md) and
|
|||||||
The Reporting route, workspace, state, consequence, and accessibility mapping
|
The Reporting route, workspace, state, consequence, and accessibility mapping
|
||||||
is recorded in
|
is recorded in
|
||||||
[docs/INTERFACE_PATTERN_MIGRATION.md](docs/INTERFACE_PATTERN_MIGRATION.md).
|
[docs/INTERFACE_PATTERN_MIGRATION.md](docs/INTERFACE_PATTERN_MIGRATION.md).
|
||||||
|
|
||||||
|
## Git-source WebUI package
|
||||||
|
|
||||||
|
The repository root exposes `@govoplan/reporting-webui` for Git-tagged release
|
||||||
|
dependencies. It mirrors the owning `webui/package.json` version, public
|
||||||
|
TypeScript/CSS exports and peer requirements, with entry paths under
|
||||||
|
`webui/src`. Consumers provide the shared Core/React peers; the facade runs no
|
||||||
|
development or install scripts. The source archive contains `webui/src`, this
|
||||||
|
README and any repository license file. Run module development checks from `webui/`; Python
|
||||||
|
installation remains governed by `pyproject.toml`.
|
||||||
|
|
||||||
|
Das Repository stellt `@govoplan/reporting-webui` am Wurzelpfad für versionierte
|
||||||
|
Git-Abhängigkeiten bereit. Version, öffentliche TypeScript-/CSS-Exporte und
|
||||||
|
Peer-Anforderungen entsprechen `webui/package.json`; die Einstiegspfade liegen
|
||||||
|
unter `webui/src`. Gemeinsame Core-/React-Peers stellt die einbindende Anwendung
|
||||||
|
bereit. Die Fassade führt keine Entwicklungs- oder Installationsskripte aus.
|
||||||
|
Entwicklungsprüfungen bleiben in `webui/`, die Python-Installation weiterhin in
|
||||||
|
`pyproject.toml` definiert.
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@govoplan/reporting-webui",
|
||||||
|
"version": "0.1.22",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "webui/src/index.ts",
|
||||||
|
"module": "webui/src/index.ts",
|
||||||
|
"types": "webui/src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./webui/src/index.ts",
|
||||||
|
"import": "./webui/src/index.ts"
|
||||||
|
},
|
||||||
|
"./styles/reporting.css": "./webui/src/styles/reporting.css"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@govoplan/core-webui": "^0.1.45",
|
||||||
|
"lucide-react": "^1.23.0",
|
||||||
|
"react": ">=19.2.7 <20",
|
||||||
|
"react-dom": ">=19.2.7 <20",
|
||||||
|
"react-router": ">=8.3.0 <9"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@govoplan/core-webui": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"webui/src",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
]
|
||||||
|
}
|
||||||
+2
-2
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-reporting"
|
name = "govoplan-reporting"
|
||||||
version = "0.1.20"
|
version = "0.1.22"
|
||||||
description = "GovOPlaN governed reporting and semantic BI module."
|
description = "GovOPlaN governed reporting and semantic BI module."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
license = { text = "AGPL-3.0-or-later" }
|
license = { text = "AGPL-3.0-or-later" }
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.37",
|
"govoplan-core>=0.1.46",
|
||||||
"govoplan-access>=0.1.18",
|
"govoplan-access>=0.1.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""GovOPlaN Reporting module."""
|
"""GovOPlaN Reporting module."""
|
||||||
|
|
||||||
__version__ = "0.1.20"
|
__version__ = "0.1.22"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import uuid
|
|||||||
from sqlalchemy import and_, exists, func, or_
|
from sqlalchemy import and_, exists, func, or_
|
||||||
from sqlalchemy.orm import Query, Session
|
from sqlalchemy.orm import Query, Session
|
||||||
|
|
||||||
|
from govoplan_core.core.principal_helpers import principal_actor_ids as _actor_ids
|
||||||
from govoplan_core.core.events import (
|
from govoplan_core.core.events import (
|
||||||
EventActorRef,
|
EventActorRef,
|
||||||
EventObjectRef,
|
EventObjectRef,
|
||||||
@@ -884,22 +885,6 @@ def _actor(principal: object) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _actor_ids(principal: object) -> tuple[str, ...]:
|
|
||||||
user = getattr(principal, "user", None)
|
|
||||||
return tuple(
|
|
||||||
dict.fromkeys(
|
|
||||||
str(value)
|
|
||||||
for value in (
|
|
||||||
getattr(principal, "account_id", None),
|
|
||||||
getattr(principal, "identity_id", None),
|
|
||||||
getattr(principal, "membership_id", None),
|
|
||||||
getattr(user, "id", None),
|
|
||||||
)
|
|
||||||
if str(value or "").strip()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _principal_tenant(principal: object) -> str:
|
def _principal_tenant(principal: object) -> str:
|
||||||
tenant_id = str(getattr(principal, "tenant_id", "") or "").strip()
|
tenant_id = str(getattr(principal, "tenant_id", "") or "").strip()
|
||||||
if not tenant_id:
|
if not tenant_id:
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ from govoplan_reporting.backend.search_source import create_reporting_search_sou
|
|||||||
|
|
||||||
MODULE_ID = "reporting"
|
MODULE_ID = "reporting"
|
||||||
MODULE_NAME = "Reporting"
|
MODULE_NAME = "Reporting"
|
||||||
MODULE_VERSION = "0.1.20"
|
MODULE_VERSION = "0.1.22"
|
||||||
|
|
||||||
|
|
||||||
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
|
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
|
||||||
@@ -597,14 +597,18 @@ manifest = ModuleManifest(
|
|||||||
title="Governed reporting and semantic BI",
|
title="Governed reporting and semantic BI",
|
||||||
summary="Build reproducible reports over provider-owned datasets without bypassing module or row-level access.",
|
summary="Build reproducible reports over provider-owned datasets without bypassing module or row-level access.",
|
||||||
body=(
|
body=(
|
||||||
|
"Documentation books sit immediately beside the visible heading or contextual label for "
|
||||||
|
"Reporting, not among operational action buttons. Field help remains beside its label. "
|
||||||
"Reporting pins dataset, semantic-model, and report revisions. Runs retain "
|
"Reporting pins dataset, semantic-model, and report revisions. Runs retain "
|
||||||
"definition hashes, source fingerprints, policy provenance, quality evidence, "
|
"definition hashes, source fingerprints, policy provenance, quality evidence, "
|
||||||
"authorized result rows, diagnostics, and output hashes. Safe dimensions, "
|
"authorized result rows, diagnostics, and output hashes. Safe dimensions, "
|
||||||
"aggregations, typed expressions, filters, pivots, saved views, chart models, "
|
"aggregations, typed expressions, filters, pivots, saved views, chart models, "
|
||||||
"schedules, exports, and publication providers replace unchecked SQL in the "
|
"schedules, exports, and publication providers replace unchecked SQL in the "
|
||||||
"presentation layer. PostgreSQL executes bounded semantic plans when available. "
|
"presentation layer. PostgreSQL executes bounded semantic plans when available. "
|
||||||
|
"Calculated measure keys may contain the documented dots and hyphens, including in nested references; generated bind names remain internal and values remain parameters, not SQL fragments. "
|
||||||
"Signed drill contexts reauthorize contributor rows, and Files/Mail publication "
|
"Signed drill contexts reauthorize contributor rows, and Files/Mail publication "
|
||||||
"adapters retain idempotent evidence. A dataset may pin one successful published Dataflow run, which is read from its exact Datasource materialization after both source boundaries reauthorize the current principal. Dataflow and module read models remain source owners."
|
"adapters retain idempotent evidence. A dataset may pin one successful published Dataflow run, which is read from its exact Datasource materialization after both source boundaries reauthorize the current principal. Dataflow and module read models remain source owners. "
|
||||||
|
"The contributor drill-down action stays in a shared action column at the right edge of horizontally scrolled results; opening it still reauthorizes every contributor."
|
||||||
),
|
),
|
||||||
layer="available",
|
layer="available",
|
||||||
documentation_types=("admin", "user"),
|
documentation_types=("admin", "user"),
|
||||||
@@ -656,6 +660,9 @@ manifest = ModuleManifest(
|
|||||||
"ohne Modul- oder Zeilenberechtigungen zu umgehen."
|
"ohne Modul- oder Zeilenberechtigungen zu umgehen."
|
||||||
),
|
),
|
||||||
"body": (
|
"body": (
|
||||||
|
"Dokumentationsbücher stehen unmittelbar neben der sichtbaren Überschrift oder "
|
||||||
|
"Kontextbezeichnung für Berichtswesen, nicht zwischen ausführbaren Aktionsschaltflächen. "
|
||||||
|
"Feldhilfe bleibt neben der Feldbezeichnung. "
|
||||||
"Reporting fixiert Revisionen von Datensätzen, semantischen Modellen und "
|
"Reporting fixiert Revisionen von Datensätzen, semantischen Modellen und "
|
||||||
"Berichten. Ausführungen bewahren Definitionsprüfsummen, Quellfingerabdrücke, "
|
"Berichten. Ausführungen bewahren Definitionsprüfsummen, Quellfingerabdrücke, "
|
||||||
"Richtlinienherkunft, Qualitätsnachweise, berechtigte Ergebniszeilen, Diagnosen "
|
"Richtlinienherkunft, Qualitätsnachweise, berechtigte Ergebniszeilen, Diagnosen "
|
||||||
@@ -663,12 +670,13 @@ manifest = ModuleManifest(
|
|||||||
"Filter, Pivotierungen, gespeicherte Ansichten, Diagrammmodelle, Zeitpläne, "
|
"Filter, Pivotierungen, gespeicherte Ansichten, Diagrammmodelle, Zeitpläne, "
|
||||||
"Exporte und Veröffentlichungsanbieter ersetzen ungeprüftes SQL in der "
|
"Exporte und Veröffentlichungsanbieter ersetzen ungeprüftes SQL in der "
|
||||||
"Darstellungsschicht. PostgreSQL führt begrenzte semantische Pläne aus, sofern "
|
"Darstellungsschicht. PostgreSQL führt begrenzte semantische Pläne aus, sofern "
|
||||||
"verfügbar. Signierte Drilldown-Kontexte autorisieren beitragende Zeilen erneut; "
|
"verfügbar. Kennungen berechneter Kennzahlen dürfen auch in verschachtelten Verweisen die vorgesehenen Punkte und Bindestriche enthalten; "
|
||||||
|
"erzeugte Bindungsnamen bleiben intern, und Werte bleiben Parameter statt SQL-Fragmente. Signierte Drilldown-Kontexte autorisieren beitragende Zeilen erneut; "
|
||||||
"Adapter für Dateien und Mail bewahren idempotente Nachweise. Ein Datensatz kann "
|
"Adapter für Dateien und Mail bewahren idempotente Nachweise. Ein Datensatz kann "
|
||||||
"genau eine erfolgreiche veröffentlichte Dataflow-Ausführung fixieren, die nach "
|
"genau eine erfolgreiche veröffentlichte Dataflow-Ausführung fixieren, die nach "
|
||||||
"erneuter Autorisierung beider Quellgrenzen aus ihrer exakten Datasource-"
|
"erneuter Autorisierung beider Quellgrenzen aus ihrer exakten Datasource-"
|
||||||
"Materialisierung gelesen wird. Dataflow und die Lesemodelle der Module bleiben "
|
"Materialisierung gelesen wird. Dataflow und die Lesemodelle der Module bleiben "
|
||||||
"führende Quellen."
|
"führende Quellen. Die Aktion zum Aufschlüsseln beitragender Zeilen bleibt in einer gemeinsamen Aktionsspalte am rechten Rand horizontal gescrollter Ergebnisse; beim Öffnen wird jeder Beitrag erneut autorisiert."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -344,7 +344,9 @@ def _calculated_sql(
|
|||||||
return _calculated_sql(
|
return _calculated_sql(
|
||||||
target.expression,
|
target.expression,
|
||||||
parameters,
|
parameters,
|
||||||
prefix + "_" + reference,
|
# The expression position already makes this prefix unique. Model
|
||||||
|
# keys may contain dots or hyphens, which are not SQL bind names.
|
||||||
|
prefix + "_ref",
|
||||||
measures=measures,
|
measures=measures,
|
||||||
stack=(*stack, reference),
|
stack=(*stack, reference),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
from govoplan_reporting.backend.postgres_planner import compile_postgres_query
|
||||||
|
from govoplan_reporting.backend.schemas import (
|
||||||
|
DatasetDefinition,
|
||||||
|
ReportQuery,
|
||||||
|
SemanticModelDefinition,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PostgresBindNameTests(unittest.TestCase):
|
||||||
|
def test_allowed_measure_key_punctuation_never_becomes_bind_parameter_syntax(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
dataset = DatasetDefinition(
|
||||||
|
source_kind="static",
|
||||||
|
source_ref="fixture",
|
||||||
|
static_rows=[{"value": 10}],
|
||||||
|
purpose="Bound parameter fixture",
|
||||||
|
)
|
||||||
|
semantic = SemanticModelDefinition.model_validate(
|
||||||
|
{
|
||||||
|
"dataset_id": "fixture",
|
||||||
|
"dataset_revision": 1,
|
||||||
|
"measures": [
|
||||||
|
{
|
||||||
|
"key": "base",
|
||||||
|
"label": "Base",
|
||||||
|
"aggregation": "sum",
|
||||||
|
"field": "value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "extra-cost",
|
||||||
|
"label": "Extra",
|
||||||
|
"aggregation": "calculated",
|
||||||
|
"expression": {
|
||||||
|
"op": "add",
|
||||||
|
"args": [
|
||||||
|
{"op": "measure", "ref": "base"},
|
||||||
|
{"op": "literal", "value": 5},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tax.factor",
|
||||||
|
"label": "Tax",
|
||||||
|
"aggregation": "calculated",
|
||||||
|
"expression": {
|
||||||
|
"op": "multiply",
|
||||||
|
"args": [
|
||||||
|
{"op": "measure", "ref": "base"},
|
||||||
|
{"op": "literal", "value": 9},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "grand-total",
|
||||||
|
"label": "Total",
|
||||||
|
"aggregation": "calculated",
|
||||||
|
"expression": {
|
||||||
|
"op": "add",
|
||||||
|
"args": [
|
||||||
|
{"op": "measure", "ref": "extra-cost"},
|
||||||
|
{"op": "measure", "ref": "tax.factor"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
plan = compile_postgres_query(
|
||||||
|
dataset,
|
||||||
|
semantic,
|
||||||
|
ReportQuery(measures=["extra-cost", "tax.factor", "grand-total"]),
|
||||||
|
)
|
||||||
|
binds = text(plan.sql).compile(dialect=postgresql.dialect()).params
|
||||||
|
self.assertEqual(
|
||||||
|
set(plan.parameters) | {"rows_json", "result_limit", "result_offset"},
|
||||||
|
set(binds),
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
all("-" not in name and "." not in name for name in plan.parameters)
|
||||||
|
)
|
||||||
|
self.assertIn('AS "extra-cost"', plan.sql)
|
||||||
|
self.assertIn('AS "tax.factor"', plan.sql)
|
||||||
|
self.assertEqual(2, list(plan.parameters.values()).count(5))
|
||||||
|
self.assertEqual(2, list(plan.parameters.values()).count(9))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -92,7 +92,7 @@ class _Provider:
|
|||||||
del session
|
del session
|
||||||
return ReportProviderResult(
|
return ReportProviderResult(
|
||||||
report_id=request.report_id,
|
report_id=request.report_id,
|
||||||
generated_at=datetime(2026, 8, 2, 10, 0, tzinfo=UTC),
|
generated_at=datetime.now(UTC),
|
||||||
payload={"metric": 12},
|
payload={"metric": 12},
|
||||||
source_revisions=(
|
source_revisions=(
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/reporting-webui",
|
"name": "@govoplan/reporting-webui",
|
||||||
"version": "0.1.20",
|
"version": "0.1.22",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"test:interface-pattern": "node scripts/test-interface-pattern.mjs"
|
"test:interface-pattern": "node scripts/test-interface-pattern.mjs"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.45",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": ">=19.2.7 <20",
|
"react": ">=19.2.7 <20",
|
||||||
"react-dom": ">=19.2.7 <20",
|
"react-dom": ">=19.2.7 <20",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ assert.ok(page.includes("PageScrollViewport"), "Reporting owns bounded catalogue
|
|||||||
assert.ok(page.includes("DataGrid"), "Tabular report results use the shared grid");
|
assert.ok(page.includes("DataGrid"), "Tabular report results use the shared grid");
|
||||||
assert.ok(page.includes("<Dialog"), "Save and schedule operations use shared dialogs");
|
assert.ok(page.includes("<Dialog"), "Save and schedule operations use shared dialogs");
|
||||||
assert.ok(page.includes("createDrillContext"), "Aggregate detail uses an actor-bound drill context");
|
assert.ok(page.includes("createDrillContext"), "Aggregate detail uses an actor-bound drill context");
|
||||||
|
assert.match(page, /id: "drill",\s*header: "Detail",\s*columnType: "actions",\s*sticky: "end"/, "Result drill-down controls use the shared pinned action-column contract");
|
||||||
|
assert.match(page, /<TableActionGroup actions=\{\[\{\s*id: "drill"/, "Drill-down renders the shared measurable action surface");
|
||||||
assert.ok(page.includes("AccessExplanation"), "Policy-hidden fields, rows, and actions are explained");
|
assert.ok(page.includes("AccessExplanation"), "Policy-hidden fields, rows, and actions are explained");
|
||||||
assert.ok(page.includes("PublishDialog"), "Publication targets use the shared dialog surface");
|
assert.ok(page.includes("PublishDialog"), "Publication targets use the shared dialog surface");
|
||||||
assert.ok(provider.includes("disabledReason={runDisabledReason}"), "Governed report blockers remain keyboard-explainable");
|
assert.ok(provider.includes("disabledReason={runDisabledReason}"), "Governed report blockers remain keyboard-explainable");
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { FormGrid,
|
|||||||
SelectionListItem,
|
SelectionListItem,
|
||||||
SelectionListItemContent,
|
SelectionListItemContent,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
|
TableActionGroup,
|
||||||
ToggleSwitch,
|
ToggleSwitch,
|
||||||
hasScope,
|
hasScope,
|
||||||
WorkspaceActionBar,
|
WorkspaceActionBar,
|
||||||
@@ -274,7 +275,9 @@ export default function ReportingPage({ settings, auth }: PlatformRouteContext)
|
|||||||
</FilterBar>
|
</FilterBar>
|
||||||
<span className="reporting-count">{reports.length + providerReports.length} reports</span>
|
<span className="reporting-count">{reports.length + providerReports.length} reports</span>
|
||||||
</>}
|
</>}
|
||||||
helpAction={<DocumentationHelpLink
|
title="Reporting"
|
||||||
|
titleLevel={1}
|
||||||
|
titleHelp={<DocumentationHelpLink
|
||||||
reference={{ topicId: "reporting.governed-bi", documentationType: "user" }}
|
reference={{ topicId: "reporting.governed-bi", documentationType: "user" }}
|
||||||
label="Open reporting documentation"
|
label="Open reporting documentation"
|
||||||
/>}
|
/>}
|
||||||
@@ -322,7 +325,7 @@ export default function ReportingPage({ settings, auth }: PlatformRouteContext)
|
|||||||
<header className="reporting-result-header">
|
<header className="reporting-result-header">
|
||||||
<div className="reporting-title">
|
<div className="reporting-title">
|
||||||
<span>{selected.definition_key} · revision {selected.revision}</span>
|
<span>{selected.definition_key} · revision {selected.revision}</span>
|
||||||
<h1>{selected.name}</h1>
|
<h2>{selected.name}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="reporting-run-actions">
|
<div className="reporting-run-actions">
|
||||||
{canSchedule &&
|
{canSchedule &&
|
||||||
@@ -543,7 +546,7 @@ function ReportTable({ execution, onDrill }: { execution: ReportExecution; onDri
|
|||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
useEffect(() => setPage(0), [execution.execution_id]);
|
useEffect(() => setPage(0), [execution.execution_id]);
|
||||||
const columns = useMemo<DataGridColumn<Record<string, unknown>>[]>(() => {
|
const columns = useMemo<DataGridColumn<Record<string, unknown>>[]>(() => {
|
||||||
const result = execution.schema.map((field) => ({
|
const result: DataGridColumn<Record<string, unknown>>[] = execution.schema.map((field) => ({
|
||||||
id: field.name,
|
id: field.name,
|
||||||
header: humanize(field.name),
|
header: humanize(field.name),
|
||||||
width: "1fr",
|
width: "1fr",
|
||||||
@@ -559,16 +562,20 @@ function ReportTable({ execution, onDrill }: { execution: ReportExecution; onDri
|
|||||||
result.push({
|
result.push({
|
||||||
id: "drill",
|
id: "drill",
|
||||||
header: "Detail",
|
header: "Detail",
|
||||||
|
columnType: "actions",
|
||||||
|
sticky: "end",
|
||||||
|
resizable: false,
|
||||||
|
align: "right",
|
||||||
width: 74,
|
width: 74,
|
||||||
minWidth: 74,
|
minWidth: 74,
|
||||||
maxWidth: 74,
|
maxWidth: 74,
|
||||||
render: (row) => (
|
render: (row) => (
|
||||||
<IconButton
|
<TableActionGroup actions={[{
|
||||||
label="Show authorized contributing rows"
|
id: "drill",
|
||||||
icon={<ChevronRight size={16} />}
|
label: "Show authorized contributing rows",
|
||||||
variant="ghost"
|
icon: <ChevronRight size={16} aria-hidden="true" />,
|
||||||
onClick={() => onDrill(row)}
|
onClick: () => onDrill(row)
|
||||||
/>
|
}]} />
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
import type { PlatformTranslations } from "@govoplan/core-webui";
|
||||||
|
|
||||||
|
/** Module-owned translations for contextual headings. */
|
||||||
|
export const generatedTranslations: PlatformTranslations = {
|
||||||
|
en: {
|
||||||
|
"Reporting": "Reporting",
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
"Reporting": "Reporting",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createElement, lazy } from "react";
|
import { createElement, lazy } from "react";
|
||||||
import type { DashboardWidgetsUiCapability, PlatformWebModule } from "@govoplan/core-webui";
|
import type { DashboardWidgetsUiCapability, PlatformWebModule } from "@govoplan/core-webui";
|
||||||
import ReportingReportsWidget from "./features/reporting/ReportingReportsWidget";
|
import ReportingReportsWidget from "./features/reporting/ReportingReportsWidget";
|
||||||
|
import { generatedTranslations } from "./i18n/generatedTranslations";
|
||||||
import "./styles/reporting.css";
|
import "./styles/reporting.css";
|
||||||
|
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ const reportingDashboardWidgets: DashboardWidgetsUiCapability = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const reportingModule: PlatformWebModule = {
|
export const reportingModule: PlatformWebModule = {
|
||||||
|
translations: generatedTranslations,
|
||||||
id: "reporting",
|
id: "reporting",
|
||||||
label: "Reporting",
|
label: "Reporting",
|
||||||
version: "0.1.14",
|
version: "0.1.14",
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reporting-title h1 {
|
.reporting-title h2 {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 2px 0 0;
|
margin: 2px 0 0;
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user