Skip to content

Commit

Permalink
Remove hacks (#91)
Browse files Browse the repository at this point in the history
Signed-off-by: Ayoub LABIDI <[email protected]>
  • Loading branch information
ayolab authored Sep 26, 2024
1 parent b497910 commit a36ca25
Show file tree
Hide file tree
Showing 17 changed files with 887 additions and 1,053 deletions.
44 changes: 12 additions & 32 deletions src/main/java/org/gridsuite/report/server/ReportController.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

Expand All @@ -40,35 +39,17 @@ public ReportController(ReportService service) {

@GetMapping(value = "/reports/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the elements of a report, its reporters, and their subreporters")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The elements of the report, reporters and subreporters"),
@ApiResponse(responseCode = "404", description = "The report does not exists")})
public ResponseEntity<List<Report>> getReport(@PathVariable("id") UUID id,
@Parameter(description = "Filter on a the report name. If provided, will only return elements matching this given name.") @RequestParam(name = "reportNameFilter", required = false, defaultValue = "") String reportNameFilter,
@Parameter(description = "Kind of matching filter to apply to the report name.") @RequestParam(name = "reportNameMatchingType", required = false) ReportService.ReportNameMatchingType reportNameMatchingType,
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The elements of the report, reporters and subreporters")})
public ResponseEntity<Report> getReport(@PathVariable("id") UUID id,
@Parameter(description = "Filter on severity levels. Will only return elements with those severities.") @RequestParam(name = "severityLevels", required = false) Set<String> severityLevels,
@Parameter(description = "Empty report with default name") @RequestParam(name = "defaultName", required = false, defaultValue = "defaultName") String defaultName) {
try {
List<Report> reports = service.getReport(id, severityLevels, reportNameFilter, reportNameMatchingType).getSubReports();
return reports.isEmpty() ?
ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(List.of(service.getEmptyReport(id, defaultName))) :
ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(reports);
Report report = service.getReport(id, severityLevels);
return report == null ?
ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(service.getEmptyReport(id, defaultName)) :
ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(report);
} catch (EntityNotFoundException ignored) {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(List.of(service.getEmptyReport(id, defaultName)));
}
}

@GetMapping(value = "/subreports/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the elements of a reporter and its subreporters")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The elements of the reporter and its subreporters"),
@ApiResponse(responseCode = "404", description = "The reporter does not exists")})
public ResponseEntity<Report> getSubReport(@PathVariable("id") UUID id,
@Parameter(description = "Filter on severity levels. Will only return elements with those severities") @RequestParam(name = "severityLevels", required = false) Set<String> severityLevels) {
try {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(service.getReport(id, severityLevels, null, null));
} catch (EntityNotFoundException ignored) {
return ResponseEntity.notFound().build();
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(service.getEmptyReport(id, defaultName));
}
}

Expand All @@ -83,21 +64,20 @@ public void createReport(@PathVariable("id") UUID id, @RequestBody ReportNodeImp
@Operation(summary = "delete the report")
@ApiResponse(responseCode = "200", description = "The report has been deleted")
public ResponseEntity<Void> deleteReport(@PathVariable("id") UUID reportUuid,
@Parameter(description = "Filter on a given report type. If provided, will only delete elements with the given report type.") @RequestParam(name = "reportTypeFilter", required = false) String reportTypeFilter,
@Parameter(description = "Return 404 if report is not found") @RequestParam(name = "errorOnReportNotFound", required = false, defaultValue = "true") boolean errorOnReportNotFound) {
try {
service.deleteReport(reportUuid, reportTypeFilter);
service.deleteReport(reportUuid);
} catch (EmptyResultDataAccessException | EntityNotFoundException ignored) {
return errorOnReportNotFound ? ResponseEntity.notFound().build() : ResponseEntity.ok().build();
}
return ResponseEntity.ok().build();
}

@DeleteMapping(value = "treereports", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "delete treereports from a list of parent reports based on a key")
@DeleteMapping(value = "reports", consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "delete reports by their UUIDs")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The reports have been deleted")})
public ResponseEntity<Void> deleteTreeReports(@Parameter(description = "parent reports to parse and their associated tree report key to identify which to delete") @RequestBody Map<UUID, String> identifiers) {
service.deleteReports(identifiers);
public ResponseEntity<Void> deleteReports(@Parameter(description = "list of reports UUIDs to delete") @RequestBody List<UUID> reportUuids) {
service.deleteReports(reportUuids);
return ResponseEntity.ok().build();
}
}
22 changes: 5 additions & 17 deletions src/main/java/org/gridsuite/report/server/ReportNodeMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package org.gridsuite.report.server;

import jakarta.persistence.EntityNotFoundException;
import org.apache.commons.lang3.StringUtils;
import org.gridsuite.report.server.dto.Report;
import org.gridsuite.report.server.entities.ReportNodeEntity;

Expand All @@ -24,12 +23,12 @@ private ReportNodeMapper() {
// Should not be instantiated
}

public static Report map(OptimizedReportNodeEntities optimizedReportNodeEntities, @Nullable Set<String> severityLevels, @Nullable String reportNameFilter, @Nullable ReportService.ReportNameMatchingType reportNameMatchingType) {
public static Report map(OptimizedReportNodeEntities optimizedReportNodeEntities, @Nullable Set<String> severityLevels) {
UUID rootId = getRootId(optimizedReportNodeEntities);

Map<UUID, Report> reportsMap = new HashMap<>();
mapRootNode(optimizedReportNodeEntities.reportNodeEntityById().get(rootId), reportsMap);
mapLevels(optimizedReportNodeEntities, reportsMap, severityLevels, reportNameFilter, reportNameMatchingType);
mapLevels(optimizedReportNodeEntities, reportsMap, severityLevels);

return reportsMap.get(rootId);
}
Expand All @@ -54,9 +53,9 @@ private static Report createReportFromNode(ReportNodeEntity reportNodeEntity) {
return report;
}

private static void mapLevels(OptimizedReportNodeEntities optimizedReportNodeEntities, Map<UUID, Report> reportsMap, @Nullable Set<String> severityLevels, @Nullable String reportNameFilter, @Nullable ReportService.ReportNameMatchingType reportNameMatchingType) {
private static void mapLevels(OptimizedReportNodeEntities optimizedReportNodeEntities, Map<UUID, Report> reportsMap, @Nullable Set<String> severityLevels) {
if (optimizedReportNodeEntities.treeDepth() > 1) {
mapLevel(optimizedReportNodeEntities, reportsMap, 1, reportMessageKeyMatches(reportNameFilter, reportNameMatchingType).and(hasOneOfSeverityLevels(severityLevels)));
mapLevel(optimizedReportNodeEntities, reportsMap, 1, hasOneOfSeverityLevels(severityLevels));
}
for (int i = 2; i < optimizedReportNodeEntities.treeDepth(); i++) {
mapLevel(optimizedReportNodeEntities, reportsMap, i, hasOneOfSeverityLevels(severityLevels));
Expand All @@ -77,16 +76,12 @@ private static void mapReportNodeEntity(UUID id, Map<UUID, ReportNodeEntity> rep
ReportNodeEntity reportNodeEntity = reportEntities.get(id);
Optional.ofNullable(reports.get(reportNodeEntity.getParent().getId())).ifPresent(parentReport -> {
Report report = parentReport.addEmptyReport();
report.setMessage(extractMessage(reportNodeEntity));
report.setMessage(reportNodeEntity.getMessage());
mapValues(reportNodeEntity, report);
reports.put(id, report);
});
}

private static String extractMessage(ReportNodeEntity reportNodeEntity) {
return reportNodeEntity.getMessage().contains("@") ? reportNodeEntity.getMessage().split("@")[0] : reportNodeEntity.getMessage();
}

private static void mapValues(ReportNodeEntity reportNodeEntity, Report report) {
report.setSeverities(reportNodeEntity.getSeverities().stream().map(Severity::valueOf).toList());
if (!reportNodeEntity.getChildren().isEmpty() || reportNodeEntity.getSeverities().isEmpty()) {
Expand All @@ -100,13 +95,6 @@ private static Predicate<ReportNodeEntity> hasOneOfSeverityLevels(Set<String> se
hasNoReportSeverity(reportNodeEntity) || reportNodeEntity.getSeverities().stream().anyMatch(severityLevels::contains);
}

private static Predicate<ReportNodeEntity> reportMessageKeyMatches(@Nullable String reportNameFilter, @Nullable ReportService.ReportNameMatchingType reportNameMatchingType) {
return reportNodeEntity -> StringUtils.isBlank(reportNameFilter)
|| reportNodeEntity.getMessage().startsWith("Root") // FIXME remove this hack when "Root" report will follow the same rules than computations and modifications
|| reportNameMatchingType == ReportService.ReportNameMatchingType.EXACT_MATCHING && reportNodeEntity.getMessage().equals(reportNameFilter)
|| reportNameMatchingType == ReportService.ReportNameMatchingType.ENDS_WITH && reportNodeEntity.getMessage().endsWith(reportNameFilter);
}

private static boolean hasNoReportSeverity(ReportNodeEntity reportNodeEntity) {
return reportNodeEntity.getSeverities().isEmpty();
}
Expand Down
71 changes: 19 additions & 52 deletions src/main/java/org/gridsuite/report/server/ReportService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.powsybl.commons.report.ReportConstants;
import com.powsybl.commons.report.ReportNode;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.gridsuite.report.server.dto.Report;
import org.gridsuite.report.server.entities.ReportNodeEntity;
import org.gridsuite.report.server.repositories.ReportNodeRepository;
Expand Down Expand Up @@ -43,11 +42,6 @@ public class ReportService {

private final ReportNodeRepository reportNodeRepository;


public enum ReportNameMatchingType {
EXACT_MATCHING, ENDS_WITH
}

static {
long nanoNow = System.nanoTime();
long nanoViaMillis = Instant.now().toEpochMilli() * 1000000;
Expand All @@ -69,10 +63,10 @@ public Optional<ReportNodeEntity> getReportNodeEntity(UUID id) {
}

@Transactional(readOnly = true)
public Report getReport(UUID reportId, Set<String> severityLevels, String reportNameFilter, ReportNameMatchingType reportNameMatchingType) {
public Report getReport(UUID reportId, Set<String> severityLevels) {
Objects.requireNonNull(reportId);
OptimizedReportNodeEntities optimizedReportNodeEntities = getOptimizedReportNodeEntities(reportId);
return map(optimizedReportNodeEntities, severityLevels, reportNameFilter, reportNameMatchingType);
return map(optimizedReportNodeEntities, severityLevels);
}

private OptimizedReportNodeEntities getOptimizedReportNodeEntities(UUID rootReportNodeId) {
Expand Down Expand Up @@ -110,7 +104,7 @@ public Report getEmptyReport(@NonNull UUID id, @NonNull String defaultName) {

@Transactional
public void createReport(UUID id, ReportNode reportNode) {
reportNodeRepository.findAllWithChildrenByIdIn(List.of(id)).stream().findFirst().ifPresentOrElse(
reportNodeRepository.findById(id).ifPresentOrElse(
reportEntity -> {
LOGGER.debug("Reporter {} present, append ", reportNode.getMessage());
appendReportElements(reportEntity, reportNode);
Expand All @@ -123,30 +117,19 @@ public void createReport(UUID id, ReportNode reportNode) {
}

private void appendReportElements(ReportNodeEntity reportEntity, ReportNode reportNode) {
// for incremental network modifications, we need to append the new report elements to the existing network modification report
reportEntity.getChildren().stream()
.filter(child -> child.getMessage().equals(reportNode.getMessage()) && child.getMessage().contains("@"))
.findFirst()
.ifPresentOrElse(
child -> {
// We don't have to update more ancestors because we only append at root level, and we know it
// But if we want to generalize appending to any report we should update the severity list of all
// the ancestors recursively
child.addSeverities(reportNode.getChildren().stream().map(ReportService::severities).flatMap(Collection::stream).collect(Collectors.toSet()));
reportNode.getChildren().forEach(c -> saveReportNodeRecursively(child, c));
},
() -> saveAllReportElements(reportEntity, reportNode)
);
}
// We don't have to update more ancestors because we only append at root level, and we know it
// But if we want to generalize appending to any report we should update the severity list of all the ancestors recursively
reportEntity.addSeverities(reportNode.getChildren().stream().map(ReportService::severities)
.flatMap(Collection::stream).collect(Collectors.toSet()));
reportNode.getChildren().forEach(c -> saveReportNodeRecursively(reportEntity, c));

private void createNewReport(UUID id, ReportNode reportNode) {
var persistedReport = reportNodeRepository.save(new ReportNodeEntity(id, System.nanoTime() - NANOS_FROM_EPOCH_TO_START));
saveAllReportElements(persistedReport, reportNode);
}

private void saveAllReportElements(ReportNodeEntity parentReportNodeEntity, ReportNode reportNode) {
saveReportNodeRecursively(parentReportNodeEntity, reportNode);
reportNodeRepository.save(parentReportNodeEntity);
private void createNewReport(UUID id, ReportNode reportNode) {
var persistedReport = reportNodeRepository.save(
new ReportNodeEntity(id, reportNode.getMessage(), System.nanoTime() - NANOS_FROM_EPOCH_TO_START, null, severities(reportNode))
);
reportNode.getChildren().forEach(c -> saveReportNodeRecursively(persistedReport, c));
}

private void saveReportNodeRecursively(ReportNodeEntity parentReportNodeEntity, ReportNode reportNode) {
Expand All @@ -172,31 +155,15 @@ private static Set<String> severities(ReportNode reportNode) {
}

@Transactional
public void deleteReport(UUID id, String reportType) {
Objects.requireNonNull(id);
ReportNodeEntity reportNodeEntity = reportNodeRepository.findById(id).orElseThrow(() -> new EmptyResultDataAccessException("No element found", 1));
List<ReportNodeEntity> filteredChildrenList = reportNodeEntity.getChildren()
.stream()
.filter(child -> StringUtils.isBlank(reportType) || child.getMessage().endsWith(reportType))
.toList();
filteredChildrenList.forEach(child -> deleteRoot(child.getId()));

if (filteredChildrenList.size() == reportNodeEntity.getChildren().size()) {
// let's remove the whole Report only if we have removed all its children
reportNodeRepository.deleteByIdIn(List.of(id));
}
public void deleteReport(UUID reportUuid) {
ReportNodeEntity reportNodeEntity = reportNodeRepository.findById(reportUuid).orElseThrow(() -> new EmptyResultDataAccessException("No element found", 1));
deleteRoot(reportNodeEntity.getId());
}

@Transactional
public void deleteReports(Map<UUID, String> identifiers) {
Objects.requireNonNull(identifiers);
identifiers.forEach(this::deleteReportByMessage);
}

private void deleteReportByMessage(UUID reportId, String reportMessage) {
Objects.requireNonNull(reportId);
List<ReportNodeEntity> reportNodeEntities = reportNodeRepository.findAllByParentIdAndMessage(reportId, reportMessage);
reportNodeEntities.forEach(reportNodeEntity -> deleteRoot(reportNodeEntity.getId()));
public void deleteReports(List<UUID> reportUuids) {
Objects.requireNonNull(reportUuids);
reportUuids.forEach(this::deleteRoot);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ public ReportNodeEntity(String message, long nanos, ReportNodeEntity parent, Set
this.severities = severities;
}

public ReportNodeEntity(UUID id, String message, long nanos, ReportNodeEntity parent, Set<String> severities) {
this.id = id;
this.message = message;
this.nanos = nanos;
this.parent = parent;
this.severities = severities;
}

public ReportNodeEntity(UUID id, long nanos) {
this.id = id;
this.nanos = nanos;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="1" author="labidiayo">
<sqlFile
dbms="postgresql"
encoding="UTF-8" path="migration_root_reports.sql"
relativeToChangelogFile="true"
splitStatements="false"
stripComments="true"/>
</changeSet>
</databaseChangeLog>
Loading

0 comments on commit a36ca25

Please sign in to comment.