Skip to content

Commit

Permalink
Allow to save metadata editor default column layout without known tas…
Browse files Browse the repository at this point in the history
…k context.
  • Loading branch information
thomaslow committed Oct 1, 2024
1 parent c4ddc1b commit fe6e012
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class DataEditorSetting extends BaseBean {
@Column(name = "user_id")
private int userId;

@Column(name = "task_id")
private int taskId;
@Column(name = "task_id", nullable = true)
private Integer taskId;

@Column(name = "structure_width")
private float structureWidth;
Expand Down Expand Up @@ -59,20 +59,20 @@ public void setUserId(int userId) {
}

/**
* Get taskId.
* Get taskId. Either the id of the task or null, for a task-independent default layout.
*
* @return value of taskId
*/
public int getTaskId() {
public Integer getTaskId() {
return taskId;
}

/**
* Set taskId.
* Set taskId. Either the id of the task or null, for a task-independent default layout.
*
* @param taskId as int
*/
public void setTaskId(int taskId) {
public void setTaskId(Integer taskId) {
this.taskId = taskId;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--
-- (c) Kitodo. Key to digital objects e. V. <[email protected]>
--
-- This file is part of the Kitodo project.
--
-- It is licensed under GNU General Public License version 3 or later.
--
-- For the full copyright and license information, please read the
-- GPL3-License.txt file that was distributed with this source code.
--

SET SQL_SAFE_UPDATES = 0;

-- allow null in column task_id of table dataeditorsetting to store task-independent layout
ALTER TABLE dataeditor_setting MODIFY COLUMN task_id INT(11) NULL;

SET SQL_SAFE_UPDATES = 1;
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,15 @@ public void open(String processID, String referringView, String taskId) {
}

private void showDataEditorSettingsLoadedMessage() throws DAOException {
String task = ServiceManager.getTaskService().getById(templateTaskId).getTitle();
Locale locale = LocaleHelper.getCurrentLocale();
String title = Helper.getString(locale, "dataEditor.layoutLoadedSuccessfullyTitle");
String text = MessageFormat.format(Helper.getString(locale, "dataEditor.layoutLoadedSuccessfullyText"), task);
String text = Helper.getString(locale, "dataEditor.layoutLoadedSuccessfullyDefaultText");
if (templateTaskId > 0) {
String task = ServiceManager.getTaskService().getById(templateTaskId).getTitle();
text = MessageFormat.format(
Helper.getString(locale, "dataEditor.layoutLoadedSuccessfullyForTaskText"), task
);
}
String script = GROWL_MESSAGE.replace("SUMMARY", title).replace("DETAIL", text)
.replace("SEVERITY", "info");
PrimeFaces.current().executeScript("PF('notifications').removeAll();");
Expand All @@ -338,16 +343,13 @@ private void checkProjectFolderConfiguration() {
}

private void loadDataEditorSettings() {
if (templateTaskId > 0) {
dataEditorSetting = ServiceManager.getDataEditorSettingService().loadDataEditorSetting(user.getId(),
templateTaskId);
if (Objects.isNull(dataEditorSetting)) {
dataEditorSetting = new DataEditorSetting();
dataEditorSetting.setUserId(user.getId());
dataEditorSetting.setTaskId(templateTaskId);
}
} else {
dataEditorSetting = null;
// use template task id if it exists, otherwise use null for task-independent layout
Integer taskId = templateTaskId > 0 ? templateTaskId : null;
dataEditorSetting = ServiceManager.getDataEditorSettingService().loadDataEditorSetting(user.getId(), taskId);
if (Objects.isNull(dataEditorSetting)) {
dataEditorSetting = new DataEditorSetting();
dataEditorSetting.setUserId(user.getId());
dataEditorSetting.setTaskId(taskId);
}
}

Expand Down Expand Up @@ -1068,14 +1070,15 @@ public void updateNumberOfScans() {
* Save current metadata editor layout.
*/
public void saveDataEditorSetting() {
if (Objects.nonNull(dataEditorSetting) && dataEditorSetting.getTaskId() > 0) {
if (Objects.nonNull(dataEditorSetting)) {
try {
ServiceManager.getDataEditorSettingService().saveToDatabase(dataEditorSetting);
PrimeFaces.current().executeScript("PF('dataEditorSavingResultDialog').show();");
} catch (DAOException e) {
Helper.setErrorMessage("errorSaving", new Object[] {ObjectType.USER.getTranslationSingular() }, logger, e);
}
} else {
// should never happen any more, since layout settings are always created (even outside of task context)
logger.error("Could not save DataEditorSettings with userId {} and templateTaskId {}", user.getId(),
templateTaskId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ public boolean areDataEditorSettingsDefinedForWorkflow(Workflow workflow) {

/**
* Delete data editor settings identified by task id.
* @param taskId ID of the associated task
* @param taskId ID of the associated task (or null for task-independent default layout)
* @throws DAOException if data editor setting could not be deleted from database
*
*/
public void removeFromDatabaseByTaskId(int taskId) throws DAOException {
public void removeFromDatabaseByTaskId(Integer taskId) throws DAOException {
List<DataEditorSetting> dataEditorSettings = getByTaskId(taskId);
for (DataEditorSetting dataEditorSetting: dataEditorSettings) {
dao.remove(dataEditorSetting.getId());
Expand All @@ -102,30 +102,41 @@ public void removeFromDatabaseByTaskId(int taskId) throws DAOException {

/**
* Retrieve data editor settings by task id.
* @param taskId ID of the task
* @param taskId ID of the task (or null for task-independent default layout)
*
* @return List of DataEditorSetting objects
*/
public List<DataEditorSetting> getByTaskId(int taskId) {
public List<DataEditorSetting> getByTaskId(Integer taskId) {
Map<String, Object> parameterMap = new HashMap<>();
parameterMap.put("taskId", taskId);
return getByQuery("FROM DataEditorSetting WHERE task_id = :taskId ORDER BY id ASC", parameterMap);
if (Objects.nonNull(taskId)) {
parameterMap.put("taskId", taskId);
return getByQuery("FROM DataEditorSetting WHERE task_id = :taskId ORDER BY id ASC", parameterMap);
}
return getByQuery("FROM DataEditorSetting WHERE task_id is NULL ORDER BY id ASC", parameterMap);
}

private List<DataEditorSetting> getByUserAndTask(int userId, int taskId) {
private List<DataEditorSetting> getByUserAndTask(int userId, Integer taskId) {
Map<String, Object> parameterMap = new HashMap<>();
parameterMap.put("userId", userId);
parameterMap.put("taskId", taskId);
return getByQuery("FROM DataEditorSetting WHERE user_id = :userId AND task_id = :taskId ORDER BY id ASC", parameterMap);
if (Objects.nonNull(taskId)) {
parameterMap.put("taskId", taskId);
return getByQuery(
"FROM DataEditorSetting WHERE user_id = :userId AND task_id = :taskId ORDER BY id ASC", parameterMap
);
}
return getByQuery(
"FROM DataEditorSetting WHERE user_id = :userId AND task_id IS NULL ORDER BY id ASC", parameterMap
);
}

/**
* Load DataEditorSetting from database or return null if no entry matches the specified ids.
* @param userId id of the user
* @param taskId id of the corresponding template task for the task that is currently edited
* @param taskId id of the corresponding template task for the task that is currently edited
* (or null for task-independent default layout)
* @return settings for the data editor
*/
public DataEditorSetting loadDataEditorSetting(int userId, int taskId) {
public DataEditorSetting loadDataEditorSetting(int userId, Integer taskId) {
List<DataEditorSetting> results = getByUserAndTask(userId, taskId);
if (Objects.nonNull(results) && !results.isEmpty()) {
return results.get(0);
Expand Down
7 changes: 4 additions & 3 deletions Kitodo/src/main/resources/messages/messages_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ dataEditor.addMetadata.newMetadata=Neue Metadaten hinzuf\u00FCgen
dataEditor.addMetadata.noMetadataAddable=Zu dem ausgew\u00E4hlten Strukturelement k\u00F6nnen keine weiteren Metadaten hinzugef\u00FCgt werden!
dataEditor.addMetadata.noMetadataAddableToGroup=Zu dieser Gruppe k\u00F6nnen keine weiteren Metadaten hinzugef\u00FCgt werden!
dataEditor.addMetadata.toGroup=Metadaten zu Gruppe hinzuf\u00FCgen
dataEditor.cannotSaveLayout=Layout kann nur gespeichert werden, wenn der Editor \u00FCber eine Aufgabe ge\u00F6ffnet wird.
dataEditor.childNotContainedError=Elternelement von Struktur {0} enth\u00E4lt die Struktur nicht!
dataEditor.comment.correctionWorkflowAlreadyActive=Dieser Vorgang befindet sich bereits in einem Korrektur-Workflow
dataEditor.comment.firstTaskInWorkflow=Sie k\u00F6nnen f\u00FCr diesen Vorgang derzeit keinen Korrekturkommentar verfassen, da noch keine seiner Aufgaben abgeschlossen sind.
Expand Down Expand Up @@ -309,7 +308,8 @@ dataEditor.galleryStructuredView=Strukturierte Ansicht
dataEditor.galleryDetailView=Detailansicht
dataEditor.invalidMetadataValue=\u201E{0}\u201C kann nicht gespeichert werden: Der Wert ist ung\u00FCltig. Wert: {1}
dataEditor.invalidStructureField=\u201E{0}\u201C kann nicht gespeichert werden: Es gibt kein solches Feld ({1}).
dataEditor.saveLayout=Aktuelle Spaltenaufteilung als Standard f\u00FCr diese Aufgabe speichern
dataEditor.saveLayoutDefault=Aktuelle Spaltenaufteilung als Standard speichern
dataEditor.saveLayoutForTask=Aktuelle Spaltenaufteilung als Standard f\u00FCr diese Aufgabe speichern
dataEditor.mediaNotFound=Es wurden keine Medien gefunden, aber mindestens eine Dateireferenz ist vorhanden. Die automatische Entfernung der fehlenden Medien aus dem Werkst\u00FCck wurde \u00FCbersprungen.
dataEditor.multipleMetadataTasksText=Sie haben mehrere Aufgaben zum Editieren von Metadaten in Bearbeitung. Bitte w\u00E4hlen Sie eine dieser Aufgaben aus!
dataEditor.noParentsError=Elternelement von {0} konnte nicht gefunden werden!
Expand All @@ -323,7 +323,8 @@ dataEditor.position.currentPosition=Aktuelle Position
dataEditor.removeElement.noConsecutivePagesSelected=Strukturelemente k\u00F6nnen nur aus fortlaufenden Medien erstellt werden!
dataEditor.selectMetadataTask=Aufgabe w\u00E4hlen
dataEditor.layoutLoadedSuccessfullyTitle=Metadaten-Editor-Layout geladen
dataEditor.layoutLoadedSuccessfullyText=Spalteneinstellungen f\u00FCr Aufgabe "{0}" erfolgreich geladen
dataEditor.layoutLoadedSuccessfullyDefaultText=Allgemeine Spalteneinstellungen erfolgreich geladen
dataEditor.layoutLoadedSuccessfullyForTaskText=Spalteneinstellungen f\u00FCr Aufgabe "{0}" erfolgreich geladen
dataEditor.layoutSavedSuccessfullyTitle=Aktuelle Spaltenaufteilung erfolgreich gespeichert
dataEditor.layoutSavedSuccessfullyText=Ihre aktuellen Metadaten-Editor-Einstellungen wurden erfolgreich gespeichert! Sie werden f\u00FCr alle zuk\u00FCnftigen Aufgaben dieses Typs wiederverwendet.
dataEditor.renamingMediaComplete=Das Umbenennen der Medien ist abgeschlossen
Expand Down
7 changes: 4 additions & 3 deletions Kitodo/src/main/resources/messages/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ dataEditor.addMetadata.newMetadata=Add new metadata
dataEditor.addMetadata.noMetadataAddable=No additional metadata can be added to this structure!
dataEditor.addMetadata.noMetadataAddableToGroup=No further metadata can be added to this group
dataEditor.addMetadata.toGroup=Add metadata to group
dataEditor.cannotSaveLayout=Layout can only be saved, when the editor is accessed from a task.
dataEditor.childNotContainedError=Parents of structure {0} do not contain structure!
dataEditor.comment.correctionWorkflowAlreadyActive=This process is already in a correction workflow state
dataEditor.comment.firstTaskInWorkflow=Correction comments cannot be created for the first task in a workflow.
Expand Down Expand Up @@ -309,7 +308,8 @@ dataEditor.galleryStructuredView=Structured view
dataEditor.galleryDetailView=Detail view
dataEditor.invalidMetadataValue=Cannot store "{0}": The value is invalid. Value: {1}
dataEditor.invalidStructureField=Cannot save "{0}": There is no such field ({1}).
dataEditor.saveLayout=Save current layout as preset for this task
dataEditor.saveLayoutDefault=Save current layout as general preset
dataEditor.saveLayoutForTask=Save current layout as preset for this task
dataEditor.mediaNotFound=No media found, but at least one file reference is present. The automatic removal of missing media from the workpiece was skipped.
dataEditor.multipleMetadataTasksText=There are multiple metadata editing tasks assigned to you for the current process. Please select the task you want to work on!
dataEditor.noParentsError=No parents of structure {0} found!
Expand All @@ -323,7 +323,8 @@ dataEditor.position.currentPosition=Current position
dataEditor.removeElement.noConsecutivePagesSelected=Select consecutive pages to create structure elements!
dataEditor.selectMetadataTask=Select task
dataEditor.layoutLoadedSuccessfullyTitle=Custom layout loaded
dataEditor.layoutLoadedSuccessfullyText=Custom column settings for task "{0}" loaded successfully
dataEditor.layoutLoadedSuccessfullyDefaultText=Custom column settings loaded successfully
dataEditor.layoutLoadedSuccessfullyForTaskText=Custom column settings for task "{0}" loaded successfully
dataEditor.layoutSavedSuccessfullyTitle=Current layout successfully saved
dataEditor.layoutSavedSuccessfullyText=Your current editor settings have been saved successfully and will be applied to all future tasks of the same type.
dataEditor.renamingMediaComplete=Finished renaming media
Expand Down
9 changes: 6 additions & 3 deletions Kitodo/src/main/resources/messages/messages_es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ dataEditor.addMetadata.newMetadata=Añadir nuevos metadatos
dataEditor.addMetadata.noMetadataAddable=No se pueden añadir más metadatos al elemento de estructura seleccionado.
dataEditor.addMetadata.noMetadataAddableToGroup=No se pueden añadir más metadatos a este grupo.
dataEditor.addMetadata.toGroup=Añadir metadatos al grupo
dataEditor.cannotSaveLayout=El diseño sólo se puede guardar si el editor se abre a través de una tarea.
dataEditor.childNotContainedError=El elemento padre de la estructura {0} no contiene la estructura.
dataEditor.comment.correctionWorkflowAlreadyActive=Esta tarea ya se encuentra en un flujo de trabajo de corrección.
dataEditor.comment.firstTaskInWorkflow=No puede escribir un comentario de corrección para esta tarea en este momento porque ninguna de sus tareas se ha completado todavía.
Expand Down Expand Up @@ -309,7 +308,9 @@ dataEditor.galleryStructuredView=Vista estructurada
dataEditor.galleryDetailView=Vista detallada
dataEditor.invalidMetadataValue=„{0}“ no se puede guardar: El valor no es válido. Valor: {1}
dataEditor.invalidStructureField=„{0}“ no se puede guardar: No existe tal campo ({1}).
dataEditor.saveLayout=Guarda el diseño de columna actual como el predeterminado para esta tarea.
# please check google translation below and remove comment if translation is acceptable
dataEditor.saveLayoutDefault=Guardar el diseño actual como preestablecido general
dataEditor.saveLayoutForTask=Guarda el diseño de columna actual como el predeterminado para esta tarea.
dataEditor.mediaNotFound=No se encontraron los medios, pero al menos un archivo de referencia está presente. La eleminación de los medios faltantes de la pieza de trabajo fue omitida.
dataEditor.multipleMetadataTasksText=Tiene varias tareas de edición de metadatos en curso. Por favor, seleccione una de estas tareas.
dataEditor.noParentsError=No se ha encontrado el elemento padre de {0}.
Expand All @@ -323,7 +324,9 @@ dataEditor.position.currentPosition=Posición actual
dataEditor.removeElement.noConsecutivePagesSelected=Los elementos estructurales sólo pueden crearse a partir de medios continuos.
dataEditor.selectMetadataTask=Seleccionar la tarea
dataEditor.layoutLoadedSuccessfullyTitle=¡La plantilla personificada está cargada!
dataEditor.layoutLoadedSuccessfullyText=La configuración de la columna personalizada para la tarea "{0}" se ha cargado correctamente.
# please check google translation below and remove comment if translation is acceptable
dataEditor.layoutLoadedSuccessfullyDefaultText=La configuración de columna personalizada se cargó correctamente
dataEditor.layoutLoadedSuccessfullyForTaskText=La configuración de la columna personalizada para la tarea "{0}" se ha cargado correctamente.
dataEditor.layoutSavedSuccessfullyTitle=La plantilla actual se guardó correctamente
dataEditor.layoutSavedSuccessfullyText=Su configuración actual del editor ha sido guardada con éxito y será aplicadad a todas las tareas futuras del mismo tipo.
dataEditor.renamingMediaComplete=El cambio de nombre de los medios ha finalizado
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
<h:form id="metadataEditorLayoutForm">
<p:commandButton onclick="saveLayout()"
action="#{DataEditorForm.saveDataEditorSetting()}"
title="#{DataEditorForm.dataEditorSetting ne null ? msgs['dataEditor.saveLayout'] : msgs['dataEditor.cannotSaveLayout']}"
disabled="#{DataEditorForm.dataEditorSetting eq null}"
title="#{DataEditorForm.dataEditorSetting.taskId ne null ? msgs['dataEditor.saveLayoutForTask'] : msgs['dataEditor.saveLayoutDefault']}"
icon="fa fa-wrench"
styleClass="secondary"/>
<h:inputHidden id="structureWidth"
Expand Down
43 changes: 43 additions & 0 deletions Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -330,6 +331,48 @@ public void createStructureElementTest() throws Exception {
assertEquals(structureType, firstChild.getText(), "Added structure element has wrong type!");
}

/**
* Tests that column layout can be saved to database as is loaded into hidden form inputs.
* Does not test whether column layout is actually applied via Javascript (see resize.js).
*/
@Test
public void saveLayoutTest() throws Exception {
login("kowal");

Pages.getProcessesPage().goTo().editMetadata(MockDatabase.MEDIA_RENAMING_TEST_PROCESS_TITLE);

String structureWithId = "metadataEditorLayoutForm:structureWidth";
String metadataWidthId = "metadataEditorLayoutForm:metadataWidth";
String galleryWithId = "metadataEditorLayoutForm:galleryWidth";

Function<String, Double> getValue =
(id) -> Double.parseDouble(Browser.getDriver().findElement(By.id(id)).getAttribute("value"));

Check notice

Code scanning / CodeQL

Missing catch of NumberFormatException Note test

Potential uncaught 'java.lang.NumberFormatException'.

// by default, layout settings are all 0
assertEquals(0.0, getValue.apply(structureWithId));
assertEquals(0.0, getValue.apply(metadataWidthId));
assertEquals(0.0, getValue.apply(galleryWithId));

// save layout settings
Browser.getDriver().findElement(By.cssSelector("#metadataEditorLayoutForm button")).click();

// wait until success message is shown
await().ignoreExceptions().pollDelay(100, TimeUnit.MILLISECONDS).atMost(3, TimeUnit.SECONDS)
.until(Browser.getDriver().findElement(By.id("dataEditorSavingResultDialog_content"))::isDisplayed);

// confirm success message
Browser.getDriver().findElement(By.id("dataEditorSavingResultForm:reload")).click();

// re-open metadata editor
Pages.getMetadataEditorPage().closeEditor();
Pages.getProcessesPage().goTo().editMetadata(MockDatabase.MEDIA_RENAMING_TEST_PROCESS_TITLE);

// verify that layout was saved
assertTrue(0.0 < getValue.apply(structureWithId));
assertTrue(0.0 < getValue.apply(metadataWidthId));
assertTrue(0.0 < getValue.apply(galleryWithId));
}

/**
* Close metadata editor and logout after every test.
* @throws Exception when page navigation fails
Expand Down

0 comments on commit fe6e012

Please sign in to comment.