directlx-claude-config/plans/synchronous-crunching-beave...

193 lines
7.8 KiB
Markdown

# Fix Agent Filtering on ATM List (Issue #7)
## Context
The ATM list in HiveOps Incident Management has three filter categories: Status, Model, and Agent Connection. The Agent Connection filter UI (Connected/Disconnected/Never Connected) is fully implemented but **does not actually filter the results**. Users can select filter options, but all ATMs continue to be displayed regardless of the selection.
**Root Cause:** The `agentFilter` variable exists in the frontend component but is never sent to the backend API. The backend also lacks the implementation to filter by agent connection status.
Agent connection status is calculated based on the `lastHeartbeat` timestamp in the `AtmProperties` table:
- **NEVER_CONNECTED**: `lastHeartbeat` is null
- **CONNECTED**: `lastHeartbeat` is within the last 5 minutes
- **DISCONNECTED**: `lastHeartbeat` is older than 5 minutes
## Implementation Plan
### 1. Add Bidirectional Relationship in Atm Entity
**File:** `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/entity/Atm.java`
Add `@OneToOne` relationship to `AtmProperties` after line 38:
```java
@OneToOne(mappedBy = "atm", fetch = FetchType.LAZY)
private AtmProperties atmProperties;
public AtmProperties getAtmProperties() { return atmProperties; }
public void setAtmProperties(AtmProperties atmProperties) { this.atmProperties = atmProperties; }
```
This enables JPA Criteria API joins in the specification.
### 2. Implement Agent Status Filtering in Specification
**File:** `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/specification/AtmSpecification.java`
Update `withFilters()` method signature (line 13) to accept `agentStatuses` parameter and implement time-based filtering logic:
```java
public static Specification<Atm> withFilters(String search, List<String> statuses,
List<String> models, List<String> agentStatuses) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// ... existing search, status, model filtering ...
// NEW: Agent connection status filtering
if (agentStatuses != null && !agentStatuses.isEmpty()) {
var propsJoin = root.join("atmProperties", jakarta.persistence.criteria.JoinType.LEFT);
List<Predicate> agentPredicates = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
LocalDateTime fiveMinutesAgo = now.minusMinutes(5);
for (String agentStatus : agentStatuses) {
switch (agentStatus.toLowerCase()) {
case "connected":
agentPredicates.add(cb.and(
cb.isNotNull(propsJoin.get("lastHeartbeat")),
cb.greaterThan(propsJoin.get("lastHeartbeat"), fiveMinutesAgo)
));
break;
case "disconnected":
agentPredicates.add(cb.and(
cb.isNotNull(propsJoin.get("lastHeartbeat")),
cb.lessThanOrEqualTo(propsJoin.get("lastHeartbeat"), fiveMinutesAgo)
));
break;
case "never_connected":
agentPredicates.add(cb.isNull(propsJoin.get("lastHeartbeat")));
break;
}
}
if (!agentPredicates.isEmpty()) {
predicates.add(cb.or(agentPredicates.toArray(new Predicate[0])));
}
}
return cb.and(predicates.toArray(new Predicate[0]));
};
}
```
Add import: `import java.time.LocalDateTime;`
### 3. Update Controller to Accept Agent Status Parameter
**File:** `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/controller/AtmController.java`
Update method signature (lines 41-48) to add `agentStatus` parameter:
```java
@GetMapping("/paginated")
public ResponseEntity<Page<AtmDTO>> getAllAtmsPaginated(
@RequestParam(required = false) String search,
@RequestParam(required = false) List<String> status,
@RequestParam(required = false) List<String> model,
@RequestParam(required = false) List<String> agentStatus,
Pageable pageable) {
return ResponseEntity.ok(atmService.getAllAtmsPaginated(search, status, model, agentStatus, pageable));
}
```
### 4. Update Service Layer
**File:** `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/service/AtmService.java`
Update method signature (lines 66-69) to pass `agentStatuses` to specification:
```java
public Page<AtmDTO> getAllAtmsPaginated(String search, List<String> statuses,
List<String> models, List<String> agentStatuses,
Pageable pageable) {
Specification<Atm> spec = AtmSpecification.withFilters(search, statuses, models, agentStatuses);
return atmRepository.findAll(spec, pageable).map(this::mapToDto);
}
```
### 5. Update Frontend API Interface
**File:** `/source/hiveops-src/hiveops-incident/frontend/src/lib/api.ts`
Add `agentStatus` parameter to API interface (lines 156-157):
```typescript
getPaginated: (params: PaginationParams & {
search?: string;
status?: string[];
model?: string[];
agentStatus?: string[]
}) =>
```
### 6. Send Agent Filter from Frontend
**File:** `/source/hiveops-src/hiveops-incident/frontend/src/components/AtmManagement/AtmList.svelte`
**Change 1:** Update `fetchAtms()` function (lines 32-44) to send agentFilter:
```typescript
function fetchAtms() {
const params: any = {
page: currentPage,
size: pageSize,
sort: `${sortField},${sortDirection}`,
};
if (searchQuery) params.search = searchQuery;
const statuses = [...statusFilter].map(s => s.toUpperCase());
if (statuses.length > 0) params.status = statuses;
const models = [...modelFilter];
if (models.length > 0) params.model = models;
const agentStatuses = [...agentFilter]; // NEW
if (agentStatuses.length > 0) params.agentStatus = agentStatuses; // NEW
loadAtmsPaginated(params);
}
```
**Change 2:** Update reactive statement (line 146) to re-fetch on agent filter change:
```typescript
$: if (statusFilter || modelFilter || agentFilter) {
```
## Verification
### Manual Testing
1. Start backend and frontend servers
2. Navigate to ATM List page
3. Test single agent filter: Select "Connected" only → verify only ATMs with heartbeat ≤ 5 min show
4. Test multiple selections: Select "Connected" + "Disconnected" → verify all ATMs except never_connected show
5. Test "Never Connected" → verify only ATMs with null lastHeartbeat show
6. Test combined filters: Select agent filter + status filter → verify both filters apply
7. Verify pagination works with agent filter active
8. Verify "Clear all" button clears agent filter
9. Check browser console for API requests → confirm `agentStatus` parameter is sent
### Backend Testing
Check database query performance with agent filtering:
```sql
-- Verify query plan includes join to atm_properties
EXPLAIN SELECT * FROM atms
LEFT JOIN atm_properties ON atms.id = atm_properties.atm_id
WHERE atm_properties.last_heartbeat > NOW() - INTERVAL 5 MINUTE;
```
## Files to Modify
1. `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/entity/Atm.java`
2. `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/specification/AtmSpecification.java`
3. `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/controller/AtmController.java`
4. `/source/hiveops-src/hiveops-incident/backend/src/main/java/com/hiveops/incident/service/AtmService.java`
5. `/source/hiveops-src/hiveops-incident/frontend/src/lib/api.ts`
6. `/source/hiveops-src/hiveops-incident/frontend/src/components/AtmManagement/AtmList.svelte`