From 2b6bd522a0e60c24128b08c617d6ad0eba3a921b Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 9 Jun 2025 10:41:13 -0500 Subject: [PATCH 1/6] DeleteConfirmationDialog to check audit event rows and diff count for Sample and Source delete cases --- .../ui/DeleteConfirmationDialog.java | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java index b63cf27c4d..ec3c2f475b 100644 --- a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java +++ b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java @@ -1,15 +1,19 @@ package org.labkey.test.components.ui; import org.jetbrains.annotations.NotNull; +import org.labkey.remoteapi.CommandException; import org.labkey.test.BootstrapLocators; import org.labkey.test.Locator; import org.labkey.test.WebDriverWrapper; +import org.labkey.test.WebTestHelper; import org.labkey.test.components.UpdatingComponent; import org.labkey.test.components.bootstrap.ModalDialog; import org.labkey.test.components.html.Input; +import org.labkey.test.util.AuditLogHelper; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; +import java.io.IOException; import java.util.function.Function; import java.util.function.Supplier; @@ -68,6 +72,28 @@ protected void waitForReady() "The delete confirmation dialog did not become ready.", 1_000); } + public Integer getCountFromTitle() + { + // expecting title to be like "Permanently Delete N Samples/Sources" + String prefix = "Permanently Delete"; + String title = getTitle(); + if (title != null && title.startsWith(prefix)) + { + try + { + title = title.replaceFirst(prefix, "").trim(); + String countString = title.substring(0, title.indexOf(' ')); + return Integer.parseInt(countString); + } + catch (NumberFormatException e) + { + // If we can't parse the number, return null + return null; + } + } + return null; + } + public void cancelDelete() { this.dismiss("Cancel"); @@ -75,12 +101,38 @@ public void cancelDelete() public ConfirmPage confirmDelete() { - return confirmDelete(10); + return confirmDelete(false, 10); } public ConfirmPage confirmDelete(Integer waitSeconds) { - return _confirmationSynchronizationFunction.apply(() -> this.dismiss("Yes, Delete", waitSeconds)); + return confirmDelete(false, waitSeconds); + } + + public ConfirmPage confirmDelete(boolean skipAuditEventCheck, Integer waitSeconds) + { + Integer count = getCountFromTitle(); + String auditEventName = new AuditLogHelper(getWrapper()).getAuditEventNameFromURL(); + + var confirmPage = _confirmationSynchronizationFunction.apply(() -> this.dismiss("Yes, Delete", waitSeconds)); + + if (!skipAuditEventCheck && count != null && auditEventName != null) + verifyAuditEvents(getWrapper(), getWrapper().getCurrentContainerPath(), auditEventName, count); + + return confirmPage; + } + + public static void verifyAuditEvents(WebDriverWrapper wrapper, String containerPath, String auditEventName, int entityCount) + { + try + { + AuditLogHelper auditLogHelper = new AuditLogHelper(wrapper, () -> WebTestHelper.getRemoteApiConnection(false)); + auditLogHelper.checkAuditEventDiffCountForLastTransaction(containerPath, auditEventName, 0, entityCount); + } + catch (CommandException | IOException e) + { + throw new RuntimeException(e); + } } public Boolean isDeleteEnabled() From 5c05e91e7fc5f2af5f94cd2e5b30cf00a8183842 Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 9 Jun 2025 16:38:08 -0500 Subject: [PATCH 2/6] move getCountFromTitle() up to ModalDialog to be shared --- .../components/bootstrap/ModalDialog.java | 20 ++++++++++++++ .../ui/DeleteConfirmationDialog.java | 26 ++----------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/org/labkey/test/components/bootstrap/ModalDialog.java b/src/org/labkey/test/components/bootstrap/ModalDialog.java index a4cf5d5b2d..673e93d44e 100644 --- a/src/org/labkey/test/components/bootstrap/ModalDialog.java +++ b/src/org/labkey/test/components/bootstrap/ModalDialog.java @@ -84,6 +84,26 @@ public String getBodyText() return elementCache().body.getText(); } + public Integer getCountFromTitle(String prefix) + { + String title = getTitle(); + if (title != null && title.startsWith(prefix)) + { + try + { + title = title.replaceFirst(prefix, "").trim(); + String countString = title.substring(0, title.indexOf(' ')); + return Integer.parseInt(countString); + } + catch (NumberFormatException e) + { + // If we can't parse the number, return null + return null; + } + } + return null; + } + public void close() { elementCache().closeButton.click(); diff --git a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java index ec3c2f475b..758aa0288b 100644 --- a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java +++ b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java @@ -72,28 +72,6 @@ protected void waitForReady() "The delete confirmation dialog did not become ready.", 1_000); } - public Integer getCountFromTitle() - { - // expecting title to be like "Permanently Delete N Samples/Sources" - String prefix = "Permanently Delete"; - String title = getTitle(); - if (title != null && title.startsWith(prefix)) - { - try - { - title = title.replaceFirst(prefix, "").trim(); - String countString = title.substring(0, title.indexOf(' ')); - return Integer.parseInt(countString); - } - catch (NumberFormatException e) - { - // If we can't parse the number, return null - return null; - } - } - return null; - } - public void cancelDelete() { this.dismiss("Cancel"); @@ -111,13 +89,13 @@ public ConfirmPage confirmDelete(Integer waitSeconds) public ConfirmPage confirmDelete(boolean skipAuditEventCheck, Integer waitSeconds) { - Integer count = getCountFromTitle(); + Integer count = getCountFromTitle("Permanently Delete"); String auditEventName = new AuditLogHelper(getWrapper()).getAuditEventNameFromURL(); var confirmPage = _confirmationSynchronizationFunction.apply(() -> this.dismiss("Yes, Delete", waitSeconds)); if (!skipAuditEventCheck && count != null && auditEventName != null) - verifyAuditEvents(getWrapper(), getWrapper().getCurrentContainerPath(), auditEventName, count); + verifyAuditEvents(getWrapper(), getWrapper().getCurrentProject(), auditEventName, count); return confirmPage; } From d081f9c7b9e77a1cfa4a9ca51313ac710643ec89 Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 10 Jun 2025 14:19:28 -0500 Subject: [PATCH 3/6] ModalDialog.getCountFromTitle fix for case without number in title --- src/org/labkey/test/components/bootstrap/ModalDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/components/bootstrap/ModalDialog.java b/src/org/labkey/test/components/bootstrap/ModalDialog.java index 673e93d44e..fd6324949e 100644 --- a/src/org/labkey/test/components/bootstrap/ModalDialog.java +++ b/src/org/labkey/test/components/bootstrap/ModalDialog.java @@ -92,7 +92,7 @@ public Integer getCountFromTitle(String prefix) try { title = title.replaceFirst(prefix, "").trim(); - String countString = title.substring(0, title.indexOf(' ')); + String countString = title.contains(" ") ? title.substring(0, title.indexOf(' ')) : title; return Integer.parseInt(countString); } catch (NumberFormatException e) From f8dbe23b3a9f24e46e23f1385d9aa34d7935ae98 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 12 Jun 2025 10:19:56 -0500 Subject: [PATCH 4/6] AuditLogHelper.AuditEvent enum --- .../components/bootstrap/ModalDialog.java | 13 +++--- .../ui/entities/EntityBulkUpdateDialog.java | 2 +- .../components/ui/grids/DetailTableEdit.java | 2 +- src/org/labkey/test/util/AuditLogHelper.java | 44 ++++++++++++++----- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/org/labkey/test/components/bootstrap/ModalDialog.java b/src/org/labkey/test/components/bootstrap/ModalDialog.java index fd6324949e..2afdb12439 100644 --- a/src/org/labkey/test/components/bootstrap/ModalDialog.java +++ b/src/org/labkey/test/components/bootstrap/ModalDialog.java @@ -26,6 +26,8 @@ import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.labkey.test.WebDriverWrapper.WAIT_FOR_JAVASCRIPT; @@ -84,16 +86,15 @@ public String getBodyText() return elementCache().body.getText(); } - public Integer getCountFromTitle(String prefix) + public Integer getCountFromTitle() { - String title = getTitle(); - if (title != null && title.startsWith(prefix)) + Pattern pattern = Pattern.compile("(\\d+)"); + Matcher matcher = pattern.matcher(getTitle()); + if (matcher.find()) { try { - title = title.replaceFirst(prefix, "").trim(); - String countString = title.contains(" ") ? title.substring(0, title.indexOf(' ')) : title; - return Integer.parseInt(countString); + return Integer.parseInt(matcher.group(1).trim()); } catch (NumberFormatException e) { diff --git a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java index 289c1e7007..ef05b771bc 100644 --- a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java +++ b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java @@ -395,7 +395,7 @@ public void clickUpdate(boolean skipAuditEventCheck) // check for the expected number of Data Changes in the latest audit event records AuditLogHelper auditLogHelper = new AuditLogHelper(getWrapper(), () -> WebTestHelper.getRemoteApiConnection(false)); - String auditEventName = auditLogHelper.getAuditEventNameFromURL(); + AuditLogHelper.AuditEvent auditEventName = auditLogHelper.getAuditEventNameFromURL(); if (!skipAuditEventCheck && auditEventName != null && !TestProperties.isTrialServer()) { try diff --git a/src/org/labkey/test/components/ui/grids/DetailTableEdit.java b/src/org/labkey/test/components/ui/grids/DetailTableEdit.java index ec4f0fa610..f153ac42df 100644 --- a/src/org/labkey/test/components/ui/grids/DetailTableEdit.java +++ b/src/org/labkey/test/components/ui/grids/DetailTableEdit.java @@ -511,7 +511,7 @@ public DetailDataPanel clickSave(boolean skipAuditEventCheck) // check for the expected number of Data Changes in the latest audit event records AuditLogHelper auditLogHelper = new AuditLogHelper(getWrapper(), () -> WebTestHelper.getRemoteApiConnection(false)); - String auditEventName = auditLogHelper.getAuditEventNameFromURL(); + AuditLogHelper.AuditEvent auditEventName = auditLogHelper.getAuditEventNameFromURL(); if (!skipAuditEventCheck && auditEventName != null && !TestProperties.isTrialServer()) { try diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 0f0d8f7b57..8914ff5190 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -50,6 +50,28 @@ public AuditLogHelper(WebDriverWrapper wrapper) this(wrapper, wrapper::createDefaultConnection); } + public enum AuditEvent + { + SAMPLE_TIMELINE_EVENT("SampleTimelineEvent"), + SOURCES_AUDIT_EVENT("SourcesAuditEvent"), + INVENTORY_AUDIT_EVENT("InventoryAuditEvent"), + LIST_AUDIT_EVENT("ListAuditEvent"), + EXPERIMENT_AUDIT_EVENT("ExperimentAuditEvent"), + SAMPLE_WORKFLOW_AUDIT_EVENT("SamplesWorkflowAuditEvent"); + + private final String _name; + + AuditEvent(String name) + { + _name = name; + } + + public String getName() + { + return _name; + } + } + public Integer getLatestAuditRowId(String auditTable) throws IOException, CommandException { String rowId = "rowId"; @@ -101,10 +123,10 @@ public DataRegionTable goToAuditEventView(String eventType) * @throws IOException Can be thrown by the SelectRowsCommand. * @throws CommandException Can be thrown by the SelectRowsCommand. */ - public SelectRowsResponse getAuditLogsFromLKS(String containerPath, String auditEventName, List columnNames, + public SelectRowsResponse getAuditLogsFromLKS(String containerPath, AuditEvent auditEventName, List columnNames, List filters, @Nullable Integer maxRows, @Nullable ContainerFilter containerFilter) throws IOException, CommandException { - SelectRowsCommand cmd = new SelectRowsCommand("auditLog", auditEventName); + SelectRowsCommand cmd = new SelectRowsCommand("auditLog", auditEventName.getName()); cmd.setColumns(columnNames); cmd.addFilter("ProjectId/Name", _wrapper.getCurrentProject(), Filter.Operator.EQUAL); filters.forEach(cmd::addFilter); @@ -115,14 +137,14 @@ public SelectRowsResponse getAuditLogsFromLKS(String containerPath, String audit return cmd.execute(_connectionSupplier.get(), containerPath); } - public List> getAuditLogsForTransactionId(String containerPath, String auditEventName, List columnNames, + public List> getAuditLogsForTransactionId(String containerPath, AuditEvent auditEventName, List columnNames, Integer transactionId, @Nullable ContainerFilter containerFilter) throws IOException, CommandException { List transactionFilter = List.of(new Filter("TransactionId", transactionId, Filter.Operator.EQUAL)); return getAuditLogsFromLKS(containerPath, auditEventName, columnNames, transactionFilter, null, containerFilter).getRows(); } - public void checkAuditEventValuesForTransactionId(String containerPath, String auditEventName, Integer transactionId, int rowCount, Map expectedValues) throws IOException, CommandException + public void checkAuditEventValuesForTransactionId(String containerPath, AuditEvent auditEventName, Integer transactionId, int rowCount, Map expectedValues) throws IOException, CommandException { List columnNames = expectedValues.keySet().stream().map(Object::toString).toList(); List> events = getAuditLogsForTransactionId(containerPath, auditEventName, columnNames, transactionId, ContainerFilter.CurrentAndSubfolders); @@ -143,11 +165,11 @@ public void checkTimelineAuditEventDiffCount(String containerPath, List { checkAuditEventDiffCount(containerPath, getAuditEventNameFromURL(), expectedDiffCounts); } - public void checkAuditEventDiffCount(String containerPath, String auditEventName, List expectedDiffCounts) throws IOException, CommandException + public void checkAuditEventDiffCount(String containerPath, AuditEvent auditEventName, List expectedDiffCounts) throws IOException, CommandException { checkAuditEventDiffCount(containerPath, auditEventName, Collections.emptyList(), expectedDiffCounts); } - public void checkAuditEventDiffCount(String containerPath, String auditEventName, List filters, List expectedDiffCounts) throws IOException, CommandException + public void checkAuditEventDiffCount(String containerPath, AuditEvent auditEventName, List filters, List expectedDiffCounts) throws IOException, CommandException { Integer maxRows = expectedDiffCounts.size(); List> events = getAuditLogsFromLKS(containerPath, auditEventName, List.of("InventoryUpdateType", "NewRecordMap"), filters, maxRows, ContainerFilter.CurrentAndSubfolders).getRows(); @@ -171,7 +193,7 @@ public void checkAuditEventDiffCount(String containerPath, String auditEventName } } - public Integer getLastTransactionId(String containerPath, String auditEventName) throws IOException, CommandException + public Integer getLastTransactionId(String containerPath, AuditEvent auditEventName) throws IOException, CommandException { List> events = getAuditLogsFromLKS(containerPath, auditEventName, List.of("TransactionId"), Collections.emptyList(), 1, ContainerFilter.CurrentAndSubfolders).getRows(); return events.size() == 1 ? (Integer) events.get(0).get("TransactionId") : null; @@ -182,7 +204,7 @@ public Integer getLastTransactionId(String containerPath, String auditEventName) * If an expectedEventCount is also provided, it will check that the number of events for that transactionId matches the expectedEventCount. * @return transactionId */ - public Integer checkAuditEventDiffCountForLastTransaction(String containerPath, String auditEventName, int expectedDiffCount, + public Integer checkAuditEventDiffCountForLastTransaction(String containerPath, AuditEvent auditEventName, int expectedDiffCount, @Nullable Integer expectedEventCount) throws IOException, CommandException { Integer transactionId = getLastTransactionId(containerPath, auditEventName); @@ -195,12 +217,12 @@ public Integer checkAuditEventDiffCountForLastTransaction(String containerPath, return transactionId; } - public String getAuditEventNameFromURL() + public AuditEvent getAuditEventNameFromURL() { if (isSamplesRoute()) - return "SampleTimelineEvent"; + return AuditEvent.SAMPLE_TIMELINE_EVENT; else if (isDataClassRoute()) - return "SourcesAuditEvent"; + return AuditEvent.SOURCES_AUDIT_EVENT; return null; } From eb3b50f53f56a946ee6cb4f86b257a1879d73531 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 12 Jun 2025 10:20:45 -0500 Subject: [PATCH 5/6] setSkipAuditEventCheck() instead of a method param --- .../ui/DeleteConfirmationDialog.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java index 758aa0288b..b2e62029bc 100644 --- a/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java +++ b/src/org/labkey/test/components/ui/DeleteConfirmationDialog.java @@ -4,6 +4,7 @@ import org.labkey.remoteapi.CommandException; import org.labkey.test.BootstrapLocators; import org.labkey.test.Locator; +import org.labkey.test.TestProperties; import org.labkey.test.WebDriverWrapper; import org.labkey.test.WebTestHelper; import org.labkey.test.components.UpdatingComponent; @@ -20,6 +21,7 @@ public class DeleteConfirmationDialog extends ModalDialog { private final Function _confirmationSynchronizationFunction; + private boolean skipAuditEventCheck = false; public DeleteConfirmationDialog(@NotNull WebDriverWrapper sourcePage, Supplier confirmPageSupplier) { @@ -72,6 +74,12 @@ protected void waitForReady() "The delete confirmation dialog did not become ready.", 1_000); } + public DeleteConfirmationDialog setSkipAuditEventCheck(boolean skipAuditEventCheck) + { + this.skipAuditEventCheck = skipAuditEventCheck; + return this; + } + public void cancelDelete() { this.dismiss("Cancel"); @@ -79,28 +87,28 @@ public void cancelDelete() public ConfirmPage confirmDelete() { - return confirmDelete(false, 10); + return confirmDelete(10); } public ConfirmPage confirmDelete(Integer waitSeconds) { - return confirmDelete(false, waitSeconds); - } - - public ConfirmPage confirmDelete(boolean skipAuditEventCheck, Integer waitSeconds) - { - Integer count = getCountFromTitle("Permanently Delete"); - String auditEventName = new AuditLogHelper(getWrapper()).getAuditEventNameFromURL(); + Integer count = getCountFromTitle(); + AuditLogHelper.AuditEvent auditEventName = getAuditEvent(); var confirmPage = _confirmationSynchronizationFunction.apply(() -> this.dismiss("Yes, Delete", waitSeconds)); - if (!skipAuditEventCheck && count != null && auditEventName != null) + if (!skipAuditEventCheck && count != null && auditEventName != null && !TestProperties.isTrialServer()) verifyAuditEvents(getWrapper(), getWrapper().getCurrentProject(), auditEventName, count); return confirmPage; } - public static void verifyAuditEvents(WebDriverWrapper wrapper, String containerPath, String auditEventName, int entityCount) + public AuditLogHelper.AuditEvent getAuditEvent() + { + return new AuditLogHelper(getWrapper()).getAuditEventNameFromURL(); + } + + public static void verifyAuditEvents(WebDriverWrapper wrapper, String containerPath, AuditLogHelper.AuditEvent auditEventName, int entityCount) { try { From 1132a942db5c6104bd9ce18cbc43882720b8be24 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 12 Jun 2025 10:21:08 -0500 Subject: [PATCH 6/6] getCountFromTitle() update to use regex pattern --- .../ui/entities/EntityBulkUpdateDialog.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java index ef05b771bc..a5300dca78 100644 --- a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java +++ b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java @@ -350,25 +350,6 @@ public EntityBulkUpdateDialog clearActionComment() return this; } - public Integer getCountFromTitle() - { - // expecting title to be like "Update N items" - String title = getTitle(); - String[] parts = title.split(" "); - if (parts.length > 1) - { - try - { - return Integer.parseInt(parts[1]); - } - catch (NumberFormatException nfe) - { - return null; - } - } - return null; - } - // dismiss the dialog public String clickUpdateExpectingError()