Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2582a278e | ||
|
|
6e03338b13 | ||
|
|
bf77ac2a8d | ||
|
|
4aa19840bc | ||
|
|
fa0bdde839 | ||
|
|
8e5415dfe7 |
@@ -33,3 +33,21 @@ in [the interface pattern audit](docs/INTERFACE_PATTERN_MIGRATION.md).
|
||||
cd webui && npm run typecheck
|
||||
cd webui && npm run test:interface-pattern
|
||||
```
|
||||
|
||||
## Git-source WebUI package
|
||||
|
||||
The repository root exposes `@govoplan/workflow-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/workflow-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,34 @@
|
||||
{
|
||||
"name": "@govoplan/workflow-webui",
|
||||
"version": "0.1.23",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.45",
|
||||
"@xyflow/react": "^12.11.2",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
"react-dom": ">=19.2.7 <20",
|
||||
"react-router": ">=8.3.0 <9",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@govoplan/core-webui": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"webui/src",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
]
|
||||
}
|
||||
+3
-3
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-workflow"
|
||||
version = "0.1.19"
|
||||
version = "0.1.23"
|
||||
description = "Optional visual authoring and inspection workspace for GovOPlaN Workflow Engine."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
license = "AGPL-3.0-or-later"
|
||||
authors = [{ name = "GovOPlaN" }]
|
||||
dependencies = [
|
||||
"govoplan-core>=0.1.18",
|
||||
"govoplan-workflow-engine>=0.1.18",
|
||||
"govoplan-core>=0.1.45",
|
||||
"govoplan-workflow-engine>=0.1.19",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""Optional GovOPlaN Workflow editor and compatibility package."""
|
||||
|
||||
__version__ = "0.1.18"
|
||||
__version__ = "0.1.23"
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from govoplan_core.core.modules import (
|
||||
CapabilityDocumentation,
|
||||
DocumentationCondition,
|
||||
DocumentationLink,
|
||||
DocumentationTopic,
|
||||
FrontendModule,
|
||||
@@ -34,7 +35,7 @@ from govoplan_workflow_engine.backend.manifest import (
|
||||
|
||||
MODULE_ID = "workflow"
|
||||
MODULE_NAME = "Workflow"
|
||||
MODULE_VERSION = "0.1.19"
|
||||
MODULE_VERSION = "0.1.23"
|
||||
SEMANTIC_SUBJECT_CAPABILITY = semantic_documentation_subject_capability(MODULE_ID)
|
||||
|
||||
|
||||
@@ -139,6 +140,76 @@ manifest = ModuleManifest(
|
||||
),
|
||||
),
|
||||
documentation=(
|
||||
DocumentationTopic(
|
||||
id="workflow.workspace-layout",
|
||||
title="Workflow workspace actions",
|
||||
summary="Find collection-wide commands in their consistent workspace position.",
|
||||
body="Reload and New workflow use the persistent full-width workspace header at the upper right; Reload sits immediately before creation. Selecting a record, changing filters, or opening an editor does not move these collection-wide commands into the left pane. Graph editing, validation, saving, publication, and execution remain separate pane-local actions. Existing permissions, disabled-state rules, and unsaved-change guards still apply. Administrators configure authority through the existing permission system; no new permission or automatic operation is introduced.",
|
||||
layer="static",
|
||||
documentation_types=("user", "admin"),
|
||||
audience=("user", "module_admin", "operator"),
|
||||
order=5,
|
||||
translations={"de": {
|
||||
"title": "Workflows: Aktionen im Arbeitsbereich",
|
||||
"summary": "Sammlungsweite Aktionen an ihrer einheitlichen Position im Arbeitsbereich finden.",
|
||||
"body": "Neu laden und Neuer Workflow stehen oben rechts in der dauerhaft sichtbaren, arbeitsbereichsweiten Leiste; Neu laden steht unmittelbar vor dem Anlegen. Auswahl, Filterwechsel und Bearbeitung verschieben diese sammlungsweiten Aktionen nicht in den linken Bereich. Graphbearbeitung, Validierung, Speichern, Veröffentlichung und Ausführung bleiben getrennte Aktionen im jeweiligen Bereich. Bestehende Berechtigungen, Deaktivierungsregeln und der Schutz ungespeicherter Änderungen gelten weiterhin. Administratoren konfigurieren Rechte im bestehenden Berechtigungssystem; es entstehen weder neue Rechte noch automatische Vorgänge.",
|
||||
}},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="workflow.external-handoff-inspection",
|
||||
title="Inspect and return to external work hand-offs",
|
||||
summary=(
|
||||
"Follow the exact linked module work while Workflow retains "
|
||||
"its durable position and immutable reference."
|
||||
),
|
||||
body=(
|
||||
"The runs dialog shows the current external hand-off state, exact "
|
||||
"revision reference, safe linked-work action, timeout, and the "
|
||||
"immutable Workflow evidence trail. Leaving the dialog or linked "
|
||||
"Campaign page does not complete the step. Declared Campaign events "
|
||||
"drive acceptance, reassignment, completion, rejection, and "
|
||||
"cancellation without browser polling. A configured focused View "
|
||||
"narrows the available surfaces while the step is active. If Views "
|
||||
"is absent, the linked Campaign action remains available and the "
|
||||
"dialog explains the reduced presentation. Missing optional Tasks or "
|
||||
"Notifications is also shown without changing the hand-off outcome."
|
||||
),
|
||||
layer="available",
|
||||
documentation_types=("admin", "user"),
|
||||
audience=("operator", "workflow_designer", "campaign_manager"),
|
||||
related_modules=(
|
||||
"workflow_engine",
|
||||
"campaigns",
|
||||
"views",
|
||||
"tasks",
|
||||
"notifications",
|
||||
),
|
||||
conditions=(
|
||||
DocumentationCondition(
|
||||
any_scopes=(INSTANCE_READ_SCOPE, ADMIN_SCOPE),
|
||||
),
|
||||
),
|
||||
translations={
|
||||
"de": {
|
||||
"title": "Externe Arbeitsübergaben prüfen und zu ihnen zurückkehren",
|
||||
"summary": "Der genau verknüpften Arbeit im Zielmodul folgen, während Workflow seine dauerhafte Position und unveränderliche Referenz beibehält.",
|
||||
"body": (
|
||||
"Der Läufe-Dialog zeigt den aktuellen Zustand der externen Übergabe, die genaue Revisionsreferenz, die sichere Aktion zur verknüpften Arbeit, den Zeitablauf und die unveränderliche Workflow-Nachweisspur. "
|
||||
"Das Verlassen des Dialogs oder der verknüpften Campaign-Seite schließt den Schritt nicht ab. Deklarierte Campaign-Ereignisse steuern Annahme, Neuzuweisung, Abschluss, Ablehnung und Abbruch ohne Browser-Abfragen. "
|
||||
"Eine konfigurierte fokussierte View grenzt die verfügbaren Oberflächen ein, solange der Schritt aktiv ist. Fehlt Views, bleibt die verknüpfte Campaign-Aktion verfügbar und der Dialog erläutert die reduzierte Darstellung. "
|
||||
"Auch fehlende optionale Module Tasks oder Notifications werden angezeigt, ohne das Ergebnis der Übergabe zu verändern."
|
||||
),
|
||||
}
|
||||
},
|
||||
metadata={
|
||||
"kind": "workflow",
|
||||
"help_contexts": [
|
||||
"workflow.external-handoff",
|
||||
"workflow.instances",
|
||||
],
|
||||
},
|
||||
order=74,
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="workflow.semantic-documentation",
|
||||
title="Document configured workflow and step meaning",
|
||||
@@ -183,6 +254,19 @@ manifest = ModuleManifest(
|
||||
kind="repository",
|
||||
),
|
||||
),
|
||||
translations={
|
||||
"de": {
|
||||
"title": "Bedeutung konfigurierter Workflows und Schritte dokumentieren",
|
||||
"summary": "Mandanteneigene semantische Hinweise einer berechtigten Workflow-Definition oder einem stabilen Schritt zuordnen, ohne die Ausführung zu verändern.",
|
||||
"body": (
|
||||
"Wenn Docs installiert ist, stellt Workflow dokumentationssichere Subjekte für jede Definition bereit, die der aktuelle Akteur sehen darf, sowie für jeden Schritt ihrer aktuellen unveränderlichen Revision. "
|
||||
"Die Schrittidentität bleibt bei Änderungen von Bezeichnung und Position auf der Zeichenfläche erhalten. Wird ein Schritt entfernt und später mit derselben Graph-ID neu angelegt, entsteht eine neue Abstammung; frühere Dokumentation bleibt ausdrücklich verwaist. "
|
||||
"Relevante Änderungen an Schritttyp, Bezeichnung, Hierarchie, sicherer Konfiguration oder der Semantik verbundener Flüsse ändern den Prüffingerabdruck; reine Layoutverschiebungen tun dies nicht. "
|
||||
"Semantische Texte können Zweck, Nicht-Zweck, Ein- und Austrittsbedingungen, Übergaben, Ausnahmen, Verantwortung, Betreuung und Beispiele erläutern, aber weder Validierung, Aktivierung, Richtlinien noch die Ausführung durch Workflow Engine verändern. "
|
||||
"Workflow prüft bei Ermittlung und direkter Auflösung erneut Mandantengrenze und aktuelle Governance-Entscheidung je Definition. Fehlt Docs, bleiben Bearbeitung und statische Hilfe verfügbar."
|
||||
),
|
||||
}
|
||||
},
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"help_contexts": ["workflow.semantic-documentation"],
|
||||
@@ -201,7 +285,16 @@ manifest = ModuleManifest(
|
||||
"catalogue, immutable revision comparison, activation controls, "
|
||||
"instance inspection, and governed override/reset experience. "
|
||||
"Definitions and instances remain owned and executed by the "
|
||||
"headless Workflow Engine module."
|
||||
"headless Workflow Engine module. Deleting a flow or node changes only "
|
||||
"the editable draft until that definition is saved; deleting a workflow "
|
||||
"removes its definition revisions and requires explicit confirmation. "
|
||||
"Retained execution and audit evidence remains governed by Workflow Engine. "
|
||||
"Opening or reloading the editor does not execute a workflow. If the editor "
|
||||
"cannot be loaded after a development update, preserve any unsaved work "
|
||||
"before reloading the browser. Administrators should distinguish failed "
|
||||
"frontend assets from definition API or access errors. Development and "
|
||||
"browser-conformance servers use separate dependency caches; an older "
|
||||
"server may need restarting after upgrading this configuration."
|
||||
),
|
||||
layer="available",
|
||||
documentation_types=("admin", "user"),
|
||||
@@ -214,6 +307,21 @@ manifest = ModuleManifest(
|
||||
kind="repository",
|
||||
),
|
||||
),
|
||||
translations={
|
||||
"de": {
|
||||
"title": "Arbeitsbereich für Workflow-Bearbeitung und -Prüfung",
|
||||
"summary": "Optionale Erstellung, Validierung, Revisionsprüfung und Betriebssteuerung für Workflow Engine.",
|
||||
"body": (
|
||||
"Workflow ergänzt den visuellen BPMN- und nativen Graph-Editor, den Definitionskatalog, den Vergleich unveränderlicher Revisionen, Aktivierungssteuerung, Instanzprüfung und gesteuerte Eingriffe zum Überschreiben oder Zurücksetzen. "
|
||||
"Definitionen und Instanzen bleiben Eigentum des ohne eigene Bedienoberfläche betriebenen Moduls Workflow Engine und werden dort ausgeführt. Das Löschen eines Flusses oder Knotens verändert nur den bearbeitbaren Entwurf, bis die Definition gespeichert wird. Das Löschen eines Workflows entfernt dessen Definitionsrevisionen und verlangt eine ausdrückliche Bestätigung. Aufbewahrte Ausführungs- und Auditnachweise bleiben durch Workflow Engine geregelt. "
|
||||
"Das Öffnen oder Neuladen des Editors führt keinen Workflow aus. Kann der Editor nach einer Entwicklungsaktualisierung nicht geladen werden, sichern Sie ungespeicherte Arbeit vor dem Neuladen des Browsers. Administratoren sollten fehlgeschlagene Oberflächendateien von Fehlern der Definitions-API oder Zugriffsfehlern unterscheiden. Entwicklungs- und Browser-Konformitätsserver verwenden getrennte Abhängigkeitscaches; ein älterer Server muss nach dieser Konfigurationsaktualisierung gegebenenfalls neu gestartet werden."
|
||||
),
|
||||
}
|
||||
},
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"help_contexts": ["workflow.action.delete-definition-parts"],
|
||||
},
|
||||
order=76,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
import unittest
|
||||
|
||||
from govoplan_workflow.backend.manifest import get_manifest
|
||||
@@ -11,6 +12,8 @@ class WorkflowManifestTests(unittest.TestCase):
|
||||
manifest = get_manifest()
|
||||
|
||||
self.assertEqual("workflow", manifest.id)
|
||||
project = tomllib.loads((Path(__file__).parents[1] / "pyproject.toml").read_text())
|
||||
self.assertEqual(project["project"]["version"], manifest.version)
|
||||
self.assertEqual(("workflow_engine",), manifest.dependencies)
|
||||
self.assertEqual(
|
||||
{"workflow.definition_graph", "workflow.definition_catalogue", "workflow.bpmn_interchange"},
|
||||
@@ -38,6 +41,13 @@ class WorkflowManifestTests(unittest.TestCase):
|
||||
manifest.frontend.package_name if manifest.frontend else None,
|
||||
)
|
||||
|
||||
for topic in manifest.documentation:
|
||||
german = topic.translations.get("de", {})
|
||||
self.assertTrue(
|
||||
all(german.get(field) for field in ("title", "summary", "body")),
|
||||
topic.id,
|
||||
)
|
||||
|
||||
def test_editor_exposes_semantic_help_without_replacing_static_help(self) -> None:
|
||||
root = Path(__file__).parents[1]
|
||||
page = (root / "webui/src/features/workflow/WorkflowPage.tsx").read_text()
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/workflow-webui",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
@@ -17,7 +17,7 @@
|
||||
"test:interface-pattern": "node scripts/test-interface-pattern.mjs"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.18",
|
||||
"@govoplan/core-webui": "^0.1.45",
|
||||
"@xyflow/react": "^12.11.2",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
|
||||
@@ -101,6 +101,8 @@ export default function WorkflowInspector({
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
helpContextId="workflow.action.delete-definition-parts"
|
||||
helpModuleId="workflow"
|
||||
className="workflow-inspector-delete"
|
||||
onClick={() => onEdgeDelete(edge.id)}
|
||||
disabled={readOnly}
|
||||
@@ -232,6 +234,8 @@ export default function WorkflowInspector({
|
||||
</> : null}
|
||||
<Button
|
||||
variant="ghost"
|
||||
helpContextId="workflow.action.delete-definition-parts"
|
||||
helpModuleId="workflow"
|
||||
className="workflow-inspector-delete"
|
||||
onClick={() => onDelete(node.id)}
|
||||
disabled={readOnly}
|
||||
|
||||
@@ -597,18 +597,8 @@ export default function WorkflowPage({
|
||||
|
||||
return (
|
||||
<WorkspaceFrame as="main" height="viewport" surface="plain" className="workflow-page" label="Workflow workspace">
|
||||
<WorkspaceLayout
|
||||
variant="split"
|
||||
primarySize="compact"
|
||||
surface="contained"
|
||||
primaryScrollable={false}
|
||||
contentScrollable={false}
|
||||
primaryLabel="Workflows"
|
||||
contentLabel="Workflow editor"
|
||||
contentClassName="workflow-workspace"
|
||||
primary={<>
|
||||
<WorkspaceActionBar
|
||||
scope="collection-pane"
|
||||
scope="workspace"
|
||||
variant="collection"
|
||||
refreshable
|
||||
reloadAction={{ onReload: () => void reload(draft?.id), loading: loading || working }}
|
||||
@@ -637,6 +627,16 @@ export default function WorkflowPage({
|
||||
disabledReason={!canWrite ? "Workflow definition write permission is required." : undefined}
|
||||
/>}
|
||||
/>
|
||||
<WorkspaceLayout
|
||||
variant="split"
|
||||
primarySize="compact"
|
||||
surface="contained"
|
||||
primaryScrollable={false}
|
||||
contentScrollable={false}
|
||||
primaryLabel="Workflows"
|
||||
contentLabel="Workflow editor"
|
||||
contentClassName="workflow-workspace"
|
||||
primary={<>
|
||||
<FilterBar surface="panel">
|
||||
<input
|
||||
type="search"
|
||||
@@ -874,6 +874,8 @@ export default function WorkflowPage({
|
||||
destructiveActions={draft.id && draft.standard?.kind !== "baseline" ? (
|
||||
<IconButton
|
||||
label="Delete workflow"
|
||||
helpContextId="workflow.action.delete-definition-parts"
|
||||
helpModuleId="workflow"
|
||||
icon={<Trash2 size={16} />}
|
||||
variant="danger"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
|
||||
@@ -285,6 +285,13 @@ export default function WorkflowRunsDialog({
|
||||
const actionUrl = typeof currentStep?.handoff.action_url === "string"
|
||||
? currentStep.handoff.action_url
|
||||
: "";
|
||||
const unavailableOptionalCapabilities = Array.isArray(
|
||||
currentStep?.handoff.unavailable_optional_capabilities
|
||||
)
|
||||
? currentStep.handoff.unavailable_optional_capabilities.filter(
|
||||
(value): value is string => typeof value === "string" && Boolean(value)
|
||||
)
|
||||
: [];
|
||||
const close = () => {
|
||||
dispatchWorkflowViewChanged(null);
|
||||
onClose();
|
||||
@@ -390,6 +397,15 @@ export default function WorkflowRunsDialog({
|
||||
{selected.error}
|
||||
</DismissibleAlert>
|
||||
) : null}
|
||||
{selected.view_context && !viewsRuntime ? (
|
||||
<DismissibleAlert
|
||||
tone="warning"
|
||||
resetKey={`view-unavailable:${selected.id}:${selected.current_step_id ?? "none"}`}
|
||||
>
|
||||
The focused View is unavailable. Workflow position is preserved;
|
||||
use the linked module action or ask an administrator to enable Views.
|
||||
</DismissibleAlert>
|
||||
) : null}
|
||||
{currentStep ? (
|
||||
<section className="workflow-run-handoff">
|
||||
<div>
|
||||
@@ -417,9 +433,18 @@ export default function WorkflowRunsDialog({
|
||||
&& currentStep.handoff.instructions ? (
|
||||
<p>{currentStep.handoff.instructions}</p>
|
||||
) : null}
|
||||
{unavailableOptionalCapabilities.length ? (
|
||||
<DismissibleAlert
|
||||
tone="info"
|
||||
resetKey={`optional:${currentStep.id}:${unavailableOptionalCapabilities.join(",")}`}
|
||||
>
|
||||
Optional integration unavailable: {unavailableOptionalCapabilities.join(", ")}.
|
||||
The hand-off remains valid and resumable.
|
||||
</DismissibleAlert>
|
||||
) : null}
|
||||
{actionUrl ? (
|
||||
<a href={actionUrl}>
|
||||
Open linked Dataflow result <ExternalLink size={14} />
|
||||
Open linked work <ExternalLink size={14} />
|
||||
</a>
|
||||
) : null}
|
||||
{allowedActions.some((action) => action !== "cancel") ? (
|
||||
|
||||
Reference in New Issue
Block a user