Skip to content

Commit 3b39ba3

Browse files
committed
feat: provide endpoint for OpenSearch evaluations including latest evaluation calculation per control
1 parent 07cc80a commit 3b39ba3

9 files changed

Lines changed: 280 additions & 22 deletions

backend/src/main/java/com/park/utmstack/service/compliance/config/UtmComplianceControlEvaluationService.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.park.utmstack.service.dto.compliance.UtmComplianceControlConfigDto;
44
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationDto;
5-
import com.park.utmstack.service.elasticsearch.ElasticsearchService;
65
import com.park.utmstack.service.mapper.compliance.UtmComplianceControlEvaluationMapper;
76
import org.springframework.stereotype.Service;
87

@@ -11,22 +10,18 @@
1110
@Service
1211
public class UtmComplianceControlEvaluationService {
1312

14-
//private final ElasticsearchService elasticsearchService;
1513
private final UtmComplianceControlConfigService configService;
1614
private final UtmComplianceControlEvaluationsService evaluationsService;
1715

18-
public UtmComplianceControlEvaluationService(ElasticsearchService elasticsearchService,
19-
UtmComplianceControlConfigService configService,
16+
public UtmComplianceControlEvaluationService(UtmComplianceControlConfigService configService,
2017
UtmComplianceControlEvaluationsService evaluationsService) {
21-
//this.elasticsearchService = elasticsearchService;
2218
this.configService = configService;
2319
this.evaluationsService = evaluationsService;
2420
}
2521

2622
public List<UtmComplianceControlEvaluationDto> getControlsWithLastEvaluation(Long sectionId) {
2723

28-
List<UtmComplianceControlConfigDto> controls =
29-
configService.getControlsBySection(sectionId);
24+
List<UtmComplianceControlConfigDto> controls = configService.getControlsBySection(sectionId);
3025

3126
return controls.stream()
3227
.map(control -> {
Lines changed: 208 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,229 @@
11
package com.park.utmstack.service.compliance.config;
22

3-
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationsDto;
3+
import com.park.utmstack.domain.compliance.UtmComplianceQueryConfig;
4+
import com.park.utmstack.repository.compliance.UtmComplianceQueryConfigRepository;
5+
import com.park.utmstack.service.dto.compliance.*;
46
import com.park.utmstack.service.elasticsearch.ElasticsearchService;
57
import org.springframework.stereotype.Service;
68

7-
import java.util.List;
9+
import java.time.Instant;
10+
import java.time.ZoneOffset;
11+
import java.util.*;
12+
import java.util.function.Function;
13+
import java.util.stream.Collectors;
814

915
@Service
1016
public class UtmComplianceControlEvaluationsService {
1117

1218
private final ElasticsearchService elasticsearchService;
19+
private final UtmComplianceQueryConfigRepository queryConfigRepository;
1320

14-
public UtmComplianceControlEvaluationsService(ElasticsearchService elasticsearchService) {
21+
public UtmComplianceControlEvaluationsService(ElasticsearchService elasticsearchService,
22+
UtmComplianceQueryConfigRepository QueryConfigRepository) {
1523
this.elasticsearchService = elasticsearchService;
24+
this.queryConfigRepository = QueryConfigRepository;
1625
}
1726

1827
public List<UtmComplianceControlEvaluationsDto> findByControlId(Long controlId) {
1928
return elasticsearchService.getControlEvaluations(controlId);
2029
}
2130

2231
public UtmComplianceControlEvaluationsDto getLastEvaluationForControl(Long controlId) {
23-
return elasticsearchService.getLastEvaluation(controlId);
32+
return elasticsearchService.getLatestControlEvaluation(controlId);
33+
}
34+
35+
/*public ControlEvaluationsResponseDto getEvaluationsWithRange(Long controlId) {
36+
var evaluations = findByControlId(controlId);
37+
38+
if (evaluations.isEmpty()) {
39+
return new ControlEvaluationsResponseDto(null, null, evaluations);
40+
}
41+
42+
var timestamps = evaluations.stream()
43+
.map(UtmComplianceControlEvaluationsDto::getTimestamp)
44+
.toList();
45+
46+
Instant min = timestamps.stream().min(Instant::compareTo).get();
47+
Instant max = timestamps.stream().max(Instant::compareTo).get();
48+
49+
LocalDate start = min.atZone(ZoneOffset.UTC).toLocalDate();
50+
LocalDate end = max.atZone(ZoneOffset.UTC).toLocalDate();
51+
52+
return new ControlEvaluationsResponseDto(start, end, evaluations);
53+
}*/
54+
55+
/*public ControlEvaluationsResponseDto getEvaluationsWithRange(Long controlId) {
56+
57+
// 1. Evaluaciones crudas desde OpenSearch - UtmComplianceControlEvaluationsDto
58+
var evaluations = findByControlId(controlId);
59+
60+
if (evaluations.isEmpty()) {
61+
return new ControlEvaluationsResponseDto(
62+
null,
63+
null,
64+
List.of()
65+
);
66+
}
67+
68+
// 2. Extraer todos los queryConfigId de todas las evaluaciones
69+
var queryConfigIds = evaluations.stream()
70+
.flatMap(ev -> ev.getQueryEvaluations().stream())
71+
.map(UtmComplianceQueryEvaluationDto::getQueryConfigId)
72+
.collect(Collectors.toSet());
73+
74+
// 3. Lookup masivo en DB - UtmComplianceQueryConfig
75+
var configs = queryConfigRepository.findAllById(queryConfigIds);
76+
var configMap = configs.stream()
77+
.collect(Collectors.toMap(
78+
UtmComplianceQueryConfig::getId,
79+
Function.identity()
80+
));
81+
82+
// 4. Enriquecer cada query evaluation
83+
evaluations.forEach(controlEval -> {
84+
controlEval.getQueryEvaluations().forEach(queryEval -> {
85+
var cfg = configMap.get(queryEval.getQueryConfigId());
86+
if (cfg != null) {
87+
queryEval.setQueryDescription(cfg.getQueryDescription());
88+
queryEval.setEvaluationRule(cfg.getEvaluationRule().name());
89+
queryEval.setIndexPatternId(cfg.getIndexPattern().getId());
90+
queryEval.setIndexPatternName(cfg.getIndexPattern().getPattern());
91+
}
92+
});
93+
});
94+
95+
// 5. Aplanar todas las queryEvaluations para agrupar por indexPattern
96+
var allQueryEvaluations = evaluations.stream()
97+
.flatMap(ev -> ev.getQueryEvaluations().stream())
98+
.toList();
99+
100+
// 6. Agrupar por indexPattern
101+
var groupedEvaluations = allQueryEvaluations.stream()
102+
.collect(Collectors.groupingBy(UtmComplianceQueryEvaluationDto::getIndexPatternId))
103+
.entrySet().stream()
104+
.map(entry -> {
105+
var first = entry.getValue().get(0);
106+
var dto = new IndexPatternQueriesGroupDto();
107+
dto.setIndexPatternId(entry.getKey());
108+
dto.setIndexPatternName(first.getIndexPatternName());
109+
dto.setQueries(entry.getValue());
110+
return dto;
111+
})
112+
.toList();
113+
114+
// 7. Calcular rango de fechas
115+
var timestamps = evaluations.stream()
116+
.map(UtmComplianceControlEvaluationsDto::getTimestamp)
117+
.toList();
118+
119+
Instant min = timestamps.stream().min(Instant::compareTo).get();
120+
Instant max = timestamps.stream().max(Instant::compareTo).get();
121+
122+
LocalDate startDate = min.atZone(ZoneOffset.UTC).toLocalDate();
123+
LocalDate endDate = max.atZone(ZoneOffset.UTC).toLocalDate();
124+
125+
var groupedDto = new UtmComplianceControlEvaluationsGroupedDto();
126+
groupedDto.setControlId(evaluations.get(0).getControlId());
127+
groupedDto.setControlName(evaluations.get(0).getControlName());
128+
groupedDto.setStatus(evaluations.get(0).getStatus());
129+
groupedDto.setTimestamp(evaluations.get(0).getTimestamp());
130+
groupedDto.setQueryEvaluations(groupedEvaluations);
131+
132+
133+
// 8. Devolver DTO final enriquecido y agrupado
134+
return new ControlEvaluationsResponseDto(
135+
startDate,
136+
endDate,
137+
List.of(groupedDto)
138+
);
139+
140+
}*/
141+
142+
public ControlEvaluationsResponseDto getEvaluationsWithRange(Long controlId) {
143+
//TODO: Elena Ordenar por fecha descendente
144+
var evaluations = findByControlId(controlId);
145+
146+
if (evaluations.isEmpty()) {
147+
return new ControlEvaluationsResponseDto(null, null, List.of());
148+
}
149+
150+
var queryConfigIds = evaluations.stream()
151+
.flatMap(ev -> ev.getQueryEvaluations().stream())
152+
.map(UtmComplianceQueryEvaluationDto::getQueryConfigId)
153+
.collect(Collectors.toSet());
154+
155+
var configMap = queryConfigRepository.findAllById(queryConfigIds).stream()
156+
.collect(Collectors.toMap(UtmComplianceQueryConfig::getId, Function.identity()));
157+
158+
//evaluations = enrichQueries(evaluations, configMap);
159+
160+
List<UtmComplianceControlEvaluationsGroupedDto> groupedList =
161+
enrichQueries(evaluations, configMap).stream()
162+
.map(evaluation -> {
163+
var grouped = groupByIndexPattern(evaluation);
164+
return buildGroupedDto(evaluation, grouped);
165+
})
166+
.toList();
167+
168+
var timestamps = evaluations.stream()
169+
.map(UtmComplianceControlEvaluationsDto::getTimestamp)
170+
.toList();
171+
172+
return new ControlEvaluationsResponseDto(
173+
timestamps.stream().min(Instant::compareTo)
174+
.get().atZone(ZoneOffset.UTC).toLocalDate(),
175+
timestamps.stream().max(Instant::compareTo)
176+
.get().atZone(ZoneOffset.UTC).toLocalDate(),
177+
groupedList);
178+
}
179+
180+
181+
private List<UtmComplianceControlEvaluationsDto> enrichQueries(
182+
List<UtmComplianceControlEvaluationsDto> evaluations,
183+
Map<Long, UtmComplianceQueryConfig> configMap
184+
) {
185+
evaluations.forEach(controlEval ->
186+
controlEval.getQueryEvaluations().forEach(queryEval -> {
187+
var cfg = configMap.get(queryEval.getQueryConfigId());
188+
if (cfg != null) {
189+
queryEval.setQueryDescription(cfg.getQueryDescription());
190+
queryEval.setEvaluationRule(cfg.getEvaluationRule().name());
191+
queryEval.setIndexPatternId(cfg.getIndexPattern().getId());
192+
queryEval.setIndexPatternName(cfg.getIndexPattern().getPattern());
193+
}
194+
})
195+
);
196+
return evaluations;
197+
}
198+
199+
private List<IndexPatternQueriesGroupDto> groupByIndexPattern(
200+
UtmComplianceControlEvaluationsDto evaluation
201+
) {
202+
return evaluation.getQueryEvaluations().stream()
203+
.collect(Collectors.groupingBy(UtmComplianceQueryEvaluationDto::getIndexPatternId))
204+
.entrySet().stream()
205+
.map(entry -> {
206+
var first = entry.getValue().get(0);
207+
var dto = new IndexPatternQueriesGroupDto();
208+
dto.setIndexPatternId(entry.getKey());
209+
dto.setIndexPatternName(first.getIndexPatternName());
210+
dto.setQueries(entry.getValue());
211+
return dto;
212+
})
213+
.toList();
214+
}
215+
216+
private UtmComplianceControlEvaluationsGroupedDto buildGroupedDto(
217+
UtmComplianceControlEvaluationsDto evaluation,
218+
List<IndexPatternQueriesGroupDto> groupedEvaluations
219+
) {
220+
var dto = new UtmComplianceControlEvaluationsGroupedDto();
221+
dto.setControlId(evaluation.getControlId());
222+
dto.setControlName(evaluation.getControlName());
223+
dto.setStatus(evaluation.getStatus());
224+
dto.setTimestamp(evaluation.getTimestamp());
225+
dto.setQueryEvaluations(groupedEvaluations);
226+
227+
return dto;
24228
}
25229
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.park.utmstack.service.dto.compliance;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
import java.time.LocalDate;
8+
import java.util.List;
9+
10+
@Data
11+
@AllArgsConstructor
12+
@NoArgsConstructor
13+
public class ControlEvaluationsResponseDto {
14+
LocalDate startDate;
15+
LocalDate endDate;
16+
List<UtmComplianceControlEvaluationsGroupedDto> evaluations;
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.park.utmstack.service.dto.compliance;
2+
3+
import lombok.Data;
4+
5+
import java.util.List;
6+
7+
@Data
8+
public class IndexPatternQueriesGroupDto {
9+
private Long indexPatternId;
10+
private String indexPatternName;
11+
private List<UtmComplianceQueryEvaluationDto> queries;
12+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.park.utmstack.service.dto.compliance;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
import java.time.Instant;
8+
import java.util.List;
9+
10+
@Data
11+
@AllArgsConstructor
12+
@NoArgsConstructor
13+
public class UtmComplianceControlEvaluationsGroupedDto {
14+
private Long controlId;
15+
private String controlName;
16+
private String status;
17+
private Instant timestamp;
18+
private List<IndexPatternQueriesGroupDto> queryEvaluations;
19+
}

backend/src/main/java/com/park/utmstack/service/dto/compliance/UtmComplianceQueryEvaluationDto.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public class UtmComplianceQueryEvaluationDto {
1212

1313
private Long queryConfigId;
1414
private String queryName;
15+
private String queryDescription;
16+
private String evaluationRule;
17+
private Long indexPatternId;
18+
private String indexPatternName;
1519
private Integer hits;
1620
private String status;
1721
private String errorMessage;

backend/src/main/java/com/park/utmstack/service/elasticsearch/ElasticsearchService.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,19 +419,27 @@ public List<UtmComplianceControlEvaluationsDto> getControlEvaluations(Long contr
419419
SearchRequest request = new SearchRequest.Builder()
420420
.index("v11-log-compliance-evaluation")
421421
.query(query)
422-
.size(1000)
422+
.size(30)
423+
.sort(s -> s.field(f -> f
424+
.field("timestamp")
425+
.order(SortOrder.Desc)
426+
))
423427
.build();
424428

425429
SearchResponse<Map> response = search(request, Map.class);
426430

427-
return response.hits().hits().stream().map(hit -> UtmComplianceControlEvaluationsMapper.mapToEvaluationDto(hit.source())).toList();
431+
var evaluations = response.hits().hits().stream()
432+
.map(hit -> UtmComplianceControlEvaluationsMapper.mapToEvaluationDto(hit.source()))
433+
.toList();
434+
435+
return evaluations;
428436

429437
} catch (Exception e) {
430438
throw new RuntimeException(ctx + ": " + e.getMessage(), e);
431439
}
432440
}
433441

434-
public UtmComplianceControlEvaluationsDto getLastEvaluation(Long controlId) {
442+
public UtmComplianceControlEvaluationsDto getLatestControlEvaluation(Long controlId) {
435443
try {
436444
SearchRequest request = new SearchRequest.Builder()
437445
.index("v11-log-compliance-evaluation")

backend/src/main/java/com/park/utmstack/service/mapper/compliance/UtmComplianceControlEvaluationMapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ public static UtmComplianceControlEvaluationDto toDto(
3636
}
3737

3838
public static UtmComplianceControlEvaluationsDto mapToEvaluationDto(Map<String, Object> source) {
39-
if (source == null) return null;
39+
if (source == null) {
40+
return null;
41+
}
4042

4143
UtmComplianceControlEvaluationsDto dto = new UtmComplianceControlEvaluationsDto();
42-
4344
dto.setControlId(getLong(source.get("control_id")));
4445
dto.setControlName(getString(source.get("control_name")));
4546
dto.setStatus(getString(source.get("status")));

0 commit comments

Comments
 (0)