docs(02): create phase plan - 3 plans, 2 waves
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
261
.planning/phases/02-transaction-search-diagrams/02-02-PLAN.md
Normal file
261
.planning/phases/02-transaction-search-diagrams/02-02-PLAN.md
Normal file
@@ -0,0 +1,261 @@
|
||||
---
|
||||
phase: 02-transaction-search-diagrams
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- cameleer3-server-app/pom.xml
|
||||
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java
|
||||
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramLayout.java
|
||||
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedNode.java
|
||||
- cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedEdge.java
|
||||
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java
|
||||
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java
|
||||
- cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/DiagramBeanConfig.java
|
||||
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramRenderControllerIT.java
|
||||
- cameleer3-server-app/src/test/java/com/cameleer3/server/app/diagram/ElkDiagramRendererTest.java
|
||||
autonomous: true
|
||||
requirements:
|
||||
- DIAG-03
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "GET /api/v1/diagrams/{hash} with Accept: image/svg+xml returns an SVG document with color-coded nodes"
|
||||
- "GET /api/v1/diagrams/{hash} with Accept: application/json returns a JSON layout with node positions"
|
||||
- "Nodes are laid out top-to-bottom using ELK layered algorithm"
|
||||
- "Node colors match the route-diagram-example.html style: blue endpoints, green processors, red error handlers, purple EIPs"
|
||||
- "Nested processors (inside split, choice, try-catch) are rendered in compound/swimlane groups"
|
||||
artifacts:
|
||||
- path: "cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java"
|
||||
provides: "Renderer interface for SVG and JSON layout output"
|
||||
exports: ["DiagramRenderer"]
|
||||
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java"
|
||||
provides: "ELK + JFreeSVG implementation of DiagramRenderer"
|
||||
min_lines: 100
|
||||
- path: "cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java"
|
||||
provides: "GET /api/v1/diagrams/{hash} with content negotiation"
|
||||
exports: ["DiagramRenderController"]
|
||||
key_links:
|
||||
- from: "DiagramRenderController"
|
||||
to: "DiagramRepository"
|
||||
via: "findByContentHash to load RouteGraph"
|
||||
pattern: "findByContentHash"
|
||||
- from: "DiagramRenderController"
|
||||
to: "DiagramRenderer"
|
||||
via: "renderSvg or layoutJson based on Accept header"
|
||||
pattern: "renderSvg|layoutJson"
|
||||
- from: "ElkDiagramRenderer"
|
||||
to: "ELK RecursiveGraphLayoutEngine"
|
||||
via: "layout computation"
|
||||
pattern: "RecursiveGraphLayoutEngine"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement route diagram rendering with Eclipse ELK for layout and JFreeSVG for SVG output, exposed via a REST endpoint with content negotiation.
|
||||
|
||||
Purpose: Users need to visualize route diagrams from stored RouteGraph definitions. The server renders color-coded, top-to-bottom SVG diagrams or returns JSON layout data for client-side rendering. This is independent of the search work and can run in parallel.
|
||||
|
||||
Output: DiagramRenderer interface in core, ElkDiagramRenderer implementation in app, DiagramRenderController with Accept header content negotiation, integration and unit tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/phases/02-transaction-search-diagrams/02-CONTEXT.md
|
||||
@.planning/phases/02-transaction-search-diagrams/02-RESEARCH.md
|
||||
|
||||
@cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/DiagramRepository.java
|
||||
@cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/ClickHouseDiagramRepository.java
|
||||
@cameleer3-server-app/pom.xml
|
||||
|
||||
<interfaces>
|
||||
<!-- Existing interfaces needed -->
|
||||
|
||||
From cameleer3-server-core/.../storage/DiagramRepository.java:
|
||||
```java
|
||||
public interface DiagramRepository {
|
||||
void store(RouteGraph graph);
|
||||
Optional<RouteGraph> findByContentHash(String contentHash);
|
||||
Optional<String> findContentHashForRoute(String routeId, String agentId);
|
||||
}
|
||||
```
|
||||
|
||||
From cameleer3-common (decompiled — diagram models):
|
||||
```java
|
||||
// RouteGraph: routeId (String), nodes (List<RouteNode>), edges (List<RouteEdge>),
|
||||
// processorNodeMapping (Map<String,String>)
|
||||
// RouteNode: id (String), label (String), type (NodeType), properties (Map<String,String>)
|
||||
// RouteEdge: source (String), target (String), label (String)
|
||||
// NodeType enum: ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA, PROCESSOR, BEAN, LOG,
|
||||
// SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL, CHOICE, WHEN, OTHERWISE,
|
||||
// SPLIT, AGGREGATE, MULTICAST, FILTER, RECIPIENT_LIST, ROUTING_SLIP, DYNAMIC_ROUTER,
|
||||
// LOAD_BALANCE, THROTTLE, DELAY, ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY,
|
||||
// DO_CATCH, DO_FINALLY, WIRE_TAP, ENRICH, POLL_ENRICH, SORT, RESEQUENCE,
|
||||
// IDEMPOTENT_CONSUMER, CIRCUIT_BREAKER, SAGA, LOOP
|
||||
```
|
||||
|
||||
NodeType color mapping (from CONTEXT.md, matching route-diagram-example.html):
|
||||
- Blue (#3B82F6): ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA (endpoints)
|
||||
- Green (#22C55E): PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL (processors)
|
||||
- Red (#EF4444): ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY (error handling)
|
||||
- Purple (#A855F7): CHOICE, WHEN, OTHERWISE, SPLIT, AGGREGATE, MULTICAST, FILTER, etc. (EIP patterns)
|
||||
- Cyan (#06B6D4): WIRE_TAP, ENRICH, POLL_ENRICH (cross-route)
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add ELK/JFreeSVG dependencies and create core diagram rendering interfaces</name>
|
||||
<files>
|
||||
cameleer3-server-app/pom.xml,
|
||||
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramRenderer.java,
|
||||
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/DiagramLayout.java,
|
||||
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedNode.java,
|
||||
cameleer3-server-core/src/main/java/com/cameleer3/server/core/diagram/PositionedEdge.java
|
||||
</files>
|
||||
<action>
|
||||
1. Add Maven dependencies to `cameleer3-server-app/pom.xml`:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.eclipse.elk</groupId>
|
||||
<artifactId>org.eclipse.elk.core</artifactId>
|
||||
<version>0.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.elk</groupId>
|
||||
<artifactId>org.eclipse.elk.alg.layered</artifactId>
|
||||
<version>0.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jfree</groupId>
|
||||
<artifactId>org.jfree.svg</artifactId>
|
||||
<version>5.0.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2. Create core diagram rendering interfaces in `com.cameleer3.server.core.diagram`:
|
||||
|
||||
- `PositionedNode` record: id (String), label (String), type (String — NodeType name), x (double), y (double), width (double), height (double), children (List<PositionedNode> — for compound/swimlane groups). JSON-serializable for the JSON layout response.
|
||||
|
||||
- `PositionedEdge` record: sourceId (String), targetId (String), label (String), points (List<double[]> — waypoints for edge routing). The points list contains [x,y] pairs from source to target.
|
||||
|
||||
- `DiagramLayout` record: width (double), height (double), nodes (List<PositionedNode>), edges (List<PositionedEdge>). This is the JSON layout response format.
|
||||
|
||||
- `DiagramRenderer` interface with two methods:
|
||||
- `String renderSvg(RouteGraph graph)` — returns SVG XML string
|
||||
- `DiagramLayout layoutJson(RouteGraph graph)` — returns positioned layout data
|
||||
Both methods take a RouteGraph and produce output. The interface lives in core so it can be swapped (e.g., for a different renderer).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn compile -pl cameleer3-server-core && mvn dependency:resolve -pl cameleer3-server-app -q</automated>
|
||||
</verify>
|
||||
<done>ELK and JFreeSVG dependencies resolve, DiagramRenderer interface and layout DTOs compile in core module</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Implement ElkDiagramRenderer, DiagramRenderController, and integration tests</name>
|
||||
<files>
|
||||
cameleer3-server-app/src/main/java/com/cameleer3/server/app/diagram/ElkDiagramRenderer.java,
|
||||
cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/DiagramRenderController.java,
|
||||
cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/DiagramBeanConfig.java,
|
||||
cameleer3-server-app/src/test/java/com/cameleer3/server/app/diagram/ElkDiagramRendererTest.java,
|
||||
cameleer3-server-app/src/test/java/com/cameleer3/server/app/controller/DiagramRenderControllerIT.java
|
||||
</files>
|
||||
<behavior>
|
||||
- Unit test: ElkDiagramRenderer.renderSvg with a simple 3-node graph (from->process->to) produces valid SVG containing svg element, rect elements for nodes, line/path elements for edges
|
||||
- Unit test: ElkDiagramRenderer.renderSvg produces SVG where endpoint nodes have blue fill (#3B82F6 or rgb equivalent)
|
||||
- Unit test: ElkDiagramRenderer.layoutJson returns DiagramLayout with correct node count and positive coordinates
|
||||
- Unit test: Nested processors (e.g., CHOICE with WHEN children) are laid out as compound nodes with children inside parent bounds
|
||||
- Integration test: GET /api/v1/diagrams/{hash} with Accept: image/svg+xml returns 200 with content-type image/svg+xml and body starting with '<svg' or '<?xml'
|
||||
- Integration test: GET /api/v1/diagrams/{hash} with Accept: application/json returns 200 with JSON containing 'nodes' and 'edges' arrays
|
||||
- Integration test: GET /api/v1/diagrams/{nonexistent-hash} returns 404
|
||||
- Integration test: GET /api/v1/diagrams/{hash} with no Accept preference defaults to SVG
|
||||
</behavior>
|
||||
<action>
|
||||
1. Create `ElkDiagramRenderer` implementing `DiagramRenderer` in `com.cameleer3.server.app.diagram`:
|
||||
|
||||
**Layout phase (shared by both SVG and JSON):**
|
||||
- Convert RouteGraph to ELK graph: create ElkNode root, set properties for LayeredOptions.ALGORITHM_ID, Direction.DOWN (top-to-bottom per user decision), spacing 40px node-node, 20px edge-node.
|
||||
- For each RouteNode: create ElkNode with estimated width (based on label length * 8 + 32, min 80) and height (40). Set identifier to node.id.
|
||||
- For compound/nesting nodes (CHOICE, SPLIT, TRY_CATCH, DO_TRY, LOOP, MULTICAST, AGGREGATE): create these as compound ElkNodes. Identify children by examining RouteEdge topology — nodes whose only incoming edge is from the compound node AND have no outgoing edge leaving the compound scope are children. Alternatively, use the NodeType hierarchy: WHEN/OTHERWISE are children of CHOICE, DO_CATCH/DO_FINALLY are children of DO_TRY. Create child ElkNodes inside the parent compound node. Set compound node padding (top: 30 for label, sides: 10).
|
||||
- For each RouteEdge: create ElkEdge connecting source to target ElkNodes.
|
||||
- Run layout: `new RecursiveGraphLayoutEngine().layout(rootNode, new BasicProgressMonitor())`.
|
||||
- Extract positions from computed layout into DiagramLayout (nodes with x/y/w/h, edges with routed waypoints).
|
||||
|
||||
**SVG rendering (renderSvg):**
|
||||
- Run layout phase to get DiagramLayout.
|
||||
- Create `SVGGraphics2D` with layout width + margins and layout height + margins (add 20px padding each side).
|
||||
- Draw edges first (behind nodes): gray (#9CA3AF) lines with 2px stroke following edge waypoints. Draw arrowheads at endpoints.
|
||||
- Draw nodes: rounded rectangles (corner radius 8) filled with type-based colors:
|
||||
- Blue (#3B82F6): ENDPOINT, TO, TO_DYNAMIC, DIRECT, SEDA
|
||||
- Green (#22C55E): PROCESSOR, BEAN, LOG, SET_HEADER, SET_BODY, TRANSFORM, MARSHAL, UNMARSHAL
|
||||
- Red (#EF4444): ERROR_HANDLER, ON_EXCEPTION, TRY_CATCH, DO_TRY, DO_CATCH, DO_FINALLY
|
||||
- Purple (#A855F7): CHOICE, WHEN, OTHERWISE, SPLIT, AGGREGATE, MULTICAST, FILTER, RECIPIENT_LIST, ROUTING_SLIP, DYNAMIC_ROUTER, LOAD_BALANCE, THROTTLE, DELAY, SORT, RESEQUENCE, IDEMPOTENT_CONSUMER, CIRCUIT_BREAKER, SAGA, LOOP
|
||||
- Cyan (#06B6D4): WIRE_TAP, ENRICH, POLL_ENRICH
|
||||
- Draw node labels: white text, centered horizontally, vertically positioned at node.y + 24.
|
||||
- For compound nodes: draw a lighter-fill (alpha 0.15) rounded rectangle for the swimlane container with a label at the top. Draw child nodes inside.
|
||||
- Return `g2.getSVGDocument()`.
|
||||
|
||||
**JSON layout (layoutJson):**
|
||||
- Run layout phase, return DiagramLayout directly. Jackson will serialize it to JSON.
|
||||
|
||||
2. Create `DiagramBeanConfig` in `com.cameleer3.server.app.config`:
|
||||
- @Configuration class that creates DiagramRenderer bean (ElkDiagramRenderer) and SearchService bean wiring (prepare for Plan 03).
|
||||
|
||||
3. Create `DiagramRenderController` in `com.cameleer3.server.app.controller`:
|
||||
- `GET /api/v1/diagrams/{contentHash}/render` — renders the diagram
|
||||
- Inject DiagramRepository and DiagramRenderer.
|
||||
- Look up RouteGraph via `diagramRepository.findByContentHash(contentHash)`. If empty, return 404.
|
||||
- Content negotiation via Accept header:
|
||||
- `image/svg+xml` or `*/*` or no Accept: call `renderer.renderSvg(graph)`, return ResponseEntity with content-type `image/svg+xml` and SVG body.
|
||||
- `application/json`: call `renderer.layoutJson(graph)`, return ResponseEntity with content-type `application/json`.
|
||||
- Use `@RequestMapping(produces = {...})` or manual Accept header parsing to handle content negotiation. Manual parsing is simpler: read `request.getHeader("Accept")`, check for "application/json", default to SVG.
|
||||
|
||||
4. Create `ElkDiagramRendererTest` (unit test, no Spring context):
|
||||
- Build a simple RouteGraph with 3 nodes (from-endpoint, process-bean, to-endpoint) and 2 edges.
|
||||
- Test renderSvg produces valid SVG string containing `<svg`, `<rect` or `<path`, node labels.
|
||||
- Test layoutJson returns DiagramLayout with 3 nodes, all with positive x/y coordinates.
|
||||
- Build a RouteGraph with CHOICE -> WHEN, OTHERWISE compound structure. Verify compound node layout has children.
|
||||
|
||||
5. Create `DiagramRenderControllerIT` (extends AbstractClickHouseIT):
|
||||
- Seed a RouteGraph into ClickHouse via the /api/v1/data/diagrams endpoint, wait for flush.
|
||||
- Look up the content hash (compute SHA-256 of the JSON-serialized RouteGraph, same as ClickHouseDiagramRepository.sha256Hex).
|
||||
- GET /api/v1/diagrams/{hash}/render with Accept: image/svg+xml -> assert 200, content-type contains "svg", body contains "<svg".
|
||||
- GET /api/v1/diagrams/{hash}/render with Accept: application/json -> assert 200, body contains "nodes", "edges".
|
||||
- GET /api/v1/diagrams/nonexistent/render -> assert 404.
|
||||
- GET /api/v1/diagrams/{hash}/render with no Accept header -> assert SVG response (default).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest="ElkDiagramRendererTest,DiagramRenderControllerIT"</automated>
|
||||
</verify>
|
||||
<done>Diagram rendering produces color-coded top-to-bottom SVG and JSON layout, content negotiation works via Accept header, compound nodes group nested processors, all tests pass</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `mvn test -pl cameleer3-server-app -Dtest=ElkDiagramRendererTest` passes (unit tests for layout and SVG)
|
||||
- `mvn test -pl cameleer3-server-app -Dtest=DiagramRenderControllerIT` passes (integration tests for REST endpoint)
|
||||
- `mvn clean verify` passes (all existing tests still green)
|
||||
- SVG output contains color-coded nodes matching the NodeType color scheme
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- GET /api/v1/diagrams/{hash}/render returns SVG with color-coded nodes (blue endpoints, green processors, red error handlers, purple EIPs, cyan cross-route)
|
||||
- GET /api/v1/diagrams/{hash}/render with Accept: application/json returns JSON layout with node positions
|
||||
- Nodes laid out top-to-bottom via ELK layered algorithm
|
||||
- Compound nodes group nested processors (CHOICE/WHEN, TRY/CATCH) in swimlane containers
|
||||
- Non-existent hash returns 404
|
||||
- Default (no Accept header) returns SVG
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-transaction-search-diagrams/02-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user