From c03b489650e3787ec551b1391f46195002756081 Mon Sep 17 00:00:00 2001 From: Maverick Studer Date: Thu, 5 Sep 2024 15:51:53 +0200 Subject: [PATCH] RED-9540: Workload columns in dossier are not showing the "R" (redacted)... --- .../v1/processor/model/AnalysisFlags.java | 7 + .../AnalysisFlagsCalculationService.java | 67 +++-- .../AnalysisFlagCalculationServiceTest.java | 46 ++++ .../entity-log/entitylog-only-images.json | 241 ++++++++++++++++++ 4 files changed, 338 insertions(+), 23 deletions(-) create mode 100644 persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/model/AnalysisFlags.java create mode 100644 persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/AnalysisFlagCalculationServiceTest.java create mode 100644 persistence-service-v1/persistence-service-server-v1/src/test/resources/files/entity-log/entitylog-only-images.json diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/model/AnalysisFlags.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/model/AnalysisFlags.java new file mode 100644 index 000000000..587b1cd12 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/model/AnalysisFlags.java @@ -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) { + +} \ No newline at end of file diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java index 566bc474f..03f7c54c8 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java @@ -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 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 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); } diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/AnalysisFlagCalculationServiceTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/AnalysisFlagCalculationServiceTest.java new file mode 100644 index 000000000..12bab19eb --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/AnalysisFlagCalculationServiceTest.java @@ -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()); + } + +} diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/resources/files/entity-log/entitylog-only-images.json b/persistence-service-v1/persistence-service-server-v1/src/test/resources/files/entity-log/entitylog-only-images.json new file mode 100644 index 000000000..03d46ff52 --- /dev/null +++ b/persistence-service-v1/persistence-service-server-v1/src/test/resources/files/entity-log/entitylog-only-images.json @@ -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 +} \ No newline at end of file