193 lines
7.8 KiB
Markdown
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`
|