Skip to content

Commit

Permalink
Merge pull request kitodo#6255 from thomaslow/link-pages-over-multipl…
Browse files Browse the repository at this point in the history
…e-hierarchy-levels-fixes-5457

Allow to link page to next element independently of hierarchy level
  • Loading branch information
solth authored Nov 13, 2024
2 parents d9b2746 + 9a16404 commit 4167f83
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1684,7 +1684,7 @@ private boolean physicalNodeStateUnknown(HashMap<PhysicalDivision, Boolean> expa
}

private LogicalDivision getTreeNodeStructuralElement(TreeNode treeNode) {
if (treeNode.getData() instanceof StructureTreeNode) {
if (Objects.nonNull(treeNode) && treeNode.getData() instanceof StructureTreeNode) {
StructureTreeNode structureTreeNode = (StructureTreeNode) treeNode.getData();
if (structureTreeNode.getDataObject() instanceof LogicalDivision) {
return (LogicalDivision) structureTreeNode.getDataObject();
Expand All @@ -1694,7 +1694,7 @@ private LogicalDivision getTreeNodeStructuralElement(TreeNode treeNode) {
}

private PhysicalDivision getTreeNodePhysicalDivision(TreeNode treeNode) {
if (treeNode.getData() instanceof StructureTreeNode) {
if (Objects.nonNull(treeNode) && treeNode.getData() instanceof StructureTreeNode) {
StructureTreeNode structureTreeNode = (StructureTreeNode) treeNode.getData();
if (structureTreeNode.getDataObject() instanceof PhysicalDivision) {
return (PhysicalDivision) structureTreeNode.getDataObject();
Expand All @@ -1703,6 +1703,16 @@ private PhysicalDivision getTreeNodePhysicalDivision(TreeNode treeNode) {
return null;
}

private View getTreeNodeView(TreeNode treeNode) {
if (Objects.nonNull(treeNode) && treeNode.getData() instanceof StructureTreeNode) {
StructureTreeNode structureTreeNode = (StructureTreeNode) treeNode.getData();
if (structureTreeNode.getDataObject() instanceof View) {
return (View) structureTreeNode.getDataObject();
}
}
return null;
}

/**
* Get List of PhysicalDivisions assigned to multiple LogicalDivisions.
*
Expand Down Expand Up @@ -1765,43 +1775,114 @@ public boolean isAssignedSeveralTimes() {
}

/**
* Check if the selected Node's PhysicalDivision can be assigned to the next logical element in addition to the current assignment.
* Find the next logical structure node that can be used to create a new link to the currently selected node.
* The node needs to be the last node amongst its siblings.
*
* @param node the tree node of the currently selected physical devision node
* @return the next logical tree node
*/
private TreeNode findNextLogicalNodeForViewAssignment(TreeNode node) {
if (Objects.isNull(getTreeNodeView(node))) {
// node is not a view
return null;
}

List<TreeNode> viewSiblings = node.getParent().getChildren();
if (viewSiblings.indexOf(node) != viewSiblings.size() - 1) {
// view is not last view amongst siblings
return null;
}

// pseudo-recursively find next logical node
return findNextLogicalNodeForViewAssignmentRecursive(node.getParent());
}

/**
* Find the next logical structure node that can be used to create a link by pseudo-recursively iterating over
* logical parent and logical children nodes.
*
* @param node the tree node of the logical division
* @return the tree node of the next logical division
*/
private TreeNode findNextLogicalNodeForViewAssignmentRecursive(TreeNode node) {
TreeNode current = node;

while (Objects.nonNull(current)) {
if (Objects.isNull(getTreeNodeStructuralElement(current))) {
// node is not a logical node
return null;
}

// check whether next sibling is a logical node as well
List<TreeNode> currentSiblings = current.getParent().getChildren();
int currentIndex = currentSiblings.indexOf(current);

if (currentSiblings.size() > currentIndex + 1) {
TreeNode nextSibling = currentSiblings.get(currentIndex + 1);
if (Objects.isNull(getTreeNodeStructuralElement(nextSibling))) {
// next sibling is not a logical node
return null;
}

// next sibling is a logical node and potential valid result, unless there are children
TreeNode nextLogical = nextSibling;

// check sibling has children (with first child being another logical node)
while (!nextLogical.getChildren().isEmpty()) {
TreeNode firstChild = nextLogical.getChildren().get(0);
if (Objects.isNull(getTreeNodeStructuralElement(firstChild))) {
// first child is not a logical node
return nextLogical;
}
// iterate to child node
nextLogical = firstChild;
}
return nextLogical;
}

// node is last amongst siblings
// iterate to parent node
current = current.getParent();
}
return null;
}

/**
* Check if the selected Node's PhysicalDivision can be assigned to the next logical element in addition to the
* current assignment.
*
* @return {@code true} if the PhysicalDivision can be assigned to the next LogicalDivision
*/
public boolean isAssignableSeveralTimes() {
if (Objects.nonNull(selectedLogicalNode) && selectedLogicalNode.getData() instanceof StructureTreeNode) {
StructureTreeNode structureTreeNode = (StructureTreeNode) selectedLogicalNode.getData();
if (structureTreeNode.getDataObject() instanceof View) {
List<TreeNode> logicalNodeSiblings = selectedLogicalNode.getParent().getParent().getChildren();
int logicalNodeIndex = logicalNodeSiblings.indexOf(selectedLogicalNode.getParent());
List<TreeNode> viewSiblings = selectedLogicalNode.getParent().getChildren();
// check for selected node's positions and siblings after selected node's parent
if (viewSiblings.indexOf(selectedLogicalNode) == viewSiblings.size() - 1
&& logicalNodeSiblings.size() > logicalNodeIndex + 1) {
TreeNode nextSibling = logicalNodeSiblings.get(logicalNodeIndex + 1);
if (nextSibling.getData() instanceof StructureTreeNode) {
StructureTreeNode structureTreeNodeSibling = (StructureTreeNode) nextSibling.getData();
return structureTreeNodeSibling.getDataObject() instanceof LogicalDivision;
TreeNode nextLogical = findNextLogicalNodeForViewAssignment(selectedLogicalNode);
if (Objects.nonNull(nextLogical)) {
// check whether first child is already view of current node (too avoid adding views multiple times)
if (!nextLogical.getChildren().isEmpty()) {
TreeNode childNode = nextLogical.getChildren().get(0);
View childNodeView = getTreeNodeView(childNode);
View selectedView = getTreeNodeView(selectedLogicalNode);
if (Objects.nonNull(childNodeView) && Objects.nonNull(selectedView)) {
if (childNodeView.equals(selectedView)) {
// first child is already a view for the currently selected node
return false;
}
}
}
return true;
}

return false;
}

/**
* Assign selected Node's PhysicalDivision to the next LogicalDivision.
*/
public void assign() {
if (isAssignableSeveralTimes()) {
TreeNode nextLogical = findNextLogicalNodeForViewAssignment(selectedLogicalNode);
if (Objects.nonNull(nextLogical)) {
View view = (View) ((StructureTreeNode) selectedLogicalNode.getData()).getDataObject();
View viewToAssign = new View();
viewToAssign.setPhysicalDivision(view.getPhysicalDivision());
List<TreeNode> logicalNodeSiblings = selectedLogicalNode.getParent().getParent().getChildren();
int logicalNodeIndex = logicalNodeSiblings.indexOf(selectedLogicalNode.getParent());
TreeNode nextSibling = logicalNodeSiblings.get(logicalNodeIndex + 1);
StructureTreeNode structureTreeNodeSibling = (StructureTreeNode) nextSibling.getData();
StructureTreeNode structureTreeNodeSibling = (StructureTreeNode) nextLogical.getData();
LogicalDivision logicalDivision = (LogicalDivision) structureTreeNodeSibling.getDataObject();
dataEditor.assignView(logicalDivision, viewToAssign, 0);
severalAssignments.add(viewToAssign.getPhysicalDivision());
Expand Down
55 changes: 55 additions & 0 deletions Kitodo/src/test/java/org/kitodo/selenium/MetadataST.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.kitodo.selenium.testframework.BaseTestSelenium;
import org.kitodo.selenium.testframework.Browser;
import org.kitodo.selenium.testframework.Pages;
import org.kitodo.selenium.testframework.pages.MetadataEditorPage;
import org.kitodo.test.utils.ProcessTestUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
Expand All @@ -57,15 +58,18 @@ public class MetadataST extends BaseTestSelenium {
private static final String TEST_MEDIA_REFERENCES_FILE = "testUpdatedMediaReferencesMeta.xml";
private static final String TEST_METADATA_LOCK_FILE = "testMetadataLockMeta.xml";
private static final String TEST_RENAME_MEDIA_FILE = "testRenameMediaMeta.xml";
private static final String TEST_LINK_PAGE_TO_NEXT_DIVISION_MEDIA_FILE = "testLinkPageToNextDivisionMeta.xml";
private static int mediaReferencesProcessId = -1;
private static int metadataLockProcessId = -1;
private static int parentProcessId = -1;
private static int renamingMediaProcessId = -1;
private static int dragndropProcessId = -1;
private static int createStructureProcessId = -1;
private static int linkPageToNextDivisionProcessId = -1;
private static final String PARENT_PROCESS_TITLE = "Parent process";
private static final String FIRST_CHILD_PROCESS_TITLE = "First child process";
private static final String SECOND_CHILD_PROCESS_TITLE = "Second child process";
private static final String LINK_PAGE_TO_NEXT_DIVISION_PROCESS_TITLE = "Link page to next division";
private static final String TEST_PARENT_PROCESS_METADATA_FILE = "testParentProcessMeta.xml";
private static final String FIRST_CHILD_ID = "FIRST_CHILD_ID";
private static final String SECOND_CHILD_ID = "SECOND_CHILD_ID";
Expand Down Expand Up @@ -104,6 +108,11 @@ private static void prepareCreateStructureProcess() throws DAOException, DataExc
copyTestFilesForCreateStructure();
}

private static void prepareLinkPageToNextDivision() throws DAOException, DataException, IOException {
linkPageToNextDivisionProcessId = MockDatabase.insertTestProcessIntoSecondProject(LINK_PAGE_TO_NEXT_DIVISION_PROCESS_TITLE);
ProcessTestUtils.copyTestFiles(linkPageToNextDivisionProcessId, TEST_LINK_PAGE_TO_NEXT_DIVISION_MEDIA_FILE);
}

/**
* Prepare tests by inserting dummy processes into database and index for sub-folders of test metadata resources.
* @throws DAOException when saving of dummy or test processes fails.
Expand All @@ -119,6 +128,7 @@ public static void prepare() throws DAOException, DataException, IOException {
prepareMediaRenamingProcess();
prepareDragNDropProcess();
prepareCreateStructureProcess();
prepareLinkPageToNextDivision();
}

/**
Expand Down Expand Up @@ -403,6 +413,50 @@ public void showPhysicalPageNumberBelowThumbnailTest() throws Exception {
assertFalse(Browser.getDriver().findElements(By.cssSelector(".thumbnail-banner")).isEmpty());
}

@Test
public void linkPageToNextDivision() throws Exception {
login("kowal");

// open metadata editor
Pages.getProcessesPage().goTo().editMetadata(LINK_PAGE_TO_NEXT_DIVISION_PROCESS_TITLE);

MetadataEditorPage metaDataEditor = Pages.getMetadataEditorPage();

// wait until structure tree is shown
await().ignoreExceptions().pollDelay(100, TimeUnit.MILLISECONDS).atMost(5, TimeUnit.SECONDS)
.until(metaDataEditor::isLogicalTreeVisible);

// check page "2" is not marked as "linked"
assertFalse(metaDataEditor.isStructureTreeNodeAssignedSeveralTimes("0_0_0_0"));

// open context menu for page "2"
metaDataEditor.openContextMenuForStructureTreeNode("0_0_0_0");

// click on 2nd menu entry "assign to next element"
metaDataEditor.clickStructureTreeContextMenuEntry(2);

// verify page "2" is now marked as "linked"
assertTrue(metaDataEditor.isStructureTreeNodeAssignedSeveralTimes("0_0_0_0"));

// verify linked page "2" was created at correct tree position
assertTrue(metaDataEditor.isStructureTreeNodeAssignedSeveralTimes("0_1_0_0"));

// check page "3" was moved to be 2nd sibling
assertFalse(metaDataEditor.isStructureTreeNodeAssignedSeveralTimes("0_1_0_1"));

// open context menu for linked page "2"
metaDataEditor.openContextMenuForStructureTreeNode("0_1_0_0");

// click on 2nd menu entry "remove assignment"
metaDataEditor.clickStructureTreeContextMenuEntry(2);

// check page "2" is not marked as "linked" any more
assertFalse(metaDataEditor.isStructureTreeNodeAssignedSeveralTimes("0_0_0_0"));

// check page "3" is now only child of folder again
assertTrue(Browser.getDriver().findElements(By.cssSelector("#logicalTree\\:0_1_0_1")).isEmpty());
}

/**
* Verifies that an image can be openend in a separate window by clicking on the corresponding
* context menu item of the first logical tree node.
Expand Down Expand Up @@ -487,6 +541,7 @@ public static void cleanup() throws DAOException, CustomResponseException, DataE
ProcessService.deleteProcess(renamingMediaProcessId);
ProcessService.deleteProcess(dragndropProcessId);
ProcessService.deleteProcess(createStructureProcessId);
ProcessService.deleteProcess(linkPageToNextDivisionProcessId);
}

private void login(String username) throws InstantiationException, IllegalAccessException, InterruptedException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ public class MetadataEditorPage extends Page<MetadataEditorPage> {
@FindBy(id = "renamingMediaResultForm:okSuccess")
private WebElement okButtonRenameMediaFiles;

@FindBy(id = "contextMenuLogicalTree")
private WebElement contextMenuLogicalTree;

@FindBy(id = "imagePreviewForm:previewButton")
private WebElement imagePreviewButton;

Expand All @@ -94,6 +97,10 @@ public boolean isStructureTreeFormVisible() {
return structureTreeForm.isDisplayed();
}

public boolean isLogicalTreeVisible() {
return logicalTree.isDisplayed();
}

/**
* Gets numberOfScans.
*
Expand Down Expand Up @@ -227,10 +234,51 @@ public long getNumberOfDisplayedStructureElements() {
}

/**
* Open context menu (right click) for specific structure tree node.
*
* @param nodeId the tree node id describing the node in the tree (e.g., "0_1_0_1")
*/
public void openContextMenuForStructureTreeNode(String nodeId) {
WebElement treeNode = Browser.getDriver().findElement(By.cssSelector(
"#logicalTree\\:" + nodeId + " .ui-treenode-content"
));
new Actions(Browser.getDriver()).contextClick(treeNode).build().perform();
await().ignoreExceptions().pollDelay(100, TimeUnit.MILLISECONDS).atMost(5, TimeUnit.SECONDS).until(
() -> contextMenuLogicalTree.isDisplayed()
);
}

/**
* Click on a menu entry in the structure tree context menu.
*
* @param menuEntry the menu entry index (starting with 1)
*/
public void clickStructureTreeContextMenuEntry(int menuEntry) {
// click on menu entry
contextMenuLogicalTree.findElement(By.cssSelector(
".ui-menuitem:nth-child(" + menuEntry + ") .ui-menuitem-link"
)).click();
// wait for context menu to disappear
await().ignoreExceptions().pollDelay(100, TimeUnit.MILLISECONDS).atMost(5, TimeUnit.SECONDS)
.until(() -> !contextMenuLogicalTree.isDisplayed());
}

/**
* Check if a structure tree node is marked as "assigned several times".
*
* @param nodeId the tree node id describing the node in the tree (e.g., "0_1_0_1")
* @return true if "assigned several times"
*/
public Boolean isStructureTreeNodeAssignedSeveralTimes(String nodeId) {
return !Browser.getDriver().findElements(By.cssSelector(
"#logicalTree\\:" + nodeId + " .assigned-several-times"
)).isEmpty();
}

/*
* Open detail view by clicking on image preview button.
*/
public void openDetailView() {
imagePreviewButton.click();
}

}
Loading

0 comments on commit 4167f83

Please sign in to comment.