Step 430: add workflow dependency graph view model
This commit is contained in:
@@ -2749,4 +2749,13 @@ target_link_libraries(step429_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step430_test tests/step430_test.cpp)
|
||||
target_include_directories(step430_test PRIVATE src)
|
||||
target_link_libraries(step430_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
94
editor/src/WorkflowDependencyView.h
Normal file
94
editor/src/WorkflowDependencyView.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "DependencyGraph.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct DependencyNodeView {
|
||||
std::string id;
|
||||
std::string label;
|
||||
std::string status;
|
||||
std::string workerType;
|
||||
std::string priority;
|
||||
std::string colorHex;
|
||||
bool criticalPath = false;
|
||||
std::string hoverText;
|
||||
std::string taskId;
|
||||
};
|
||||
|
||||
struct DependencyEdgeView {
|
||||
std::string from;
|
||||
std::string to;
|
||||
bool criticalPath = false;
|
||||
};
|
||||
|
||||
struct DependencyGraphView {
|
||||
std::vector<DependencyNodeView> nodes;
|
||||
std::vector<DependencyEdgeView> edges;
|
||||
std::vector<std::string> criticalPath;
|
||||
|
||||
std::optional<DependencyNodeView> getNode(const std::string& id) const {
|
||||
for (const auto& n : nodes) if (n.id == id) return n;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
class WorkflowDependencyView {
|
||||
public:
|
||||
static DependencyGraphView build(const WorkflowState& wf) {
|
||||
auto graph = buildDependencyGraph(wf);
|
||||
auto critical = getCriticalPath(graph);
|
||||
std::set<std::string> criticalSet(critical.begin(), critical.end());
|
||||
std::set<std::string> criticalEdges;
|
||||
for (size_t i = 1; i < critical.size(); ++i) {
|
||||
criticalEdges.insert(critical[i - 1] + "->" + critical[i]);
|
||||
}
|
||||
|
||||
DependencyGraphView out;
|
||||
out.criticalPath = critical;
|
||||
|
||||
for (const auto& n : graph.nodes) {
|
||||
DependencyNodeView v;
|
||||
v.id = n.id;
|
||||
v.label = n.label;
|
||||
v.status = n.status;
|
||||
v.workerType = n.workerType;
|
||||
v.priority = n.priority;
|
||||
v.colorHex = colorForStatus(n.status);
|
||||
v.criticalPath = criticalSet.count(n.id) > 0;
|
||||
v.taskId = n.id;
|
||||
v.hoverText = "Task: " + n.label + "\nStatus: " + n.status +
|
||||
"\nWorker: " + n.workerType +
|
||||
"\nPriority: " + n.priority;
|
||||
out.nodes.push_back(v);
|
||||
}
|
||||
|
||||
for (const auto& e : graph.edges) {
|
||||
DependencyEdgeView ev;
|
||||
ev.from = e.from;
|
||||
ev.to = e.to;
|
||||
ev.criticalPath = criticalEdges.count(e.from + "->" + e.to) > 0;
|
||||
out.edges.push_back(ev);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string colorForStatus(const std::string& status) {
|
||||
if (status == WI_COMPLETE) return "#2ECC71"; // green
|
||||
if (status == WI_IN_PROGRESS || status == WI_ASSIGNED) return "#3498DB"; // blue
|
||||
if (status == WI_PENDING || status == WI_READY) return "#95A5A6"; // gray
|
||||
if (status == WI_REVIEW || status == WI_REJECTED) return "#E74C3C"; // red
|
||||
return "#7F8C8D";
|
||||
}
|
||||
|
||||
static std::string navigateToTaskId(const DependencyGraphView& view,
|
||||
const std::string& nodeId) {
|
||||
auto n = view.getNode(nodeId);
|
||||
if (!n.has_value()) return "";
|
||||
return n->taskId;
|
||||
}
|
||||
};
|
||||
155
editor/tests/step430_test.cpp
Normal file
155
editor/tests/step430_test.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
// Step 430: Dependency Graph Visualization Tests (12 tests)
|
||||
|
||||
#include "WorkflowDependencyView.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
static WorkItem makeItem(const std::string& id, const std::string& status,
|
||||
const std::vector<std::string>& deps = {}) {
|
||||
WorkItem wi;
|
||||
wi.id = id;
|
||||
wi.nodeId = id + "_node";
|
||||
wi.nodeName = id;
|
||||
wi.nodeType = "Function";
|
||||
wi.status = status;
|
||||
wi.workerType = "llm";
|
||||
wi.priority = "medium";
|
||||
wi.dependencies = deps;
|
||||
return wi;
|
||||
}
|
||||
|
||||
static WorkflowState makeChainWorkflow() {
|
||||
WorkflowState wf("graph");
|
||||
wf.queue.enqueue(makeItem("a", WI_COMPLETE, {}));
|
||||
wf.queue.enqueue(makeItem("b", WI_IN_PROGRESS, {"a"}));
|
||||
wf.queue.enqueue(makeItem("c", WI_READY, {"b"}));
|
||||
return wf;
|
||||
}
|
||||
|
||||
void test_build_graph_view_node_and_edge_counts() {
|
||||
TEST(build_graph_view_node_and_edge_counts);
|
||||
auto view = WorkflowDependencyView::build(makeChainWorkflow());
|
||||
CHECK(view.nodes.size() == 3, "expected 3 nodes");
|
||||
CHECK(view.edges.size() == 2, "expected 2 edges");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_status_color_complete_green() {
|
||||
TEST(status_color_complete_green);
|
||||
CHECK(WorkflowDependencyView::colorForStatus(WI_COMPLETE) == "#2ECC71",
|
||||
"complete should be green");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_status_color_in_progress_blue() {
|
||||
TEST(status_color_in_progress_blue);
|
||||
CHECK(WorkflowDependencyView::colorForStatus(WI_IN_PROGRESS) == "#3498DB",
|
||||
"in-progress should be blue");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_status_color_pending_ready_gray() {
|
||||
TEST(status_color_pending_ready_gray);
|
||||
CHECK(WorkflowDependencyView::colorForStatus(WI_PENDING) == "#95A5A6", "pending gray");
|
||||
CHECK(WorkflowDependencyView::colorForStatus(WI_READY) == "#95A5A6", "ready gray");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_status_color_review_red() {
|
||||
TEST(status_color_review_red);
|
||||
CHECK(WorkflowDependencyView::colorForStatus(WI_REVIEW) == "#E74C3C", "review red");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_critical_path_detected_for_chain() {
|
||||
TEST(critical_path_detected_for_chain);
|
||||
auto view = WorkflowDependencyView::build(makeChainWorkflow());
|
||||
CHECK(view.criticalPath.size() == 3, "chain critical path should be 3");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_critical_path_nodes_marked() {
|
||||
TEST(critical_path_nodes_marked);
|
||||
auto view = WorkflowDependencyView::build(makeChainWorkflow());
|
||||
int crit = 0;
|
||||
for (const auto& n : view.nodes) if (n.criticalPath) ++crit;
|
||||
CHECK(crit >= 3, "all chain nodes should be critical");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_critical_path_edges_marked() {
|
||||
TEST(critical_path_edges_marked);
|
||||
auto view = WorkflowDependencyView::build(makeChainWorkflow());
|
||||
int crit = 0;
|
||||
for (const auto& e : view.edges) if (e.criticalPath) ++crit;
|
||||
CHECK(crit == 2, "both chain edges should be critical");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_hover_text_contains_details() {
|
||||
TEST(hover_text_contains_details);
|
||||
auto view = WorkflowDependencyView::build(makeChainWorkflow());
|
||||
auto node = view.getNode("b");
|
||||
CHECK(node.has_value(), "node b missing");
|
||||
CHECK(node->hoverText.find("Status:") != std::string::npos, "hover missing status");
|
||||
CHECK(node->hoverText.find("Worker:") != std::string::npos, "hover missing worker");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_navigate_to_task_id_from_node_click() {
|
||||
TEST(navigate_to_task_id_from_node_click);
|
||||
auto view = WorkflowDependencyView::build(makeChainWorkflow());
|
||||
std::string taskId = WorkflowDependencyView::navigateToTaskId(view, "c");
|
||||
CHECK(taskId == "c", "navigation should return task id");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_navigate_missing_node_returns_empty() {
|
||||
TEST(navigate_missing_node_returns_empty);
|
||||
auto view = WorkflowDependencyView::build(makeChainWorkflow());
|
||||
CHECK(WorkflowDependencyView::navigateToTaskId(view, "missing").empty(),
|
||||
"missing node should return empty task id");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_graph_updates_after_status_change() {
|
||||
TEST(graph_updates_after_status_change);
|
||||
auto wf = makeChainWorkflow();
|
||||
auto v1 = WorkflowDependencyView::build(wf);
|
||||
auto b1 = v1.getNode("b");
|
||||
CHECK(b1.has_value() && b1->colorHex == "#3498DB", "b should start blue");
|
||||
auto item = wf.queue.getItem("b");
|
||||
item->status = WI_COMPLETE;
|
||||
wf.queue.updateItem("b", *item);
|
||||
auto v2 = WorkflowDependencyView::build(wf);
|
||||
auto b2 = v2.getNode("b");
|
||||
CHECK(b2.has_value() && b2->colorHex == "#2ECC71", "b should turn green");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 430: Dependency Graph Visualization Tests\n";
|
||||
|
||||
test_build_graph_view_node_and_edge_counts(); // 1
|
||||
test_status_color_complete_green(); // 2
|
||||
test_status_color_in_progress_blue(); // 3
|
||||
test_status_color_pending_ready_gray(); // 4
|
||||
test_status_color_review_red(); // 5
|
||||
test_critical_path_detected_for_chain(); // 6
|
||||
test_critical_path_nodes_marked(); // 7
|
||||
test_critical_path_edges_marked(); // 8
|
||||
test_hover_text_contains_details(); // 9
|
||||
test_navigate_to_task_id_from_node_click(); // 10
|
||||
test_navigate_missing_node_returns_empty(); // 11
|
||||
test_graph_updates_after_status_change(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||
<< " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
48
progress.md
48
progress.md
@@ -4852,6 +4852,54 @@ rejection operations in one consistent view model.
|
||||
- `editor/src/MCPServer.h` (`1940` > `600`)
|
||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`)
|
||||
|
||||
### Step 430: Dependency Graph Visualization
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Added a dependency-graph visualization adapter that projects workflow
|
||||
dependencies into render-ready node/edge view data, including status-based color
|
||||
coding, critical-path highlighting, hover details, and click navigation IDs.
|
||||
|
||||
**Files created:**
|
||||
- `editor/src/WorkflowDependencyView.h` — dependency-view support:
|
||||
- node/edge view structs for DAG rendering
|
||||
- status->color mapping:
|
||||
- complete: green
|
||||
- in-progress/assigned: blue
|
||||
- pending/ready: gray
|
||||
- review/rejected: red
|
||||
- critical path integration from `DependencyGraph.h`
|
||||
- critical-edge tagging
|
||||
- hover payload synthesis and task-navigation mapping
|
||||
- `editor/tests/step430_test.cpp` — 12 tests covering:
|
||||
1. graph node/edge projection counts
|
||||
2. complete color mapping
|
||||
3. in-progress color mapping
|
||||
4. pending/ready color mapping
|
||||
5. review color mapping
|
||||
6. critical-path detection on chain
|
||||
7. critical-path node tagging
|
||||
8. critical-path edge tagging
|
||||
9. hover payload content
|
||||
10. node->task navigation ID mapping
|
||||
11. missing-node navigation edge case
|
||||
12. re-render update after status change
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` — `step430_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `step430_test` — PASS (12/12) new step coverage
|
||||
- `step429_test` — PASS (12/12) regression coverage
|
||||
- `step428_test` — PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/WorkflowDependencyView.h` within header-size limit (`94` <= `600`)
|
||||
- `editor/tests/step430_test.cpp` within test-file size guidance (`155` lines)
|
||||
- Legacy oversized headers persist:
|
||||
- `editor/src/ast/Serialization.h` (`1427` > `600`)
|
||||
- `editor/src/MCPServer.h` (`1940` > `600`)
|
||||
- `editor/src/HeadlessAgentRPCHandler.h` (`2768` > `600`)
|
||||
|
||||
# Roadmap Planning — Sprints 12-25+
|
||||
|
||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||
|
||||
Reference in New Issue
Block a user