refactor: remove diagramNodeId indirection, use processorId directly
Agent now uses Camel processorId as RouteNode.id, eliminating the nodeId mapping layer. Drop diagram_node_id column (V6 migration), remove from ProcessorRecord/ProcessorNode/IngestionService/DetailService, add /processor-routes endpoint for processorId→routeId lookup, simplify frontend diagram-mapping and ExchangeDetail overlays, replace N diagram fetches in AppConfigPage with single hook. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import com.cameleer3.server.core.agent.AgentRegistryService;
|
|||||||
import com.cameleer3.server.core.agent.AgentState;
|
import com.cameleer3.server.core.agent.AgentState;
|
||||||
import com.cameleer3.server.core.agent.CommandReply;
|
import com.cameleer3.server.core.agent.CommandReply;
|
||||||
import com.cameleer3.server.core.agent.CommandType;
|
import com.cameleer3.server.core.agent.CommandType;
|
||||||
|
import com.cameleer3.server.core.storage.ExecutionStore;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -48,15 +49,18 @@ public class ApplicationConfigController {
|
|||||||
private final AgentRegistryService registryService;
|
private final AgentRegistryService registryService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final AuditService auditService;
|
private final AuditService auditService;
|
||||||
|
private final ExecutionStore executionStore;
|
||||||
|
|
||||||
public ApplicationConfigController(PostgresApplicationConfigRepository configRepository,
|
public ApplicationConfigController(PostgresApplicationConfigRepository configRepository,
|
||||||
AgentRegistryService registryService,
|
AgentRegistryService registryService,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
AuditService auditService) {
|
AuditService auditService,
|
||||||
|
ExecutionStore executionStore) {
|
||||||
this.configRepository = configRepository;
|
this.configRepository = configRepository;
|
||||||
this.registryService = registryService;
|
this.registryService = registryService;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.auditService = auditService;
|
this.auditService = auditService;
|
||||||
|
this.executionStore = executionStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@@ -103,6 +107,14 @@ public class ApplicationConfigController {
|
|||||||
return ResponseEntity.ok(saved);
|
return ResponseEntity.ok(saved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{application}/processor-routes")
|
||||||
|
@Operation(summary = "Get processor to route mapping",
|
||||||
|
description = "Returns a map of processorId → routeId for all processors seen in this application")
|
||||||
|
@ApiResponse(responseCode = "200", description = "Mapping returned")
|
||||||
|
public ResponseEntity<Map<String, String>> getProcessorRouteMapping(@PathVariable String application) {
|
||||||
|
return ResponseEntity.ok(executionStore.findProcessorRouteMapping(application));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/{application}/test-expression")
|
@PostMapping("/{application}/test-expression")
|
||||||
@Operation(summary = "Test a tap expression against sample data via a live agent")
|
@Operation(summary = "Test a tap expression against sample data via a live agent")
|
||||||
@ApiResponse(responseCode = "200", description = "Expression evaluated successfully")
|
@ApiResponse(responseCode = "200", description = "Expression evaluated successfully")
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import java.sql.ResultSet;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@@ -70,10 +72,10 @@ public class PostgresExecutionStore implements ExecutionStore {
|
|||||||
List<ProcessorRecord> processors) {
|
List<ProcessorRecord> processors) {
|
||||||
jdbc.batchUpdate("""
|
jdbc.batchUpdate("""
|
||||||
INSERT INTO processor_executions (execution_id, processor_id, processor_type,
|
INSERT INTO processor_executions (execution_id, processor_id, processor_type,
|
||||||
diagram_node_id, application_name, route_id, depth, parent_processor_id,
|
application_name, route_id, depth, parent_processor_id,
|
||||||
status, start_time, end_time, duration_ms, error_message, error_stacktrace,
|
status, start_time, end_time, duration_ms, error_message, error_stacktrace,
|
||||||
input_body, output_body, input_headers, output_headers, attributes)
|
input_body, output_body, input_headers, output_headers, attributes)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb)
|
||||||
ON CONFLICT (execution_id, processor_id, start_time) DO UPDATE SET
|
ON CONFLICT (execution_id, processor_id, start_time) DO UPDATE SET
|
||||||
status = EXCLUDED.status,
|
status = EXCLUDED.status,
|
||||||
end_time = COALESCE(EXCLUDED.end_time, processor_executions.end_time),
|
end_time = COALESCE(EXCLUDED.end_time, processor_executions.end_time),
|
||||||
@@ -88,7 +90,7 @@ public class PostgresExecutionStore implements ExecutionStore {
|
|||||||
""",
|
""",
|
||||||
processors.stream().map(p -> new Object[]{
|
processors.stream().map(p -> new Object[]{
|
||||||
p.executionId(), p.processorId(), p.processorType(),
|
p.executionId(), p.processorId(), p.processorType(),
|
||||||
p.diagramNodeId(), p.applicationName(), p.routeId(),
|
p.applicationName(), p.routeId(),
|
||||||
p.depth(), p.parentProcessorId(), p.status(),
|
p.depth(), p.parentProcessorId(), p.status(),
|
||||||
Timestamp.from(p.startTime()),
|
Timestamp.from(p.startTime()),
|
||||||
p.endTime() != null ? Timestamp.from(p.endTime()) : null,
|
p.endTime() != null ? Timestamp.from(p.endTime()) : null,
|
||||||
@@ -113,6 +115,18 @@ public class PostgresExecutionStore implements ExecutionStore {
|
|||||||
PROCESSOR_MAPPER, executionId);
|
PROCESSOR_MAPPER, executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> findProcessorRouteMapping(String applicationName) {
|
||||||
|
Map<String, String> mapping = new HashMap<>();
|
||||||
|
jdbc.query("""
|
||||||
|
SELECT DISTINCT ON (processor_id) processor_id, route_id
|
||||||
|
FROM processor_executions WHERE application_name = ? ORDER BY processor_id
|
||||||
|
""",
|
||||||
|
rs -> { mapping.put(rs.getString("processor_id"), rs.getString("route_id")); },
|
||||||
|
applicationName);
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
private static final RowMapper<ExecutionRecord> EXECUTION_MAPPER = (rs, rowNum) ->
|
private static final RowMapper<ExecutionRecord> EXECUTION_MAPPER = (rs, rowNum) ->
|
||||||
new ExecutionRecord(
|
new ExecutionRecord(
|
||||||
rs.getString("execution_id"), rs.getString("route_id"),
|
rs.getString("execution_id"), rs.getString("route_id"),
|
||||||
@@ -131,7 +145,7 @@ public class PostgresExecutionStore implements ExecutionStore {
|
|||||||
private static final RowMapper<ProcessorRecord> PROCESSOR_MAPPER = (rs, rowNum) ->
|
private static final RowMapper<ProcessorRecord> PROCESSOR_MAPPER = (rs, rowNum) ->
|
||||||
new ProcessorRecord(
|
new ProcessorRecord(
|
||||||
rs.getString("execution_id"), rs.getString("processor_id"),
|
rs.getString("execution_id"), rs.getString("processor_id"),
|
||||||
rs.getString("processor_type"), rs.getString("diagram_node_id"),
|
rs.getString("processor_type"),
|
||||||
rs.getString("application_name"), rs.getString("route_id"),
|
rs.getString("application_name"), rs.getString("route_id"),
|
||||||
rs.getInt("depth"), rs.getString("parent_processor_id"),
|
rs.getInt("depth"), rs.getString("parent_processor_id"),
|
||||||
rs.getString("status"),
|
rs.getString("status"),
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE processor_executions DROP COLUMN IF EXISTS diagram_node_id;
|
||||||
@@ -65,7 +65,6 @@ class DetailControllerIT extends AbstractPostgresIT {
|
|||||||
"startTime": "2026-03-10T10:00:00Z",
|
"startTime": "2026-03-10T10:00:00Z",
|
||||||
"endTime": "2026-03-10T10:00:01Z",
|
"endTime": "2026-03-10T10:00:01Z",
|
||||||
"durationMs": 1000,
|
"durationMs": 1000,
|
||||||
"diagramNodeId": "node-root",
|
|
||||||
"inputBody": "root-input-body",
|
"inputBody": "root-input-body",
|
||||||
"outputBody": "root-output-body",
|
"outputBody": "root-output-body",
|
||||||
"inputHeaders": {"Content-Type": "application/json"},
|
"inputHeaders": {"Content-Type": "application/json"},
|
||||||
@@ -78,7 +77,6 @@ class DetailControllerIT extends AbstractPostgresIT {
|
|||||||
"startTime": "2026-03-10T10:00:00.100Z",
|
"startTime": "2026-03-10T10:00:00.100Z",
|
||||||
"endTime": "2026-03-10T10:00:00.200Z",
|
"endTime": "2026-03-10T10:00:00.200Z",
|
||||||
"durationMs": 100,
|
"durationMs": 100,
|
||||||
"diagramNodeId": "node-child1",
|
|
||||||
"inputBody": "child1-input",
|
"inputBody": "child1-input",
|
||||||
"outputBody": "child1-output",
|
"outputBody": "child1-output",
|
||||||
"inputHeaders": {},
|
"inputHeaders": {},
|
||||||
@@ -91,7 +89,6 @@ class DetailControllerIT extends AbstractPostgresIT {
|
|||||||
"startTime": "2026-03-10T10:00:00.200Z",
|
"startTime": "2026-03-10T10:00:00.200Z",
|
||||||
"endTime": "2026-03-10T10:00:00.800Z",
|
"endTime": "2026-03-10T10:00:00.800Z",
|
||||||
"durationMs": 600,
|
"durationMs": 600,
|
||||||
"diagramNodeId": "node-child2",
|
|
||||||
"inputBody": "child2-input",
|
"inputBody": "child2-input",
|
||||||
"outputBody": "child2-output",
|
"outputBody": "child2-output",
|
||||||
"inputHeaders": {},
|
"inputHeaders": {},
|
||||||
@@ -104,7 +101,6 @@ class DetailControllerIT extends AbstractPostgresIT {
|
|||||||
"startTime": "2026-03-10T10:00:00.300Z",
|
"startTime": "2026-03-10T10:00:00.300Z",
|
||||||
"endTime": "2026-03-10T10:00:00.700Z",
|
"endTime": "2026-03-10T10:00:00.700Z",
|
||||||
"durationMs": 400,
|
"durationMs": 400,
|
||||||
"diagramNodeId": "node-gc",
|
|
||||||
"inputBody": "gc-input",
|
"inputBody": "gc-input",
|
||||||
"outputBody": "gc-output",
|
"outputBody": "gc-output",
|
||||||
"inputHeaders": {"X-GC": "true"},
|
"inputHeaders": {"X-GC": "true"},
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ class DiagramControllerIT extends AbstractPostgresIT {
|
|||||||
"description": "Test route",
|
"description": "Test route",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"nodes": [],
|
"nodes": [],
|
||||||
"edges": [],
|
"edges": []
|
||||||
"processorNodeMapping": {}
|
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@@ -60,8 +59,7 @@ class DiagramControllerIT extends AbstractPostgresIT {
|
|||||||
"description": "Flush test",
|
"description": "Flush test",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"nodes": [],
|
"nodes": [],
|
||||||
"edges": [],
|
"edges": []
|
||||||
"processorNodeMapping": {}
|
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ class DiagramRenderControllerIT extends AbstractPostgresIT {
|
|||||||
"edges": [
|
"edges": [
|
||||||
{"source": "n1", "target": "n2", "edgeType": "FLOW"},
|
{"source": "n1", "target": "n2", "edgeType": "FLOW"},
|
||||||
{"source": "n2", "target": "n3", "edgeType": "FLOW"}
|
{"source": "n2", "target": "n3", "edgeType": "FLOW"}
|
||||||
],
|
]
|
||||||
"processorNodeMapping": {}
|
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ class DiagramLinkingIT extends AbstractPostgresIT {
|
|||||||
],
|
],
|
||||||
"edges": [
|
"edges": [
|
||||||
{"source": "n1", "target": "n2", "edgeType": "FLOW"}
|
{"source": "n1", "target": "n2", "edgeType": "FLOW"}
|
||||||
],
|
]
|
||||||
"processorNodeMapping": {}
|
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ class IngestionSchemaIT extends AbstractPostgresIT {
|
|||||||
"startTime": "2026-03-11T10:00:00Z",
|
"startTime": "2026-03-11T10:00:00Z",
|
||||||
"endTime": "2026-03-11T10:00:00.500Z",
|
"endTime": "2026-03-11T10:00:00.500Z",
|
||||||
"durationMs": 500,
|
"durationMs": 500,
|
||||||
"diagramNodeId": "node-root",
|
"inputBody": "root-input",
|
||||||
"inputBody": "root-input",
|
|
||||||
"outputBody": "root-output",
|
"outputBody": "root-output",
|
||||||
"inputHeaders": {"Content-Type": "application/json"},
|
"inputHeaders": {"Content-Type": "application/json"},
|
||||||
"outputHeaders": {"X-Result": "ok"},
|
"outputHeaders": {"X-Result": "ok"},
|
||||||
@@ -68,8 +67,7 @@ class IngestionSchemaIT extends AbstractPostgresIT {
|
|||||||
"startTime": "2026-03-11T10:00:00.100Z",
|
"startTime": "2026-03-11T10:00:00.100Z",
|
||||||
"endTime": "2026-03-11T10:00:00.400Z",
|
"endTime": "2026-03-11T10:00:00.400Z",
|
||||||
"durationMs": 300,
|
"durationMs": 300,
|
||||||
"diagramNodeId": "node-child",
|
"inputBody": "child-input",
|
||||||
"inputBody": "child-input",
|
|
||||||
"outputBody": "child-output",
|
"outputBody": "child-output",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
@@ -79,8 +77,7 @@ class IngestionSchemaIT extends AbstractPostgresIT {
|
|||||||
"startTime": "2026-03-11T10:00:00.200Z",
|
"startTime": "2026-03-11T10:00:00.200Z",
|
||||||
"endTime": "2026-03-11T10:00:00.300Z",
|
"endTime": "2026-03-11T10:00:00.300Z",
|
||||||
"durationMs": 100,
|
"durationMs": 100,
|
||||||
"diagramNodeId": "node-grandchild",
|
"children": []
|
||||||
"children": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -101,7 +98,7 @@ class IngestionSchemaIT extends AbstractPostgresIT {
|
|||||||
// Verify processors were flattened into processor_executions
|
// Verify processors were flattened into processor_executions
|
||||||
List<Map<String, Object>> processors = jdbcTemplate.queryForList(
|
List<Map<String, Object>> processors = jdbcTemplate.queryForList(
|
||||||
"SELECT processor_id, processor_type, depth, parent_processor_id, " +
|
"SELECT processor_id, processor_type, depth, parent_processor_id, " +
|
||||||
"diagram_node_id, input_body, output_body, input_headers " +
|
"input_body, output_body, input_headers " +
|
||||||
"FROM processor_executions WHERE execution_id = 'ex-tree-1' " +
|
"FROM processor_executions WHERE execution_id = 'ex-tree-1' " +
|
||||||
"ORDER BY depth, processor_id");
|
"ORDER BY depth, processor_id");
|
||||||
assertThat(processors).hasSize(3);
|
assertThat(processors).hasSize(3);
|
||||||
@@ -110,7 +107,6 @@ class IngestionSchemaIT extends AbstractPostgresIT {
|
|||||||
assertThat(processors.get(0).get("processor_id")).isEqualTo("root-proc");
|
assertThat(processors.get(0).get("processor_id")).isEqualTo("root-proc");
|
||||||
assertThat(((Number) processors.get(0).get("depth")).intValue()).isEqualTo(0);
|
assertThat(((Number) processors.get(0).get("depth")).intValue()).isEqualTo(0);
|
||||||
assertThat(processors.get(0).get("parent_processor_id")).isNull();
|
assertThat(processors.get(0).get("parent_processor_id")).isNull();
|
||||||
assertThat(processors.get(0).get("diagram_node_id")).isEqualTo("node-root");
|
|
||||||
assertThat(processors.get(0).get("input_body")).isEqualTo("root-input");
|
assertThat(processors.get(0).get("input_body")).isEqualTo("root-input");
|
||||||
assertThat(processors.get(0).get("output_body")).isEqualTo("root-output");
|
assertThat(processors.get(0).get("output_body")).isEqualTo("root-output");
|
||||||
assertThat(processors.get(0).get("input_headers").toString()).contains("Content-Type");
|
assertThat(processors.get(0).get("input_headers").toString()).contains("Content-Type");
|
||||||
@@ -119,7 +115,6 @@ class IngestionSchemaIT extends AbstractPostgresIT {
|
|||||||
assertThat(processors.get(1).get("processor_id")).isEqualTo("child-proc");
|
assertThat(processors.get(1).get("processor_id")).isEqualTo("child-proc");
|
||||||
assertThat(((Number) processors.get(1).get("depth")).intValue()).isEqualTo(1);
|
assertThat(((Number) processors.get(1).get("depth")).intValue()).isEqualTo(1);
|
||||||
assertThat(processors.get(1).get("parent_processor_id")).isEqualTo("root-proc");
|
assertThat(processors.get(1).get("parent_processor_id")).isEqualTo("root-proc");
|
||||||
assertThat(processors.get(1).get("diagram_node_id")).isEqualTo("node-child");
|
|
||||||
assertThat(processors.get(1).get("input_body")).isEqualTo("child-input");
|
assertThat(processors.get(1).get("input_body")).isEqualTo("child-input");
|
||||||
assertThat(processors.get(1).get("output_body")).isEqualTo("child-output");
|
assertThat(processors.get(1).get("output_body")).isEqualTo("child-output");
|
||||||
|
|
||||||
@@ -127,7 +122,6 @@ class IngestionSchemaIT extends AbstractPostgresIT {
|
|||||||
assertThat(processors.get(2).get("processor_id")).isEqualTo("grandchild-proc");
|
assertThat(processors.get(2).get("processor_id")).isEqualTo("grandchild-proc");
|
||||||
assertThat(((Number) processors.get(2).get("depth")).intValue()).isEqualTo(2);
|
assertThat(((Number) processors.get(2).get("depth")).intValue()).isEqualTo(2);
|
||||||
assertThat(processors.get(2).get("parent_processor_id")).isEqualTo("child-proc");
|
assertThat(processors.get(2).get("parent_processor_id")).isEqualTo("child-proc");
|
||||||
assertThat(processors.get(2).get("diagram_node_id")).isEqualTo("node-grandchild");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class DetailService {
|
|||||||
p.processorId(), p.processorType(), p.status(),
|
p.processorId(), p.processorType(), p.status(),
|
||||||
p.startTime(), p.endTime(),
|
p.startTime(), p.endTime(),
|
||||||
p.durationMs() != null ? p.durationMs() : 0L,
|
p.durationMs() != null ? p.durationMs() : 0L,
|
||||||
p.diagramNodeId(), p.errorMessage(), p.errorStacktrace(),
|
p.errorMessage(), p.errorStacktrace(),
|
||||||
parseAttributes(p.attributes())
|
parseAttributes(p.attributes())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public final class ProcessorNode {
|
|||||||
private final Instant startTime;
|
private final Instant startTime;
|
||||||
private final Instant endTime;
|
private final Instant endTime;
|
||||||
private final long durationMs;
|
private final long durationMs;
|
||||||
private final String diagramNodeId;
|
|
||||||
private final String errorMessage;
|
private final String errorMessage;
|
||||||
private final String errorStackTrace;
|
private final String errorStackTrace;
|
||||||
private final Map<String, String> attributes;
|
private final Map<String, String> attributes;
|
||||||
@@ -27,7 +26,7 @@ public final class ProcessorNode {
|
|||||||
|
|
||||||
public ProcessorNode(String processorId, String processorType, String status,
|
public ProcessorNode(String processorId, String processorType, String status,
|
||||||
Instant startTime, Instant endTime, long durationMs,
|
Instant startTime, Instant endTime, long durationMs,
|
||||||
String diagramNodeId, String errorMessage, String errorStackTrace,
|
String errorMessage, String errorStackTrace,
|
||||||
Map<String, String> attributes) {
|
Map<String, String> attributes) {
|
||||||
this.processorId = processorId;
|
this.processorId = processorId;
|
||||||
this.processorType = processorType;
|
this.processorType = processorType;
|
||||||
@@ -35,7 +34,6 @@ public final class ProcessorNode {
|
|||||||
this.startTime = startTime;
|
this.startTime = startTime;
|
||||||
this.endTime = endTime;
|
this.endTime = endTime;
|
||||||
this.durationMs = durationMs;
|
this.durationMs = durationMs;
|
||||||
this.diagramNodeId = diagramNodeId;
|
|
||||||
this.errorMessage = errorMessage;
|
this.errorMessage = errorMessage;
|
||||||
this.errorStackTrace = errorStackTrace;
|
this.errorStackTrace = errorStackTrace;
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
@@ -52,7 +50,6 @@ public final class ProcessorNode {
|
|||||||
public Instant getStartTime() { return startTime; }
|
public Instant getStartTime() { return startTime; }
|
||||||
public Instant getEndTime() { return endTime; }
|
public Instant getEndTime() { return endTime; }
|
||||||
public long getDurationMs() { return durationMs; }
|
public long getDurationMs() { return durationMs; }
|
||||||
public String getDiagramNodeId() { return diagramNodeId; }
|
|
||||||
public String getErrorMessage() { return errorMessage; }
|
public String getErrorMessage() { return errorMessage; }
|
||||||
public String getErrorStackTrace() { return errorStackTrace; }
|
public String getErrorStackTrace() { return errorStackTrace; }
|
||||||
public Map<String, String> getAttributes() { return attributes; }
|
public Map<String, String> getAttributes() { return attributes; }
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ public class IngestionService {
|
|||||||
for (ProcessorExecution p : processors) {
|
for (ProcessorExecution p : processors) {
|
||||||
flat.add(new ProcessorRecord(
|
flat.add(new ProcessorRecord(
|
||||||
executionId, p.getProcessorId(), p.getProcessorType(),
|
executionId, p.getProcessorId(), p.getProcessorType(),
|
||||||
p.getDiagramNodeId(), applicationName, routeId,
|
applicationName, routeId,
|
||||||
depth, parentProcessorId,
|
depth, parentProcessorId,
|
||||||
p.getStatus() != null ? p.getStatus().name() : "RUNNING",
|
p.getStatus() != null ? p.getStatus().name() : "RUNNING",
|
||||||
p.getStartTime() != null ? p.getStartTime() : execStartTime,
|
p.getStartTime() != null ? p.getStartTime() : execStartTime,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cameleer3.server.core.storage;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface ExecutionStore {
|
public interface ExecutionStore {
|
||||||
@@ -16,6 +17,8 @@ public interface ExecutionStore {
|
|||||||
|
|
||||||
List<ProcessorRecord> findProcessors(String executionId);
|
List<ProcessorRecord> findProcessors(String executionId);
|
||||||
|
|
||||||
|
Map<String, String> findProcessorRouteMapping(String applicationName);
|
||||||
|
|
||||||
record ExecutionRecord(
|
record ExecutionRecord(
|
||||||
String executionId, String routeId, String agentId, String applicationName,
|
String executionId, String routeId, String agentId, String applicationName,
|
||||||
String status, String correlationId, String exchangeId,
|
String status, String correlationId, String exchangeId,
|
||||||
@@ -28,7 +31,7 @@ public interface ExecutionStore {
|
|||||||
|
|
||||||
record ProcessorRecord(
|
record ProcessorRecord(
|
||||||
String executionId, String processorId, String processorType,
|
String executionId, String processorId, String processorType,
|
||||||
String diagramNodeId, String applicationName, String routeId,
|
String applicationName, String routeId,
|
||||||
int depth, String parentProcessorId, String status,
|
int depth, String parentProcessorId, String status,
|
||||||
Instant startTime, Instant endTime, Long durationMs,
|
Instant startTime, Instant endTime, Long durationMs,
|
||||||
String errorMessage, String errorStacktrace,
|
String errorMessage, String errorStacktrace,
|
||||||
|
|||||||
@@ -83,6 +83,20 @@ export function useUpdateApplicationConfig() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Processor → Route Mapping ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function useProcessorRouteMapping(application?: string) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['config', application, 'processor-routes'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await authFetch(`/api/v1/config/${application}/processor-routes`)
|
||||||
|
if (!res.ok) throw new Error('Failed to fetch processor-route mapping')
|
||||||
|
return res.json() as Promise<Record<string, string>>
|
||||||
|
},
|
||||||
|
enabled: !!application,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ── Generic Group Command (kept for non-config commands) ──────────────────
|
// ── Generic Group Command (kept for non-config commands) ──────────────────
|
||||||
|
|
||||||
interface SendGroupCommandParams {
|
interface SendGroupCommandParams {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { useState, useMemo, useEffect } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { useQueries } from '@tanstack/react-query';
|
|
||||||
import {
|
import {
|
||||||
DataTable, Badge, MonoText, DetailPanel, SectionHeader, Button, Toggle, Spinner, useToast,
|
DataTable, Badge, MonoText, DetailPanel, SectionHeader, Button, Toggle, Spinner, useToast,
|
||||||
} from '@cameleer/design-system';
|
} from '@cameleer/design-system';
|
||||||
import type { Column } from '@cameleer/design-system';
|
import type { Column } from '@cameleer/design-system';
|
||||||
import { useAllApplicationConfigs, useApplicationConfig, useUpdateApplicationConfig } from '../../api/queries/commands';
|
import { useAllApplicationConfigs, useApplicationConfig, useUpdateApplicationConfig, useProcessorRouteMapping } from '../../api/queries/commands';
|
||||||
import type { ApplicationConfig, TapDefinition } from '../../api/queries/commands';
|
import type { ApplicationConfig, TapDefinition } from '../../api/queries/commands';
|
||||||
import { useRouteCatalog } from '../../api/queries/catalog';
|
import { useRouteCatalog } from '../../api/queries/catalog';
|
||||||
import { api } from '../../api/client';
|
|
||||||
import type { AppCatalogEntry, RouteSummary } from '../../api/types';
|
import type { AppCatalogEntry, RouteSummary } from '../../api/types';
|
||||||
import styles from './AppConfigPage.module.css';
|
import styles from './AppConfigPage.module.css';
|
||||||
|
|
||||||
@@ -87,32 +85,8 @@ function AppConfigDetail({ appId, onClose }: { appId: string; onClose: () => voi
|
|||||||
return entry?.routes ?? [];
|
return entry?.routes ?? [];
|
||||||
}, [catalog, appId]);
|
}, [catalog, appId]);
|
||||||
|
|
||||||
// Fetch diagrams for all routes to build processorId → routeId mapping
|
// processorId → routeId mapping from backend
|
||||||
const diagramQueries = useQueries({
|
const { data: processorToRoute = {} } = useProcessorRouteMapping(appId);
|
||||||
queries: appRoutes.map((r) => ({
|
|
||||||
queryKey: ['diagrams', 'byRoute', appId, r.routeId],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data, error } = await api.GET('/diagrams', {
|
|
||||||
params: { query: { application: appId!, routeId: r.routeId } },
|
|
||||||
});
|
|
||||||
if (error) return { routeId: r.routeId, nodes: [] as Array<{ id?: string }> };
|
|
||||||
return { routeId: r.routeId, nodes: (data as any)?.nodes ?? [] };
|
|
||||||
},
|
|
||||||
enabled: !!appId,
|
|
||||||
staleTime: 60_000,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
const processorToRoute = useMemo(() => {
|
|
||||||
const map: Record<string, string> = {};
|
|
||||||
for (const q of diagramQueries) {
|
|
||||||
if (!q.data) continue;
|
|
||||||
for (const node of q.data.nodes) {
|
|
||||||
if (node.id) map[node.id] = q.data.routeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}, [diagramQueries.map(q => q.data)]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config) {
|
if (config) {
|
||||||
|
|||||||
@@ -172,19 +172,10 @@ export default function ExchangeDetail() {
|
|||||||
const { routeFlows, flowIndexMap } = useMemo(() => {
|
const { routeFlows, flowIndexMap } = useMemo(() => {
|
||||||
let nodes: RouteNode[]
|
let nodes: RouteNode[]
|
||||||
if (diagram?.nodes) {
|
if (diagram?.nodes) {
|
||||||
// Flatten processors to build diagramNodeId → processorId lookup
|
// node.id is the processorId (Camel nodeId), so lookup is direct
|
||||||
const flatProcs: Array<{ diagramNodeId?: string; processorId?: string }> = []
|
|
||||||
function flattenProcs(list: any[]) {
|
|
||||||
for (const n of list) { flatProcs.push(n); if (n.children) flattenProcs(n.children) }
|
|
||||||
}
|
|
||||||
flattenProcs(procList)
|
|
||||||
const pidLookup = new Map(flatProcs
|
|
||||||
.filter(p => p.diagramNodeId && p.processorId)
|
|
||||||
.map(p => [p.diagramNodeId!, p.processorId!]))
|
|
||||||
|
|
||||||
nodes = mapDiagramToRouteNodes(diagram.nodes, procList).map((node, i) => ({
|
nodes = mapDiagramToRouteNodes(diagram.nodes, procList).map((node, i) => ({
|
||||||
...node,
|
...node,
|
||||||
badges: badgesFor(pidLookup.get(diagram.nodes[i]?.id ?? '') ?? diagram.nodes[i]?.id ?? ''),
|
badges: badgesFor(diagram.nodes[i]?.id ?? ''),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
// Fallback: build from processor list
|
// Fallback: build from processor list
|
||||||
@@ -211,22 +202,11 @@ export default function ExchangeDetail() {
|
|||||||
return ids
|
return ids
|
||||||
}, [procList])
|
}, [procList])
|
||||||
|
|
||||||
// ProcessorId lookup: diagram node index → processorId
|
// ProcessorId lookup: diagram node index → processorId (node.id IS processorId)
|
||||||
const flowProcessorIds: string[] = useMemo(() => {
|
const flowProcessorIds: string[] = useMemo(() => {
|
||||||
if (!diagram?.nodes) return processorIds
|
if (!diagram?.nodes) return processorIds
|
||||||
const flatProcs: Array<{ diagramNodeId?: string; processorId?: string }> = []
|
return diagram.nodes.map(node => node.id ?? '')
|
||||||
function flatten(nodes: any[]) {
|
}, [diagram, processorIds])
|
||||||
for (const n of nodes) {
|
|
||||||
flatProcs.push(n)
|
|
||||||
if (n.children) flatten(n.children)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flatten(procList)
|
|
||||||
const lookup = new Map(flatProcs
|
|
||||||
.filter(p => p.diagramNodeId && p.processorId)
|
|
||||||
.map(p => [p.diagramNodeId!, p.processorId!]))
|
|
||||||
return diagram.nodes.map(node => lookup.get(node.id ?? '') ?? node.id ?? '')
|
|
||||||
}, [diagram, procList, processorIds])
|
|
||||||
|
|
||||||
// Map flow display index → processor tree index (for snapshot API)
|
// Map flow display index → processor tree index (for snapshot API)
|
||||||
const flowToTreeIndex = useMemo(() =>
|
const flowToTreeIndex = useMemo(() =>
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ function mapStatus(status: string | undefined): RouteNode['status'] {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps diagram PositionedNodes + execution ProcessorNodes to RouteFlow RouteNode[] format.
|
* Maps diagram PositionedNodes + execution ProcessorNodes to RouteFlow RouteNode[] format.
|
||||||
* Joins on diagramNodeId → node.id.
|
* Joins on processorId → node.id (node IDs are Camel processor IDs).
|
||||||
*/
|
*/
|
||||||
export function mapDiagramToRouteNodes(
|
export function mapDiagramToRouteNodes(
|
||||||
diagramNodes: Array<{ id?: string; label?: string; type?: string }>,
|
diagramNodes: Array<{ id?: string; label?: string; type?: string }>,
|
||||||
processors: Array<{ diagramNodeId?: string; processorId?: string; status?: string; durationMs?: number; children?: any[] }>
|
processors: Array<{ processorId?: string; status?: string; durationMs?: number; children?: any[] }>
|
||||||
): RouteNode[] {
|
): RouteNode[] {
|
||||||
// Flatten processor tree
|
// Flatten processor tree
|
||||||
const flatProcessors: typeof processors = [];
|
const flatProcessors: typeof processors = [];
|
||||||
@@ -36,10 +36,10 @@ export function mapDiagramToRouteNodes(
|
|||||||
}
|
}
|
||||||
flatten(processors || []);
|
flatten(processors || []);
|
||||||
|
|
||||||
// Build lookup: diagramNodeId → processor
|
// Build lookup: processorId → processor
|
||||||
const procMap = new Map<string, (typeof flatProcessors)[0]>();
|
const procMap = new Map<string, (typeof flatProcessors)[0]>();
|
||||||
for (const p of flatProcessors) {
|
for (const p of flatProcessors) {
|
||||||
if (p.diagramNodeId) procMap.set(p.diagramNodeId, p);
|
if (p.processorId) procMap.set(p.processorId, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
return diagramNodes.map(node => {
|
return diagramNodes.map(node => {
|
||||||
|
|||||||
Reference in New Issue
Block a user