Merge branch 'RED-9540' into 'master'

RED-9540: Workload columns in dossier are not showing the "R" (redacted)...

Closes RED-9540

See merge request redactmanager/persistence-service!721
This commit is contained in:
Maverick Studer 2024-09-05 15:51:53 +02:00
commit 84d2a6fac0
4 changed files with 338 additions and 23 deletions

View File

@ -0,0 +1,7 @@
package com.iqser.red.service.persistence.management.v1.processor.model;
import java.time.OffsetDateTime;
public record AnalysisFlags(boolean hasRedactions, boolean hasHints, boolean hasSuggestions, boolean hasImages, boolean hasUpdates, boolean hasComments, OffsetDateTime lastRedactionModification, OffsetDateTime lastManualChangeDate) {
}

View File

@ -8,10 +8,13 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ViewedPageEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.model.AnalysisFlags;
import com.iqser.red.service.persistence.management.v1.processor.model.websocket.FileEventType;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ViewedPagesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.websocket.WebsocketService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryState;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryType;
@ -56,6 +59,41 @@ public class AnalysisFlagsCalculationService {
Map<Integer, OffsetDateTime> viewedPages = viewedPagesForCurrentAssignee.stream()
.collect(Collectors.toMap(ViewedPageEntity::getPage, ViewedPageEntity::getViewedTime));
AnalysisFlags flags = calculateFlags(entityLog, viewedPages, file);
log.debug("Flag Calculations for file: {} took: {}ms", fileId, System.currentTimeMillis() - startTime);
if (file.isHasRedactions() == flags.hasRedactions()
&& file.isHasHints() == flags.hasHints()
&& file.isHasImages() == flags.hasImages()
&& file.isHasSuggestions() == flags.hasSuggestions()
&& file.isHasAnnotationComments() == flags.hasComments()
&& file.isHasUpdates() == flags.hasUpdates()) {
log.debug("Nothing Changed for file: {}", fileId);
} else {
fileStatusPersistenceService.updateFlags(fileId,
flags.hasRedactions(),
flags.hasHints(),
flags.hasImages(),
flags.hasSuggestions(),
flags.hasComments(),
flags.hasUpdates());
}
if (flags.lastRedactionModification() != null && (file.getRedactionModificationDate() == null || file.getRedactionModificationDate()
.isBefore(flags.lastRedactionModification()))) {
fileStatusPersistenceService.setLastRedactionModificationDateForFile(fileId, flags.lastRedactionModification());
}
if (flags.lastManualChangeDate() != null && (file.getLastManualChangeDate() == null || file.getLastManualChangeDate().isBefore(flags.lastManualChangeDate()))) {
fileStatusPersistenceService.setLastManualChangeDate(fileId, flags.lastManualChangeDate());
}
fileStatusPersistenceService.setLastFlagCalculation(fileId, OffsetDateTime.now());
websocketService.sendFileEvent(dossierId, fileId, FileEventType.UPDATE);
}
public AnalysisFlags calculateFlags(EntityLog entityLog, Map<Integer, OffsetDateTime> viewedPages, FileEntity file) {
boolean hasRedactions = false;
boolean hasHints = false;
boolean hasSuggestions = false;
@ -98,7 +136,11 @@ public class AnalysisFlagsCalculationService {
}
if (!hasRedactions && entry.getState().equals(EntryState.APPLIED) && //
(entryType.equals(EntryType.ENTITY) || entryType.equals(EntryType.IMAGE) || entryType.equals(EntryType.AREA))) {
(entryType.equals(EntryType.ENTITY)
|| entryType.equals(EntryType.IMAGE)
|| entryType.equals(EntryType.HINT)
|| entryType.equals(EntryType.IMAGE_HINT)
|| entryType.equals(EntryType.AREA))) {
hasRedactions = true;
}
@ -123,28 +165,7 @@ public class AnalysisFlagsCalculationService {
}
}
log.debug("Flag Calculations for file: {} took: {}ms", fileId, System.currentTimeMillis() - startTime);
if (file.isHasRedactions() == hasRedactions
&& file.isHasHints() == hasHints
&& file.isHasImages() == hasImages
&& file.isHasSuggestions() == hasSuggestions
&& file.isHasAnnotationComments() == hasComments
&& file.isHasUpdates() == hasUpdates) {
log.debug("Nothing Changed for file: {}", fileId);
} else {
fileStatusPersistenceService.updateFlags(fileId, hasRedactions, hasHints, hasImages, hasSuggestions, hasComments, hasUpdates);
}
if (lastRedactionModification != null && (file.getRedactionModificationDate() == null || file.getRedactionModificationDate().isBefore(lastRedactionModification))) {
fileStatusPersistenceService.setLastRedactionModificationDateForFile(fileId, lastRedactionModification);
}
if (lastManualChangeDate != null && (file.getLastManualChangeDate() == null || file.getLastManualChangeDate().isBefore(lastManualChangeDate))) {
fileStatusPersistenceService.setLastManualChangeDate(fileId, lastManualChangeDate);
}
fileStatusPersistenceService.setLastFlagCalculation(fileId, OffsetDateTime.now());
websocketService.sendFileEvent(dossierId, fileId, FileEventType.UPDATE);
return new AnalysisFlags(hasRedactions, hasHints, hasSuggestions, hasImages, hasUpdates, hasComments, lastRedactionModification, lastManualChangeDate);
}

View File

@ -0,0 +1,46 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.model.AnalysisFlags;
import com.iqser.red.service.persistence.management.v1.processor.service.AnalysisFlagsCalculationService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import io.swagger.v3.core.util.ObjectMapperFactory;
public class AnalysisFlagCalculationServiceTest extends AbstractPersistenceServerServiceTest {
@Autowired
private AnalysisFlagsCalculationService analysisFlagsCalculationService;
@Test
public void testWithEntityLogContainingOnlyImages() throws IOException {
var file = new ClassPathResource(String.format("files/entity-log/entitylog-only-images.json"));
ObjectMapper objectMapper = ObjectMapperFactory.buildStrictGenericObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
AnalysisFlags analysisFlags = analysisFlagsCalculationService.calculateFlags(entityLog,
Map.of(1, OffsetDateTime.now()),
FileEntity.builder().workflowStatus(WorkflowStatus.UNDER_REVIEW).build());
assertTrue(analysisFlags.hasRedactions());
}
}

View File

@ -0,0 +1,241 @@
{
"analysisVersion": 1,
"analysisNumber": 2,
"entityLogEntry": [
{
"id": "c42ff702a58c14f3b5c3e1f65be6f13f",
"type": "image",
"entryType": "IMAGE_HINT",
"state": "SKIPPED",
"value": "Image:Other",
"reason": "",
"matchedRule": "",
"legalBasis": "",
"imported": false,
"containingNodeId": [
0
],
"closestHeadline": "",
"section": "Document: ",
"color": null,
"positions": [
{
"rectangle": [
48.0,
583.0,
228.0,
80.0
],
"pageNumber": 1
}
],
"textBefore": null,
"textAfter": null,
"startOffset": 0,
"endOffset": 0,
"imageHasTransparency": false,
"dictionaryEntry": false,
"dossierDictionaryEntry": false,
"excluded": false,
"changes": [
{
"analysisNumber": 1,
"type": "ADDED",
"dateTime": "2024-09-05T11:46:42.889Z",
"propertyChanges": {}
}
],
"manualChanges": [],
"engines": [],
"reference": [],
"importedRedactionIntersections": [],
"numberOfComments": 0,
"paragraphPageIdx": -1
},
{
"id": "433e5143ac14f4a809547311d3fbe3d2",
"type": "logo",
"entryType": "IMAGE",
"state": "SKIPPED",
"value": "Image:Logo",
"reason": "Logo Found",
"matchedRule": "ETC.3.1",
"legalBasis": "",
"imported": false,
"containingNodeId": [
1
],
"closestHeadline": "",
"section": "Document: ",
"color": null,
"positions": [
{
"rectangle": [
54.0,
683.0,
88.0,
76.0
],
"pageNumber": 1
}
],
"textBefore": null,
"textAfter": null,
"startOffset": 0,
"endOffset": 0,
"imageHasTransparency": false,
"dictionaryEntry": false,
"dossierDictionaryEntry": false,
"excluded": false,
"changes": [
{
"analysisNumber": 1,
"type": "ADDED",
"dateTime": "2024-09-05T11:46:42.889Z",
"propertyChanges": {}
}
],
"manualChanges": [],
"engines": [],
"reference": [],
"importedRedactionIntersections": [],
"numberOfComments": 0,
"paragraphPageIdx": -1
},
{
"id": "9fb1e7b11a4eec67c8397990286bba46",
"type": "image",
"entryType": "IMAGE_HINT",
"state": "APPLIED",
"value": "Image:Other",
"reason": "forced by manual override",
"matchedRule": "",
"legalBasis": "Article 39(e)(3) of Regulation (EC) No 178/2002",
"imported": false,
"containingNodeId": [
2
],
"closestHeadline": "",
"section": "Document: ",
"color": null,
"positions": [
{
"rectangle": [
55.0,
410.0,
179.0,
110.0
],
"pageNumber": 1
}
],
"textBefore": null,
"textAfter": null,
"startOffset": 0,
"endOffset": 0,
"imageHasTransparency": false,
"dictionaryEntry": false,
"dossierDictionaryEntry": false,
"excluded": false,
"changes": [
{
"analysisNumber": 1,
"type": "ADDED",
"dateTime": "2024-09-05T11:46:42.889Z",
"propertyChanges": {}
},
{
"analysisNumber": 2,
"type": "FORCE_REDACT",
"dateTime": "2024-09-05T11:47:32.509Z",
"propertyChanges": {
"reason": " -> forced by manual override",
"engines": "[] -> [MANUAL]",
"legalBasis": " -> Article 39(e)(3) of Regulation (EC) No 178/2002",
"state": "SKIPPED -> APPLIED"
}
}
],
"manualChanges": [
{
"manualRedactionType": "FORCE",
"processedDate": "2024-09-05T11:47:33.181Z",
"requestedDate": "2024-09-05T11:47:32.509Z",
"userId": "3611d252-45ea-4dae-bb55-0b12ba5d1d64",
"propertyChanges": {
"legalBasis": "Article 39(e)(3) of Regulation (EC) No 178/2002"
},
"processedAnalysisNumber": 2,
"processed": true
}
],
"engines": [
"MANUAL"
],
"reference": [],
"importedRedactionIntersections": [],
"numberOfComments": 0,
"paragraphPageIdx": -1
}
],
"legalBasis": [
{
"name": "1.1 personal data (incl. geolocation); Article 39(e)(3)",
"description": "(Regulations (EU) 2016/679 and (EU) 2018/1725 shall apply to the processing of personal data carried out pursuant to this Regulation. Any personal data made public pursuant to Article 38 of this Regulation and this Article shall only be used to ensure the transparency of the risk assessment under this Regulation and shall not be further processed in a manner that is incompatible with these purposes, in accordance with point (b) of Article 5(1) of Regulation (EU) 2016/679 and point (b) of Article 4(1) of Regulation (EU) 2018/1725, as the case may be)",
"reason": "Article 39(e)(3) of Regulation (EC) No 178/2002",
"technicalName": "personal_data_geolocation_article_39e3"
},
{
"name": "1.2 vertebrate study related personal data (incl. geolocation); Article 39(e)(2)",
"description": "personal data (names and addresses) of individuals involved in testing on vertebrate studies or in obtaining toxicological information",
"reason": "Article 39(e)(2) of Regulation (EC) No 178/2002",
"technicalName": "vertebrate_study_personal_data_geolocation_article_39e2"
},
{
"name": "2. manufacturing or production process",
"description": "the manufacturing or production process, including the method and innovative aspects thereof, as well as other technical and industrial specifications inherent to that process or method, except for information which is relevant to the assessment of safety",
"reason": "Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)",
"technicalName": "manufacturing_production_process"
},
{
"name": "3. links between a producer and applicant",
"description": "commercial links between a producer or importer and the applicant or the authorisation holder, where applicable",
"reason": "Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)",
"technicalName": "links_producer_applicant"
},
{
"name": "4. commercial information",
"description": "commercial information revealing sourcing, market shares or business strategy of the applicant",
"reason": "Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)",
"technicalName": "commercial_information"
},
{
"name": "5. quantitative composition",
"description": "quantitative composition of the subject matter of the request, except for information which is relevant to the assessment of safety",
"reason": "Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)",
"technicalName": "quantitative_composition"
},
{
"name": "6. specification of impurity",
"description": "the specification of impurity of the active substance and the related methods of analysis for impurities in the active substance as manufactured, except for the impurities that are considered to be toxicologically, ecotoxicologically or environmentally relevant and the related methods of analysis for such impurities",
"reason": "Article 63(2)(b) of Regulation (EC) No 1107/2009",
"technicalName": "specification_impurity"
},
{
"name": "7. results of production batches",
"description": "results of production batches of the active substance including impurities",
"reason": "Article 63(2)(c) of Regulation (EC) No 1107/2009",
"technicalName": "results_production_batches"
},
{
"name": "8. composition of a plant protection product",
"description": "information on the complete composition of a plant protection product",
"reason": "Article 63(2)(d) of Regulation (EC) No 1107/2009",
"technicalName": "composition_plant_protection_product"
}
],
"dictionaryVersion": 22,
"dossierDictionaryVersion": 15,
"rulesVersion": 0,
"legalBasisVersion": 2
}