From c0197fa8b8c2af3832110c579d058a00453e4afd Mon Sep 17 00:00:00 2001 From: vagisha Date: Tue, 18 Nov 2025 12:48:59 -0800 Subject: [PATCH 1/2] Remove public account details from the "Download Data" webpart (#579) - Removed public account details from the "Download Data" webpart - Added text about WebDAV downloads requiring an account on PanoramaWeb. - Added a note about contacting the PanoramaWeb team before initiating very large downloads via WebDAV --- .../view/publish/dataDownloadInfo.jsp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/panoramapublic/src/org/labkey/panoramapublic/view/publish/dataDownloadInfo.jsp b/panoramapublic/src/org/labkey/panoramapublic/view/publish/dataDownloadInfo.jsp index 878b9c42..7c141e69 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/view/publish/dataDownloadInfo.jsp +++ b/panoramapublic/src/org/labkey/panoramapublic/view/publish/dataDownloadInfo.jsp @@ -23,17 +23,20 @@ %>

Select one or more files or folders in the browser above and click the download icon ( ). +
+
Data can also be downloaded by mapping this folder as a network drive in Windows Explorer, or by using a <%=simpleLink("WebDAV", "https://en.wikipedia.org/wiki/WebDAV")%> client such as <%=simpleLink("CyberDuck", "https://cyberduck.io")%> or <%=simpleLink("WinSCP", "https://winscp.net/eng/docs/introduction")%>. - For details look at <%=simpleLink("Download data from Panorama Public", downloadDataDocHref)%>. - Use the following URL, login email and password to connect to this folder: -
+ WebDAV downloads require an account on the PanoramaWeb server. Information on obtaining an account and other download options + is available on the <%=simpleLink("Download data from Panorama Public", downloadDataDocHref)%> help page. + Use the following URL to connect to this folder for WebDAV downloads:
URL: <%=h(webdavUrl)%>
- Login email: <%=h(publicDataUser.getEmail())%>
- Password: <%=h(publicDataUser.getPassword())%> + Note: If you plan to download large volumes of data or datasets from multiple projects via WebDAV, + please contact the PanoramaWeb team in advance so that we can coordinate the download to minimize load on the server + and ensure reliable access.

From 4a20b8dd34dcc915de5b7cf287d9b5da4736726b Mon Sep 17 00:00:00 2001 From: vagisha Date: Thu, 20 Nov 2025 11:17:24 -0800 Subject: [PATCH 2/2] PanoramaPublic bug fixes (#580) - Do not send private data reminders for datasets that may have been resubmitted and are pending copy. - Fix NPE in `PrivateDataReminderJob` when a submitted experiment has no associated `announcementId`. - Allow re-submitting data that is already public but not yet published in a peer-reviewed journal e.g., manuscript is on pre-print servers like bioRxiv or medRxiv. - Removed logic that prevented making data public when full publication details were entered at submission time but the data was kept private. - Include experiment title in the private data reminder message - Updated `PanoramaPublicMakePublicTest` to verify that data pending copy cannot be made public by entering the MakePublicAction URL in the browser --- .../PanoramaPublicController.java | 24 +++--- .../PanoramaPublicNotification.java | 15 ++-- .../model/ExperimentAnnotations.java | 9 +- .../model/JournalSubmission.java | 15 +++- .../PostPanoramaPublicMessageJob.java | 3 +- .../pipeline/PrivateDataReminderJob.java | 9 +- .../proteomexchange/PxHtmlWriter.java | 2 +- .../view/expannotations/experimentDetails.jsp | 2 +- .../TargetedMsExperimentWebPart.java | 24 ++++++ .../PanoramaPublicMakePublicTest.java | 86 +++++++++++++++++-- 10 files changed, 155 insertions(+), 34 deletions(-) diff --git a/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicController.java b/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicController.java index fe84ac06..18a51ea2 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicController.java +++ b/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicController.java @@ -6876,6 +6876,11 @@ private ExperimentAnnotations getCopiedExperimentFor(ExperimentAnnotations expAn return null; } + if (_journalSubmission.hasPendingSubmission()) + { + errors.reject(ERROR_MSG, String.format("There is a pending re-submit request for this experiment on '%s'", _journal.getName())); + return null; + } if (expAnnot.isJournalCopy()) { if (!_journalSubmission.isLatestExperimentCopy(_expAnnot.getId())) @@ -7007,12 +7012,10 @@ public void validateCommand(PublicationDetailsForm form, Errors errors) return; } } - if (!form.hasNewPublicationDetails(_copiedExperiment)) - { - errors.reject(ERROR_MSG, String.format("Publication details are the same as the ones associated with the data on %s at %s", - _journal.getName(), _copiedExperiment.getShortUrl().renderShortURL())); - return; - } + // Validation removed: no need to ensure that publication details entered in the form differ from the + // publication details already associated with the experiment. The user may have entered the + // correct PubMedID etc. when submitting but forgot to make the data public. + // (See commit history for the original logic.) } else if (_copiedExperiment.isPublic()) { @@ -7356,13 +7359,6 @@ public boolean hasLinkAndCitation() { return !(StringUtils.isBlank(_link) || StringUtils.isBlank(_citation)); } - - public boolean hasNewPublicationDetails(ExperimentAnnotations copiedExperiment) - { - return !(Objects.equals(_pubmedId, copiedExperiment.getPubmedId()) - && Objects.equals(_link, copiedExperiment.getPublicationLink()) - && Objects.equals(_citation, copiedExperiment.getCitation())); - } } public static abstract class PanoramaPublicExperimentAction extends FormViewAction @@ -9849,7 +9845,7 @@ private MessageExampleBean createExampleMessage(List experimentIds, Pan { continue; } - Announcement announcement = announcementSvc.getAnnouncement(announcementsContainer, getUser(), submission.getAnnouncementId()); + Announcement announcement = submission.getAnnouncement(announcementSvc, announcementsContainer, getUser()); if (announcement == null) { continue; // old data before we started posting submission requests to a message board diff --git a/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicNotification.java b/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicNotification.java index 2bb66369..bb79789c 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicNotification.java +++ b/panoramapublic/src/org/labkey/panoramapublic/PanoramaPublicNotification.java @@ -396,14 +396,15 @@ public static String getDataStatusReminderMessage(@NotNull ExperimentAnnotations message.append("Dear ").append(getUserName(submitter)).append(",").append(NL2) .append("We are reaching out regarding your data on Panorama Public (").append(shortUrl).append("), which has been private since ") .append(dateString).append(".") - .append("\n\n**Is the paper associated with this work already published?**") - .append("\n- If yes: Please make your data public by clicking the \"Make Public\" button in your folder or by clicking this link: ") + .append(NL2).append(bold("Title:")).append(" ").append(escape(exptAnnotations.getTitle())) + .append(NL2).append(bold("Is the paper associated with this work already published?")) + .append(NL).append("- If yes: Please make your data public by clicking the \"Make Public\" button in your folder or by clicking this link: ") .append(bold(link("Make Data Public", makePublicLink))) .append(". This helps ensure that your valuable research is easily accessible to the community.") - .append("\n- If not: You have a couple of options:") - .append("\n - **Request an Extension** - If your paper is still under review, or you need additional time, please let us know by clicking ") + .append(NL).append("- If not: You have a couple of options:") + .append(NL).append(" - ").append(bold("Request an Extension")).append(" - If your paper is still under review, or you need additional time, please let us know by clicking ") .append(bold(link("Request Extension", requestExtensionUrl.getURIString()))).append(".") - .append("\n - **Delete from Panorama Public** - If you no longer wish to host your data on Panorama Public, please click ") + .append(NL).append(" - ").append(bold("Delete from Panorama Public")).append(" - If you no longer wish to host your data on Panorama Public, please click ") .append(bold(link("Request Deletion", requesDeletionUrl.getURIString()))).append(". ") .append("We will remove your data from Panorama Public."); if (sourceExperiment != null) @@ -413,9 +414,9 @@ public static String getDataStatusReminderMessage(@NotNull ExperimentAnnotations .append(") will remain intact, allowing you to resubmit your data in the future if you wish."); } - message.append("\n\nIf you have any questions or need further assistance, please do not hesitate to respond to this message by ") + message.append(NL2).append("If you have any questions or need further assistance, please do not hesitate to respond to this message by ") .append(bold(link("clicking here", respondToMessageUrl.getURIString()))).append(".") - .append("\n\nThank you for sharing your research on Panorama Public. We appreciate your commitment to open science and your contributions to the research community.") + .append(NL2).append("Thank you for sharing your research on Panorama Public. We appreciate your commitment to open science and your contributions to the research community.") .append(NL2).append("Best regards,") .append(NL).append(getUserName(journalAdmin)); return message.toString(); diff --git a/panoramapublic/src/org/labkey/panoramapublic/model/ExperimentAnnotations.java b/panoramapublic/src/org/labkey/panoramapublic/model/ExperimentAnnotations.java index 9696b75c..a47a4ae6 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/model/ExperimentAnnotations.java +++ b/panoramapublic/src/org/labkey/panoramapublic/model/ExperimentAnnotations.java @@ -224,6 +224,11 @@ public List getInstruments() return instruments; } + public String getInstrumentsCommaSeparated() + { + return StringUtils.join(getInstruments(), ", "); + } + public void setInstrument(String instrument) { _instrument = instrument; @@ -504,12 +509,12 @@ public DataLicense getDataLicense() /** * Returns true if the experiment is in an 'Experimental Data' folder that is public and the experiment is - * associated with a published paper. + * associated with a peer-reviewed paper (excludes biorxiv and medrxiv preprint servers). */ public boolean isFinal() { TargetedMSService.FolderType folderType = TargetedMSService.get().getFolderType(getContainer()); - return Experiment.equals(folderType) && isPublic() && isPublished(); + return Experiment.equals(folderType) && isPublic() && isPeerReviewed(); } public boolean hasCompletePublicationInfo() diff --git a/panoramapublic/src/org/labkey/panoramapublic/model/JournalSubmission.java b/panoramapublic/src/org/labkey/panoramapublic/model/JournalSubmission.java index 5760be33..8151aea3 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/model/JournalSubmission.java +++ b/panoramapublic/src/org/labkey/panoramapublic/model/JournalSubmission.java @@ -2,6 +2,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.labkey.api.announcements.api.Announcement; +import org.labkey.api.announcements.api.AnnouncementService; +import org.labkey.api.data.Container; +import org.labkey.api.security.User; import org.labkey.api.view.ShortURLRecord; import org.labkey.panoramapublic.query.SubmissionManager; @@ -69,11 +73,20 @@ public int getModifiedBy() return _journalExperiment.getModifiedBy(); } - public Integer getAnnouncementId() + public @Nullable Integer getAnnouncementId() { return _journalExperiment.getAnnouncementId(); } + public @Nullable Announcement getAnnouncement(@NotNull AnnouncementService announcementSvc, + @NotNull Container announcementsContainer, + @NotNull User user) + { + return (getAnnouncementId() != null) + ? announcementSvc.getAnnouncement(announcementsContainer, user, getAnnouncementId()) + : null; // old data before we started posting submission requests to a message board + } + public Integer getReviewerId() { return _journalExperiment.getReviewer(); diff --git a/panoramapublic/src/org/labkey/panoramapublic/pipeline/PostPanoramaPublicMessageJob.java b/panoramapublic/src/org/labkey/panoramapublic/pipeline/PostPanoramaPublicMessageJob.java index f6f0e3a4..31a1f80a 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/pipeline/PostPanoramaPublicMessageJob.java +++ b/panoramapublic/src/org/labkey/panoramapublic/pipeline/PostPanoramaPublicMessageJob.java @@ -103,7 +103,8 @@ private void postMessage() continue; } - Announcement announcement = announcementSvc.getAnnouncement(announcementsContainer, getUser(), submission.getAnnouncementId()); + Announcement announcement = submission.getAnnouncement(announcementSvc, announcementsContainer, getUser()); + if (announcement == null) { getLogger().error("Could not find the message thread for experiment Id: " + experimentAnnotationsId diff --git a/panoramapublic/src/org/labkey/panoramapublic/pipeline/PrivateDataReminderJob.java b/panoramapublic/src/org/labkey/panoramapublic/pipeline/PrivateDataReminderJob.java index 2b5bfe6b..47d9d962 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/pipeline/PrivateDataReminderJob.java +++ b/panoramapublic/src/org/labkey/panoramapublic/pipeline/PrivateDataReminderJob.java @@ -100,6 +100,13 @@ private static ReminderDecision getReminderDecision(@NotNull ExperimentAnnotatio return ReminderDecision.skip("Not the current version of the experiment"); } + // Make sure the data does not have a pending re-submission request + JournalSubmission journalSubmission = SubmissionManager.getSubmissionForJournalCopy(exptAnnotations); + if (journalSubmission != null && journalSubmission.hasPendingSubmission()) + { + return ReminderDecision.skip("Data has a pending re-submission request"); + } + DatasetStatus datasetStatus = DatasetStatusManager.getForExperiment(exptAnnotations); if (datasetStatus != null) { @@ -244,7 +251,7 @@ private void processExperiment(Integer experimentAnnotationsId, ProcessingContex } Container announcementsFolder = context.getAnnouncementsFolder(); - Announcement announcement = context.getAnnouncementService().getAnnouncement(announcementsFolder, getUser(), submission.getAnnouncementId()); + Announcement announcement = submission.getAnnouncement(context.getAnnouncementService(), context.getAnnouncementsFolder(), getUser()); if (announcement == null) { processingResults.addAnnouncementNotFound(experimentAnnotationsId, submission, announcementsFolder); diff --git a/panoramapublic/src/org/labkey/panoramapublic/proteomexchange/PxHtmlWriter.java b/panoramapublic/src/org/labkey/panoramapublic/proteomexchange/PxHtmlWriter.java index 999e45f5..fb6e61b4 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/proteomexchange/PxHtmlWriter.java +++ b/panoramapublic/src/org/labkey/panoramapublic/proteomexchange/PxHtmlWriter.java @@ -300,7 +300,7 @@ void writePublicationList(ExperimentAnnotations experimentAnnotations) HtmlList publicationList = new HtmlList(); if(experimentAnnotations.isPublished()) { - publicationList.addItem("Link: ", experimentAnnotations.getPublicationLink(), false); + publicationList.addItem("Link", experimentAnnotations.getPublicationLink(), false); if(experimentAnnotations.hasPubmedId()) { diff --git a/panoramapublic/src/org/labkey/panoramapublic/view/expannotations/experimentDetails.jsp b/panoramapublic/src/org/labkey/panoramapublic/view/expannotations/experimentDetails.jsp index 7216d8e5..38426cad 100644 --- a/panoramapublic/src/org/labkey/panoramapublic/view/expannotations/experimentDetails.jsp +++ b/panoramapublic/src/org/labkey/panoramapublic/view/expannotations/experimentDetails.jsp @@ -337,7 +337,7 @@
  • Organism: <%=h(annot.getOrganismsNoTaxId())%>
  • <%}%> <%if(annot.getInstrument() != null){%> -
  • Instrument: <%=h(annot.getInstrument())%>
  • +
  • Instrument: <%=h(annot.getInstrumentsCommaSeparated())%>
  • <%}%> <%if(annot.getSpikeIn() != null){%>
  • SpikeIn: diff --git a/panoramapublic/test/src/org/labkey/test/components/panoramapublic/TargetedMsExperimentWebPart.java b/panoramapublic/test/src/org/labkey/test/components/panoramapublic/TargetedMsExperimentWebPart.java index 53d523b5..f610c43c 100644 --- a/panoramapublic/test/src/org/labkey/test/components/panoramapublic/TargetedMsExperimentWebPart.java +++ b/panoramapublic/test/src/org/labkey/test/components/panoramapublic/TargetedMsExperimentWebPart.java @@ -2,10 +2,16 @@ import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; +import org.labkey.test.WebTest; +import org.labkey.test.WebTestHelper; import org.labkey.test.components.BodyWebPart; import org.labkey.test.util.DataRegionTable; import org.openqa.selenium.WebElement; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -83,6 +89,24 @@ public void clickMakePublic() clickLink(elementCache().makePublicButton, "Expected to see a \"Make Public\" button"); } + public Integer getExperimentAnnotationsId() + { + WebElement moreDetailsLink = elementCache().moreDetailsLink; + if (moreDetailsLink != null) + { + String href = moreDetailsLink.getAttribute("href"); + try + { + return Integer.parseInt(WebTestHelper.parseUrlQuery(new URL(href)).get("id")); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + } + return null; + } + public void clickAddPublication() { clickLink(elementCache().addPublicationButton, "Expected to see a \"Add Publication\" button"); diff --git a/panoramapublic/test/src/org/labkey/test/tests/panoramapublic/PanoramaPublicMakePublicTest.java b/panoramapublic/test/src/org/labkey/test/tests/panoramapublic/PanoramaPublicMakePublicTest.java index 4abe965b..4fd7ada0 100644 --- a/panoramapublic/test/src/org/labkey/test/tests/panoramapublic/PanoramaPublicMakePublicTest.java +++ b/panoramapublic/test/src/org/labkey/test/tests/panoramapublic/PanoramaPublicMakePublicTest.java @@ -18,11 +18,13 @@ import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @Category({External.class, MacCossLabModules.class}) @@ -45,24 +47,38 @@ public void testExperimentCopy() verifyIsPublicColumn(PANORAMA_PUBLIC, experimentTitle, false); verifyPermissions(projectName, folderName, PANORAMA_PUBLIC, targetFolder); - // Verify that the submitter can make the data public - verifyMakePublic(PANORAMA_PUBLIC, targetFolder, SUBMITTER, true); // Verify that a folder admin in the source folder, who is not the submitter or lab head will not see the // "Make Public" button in the Panorama Public copy. - verifyMakePublic(PANORAMA_PUBLIC, targetFolder, ADMIN_2, false); + verifyMakePublicButtonIsNotVisible(PANORAMA_PUBLIC, targetFolder, ADMIN_2, + "Make Public button should not be visible in the Panorama Public copy to a user who is neither submitter nor lab head"); + + // Verify that the submitter can make the data public + verifyMakePublic(PANORAMA_PUBLIC, targetFolder, SUBMITTER, true); // Resubmit the folder. This is still possible since the Panorama Public copy is not yet associated with a publication. resubmitFolder(projectName, folderName, SUBMITTER, true); + // Data copy is pending. Verify that the "Make Public" button in not visible in the source folder or the copied folder + verifyMakePublicButtonIsNotVisible(PANORAMA_PUBLIC, targetFolder, SUBMITTER, + "Make Public button should not be visible in the Panorama Public copy if data copy is pending"); + verifyMakePublicButtonIsNotVisible(projectName, folderName, SUBMITTER, + "Make Public button should not be visible in the source folder if data copy is pending"); + + // Verify that the submitter cannot enter the MakePublicAction URL in the browser to make the data public + verifyCannotMakePublicPendingResubmit(PANORAMA_PUBLIC, targetFolder, SUBMITTER); // In the target folder + verifyCannotMakePublicPendingResubmit(projectName, folderName, SUBMITTER); // In the source folder + // Re-copy the experiment to the Panorama Public project. Do not delete the previous copy makeCopy(shortAccessUrl, experimentTitle, targetFolder, true, false); // Verify that the "Make Public button is not visible in the older copy of the data. String v1Folder = targetFolder + " V.1"; - verifyMakePublic(PANORAMA_PUBLIC, v1Folder, SUBMITTER, true); - // Verify that the submitter can make data public, and add publication details + verifyMakePublicButtonIsNotVisible(PANORAMA_PUBLIC, v1Folder, SUBMITTER, + "Make Public button should not be visible in an older copy of the data"); + verifyCannotMakePublicOldCopy(PANORAMA_PUBLIC, v1Folder, SUBMITTER); + + // Verify that the submitter can make the latest copy of the data public, and add publication details verifyMakePublic(PANORAMA_PUBLIC, targetFolder, SUBMITTER, true, true); - verifyMakePublic(PANORAMA_PUBLIC, v1Folder, ADMIN_2, false); verifyIsPublicColumn(PANORAMA_PUBLIC, experimentTitle, true); @@ -281,6 +297,64 @@ private void verifyMakePublic(String projectName, String folderName, String user stopImpersonating(); } + private void verifyMakePublicButtonVisible(boolean expectVisible, String projectName, String folderName, String user, String errorMessage) + { + if (isImpersonating()) + { + stopImpersonating(true); + } + goToProjectFolder(projectName, folderName); + impersonate(user); + goToDashboard(); + TargetedMsExperimentWebPart expWebPart = new TargetedMsExperimentWebPart(this); + + if (expectVisible) + { + assertTrue(errorMessage, expWebPart.hasMakePublicButton()); + } + else + { + assertFalse(errorMessage, expWebPart.hasMakePublicButton()); + } + } + + private void verifyMakePublicButtonIsVisible(String projectName, String folderName, String user, String errorMessage) + { + verifyMakePublicButtonVisible(true, projectName, folderName, user, errorMessage); + } + + private void verifyMakePublicButtonIsNotVisible(String projectName, String folderName, String user, String errorMessage) + { + verifyMakePublicButtonVisible(false, projectName, folderName, user, errorMessage); + } + + private void verifyCannotMakePublicOldCopy(String projectName, String folderName, String user) + { + verifyCannotMakePublic(projectName, folderName, user, "not the most recent copy of the data"); + } + + private void verifyCannotMakePublicPendingResubmit(String projectName, String folderName, String user) + { + verifyCannotMakePublic(projectName, folderName, user, "There is a pending re-submit request for this experiment"); + } + + private void verifyCannotMakePublic(String projectName, String folderName, String user, String expectedMessage) + { + if (isImpersonating()) + { + stopImpersonating(true); + } + goToProjectFolder(projectName, folderName); + impersonate(user); + goToDashboard(); + TargetedMsExperimentWebPart expWebPart = new TargetedMsExperimentWebPart(this); + Integer expAnnotationsId = expWebPart.getExperimentAnnotationsId(); + assertNotNull(expAnnotationsId); + beginAt(WebTestHelper.buildURL("panoramapublic", getCurrentContainerPath(), "makePublic", Map.of("id", expAnnotationsId))); + waitForText(expectedMessage); + } + + private void makeDataPublic() { makeDataPublic(true);