From fbf365a5748b4df19bdc9fc13890634bff2d5e06 Mon Sep 17 00:00:00 2001
From: Adam Rauch
Date: Thu, 11 Jun 2026 12:36:42 -0700
Subject: [PATCH 1/7] Modernize specimen, report, and messages rendering
---
.../AnnouncementsController.java | 6 +-
.../specimen/actions/SpecimenController.java | 12 ++--
.../specimen/actions/SpecimenHeaderBean.java | 26 ++++----
.../specimen/view/manageRequirement.jsp | 2 +-
.../labkey/specimen/view/specimenHeader.jsp | 61 +++++++++++--------
.../reports/ReportsController.java | 3 +-
6 files changed, 60 insertions(+), 50 deletions(-)
diff --git a/announcements/src/org/labkey/announcements/AnnouncementsController.java b/announcements/src/org/labkey/announcements/AnnouncementsController.java
index a52c4c4278e..1a18dd9c172 100644
--- a/announcements/src/org/labkey/announcements/AnnouncementsController.java
+++ b/announcements/src/org/labkey/announcements/AnnouncementsController.java
@@ -107,11 +107,11 @@
import org.labkey.api.util.DateUtil;
import org.labkey.api.util.GUID;
import org.labkey.api.util.HtmlString;
+import org.labkey.api.util.OptionBuilder;
import org.labkey.api.util.PageFlowUtil;
import org.labkey.api.util.Pair;
-import org.labkey.api.util.URLHelper;
-import org.labkey.api.util.OptionBuilder;
import org.labkey.api.util.SelectBuilder;
+import org.labkey.api.util.URLHelper;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.AjaxCompletion;
import org.labkey.api.view.AlwaysAvailableWebPartFactory;
@@ -137,7 +137,6 @@
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -218,7 +217,6 @@ public static ActionURL getBeginURL(Container c)
public static AnnouncementModel copyEditableProps(AnnouncementModel target, AnnouncementModel source, boolean isInsert)
{
- if (source.getApproved() != null) target.setApproved(source.getApproved());
if (source.getAssignedTo() != null) target.setAssignedTo(source.getAssignedTo());
if (source.getBody() != null) target.setBody(source.getBody());
if (source.getExpires() != null) target.setExpires(source.getExpires());
diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenController.java b/specimen/src/org/labkey/specimen/actions/SpecimenController.java
index f3d79169d7e..9d48a30e7c7 100644
--- a/specimen/src/org/labkey/specimen/actions/SpecimenController.java
+++ b/specimen/src/org/labkey/specimen/actions/SpecimenController.java
@@ -5546,6 +5546,8 @@ public void addNavTrail(NavTree root)
}
}
+ public record PtidVisit(String ptid, String visit){}
+
@RequiresPermission(ReadPermission.class)
public class SelectedSpecimensAction extends QueryViewAction
{
@@ -5561,26 +5563,26 @@ public SelectedSpecimensAction()
protected ModelAndView getHtmlView(SpecimenViewTypeForm form, BindException errors) throws Exception
{
Study study = getStudyRedirectIfNull();
- Set> ptidVisits = new HashSet<>();
+ Set ptidVisits = new HashSet<>();
for (ParticipantDataset pd : getFilterPds())
{
if (pd.getSequenceNum() == null)
{
- ptidVisits.add(new Pair<>(pd.getParticipantId(), null));
+ ptidVisits.add(new PtidVisit(pd.getParticipantId(), null));
}
else if (study.getTimepointType() != TimepointType.VISIT && pd.getVisitDate() != null)
{
- ptidVisits.add(new Pair<>(pd.getParticipantId(), DateUtil.formatDate(pd.getContainer(), pd.getVisitDate())));
+ ptidVisits.add(new PtidVisit(pd.getParticipantId(), DateUtil.formatDate(pd.getContainer(), pd.getVisitDate())));
}
else
{
Visit visit = pd.getSequenceNum() != null ? StudyInternalService.get().getVisitForSequence(study, pd.getSequenceNum()) : null;
- ptidVisits.add(new Pair<>(pd.getParticipantId(), visit != null ? visit.getLabel() : "" + StudyInternalService.get().formatSequenceNum(pd.getSequenceNum())));
+ ptidVisits.add(new PtidVisit(pd.getParticipantId(), visit != null ? visit.getLabel() : StudyInternalService.get().formatSequenceNum(pd.getSequenceNum())));
}
}
SpecimenQueryView view = createInitializedQueryView(form, errors, form.getExportType() != null, null);
JspView header = new JspView<>("/org/labkey/specimen/view/specimenHeader.jsp",
- new SpecimenHeaderBean(getViewContext(), view, ptidVisits));
+ new SpecimenHeaderBean(getViewContext(), view, ptidVisits));
return new VBox(header, view);
}
diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenHeaderBean.java b/specimen/src/org/labkey/specimen/actions/SpecimenHeaderBean.java
index fe32d12dc24..fd0cbd575df 100644
--- a/specimen/src/org/labkey/specimen/actions/SpecimenHeaderBean.java
+++ b/specimen/src/org/labkey/specimen/actions/SpecimenHeaderBean.java
@@ -5,13 +5,13 @@
import org.labkey.api.query.FieldKey;
import org.labkey.api.query.QueryService;
import org.labkey.api.specimen.SpecimenQuerySchema;
-import org.labkey.specimen.query.SpecimenQueryView;
import org.labkey.api.study.Study;
import org.labkey.api.study.StudyService;
-import org.labkey.api.util.Pair;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.NotFoundException;
import org.labkey.api.view.ViewContext;
+import org.labkey.specimen.actions.SpecimenController.PtidVisit;
+import org.labkey.specimen.query.SpecimenQueryView;
import java.util.Collections;
import java.util.Iterator;
@@ -24,7 +24,7 @@ public final class SpecimenHeaderBean
private final ActionURL _otherViewURL;
private final ViewContext _viewContext;
private final boolean _showingVials;
- private final Set> _filteredPtidVisits;
+ private final Set _ptidVisits;
private Integer _selectedRequest;
@@ -33,7 +33,7 @@ public SpecimenHeaderBean(ViewContext context, SpecimenQueryView view)
this(context, view, Collections.emptySet());
}
- public SpecimenHeaderBean(ViewContext context, SpecimenQueryView view, Set> filteredPtidVisits) throws RuntimeException
+ public SpecimenHeaderBean(ViewContext context, SpecimenQueryView view, Set ptidVisits) throws RuntimeException
{
Map params = context.getRequest().getParameterMap();
@@ -90,11 +90,11 @@ public SpecimenHeaderBean(ViewContext context, SpecimenQueryView view, Set> getFilteredPtidVisits()
+ public Set getPtidVisits()
{
- return _filteredPtidVisits;
+ return _ptidVisits;
}
public boolean isSingleVisitFilter()
{
- if (getFilteredPtidVisits().isEmpty())
+ if (getPtidVisits().isEmpty())
return false;
- Iterator> visitIt = getFilteredPtidVisits().iterator();
- String firstVisit = visitIt.next().getValue();
- while (visitIt.hasNext())
+ Iterator ptidVisit = getPtidVisits().iterator();
+ String firstVisit = ptidVisit.next().visit();
+ while (ptidVisit.hasNext())
{
- if (!Objects.equals(firstVisit, visitIt.next().getValue()))
+ if (!Objects.equals(firstVisit, ptidVisit.next().visit()))
return false;
}
return true;
diff --git a/specimen/src/org/labkey/specimen/view/manageRequirement.jsp b/specimen/src/org/labkey/specimen/view/manageRequirement.jsp
index 68109262af7..6721bbc61de 100644
--- a/specimen/src/org/labkey/specimen/view/manageRequirement.jsp
+++ b/specimen/src/org/labkey/specimen/view/manageRequirement.jsp
@@ -84,7 +84,7 @@
| Description |
- <%= unsafe(requirement.getDescription()) %> |
+ <%= h(requirement.getDescription()) %> |
<%
if (!bean.isRequestManager())
diff --git a/specimen/src/org/labkey/specimen/view/specimenHeader.jsp b/specimen/src/org/labkey/specimen/view/specimenHeader.jsp
index b0599197eab..f80bcd4ca23 100644
--- a/specimen/src/org/labkey/specimen/view/specimenHeader.jsp
+++ b/specimen/src/org/labkey/specimen/view/specimenHeader.jsp
@@ -15,16 +15,17 @@
* limitations under the License.
*/
%>
-<%@ page import="org.labkey.api.security.permissions.AdminPermission"%>
-<%@ page import="org.labkey.api.study.StudyService"%>
-<%@ page import="org.labkey.api.study.StudyUrls"%>
-<%@ page import="org.labkey.api.util.Pair"%>
+<%@ page import="org.labkey.api.security.permissions.AdminPermission" %>
+<%@ page import="org.labkey.api.study.StudyService" %>
+<%@ page import="org.labkey.api.study.StudyUrls" %>
+<%@ page import="org.labkey.api.util.HtmlStringBuilder" %>
<%@ page import="org.labkey.api.view.ActionURL" %>
<%@ page import="org.labkey.api.view.HttpView" %>
<%@ page import="org.labkey.api.view.JspView" %>
<%@ page import="org.labkey.api.view.template.ClientDependencies" %>
<%@ page import="org.labkey.specimen.actions.ShowSearchAction" %>
<%@ page import="org.labkey.specimen.actions.SpecimenController" %>
+<%@ page import="org.labkey.specimen.actions.SpecimenController.PtidVisit" %>
<%@ page import="org.labkey.specimen.actions.SpecimenController.SpecimensAction" %>
<%@ page import="org.labkey.specimen.actions.SpecimenHeaderBean" %>
<%@ page import="java.util.Iterator" %>
@@ -74,47 +75,57 @@
<%=link("Search", ShowSearchAction.getShowSearchURL(getContainer(), bean.isShowingVials()))%>
<%=link("Reports", urlFor(SpecimenController.AutoReportListAction.class)) %>
<%
- if (!bean.getFilteredPtidVisits().isEmpty())
+ if (!bean.getPtidVisits().isEmpty())
{
// get the first visit label:
- StringBuilder filterString = new StringBuilder();
- filterString.append("This view is displaying specimens only from ");
- boolean usePlural = bean.getFilteredPtidVisits().size() != 1;
+ HtmlStringBuilder builder = HtmlStringBuilder.of()
+ .unsafeAppend("")
+ .append("This view is displaying specimens only from ");
+ boolean usePlural = bean.getPtidVisits().size() != 1;
if (bean.isSingleVisitFilter())
{
- filterString.append(h((usePlural?subjectNounPlural:subjectNounSingle).toLowerCase())).append(" ");
- for (Iterator> it = bean.getFilteredPtidVisits().iterator(); it.hasNext();)
+ builder.append((usePlural ? subjectNounPlural : subjectNounSingle).toLowerCase())
+ .append(" ");
+ for (Iterator it = bean.getPtidVisits().iterator(); it.hasNext();)
{
- String ptid = it.next().getKey();
- filterString.append(ptid);
+ String ptid = it.next().ptid();
+ builder.append(ptid);
if (it.hasNext())
- filterString.append(", ");
+ builder.append(", ");
}
- String visit = bean.getFilteredPtidVisits().iterator().next().getValue();
+ String visit = bean.getPtidVisits().iterator().next().visit();
if (visit != null)
- filterString.append(" at visit ").append(visit);
- filterString.append(".
");
+ builder.append(" at visit ").append(visit);
+
+ builder.append(".")
+ .unsafeAppend("
");
}
else
{
- filterString.append(" the following ").append(h(subjectNounSingle.toLowerCase())).append("/visit ").append(usePlural?"pairs":"pair").append(":
");
- for (Iterator> it = bean.getFilteredPtidVisits().iterator(); it.hasNext();)
+ builder.append(" the following ")
+ .append(subjectNounSingle.toLowerCase())
+ .append("/visit ").append(usePlural ? "pairs" : "pair")
+ .append(":")
+ .unsafeAppend("
");
+ for (Iterator it = bean.getPtidVisits().iterator(); it.hasNext();)
{
- Pair ptidVisit = it.next();
- filterString.append(ptidVisit.getKey()).append("/").append(ptidVisit.getValue());
+ PtidVisit ptidVisit = it.next();
+ builder.append(ptidVisit.ptid())
+ .append("/")
+ .append(ptidVisit.visit());
if (it.hasNext())
- filterString.append(", ");
+ builder.append(", ");
}
- filterString.append(".");
+ builder.append(".");
}
- ActionURL noFitlerUrl = getViewContext().cloneActionURL().setAction(SpecimensAction.class);
+ ActionURL noFilterUrl = getViewContext().cloneActionURL().setAction(SpecimensAction.class);
%>
- | <%= unsafe(filterString.toString()) %> |
+ | <%=builder%> |
-<%= link("Remove " + subjectNounSingle + "/Visit Filter", noFitlerUrl)%><%
+<%= link("Remove " + subjectNounSingle + "/Visit Filter", noFilterUrl)%><%
}
%>
diff --git a/study/src/org/labkey/study/controllers/reports/ReportsController.java b/study/src/org/labkey/study/controllers/reports/ReportsController.java
index e09c6265b59..1ad9f5a1fd5 100644
--- a/study/src/org/labkey/study/controllers/reports/ReportsController.java
+++ b/study/src/org/labkey/study/controllers/reports/ReportsController.java
@@ -49,7 +49,6 @@
import org.labkey.api.reports.report.view.ReportUtil;
import org.labkey.api.reports.report.view.ScriptReportBean;
import org.labkey.api.security.RequiresLogin;
-import org.labkey.api.security.RequiresNoPermission;
import org.labkey.api.security.RequiresPermission;
import org.labkey.api.security.User;
import org.labkey.api.security.permissions.AdminPermission;
@@ -533,7 +532,7 @@ public void addNavTrail(NavTree root)
}
}
- @RequiresNoPermission
+ @RequiresPermission(ReadPermission.class)
public class CreateCrosstabReportAction extends SimpleViewAction