docs: add implementation plan for taps, attributes, replay UI features
14-task plan covering: database migration, attributes pipeline, test-expression command with request-reply, OpenAPI regeneration, frontend types/hooks, ExchangeDetail attributes + replay modal, Dashboard attributes column, RouteDetail recording toggle + taps tab + tap CRUD modal, and AppConfigDetailPage restructure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
858
docs/superpowers/plans/2026-03-26-taps-attributes-replay.md
Normal file
858
docs/superpowers/plans/2026-03-26-taps-attributes-replay.md
Normal file
@@ -0,0 +1,858 @@
|
||||
# Taps, Business Attributes & Enhanced Replay — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add UI and backend support for tap management, business attribute display, enhanced replay, per-route recording toggles, and success compression.
|
||||
|
||||
**Architecture:** Backend-first approach — add attributes to the execution pipeline, then build the command infrastructure for test-expression and replay, then layer on the frontend features page by page. Each task produces a self-contained, committable unit.
|
||||
|
||||
**Tech Stack:** Java 17 / Spring Boot 3.4 (backend), React 18 / TypeScript / TanStack Query (frontend), @cameleer/design-system components, PostgreSQL (JSONB), OpenSearch.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-03-26-taps-attributes-replay-ui-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
### Backend — New Files
|
||||
- `cameleer3-server-app/src/main/resources/db/migration/V5__attributes.sql` — Flyway migration adding `attributes JSONB` to executions and processor_executions tables
|
||||
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/TestExpressionRequest.java` — Request DTO for test-expression endpoint
|
||||
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/TestExpressionResponse.java` — Response DTO for test-expression endpoint
|
||||
|
||||
### Backend — Modified Files
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandType.java` — add TEST_EXPRESSION
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java` — add attributes to ExecutionRecord and ProcessorRecord
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java` — add attributes field
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ProcessorNode.java` — add attributes field
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java` — pass attributes through tree reconstruction
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java` — add attributes field
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java` — extract attributes from RouteExecution/ProcessorExecution
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/ExecutionDocument.java` — add attributes to ProcessorDoc
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/indexing/SearchIndexer.java` — include attributes in indexing
|
||||
- `cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentRegistryService.java` — add CompletableFuture-based command reply support
|
||||
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java` — add attributes to INSERT/UPDATE queries
|
||||
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/OpenSearchIndex.java` — add attributes to toMap() and fromSearchHit()
|
||||
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ApplicationConfigController.java` — add test-expression endpoint
|
||||
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java` — add test-expression mapping, complete futures on ACK
|
||||
- `cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/CommandAckRequest.java` — add optional data field
|
||||
|
||||
### Frontend — Modified Files
|
||||
- `ui/src/api/schema.d.ts` — add attributes to ExecutionDetail, ProcessorNode, ExecutionSummary
|
||||
- `ui/src/api/queries/commands.ts` — add TapDefinition type, extend ApplicationConfig, add test-expression mutation, add replay mutation
|
||||
- `ui/src/pages/ExchangeDetail/ExchangeDetail.tsx` — attributes strip, per-processor attributes, replay modal
|
||||
- `ui/src/pages/ExchangeDetail/ExchangeDetail.module.css` — attributes strip and replay styles
|
||||
- `ui/src/pages/Dashboard/Dashboard.tsx` — attributes column in exchanges table
|
||||
- `ui/src/pages/Routes/RouteDetail.tsx` — recording toggle, taps tab, tap modal with test
|
||||
- `ui/src/pages/Routes/RouteDetail.module.css` — taps and recording styles
|
||||
- `ui/src/pages/Admin/AppConfigDetailPage.tsx` — restructure to 3 sections
|
||||
- `ui/src/pages/Admin/AppConfigDetailPage.module.css` — updated styles
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Verify Prerequisites and Database Migration
|
||||
|
||||
**Files:**
|
||||
- Create: `cameleer3-server-app/src/main/resources/db/migration/V5__attributes.sql`
|
||||
|
||||
- [ ] **Step 1: Verify cameleer3-common has attributes support**
|
||||
|
||||
Confirm the `cameleer3-common` SNAPSHOT dependency includes `RouteExecution.getAttributes()` and `ProcessorExecution.getAttributes()`. Run:
|
||||
```bash
|
||||
mvn dependency:sources -pl cameleer3-server-core -q
|
||||
```
|
||||
Then inspect the source jar for `RouteExecution.java` to confirm the `attributes` field exists. If it does not, the dependency must be updated first.
|
||||
|
||||
- [ ] **Step 2: Write migration SQL**
|
||||
|
||||
```sql
|
||||
-- V5__attributes.sql
|
||||
ALTER TABLE executions ADD COLUMN IF NOT EXISTS attributes JSONB;
|
||||
ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS attributes JSONB;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify migration compiles**
|
||||
|
||||
Run: `cd cameleer3-server-app && mvn compile -pl . -q`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add cameleer3-server-app/src/main/resources/db/migration/V5__attributes.sql
|
||||
git commit -m "feat: add attributes JSONB columns to executions and processor_executions"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Backend — Add Attributes to Storage Records and Detail Models
|
||||
|
||||
**Files:**
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/ExecutionStore.java`
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ExecutionDetail.java`
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/ProcessorNode.java`
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/search/ExecutionSummary.java`
|
||||
|
||||
- [ ] **Step 1: Add `attributes` field to `ExecutionRecord`**
|
||||
|
||||
In `ExecutionStore.java`, add `String attributes` (JSONB as string) as the last parameter of the `ExecutionRecord` record. This is a serialized `Map<String, String>`.
|
||||
|
||||
- [ ] **Step 2: Add `attributes` field to `ProcessorRecord`**
|
||||
|
||||
In `ExecutionStore.java`, add `String attributes` (JSONB as string) as the last parameter of the `ProcessorRecord` record.
|
||||
|
||||
- [ ] **Step 3: Add `attributes` field to `ExecutionDetail`**
|
||||
|
||||
Add `Map<String, String> attributes` as the last parameter of the `ExecutionDetail` record (after `outputHeaders`).
|
||||
|
||||
- [ ] **Step 4: Add `attributes` field to `ProcessorNode`**
|
||||
|
||||
`ProcessorNode` is a mutable class with a constructor. Add a `Map<String, String> attributes` field with getter. Add it to the constructor. Update the existing `ProcessorNode` constructor calls in `DetailService.java` to pass `null` or the attributes map.
|
||||
|
||||
- [ ] **Step 5: Add `attributes` field to `ExecutionSummary`**
|
||||
|
||||
Add `Map<String, String> attributes` as the last parameter (after `highlight`).
|
||||
|
||||
- [ ] **Step 6: Verify compilation**
|
||||
|
||||
Run: `mvn compile -q`
|
||||
Expected: Compilation errors in files that construct these records — these will be fixed in the next tasks.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add cameleer3-server-core/
|
||||
git commit -m "feat: add attributes field to ExecutionRecord, ProcessorRecord, ExecutionDetail, ProcessorNode, ExecutionSummary"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Backend — Attributes Ingestion Pipeline
|
||||
|
||||
**Files:**
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java`
|
||||
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java`
|
||||
|
||||
- [ ] **Step 1: Extract attributes in `IngestionService.toExecutionRecord()`**
|
||||
|
||||
In the `toExecutionRecord()` method (~line 76-111), serialize `execution.getAttributes()` to JSON string using Jackson `ObjectMapper`. Pass it as the new `attributes` parameter to `ExecutionRecord`. If attributes is null or empty, pass `null`.
|
||||
|
||||
```java
|
||||
String attributes = null;
|
||||
if (execution.getAttributes() != null && !execution.getAttributes().isEmpty()) {
|
||||
attributes = JSON.writeValueAsString(execution.getAttributes());
|
||||
}
|
||||
```
|
||||
|
||||
Note: `IngestionService` has a static `private static final ObjectMapper JSON` field (line 22). Use `JSON.writeValueAsString()`.
|
||||
|
||||
- [ ] **Step 2: Extract attributes in `IngestionService.flattenProcessors()`**
|
||||
|
||||
In the `flattenProcessors()` method (~line 113-138), serialize each `ProcessorExecution.getAttributes()` to JSON string. Pass as the new `attributes` parameter to `ProcessorRecord`.
|
||||
|
||||
- [ ] **Step 3: Update `PostgresExecutionStore.upsert()`**
|
||||
|
||||
Add `attributes` to the INSERT statement and bind parameters. The column is JSONB, so use `PGobject` with type "jsonb" or cast `?::jsonb` in the SQL.
|
||||
|
||||
In the INSERT (~line 26-32): add `attributes` column and `?::jsonb` placeholder.
|
||||
In the ON CONFLICT UPDATE (~line 33-51): add `attributes = COALESCE(EXCLUDED.attributes, executions.attributes)` merge (follows the existing pattern, e.g., `input_body = COALESCE(EXCLUDED.input_body, executions.input_body)`).
|
||||
In the bind parameters (~line 53-62): bind `record.attributes()`.
|
||||
|
||||
- [ ] **Step 4: Update `PostgresExecutionStore.upsertProcessors()`**
|
||||
|
||||
Same pattern: add `attributes` column, `?::jsonb` placeholder, bind parameter.
|
||||
|
||||
- [ ] **Step 5: Verify compilation**
|
||||
|
||||
Run: `mvn compile -q`
|
||||
Expected: BUILD SUCCESS (or remaining errors from DetailService/SearchIndexer which are next tasks)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add cameleer3-server-core/src/main/java/com/cameleer3/server/core/ingestion/IngestionService.java
|
||||
git add cameleer3-server-app/src/main/java/com/cameleer3/server/app/storage/PostgresExecutionStore.java
|
||||
git commit -m "feat: store execution and processor attributes from agent data"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Backend — Attributes in Detail Service and OpenSearch Indexing
|
||||
|
||||
**Files:**
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/detail/DetailService.java`
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/storage/model/ExecutionDocument.java`
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/indexing/SearchIndexer.java`
|
||||
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/search/OpenSearchIndex.java`
|
||||
|
||||
- [ ] **Step 1: Pass attributes through `DetailService.buildTree()`**
|
||||
|
||||
In `buildTree()` (~line 35-63), when constructing `ProcessorNode` from `ProcessorRecord`, deserialize the `attributes` JSON string back to `Map<String, String>` and pass it to the constructor.
|
||||
|
||||
In `getDetail()` (~line 16-33), when constructing `ExecutionDetail`, deserialize the `ExecutionRecord.attributes()` JSON and pass it as the `attributes` parameter.
|
||||
|
||||
- [ ] **Step 2: Update `PostgresExecutionStore.findById()` and `findProcessors()` queries**
|
||||
|
||||
These SELECT queries need to include the new `attributes` column and map it into `ExecutionRecord` / `ProcessorRecord` via the row mapper.
|
||||
|
||||
- [ ] **Step 3: Add attributes to `ExecutionDocument.ProcessorDoc`**
|
||||
|
||||
Add `String attributes` field to the `ProcessorDoc` record in `ExecutionDocument.java`. Also add `String attributes` to `ExecutionDocument` itself for route-level attributes.
|
||||
|
||||
- [ ] **Step 4: Update `SearchIndexer.indexExecution()`**
|
||||
|
||||
When constructing `ProcessorDoc` objects (~line 68-74), pass `processor.attributes()`. When constructing `ExecutionDocument` (~line 76-80), pass the execution record's attributes.
|
||||
|
||||
- [ ] **Step 5: Update `OpenSearchIndex.toMap()`**
|
||||
|
||||
In the `toMap()` method (~line 303-333), add `"attributes"` to the document map and to each processor sub-document map.
|
||||
|
||||
- [ ] **Step 6: Update `OpenSearchIndex.fromSearchHit()` (or equivalent)**
|
||||
|
||||
When parsing search results back into `ExecutionSummary`, extract the `attributes` field from the OpenSearch hit source and deserialize it into `Map<String, String>`.
|
||||
|
||||
- [ ] **Step 7: Verify compilation**
|
||||
|
||||
Run: `mvn compile -q`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add cameleer3-server-core/ cameleer3-server-app/
|
||||
git commit -m "feat: thread attributes through detail service and OpenSearch indexing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Backend — TEST_EXPRESSION Command and Request-Reply Infrastructure
|
||||
|
||||
**Files:**
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/CommandType.java`
|
||||
- Modify: `cameleer3-server-core/src/main/java/com/cameleer3/server/core/agent/AgentRegistryService.java`
|
||||
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/CommandAckRequest.java`
|
||||
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java`
|
||||
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/TestExpressionRequest.java`
|
||||
- Create: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/TestExpressionResponse.java`
|
||||
- Modify: `cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/ApplicationConfigController.java`
|
||||
|
||||
- [ ] **Step 1: Add TEST_EXPRESSION to CommandType enum**
|
||||
|
||||
```java
|
||||
public enum CommandType {
|
||||
CONFIG_UPDATE,
|
||||
DEEP_TRACE,
|
||||
REPLAY,
|
||||
SET_TRACED_PROCESSORS,
|
||||
TEST_EXPRESSION
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add `data` field to `CommandAckRequest`**
|
||||
|
||||
```java
|
||||
public record CommandAckRequest(String status, String message, String data) {}
|
||||
```
|
||||
|
||||
The `data` field carries structured JSON results (e.g., expression test result). Existing ACKs that don't send data will deserialize `data` as `null`.
|
||||
|
||||
- [ ] **Step 3: Add CompletableFuture map to AgentRegistryService**
|
||||
|
||||
Add a `ConcurrentHashMap<String, CompletableFuture<CommandAckRequest>>` for pending request-reply commands. Add methods:
|
||||
|
||||
```java
|
||||
public CompletableFuture<CommandAckRequest> addCommandWithReply(String agentId, CommandType type, String payload) {
|
||||
AgentCommand command = addCommand(agentId, type, payload);
|
||||
CompletableFuture<CommandAckRequest> future = new CompletableFuture<>();
|
||||
pendingReplies.put(command.id(), future);
|
||||
return future;
|
||||
}
|
||||
|
||||
public void completeReply(String commandId, CommandAckRequest ack) {
|
||||
CompletableFuture<CommandAckRequest> future = pendingReplies.remove(commandId);
|
||||
if (future != null) {
|
||||
future.complete(ack);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: Use `future.orTimeout(5, TimeUnit.SECONDS)` in the caller. The future auto-completes exceptionally on timeout. Add a `whenComplete` handler that removes the entry from `pendingReplies` to prevent leaks:
|
||||
```java
|
||||
future.whenComplete((result, ex) -> pendingReplies.remove(command.id()));
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Complete futures in AgentCommandController.acknowledgeCommand()**
|
||||
|
||||
In the ACK endpoint (~line 156-179), after `registryService.acknowledgeCommand()`, call `registryService.completeReply(commandId, ack)`.
|
||||
|
||||
- [ ] **Step 5: Add test-expression mapping to mapCommandType()**
|
||||
|
||||
```java
|
||||
case "test-expression" -> CommandType.TEST_EXPRESSION;
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Create TestExpressionRequest and TestExpressionResponse DTOs**
|
||||
|
||||
```java
|
||||
// TestExpressionRequest.java
|
||||
public record TestExpressionRequest(String expression, String language, String body, String target) {}
|
||||
|
||||
// TestExpressionResponse.java
|
||||
public record TestExpressionResponse(String result, String error) {}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Add test-expression endpoint to ApplicationConfigController**
|
||||
|
||||
Note: `ApplicationConfigController` does not use `@PreAuthorize` — security is handled at the URL pattern level in the security config. The test-expression endpoint inherits the same access rules as other config endpoints. No `@PreAuthorize` annotation needed.
|
||||
|
||||
```java
|
||||
@PostMapping("/{application}/test-expression")
|
||||
@Operation(summary = "Test a tap expression against sample data via a live agent")
|
||||
public ResponseEntity<TestExpressionResponse> testExpression(
|
||||
@PathVariable String application,
|
||||
@RequestBody TestExpressionRequest request) {
|
||||
// 1. Find a LIVE agent for this application via registryService
|
||||
// 2. Send TEST_EXPRESSION command with addCommandWithReply()
|
||||
// 3. Await CompletableFuture with 5s timeout via future.orTimeout(5, TimeUnit.SECONDS)
|
||||
// 4. Parse ACK data as result/error, return TestExpressionResponse
|
||||
// Handle: no live agent (404), timeout (504), parse error (500)
|
||||
// Clean up: future.whenComplete removes from pendingReplies map on timeout
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Verify compilation**
|
||||
|
||||
Run: `mvn compile -q`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add cameleer3-server-core/ cameleer3-server-app/
|
||||
git commit -m "feat: add TEST_EXPRESSION command with request-reply infrastructure"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Backend — Regenerate OpenAPI and Schema
|
||||
|
||||
**Files:**
|
||||
- Modify: `openapi.json` (regenerated)
|
||||
- Modify: `ui/src/api/schema.d.ts` (regenerated)
|
||||
|
||||
- [ ] **Step 1: Build the server to generate updated OpenAPI spec**
|
||||
|
||||
Run: `mvn clean compile -q`
|
||||
|
||||
- [ ] **Step 2: Start the server temporarily to extract OpenAPI JSON**
|
||||
|
||||
Run the server, fetch `http://localhost:8080/v3/api-docs`, save to `openapi.json`. Alternatively, if the project has an automated OpenAPI generation step, use that.
|
||||
|
||||
- [ ] **Step 3: Regenerate schema.d.ts from openapi.json**
|
||||
|
||||
Run the existing schema generation command (check package.json scripts in ui/).
|
||||
|
||||
- [ ] **Step 4: Verify the new types include `attributes` on ExecutionDetail, ProcessorNode, ExecutionSummary**
|
||||
|
||||
Read `ui/src/api/schema.d.ts` and confirm the fields are present. Note: the OpenAPI generator may strip nullable fields (e.g., `highlight` exists on Java `ExecutionSummary` but not in the current schema). If `attributes` is missing, add `@Schema(nullable = true)` or `@JsonInclude(JsonInclude.Include.ALWAYS)` annotation on the Java DTO and regenerate. Alternatively, manually add the field to `schema.d.ts`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add openapi.json ui/src/api/schema.d.ts
|
||||
git commit -m "chore: regenerate openapi.json and schema.d.ts with attributes and test-expression"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Frontend — TypeScript Types and API Hooks
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/api/queries/commands.ts`
|
||||
|
||||
- [ ] **Step 1: Add TapDefinition interface**
|
||||
|
||||
```typescript
|
||||
export interface TapDefinition {
|
||||
tapId: string;
|
||||
processorId: string;
|
||||
target: 'INPUT' | 'OUTPUT' | 'BOTH';
|
||||
expression: string;
|
||||
language: string;
|
||||
attributeName: string;
|
||||
attributeType: 'BUSINESS_OBJECT' | 'CORRELATION' | 'EVENT' | 'CUSTOM';
|
||||
enabled: boolean;
|
||||
version: number;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Extend ApplicationConfig interface**
|
||||
|
||||
Add to the existing `ApplicationConfig` interface:
|
||||
|
||||
```typescript
|
||||
taps: TapDefinition[];
|
||||
tapVersion: number;
|
||||
routeRecording: Record<string, boolean>;
|
||||
compressSuccess: boolean;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add useTestExpression mutation hook**
|
||||
|
||||
```typescript
|
||||
export function useTestExpression() {
|
||||
return useMutation({
|
||||
mutationFn: async ({ application, expression, language, body, target }: {
|
||||
application: string;
|
||||
expression: string;
|
||||
language: string;
|
||||
body: string;
|
||||
target: string;
|
||||
}) => {
|
||||
const { data, error } = await api.POST('/config/{application}/test-expression', {
|
||||
params: { path: { application } },
|
||||
body: { expression, language, body, target },
|
||||
});
|
||||
if (error) throw new Error('Failed to test expression');
|
||||
return data!;
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add useReplayExchange mutation hook**
|
||||
|
||||
```typescript
|
||||
export function useReplayExchange() {
|
||||
return useMutation({
|
||||
mutationFn: async ({ agentId, headers, body }: {
|
||||
agentId: string;
|
||||
headers: Record<string, string>;
|
||||
body: string;
|
||||
}) => {
|
||||
const { data, error } = await api.POST('/agents/{id}/commands', {
|
||||
params: { path: { id: agentId } },
|
||||
body: { type: 'replay', payload: { headers, body } } as any,
|
||||
});
|
||||
if (error) throw new Error('Failed to send replay command');
|
||||
return data!;
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS (or type errors in pages that now receive new fields — those pages are updated in later tasks)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/api/queries/commands.ts
|
||||
git commit -m "feat: add TapDefinition type, extend ApplicationConfig, add test-expression and replay hooks"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Frontend — Business Attributes on ExchangeDetail
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/pages/ExchangeDetail/ExchangeDetail.tsx`
|
||||
- Modify: `ui/src/pages/ExchangeDetail/ExchangeDetail.module.css`
|
||||
|
||||
- [ ] **Step 1: Add attributes strip to exchange header**
|
||||
|
||||
After the header info row and before the stat boxes, render the route-level attributes:
|
||||
|
||||
```tsx
|
||||
{detail.attributes && Object.keys(detail.attributes).length > 0 && (
|
||||
<div className={styles.attributesStrip}>
|
||||
<span className={styles.attributesLabel}>Attributes</span>
|
||||
{Object.entries(detail.attributes).map(([key, value]) => (
|
||||
<Badge key={key} label={`${key}: ${value}`} color="auto" variant="filled" />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add per-processor attributes in processor detail panel**
|
||||
|
||||
In the processor detail section (where the selected processor's message IN/OUT is shown), add attributes badges if the selected processor has them. Access via `detail.processors` tree — traverse the nested tree to find the processor at the selected index and read its `attributes` map. Note: body/headers data comes from a separate `useProcessorSnapshot` call, but `attributes` is inline on the `ProcessorNode` in the detail response — no additional API call needed.
|
||||
|
||||
- [ ] **Step 3: Add CSS for attributes strip**
|
||||
|
||||
```css
|
||||
.attributesStrip {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.attributesLabel {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-right: 4px;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/pages/ExchangeDetail/
|
||||
git commit -m "feat: display business attributes on ExchangeDetail page"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Frontend — Replay Modal on ExchangeDetail
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/pages/ExchangeDetail/ExchangeDetail.tsx`
|
||||
- Modify: `ui/src/pages/ExchangeDetail/ExchangeDetail.module.css`
|
||||
|
||||
- [ ] **Step 1: Add replay button to exchange header**
|
||||
|
||||
Add a "Replay" button (primary variant) in the header action area. Only render for OPERATOR/ADMIN roles (check with `useAuthStore()`).
|
||||
|
||||
```tsx
|
||||
<Button variant="primary" size="sm" onClick={() => setReplayOpen(true)}>
|
||||
↻ Replay
|
||||
</Button>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Build the replay modal component**
|
||||
|
||||
Add state: `replayOpen`, `replayHeaders` (key-value array), `replayBody` (string), `replayAgent` (string), `replayTab` ('headers' | 'body').
|
||||
|
||||
Pre-populate from `detail.inputHeaders` (parse JSON string to object) and `detail.inputBody`.
|
||||
|
||||
Use Modal (size="lg"), Tabs for Headers/Body, and the `useReplayExchange` mutation hook.
|
||||
|
||||
Headers tab: render editable rows with Input fields for key and value, remove button per row, "Add header" link at bottom.
|
||||
|
||||
Body tab: Textarea with monospace font, pre-populated with `detail.inputBody`.
|
||||
|
||||
- [ ] **Step 3: Wire up agent selector**
|
||||
|
||||
Use `useAgents('LIVE', detail.applicationName)` to populate a Select dropdown. Default to the agent that originally processed this exchange (`detail.agentId`) if it's still LIVE.
|
||||
|
||||
- [ ] **Step 4: Wire up replay submission**
|
||||
|
||||
On "Replay" click: call `replayExchange.mutate({ agentId, headers, body })`. Show loading spinner on button. On success: `toast('Replay command sent')`, close modal. On error: `toast('Replay failed: ...')`.
|
||||
|
||||
- [ ] **Step 5: Add CSS for replay modal elements**
|
||||
|
||||
Style the warning banner, header table, body textarea, and agent selector.
|
||||
|
||||
- [ ] **Step 6: Verify build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/pages/ExchangeDetail/
|
||||
git commit -m "feat: add replay modal with editable headers and body on ExchangeDetail"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Frontend — Attributes Column on Dashboard
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/pages/Dashboard/Dashboard.tsx`
|
||||
|
||||
- [ ] **Step 1: Add attributes column to the exchanges table**
|
||||
|
||||
In `buildBaseColumns()` (~line 97-163), add a new column after the `applicationName` column. Use CSS module classes (not inline styles — per project convention in `feedback_css_modules_not_inline.md`):
|
||||
|
||||
```typescript
|
||||
{
|
||||
key: 'attributes',
|
||||
header: 'Attributes',
|
||||
render: (_, row) => {
|
||||
const attrs = row.attributes;
|
||||
if (!attrs || Object.keys(attrs).length === 0) return <span className={styles.muted}>—</span>;
|
||||
const entries = Object.entries(attrs);
|
||||
const shown = entries.slice(0, 2);
|
||||
const overflow = entries.length - 2;
|
||||
return (
|
||||
<div className={styles.attrCell}>
|
||||
{shown.map(([k, v]) => (
|
||||
<Badge key={k} label={String(v)} color="auto" title={k} />
|
||||
))}
|
||||
{overflow > 0 && <span className={styles.attrOverflow}>+{overflow}</span>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Add corresponding CSS classes to `Dashboard.module.css`:
|
||||
```css
|
||||
.attrCell { display: flex; gap: 4px; align-items: center; }
|
||||
.attrOverflow { font-size: 10px; color: var(--text-muted); }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/pages/Dashboard/Dashboard.tsx
|
||||
git commit -m "feat: show business attributes as compact badges in dashboard exchanges table"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 11: Frontend — RouteDetail Recording Toggle and Taps KPI
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/pages/Routes/RouteDetail.tsx`
|
||||
- Modify: `ui/src/pages/Routes/RouteDetail.module.css`
|
||||
|
||||
- [ ] **Step 1: Add recording toggle to route header**
|
||||
|
||||
Add imports: `import { useApplicationConfig, useUpdateApplicationConfig } from '../../api/queries/commands'` and `Toggle` from `@cameleer/design-system`.
|
||||
|
||||
In the route header section, add a pill-styled container with a Toggle component:
|
||||
|
||||
```tsx
|
||||
const config = useApplicationConfig(appId);
|
||||
const updateConfig = useUpdateApplicationConfig();
|
||||
|
||||
const isRecording = config.data?.routeRecording?.[routeId] !== false; // default true
|
||||
|
||||
function toggleRecording() {
|
||||
if (!config.data) return;
|
||||
const routeRecording = { ...config.data.routeRecording, [routeId]: !isRecording };
|
||||
updateConfig.mutate({ ...config.data, routeRecording });
|
||||
}
|
||||
```
|
||||
|
||||
Render:
|
||||
```tsx
|
||||
<div className={styles.recordingPill}>
|
||||
<span className={styles.recordingLabel}>Recording</span>
|
||||
<Toggle checked={isRecording} onChange={toggleRecording} />
|
||||
</div>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add "Active Taps" to KPI strip**
|
||||
|
||||
Count enabled taps for this route's processors (cross-reference tap processorIds with this route's processor list from diagram data). Add to `kpiItems` array.
|
||||
|
||||
- [ ] **Step 3: Add "Taps" tab to tabs array**
|
||||
|
||||
```typescript
|
||||
const tapCount = /* count taps for this route */;
|
||||
const tabs = [
|
||||
{ label: 'Performance', value: 'performance' },
|
||||
{ label: 'Recent Executions', value: 'executions', count: exchangeRows.length },
|
||||
{ label: 'Error Patterns', value: 'errors', count: errorPatterns.length },
|
||||
{ label: 'Taps', value: 'taps', count: tapCount },
|
||||
];
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add CSS for recording pill**
|
||||
|
||||
```css
|
||||
.recordingPill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.recordingLabel {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/pages/Routes/
|
||||
git commit -m "feat: add recording toggle and taps KPI to RouteDetail header"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 12: Frontend — RouteDetail Taps Tab and Tap Modal
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/pages/Routes/RouteDetail.tsx`
|
||||
- Modify: `ui/src/pages/Routes/RouteDetail.module.css`
|
||||
|
||||
- [ ] **Step 1: Render taps DataTable when "Taps" tab is active**
|
||||
|
||||
Filter `config.data.taps` to only taps whose `processorId` exists in this route's diagram. Display in a DataTable with columns: Attribute, Processor, Expression, Language, Target, Type, Enabled (Toggle), Actions.
|
||||
|
||||
Empty state: "No taps configured for this route. Add a tap to extract business attributes from exchange data."
|
||||
|
||||
- [ ] **Step 2: Build the Add/Edit Tap modal**
|
||||
|
||||
State: `tapModalOpen`, `editingTap` (null for new, TapDefinition for edit), form fields.
|
||||
|
||||
Modal contents:
|
||||
- FormField + Input for Attribute Name
|
||||
- FormField + Select for Processor (options from `useDiagramLayout` node list)
|
||||
- Two FormFields side-by-side: Select for Language (simple, jsonpath, xpath, jq, groovy) and Select for Target (INPUT, OUTPUT, BOTH)
|
||||
- FormField + Textarea for Expression (monospace)
|
||||
- Attribute Type pill selector (4 options, styled as button group)
|
||||
- Toggle for Enabled
|
||||
|
||||
- [ ] **Step 3: Add Test Expression section to tap modal**
|
||||
|
||||
Collapsible section (default expanded) with two tabs: "Recent Exchange" and "Custom Payload".
|
||||
|
||||
Recent Exchange tab:
|
||||
- Use `useSearchExecutions` with this route's filter to get recent exchanges as summaries
|
||||
- Auto-select most recent exchange, then fetch its detail via `useExecutionDetail` to get the `inputBody` for the test payload
|
||||
- Select dropdown to change exchange
|
||||
- "Test" button calls `useTestExpression` mutation with the exchange's body
|
||||
|
||||
Custom Payload tab:
|
||||
- Textarea pre-populated from the most recent exchange's body (fetched via detail endpoint)
|
||||
- Switching from Recent Exchange tab carries the payload over
|
||||
- "Test" button calls `useTestExpression` mutation
|
||||
|
||||
Result display: green box for success, red box for error.
|
||||
|
||||
- [ ] **Step 4: Wire up tap save**
|
||||
|
||||
On save: update the `taps` array in ApplicationConfig (add new or replace existing by tapId), then call `updateConfig.mutate()`. Generate `tapId` as UUID for new taps.
|
||||
|
||||
- [ ] **Step 5: Wire up tap delete**
|
||||
|
||||
On delete: remove tap from array, call `updateConfig.mutate()`. Import and use `ConfirmDialog` from `@cameleer/design-system` before deleting.
|
||||
|
||||
- [ ] **Step 6: Wire up enabled toggle inline**
|
||||
|
||||
Toggle in the DataTable row directly calls config update (toggle the specific tap's `enabled` field).
|
||||
|
||||
- [ ] **Step 7: Add CSS for taps tab content**
|
||||
|
||||
Style the taps header (title + button), tap modal form layout, test expression section, result boxes.
|
||||
|
||||
- [ ] **Step 8: Verify build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/pages/Routes/
|
||||
git commit -m "feat: add taps management tab with CRUD modal and expression testing on RouteDetail"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 13: Frontend — AppConfigDetailPage Restructure
|
||||
|
||||
**Files:**
|
||||
- Modify: `ui/src/pages/Admin/AppConfigDetailPage.tsx`
|
||||
- Modify: `ui/src/pages/Admin/AppConfigDetailPage.module.css`
|
||||
|
||||
- [ ] **Step 1: Merge Logging + Observability into "Settings" section**
|
||||
|
||||
Replace the two separate `SectionHeader` sections with a single "Settings" section. Render all setting badges in a single flex row: Log Forwarding, Engine Level, Payload Capture, Metrics, Sampling Rate, Compress Success (new field).
|
||||
|
||||
Edit mode: all badges become dropdowns/toggles as before, plus a new Toggle for `compressSuccess`.
|
||||
|
||||
- [ ] **Step 2: Merge Traced Processors + Taps into "Traces & Taps" section**
|
||||
|
||||
Build a merged data structure: for each processor that has either a trace override or taps, create a row with Route, Processor, Capture badge, Taps badges.
|
||||
|
||||
To resolve processor-to-route mapping: fetch route catalog for this application, then for each route fetch its diagram. Build a `Map<processorId, routeId>` by iterating diagram nodes. For processors not found, show "unknown".
|
||||
|
||||
Table columns: Route, Processor, Capture (badge/select in edit mode), Taps (attribute badges with enabled indicators, read-only).
|
||||
|
||||
Summary: "N traced · M taps · manage taps on route pages".
|
||||
|
||||
- [ ] **Step 3: Add "Route Recording" section**
|
||||
|
||||
Fetch route list from `useRouteCatalog` filtered by application. Render table with Route name and Toggle.
|
||||
|
||||
In view mode: toggles show current state (disabled).
|
||||
In edit mode: toggles are interactive.
|
||||
|
||||
Default for routes not in `routeRecording` map: recording enabled (true).
|
||||
|
||||
Summary: "N of M routes recording".
|
||||
|
||||
- [ ] **Step 4: Update form state for new fields**
|
||||
|
||||
Add `compressSuccess` and `routeRecording` to the form state object and `updateField` handler. Ensure save sends the complete config including new fields.
|
||||
|
||||
- [ ] **Step 5: Update CSS for restructured sections**
|
||||
|
||||
Adjust section spacing, flex row for merged settings badges.
|
||||
|
||||
- [ ] **Step 6: Verify build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add ui/src/pages/Admin/
|
||||
git commit -m "feat: restructure AppConfigDetailPage to Settings, Traces & Taps, Route Recording sections"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 14: Final Build Verification and Push
|
||||
|
||||
- [ ] **Step 1: Run full backend build**
|
||||
|
||||
Run: `mvn clean compile -q`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 2: Run full frontend build**
|
||||
|
||||
Run: `cd ui && npm run build`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
- [ ] **Step 3: Manual smoke test checklist**
|
||||
|
||||
Verify in browser:
|
||||
- ExchangeDetail shows attributes strip when attributes exist
|
||||
- ExchangeDetail replay button opens modal, can send replay
|
||||
- Dashboard table shows attributes column
|
||||
- RouteDetail shows recording toggle, taps tab with CRUD
|
||||
- Tap modal test expression section works (if live agent available)
|
||||
- AppConfigDetailPage shows 3 merged sections
|
||||
- AppConfigDetailPage edit mode works for compress success and route recording
|
||||
|
||||
- [ ] **Step 4: Push to remote**
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
Reference in New Issue
Block a user