Skip to content

Commit 2c44b06

Browse files
committed
feat: provide endpoint for OpenSearch evaluations including latest evaluation calculation per control
1 parent 94a84f5 commit 2c44b06

11 files changed

Lines changed: 255 additions & 44 deletions

backend/src/main/java/com/park/utmstack/repository/compliance/UtmComplianceControlConfigRepository.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.data.repository.query.Param;
88
import org.springframework.stereotype.Repository;
99

10+
import java.util.List;
1011
import java.util.Optional;
1112

1213
@Repository
@@ -19,4 +20,13 @@ public interface UtmComplianceControlConfigRepository extends JpaRepository<UtmC
1920
WHERE c.id = :id
2021
""")
2122
Optional<UtmComplianceControlConfig> findByIdWithQueries(@Param("id") Long id);
23+
24+
@Query("""
25+
SELECT DISTINCT c FROM UtmComplianceControlConfig c
26+
LEFT JOIN FETCH c.section s
27+
LEFT JOIN FETCH c.queriesConfigs q
28+
WHERE c.standardSectionId = :sectionId
29+
""")
30+
List<UtmComplianceControlConfig> findBySectionIdWithQueries(@Param("sectionId") Long sectionId);
31+
2232
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.springframework.stereotype.Service;
1212

1313
import javax.transaction.Transactional;
14+
import java.util.List;
1415
import java.util.Map;
1516
import java.util.stream.Collectors;
1617

@@ -108,4 +109,12 @@ private void validateControlConfig(UtmComplianceControlConfigDto dto) {
108109
}
109110
}
110111
}
112+
113+
public List<UtmComplianceControlConfigDto> getControlsBySection(Long sectionId) {
114+
var entities = repository.findBySectionIdWithQueries(sectionId);
115+
return entities.stream()
116+
.map(mapper::toDto)
117+
.collect(Collectors.toList());
118+
}
119+
111120
}
Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
11
package com.park.utmstack.service.compliance.config;
22

3+
import com.park.utmstack.service.dto.compliance.UtmComplianceControlConfigDto;
34
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationDto;
45
import com.park.utmstack.service.elasticsearch.ElasticsearchService;
6+
import com.park.utmstack.service.mapper.compliance.UtmComplianceControlEvaluationMapper;
57
import org.springframework.stereotype.Service;
68

79
import java.util.List;
810

911
@Service
1012
public class UtmComplianceControlEvaluationService {
1113

12-
private final ElasticsearchService elasticsearchService;
14+
//private final ElasticsearchService elasticsearchService;
15+
private final UtmComplianceControlConfigService configService;
16+
private final UtmComplianceControlEvaluationsService evaluationsService;
1317

14-
public UtmComplianceControlEvaluationService(ElasticsearchService elasticsearchService) {
15-
this.elasticsearchService = elasticsearchService;
18+
public UtmComplianceControlEvaluationService(ElasticsearchService elasticsearchService,
19+
UtmComplianceControlConfigService configService,
20+
UtmComplianceControlEvaluationsService evaluationsService) {
21+
//this.elasticsearchService = elasticsearchService;
22+
this.configService = configService;
23+
this.evaluationsService = evaluationsService;
1624
}
1725

18-
public List<UtmComplianceControlEvaluationDto> findByControlId(Long controlId) {
19-
return elasticsearchService.getControlEvaluations(controlId);
26+
public List<UtmComplianceControlEvaluationDto> getControlsWithLastEvaluation(Long sectionId) {
27+
28+
List<UtmComplianceControlConfigDto> controls =
29+
configService.getControlsBySection(sectionId);
30+
31+
return controls.stream()
32+
.map(control -> {
33+
var lastEval = evaluationsService.getLastEvaluationForControl(control.getId());
34+
return UtmComplianceControlEvaluationMapper.toDto(control, lastEval);
35+
})
36+
.toList();
37+
}
2038
}
21-
}
2239

2340

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.park.utmstack.service.compliance.config;
2+
3+
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationsDto;
4+
import com.park.utmstack.service.elasticsearch.ElasticsearchService;
5+
import org.springframework.stereotype.Service;
6+
7+
import java.util.List;
8+
9+
@Service
10+
public class UtmComplianceControlEvaluationsService {
11+
12+
private final ElasticsearchService elasticsearchService;
13+
14+
public UtmComplianceControlEvaluationsService(ElasticsearchService elasticsearchService) {
15+
this.elasticsearchService = elasticsearchService;
16+
}
17+
18+
public List<UtmComplianceControlEvaluationsDto> findByControlId(Long controlId) {
19+
return elasticsearchService.getControlEvaluations(controlId);
20+
}
21+
22+
public UtmComplianceControlEvaluationsDto getLastEvaluationForControl(Long controlId) {
23+
return elasticsearchService.getLastEvaluation(controlId);
24+
}
25+
}
Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package com.park.utmstack.service.dto.compliance;
22

3-
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
3+
import com.park.utmstack.domain.compliance.enums.ComplianceStrategy;
44
import lombok.Data;
55

6-
import java.time.Instant;
76
import java.util.List;
87

98
@Data
10-
@JsonIgnoreProperties(ignoreUnknown = true)
119
public class UtmComplianceControlEvaluationDto {
10+
private Long id;
11+
private Long standardSectionId;
12+
private UtmComplianceStandardSectionDto section;
1213

13-
private Long controlId;
1414
private String controlName;
15-
private String status;
16-
private Instant timestamp;
17-
private List<UtmComplianceQueryEvaluationDto> queryEvaluations;
18-
}
15+
private String controlSolution;
16+
private String controlRemediation;
17+
private ComplianceStrategy controlStrategy;
18+
19+
private List<UtmComplianceQueryConfigDto> queriesConfigs;
1920

21+
private String lastEvaluationStatus;
22+
private String lastEvaluationTimestamp;
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.park.utmstack.service.dto.compliance;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import lombok.Data;
5+
6+
import java.time.Instant;
7+
import java.util.List;
8+
9+
@Data
10+
@JsonIgnoreProperties(ignoreUnknown = true)
11+
public class UtmComplianceControlEvaluationsDto {
12+
private Long controlId;
13+
private String controlName;
14+
private String status;
15+
private Instant timestamp;
16+
private List<UtmComplianceQueryEvaluationDto> queryEvaluations;
17+
}
18+

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import com.park.utmstack.service.MailService;
1111
import com.park.utmstack.service.UtmSpaceNotificationControlService;
1212
import com.park.utmstack.service.application_events.ApplicationEventService;
13-
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationDto;
13+
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationsDto;
1414
import com.park.utmstack.service.mapper.compliance.UtmComplianceControlEvaluationMapper;
15+
import com.park.utmstack.service.mapper.compliance.UtmComplianceControlEvaluationsMapper;
1516
import com.park.utmstack.util.chart_builder.IndexPropertyType;
1617
import com.park.utmstack.util.exceptions.OpenSearchIndexNotFoundException;
1718
import com.park.utmstack.util.exceptions.UtmElasticsearchException;
@@ -407,19 +408,54 @@ public <T> SearchSqlResponse<T> searchBySql(SqlQueryRequest request, Class<T> re
407408
}
408409
}
409410

410-
public List<UtmComplianceControlEvaluationDto> getControlEvaluations(Long controlId) {
411+
public List<UtmComplianceControlEvaluationsDto> getControlEvaluations(Long controlId) {
411412
final String ctx = CLASSNAME + ".getControlEvaluations";
412413
try {
413-
Query query = Query.of(q -> q.term(t -> t.field("control_id").value(FieldValue.of(controlId.toString())))
414+
Query query = Query.of(q -> q.term(t -> t
415+
.field("control_id")
416+
.value(FieldValue.of(controlId.toString())))
414417
);
415418

416-
SearchRequest request = new SearchRequest.Builder().index("v11-log-compliance-evaluation").query(query).size(1000).build();
419+
SearchRequest request = new SearchRequest.Builder()
420+
.index("v11-log-compliance-evaluation")
421+
.query(query)
422+
.size(1000)
423+
.build();
424+
417425
SearchResponse<Map> response = search(request, Map.class);
418426

419-
return response.hits().hits().stream().map(hit -> UtmComplianceControlEvaluationMapper.mapToEvaluationDto(hit.source())).toList();
427+
return response.hits().hits().stream().map(hit -> UtmComplianceControlEvaluationsMapper.mapToEvaluationDto(hit.source())).toList();
420428

421429
} catch (Exception e) {
422430
throw new RuntimeException(ctx + ": " + e.getMessage(), e);
423431
}
424432
}
433+
434+
public UtmComplianceControlEvaluationsDto getLastEvaluation(Long controlId) {
435+
try {
436+
SearchRequest request = new SearchRequest.Builder()
437+
.index("v11-log-compliance-evaluation")
438+
.query(q -> q.term(t -> t
439+
.field("control_id")
440+
.value(v -> v.longValue(controlId))
441+
))
442+
.sort(s -> s.field(f -> f.field("timestamp").order(SortOrder.Desc)))
443+
.size(1)
444+
.build();
445+
446+
SearchResponse<Map> response = client.getClient().search(request, Map.class);
447+
448+
if (response.hits().hits().isEmpty()) {
449+
return null;
450+
}
451+
452+
Map<String, Object> source = response.hits().hits().get(0).source();
453+
454+
return UtmComplianceControlEvaluationMapper.mapToEvaluationDto(source);
455+
456+
} catch (Exception e) {
457+
throw new RuntimeException("Error fetching last evaluation for control " + controlId, e);
458+
}
459+
}
460+
425461
}
Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,64 @@
11
package com.park.utmstack.service.mapper.compliance;
22

3+
import com.park.utmstack.service.dto.compliance.UtmComplianceControlConfigDto;
34
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationDto;
4-
import com.park.utmstack.service.dto.compliance.UtmComplianceQueryEvaluationDto;
5+
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationsDto;
56

67
import java.time.Instant;
7-
import java.util.List;
88
import java.util.Map;
99

1010
public class UtmComplianceControlEvaluationMapper {
1111

12-
private UtmComplianceControlEvaluationMapper() {
13-
14-
}
15-
16-
public static UtmComplianceControlEvaluationDto mapToEvaluationDto(Map<String, Object> src) {
12+
public static UtmComplianceControlEvaluationDto toDto(
13+
UtmComplianceControlConfigDto control,
14+
UtmComplianceControlEvaluationsDto controlEvaluations
15+
) {
1716
UtmComplianceControlEvaluationDto dto = new UtmComplianceControlEvaluationDto();
1817

19-
dto.setControlId(((Number) src.get("control_id")).longValue());
20-
dto.setControlName((String) src.get("control_name"));
21-
dto.setStatus((String) src.get("status"));
22-
dto.setTimestamp(Instant.parse((String) src.get("timestamp")));
18+
dto.setId(control.getId());
19+
dto.setStandardSectionId(control.getStandardSectionId());
20+
dto.setSection(control.getSection());
21+
dto.setControlName(control.getControlName());
22+
dto.setControlSolution(control.getControlSolution());
23+
dto.setControlRemediation(control.getControlRemediation());
24+
dto.setControlStrategy(control.getControlStrategy());
25+
dto.setQueriesConfigs(control.getQueriesConfigs());
2326

24-
List<Map<String, Object>> q = (List<Map<String, Object>>) src.get("query_evaluations");
25-
if (q != null) {
26-
dto.setQueryEvaluations(q.stream().map(UtmComplianceControlEvaluationMapper::mapQueryEval).toList());
27+
//TODO: ELENA - this is a temporary solution, we need to decide how to handle multiple evaluations for the same control
28+
if (controlEvaluations != null) {
29+
dto.setLastEvaluationStatus(controlEvaluations.getStatus());
30+
dto.setLastEvaluationTimestamp(
31+
controlEvaluations.getTimestamp() != null ? controlEvaluations.getTimestamp().toString() : null
32+
);
2733
}
2834

2935
return dto;
3036
}
3137

32-
private static UtmComplianceQueryEvaluationDto mapQueryEval(Map<String, Object> src) {
33-
UtmComplianceQueryEvaluationDto dto = new UtmComplianceQueryEvaluationDto();
38+
public static UtmComplianceControlEvaluationsDto mapToEvaluationDto(Map<String, Object> source) {
39+
if (source == null) return null;
40+
41+
UtmComplianceControlEvaluationsDto dto = new UtmComplianceControlEvaluationsDto();
42+
43+
dto.setControlId(getLong(source.get("control_id")));
44+
dto.setControlName(getString(source.get("control_name")));
45+
dto.setStatus(getString(source.get("status")));
3446

35-
dto.setQueryConfigId(((Number) src.get("queryConfigId")).longValue());
36-
dto.setQueryName((String) src.get("queryName"));
37-
dto.setHits(((Number) src.get("hits")).intValue());
38-
dto.setStatus((String) src.get("status"));
39-
dto.setEvidence((List<Map<String, Object>>) src.get("evidence"));
47+
Object ts = source.get("timestamp");
48+
if (ts != null) {
49+
dto.setTimestamp(Instant.parse(ts.toString()));
50+
}
4051

4152
return dto;
4253
}
54+
55+
private static String getString(Object o) {
56+
return o != null ? o.toString() : null;
57+
}
58+
59+
private static Long getLong(Object o) {
60+
if (o == null) return null;
61+
if (o instanceof Number n) return n.longValue();
62+
return Long.parseLong(o.toString());
63+
}
4364
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.park.utmstack.service.mapper.compliance;
2+
3+
import com.park.utmstack.service.dto.compliance.UtmComplianceControlEvaluationsDto;
4+
import com.park.utmstack.service.dto.compliance.UtmComplianceQueryEvaluationDto;
5+
6+
import java.time.Instant;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
public class UtmComplianceControlEvaluationsMapper {
11+
12+
private UtmComplianceControlEvaluationsMapper() {
13+
14+
}
15+
16+
public static UtmComplianceControlEvaluationsDto mapToEvaluationDto(Map<String, Object> src) {
17+
UtmComplianceControlEvaluationsDto dto = new UtmComplianceControlEvaluationsDto();
18+
19+
dto.setControlId(((Number) src.get("control_id")).longValue());
20+
dto.setControlName((String) src.get("control_name"));
21+
dto.setStatus((String) src.get("status"));
22+
dto.setTimestamp(Instant.parse((String) src.get("timestamp")));
23+
24+
List<Map<String, Object>> q = (List<Map<String, Object>>) src.get("query_evaluations");
25+
if (q != null) {
26+
dto.setQueryEvaluations(q.stream().map(UtmComplianceControlEvaluationsMapper::mapQueryEval).toList());
27+
}
28+
29+
return dto;
30+
}
31+
32+
private static UtmComplianceQueryEvaluationDto mapQueryEval(Map<String, Object> src) {
33+
UtmComplianceQueryEvaluationDto dto = new UtmComplianceQueryEvaluationDto();
34+
35+
dto.setQueryConfigId(((Number) src.get("queryConfigId")).longValue());
36+
dto.setQueryName((String) src.get("queryName"));
37+
dto.setHits(((Number) src.get("hits")).intValue());
38+
dto.setStatus((String) src.get("status"));
39+
dto.setEvidence((List<Map<String, Object>>) src.get("evidence"));
40+
41+
return dto;
42+
}
43+
}

backend/src/main/java/com/park/utmstack/web/rest/compliance/config/UtmComplianceControlEvaluationResource.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ public UtmComplianceControlEvaluationResource(UtmComplianceControlEvaluationServ
1717
this.evaluationService = evaluationService;
1818
}
1919

20-
@GetMapping("/{id}/evaluations")
21-
public ResponseEntity<List<UtmComplianceControlEvaluationDto>> getControlEvaluations(@PathVariable Long id) {
22-
var evaluations = evaluationService.findByControlId(id);
23-
return ResponseEntity.ok(evaluations);
20+
@GetMapping("/get-by-section")
21+
public ResponseEntity<List<UtmComplianceControlEvaluationDto>> getControlsBySection(
22+
@RequestParam Long sectionId) {
23+
24+
var controls = evaluationService.getControlsWithLastEvaluation(sectionId);
25+
return ResponseEntity.ok(controls);
2426
}
2527
}
2628

0 commit comments

Comments
 (0)