diff --git a/api/src/org/labkey/api/view/JspTemplate.java b/api/src/org/labkey/api/view/JspTemplate.java index 5188ad43df8..3b58538037d 100644 --- a/api/src/org/labkey/api/view/JspTemplate.java +++ b/api/src/org/labkey/api/view/JspTemplate.java @@ -60,7 +60,7 @@ public String render() throws Exception context.setRequest(new MockHttpServletRequest("GET", null)); var mockResponse = new MockHttpServletResponse(); context.setResponse(mockResponse); - try (var init = HttpView.initForRequest(context, context.getRequest(), context.getResponse())) + try (var ignored = HttpView.initForRequest(context, context.getRequest(), context.getResponse())) { // Tomcat 8 Jasper rejects requests other than GET, POST, or HEAD... so, make this mock request a GET. #24750 include(this, out, context.getRequest(), context.getResponse()); diff --git a/core/src/org/labkey/core/CoreModule.java b/core/src/org/labkey/core/CoreModule.java index 433f18e2414..6b285532deb 100644 --- a/core/src/org/labkey/core/CoreModule.java +++ b/core/src/org/labkey/core/CoreModule.java @@ -166,7 +166,6 @@ import org.labkey.api.thumbnail.ThumbnailService; import org.labkey.api.usageMetrics.SimpleMetricsService; import org.labkey.api.usageMetrics.UsageMetricsService; -import org.labkey.api.util.ConfigurationException; import org.labkey.api.util.ContextListener; import org.labkey.api.util.ExceptionUtil; import org.labkey.api.util.FileUtil; diff --git a/core/src/org/labkey/core/admin/AdminController.java b/core/src/org/labkey/core/admin/AdminController.java index d0210826167..b668879c615 100644 --- a/core/src/org/labkey/core/admin/AdminController.java +++ b/core/src/org/labkey/core/admin/AdminController.java @@ -293,6 +293,7 @@ import org.labkey.api.writer.ZipUtil; import org.labkey.bootstrap.ExplodedModuleService; import org.labkey.core.admin.miniprofiler.MiniProfilerController; +import org.labkey.core.admin.sitevalidation.SiteValidationJob; import org.labkey.core.admin.sql.SqlScriptController; import org.labkey.core.portal.CollaborationFolderType; import org.labkey.core.portal.ProjectController; @@ -482,7 +483,6 @@ public static void registerManagementTabs() addTab(TYPE.FolderManagement,"Files", "files", FOLDERS_AND_PROJECTS, FileRootsAction.class); addTab(TYPE.FolderManagement,"Formats", "settings", FOLDERS_ONLY, FolderSettingsAction.class); addTab(TYPE.FolderManagement,"Information", "info", NOT_ROOT, FolderInformationAction.class); - addTab(TYPE.FolderManagement,"Validate", "validate", EVERY_CONTAINER, ConfigureSiteValidationAction.class); addTab(TYPE.FolderManagement,"R Config", "rConfig", NOT_ROOT, RConfigurationAction.class); addTab(TYPE.ProjectSettings, "Properties", "properties", PROJECTS_ONLY, ProjectSettingsAction.class); @@ -1495,10 +1495,10 @@ public HtmlString getSiteSettingsHelpLink(String fragment) } @RequiresPermission(AdminPermission.class) - public class ConfigureSiteValidationAction extends FolderManagementViewAction + public class ConfigureSiteValidationAction extends SimpleViewAction { @Override - protected JspView getTabView() + public ModelAndView getView(Object o, BindException errors) throws Exception { return new JspView<>("/org/labkey/core/admin/sitevalidation/configureSiteValidation.jsp"); } @@ -1513,8 +1513,19 @@ public void addNavTrail(NavTree root) public static class SiteValidationForm { - private boolean _includeSubfolders = false; private List _providers; + private boolean _includeSubfolders = false; + private transient Consumer _logger = s -> {}; // No-op by default + + public List getProviders() + { + return _providers; + } + + public void setProviders(List providers) + { + _providers = providers; + } public boolean isIncludeSubfolders() { @@ -1526,14 +1537,14 @@ public void setIncludeSubfolders(boolean includeSubfolders) _includeSubfolders = includeSubfolders; } - public List getProviders() + public Consumer getLogger() { - return _providers; + return _logger; } - public void setProviders(List providers) + public void setLogger(Consumer logger) { - _providers = providers; + _logger = logger; } } @@ -1554,6 +1565,84 @@ public void addNavTrail(NavTree root) } } + @RequiresPermission(AdminPermission.class) + public static class SiteValidationBackgroundAction extends FormHandlerAction + { + private ActionURL _redirectUrl; + + @Override + public void validateCommand(SiteValidationForm form, Errors errors) + { + } + + @Override + public boolean handlePost(SiteValidationForm form, BindException errors) throws PipelineValidationException + { + ViewBackgroundInfo vbi = new ViewBackgroundInfo(getContainer(), getUser(), null); + PipeRoot root = PipelineService.get().findPipelineRoot(getContainer()); + SiteValidationJob job = new SiteValidationJob(vbi, root, form); + PipelineService.get().queueJob(job); + String jobGuid = job.getJobGUID(); + + if (null == jobGuid) + throw new NotFoundException("Unable to determine pipeline job GUID"); + + Integer jobId = PipelineService.get().getJobId(getUser(), getContainer(), jobGuid); + + if (null == jobId) + throw new NotFoundException("Unable to determine pipeline job ID"); + + PipelineStatusUrls urls = urlProvider(PipelineStatusUrls.class); + _redirectUrl = urls.urlDetails(getContainer(), jobId); + + return true; + } + + @Override + public URLHelper getSuccessURL(SiteValidationForm form) + { + return _redirectUrl; + } + } + + public static class ViewValidationResultsForm + { + private String _fileName; + + public String getFileName() + { + return _fileName; + } + + @SuppressWarnings("unused") + public void setFileName(String fileName) + { + _fileName = fileName; + } + } + + @RequiresPermission(AdminPermission.class) + public class ViewValidationResultsAction extends SimpleViewAction + { + @Override + public ModelAndView getView(ViewValidationResultsForm form, BindException errors) throws Exception + { + PipeRoot root = PipelineService.get().findPipelineRoot(getContainer()); + File results = new File(root.getLogDirectory(), form.getFileName()); + if (!results.isFile()) + throw new NotFoundException("File not found: " + form.getFileName()); + + return new HtmlView(HtmlString.unsafe(PageFlowUtil.getFileContentsAsString(results))); + } + + @Override + public void addNavTrail(NavTree root) + { + setHelpTopic("siteValidation"); + addAdminNavTrail(root, "View " + (getContainer().isRoot() ? "Site" : "Folder") + " Validation Results", getClass()); + } + } + public interface FileManagementForm { String getFolderRootPath(); diff --git a/core/src/org/labkey/core/admin/sitevalidation/SiteValidationJob.java b/core/src/org/labkey/core/admin/sitevalidation/SiteValidationJob.java new file mode 100644 index 00000000000..9fac870cd18 --- /dev/null +++ b/core/src/org/labkey/core/admin/sitevalidation/SiteValidationJob.java @@ -0,0 +1,82 @@ +package org.labkey.core.admin.sitevalidation; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.labkey.api.pipeline.PipeRoot; +import org.labkey.api.pipeline.PipelineJob; +import org.labkey.api.util.FileUtil; +import org.labkey.api.util.StringUtilsLabKey; +import org.labkey.api.util.URLHelper; +import org.labkey.api.view.ActionURL; +import org.labkey.api.view.JspTemplate; +import org.labkey.api.view.ViewBackgroundInfo; +import org.labkey.api.view.ViewContext; +import org.labkey.core.admin.AdminController.SiteValidationForm; +import org.labkey.core.admin.AdminController.ViewValidationResultsAction; + +import java.io.File; +import java.io.PrintWriter; + +public class SiteValidationJob extends PipelineJob +{ + private final SiteValidationForm _form; + + @JsonCreator + protected SiteValidationJob(@JsonProperty("_form") SiteValidationForm form) + { + _form = form; + } + + public SiteValidationJob(ViewBackgroundInfo info, PipeRoot pipeRoot, SiteValidationForm form) + { + super("SiteValidation", info, pipeRoot); + setLogFile(new File(pipeRoot.getLogDirectory(), FileUtil.makeFileNameWithTimestamp("site_validation", "log")).toPath()); + _form = form; + } + + @Override + public URLHelper getStatusHref() + { + return new ActionURL(ViewValidationResultsAction.class, getContainer()) + .addParameter("fileName", getResultsFileName()); + } + + @Override + public String getDescription() + { + return "Site Validation"; + } + + @Override + public void run() + { + info("Site validation started"); + PipelineJob.TaskStatus finalStatus = PipelineJob.TaskStatus.complete; + _form.setLogger(s -> { + getLogger().info(s); + setStatus(s); + }); + JspTemplate template = new JspTemplate<>("/org/labkey/core/admin/sitevalidation/siteValidation.jsp", _form); + ViewContext context = new ViewContext(getInfo()); + template.setViewContext(context); + File results = new File(getPipeRoot().getLogDirectory(), getResultsFileName()); + + try (PrintWriter out = new PrintWriter(results, StringUtilsLabKey.DEFAULT_CHARSET)) + { + out.println(template.render()); + } + catch (Exception e) + { + getLogger().error("Site validation failed", e); + finalStatus = TaskStatus.error; + } + + info("Site validation complete"); + setStatus(finalStatus); + } + + private String getResultsFileName() + { + return getLogFile().getName().replace(".log", ".html"); + } +} diff --git a/core/src/org/labkey/core/admin/sitevalidation/configureSiteValidation.jsp b/core/src/org/labkey/core/admin/sitevalidation/configureSiteValidation.jsp index c068641c2d9..946c69e9037 100644 --- a/core/src/org/labkey/core/admin/sitevalidation/configureSiteValidation.jsp +++ b/core/src/org/labkey/core/admin/sitevalidation/configureSiteValidation.jsp @@ -19,7 +19,8 @@ <%@ page import="org.labkey.api.admin.sitevalidation.SiteValidationService" %> <%@ page import="org.labkey.api.util.DOM" %> <%@ page import="org.labkey.api.util.HtmlString" %> -<%@ page import="org.labkey.core.admin.AdminController" %> +<%@ page import="org.labkey.core.admin.AdminController.SiteValidationAction" %> +<%@ page import="org.labkey.core.admin.AdminController.SiteValidationBackgroundAction" %> <%@ page import="java.io.IOException" %> <%@ page import="java.util.Collection" %> <%@ page import="java.util.List" %> @@ -67,7 +68,7 @@ { %> Clicking the "Validate" button will run the selected validators in the designated folder(s). Producing the results could take some time, especially with many folders, providers, and/or objects that need to be validated.

- + <% if (getContainer().isRoot()) renderProviderList("Site Validation Providers", validationService.getSiteProviders(), out); @@ -91,17 +92,37 @@ Clicking the "Validate" button will run the selected validators in the designate { out.println( DOM.createHtmlFragment( - input().type("checkbox").checked(true).name("includeSubfolders"), + input().type("checkbox").checked(true).value("true").name("includeSubfolders"), "Include subfolders", - DOM.BR(), DOM.BR() ) ); } + out.println( + DOM.createHtmlFragment( + input().id("background").type("checkbox").checked(false).value("true").name("background").onChange("change()"), + "Run in the background", + helpPopup("Validating many folders can take a long time. Running in a background pipeline job avoids proxy timeouts. Once the job completes, click the \"Data\" button to see the report."), + DOM.BR(), + DOM.BR() + ) + ); %> - <%=button("Validate").usePost().submit(true)%> + <%=button("Validate").submit(true)%> <%=generateBackButton("Cancel")%> + <% } -%> \ No newline at end of file +%> + \ No newline at end of file diff --git a/core/src/org/labkey/core/admin/sitevalidation/siteValidation.jsp b/core/src/org/labkey/core/admin/sitevalidation/siteValidation.jsp index 4c798d573e1..28bdf84e5bd 100644 --- a/core/src/org/labkey/core/admin/sitevalidation/siteValidation.jsp +++ b/core/src/org/labkey/core/admin/sitevalidation/siteValidation.jsp @@ -24,7 +24,12 @@ <%@ page import="java.util.List" %> <%@ page import="java.util.Map" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> - +<%! + void info(SiteValidationForm form, String message) + { + form.getLogger().accept(message); + } +%> @@ -47,15 +52,18 @@ <% if (validationService.getSiteProviders().isEmpty()) { + info(form, "No site-wide validators are registered"); %>

No site-wide validators are registered.

<% } else { + info(form, "Running selected site-wide validators"); Map> siteResults = validationService.runSiteScopeValidators(form.getProviders(), getUser()); if (siteResults.isEmpty()) { + info(form, "No site-wide validators were selected"); %>

No site-wide validators were selected.

<% @@ -64,58 +72,115 @@ { %>
    - <% List infos; - List errors; - List warnings; - for (Map.Entry> moduleResults : siteResults.entrySet()) - { - %> +<% + List infos; + List errors; + List warnings; + + for (Map.Entry> moduleResults : siteResults.entrySet()) + { +%>
  • Module: <%=h(moduleResults.getKey())%>
      - <% for (Map.Entry results : moduleResults.getValue().entrySet()) { %> +<% + for (Map.Entry results : moduleResults.getValue().entrySet()) + { +%>
    • Validator: <%=h(results.getKey().getName() + " ")%><%=h(results.getKey().getDescription())%>
        - <% if (results.getValue().getResults().isEmpty()) { %> -
      • Nothing to report
      • - <% } else { - infos = results.getValue().getResults(Level.INFO); - warnings = results.getValue().getResults(Level.WARN); - errors = results.getValue().getResults(Level.ERROR); - for (SiteValidationResult result : infos) { %> +<% + if (results.getValue().getResults().isEmpty()) + { +%> +
      • Nothing to report
      • +<% + } + else + { + infos = results.getValue().getResults(Level.INFO); + warnings = results.getValue().getResults(Level.WARN); + errors = results.getValue().getResults(Level.ERROR); + + for (SiteValidationResult result : infos) + { +%>
      • <%=h(result.getMessage())%> - <% if (null != result.getLink()) { %> +<% + if (null != result.getLink()) + { +%> <%=link(LINK_HEADING, result.getLink())%> - <% } %> +<% + } +%>
      • - <% } %> - <% if (!errors.isEmpty()) { %> -
      • Errors: -
          - <% for (SiteValidationResult result : errors) { %> -
        • +<% + } + + if (!errors.isEmpty()) + { +%> +
        • Errors: +
            +<% + for (SiteValidationResult result : errors) + { +%> +
          • <%=h(result.getMessage())%> - <% if (null != result.getLink()) { %> +<% + if (null != result.getLink()) + { +%> <%=link(LINK_HEADING, result.getLink())%> - <% } %> -
          • - <% } %>
          - <% } %> - <% if (!warnings.isEmpty()) { %> -
        • Warnings: -
            - <% for (SiteValidationResult result : warnings) { %> -
          • +<% + } +%> +
          • +<% + } +%> +
          +<% + } + + if (!warnings.isEmpty()) + { +%> +
        • Warnings: +
            +<% + for (SiteValidationResult result : warnings) + { +%> +
          • <%=h(result.getMessage())%> - <% if (null != result.getLink()) { %> +<% + if (null != result.getLink()) + { +%> <%=link(LINK_HEADING, result.getLink())%> - <% } %> -
          • - <% } %>
        • - <% } %> - <% } %>
      • - <% } %>

    • - <% } %> +<% + } +%> + +<% + } +%>
    +
  • +<% + } + } +%>
+ +<% + } +%>
+ +<% + } +%> <% } @@ -124,6 +189,7 @@ %> Folder Validation Results <% + info(form, "Running all selected folder validators for all selected folders"); Map>>> containerResults = validationService.runContainerScopeValidators(getContainer(), form.isIncludeSubfolders(), form.getProviders(), getUser()); if (containerResults.isEmpty()) { @@ -146,79 +212,89 @@
  • Module: <%=h(moduleResults.getKey())%>
      <% - for (Map.Entry>> validatorResults : moduleResults.getValue().entrySet()) - { + for (Map.Entry>> validatorResults : moduleResults.getValue().entrySet()) + { %>
    • Validator: <%=h(validatorResults.getKey().getName() + " ")%><%=h(validatorResults.getKey().getDescription())%>
        -<% if (validatorResults.getValue().isEmpty()) - { +<% + if (validatorResults.getValue().isEmpty()) + { %>
      • Nothing to report
      • -<% } - else +<% + } + else + { + for (Map.Entry> projectResult : validatorResults.getValue().entrySet()) { - for (Map.Entry> projectResult : validatorResults.getValue().entrySet()) - { %>
      • <%=h("Project: " + projectResult.getKey())%>
          -<% for (Map.Entry subtreeResult : projectResult.getValue().entrySet()) - { +<% + for (Map.Entry subtreeResult : projectResult.getValue().entrySet()) + { %>
        • <%=h("Folder: " + subtreeResult.getKey())%>
            - <% if (subtreeResult.getValue() != null) - { - containerInfos = subtreeResult.getValue().getResults(Level.INFO); - containerErrors = subtreeResult.getValue().getResults(Level.ERROR); - containerWarnings = subtreeResult.getValue().getResults(Level.WARN); - for (SiteValidationResult result : containerInfos) { %> -
          • <%=h(result.getMessage())%> - <% if (null != result.getLink()) { %> - <%=link(LINK_HEADING, result.getLink())%> - <% } %> -
          • +<% + if (subtreeResult.getValue() != null) + { + containerInfos = subtreeResult.getValue().getResults(Level.INFO); + containerErrors = subtreeResult.getValue().getResults(Level.ERROR); + containerWarnings = subtreeResult.getValue().getResults(Level.WARN); + for (SiteValidationResult result : containerInfos) + { +%> +
          • <%=h(result.getMessage())%> + <% if (null != result.getLink()) { %> + <%=link(LINK_HEADING, result.getLink())%> + <% } %> +
          • + <% } %> + <% if (!containerErrors.isEmpty()) { %> +
          • Errors: +
              + <% for (SiteValidationResult result : containerErrors) { %> +
            • <%=h(result.getMessage())%> + <% if (null != result.getLink()) { %> + <%=link(LINK_HEADING, result.getLink())%> <% } %> - <% if (!containerErrors.isEmpty()) { %> -
            • Errors: -
                - <% for (SiteValidationResult result : containerErrors) { %> -
              • <%=h(result.getMessage())%> - <% if (null != result.getLink()) { %> - <%=link(LINK_HEADING, result.getLink())%> - <% } %> -
              • - <% } %> -
              -
            • - <% } - if (!containerWarnings.isEmpty()) - { %> -
            • Warnings: -
                - <% for (SiteValidationResult result : containerWarnings) - { %> -
              • <%=h(result.getMessage())%> - <% if (null != result.getLink()) { %> - <%=link(LINK_HEADING, result.getLink())%> - <% } %> -
              • - <% } %> -
            • + <% } %> +
            +
          • +<% + } + if (!containerWarnings.isEmpty()) + { +%> +
          • Warnings: +
              + <% for (SiteValidationResult result : containerWarnings) + { %> +
            • <%=h(result.getMessage())%> + <% if (null != result.getLink()) { %> + <%=link(LINK_HEADING, result.getLink())%> <% } %> +
            • <% } %>
          • <% + } } %>
        • <% } +%> +
        +
      • +<% } + } %>
    • diff --git a/core/webapp/admin/FolderManagementPanel.js b/core/webapp/admin/FolderManagementPanel.js index 3db48178f01..d84ffff8851 100644 --- a/core/webapp/admin/FolderManagementPanel.js +++ b/core/webapp/admin/FolderManagementPanel.js @@ -89,7 +89,7 @@ Ext4.define('LABKEY.ext4.panel.FolderManagement', { rename : 'renameFolder', reorder: 'reorderFolders', revert : this.revertAction, - validate : 'siteValidation' + validate : 'configureSiteValidation' };