feat: add lda report workflow example

This commit is contained in:
lda
2026-07-01 07:14:25 +07:00 Verified
parent 9e313cd5d0
commit 7bdfa3132d
18 changed files with 1610 additions and 2 deletions
@@ -0,0 +1,119 @@
from __future__ import annotations
import json
import tempfile
from pathlib import Path
from pydantic import BaseModel, Field
from wf_authoring import node
from .report_source import CreatedIssue, ProposedIssue
_EXAMPLE_DIR = Path(__file__).resolve().parent
class ResetIssueBoardInput(BaseModel):
board_path: str = Field(default="issue-board.json")
class ResetIssueBoardOutput(BaseModel):
reset: bool
board_path: str
class CreateIssuesInput(BaseModel):
issues: list[ProposedIssue]
selected_issue_ids: list[str]
board_path: str = Field(default="issue-board.json")
class CreateIssuesOutput(BaseModel):
created_issues: list[CreatedIssue]
board_path: str
@node(name="reset_issue_board", description="Reset the local demo issue board file.")
def reset_issue_board(payload: ResetIssueBoardInput) -> ResetIssueBoardOutput:
return _reset_issue_board(payload)
@node(
name="create_issues",
description="Create selected issues in the local demo issue board.",
)
def create_issues(payload: CreateIssuesInput) -> CreateIssuesOutput:
return _create_issues(payload)
def _reset_issue_board(payload: ResetIssueBoardInput) -> ResetIssueBoardOutput:
path = _resolve_board_path(payload.board_path)
path.unlink(missing_ok=True)
return ResetIssueBoardOutput(reset=True, board_path=str(path))
def _create_issues(payload: CreateIssuesInput) -> CreateIssuesOutput:
path = _resolve_board_path(payload.board_path)
selected = set(payload.selected_issue_ids)
existing = _read_board(path)
created: list[CreatedIssue] = []
next_number = len(existing) + 1
for issue in payload.issues:
if issue.id not in selected:
continue
issue_id = f"ISSUE-{next_number:03d}"
created_issue = CreatedIssue(
id=issue_id,
title=issue.title,
url=f"local://issue-board/{issue_id}",
)
existing.append(
{
"id": created_issue.id,
"title": created_issue.title,
"url": created_issue.url,
"body": issue.body,
"severity": issue.severity,
}
)
created.append(created_issue)
next_number += 1
_write_board(path, existing)
return CreateIssuesOutput(created_issues=created, board_path=str(path))
def _resolve_board_path(path: str) -> Path:
candidate = Path(path)
if candidate.is_absolute():
resolved = candidate.resolve()
else:
resolved = (_EXAMPLE_DIR / candidate).resolve()
# Keep the demo capability from deleting or writing arbitrary files while still
# allowing pytest tmp_path fixtures to exercise isolated board files.
allowed_roots = [_EXAMPLE_DIR.resolve(), Path(tempfile.gettempdir()).resolve()]
if not any(_is_relative_to(resolved, root) for root in allowed_roots):
raise ValueError("board_path must stay inside the example dir or temp dir")
return resolved
def _is_relative_to(path: Path, root: Path) -> bool:
return path == root or path.is_relative_to(root)
def _read_board(path: Path) -> list[dict[str, str]]:
if not path.exists():
return []
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, list):
raise ValueError("issue board file must contain a JSON list")
return [item for item in value if isinstance(item, dict)]
def _write_board(path: Path, value: list[dict[str, str]]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_suffix(path.suffix + ".tmp")
tmp.write_text(json.dumps(value, indent=2, sort_keys=True), encoding="utf-8")
tmp.replace(path)
registry = [reset_issue_board, create_issues]