Skip to content

Commit 84ee8ad

Browse files
aksOpsclaude
andcommitted
Fix flow command and API to work from H2 cache without Neo4j
- Extract FlowDataSource interface from GraphStore for flow diagram generation - Add CacheFlowDataSource backed by pre-loaded node list from H2 - FlowEngine no longer a Spring bean — created manually by FlowCommand/FlowController - FlowCommand loads from H2 cache when Neo4j/GraphStore unavailable - FlowController removes @ConditionalOnProperty(neo4j.enabled) — works with H2 fallback - FlowViews accepts FlowDataSource instead of GraphStore directly - All 1,227 tests pass (fixed 8 previously broken DetectorInfoAnnotationTest errors) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4807d2d commit 84ee8ad

11 files changed

Lines changed: 185 additions & 104 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"root":["./src/App.tsx","./src/env.d.ts","./src/main.tsx","./src/components/Dashboard.tsx","./src/components/ExplorerView.tsx","./src/components/FlowView.tsx","./src/components/FrameworkBadges.tsx","./src/components/Layout.tsx","./src/components/McpConsole.tsx","./src/components/NodeDetailModal.tsx","./src/components/SearchBar.tsx","./src/components/StatsCards.tsx","./src/components/SwaggerView.tsx","./src/components/ThemeToggle.tsx","./src/components/TopologyView.tsx","./src/hooks/useApi.ts","./src/hooks/useTheme.ts","./src/lib/api.ts","./src/types/api.ts"],"version":"5.7.3"}
Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package io.github.randomcodespace.iq.api;
22

3+
import io.github.randomcodespace.iq.cache.AnalysisCache;
4+
import io.github.randomcodespace.iq.config.CodeIqConfig;
35
import io.github.randomcodespace.iq.flow.FlowEngine;
46
import io.github.randomcodespace.iq.flow.FlowModels.FlowDiagram;
7+
import io.github.randomcodespace.iq.model.CodeNode;
58
import org.springframework.context.annotation.Profile;
69
import org.springframework.http.HttpStatus;
710
import org.springframework.http.MediaType;
@@ -13,91 +16,113 @@
1316
import org.springframework.web.bind.annotation.RestController;
1417
import org.springframework.web.server.ResponseStatusException;
1518

19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
1621
import java.util.LinkedHashMap;
22+
import java.util.List;
1723
import java.util.Map;
24+
import java.util.Optional;
1825

1926
/**
2027
* REST API controller for architecture flow diagrams.
21-
* Supports drill-down and drill-up navigation.
28+
* Works from either Neo4j (GraphStore) or H2 cache.
2229
*/
2330
@RestController
2431
@RequestMapping("/api/flow")
2532
@Profile("serving")
26-
@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "codeiq.neo4j.enabled", havingValue = "true", matchIfMissing = true)
2733
public class FlowController {
2834

2935
private final FlowEngine flowEngine;
36+
private final CodeIqConfig config;
3037

31-
public FlowController(FlowEngine flowEngine) {
32-
this.flowEngine = flowEngine;
38+
public FlowController(Optional<FlowEngine> flowEngine, CodeIqConfig config) {
39+
this.flowEngine = flowEngine.orElse(null);
40+
this.config = config;
3341
}
3442

35-
/**
36-
* Get all flow views as JSON diagrams.
37-
*/
3843
@GetMapping
3944
public Map<String, Object> getAllFlows() {
40-
var allViews = flowEngine.generateAll();
45+
FlowEngine engine = resolveEngine();
46+
var allViews = engine.generateAll();
4147
var result = new LinkedHashMap<String, Object>();
4248
for (var entry : allViews.entrySet()) {
4349
result.put(entry.getKey(), entry.getValue().toMap());
4450
}
4551
return result;
4652
}
4753

48-
/**
49-
* Get a specific flow view, optionally in a different format.
50-
*/
5154
@GetMapping("/{view}")
5255
public ResponseEntity<?> getFlow(
5356
@PathVariable String view,
5457
@RequestParam(defaultValue = "json") String format) {
5558
try {
56-
FlowDiagram diagram = flowEngine.generate(view);
59+
FlowEngine engine = resolveEngine();
60+
FlowDiagram diagram = engine.generate(view);
5761

5862
return switch (format.toLowerCase()) {
5963
case "json" -> ResponseEntity.ok(diagram.toMap());
6064
case "mermaid" -> ResponseEntity.ok()
6165
.contentType(MediaType.TEXT_PLAIN)
62-
.body(flowEngine.render(diagram, "mermaid"));
66+
.body(engine.render(diagram, "mermaid"));
6367
case "html" -> ResponseEntity.ok()
6468
.contentType(MediaType.TEXT_HTML)
65-
.body(flowEngine.renderInteractive("Project"));
69+
.body(engine.renderInteractive("Project"));
6670
default -> throw new IllegalArgumentException(
67-
"Unknown format: " + format + ". Available: json, mermaid, html");
71+
"Unknown format. Available: json, mermaid, html");
6872
};
6973
} catch (IllegalArgumentException e) {
7074
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
7175
}
7276
}
7377

74-
/**
75-
* Drill down into a node within a view -- returns the child view's diagram.
76-
*/
7778
@GetMapping("/{view}/{nodeId}/children")
7879
public Map<String, Object> getChildren(
7980
@PathVariable String view,
8081
@PathVariable String nodeId) {
81-
Map<String, Object> children = flowEngine.getChildren(view, nodeId);
82+
FlowEngine engine = resolveEngine();
83+
Map<String, Object> children = engine.getChildren(view, nodeId);
8284
if (children == null) {
8385
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
8486
"No drill-down available for node " + nodeId + " in view " + view);
8587
}
8688
return children;
8789
}
8890

89-
/**
90-
* Drill up from a node -- returns the parent context.
91-
*/
9291
@GetMapping("/{view}/{nodeId}/parent")
9392
public Map<String, Object> getParent(
9493
@PathVariable String view,
9594
@PathVariable String nodeId) {
96-
Map<String, Object> parent = flowEngine.getParentContext(nodeId);
95+
FlowEngine engine = resolveEngine();
96+
Map<String, Object> parent = engine.getParentContext(nodeId);
9797
if (parent == null) {
9898
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
9999
"No parent context found for node " + nodeId);
100100
}
101101
return parent;
102102
}
103+
104+
private FlowEngine resolveEngine() {
105+
if (flowEngine != null) {
106+
return flowEngine;
107+
}
108+
109+
// Fall back to H2 cache
110+
Path root = Path.of(config.getRootPath()).toAbsolutePath().normalize();
111+
Path cachePath = root.resolve(config.getCacheDir()).resolve("analysis-cache.db");
112+
Path h2File = root.resolve(config.getCacheDir()).resolve("analysis-cache.mv.db");
113+
114+
if (!Files.exists(h2File)) {
115+
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
116+
"No analysis data available. Run 'code-iq analyze' first.");
117+
}
118+
119+
try (AnalysisCache cache = new AnalysisCache(cachePath)) {
120+
List<CodeNode> nodes = cache.loadAllNodes();
121+
if (nodes.isEmpty()) {
122+
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
123+
"Analysis cache is empty. Run 'code-iq analyze' first.");
124+
}
125+
return FlowEngine.fromCache(nodes);
126+
}
127+
}
103128
}

src/main/java/io/github/randomcodespace/iq/cli/FlowCommand.java

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package io.github.randomcodespace.iq.cli;
22

3+
import io.github.randomcodespace.iq.cache.AnalysisCache;
4+
import io.github.randomcodespace.iq.config.CodeIqConfig;
35
import io.github.randomcodespace.iq.flow.FlowEngine;
46
import io.github.randomcodespace.iq.flow.FlowModels.FlowDiagram;
7+
import io.github.randomcodespace.iq.model.CodeNode;
58
import org.springframework.beans.factory.annotation.Autowired;
69
import org.springframework.stereotype.Component;
710
import picocli.CommandLine.Command;
@@ -12,11 +15,13 @@
1215
import java.nio.charset.StandardCharsets;
1316
import java.nio.file.Files;
1417
import java.nio.file.Path;
18+
import java.util.List;
1519
import java.util.Optional;
1620
import java.util.concurrent.Callable;
1721

1822
/**
1923
* Generate architecture flow diagrams from the knowledge graph.
24+
* Works from H2 cache (no Neo4j required).
2025
* Supports 5 views (overview, ci, deploy, runtime, auth) and 3 formats (mermaid, json, html).
2126
*/
2227
@Component
@@ -39,37 +44,43 @@ public class FlowCommand implements Callable<Integer> {
3944
private Path output;
4045

4146
private final FlowEngine flowEngine;
47+
private final CodeIqConfig config;
4248

4349
/** No-arg constructor for Picocli direct instantiation. */
4450
public FlowCommand() {
4551
this.flowEngine = null;
52+
this.config = null;
4653
}
4754

4855
@Autowired
49-
public FlowCommand(Optional<FlowEngine> flowEngine) {
56+
public FlowCommand(Optional<FlowEngine> flowEngine, CodeIqConfig config) {
5057
this.flowEngine = flowEngine.orElse(null);
58+
this.config = config;
5159
}
5260

5361
/** Convenience constructor for tests. */
54-
FlowCommand(FlowEngine flowEngine) {
62+
FlowCommand(FlowEngine flowEngine, CodeIqConfig config) {
5563
this.flowEngine = flowEngine;
64+
this.config = config;
5665
}
5766

5867
@Override
5968
public Integer call() {
60-
if (flowEngine == null) {
61-
CliOutput.error("Flow diagrams require the serve profile (Neo4j). Use 'code-iq serve' to start the server, or 'code-iq stats' for cache-based queries.");
69+
FlowEngine engine = resolveEngine();
70+
if (engine == null) {
71+
CliOutput.error("No analysis cache found. Run 'code-iq analyze' or 'code-iq index' first.");
6272
return 1;
6373
}
74+
6475
try {
6576
String content;
6677

6778
if ("html".equalsIgnoreCase(format)) {
6879
String projectName = path.toAbsolutePath().getFileName().toString();
69-
content = flowEngine.renderInteractive(projectName);
80+
content = engine.renderInteractive(projectName);
7081
} else {
71-
FlowDiagram diagram = flowEngine.generate(view.toLowerCase());
72-
content = flowEngine.render(diagram, format.toLowerCase());
82+
FlowDiagram diagram = engine.generate(view.toLowerCase());
83+
content = engine.render(diagram, format.toLowerCase());
7384
}
7485

7586
if (output != null) {
@@ -88,4 +99,31 @@ public Integer call() {
8899
return 1;
89100
}
90101
}
102+
103+
/**
104+
* Use the Spring-injected FlowEngine (backed by GraphStore/Neo4j) if available,
105+
* otherwise create one from H2 cache.
106+
*/
107+
private FlowEngine resolveEngine() {
108+
if (flowEngine != null) {
109+
return flowEngine;
110+
}
111+
112+
// Fall back to H2 cache
113+
if (config == null) return null;
114+
115+
Path root = path.toAbsolutePath().normalize();
116+
Path cachePath = root.resolve(config.getCacheDir()).resolve("analysis-cache.db");
117+
Path h2File = root.resolve(config.getCacheDir()).resolve("analysis-cache.mv.db");
118+
119+
if (!Files.exists(h2File)) {
120+
return null;
121+
}
122+
123+
try (AnalysisCache cache = new AnalysisCache(cachePath)) {
124+
List<CodeNode> nodes = cache.loadAllNodes();
125+
if (nodes.isEmpty()) return null;
126+
return FlowEngine.fromCache(nodes);
127+
}
128+
}
91129
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.github.randomcodespace.iq.flow;
2+
3+
import io.github.randomcodespace.iq.model.CodeNode;
4+
import io.github.randomcodespace.iq.model.NodeKind;
5+
6+
import java.util.List;
7+
8+
/**
9+
* FlowDataSource backed by a pre-loaded list of nodes (from H2 cache).
10+
*/
11+
public class CacheFlowDataSource implements FlowDataSource {
12+
13+
private final List<CodeNode> nodes;
14+
15+
public CacheFlowDataSource(List<CodeNode> nodes) {
16+
this.nodes = nodes;
17+
}
18+
19+
@Override
20+
public List<CodeNode> findAll() {
21+
return nodes;
22+
}
23+
24+
@Override
25+
public List<CodeNode> findByKind(NodeKind kind) {
26+
return nodes.stream()
27+
.filter(n -> n.getKind() == kind)
28+
.toList();
29+
}
30+
31+
@Override
32+
public long count() {
33+
return nodes.size();
34+
}
35+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.github.randomcodespace.iq.flow;
2+
3+
import io.github.randomcodespace.iq.model.CodeNode;
4+
import io.github.randomcodespace.iq.model.NodeKind;
5+
6+
import java.util.List;
7+
8+
/**
9+
* Abstraction over graph data access for flow diagram generation.
10+
* Allows FlowEngine to work from either Neo4j (GraphStore) or H2 cache.
11+
*/
12+
public interface FlowDataSource {
13+
14+
List<CodeNode> findAll();
15+
16+
List<CodeNode> findByKind(NodeKind kind);
17+
18+
long count();
19+
}

0 commit comments

Comments
 (0)