Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
<!-- list of standard tasks (remove this comment to enable)
#### Tasks 📍
- [ ] Manual Testing
- [ ] Needs Automation
- [ ] Test Automation
- [ ] Verify Fix
-->
-->
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ if (!announcementModel.getAttachments().isEmpty())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(announcementModel, d.getName());
%>
<a href="<%=h(downloadURL)%>"><img alt="" src="<%=getWebappURL(d.getFileIcon())%>">&nbsp;<%=h(d.getName())%></a>&nbsp;<%
<%=d.renderDownloadLink(downloadURL)%>&nbsp;<%
} %>
</div></td>
</tr><%
Expand Down Expand Up @@ -210,7 +210,7 @@ if (!announcementModel.getResponses().isEmpty())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(r, rd.getName());
%>
<a href="<%=h(downloadURL)%>"><img alt="" src="<%=getWebappURL(rd.getFileIcon())%>">&nbsp;<%=h(rd.getName())%></a>&nbsp;<%
<%=rd.renderDownloadLink(downloadURL)%>&nbsp;<%
}
%>
</div></td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ for (AnnouncementModel a : bean.announcementModels)
for (Attachment d : a.getAttachments())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(a, d.getName());
%><a href="<%=h(downloadURL)%>"><img src="<%=getWebappURL(d.getFileIcon())%>">&nbsp;<%=h(d.getName())%></a>&nbsp;<%
%><%=d.renderDownloadLink(downloadURL)%>&nbsp;<%
}
%></td></tr><%
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ for (AnnouncementModel a : bean.announcementModels)
for (Attachment d : a.getAttachments())
{
ActionURL downloadURL = AnnouncementsController.getDownloadURL(a, d.getName());
%><a href="<%=h(downloadURL)%>"><img src="<%=getWebappURL(d.getFileIcon())%>">&nbsp;<%=h(d.getName())%></a>&nbsp;<%
%><%=d.renderDownloadLink(downloadURL)%>&nbsp;<%
}
%></td></tr><%
}
Expand Down
1 change: 0 additions & 1 deletion announcements/src/org/labkey/announcements/update.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ if (settings.hasExpires())
<tbody>
<%
int x = -1;
String id;
for (Attachment att : ann.getAttachments())
{
x++;
Expand Down
5 changes: 3 additions & 2 deletions api/src/org/labkey/api/assay/AbstractAssayProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
import javax.script.ScriptEngine;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.sql.ResultSet;
Expand Down Expand Up @@ -1274,9 +1275,9 @@ public Pair<ValidationException, Pair<String, String>> setValidationAndAnalysisS
if (!(engine instanceof ExternalScriptEngine && ((ExternalScriptEngine) engine).isBinary(scriptFile)))
{
String scriptText;
try
try (InputStream is = scriptFile.openInputStream())
{
scriptText = IOUtils.toString(scriptFile.openInputStream(), StringUtilsLabKey.DEFAULT_CHARSET);
scriptText = IOUtils.toString(is, StringUtilsLabKey.DEFAULT_CHARSET);
}
catch (IOException e)
{
Expand Down
29 changes: 29 additions & 0 deletions api/src/org/labkey/api/attachments/Attachment.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
import org.labkey.api.security.User;
import org.labkey.api.security.UserManager;
import org.labkey.api.services.ServiceRegistry;
import org.labkey.api.util.DOM;
import org.labkey.api.util.HtmlString;
import org.labkey.api.util.MemTracker;
import org.labkey.api.util.MimeMap;
import org.labkey.api.util.PageFlowUtil;
import org.labkey.api.util.Path;
import org.labkey.api.view.ActionURL;
import org.labkey.api.view.ViewServlet;
import org.labkey.api.webdav.WebdavResolver;

Expand Down Expand Up @@ -350,4 +354,29 @@ public void setDocumentSize(int documentSize)
{
_documentSize = documentSize;
}

/**
* Returns an HtmlString rendering a download link: an anchor containing a file type icon and the filename.
* The icon is marked aria-hidden since it is decorative; the link text serves as the accessible name.
*/
public HtmlString renderDownloadLink(ActionURL downloadURL)
{
return renderDownloadLink(downloadURL, getName());
}

/**
* Returns an HtmlString rendering a download link: an anchor containing a file type icon and custom link text.
* Use this overload when the visible link label differs from the filename (e.g. "Study Protocol Document").
* The icon is marked aria-hidden since it is decorative; linkText serves as the accessible name.
*/
public HtmlString renderDownloadLink(ActionURL downloadURL, String linkText)
{
return DOM.createHtmlFragment(
DOM.A(DOM.at(DOM.Attribute.href, downloadURL.toString()),
DOM.IMG(DOM.at(DOM.Attribute.alt, "").at(DOM.Attribute.src, PageFlowUtil.staticResourceUrl(getFileIcon()))),
HtmlString.NBSP,
linkText
)
);
}
}
9 changes: 5 additions & 4 deletions api/src/org/labkey/api/mcp/McpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
/// ### MCP Development Guide
/// `McpService` lets you expose functionality over the MCP protocol (only simple http for now). This allows external
/// chat sessions to pull information from LabKey Server. Exposed functionality is also made available to chat sessions
/// hosted by LabKey (see `AbstractAgentAction``).
/// hosted by LabKey (see `AbstractAgentAction`).
///
/// ### Adding a new MCP class
/// 1. Create a new class that implements `McpImpl` (see below) in the appropriate module
/// 2. Register that class in your module `init()` method: `McpService.get().register(new MyMcp())`
/// 2. Register that class in your module's `startup()` method: `McpService.get().register(new MyMcp())`
/// 3. Add tools and resources
///
/// ### Adding a new MCP tool
Expand All @@ -44,13 +44,13 @@
/// permission annotation is required, otherwise your tool will not be registered.**
/// 4. Add `ToolContext` as the first parameter to the method
/// 5. Add additional required or optional parameters to the method signature, as needed. Note that "required" is the
/// default. Again here, the parameter descriptions are very important. Provide examples.
/// default. Again here, the parameter descriptions are very important. Provide examples of parameter values.
/// 6. Use the helper method `getContext(ToolContext)` to retrieve the current `Container` and `User`
/// 7. Use the helper method `getUser(ToolContext)` in the rare cases where you need just a `User`
/// 8. Perform additional permissions checking (beyond what the annotations offer), where appropriate
/// 9. Filter all results to the current container, of course
/// 10. For any error conditions, throw exceptions with detailed information. These will get translated into appropriate
/// failure responses and the LLM client will attempt to correct the problem.
/// failure responses and the LLM client will attempt to correct any problems (hopefully).
/// 11. For success cases, return a String with a message or JSON content, for example, `JSONObject.toString()`. Spring
/// has some limited ability to convert other objects into JSON strings, but we haven't experimented with that. See
/// `DefaultToolCallResultConverter` and the ability to provide a custom result converter via the `@Tool` annotation.
Expand Down Expand Up @@ -126,6 +126,7 @@ static void setInstance(McpService service)

boolean isReady();

// Register MCPs in Module.startup()
default void register(McpImpl mcp)
{
try
Expand Down
37 changes: 23 additions & 14 deletions api/src/org/labkey/api/module/ModuleLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -580,19 +580,6 @@ private void doInit(Execution execution) throws ServletException
throw new IllegalStateException("Core module was not first or could not find the Core module. Ensure that Tomcat user can create directories under the <LABKEY_HOME>/modules directory.");
setProjectRoot(coreModule);

for (Module module : modules)
{
module.registerFilters(_servletContext);
}
for (Module module : modules)
{
module.registerServlets(_servletContext);
}
for (Module module : modules)
{
module.registerFinalServlets(_servletContext);
}

// Do this after we've checked to see if we can find the core module. See issue 22797.
verifyProductionModeMatchesBuild();

Expand Down Expand Up @@ -790,12 +777,34 @@ public void addStaticWarnings(@NotNull Warnings warnings, boolean showAllWarning
if (!modulesRequiringUpgrade.isEmpty() || !additionalSchemasRequiringUpgrade.isEmpty())
setUpgradeState(UpgradeState.UpgradeRequired);

// Don't accept any requests if we're bootstrapping empty schemas or migrating from SQL Server
// Don't accept any requests if we're bootstrapping empty schemas or doing a database migration
if (!shouldInsertData())
execution = Execution.Synchronous;

startNonCoreUpgradeAndStartup(execution, lockFile);

// Register filters and servlets at the last minute, just before Tomcat starts. At this point, the list of
// modules is final. We've had one case where the CSP filter was getting initialized before the core module
// was initialized, GitHub Issue 1008. We have no idea how that happened, but registering late won't hurt.

_log.info("Registering filters");

for (Module module : _modules)
{
module.registerFilters(_servletContext);
}

_log.info("Registering servlets");

for (Module module : _modules)
{
module.registerServlets(_servletContext);
}
for (Module module : _modules)
{
module.registerFinalServlets(_servletContext);
}

_log.info("LabKey Server startup is complete; {}", execution.getLogMessage());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public void renderInputHtml(RenderContext ctx, HtmlWriter out, Object value)
return ret2;
}
),
disabledInput ? InputBuilder.hidden().name(_typeInputName).value(finalSelected.getName()).appendTo(out) : null
disabledInput ? InputBuilder.hidden().name(_typeInputName).value(finalSelected.getName()).getHtmlString() : null
)
).appendTo(out);
}
Expand Down
51 changes: 49 additions & 2 deletions api/src/org/labkey/api/util/DOM.java
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,54 @@ public enum Attribute
action,
align,
alt,
aria_activedescendant,
aria_atomic,
aria_autocomplete,
aria_busy,
aria_checked,
aria_colcount,
aria_colindex,
aria_colspan,
aria_controls,
aria_current,
aria_describedby,
aria_details,
aria_disabled,
aria_dropeffect,
aria_errormessage,
aria_expanded,
aria_flowto,
aria_grabbed,
aria_haspopup,
aria_hidden,
aria_invalid,
aria_keyshortcuts,
aria_label,
aria_labelledby,
aria_level,
aria_live,
aria_modal,
aria_multiline,
aria_multiselectable,
aria_orientation,
aria_owns,
aria_placeholder,
aria_posinset,
aria_pressed,
aria_readonly,
aria_relevant,
aria_required,
aria_roledescription,
aria_rowcount,
aria_rowindex,
aria_rowspan,
aria_selected,
aria_setsize,
aria_sort,
aria_valuemax,
aria_valuemin,
aria_valuenow,
aria_valuetext,
async,
autocomplete,
autofocus,
Expand Down Expand Up @@ -570,7 +618,6 @@ public _Attributes data(boolean condition, String datakey, Object value)
}
return this;
}

public _Attributes cl(String...names)
{
if (null != names)
Expand Down Expand Up @@ -891,7 +938,7 @@ private static Appendable appendAttribute(Appendable html, Attribute key, Object
if (null==value)
return html;
html.append(" ");
html.append(key.name());
html.append(key.name().replace('_', '-'));
html.append("=\"");
// NOTE it is somewhat unusual to pass in a Renderable, but it is possible that we
// want to render HTML into an attribute. We still need to re-encode the value before trying to wrap with "".
Expand Down
Loading
Loading