feat: expand module workflows, dynamic config, notifications and console API
This commit is contained in:
@@ -42,6 +42,15 @@ class WorkflowDataFlow:
|
||||
destinations: tuple[WorkflowDataEndpoint, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class WorkflowTopicConfig:
|
||||
"""Operator-facing topic input metadata for a workflow."""
|
||||
|
||||
default_keyword: str
|
||||
label: str = "话题关键词"
|
||||
hint: str = "输入完整话题或关键词,系统会在搜索结果中按包含关系选中。"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class WorkflowStepEntry:
|
||||
step_id: str
|
||||
@@ -52,8 +61,9 @@ class WorkflowStepEntry:
|
||||
depends_on: tuple[str, ...] = ()
|
||||
run_after_failure: bool = False
|
||||
timeout_seconds: float | None = None
|
||||
replay_policy: Literal["guarded", "idempotent"] | None = None
|
||||
replay_policy: Literal["guarded", "idempotent", "repeatable"] | None = None
|
||||
data_flow: WorkflowDataFlow | None = None
|
||||
has_hyperlink: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
@@ -67,6 +77,8 @@ class WorkflowEntry:
|
||||
source_task_name: str | None = None
|
||||
note: str | None = None
|
||||
steps: tuple[WorkflowStepEntry, ...] = ()
|
||||
notification_workflow_id: str | None = None
|
||||
topic_config: WorkflowTopicConfig | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
@@ -205,6 +217,7 @@ def _parse_workflow(item: Any) -> WorkflowEntry:
|
||||
task_name = provenance.get("task_name")
|
||||
if trigger == "scheduled" and (not isinstance(task_name, str) or not task_name):
|
||||
raise CatalogError(f"scheduled workflow requires source task name: {workflow_id}")
|
||||
topic_config = _parse_topic_config(item.get("topic_config"), workflow_id)
|
||||
return WorkflowEntry(
|
||||
workflow_id=workflow_id,
|
||||
module=module,
|
||||
@@ -215,6 +228,37 @@ def _parse_workflow(item: Any) -> WorkflowEntry:
|
||||
source_task_name=task_name,
|
||||
note=item.get("note"),
|
||||
steps=steps,
|
||||
topic_config=topic_config,
|
||||
)
|
||||
|
||||
|
||||
def _parse_topic_config(value: Any, workflow_id: str) -> WorkflowTopicConfig | None:
|
||||
if value is None:
|
||||
return None
|
||||
if not isinstance(value, dict):
|
||||
raise CatalogError(f"topic_config must be an object for {workflow_id}")
|
||||
default_keyword = value.get("default_keyword")
|
||||
if (
|
||||
not isinstance(default_keyword, str)
|
||||
or not default_keyword.strip()
|
||||
or len(default_keyword.strip()) > 120
|
||||
):
|
||||
raise CatalogError(
|
||||
f"topic_config default_keyword is invalid for {workflow_id}"
|
||||
)
|
||||
fields: dict[str, str] = {}
|
||||
for field, fallback in (
|
||||
("label", "话题关键词"),
|
||||
("hint", "输入完整话题或关键词,系统会在搜索结果中按包含关系选中。"),
|
||||
):
|
||||
raw = value.get(field, fallback)
|
||||
if not isinstance(raw, str) or not raw.strip() or len(raw.strip()) > 240:
|
||||
raise CatalogError(f"topic_config {field} is invalid for {workflow_id}")
|
||||
fields[field] = raw.strip()
|
||||
return WorkflowTopicConfig(
|
||||
default_keyword=default_keyword.strip(),
|
||||
label=fields["label"],
|
||||
hint=fields["hint"],
|
||||
)
|
||||
|
||||
|
||||
@@ -263,6 +307,7 @@ def _parse_workflow_steps(
|
||||
if replay_policy is not None and replay_policy not in {
|
||||
"guarded",
|
||||
"idempotent",
|
||||
"repeatable",
|
||||
}:
|
||||
raise CatalogError(
|
||||
f"workflow step replay_policy is invalid for {workflow_id}.{step_id}"
|
||||
@@ -279,12 +324,18 @@ def _parse_workflow_steps(
|
||||
raise CatalogError(
|
||||
f"workflow step description is invalid for {workflow_id}.{step_id}"
|
||||
)
|
||||
has_hyperlink = raw_step.get("has_hyperlink", False)
|
||||
if not isinstance(has_hyperlink, bool):
|
||||
raise CatalogError(
|
||||
f"workflow step has_hyperlink is invalid for {workflow_id}.{step_id}"
|
||||
)
|
||||
steps.append(
|
||||
WorkflowStepEntry(
|
||||
step_id=step_id,
|
||||
entry=entry,
|
||||
name=name.strip() if name is not None else None,
|
||||
description=description.strip() if description is not None else None,
|
||||
has_hyperlink=has_hyperlink,
|
||||
args=_parse_args(raw_step.get("args", []), workflow_id),
|
||||
depends_on=tuple(depends_on),
|
||||
run_after_failure=run_after_failure,
|
||||
|
||||
Reference in New Issue
Block a user