# 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 withFilters(String search, List statuses, List models, List agentStatuses) { return (root, query, cb) -> { List 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 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> getAllAtmsPaginated( @RequestParam(required = false) String search, @RequestParam(required = false) List status, @RequestParam(required = false) List model, @RequestParam(required = false) List 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 getAllAtmsPaginated(String search, List statuses, List models, List agentStatuses, Pageable pageable) { Specification 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`