feat: add reusable workflow definition graphs
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from govoplan_core.core.definition_graphs import (
|
||||
DefinitionConfigField,
|
||||
DefinitionGraphConstraints,
|
||||
DefinitionGraphLibrary,
|
||||
DefinitionNodeCountConstraint,
|
||||
DefinitionNodeType,
|
||||
DefinitionPort,
|
||||
)
|
||||
|
||||
|
||||
CATEGORY_LABELS = {
|
||||
"trigger": "Start",
|
||||
"activity": "Activities",
|
||||
"decision": "Decisions",
|
||||
"wait": "Wait",
|
||||
"integration": "Integrations",
|
||||
"outcome": "Outcomes",
|
||||
}
|
||||
|
||||
WORKFLOW_NODE_TYPES = (
|
||||
DefinitionNodeType(
|
||||
type="workflow.start.manual",
|
||||
category="trigger",
|
||||
label="Manual start",
|
||||
description="Start an instance through an explicit user or API action.",
|
||||
icon="circle-play",
|
||||
default_config={"input_schema_ref": ""},
|
||||
config_fields=(
|
||||
DefinitionConfigField(
|
||||
id="input_schema_ref",
|
||||
label="Input schema",
|
||||
kind="text",
|
||||
description="Optional schema contract for instance input.",
|
||||
),
|
||||
),
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.start.event",
|
||||
category="trigger",
|
||||
label="Event start",
|
||||
description="Start when a matching platform event is received.",
|
||||
icon="radio",
|
||||
config_fields=(
|
||||
DefinitionConfigField(
|
||||
id="event_type",
|
||||
label="Event type",
|
||||
kind="text",
|
||||
required=True,
|
||||
),
|
||||
DefinitionConfigField(
|
||||
id="filter",
|
||||
label="Event filter",
|
||||
kind="expression",
|
||||
description="A constrained expression evaluated against the event envelope.",
|
||||
),
|
||||
),
|
||||
default_config={"event_type": "", "filter": ""},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.start.schedule",
|
||||
category="trigger",
|
||||
label="Scheduled start",
|
||||
description="Start according to a governed schedule.",
|
||||
icon="calendar-clock",
|
||||
config_fields=(
|
||||
DefinitionConfigField(
|
||||
id="schedule",
|
||||
label="Schedule",
|
||||
kind="schedule",
|
||||
required=True,
|
||||
),
|
||||
DefinitionConfigField(
|
||||
id="timezone",
|
||||
label="Time zone",
|
||||
kind="timezone",
|
||||
required=True,
|
||||
),
|
||||
),
|
||||
default_config={"schedule": "", "timezone": "Europe/Berlin"},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.activity",
|
||||
category="activity",
|
||||
label="Activity",
|
||||
description="Record and complete a governed unit of work.",
|
||||
icon="square-check-big",
|
||||
input_ports=(DefinitionPort(id="input", label="Input"),),
|
||||
config_fields=(
|
||||
DefinitionConfigField(id="title", label="Title", kind="text", required=True),
|
||||
DefinitionConfigField(id="instructions", label="Instructions", kind="textarea"),
|
||||
DefinitionConfigField(id="assignee", label="Assignee", kind="subject"),
|
||||
DefinitionConfigField(id="due_after", label="Due after", kind="duration"),
|
||||
),
|
||||
default_config={
|
||||
"title": "",
|
||||
"instructions": "",
|
||||
"assignee": "",
|
||||
"due_after": "",
|
||||
},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.review",
|
||||
category="activity",
|
||||
label="Review",
|
||||
description="Pause for a human review with explicit outcomes.",
|
||||
icon="clipboard-check",
|
||||
input_ports=(DefinitionPort(id="input", label="Input"),),
|
||||
output_ports=(
|
||||
DefinitionPort(id="approved", label="Approved", required=False),
|
||||
DefinitionPort(id="changes", label="Changes requested", required=False),
|
||||
DefinitionPort(id="rejected", label="Rejected", required=False),
|
||||
),
|
||||
config_fields=(
|
||||
DefinitionConfigField(id="title", label="Title", kind="text", required=True),
|
||||
DefinitionConfigField(id="reviewer", label="Reviewer", kind="subject"),
|
||||
DefinitionConfigField(
|
||||
id="required_evidence",
|
||||
label="Required evidence",
|
||||
kind="string_list",
|
||||
),
|
||||
),
|
||||
default_config={"title": "", "reviewer": "", "required_evidence": []},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.decision",
|
||||
category="decision",
|
||||
label="Decision",
|
||||
description="Choose a path using a constrained expression.",
|
||||
icon="split",
|
||||
input_ports=(DefinitionPort(id="input", label="Input"),),
|
||||
output_ports=(
|
||||
DefinitionPort(id="true", label="Matches", required=False),
|
||||
DefinitionPort(id="false", label="Does not match", required=False),
|
||||
),
|
||||
config_fields=(
|
||||
DefinitionConfigField(
|
||||
id="expression",
|
||||
label="Expression",
|
||||
kind="expression",
|
||||
required=True,
|
||||
),
|
||||
),
|
||||
default_config={"expression": ""},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.wait",
|
||||
category="wait",
|
||||
label="Wait",
|
||||
description="Wait for a duration, deadline, event, or explicit resume action.",
|
||||
icon="timer",
|
||||
input_ports=(DefinitionPort(id="input", label="Input"),),
|
||||
output_ports=(
|
||||
DefinitionPort(id="resumed", label="Resumed", required=False),
|
||||
DefinitionPort(id="timed_out", label="Timed out", required=False),
|
||||
),
|
||||
config_fields=(
|
||||
DefinitionConfigField(
|
||||
id="mode",
|
||||
label="Wait for",
|
||||
kind="select",
|
||||
required=True,
|
||||
options=(
|
||||
("duration", "Duration"),
|
||||
("deadline", "Deadline"),
|
||||
("event", "Event"),
|
||||
("manual", "Manual resume"),
|
||||
),
|
||||
),
|
||||
DefinitionConfigField(id="value", label="Value", kind="text"),
|
||||
),
|
||||
default_config={"mode": "manual", "value": ""},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.capability",
|
||||
category="integration",
|
||||
label="Module action",
|
||||
description="Invoke a versioned module capability without importing its implementation.",
|
||||
icon="plug-zap",
|
||||
input_ports=(DefinitionPort(id="input", label="Input"),),
|
||||
output_ports=(
|
||||
DefinitionPort(id="success", label="Success", required=False),
|
||||
DefinitionPort(id="failure", label="Failure", required=False),
|
||||
),
|
||||
config_fields=(
|
||||
DefinitionConfigField(
|
||||
id="capability",
|
||||
label="Capability",
|
||||
kind="capability",
|
||||
required=True,
|
||||
),
|
||||
DefinitionConfigField(
|
||||
id="operation",
|
||||
label="Operation",
|
||||
kind="text",
|
||||
required=True,
|
||||
),
|
||||
DefinitionConfigField(id="input_mapping", label="Input mapping", kind="mapping"),
|
||||
DefinitionConfigField(
|
||||
id="idempotency_key",
|
||||
label="Idempotency key",
|
||||
kind="expression",
|
||||
required=True,
|
||||
),
|
||||
DefinitionConfigField(
|
||||
id="failure_policy",
|
||||
label="On failure",
|
||||
kind="select",
|
||||
required=True,
|
||||
options=(
|
||||
("retry", "Retry"),
|
||||
("manual", "Require intervention"),
|
||||
("continue", "Continue on failure"),
|
||||
),
|
||||
),
|
||||
),
|
||||
default_config={
|
||||
"capability": "",
|
||||
"operation": "",
|
||||
"input_mapping": {},
|
||||
"idempotency_key": "",
|
||||
"failure_policy": "manual",
|
||||
},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.dataflow",
|
||||
category="integration",
|
||||
label="Run dataflow",
|
||||
description="Run a published Dataflow pipeline and retain its output reference.",
|
||||
icon="waypoints",
|
||||
input_ports=(DefinitionPort(id="input", label="Input"),),
|
||||
output_ports=(
|
||||
DefinitionPort(id="success", label="Success", required=False),
|
||||
DefinitionPort(id="failure", label="Failure", required=False),
|
||||
),
|
||||
config_fields=(
|
||||
DefinitionConfigField(
|
||||
id="pipeline_ref",
|
||||
label="Pipeline",
|
||||
kind="dataflow",
|
||||
required=True,
|
||||
),
|
||||
DefinitionConfigField(id="input_mapping", label="Input mapping", kind="mapping"),
|
||||
),
|
||||
default_config={"pipeline_ref": "", "input_mapping": {}},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.end.completed",
|
||||
category="outcome",
|
||||
label="Completed",
|
||||
description="Complete the workflow successfully.",
|
||||
icon="circle-check-big",
|
||||
input_ports=(
|
||||
DefinitionPort(
|
||||
id="input",
|
||||
label="Input",
|
||||
multiple=True,
|
||||
minimum_connections=1,
|
||||
),
|
||||
),
|
||||
output_ports=(),
|
||||
config_fields=(
|
||||
DefinitionConfigField(id="output_mapping", label="Output mapping", kind="mapping"),
|
||||
),
|
||||
default_config={"output_mapping": {}},
|
||||
),
|
||||
DefinitionNodeType(
|
||||
type="workflow.end.cancelled",
|
||||
category="outcome",
|
||||
label="Cancelled",
|
||||
description="End the workflow without a successful outcome.",
|
||||
icon="circle-x",
|
||||
input_ports=(
|
||||
DefinitionPort(
|
||||
id="input",
|
||||
label="Input",
|
||||
multiple=True,
|
||||
minimum_connections=1,
|
||||
),
|
||||
),
|
||||
output_ports=(),
|
||||
config_fields=(
|
||||
DefinitionConfigField(id="reason", label="Reason", kind="text"),
|
||||
),
|
||||
default_config={"reason": ""},
|
||||
),
|
||||
)
|
||||
|
||||
WORKFLOW_GRAPH_LIBRARY = DefinitionGraphLibrary(
|
||||
id="workflow",
|
||||
version="0.1.0",
|
||||
category_labels=CATEGORY_LABELS,
|
||||
node_types=WORKFLOW_NODE_TYPES,
|
||||
constraints=DefinitionGraphConstraints(
|
||||
max_nodes=150,
|
||||
max_edges=300,
|
||||
allow_cycles=True,
|
||||
require_connected=True,
|
||||
node_counts=(
|
||||
DefinitionNodeCountConstraint(
|
||||
code="graph.trigger_count",
|
||||
label="start",
|
||||
minimum=1,
|
||||
maximum=1,
|
||||
categories=("trigger",),
|
||||
),
|
||||
DefinitionNodeCountConstraint(
|
||||
code="graph.outcome_count",
|
||||
label="outcome",
|
||||
minimum=1,
|
||||
categories=("outcome",),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
WORKFLOW_NODE_TYPES_BY_ID = {
|
||||
definition.type: definition for definition in WORKFLOW_NODE_TYPES
|
||||
}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CATEGORY_LABELS",
|
||||
"WORKFLOW_GRAPH_LIBRARY",
|
||||
"WORKFLOW_NODE_TYPES",
|
||||
"WORKFLOW_NODE_TYPES_BY_ID",
|
||||
]
|
||||
Reference in New Issue
Block a user