RED-9474 - Revert only local manual changes for an annotation #590

Merged
corina.olariu.ext1 merged 1 commits from RED-9474 into master 2024-07-15 15:02:21 +02:00
19 changed files with 394 additions and 251 deletions

View File

@ -71,12 +71,13 @@ public class ManualRedactionController implements ManualRedactionResource {
public void undo(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<String> annotationIds,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
@RequestParam(value = "includeOnlyUnprocessed", required = false, defaultValue = FALSE) boolean includeOnlyUnprocessed,
@RequestParam(value = "includeOnlyLocal", required = false, defaultValue = FALSE) boolean includeOnlyLocal) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsApprover(dossierId);
manualRedactionUndoService.undo(dossierId, fileId, annotationIds, includeUnprocessed);
manualRedactionUndoService.undo(dossierId, fileId, annotationIds, includeOnlyUnprocessed, includeOnlyLocal);
}

View File

@ -1,6 +1,5 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpStatus;
@ -15,7 +14,6 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CommentResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationComments;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualAddResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactionResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddCommentRequestModel;
@ -57,7 +55,8 @@ public interface ManualRedactionResource {
void undo(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<String> annotationIds,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
@RequestParam(value = "includeOnlyUnprocessed", required = false, defaultValue = FALSE) boolean includeOnlyUnprocessed,
@RequestParam(value = "includeOnlyLocal", required = false, defaultValue = FALSE) boolean includeOnlyLocal);
@ResponseStatus(value = HttpStatus.OK)

View File

@ -5,7 +5,6 @@ import java.util.List;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.BaseAnnotation;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRedactionEntry;
import lombok.Builder;
import lombok.Getter;
@ -23,6 +22,8 @@ public class ManualChangesQueryOptions {
private boolean includeDeletions = false;
@Builder.Default
private Set<Class<? extends BaseAnnotation>> excludedClasses = Collections.emptySet();
@Builder.Default
private List<String> annotationIds = Collections.emptyList();
public static ManualChangesQueryOptions allWithoutDeleted() {
@ -42,6 +43,11 @@ public class ManualChangesQueryOptions {
return ManualChangesQueryOptions.builder().includeDeletions(false).includeDictChanges(true).includeOnlyUnprocessed(true).build();
}
public static ManualChangesQueryOptions unprocessedOnlyForIds(List<String> annotationIds) {
return ManualChangesQueryOptions.builder().includeDeletions(false).includeDictChanges(true).includeOnlyUnprocessed(true).annotationIds(annotationIds).build();
}
public static ManualChangesQueryOptions localChangesOnly() {

View File

@ -36,7 +36,7 @@ public class EntityLogMongoWrapperService {
List<EntityLogEntry> entityLogEntries = entityLogMongoService.findEntityLogEntriesByIds(dossierId, fileId, ids);
if (includeUnprocessed) {
DossierEntity dossier = dossierService.getDossierById(dossierId);
ManualRedactions unprocessedManualRedactions = manualRedactionProviderService.getManualRedactions(fileId, ManualChangesQueryOptions.unprocessedOnly(), ids);
ManualRedactions unprocessedManualRedactions = manualRedactionProviderService.getManualRedactions(fileId, ManualChangesQueryOptions.unprocessedOnlyForIds(ids));
entityLogEntries = entityLogMergeService.mergeEntityLogEntries(unprocessedManualRedactions, entityLogEntries.stream().map(EntityLogEntry::getId).toList(), dossier, fileId);
}
return entityLogEntries;

View File

@ -4,9 +4,7 @@ import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicC
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
@ -49,11 +47,11 @@ public class ManualRedactionProviderService {
private final ResizeRedactionPersistenceService resizeRedactionPersistenceService;
public void convertUnprocessedAddToDictionariesToLocalChanges(String fileId) {
public void convertUnprocessedAddToDictionariesToLocalChanges(String fileId){
var unprocessedManualAdds = addRedactionPersistenceService.findEntriesByFileIdAndOptions(fileId, ManualChangesQueryOptions.unprocessedOnly());
for (var unprocessedManualAdd : unprocessedManualAdds){
if (unprocessedManualAdd.isAddToDictionary() || unprocessedManualAdd.isAddToAllDossiers()){
for (var unprocessedManualAdd : unprocessedManualAdds) {
if (unprocessedManualAdd.isAddToDictionary() || unprocessedManualAdd.isAddToAllDossiers()) {
unprocessedManualAdd.setAddToDictionary(false);
unprocessedManualAdd.setAddToAllDossiers(false);
unprocessedManualAdd.setLegalBasis("");
@ -65,12 +63,6 @@ public class ManualRedactionProviderService {
@Transactional
public ManualRedactions getManualRedactions(String fileId, ManualChangesQueryOptions options) {
return getManualRedactions(fileId, options, Collections.emptyList());
}
@Transactional
public ManualRedactions getManualRedactions(String fileId, ManualChangesQueryOptions options, List<String> annotationIds) {
Set<ManualRedactionEntry> entriesToAdd;
Set<IdRemoval> removals;
@ -121,15 +113,6 @@ public class ManualRedactionProviderService {
legalBasisChanges = Collections.emptySet();
}
if(!annotationIds.isEmpty()){
return new ManualRedactions(removals.stream().filter(r -> annotationIds.contains(r.getAnnotationId())).collect(Collectors.toSet()),
entriesToAdd.stream().filter(r -> annotationIds.contains(r.getAnnotationId())).collect(Collectors.toSet()),
forceRedactions.stream().filter(r -> annotationIds.contains(r.getAnnotationId())).collect(Collectors.toSet()),
recategorizations.stream().filter(r -> annotationIds.contains(r.getAnnotationId())).collect(Collectors.toSet()),
legalBasisChanges.stream().filter(r -> annotationIds.contains(r.getAnnotationId())).collect(Collectors.toSet()),
resizeRedactions.stream().filter(r -> annotationIds.contains(r.getAnnotationId())).collect(Collectors.toSet()));
}
return new ManualRedactions(removals, entriesToAdd, forceRedactions, recategorizations, legalBasisChanges, resizeRedactions);
}

View File

@ -19,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.IdRemovalEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRecategorizationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.model.ManualChangesQueryOptions;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogService;
@ -72,31 +73,56 @@ public class ManualRedactionUndoService {
@Transactional
public void undo(String dossierId, String fileId, Set<String> annotationIds, boolean includeUnprocessed) {
public void undo(String dossierId, String fileId, Set<String> annotationIds, boolean includeOnlyUnprocessed, boolean includeOnlyLocal) {
// undo the latest manual redaction for each annotationId
ManualRedactions manualRedactions = getManualRedactions(fileId);
// undo the manual redaction for each annotationId
ManualRedactions manualRedactions = getManualRedactions(fileId, includeOnlyUnprocessed, includeOnlyLocal, new ArrayList<>(annotationIds));
Map<String, ManualRedactionWrapperModel> manualRedactionWrappers = getLatestManualRedactionsForAnnotationIds(manualRedactions, annotationIds);
Map<String, List<ManualRedactionWrapperModel>> manualRedactionWrappers = groupAndSortManualRedactionsForAnnotationIds(manualRedactions, annotationIds);
if (manualRedactionWrappers.isEmpty()) {
throw new NotFoundException(String.format("ManualRedaction with annotationIds %s could not be found.", annotationIds));
}
undoManualRedactionEntries(dossierId, fileId, manualRedactionWrappers);
undoIdRemovals(dossierId, fileId, manualRedactionWrappers);
undoForceRedactions(dossierId, fileId, manualRedactionWrappers);
undoRecategorization(dossierId, fileId, manualRedactionWrappers, includeUnprocessed);
undoLegalBasisChange(dossierId, fileId, manualRedactionWrappers);
undoResize(dossierId, fileId, manualRedactionWrappers);
for (String annotationId : manualRedactionWrappers.keySet()) {
manualRedactionWrappers.get(annotationId)
.stream()
.forEach(manualChange -> {
if (manualChange.getItem() instanceof ManualRedactionEntry) {
undoManualRedactionEntry(dossierId, fileId, annotationId);
} else if (manualChange.getItem() instanceof IdRemoval) {
undoIdRemoval(dossierId, fileId, annotationId);
} else if (manualChange.getItem() instanceof ManualResizeRedaction) {
undoResizeEntry(dossierId, fileId, annotationId);
} else if (manualChange.getItem() instanceof ManualLegalBasisChange) {
undoLegalBasisChangeEntry(dossierId, fileId, annotationId);
} else if (manualChange.getItem() instanceof ManualRecategorization) {
undoRecategorizationEntry(dossierId, fileId, annotationId, includeOnlyUnprocessed);
} else if (manualChange.getItem() instanceof ManualForceRedaction) {
undoForceRedactionEntry(dossierId, fileId, annotationId);
}
});
}
reprocess(dossierId, fileId);
fileStatusPersistenceService.setLastManualChangeDate(fileId, OffsetDateTime.now());
}
private ManualRedactions getManualRedactions(String fileId) {
private ManualRedactions getManualRedactions(String fileId, boolean includeOnlyUnprocessed, boolean includeOnlyLocal, List<String> annotationIds) {
return manualRedactionProviderService.getManualRedactions(fileId, ManualChangesQueryOptions.allWithoutDeleted());
var fileStatus = fileStatusService.getStatus(fileId);
if (fileStatus.isExcludedFromAutomaticAnalysis() && !includeOnlyUnprocessed) { // analysis is disabled, only unprocessed changes can be reverted
throw new BadRequestException("Only unprocessed manual redactions can be reverted. includeOnlyUnprocessed must be set to true");
}
ManualChangesQueryOptions options = ManualChangesQueryOptions.builder()
.includeDeletions(false)
.includeDictChanges(!includeOnlyLocal)
.includeOnlyUnprocessed(includeOnlyUnprocessed)
.annotationIds(annotationIds)
.build();
return manualRedactionProviderService.getManualRedactions(fileId, options);
}
@ -106,236 +132,112 @@ public class ManualRedactionUndoService {
}
private void undoResize(String dossierId, String fileId, Map<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> manualResizeRedactions = manualRedactionWrappers.values()
.stream()
.filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualResizeRedaction)
.map(ManualRedactionWrapperModel::getId)
.toList();
if (!manualResizeRedactions.isEmpty()) {
deleteResizeRedaction(dossierId, fileId, manualResizeRedactions);
manualResizeRedactions.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual resize redaction was done.")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
ANNOTATION_ID,
annotationId))
.build()));
}
}
private void deleteResizeRedaction(String dossierId, String fileId, List<String> annotationIds) {
private void undoResizeEntry(String dossierId, String fileId, String annotationId) {
OffsetDateTime now = OffsetDateTime.now();
for (var annotationId : annotationIds) {
resizeRedactionPersistenceService.softDelete(fileId, annotationId, now);
}
resizeRedactionPersistenceService.softDelete(fileId, annotationId, now);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual resize redaction was done.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
.build());
}
private void undoLegalBasisChange(String dossierId, String fileId, Map<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
private void undoLegalBasisChangeEntry(String dossierId, String fileId, String annotationId) {
List<String> manualLegalBasisChanges = manualRedactionWrappers.values()
.stream()
.filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualLegalBasisChange)
.map(ManualRedactionWrapperModel::getId)
.toList();
if (!manualLegalBasisChanges.isEmpty()) {
deleteLegalBasisChange(dossierId, fileId, manualLegalBasisChanges);
manualLegalBasisChanges.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of legal basis change was done.")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
ANNOTATION_ID,
annotationId))
.build()));
}
legalBasisChangePersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of legal basis change was done.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
.build());
}
private void deleteLegalBasisChange(String dossierId, String fileId, List<String> annotationIds) {
private void undoRecategorizationEntry(String dossierId, String fileId, String annotationId, boolean includeUnprocessed) {
for (var annotationId : annotationIds) {
legalBasisChangePersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
}
private void undoRecategorization(String dossierId, String fileId, Map<String, ManualRedactionWrapperModel> manualRedactionWrappers, boolean includeUnprocessed) {
List<String> manualRecategorizations = manualRedactionWrappers.values()
.stream()
.filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualRecategorization)
.map(ManualRedactionWrapperModel::getId)
.toList();
if (!manualRecategorizations.isEmpty()) {
deleteRecategorization(dossierId, fileId, manualRecategorizations, includeUnprocessed);
manualRecategorizations.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual recategorization was done.")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
ANNOTATION_ID,
annotationId))
.build()));
}
}
private void deleteRecategorization(String dossierId, String fileId, List<String> annotationIds, boolean includeUnprocessed) {
dossierPersistenceService.getAndValidateDossier(dossierId);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, includeUnprocessed);
for (var annotationId : annotationIds) {
ManualRecategorizationEntity recategorizationEntity = recategorizationPersistenceService.findRecategorization(fileId, annotationId);
String originalValue = getEntityLogEntry(entityLog, annotationId).getValue();
manualRedactionDictionaryUpdateHandler.revertRemoveFromDictionary(originalValue, dossierId, fileId, recategorizationEntity.getTypeIdsOfDictionariesWithDelete());
manualRedactionDictionaryUpdateHandler.revertAddToDictionary(originalValue,
DictionaryEntryType.ENTRY,
fileId,
dossierId,
recategorizationEntity.getTypeIdsOfDictionariesWithAdd());
recategorizationEntity.setTypeIdsOfDictionariesWithAdd(Collections.emptySet());
recategorizationEntity.setTypeIdsOfDictionariesWithDelete(Collections.emptySet());
recategorizationEntity.setSoftDeletedTime(OffsetDateTime.now());
recategorizationPersistenceService.saveAndFlush(recategorizationEntity);
}
ManualRecategorizationEntity recategorizationEntity = recategorizationPersistenceService.findRecategorization(fileId, annotationId);
String originalValue = getEntityLogEntry(entityLog, annotationId).getValue();
manualRedactionDictionaryUpdateHandler.revertRemoveFromDictionary(originalValue, dossierId, fileId, recategorizationEntity.getTypeIdsOfDictionariesWithDelete());
manualRedactionDictionaryUpdateHandler.revertAddToDictionary(originalValue,
DictionaryEntryType.ENTRY,
fileId,
dossierId,
recategorizationEntity.getTypeIdsOfDictionariesWithAdd());
recategorizationEntity.setTypeIdsOfDictionariesWithAdd(Collections.emptySet());
recategorizationEntity.setTypeIdsOfDictionariesWithDelete(Collections.emptySet());
recategorizationEntity.setSoftDeletedTime(OffsetDateTime.now());
recategorizationPersistenceService.saveAndFlush(recategorizationEntity);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual recategorization was done.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
.build());
}
private void undoForceRedactions(String dossierId, String fileId, Map<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> manualForceRedactions = manualRedactionWrappers.values()
.stream()
.filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualForceRedaction)
.map(ManualRedactionWrapperModel::getId)
.toList();
if (!manualForceRedactions.isEmpty()) {
deleteForceRedaction(dossierId, fileId, manualForceRedactions);
manualForceRedactions.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual force redaction was done.")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
ANNOTATION_ID,
annotationId))
.build()));
}
}
private void deleteForceRedaction(String dossierId, String fileId, List<String> annotationIds) {
private void undoForceRedactionEntry(String dossierId, String fileId, String annotationId) {
var now = OffsetDateTime.now();
for (var annotationId : annotationIds) {
forceRedactionPersistenceService.softDelete(fileId, annotationId, now);
}
forceRedactionPersistenceService.softDelete(fileId, annotationId, now);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual force redaction was done.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
.build());
}
private void undoIdRemovals(String dossierId, String fileId, Map<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
private void undoIdRemoval(String dossierId, String fileId, String annotationId) {
List<String> idRemovals = manualRedactionWrappers.values()
.stream()
.filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof IdRemoval)
.map(ManualRedactionWrapperModel::getId)
.toList();
if (!idRemovals.isEmpty()) {
deleteRemoveRedaction(dossierId, fileId, idRemovals);
idRemovals.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual remove redaction was done.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
.build()));
}
}
private void deleteRemoveRedaction(String dossierId, String fileId, List<String> annotationIds) {
dossierPersistenceService.getAndValidateDossier(dossierId);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, true);
for (String annotationId : annotationIds) {
IdRemovalEntity removeRedaction = removeRedactionPersistenceService.findRemoveRedaction(fileId, annotationId);
String originalValue = getEntityLogEntry(entityLog, annotationId).getValue();
boolean dictionaryChanged = manualRedactionDictionaryUpdateHandler.revertRemoveFromDictionary(originalValue, dossierId, fileId, removeRedaction);
removeRedactionPersistenceService.updateModifiedDictionaries(fileId, annotationId, dictionaryChanged, Collections.emptySet());
removeRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
IdRemovalEntity removeRedaction = removeRedactionPersistenceService.findRemoveRedaction(fileId, annotationId);
String originalValue = getEntityLogEntry(entityLog, annotationId).getValue();
boolean dictionaryChanged = manualRedactionDictionaryUpdateHandler.revertRemoveFromDictionary(originalValue, dossierId, fileId, removeRedaction);
removeRedactionPersistenceService.updateModifiedDictionaries(fileId, annotationId, dictionaryChanged, Collections.emptySet());
removeRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual remove redaction was done.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
.build());
}
private void undoManualRedactionEntries(String dossierId, String fileId, Map<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
private void undoManualRedactionEntry(String dossierId, String fileId, String annotationId) {
List<String> manualRedactionEntries = manualRedactionWrappers.values()
.stream()
.filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualRedactionEntry)
.map(ManualRedactionWrapperModel::getId)
.toList();
if (!manualRedactionEntries.isEmpty()) {
deleteAddRedaction(dossierId, fileId, manualRedactionEntries);
manualRedactionEntries.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual add redaction was done.")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
ANNOTATION_ID,
annotationId))
.build()));
}
}
ManualRedactionEntryEntity addRedaction = addRedactionPersistenceService.findAddRedaction(fileId, annotationId);
boolean dictionaryChanged = manualRedactionDictionaryUpdateHandler.revertAddToDictionary(fileId, dossierId, addRedaction);
addRedactionPersistenceService.updateModifiedDictionaries(fileId, annotationId, dictionaryChanged, null);
addRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
private void deleteAddRedaction(String dossierId, String fileId, List<String> annotationIds) {
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
for (String annotationId : annotationIds) {
ManualRedactionEntryEntity addRedaction = addRedactionPersistenceService.findAddRedaction(fileId, annotationId);
boolean dictionaryChanged = manualRedactionDictionaryUpdateHandler.revertAddToDictionary(fileId, dossier.getId(), addRedaction);
addRedactionPersistenceService.updateModifiedDictionaries(fileId, annotationId, dictionaryChanged, null);
addRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Undo of manual add redaction was done.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
.build());
}
@ -350,13 +252,13 @@ public class ManualRedactionUndoService {
}
private Map<String, ManualRedactionWrapperModel> getLatestManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set<String> annotationIds) {
private Map<String, List<ManualRedactionWrapperModel>> groupAndSortManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set<String> annotationIds) {
Map<String, ManualRedactionWrapperModel> result = new HashMap<>();
Map<String, List<ManualRedactionWrapperModel>> result = new HashMap<>();
annotationIds.forEach(annotationId -> {
var last = getLatestManualRedactionForAnnotationId(manualRedactions, annotationId);
if (last != null) {
result.put(annotationId, last);
var manualRedactionsForId = getLatestManualRedactionForAnnotationId(manualRedactions, annotationId);
if (manualRedactionsForId != null) {
result.put(annotationId, manualRedactionsForId);
}
});
@ -364,7 +266,21 @@ public class ManualRedactionUndoService {
}
private ManualRedactionWrapperModel getLatestManualRedactionForAnnotationId(ManualRedactions manualRedactions, String annotationId) {
private Map<String, ManualRedactionWrapperModel> getLatestManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set<String> annotationIds) {
Map<String, ManualRedactionWrapperModel> result = new HashMap<>();
annotationIds.forEach(annotationId -> {
var last = getLatestManualRedactionForAnnotationId(manualRedactions, annotationId);
if (last != null) {
result.put(annotationId, last.get(0));
}
});
return result;
}
private List<ManualRedactionWrapperModel> getLatestManualRedactionForAnnotationId(ManualRedactions manualRedactions, String annotationId) {
final List<ManualRedactionWrapperModel> manualRedactionWrappers = new ArrayList<>();
@ -402,7 +318,7 @@ public class ManualRedactionUndoService {
.sorted(Comparator.comparing(ManualRedactionWrapperModel::getDate, Comparator.nullsLast(Comparator.reverseOrder())))
.toList();
return sortedManualRedactionWrappers.isEmpty() ? null : sortedManualRedactionWrappers.get(0);
return sortedManualRedactionWrappers.isEmpty() ? null : sortedManualRedactionWrappers;
}
}

View File

@ -83,7 +83,14 @@ public class AddRedactionPersistenceService {
@Transactional
public List<ManualRedactionEntryEntity> findEntriesByFileIdAndOptions(String fileId, ManualChangesQueryOptions options) {
return manualRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
if (options.getAnnotationIds().isEmpty()) {
return manualRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
}
return manualRedactionRepository.findByFileIdAndOptionsAndAnnotationIds(fileId,
options.isIncludeDeletions(),
options.isIncludeOnlyUnprocessed(),
options.isIncludeDictChanges(),
options.getAnnotationIds());
}

View File

@ -40,7 +40,10 @@ public class ForceRedactionPersistenceService {
public List<ManualForceRedactionEntity> findEntriesByFileIdAndOptions(String fileId, ManualChangesQueryOptions options) {
return forceRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed());
if (options.getAnnotationIds().isEmpty()) {
return forceRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed());
}
return forceRedactionRepository.findByFileIdAndOptionsAndAnnotationIds(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.getAnnotationIds());
}
public int softDeleteByFileIds(List<String> fileIds, OffsetDateTime softDeletionTime) {

View File

@ -81,7 +81,10 @@ public class LegalBasisChangePersistenceService {
public List<ManualLegalBasisChangeEntity> findEntriesByFileIdAndOptions(String fileId, ManualChangesQueryOptions options) {
return legalBasisChangeRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed());
if (options.getAnnotationIds().isEmpty()) {
return legalBasisChangeRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed());
}
return legalBasisChangeRepository.findByFileIdAndOptionsAndAnnotationIds(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.getAnnotationIds());
}
public int softDeleteByFileIds(List<String> fileIds, OffsetDateTime softDeletionTime) {

View File

@ -89,7 +89,10 @@ public class RecategorizationPersistenceService {
public List<ManualRecategorizationEntity> findEntriesByFileIdAndOptions(String fileId, ManualChangesQueryOptions options) {
return recategorizationRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
if (options.getAnnotationIds().isEmpty()) {
return recategorizationRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
}
return recategorizationRepository.findByFileIdAndOptionsAndAnnotationIds(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges(), options.getAnnotationIds());
}
public int softDeleteByFileIds(List<String> fileIds, OffsetDateTime softDeletionTime) {

View File

@ -49,7 +49,10 @@ public class RemoveRedactionPersistenceService {
public List<IdRemovalEntity> findEntriesByFileIdAndOptions(String fileId, ManualChangesQueryOptions options) {
return removeRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
if (options.getAnnotationIds().isEmpty()) {
return removeRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
}
return removeRedactionRepository.findByFileIdAndOptionsAndAnnotationIds(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges(), options.getAnnotationIds());
}
public int softDeleteByFileIds(List<String> fileIds, OffsetDateTime softDeletionTime) {

View File

@ -101,7 +101,10 @@ public class ResizeRedactionPersistenceService {
public List<ManualResizeRedactionEntity> findEntriesByFileIdAndOptions(String fileId, ManualChangesQueryOptions options) {
return resizeRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
if (options.getAnnotationIds().isEmpty()) {
return resizeRedactionRepository.findByFileIdAndOptions(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges());
}
return resizeRedactionRepository.findByFileIdAndOptionsAndAnnotationIds(fileId, options.isIncludeDeletions(), options.isIncludeOnlyUnprocessed(), options.isIncludeDictChanges(), options.getAnnotationIds());
}

View File

@ -48,6 +48,20 @@ public interface ForceRedactionRepository extends JpaRepository<ManualForceRedac
@Param("unprocessed") boolean unprocessed);
@Query("""
select m
from ManualForceRedactionEntity m
where m.id.fileId = :fileId
and ((:unprocessed = true and m.processedDate is null) or :unprocessed = false)
and (:includeDeletions = true or m.softDeletedTime is null)
and m.id.annotationId in (:annotationIds)
""")
List<ManualForceRedactionEntity> findByFileIdAndOptionsAndAnnotationIds(@Param("fileId") String fileId,
@Param("includeDeletions") boolean includeDeletions,
@Param("unprocessed") boolean unprocessed,
@Param("annotationIds") List<String> annotationIds);
@Modifying
@Query("update ManualForceRedactionEntity m set m.softDeletedTime = :softDeletedTime where m.id.fileId in (:fileIds) and m.softDeletedTime is null")
int softDeleteByFileIds(@Param("fileIds") List<String> fileIds, @Param("softDeletedTime") OffsetDateTime softDeletedTime);

View File

@ -47,6 +47,19 @@ public interface LegalBasisChangeRepository extends JpaRepository<ManualLegalBas
@Param("includeDeletions") boolean includeDeletions,
@Param("unprocessed") boolean unprocessed);
@Query("""
select m
from ManualLegalBasisChangeEntity m
where m.id.fileId = :fileId
and ((:unprocessed = true and m.processedDate is null) or :unprocessed = false)
and (:includeDeletions = true or m.softDeletedTime is null)
and m.id.annotationId in (:annotationIds)
""")
List<ManualLegalBasisChangeEntity> findByFileIdAndOptionsAndAnnotationIds(@Param("fileId") String fileId,
@Param("includeDeletions") boolean includeDeletions,
@Param("unprocessed") boolean unprocessed,
@Param("annotationIds") List<String> annotationIds);
@Modifying
@Query("update ManualLegalBasisChangeEntity m set m.softDeletedTime = :softDeletedTime where m.id.fileId in (:fileIds) and m.softDeletedTime is null")

View File

@ -51,6 +51,21 @@ public interface ManualRedactionRepository extends JpaRepository<ManualRedaction
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges);
@Query("""
select m
from ManualRedactionEntryEntity m
where m.id.fileId = :fileId
and ((:unprocessed = true and m.processedDate is null) or :unprocessed = false)
and (:includeDeletions = true or m.softDeletedTime is null)
and (:includeDictChanges = true or (m.addToDictionary = false and m.addToAllDossiers = false))
and m.id.annotationId in (:annotationIds)
""")
List<ManualRedactionEntryEntity> findByFileIdAndOptionsAndAnnotationIds(@Param("fileId") String fileId,
@Param("includeDeletions") boolean includeDeletions,
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges,
@Param("annotationIds") List<String> annotationIds);
@Modifying
@Query("update ManualRedactionEntryEntity m set m.softDeletedTime = :softDeletedTime where m.id.fileId in (:fileIds) and m.softDeletedTime is null")
int softDeleteByFileIds(@Param("fileIds") List<String> fileIds,

View File

@ -49,6 +49,23 @@ public interface RecategorizationRepository extends JpaRepository<ManualRecatego
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges);
@Query("""
select m
from ManualRecategorizationEntity m
where m.id.fileId = :fileId
and ((:unprocessed = true and m.processedDate is null) or :unprocessed = false)
and (:includeDeletions = true or m.softDeletedTime is null)
and (:includeDictChanges = true or (m.addToDictionary = false and m.addToAllDossiers = false))
and m.id.annotationId in (:annotationIds)
""")
List<ManualRecategorizationEntity> findByFileIdAndOptionsAndAnnotationIds(@Param("fileId") String fileId,
@Param("includeDeletions") boolean includeDeletions,
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges,
@Param("annotationIds") List<String> annotationIds);
@Modifying
@Query("update ManualRecategorizationEntity m set m.softDeletedTime = :softDeletedTime where m.id.fileId in (:fileIds) and m.softDeletedTime is null")
int softDeleteByFileIds(@Param("fileIds") List<String> fileIds,

View File

@ -49,6 +49,21 @@ public interface RemoveRedactionRepository extends JpaRepository<IdRemovalEntity
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges);
@Query("""
select m
from IdRemovalEntity m
where m.id.fileId = :fileId
and ((:unprocessed = true and m.processedDate is null) or :unprocessed = false)
and (:includeDeletions = true or m.softDeletedTime is null)
and (:includeDictChanges = true or (m.removeFromDictionary = false and m.removeFromAllDossiers = false))
and m.id.annotationId in (:annotationIds)
""")
List<IdRemovalEntity> findByFileIdAndOptionsAndAnnotationIds(@Param("fileId") String fileId,
@Param("includeDeletions") boolean includeDeletions,
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges,
@Param("annotationIds") List<String> annotationIds);
@Modifying
@Query("update IdRemovalEntity m set m.softDeletedTime = :softDeletedTime where m.id.fileId in (:fileIds) and m.softDeletedTime is null")
int softDeleteByFileIds(@Param("fileIds") List<String> fileIds,

View File

@ -57,6 +57,23 @@ public interface ResizeRedactionRepository extends JpaRepository<ManualResizeRed
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges);
@Query("""
select m
from ManualResizeRedactionEntity m
where m.id.fileId = :fileId
and ((:unprocessed = true and m.processedDate is null) or :unprocessed = false)
and (:includeDeletions = true or m.softDeletedTime is null)
and (:includeDictChanges = true or (m.updateDictionary = false and m.addToAllDossiers = false))
and m.id.annotationId in (:annotationIds)
""")
List<ManualResizeRedactionEntity> findByFileIdAndOptionsAndAnnotationIds(@Param("fileId") String fileId,
@Param("includeDeletions") boolean includeDeletions,
@Param("unprocessed") boolean unprocessed,
@Param("includeDictChanges") boolean includeDictChanges,
@Param("annotationIds") List<String> annotationIds);
@Modifying
@Query("update ManualResizeRedactionEntity m set m.softDeletedTime = :softDeletedTime where m.id.fileId in (:fileIds) and m.softDeletedTime is null")
int softDeleteByFileIds(@Param("fileIds") List<String> fileIds,

View File

@ -366,7 +366,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary.getEntries()).isEmpty();
manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of("AnnotationId"), false);
manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of("AnnotationId"), false, false);
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder("Luke Skywalker", "Darth Vader");
@ -1244,7 +1244,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary2.getEntries()).isEmpty();
manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of(annotationId), false);
manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of(annotationId), false, false);
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder(lukeSkywalker, darthVader);
@ -1258,6 +1258,131 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
}
@Test
public void testManualRecategorizeAndUndoOnlyManualChanges() {
var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate);
var file = fileTesterAndProvider.testAndProvideFile(dossier);
var type = typeProvider.testAndProvideType(dossierTemplate);
var type2 = typeProvider.testAndProvideType(dossierTemplate, dossier, "test2", false, 66);
var entries = new ArrayList<String>();
var lukeSkywalker = "Luke Skywalker";
var darthVader = "Darth Vader";
entries.add(lukeSkywalker);
entries.add(darthVader);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of(lukeSkywalker), false, dossier.getId(), DictionaryEntryType.ENTRY);
Dictionary dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder(lukeSkywalker, darthVader);
Dictionary dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary.getEntries()).containsExactlyInAnyOrder(lukeSkywalker);
Dictionary dossierTemplateDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary2.getEntries()).isEmpty();
Dictionary dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary2.getEntries()).isEmpty();
var annotationId = "AnnotationId";
var entityLog = new EntityLog(1,
1,
List.of(EntityLogEntry.builder()
.id(annotationId)
.type(type.getType())
.value(lukeSkywalker)
.dictionaryEntry(true)
.entryType(EntryType.ENTITY)
.state(EntryState.APPLIED)
.build()),
null,
0,
0,
0,
0);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.recategorizeBulk(dossier.getId(),
file.getId(),
Set.of(RecategorizationRequestModel.builder()
.type(type2.getType())
.annotationId(annotationId)
.addToDictionary(true)
.addToAllDossiers(true)
.legalBasis("")
.section("section")
.value(lukeSkywalker)
.build()),
false);
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder(darthVader);
dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary.getEntries()).isEmpty();
dossierTemplateDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary2.getEntries()).containsExactlyInAnyOrder(lukeSkywalker);
dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary2.getEntries()).isEmpty();
// try to undo only local changes, but there are none for the requested annotationId
String errorMessage = assertThrows(FeignException.NotFound.class,
() -> manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of(annotationId), false, true)).getMessage();
assertTrue(errorMessage.contains("ManualRedaction with annotationIds [AnnotationId] could not be found"));
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder(darthVader);
dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary.getEntries()).isEmpty();
dossierTemplateDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary2.getEntries()).containsExactlyInAnyOrder(lukeSkywalker);
dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary2.getEntries()).isEmpty();
var resizeRedactionRequestModel = ResizeRedactionRequestModel.builder()
.annotationId(annotationId)
.value(lukeSkywalker + " resized")
.updateDictionary(false)
.addToAllDossiers(false)
.positions(List.of(Rectangle.builder().page(1).height(1).width(2).topLeftX(1).topLeftY(1).build()))
.build();
ManualRedactionResponse response = manualRedactionClient.resizeRedactionBulk(dossier.getId(), file.getId(), Set.of(resizeRedactionRequestModel), false);
var allManualRedactions = manualRedactionClient.getManualRedactions(dossier.getId(), file.getId(), true, true);
assertEquals(allManualRedactions.getResizeRedactions().size(), 1);
assertEquals(allManualRedactions.getEntriesToAdd().size(), 1);
assertEquals(allManualRedactions.getRecategorizations().size(), 1);
var entryToResize = allManualRedactions.getResizeRedactions()
.stream()
.findFirst()
.get();
var entryToAdd = allManualRedactions.getEntriesToAdd()
.stream()
.findFirst()
.get();
assertFalse(entryToAdd.isAddToDictionary());
assertFalse(entryToAdd.isAddToDossierDictionary());
assertEquals(entryToAdd.getAnnotationId(), entryToResize.getAnnotationId());
manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of(annotationId, entryToResize.getAnnotationId()), false, true);
allManualRedactions = manualRedactionClient.getManualRedactions(dossier.getId(), file.getId(), true, true);
assertEquals(allManualRedactions.getResizeRedactions().size(), 0);
assertEquals(allManualRedactions.getEntriesToAdd().size(), 0);
assertEquals(allManualRedactions.getRecategorizations().size(), 1);
}
@Test
public void testManualRecategorizeAndUndoDossierLevelOnly() {
@ -1330,7 +1455,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getId());
assertThat(dossierDictionary2.getEntries()).containsExactlyInAnyOrder(lukeSkywalker);
manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of(annotationId), false);
manualRedactionClient.undo(dossier.getId(), file.getFileId(), Set.of(annotationId), false, false);
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).isEmpty();