diff --git a/api/src/org/labkey/api/util/ErrorRenderer.java b/api/src/org/labkey/api/util/ErrorRenderer.java index 171ac4c4344..3c7ba68a0fd 100644 --- a/api/src/org/labkey/api/util/ErrorRenderer.java +++ b/api/src/org/labkey/api/util/ErrorRenderer.java @@ -17,6 +17,7 @@ package org.labkey.api.util; import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.jetbrains.annotations.Nullable; import org.labkey.api.data.CoreSchema; import org.labkey.api.data.DbSchema; @@ -40,16 +41,16 @@ public class ErrorRenderer private final boolean _isStartupFailure; private final ErrorRendererProperties _errorRendererProps; private final String _title; - - // TODO: ErrorPage use these in React app - private boolean _includeHomeButton = true; - private boolean _includeBackButton = true; - private boolean _includeFolderButton = true; - private boolean _includeStopImpersonatingButton = false; + private final String _errorCode; private ErrorType _errorType; - enum ErrorType + public String getStackTrace() + { + return ExceptionUtils.getStackTrace(getException()); + } + + public enum ErrorType { notFound, permission, @@ -57,52 +58,13 @@ enum ErrorType execution } - public boolean isIncludeHomeButton() - { - return _includeHomeButton; - } - - public void setIncludeHomeButton(boolean includeHomeButton) - { - _includeHomeButton = includeHomeButton; - } - - public boolean isIncludeBackButton() - { - return _includeBackButton; - } - - public void setIncludeBackButton(boolean includeBackButton) - { - _includeBackButton = includeBackButton; - } - - public boolean isIncludeFolderButton() - { - return _includeFolderButton; - } - - public void setIncludeFolderButton(boolean includeFolderButton) - { - _includeFolderButton = includeFolderButton; - } - - public boolean isIncludeStopImpersonatingButton() - { - return _includeStopImpersonatingButton; - } - - public void setIncludeStopImpersonatingButton(boolean includeStopImpersonatingButton) - { - _includeStopImpersonatingButton = includeStopImpersonatingButton; - } - - ErrorRenderer(int status, String heading, Throwable x, boolean isStartupFailure) + ErrorRenderer(int status, String errorCode, String heading, Throwable x, boolean isStartupFailure) { _status = status; _exception = x; _isStartupFailure = isStartupFailure; _errorRendererProps = (x instanceof ErrorRendererProperties ? (ErrorRendererProperties)x : null); + _errorCode = errorCode; if (null == _errorRendererProps) { @@ -111,7 +73,7 @@ public void setIncludeStopImpersonatingButton(boolean includeStopImpersonatingBu } else { - _heading = _errorRendererProps.getHeading(_isStartupFailure); + _heading = null != heading ? heading : _errorRendererProps.getHeading(_isStartupFailure); _title = _errorRendererProps.getTitle(); } } @@ -301,4 +263,9 @@ public void setErrorType(ErrorType errorType) { _errorType = errorType; } + + public String getErrorCode() + { + return _errorCode; + } } diff --git a/api/src/org/labkey/api/util/ErrorTemplate.java b/api/src/org/labkey/api/util/ErrorTemplate.java deleted file mode 100644 index e6cd3ebd705..00000000000 --- a/api/src/org/labkey/api/util/ErrorTemplate.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.labkey.api.util; - -import org.labkey.api.view.JspView; - -// TODO: ErrorPage - rename this to ErrorView when that object is deleted -public class ErrorTemplate extends JspView -{ - public ErrorTemplate(ErrorRenderer renderer) - { - super("/org/labkey/api/view/template/errorView.jsp", renderer); - } -} diff --git a/api/src/org/labkey/api/util/ErrorView.java b/api/src/org/labkey/api/util/ErrorView.java index 568cad14643..58345a0d23c 100644 --- a/api/src/org/labkey/api/util/ErrorView.java +++ b/api/src/org/labkey/api/util/ErrorView.java @@ -1,226 +1,13 @@ -/* - * Copyright (c) 2009-2019 LabKey Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.labkey.api.util; - -import org.apache.commons.lang3.StringUtils; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerManager; -import org.labkey.api.portal.ProjectUrls; -import org.labkey.api.security.LoginUrls; -import org.labkey.api.settings.AppProps; -import org.labkey.api.settings.LookAndFeelProperties; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.HttpView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.PrintWriter; - -class ErrorView extends HttpView -{ - private final ErrorRenderer _renderer; - private final boolean _startupFailure; - private final boolean _popup; - - private ButtonBarRenderer _bbr = null; - - private boolean _includeHomeButton = true; - private boolean _includeBackButton = true; - private boolean _includeFolderButton = true; - private boolean _includeStopImpersonatingButton = false; - - ErrorView(ErrorRenderer renderer, boolean startupFailure) - { - this(renderer, startupFailure, false); - } - - ErrorView(ErrorRenderer renderer, boolean startupFailure, boolean popup) - { - _renderer = renderer; - _startupFailure = startupFailure; - _popup = popup; - } - - - @Override - public void renderInternal(Object model, HttpServletRequest request, HttpServletResponse response) throws Exception - { - PrintWriter out = response.getWriter(); - - doStartTag(out); - _renderer.renderContent(out, request, _bbr); - doEndTag(out); - } - - - public void doStartTag(PrintWriter out) - { - Container c = null; - - out.println(""); - out.println(""); - - // If it's a startup failure we likely don't have a database, so don't try to handle containers. Instead, hard-code - // some reasonable styles. - if (!_startupFailure) - { - out.println(PageFlowUtil.getStandardIncludes(getViewContext(), null, false)); - - c = getViewContext().getContainer(); - } - else - { - out.println(""); - } - - if (null == c) - { - try - { - c = ContainerManager.getRoot(); - } - catch (Throwable t) - { - // Exception at initial bootstrap (e.g., database not supported) might result in no root - } - } - - //NOTE: Selenium tests expect errors to start with error number and include word "Error" in title - out.println("" + PageFlowUtil.filter(_renderer.getTitle()) + ""); - out.println(""); - _renderer.renderStart(out); - - if (null != _renderer.getHeading()) - { - out.println("

" + PageFlowUtil.filter(_renderer.getHeading()) + "

"); - } - - // Why don't we just use c below? - Container contextContainer = getContextContainer(); - - if (null == contextContainer) - contextContainer = c; - - // These buttons are useless if the server fails to start up. Also, they try to hit a database that probably doesn't exist. - if (!_startupFailure) - { - if (_renderer.getStatus() == HttpServletResponse.SC_UNAUTHORIZED) - { - if (getViewContext().getUser().isGuest()) - { - ActionURL url = PageFlowUtil.urlProvider(LoginUrls.class).getLoginURL(getViewContext().getContainer(), null); - out.println("

You are not currently logged in. You may need to log in to gain the necessary permissions.

"); - } - LookAndFeelProperties props = LookAndFeelProperties.getInstance(contextContainer); - if (!StringUtils.isBlank(props.getSupportEmail())) - { - out.println("

If you believe you should have permission to perform this action, please email your system administrator.

"); - } - } - - if (_popup) - { - out.print(PageFlowUtil.button("Close").href("#").onClick("window.close(); return false;")); - out.println("
"); - } - else - { - _bbr = new ErrorButtonBarRenderer(c); - } - } - } - - private class ErrorButtonBarRenderer implements ButtonBarRenderer - { - private Container _c; - - ErrorButtonBarRenderer(Container c) - { - _c = c; - } - - @Override - public void render(PrintWriter out) - { - doButtonBar(out, _c); - } - } - - private void doButtonBar(PrintWriter out, Container c) - { - if (_includeHomeButton) - { - out.print(PageFlowUtil.button("Home").href(AppProps.getInstance().getHomePageActionURL())); - out.print(" "); - } - if (_includeBackButton) - { - out.print(PageFlowUtil.generateBackButton()); - out.print(" "); - } - if (_includeFolderButton && !c.isRoot()) - { - ActionURL folderURL = PageFlowUtil.urlProvider(ProjectUrls.class).getStartURL(c); - out.print(PageFlowUtil.button("Folder").href(folderURL)); - out.print(" "); - } - if (_includeStopImpersonatingButton) - { - ActionURL stopUrl = PageFlowUtil.urlProvider(LoginUrls.class).getStopImpersonatingURL(c, getViewContext().getActionURL()); - out.println(PageFlowUtil.button("Stop Impersonating").href(stopUrl).usePost()); - } - - out.println("
"); - } - - public void doEndTag(PrintWriter out) - { - _renderer.renderEnd(out); - out.println(""); - } - - public void setIncludeHomeButton(boolean includeHomeButton) - { - _includeHomeButton = includeHomeButton; - } - - public void setIncludeBackButton(boolean includeBackButton) - { - _includeBackButton = includeBackButton; - } - - public void setIncludeFolderButton(boolean includeFolderButton) - { - _includeFolderButton = includeFolderButton; - } - - public void setIncludeStopImpersonatingButton(boolean includeStopImpersonatingButton) - { - _includeStopImpersonatingButton = includeStopImpersonatingButton; - } -} +package org.labkey.api.util; + +import org.labkey.api.view.JspView; + +public class ErrorView extends JspView +{ + public static String ERROR_PAGE_TITLE = "Error Page"; + + public ErrorView(ErrorRenderer renderer) + { + super("/org/labkey/api/view/template/errorView.jsp", renderer); + } +} diff --git a/api/src/org/labkey/api/util/ExceptionUtil.java b/api/src/org/labkey/api/util/ExceptionUtil.java index b8c4902039a..d549aa88168 100644 --- a/api/src/org/labkey/api/util/ExceptionUtil.java +++ b/api/src/org/labkey/api/util/ExceptionUtil.java @@ -44,7 +44,6 @@ import org.labkey.api.settings.LookAndFeelProperties; import org.labkey.api.view.ActionURL; import org.labkey.api.view.BadRequestException; -import org.labkey.api.view.ForbiddenProjectException; import org.labkey.api.view.HttpView; import org.labkey.api.view.NotFoundException; import org.labkey.api.view.RedirectException; @@ -173,23 +172,6 @@ public static HtmlString getUnauthorizedMessage(ViewContext context) ""); } - - public static ErrorView getErrorView(int responseStatus, String message, Throwable ex, - HttpServletRequest request, boolean startupFailure) - { - ErrorRenderer renderer = getErrorRenderer(responseStatus, message, ex, request, false, startupFailure); - return new ErrorView(renderer, startupFailure); - } - - - public static ErrorView getErrorView(int responseStatus, String message, Throwable ex, - HttpServletRequest request, boolean startupFailure, boolean popup) - { - ErrorRenderer renderer = getErrorRenderer(responseStatus, message, ex, request, false, startupFailure); - return new ErrorView(renderer, startupFailure, popup); - } - - public static WebPartView getErrorWebPartView(int responseStatus, String message, Throwable ex, HttpServletRequest request) { @@ -201,27 +183,16 @@ public static WebPartView getErrorWebPartView(int responseStatus, String message public static ErrorRenderer getErrorRenderer(int responseStatus, String message, Throwable ex, @Nullable HttpServletRequest request, boolean isPart, boolean isStartupFailure) { + String errorCode = null; if (!isStartupFailure && responseStatus == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { - String errorCode = logExceptionToMothership(request, ex); - if (null != errorCode) - { - message = StringUtils.trimToEmpty(message); - if (message.length() > 0) - { - if (message.endsWith(".")) - message += " "; - else - message += ". "; - } - message += "If contacting support regarding this error, please refer to error code: " + errorCode; - } + errorCode = logExceptionToMothership(request, ex); } if (isPart) - return new WebPartErrorRenderer(responseStatus, message, ex, isStartupFailure); + return new WebPartErrorRenderer(responseStatus, errorCode, message, ex, isStartupFailure); else - return new ErrorRenderer(responseStatus, message, ex, isStartupFailure); + return new ErrorRenderer(responseStatus, errorCode, message, ex, isStartupFailure); } private static ExceptionReportingLevel getExceptionReportingLevel() @@ -792,7 +763,7 @@ else if (ex instanceof ConfigurationException) if (null == message && null != unhandledException) { responseStatus = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - message = responseStatus + ": Unexpected server error"; + message = ex.getMessage(); } //don't log unauthorized (basic-auth challenge), forbiddens, or simple not found (404s) @@ -861,47 +832,13 @@ else if (isJSON || isXML) log.error("Global.handleException", x); } } - else if (AppProps.getInstance().isExperimentalFeatureEnabled(AppProps.EXPERIMENTAL_ERROR_PAGE)) - { - renderErrorPage(ex, responseStatus, message, request, response, pageConfig, errorType, user, log, startupFailure); - } else { - // 7629: Error page redesign -- development in-progress. This path shows the old error view. - ErrorView errorView = ExceptionUtil.getErrorView(responseStatus, message, unhandledException, request, startupFailure); - - if (ex instanceof UnauthorizedException) - { - if (ex instanceof ForbiddenProjectException) - { - // Not allowed in the project... don't offer Home or Folder buttons - errorView.setIncludeHomeButton(false); - errorView.setIncludeFolderButton(false); - } - - // Provide "Stop Impersonating" button if unauthorized while impersonating - if (user.isImpersonated()) - errorView.setIncludeStopImpersonatingButton(true); - } - - try - { - response.setContentType("text/html"); - if (null == responseStatusMessage) - response.setStatus(responseStatus); - else - response.setStatus(responseStatus, responseStatusMessage); - for (Map.Entry entry : headers.entrySet()) - response.addHeader(entry.getKey(), entry.getValue()); - errorView.render(request, response); - } - catch (IllegalStateException ignored) - { - } - catch (Exception x) - { - log.error("Global.handleException", x); - } + response.setContentType("text/html"); + response.setStatus(responseStatus); + for (Map.Entry entry : headers.entrySet()) + response.addHeader(entry.getKey(), entry.getValue()); + renderErrorPage(ex, responseStatus, message, request, response, pageConfig, errorType, user, log, startupFailure); } return null; @@ -914,16 +851,7 @@ private static void renderErrorPage(Throwable ex, int responseStatus, String mes if (ex instanceof UnauthorizedException) { - if (ex instanceof ForbiddenProjectException) - { - // Not allowed in the project... don't offer Home or Folder buttons - renderer.setIncludeHomeButton(false); - renderer.setIncludeFolderButton(false); - } - - // Provide "Stop Impersonating" button if unauthorized while impersonating - if (user.isImpersonated()) - renderer.setIncludeStopImpersonatingButton(true); + errorType = ErrorRenderer.ErrorType.permission; } if (ex instanceof DavException) @@ -944,38 +872,43 @@ private static void renderErrorPage(Throwable ex, int responseStatus, String mes { context = HttpView.currentContext(); } + else + { + // context is null in cases of garbage urls, this helps in rendering error page in App template + context = new ViewContext(); + context.setRequest(request); + context.setResponse(response); + } try { + renderer.setErrorType(errorType); + if (HttpView.hasCurrentView()) { - renderer.setErrorType(errorType); - errorView = pageConfig.getTemplate().getTemplate(context, new ErrorTemplate(renderer), pageConfig); - - if (null == errorView) - { - log.error("Failed to create errorView in response to exception", ex); - return; - } + errorView = pageConfig.getTemplate().getTemplate(context, new ErrorView(renderer), pageConfig); } else { // context can be null for configuration exceptions depending on how far server got through initialization - errorView = PageConfig.Template.Body.getTemplate(new ViewContext(request, response, new ActionURL(ActionURL.getBaseServerURL())), new ErrorTemplate(renderer), pageConfig); + errorView = PageConfig.Template.Body.getTemplate(new ViewContext(request, response, new ActionURL(ActionURL.getBaseServerURL())), new ErrorView(renderer), pageConfig); } - pageConfig.addClientDependencies(errorView.getClientDependencies()); - errorView.getView().render(errorView.getModel(), request, response); + addDependenciesAndRender(responseStatus, pageConfig, errorView, ex, request, response); } catch (Exception e) { + // config exceptions that occur before jsps have been initialized + if (ex instanceof ConfigurationException && null != ModuleLoader.getInstance().getStartupFailure()) + { + throw new ConfigurationException(ex.getMessage()); + } + // try to render just the react app try { - // TODO : ErrorPage, this app template doesn't work - errorView = PageConfig.Template.App.getTemplate(context, new ErrorTemplate(renderer), pageConfig); - pageConfig.addClientDependencies(errorView.getClientDependencies()); - errorView.getView().render(errorView.getModel(), request, response); + errorView = PageConfig.Template.App.getTemplate(context, new ErrorView(renderer), pageConfig); + addDependenciesAndRender(responseStatus, pageConfig, errorView, ex, request, response); } catch (Exception exc) @@ -987,6 +920,31 @@ private static void renderErrorPage(Throwable ex, int responseStatus, String mes } } + public static void renderErrorView(ViewContext context, PageConfig pageConfig, ErrorRenderer.ErrorType errorType, int responseStatus, String message, @Nullable Throwable ex, boolean isPart, boolean isStartupFailure) throws Exception + { + ErrorRenderer renderer = ExceptionUtil.getErrorRenderer(responseStatus, message, ex, context.getRequest(), isPart, isStartupFailure); + renderer.setErrorType(errorType); + HttpView errorView = PageConfig.Template.App.getTemplate(context, new ErrorView(renderer), pageConfig); + if (null != errorView) + { + addDependenciesAndRender(responseStatus, pageConfig, errorView, ex, context.getRequest(), context.getResponse()); + } + } + + private static void addDependenciesAndRender(int responseStatus, PageConfig pageConfig, HttpView errorView, Throwable ex, HttpServletRequest request, HttpServletResponse response) throws Exception + { + if (null == errorView) + { + LOG.error("Failed to create errorView in response to exception", ex); + return; + } + pageConfig.addClientDependencies(errorView.getClientDependencies()); + + var title = responseStatus + ": " + ErrorView.ERROR_PAGE_TITLE + " -- " + ex.getMessage(); + pageConfig.setTitle(title, false); + errorView.getView().render(errorView.getModel(), request, response); + } + public static void doErrorRedirect(HttpServletResponse response, String url) { diff --git a/api/src/org/labkey/api/util/WebPartErrorRenderer.java b/api/src/org/labkey/api/util/WebPartErrorRenderer.java index 3764ff71e58..6d6f35a69f6 100644 --- a/api/src/org/labkey/api/util/WebPartErrorRenderer.java +++ b/api/src/org/labkey/api/util/WebPartErrorRenderer.java @@ -22,9 +22,9 @@ class WebPartErrorRenderer extends ErrorRenderer { private String _id; - WebPartErrorRenderer(int status, String message, Throwable x, boolean isStartupFailure) + WebPartErrorRenderer(int status, String errorCode, String message, Throwable x, boolean isStartupFailure) { - super(status, message, x, isStartupFailure); + super(status, errorCode, message, x, isStartupFailure); } @Override diff --git a/api/src/org/labkey/api/view/template/errorView.jsp b/api/src/org/labkey/api/view/template/errorView.jsp index 77342f105f5..d4d60e53b93 100644 --- a/api/src/org/labkey/api/view/template/errorView.jsp +++ b/api/src/org/labkey/api/view/template/errorView.jsp @@ -1,8 +1,9 @@ <%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.util.ErrorTemplate" %> +<%@ page import="org.labkey.api.util.ErrorView" %> <%@ page import="org.labkey.api.util.ErrorRenderer" %> <%@ page import="org.labkey.api.util.UniqueID" %> +<%@ page import="org.labkey.api.util.PageFlowUtil" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> @@ -10,13 +11,12 @@ @Override public void addClientDependencies(ClientDependencies dependencies) { - dependencies.add("clientapi"); // added this for App Template -// dependencies.add("http://localhost:3001/errorHandler.js"); - dependencies.add("core/gen/errorHandler"); + dependencies.add("core/css/core.css"); + dependencies.add("clientapi"); } %> <% - ErrorTemplate me = (ErrorTemplate) HttpView.currentView(); + ErrorView me = (ErrorView) HttpView.currentView(); ErrorRenderer model = me.getModelBean(); String appId = "error-handler-app-" + UniqueID.getServerSessionScopedUID(); @@ -24,9 +24,39 @@
+<% + StringBuilder stackTrace = new StringBuilder(); + if (null != model.getException()) + { + stackTrace.append(model.getException().getMessage()); + for (StackTraceElement stackTraceElement : model.getException().getStackTrace()) + { + stackTrace.append("\n"); + stackTrace.append(stackTraceElement.toString()); + } + } +%> + \ No newline at end of file diff --git a/assay/package-lock.json b/assay/package-lock.json index 594eec1fee5..0d4922b416a 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -172,9 +172,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" }, "@fortawesome/fontawesome-free": { "version": "5.14.0", @@ -243,21 +243,21 @@ "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, "@labkey/api": { - "version": "1.1.0", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.0.tgz", - "integrity": "sha1-tw++dJ18OzCud/XY6eEqLN5c7b4=" + "version": "1.1.1", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.1.tgz", + "integrity": "sha1-q4diKlMLLdE6QjjCAhCKfz9qhNY=" }, "@labkey/components": { - "version": "0.96.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.96.2.tgz", - "integrity": "sha1-S5Ee76gyJfrhAB8UvyCqFFHaw1o=", + "version": "0.97.0", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.97.0.tgz", + "integrity": "sha1-EeGjxLxb4h+c9AWbmJzdj/vCRw0=", "requires": { "@fortawesome/fontawesome-free": "5.14.0", "@fortawesome/fontawesome-svg-core": "1.2.30", "@fortawesome/free-regular-svg-icons": "5.14.0", "@fortawesome/free-solid-svg-icons": "5.14.0", "@fortawesome/react-fontawesome": "0.1.11", - "@labkey/api": "1.1.0", + "@labkey/api": "1.1.1", "bootstrap": "3.4.1", "classnames": "2.2.6", "font-awesome": "4.7.0", diff --git a/assay/package.json b/assay/package.json index ac02a8665d6..af55931d3c9 100644 --- a/assay/package.json +++ b/assay/package.json @@ -11,7 +11,7 @@ "clean": "rimraf resources/web/assay/gen && rimraf resources/views/*.*" }, "dependencies": { - "@labkey/components": "0.96.2" + "@labkey/components": "0.97.0" }, "devDependencies": { "@hot-loader/react-dom": "16.13.0", diff --git a/core/package-lock.json b/core/package-lock.json index e5d2b079fbf..61da9600223 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -830,9 +830,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" }, "@fortawesome/fontawesome-free": { "version": "5.14.0", @@ -1887,21 +1887,21 @@ } }, "@labkey/api": { - "version": "1.0.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.0.2.tgz", - "integrity": "sha1-Yc8EtcFZ9cyvtNwXKDHOr5VgY8g=" + "version": "1.1.1", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.1.tgz", + "integrity": "sha1-q4diKlMLLdE6QjjCAhCKfz9qhNY=" }, "@labkey/components": { - "version": "0.96.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.96.2.tgz", - "integrity": "sha1-S5Ee76gyJfrhAB8UvyCqFFHaw1o=", + "version": "0.97.0", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.97.0.tgz", + "integrity": "sha1-EeGjxLxb4h+c9AWbmJzdj/vCRw0=", "requires": { "@fortawesome/fontawesome-free": "5.14.0", "@fortawesome/fontawesome-svg-core": "1.2.30", "@fortawesome/free-regular-svg-icons": "5.14.0", "@fortawesome/free-solid-svg-icons": "5.14.0", "@fortawesome/react-fontawesome": "0.1.11", - "@labkey/api": "1.1.0", + "@labkey/api": "1.1.1", "bootstrap": "3.4.1", "classnames": "2.2.6", "font-awesome": "4.7.0", @@ -1931,13 +1931,6 @@ "reactn": "2.2.7", "redux-actions": "2.3.2", "vis-network": "6.5.2" - }, - "dependencies": { - "@labkey/api": { - "version": "1.1.0", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.0.tgz", - "integrity": "sha1-tw++dJ18OzCud/XY6eEqLN5c7b4=" - } } }, "@labkey/eslint-config-base": { diff --git a/core/package.json b/core/package.json index 8cce33c8d47..fe7bd656af0 100644 --- a/core/package.json +++ b/core/package.json @@ -47,8 +47,8 @@ } }, "dependencies": { - "@labkey/api": "1.0.2", - "@labkey/components": "0.96.2", + "@labkey/api": "1.1.1", + "@labkey/components": "0.97.0", "@labkey/eslint-config-react": "0.0.8", "@labkey/themes": "1.0.0" }, diff --git a/core/src/client/ErrorHandler/ErrorHandler.spec.tsx b/core/src/client/ErrorHandler/ErrorHandler.spec.tsx new file mode 100644 index 00000000000..8457d4c9ba5 --- /dev/null +++ b/core/src/client/ErrorHandler/ErrorHandler.spec.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import { getServerContext } from '@labkey/api'; + +import { ErrorHandler } from './ErrorHandler'; +import { ErrorDetails, ErrorType } from './model'; + +describe('ErrorHandler', () => { + test('Not found exception', () => { + const errorDetails: ErrorDetails = { + errorType: ErrorType.notFound, + errorCode: '123XYZ', + message: 'This is a not found exception', + }; + const wrapper = mount(); + expect(wrapper.find('.labkey-error-subheading').text().includes(errorDetails.message)).toBeTruthy(); + expect(wrapper.find('.error-details-container')).toHaveLength(0); + + wrapper.setState({ showDetails: true }); + const question = wrapper.find('.error-details-container'); + expect(question.text().startsWith('What went wrong?')).toBeTruthy(); + expect(question.text().includes('Incorrect URL:')).toBeTruthy(); + + wrapper.unmount(); + }); + + test('Configuration exception', () => { + const errorDetails: ErrorDetails = { + errorType: ErrorType.configuration, + message: 'This is a configuration exception', + }; + const subheading = 'The requested page cannot be found.'; + const wrapper = mount(); + expect(wrapper.find('.labkey-error-subheading').text().includes(subheading)).toBeTruthy(); + expect(wrapper.find('.error-details-container')).toHaveLength(0); + + wrapper.setState({ showDetails: true }); + const question = wrapper.find('.error-details-container'); + expect(question.text().startsWith('What went wrong?')).toBeTruthy(); + expect(question.text().includes('Server Configuration Errors')).toBeTruthy(); + + wrapper.unmount(); + }); + + test('Permission exception', () => { + const errorDetails: ErrorDetails = { + errorType: ErrorType.permission, + message: 'This is a permission exception', + }; + + const realUser = 'realUser'; + const impersonatedUser = 'impersonatedUser'; + getServerContext().impersonatingUser = { displayName: impersonatedUser }; + getServerContext().user = { displayName: realUser, isSignedIn: true }; + + const subheading = 'You do not have the permissions required to access this page.'; + const wrapper = shallow(); + expect(wrapper.find('.labkey-error-subheading').text().includes(subheading)).toBeTruthy(); + expect(wrapper.find('.error-details-container')).toHaveLength(0); + + wrapper.setState({ showDetails: true }); + const question = wrapper.find('.error-details-container'); + expect(question.text().startsWith('What is a permission error?')).toBeTruthy(); + expect(question.text().includes('You are currently logged in as: ' + impersonatedUser)).toBeTruthy(); + expect(question.text().includes('You are currently impersonating: ' + realUser)).toBeTruthy(); + + wrapper.unmount(); + }); + + test('Execution exception', () => { + const expectedStackTrace = 'java.lang.NullPointerException: null'; + + const errorDetails: ErrorDetails = { + errorType: ErrorType.execution, + errorCode: '456AAA', + message: 'This is a execution exception', + stackTrace: expectedStackTrace, + }; + const wrapper = shallow(); + expect(wrapper.find('.labkey-error-subheading').text().includes(errorDetails.message)).toBeTruthy(); + expect(wrapper.find('.error-details-container')).toHaveLength(0); + + wrapper.setState({ showDetails: true }); + const question = wrapper.find('.error-details-container'); + expect(question.text().startsWith(expectedStackTrace)).toBeTruthy(); + + wrapper.unmount(); + }); +}); diff --git a/core/src/client/ErrorHandler/ErrorHandler.tsx b/core/src/client/ErrorHandler/ErrorHandler.tsx index 09c3476d77a..03ff6f02baf 100644 --- a/core/src/client/ErrorHandler/ErrorHandler.tsx +++ b/core/src/client/ErrorHandler/ErrorHandler.tsx @@ -1,13 +1,13 @@ -import React from 'react'; +import React, { PureComponent } from 'react'; +import { ActionURL } from '@labkey/api'; -import { ErrorTopSection } from '../components/ErrorTopSection'; -import { ErrorType } from './ErrorType'; +import { getErrorHeading, getImage, getInstruction, getSubHeading, getViewDetails } from './ErrorType'; +import { ErrorDetails } from './model'; import './errorHandler.scss'; export interface AppContext { - message: string; - errorType: ErrorType; + errorDetails: ErrorDetails; } interface ErrorHandlerProps { @@ -18,38 +18,53 @@ interface ErrorHandlerState { showDetails: boolean; } -export class ErrorHandler extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - showDetails: false, - }; - } +export class ErrorHandler extends PureComponent { + state: Readonly = { showDetails: false }; onBackClick = (): void => { - window.history.back(); + // Back button - takes you back to the previous page if available + // and to the ‘home’ folder if not possible to go back to the previous page + if (window.history.length !== 1) { + // browsers like chrome stores their homepage as first item + window.history.back(); + } else { + window.location.href = ActionURL.getBaseURL(false); + } }; onViewDetailsClick = (): void => { - this.setState(() => ({ - showDetails: true, - })); + this.setState(state => ({ showDetails: !state.showDetails })); }; render() { - const { errorType, message } = this.props.context; + const { errorDetails } = this.props.context; const { showDetails } = this.state; + const viewDetailsBtnText = showDetails ? 'Hide Details' : 'View Details'; + return ( <> - - {/* TODO : ErrorPage, following section in next story*/} - {showDetails &&

{message}

} +
+
+
+
+
+ {getErrorHeading(errorDetails)} + {getSubHeading(errorDetails)} + {getInstruction(errorDetails)} + + +
+
+
{getImage(errorDetails)}
+
+
+
+ {showDetails &&
{getViewDetails(errorDetails)}
} ); } diff --git a/core/src/client/ErrorHandler/ErrorType.tsx b/core/src/client/ErrorHandler/ErrorType.tsx index 9e200e360c4..ed869621a2d 100644 --- a/core/src/client/ErrorHandler/ErrorType.tsx +++ b/core/src/client/ErrorHandler/ErrorType.tsx @@ -1,90 +1,314 @@ import React, { ReactNode } from 'react'; -import { imageURL } from '@labkey/components'; +import { Button } from 'react-bootstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCheckCircle, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; +import { helpLinkNode, imageURL } from '@labkey/components'; +import { ActionURL, Ajax, getServerContext } from '@labkey/api'; + +import { ErrorDetails, ErrorType } from './model'; const ERROR_HEADING = 'Oops! An error has occurred.'; -const NOTFOUND_SUBHEADING = <>It seems like something went wrong. The requested page cannot be found.; -const NOTFOUND_INSTRUCTION = ( +const DETAILS_SUB_INSTRUCTION = ( + <> +

What else can I do?

+

+ Search through the {helpLinkNode('default', 'LabKey support documentation')} and previous forum questions to + troubleshoot your issue. +

+

+ If you are part of a{' '} + + {' '} + LabKey Server Premium Edition + {' '} + subscription, please use your support portal or contact your account manager for assistance. If you are + using the free Community Edition, you may find help by posting on the{' '} + + LabKey support forum. + +

+ +); + +const NOTFOUND_HEADING = 'Oops! The requested page cannot be found.'; +const NOTFOUND_SUBHEADING = (errorMessage?: string) => ( + <> + {errorMessage !== undefined + ? errorMessage.endsWith('.') + ? errorMessage + : errorMessage + '.' + : 'It seems like something went wrong.'} + +); +const NOTFOUND_INSTRUCTION = (errorCode?: string) => ( <>
- {/* TODO: ErrorPage - add href link*/} - Please contact your admin or reference the LabKey support forum. + Please contact your admin or reference the{' '} + + LabKey support forum. +
-
- {/* TODO: ErrorPage - add href link*/} - If you would like to file a LabKey support ticket, your unique reference code is: + {errorCode !== undefined && errorCode !== null && ( +
+ If you would like to file a{' '} + + {' '} + LabKey support ticket + + , your unique reference code is: {errorCode} +
+ )} + +); +const NOTFOUND_DETAILS = (errorDetails: ErrorDetails) => ( + <> +
What went wrong?
+
+ +

+ {errorDetails.message !== undefined + ? 'Here are the most common errors:' + : 'Unfortunately, we are unable to specifically identify what went wrong. However, here are the most common\n' + + ' errors:'} +

+
+
+
    +
  • + Incorrect URL: the wrong web address has been typed. {helpLinkNode('url', 'Read More >')} +
  • +
+
+ Double check and + make sure that your URL has been correctly input. +
+
    +
  • + Permissions: your account does not have the permissions to view this page.{' '} + {helpLinkNode('permissionLevels', 'Read More >')} +
  • +
+
+ Contact your + administrator to request access. +
+
+ + {DETAILS_SUB_INSTRUCTION} + +); + +const PERMISSION_SUBHEADING = () => 'You do not have the permissions required to access this page.'; +const PERMISSION_INSTRUCTION = () => "Please contact this server's administrator to gain access."; +const PERMISSION_DETAILS = () => ( + <> +

What is a permission error?

+ +

+ A permission error occurs when the account you've logged into does not have the set permissions to access + this page. {helpLinkNode('permissionLevels', 'Read More >')} +

+
+ Try contacting your + server administrator to request access to this page.
+
+
    +
  • + {' '} + {getServerContext().user.isSignedIn + ? 'You are currently logged in as: ' + + (getServerContext().impersonatingUser !== undefined + ? getServerContext().impersonatingUser.displayName + : getServerContext().user.displayName) + : 'You are not logged in.'} +
  • +
+
+ {getServerContext().impersonatingUser !== undefined && ( +
+ You are currently + impersonating: {getServerContext().user.displayName} +
+
+ +
+ )} + +); + +const CONFIGURATION_HEADING = 'Oops! A server configuration error has occurred.'; +const CONFIGURATION_SUBHEADING = (errorMessage?: string) => ( + <> + {'The requested page cannot be found. '} + {errorMessage !== undefined + ? errorMessage.endsWith('.') + ? errorMessage + : errorMessage + '.' + : 'It seems like something went wrong.'} ); +const CONFIGURATION_INSTRUCTION = () => 'Please check your server configurations.'; +const CONFIGURATION_DETAILS = (errorDetails: ErrorDetails) => ( + <> +

What went wrong?

-const PERMISSION_SUBHEADING = <>You do not have the permissions required to access this page.; -const PERMISSION_INSTRUCTION = <>Please contact this server's admin to gain access.; +

+ {errorDetails.message !== undefined + ? errorDetails.message.endsWith('.') + ? errorDetails.message + : errorDetails.message + '.' + : 'Unfortunately, we are unable to specifically identify what went wrong.'}{' '} + It seems that there might be some issues with your server configuration. +

+
+
+
+
    +
  • + Server Configuration Errors: issues related to your machine, software version, or running + dependencies. {helpLinkNode('troubleshootingAdmin', 'Read More >')} +
  • +
+
+ Try restarting your + current instance of LabKey. +
+
-const CONFIGURATION_SUBHEADING = <>It seems like something went wrong. The requested page cannot be found.; -const CONFIGURATION_INSTRUCTION = <>Please check your server configurations.; + {DETAILS_SUB_INSTRUCTION} +
{errorDetails.stackTrace}
+ +); -const EXECUTION_SUB_HEADING = <>It seems like there is an issue with this installation of LabKey server.; -const EXECUTION_INSTRUCTION = ( +const EXECUTION_SUB_HEADING = (errorMessage?: string) => ( + <> + {' '} + {errorMessage !== undefined + ? errorMessage + : 'It seems like there is an issue with this installation of LabKey server.'} + +); +const EXECUTION_INSTRUCTION = (errorCode?: string) => ( <>
- Please report this bug to LabKey Support by copying and pasting both unique reference code - and code under 'view details'. + Please report this bug to{' '} + + {' '} + LabKey Support{' '} + {' '} + by copying and pasting both your unique reference code and the full stack trace in the View Details section + below. +
+
+ Your unique reference code is: {errorCode}
-
Your unique reference code is:
); +const EXECUTION_DETAILS = (errorDetails: ErrorDetails) =>
{errorDetails.stackTrace}
; -export enum ErrorType { - notFound = 'general', - permission = 'permission', - configuration = 'configuration', - execution = 'execution', -} +type ErrorTypeInfo = { + details: (errorDetails?: ErrorDetails) => ReactNode; + heading: string; + subHeading: (errorMessage?: string) => ReactNode; + imagePath: string; + instruction: (errorCode?: string) => ReactNode; +}; -const ERROR_TYPE_INFO = { - notFound: { - heading: NOTFOUND_SUBHEADING, - instruction: NOTFOUND_INSTRUCTION, - imagePath: 'notFound_error.svg', - }, - permission: { - heading: PERMISSION_SUBHEADING, - instruction: PERMISSION_INSTRUCTION, - imagePath: 'permission_error.svg', - }, +const ERROR_TYPE_INFO: { [key in ErrorType]: ErrorTypeInfo } = { configuration: { - heading: CONFIGURATION_SUBHEADING, - instruction: CONFIGURATION_INSTRUCTION, + details: CONFIGURATION_DETAILS, + heading: CONFIGURATION_HEADING, imagePath: 'configuration_error.svg', + instruction: CONFIGURATION_INSTRUCTION, + subHeading: CONFIGURATION_SUBHEADING, }, execution: { - heading: EXECUTION_SUB_HEADING, - instruction: EXECUTION_INSTRUCTION, + details: EXECUTION_DETAILS, + heading: ERROR_HEADING, imagePath: 'code_error.svg', + instruction: EXECUTION_INSTRUCTION, + subHeading: EXECUTION_SUB_HEADING, + }, + notFound: { + details: NOTFOUND_DETAILS, + heading: NOTFOUND_HEADING, + imagePath: 'notFound_error.svg', + instruction: NOTFOUND_INSTRUCTION, + subHeading: NOTFOUND_SUBHEADING, + }, + permission: { + details: PERMISSION_DETAILS, + heading: ERROR_HEADING, + imagePath: 'permission_error.svg', + instruction: PERMISSION_INSTRUCTION, + subHeading: PERMISSION_SUBHEADING, }, }; -export const getErrorHeading = (): ReactNode => { - return
{ERROR_HEADING}
; +export const getErrorHeading = (errorDetails: ErrorDetails): ReactNode => { + const info = ERROR_TYPE_INFO[errorDetails.errorType]; + if (!info) return null; + + return
{info.heading}
; }; -export const getImage = (errorType: ErrorType): ReactNode => { - if (ERROR_TYPE_INFO[errorType]) { - const path = ERROR_TYPE_INFO[errorType].imagePath; - return LabKey Error; - } +export const getImage = (errorDetails: ErrorDetails): ReactNode => { + const info = ERROR_TYPE_INFO[errorDetails.errorType]; + if (!info) return null; + + return LabKey Error; }; -export const getSubHeading = (errorType: ErrorType): ReactNode => { - if (ERROR_TYPE_INFO[errorType]) { - const subHeading = ERROR_TYPE_INFO[errorType].heading; - return
{subHeading}
; - } +export const getSubHeading = (errorDetails: ErrorDetails): ReactNode => { + const info = ERROR_TYPE_INFO[errorDetails.errorType]; + if (!info) return null; + + return
{info.subHeading(errorDetails.message)}
; }; -export const getInstruction = (errorType: ErrorType): ReactNode => { - if (ERROR_TYPE_INFO[errorType]) { - const instruction = ERROR_TYPE_INFO[errorType].instruction; - return
{instruction}
; - } +export const getInstruction = (errorDetails: ErrorDetails): ReactNode => { + const info = ERROR_TYPE_INFO[errorDetails.errorType]; + if (!info) return null; + + return
{info.instruction(errorDetails.errorCode)}
; +}; + +export const getViewDetails = (errorDetails: ErrorDetails): ReactNode => { + const { errorType, message, stackTrace } = errorDetails; + const info = ERROR_TYPE_INFO[errorType]; + if (!info) return null; + + return ( +
+
+
{info.details(errorDetails)}
+
+
+ ); }; diff --git a/core/src/client/ErrorHandler/errorHandler.scss b/core/src/client/ErrorHandler/errorHandler.scss index c52dc29f7c3..2f45b9b443b 100644 --- a/core/src/client/ErrorHandler/errorHandler.scss +++ b/core/src/client/ErrorHandler/errorHandler.scss @@ -1,37 +1,83 @@ @import "~@labkey/components/dist/assets/scss/theme"; -.labkey-error-top { - width: 100% -} - .labkey-error-heading { color : #B82025; font-size: 48px; - font-weight: 600 } .labkey-error-subheading { font-size: 24px; text-overflow: ellipsis; padding-bottom: 3rem; - width: 587px } .labkey-error-instruction { font-size: 18px; padding-bottom: 15px; - width: 560px; +} + +.labkey-error-details { + font-size: 16px; +} + +.labkey-error-subdetails { + padding-left: 5rem; + padding-bottom: 3rem; +} + +.labkey-error-details-question { + font-weight: bold; } .error-backButton { + display: inline-block; + text-align: center; margin: 0.5rem 5rem 0.5rem 0.5rem; background-color: #538AC5; font-size: 14px; - width: 10%; padding-left: 15px; padding-right: 15px; } +.error-details-body { + padding: 15px; +} + .error-details-btn { + display: inline-block; + text-align: center; +} + +.error-details-container { + position: relative; + background: #F3F3F4; + border: 1px solid transparent; + margin-top: 5rem; + padding: 3rem 1rem; +} + +.error-details-container:before, .error-details-container:after { + content: ''; + position: absolute; + bottom: 100%; + border: 4rem solid transparent; + border-bottom-color: #F3F3F4; + + @media (min-width: $screen-xs-min) { + left: 14rem; + } + @media (min-width: $screen-md-min) { + left: 22rem; + } + @media (min-width: $screen-lg-min) { + left: 25rem; + } +} + +.permission-warning-icon { + color: #B82025; +} +.error-page-br { + margin-bottom: 1rem; } \ No newline at end of file diff --git a/core/src/client/ErrorHandler/model.ts b/core/src/client/ErrorHandler/model.ts new file mode 100644 index 00000000000..0e41da8c5ad --- /dev/null +++ b/core/src/client/ErrorHandler/model.ts @@ -0,0 +1,13 @@ +export enum ErrorType { + notFound = 'notFound', + permission = 'permission', + configuration = 'configuration', + execution = 'execution', +} + +export interface ErrorDetails { + errorCode?: string; + errorType: ErrorType; + message?: string; + stackTrace?: string; +} diff --git a/core/src/client/components/ErrorTopSection.tsx b/core/src/client/components/ErrorTopSection.tsx deleted file mode 100644 index 60badd18141..00000000000 --- a/core/src/client/components/ErrorTopSection.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { ReactNode } from 'react'; -import { Button, Col, Row } from 'react-bootstrap'; - -import '../ErrorHandler/errorHandler.scss'; -import { ErrorType, getErrorHeading, getImage, getInstruction, getSubHeading } from '../ErrorHandler/ErrorType'; - -interface ErrorTopSectionProps { - errorType: ErrorType; - onBackClick: () => void; - onViewDetailsClick: () => void; -} - -export class ErrorTopSection extends React.PureComponent { - render(): ReactNode { - const { errorType, onBackClick, onViewDetailsClick } = this.props; - return ( - <> -
- - -
- {getErrorHeading()} - {getSubHeading(errorType)} - {getInstruction(errorType)} - - -
- - {getImage(errorType)} -
-
- - ); - } -} diff --git a/core/src/org/labkey/core/CoreModule.java b/core/src/org/labkey/core/CoreModule.java index 06673496d1a..6a58b3fd270 100644 --- a/core/src/org/labkey/core/CoreModule.java +++ b/core/src/org/labkey/core/CoreModule.java @@ -387,8 +387,6 @@ public QuerySchema createSchema(DefaultSchema schema, Module module) AdminConsole.addExperimentalFeatureFlag(RemapCache.EXPERIMENTAL_RESOLVE_LOOKUPS_BY_VALUE, "Resolve lookups by Value", "This feature will attempt to resolve lookups by value through the UI insert/update form. This can be useful when the " + "lookup list is long (> 10000) and the UI stops rendering a dropdown.", false); - AdminConsole.addExperimentalFeatureFlag(AppProps.EXPERIMENTAL_ERROR_PAGE, "New Error Page", - "This is the new error page.", false); SiteValidationService svc = SiteValidationService.get(); if (null != svc) diff --git a/core/src/org/labkey/core/view/template/bootstrap/pageTemplate.jsp b/core/src/org/labkey/core/view/template/bootstrap/pageTemplate.jsp index 9962b532d58..745a798eaa4 100644 --- a/core/src/org/labkey/core/view/template/bootstrap/pageTemplate.jsp +++ b/core/src/org/labkey/core/view/template/bootstrap/pageTemplate.jsp @@ -98,7 +98,11 @@ <% } %> <% - String anchor = model.getAnchor(url); + String anchor = null; + if (null != url) + { + anchor = model.getAnchor(url); + } if (null != anchor) { %> @@ -108,18 +112,14 @@ %> -<%-- TODO : ErrorPage, look into this--%> <% - try + // container is null for notfound pages + if (null != me.getViewContext().getContainer()) { %> - + <% } - catch (Exception e) - { - LogManager.getLogger().info("This will be resolved in next error page story", e); - } %> \ No newline at end of file diff --git a/core/src/org/labkey/core/webdav/DavController.java b/core/src/org/labkey/core/webdav/DavController.java index 4458009b88e..8bec99c99fb 100644 --- a/core/src/org/labkey/core/webdav/DavController.java +++ b/core/src/org/labkey/core/webdav/DavController.java @@ -25,8 +25,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.FastDateFormat; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.json.JSONArray; @@ -72,25 +72,7 @@ import org.labkey.api.settings.AppProps; import org.labkey.api.settings.LookAndFeelProperties; import org.labkey.api.test.TestWhen; -import org.labkey.api.util.CSRFException; -import org.labkey.api.util.CSRFUtil; -import org.labkey.api.util.ConfigurationException; -import org.labkey.api.util.DateUtil; -import org.labkey.api.util.ExceptionUtil; -import org.labkey.api.util.FileStream; -import org.labkey.api.util.FileUtil; -import org.labkey.api.util.GUID; -import org.labkey.api.util.HeartBeat; -import org.labkey.api.util.HttpUtil; -import org.labkey.api.util.MemTracker; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.Pair; -import org.labkey.api.util.Path; -import org.labkey.api.util.ResponseHelper; -import org.labkey.api.util.ShutdownListener; -import org.labkey.api.util.StringUtilsLabKey; -import org.labkey.api.util.URLHelper; -import org.labkey.api.util.UnexpectedException; +import org.labkey.api.util.*; import org.labkey.api.view.ActionURL; import org.labkey.api.view.DefaultModelAndView; import org.labkey.api.view.JspView; @@ -168,7 +150,25 @@ import java.nio.file.attribute.FileTime; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; @@ -195,6 +195,7 @@ public class DavController extends SpringActionController public static final String name = "_dav_"; public static final String mimeSeparation = "<[[mime " + GUID.makeHash() + "_separator_]]>"; + public static final String CONTENT_TYPE_HTML = "text/html"; static boolean _readOnly = false; static boolean _locking = true; @@ -469,6 +470,19 @@ WebdavStatus sendError(WebdavStatus status, String message) super.setStatus(HttpServletResponse.SC_OK); } } + else if (CONTENT_TYPE_HTML.equals(getRequest().getHeader("Content-Type)")) || + accept.contains(CONTENT_TYPE_HTML)) + { + try + { + ExceptionUtil.renderErrorView(getViewContext(), new PageConfig(), ErrorRenderer.ErrorType.notFound, status.code, message, null, false, false ); + return WebdavStatus.fromCode(status.code); + } + catch (Exception e) + { + super.sendError(status.code, message); + } + } else { super.sendError(status.code, message); @@ -776,15 +790,8 @@ else if ("GET".equals(method) && HttpUtil.isBrowser(getRequest())) if (null != ss) ss.notFound((URLHelper)getRequest().getAttribute(ViewServlet.ORIGINAL_URL_URLHELPER)); } - if (AppProps.getInstance().isExperimentalFeatureEnabled(AppProps.EXPERIMENTAL_ERROR_PAGE)) - { - ExceptionUtil.handleException(request, response, dex, dex.getMessage(), false, null); - } - // TODO: ErrorPage, look into inserting the following details generated by sendError in ViewDetails section - else - { - getResponse().sendError(dex.getStatus(), dex.getMessage()); - } + getResponse().sendError(dex.getStatus(), dex.getMessage()); + if (dex.getStatus() != null && dex.getStatus().code == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { ExceptionUtil.logExceptionToMothership(request, dex); diff --git a/experiment/package-lock.json b/experiment/package-lock.json index 93705a11cf4..01f4eadc9cc 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -172,9 +172,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" }, "@fortawesome/fontawesome-free": { "version": "5.14.0", @@ -243,21 +243,21 @@ "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, "@labkey/api": { - "version": "1.1.0", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.0.tgz", - "integrity": "sha1-tw++dJ18OzCud/XY6eEqLN5c7b4=" + "version": "1.1.1", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.1.tgz", + "integrity": "sha1-q4diKlMLLdE6QjjCAhCKfz9qhNY=" }, "@labkey/components": { - "version": "0.96.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.96.2.tgz", - "integrity": "sha1-S5Ee76gyJfrhAB8UvyCqFFHaw1o=", + "version": "0.97.0", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.97.0.tgz", + "integrity": "sha1-EeGjxLxb4h+c9AWbmJzdj/vCRw0=", "requires": { "@fortawesome/fontawesome-free": "5.14.0", "@fortawesome/fontawesome-svg-core": "1.2.30", "@fortawesome/free-regular-svg-icons": "5.14.0", "@fortawesome/free-solid-svg-icons": "5.14.0", "@fortawesome/react-fontawesome": "0.1.11", - "@labkey/api": "1.1.0", + "@labkey/api": "1.1.1", "bootstrap": "3.4.1", "classnames": "2.2.6", "font-awesome": "4.7.0", diff --git a/experiment/package.json b/experiment/package.json index 089f83a083f..bf3dbc6b824 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -11,7 +11,7 @@ "clean": "rimraf resources/web/experiment/gen && rimraf resources/views/*.*" }, "dependencies": { - "@labkey/components": "0.96.2" + "@labkey/components": "0.97.0" }, "devDependencies": { "@hot-loader/react-dom": "16.13.0", diff --git a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java index e6ac11fbc5d..ac882c76ce0 100644 --- a/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java +++ b/experiment/src/org/labkey/experiment/controllers/exp/ExperimentController.java @@ -150,6 +150,9 @@ import org.labkey.api.study.StudyUrls; import org.labkey.api.util.DOM; import org.labkey.api.util.DOM.LK; +import org.labkey.api.util.ErrorRenderer; +import org.labkey.api.util.ErrorView; +import org.labkey.api.util.ErrorView; import org.labkey.api.util.ExceptionUtil; import org.labkey.api.util.FileUtil; import org.labkey.api.util.HelpTopic; @@ -2235,8 +2238,7 @@ public void export(ConvertArraysToExcelForm form, HttpServletResponse response, catch (JSONException | ClassCastException e) { // We can get a ClassCastException if we expect an array and get a simple String, for example - HttpView errorView = ExceptionUtil.getErrorView(HttpServletResponse.SC_BAD_REQUEST, "Failed to convert to Excel - invalid input", e, getViewContext().getRequest(), false); - errorView.render(getViewContext().getRequest(), getViewContext().getResponse()); + ExceptionUtil.renderErrorView(getViewContext(), getPageConfig(), ErrorRenderer.ErrorType.notFound, HttpServletResponse.SC_BAD_REQUEST, "Failed to convert to Excel - invalid input", e, false, false); } } } @@ -2305,8 +2307,7 @@ public void export(ConvertArraysToExcelForm form, HttpServletResponse response, } catch (JSONException e) { - HttpView errorView = ExceptionUtil.getErrorView(HttpServletResponse.SC_BAD_REQUEST, "Failed to convert to table - invalid input", e, getViewContext().getRequest(), false); - errorView.render(getViewContext().getRequest(), getViewContext().getResponse()); + ExceptionUtil.renderErrorView(getViewContext(), getPageConfig(), ErrorRenderer.ErrorType.notFound, HttpServletResponse.SC_BAD_REQUEST, "Failed to convert to table - invalid input", e, false, false); } } } diff --git a/internal/webapp/labkey.js b/internal/webapp/labkey.js index 8c95e34cb5f..a8b0db95736 100644 --- a/internal/webapp/labkey.js +++ b/internal/webapp/labkey.js @@ -354,11 +354,30 @@ if (typeof LABKEY == "undefined") return qs; }; + var slash = function(part) + { + return part ? (part.indexOf('/') === 0 ? '' : '/') + part : ''; + } + + // Composes a URL from origin so as not to assume pathing + var buildURL = function(controller, action, params, containerPath) + { + var qsParams = qs(params); + + return [ + window.location.origin, + slash(configs.contextPath), + slash(containerPath), + slash(controller + '-' + action), + qsParams ? '?' + qsParams : '' + ].join(''); + } + // So as not to confuse with native support for fetch() - var _fetch = function(url, params, success, failure) + var _fetch = function(controller, action, params, containerPath, success, failure) { var xhr = new XMLHttpRequest(); - var _url = url + (url.indexOf('?') === -1 ? '?' : '&') + qs(params); + var url = buildURL(controller, action, params, containerPath); xhr.onreadystatechange = function() { @@ -369,7 +388,7 @@ if (typeof LABKEY == "undefined") } }; - xhr.open('GET', _url, true); + xhr.open('GET', url, true); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); if (LABKEY.CSRF) @@ -559,9 +578,10 @@ if (typeof LABKEY == "undefined") scriptCache.callbacksOnCache(_lib); }; - _fetch('core-loadLibrary.api', { + // fetch library definition from the root container + _fetch('core', 'loadLibrary.api', { library: _lib - }, function(data) { + }, undefined, function(data) { // success var json = JSON.parse(data.responseText); var definition = json['libraries'][_lib]; diff --git a/issues/package-lock.json b/issues/package-lock.json index f565d8b35aa..7cc1bd0ce27 100644 --- a/issues/package-lock.json +++ b/issues/package-lock.json @@ -172,9 +172,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" }, "@fortawesome/fontawesome-free": { "version": "5.14.0", @@ -243,21 +243,21 @@ "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, "@labkey/api": { - "version": "1.1.0", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.0.tgz", - "integrity": "sha1-tw++dJ18OzCud/XY6eEqLN5c7b4=" + "version": "1.1.1", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.1.tgz", + "integrity": "sha1-q4diKlMLLdE6QjjCAhCKfz9qhNY=" }, "@labkey/components": { - "version": "0.96.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.96.2.tgz", - "integrity": "sha1-S5Ee76gyJfrhAB8UvyCqFFHaw1o=", + "version": "0.97.0", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.97.0.tgz", + "integrity": "sha1-EeGjxLxb4h+c9AWbmJzdj/vCRw0=", "requires": { "@fortawesome/fontawesome-free": "5.14.0", "@fortawesome/fontawesome-svg-core": "1.2.30", "@fortawesome/free-regular-svg-icons": "5.14.0", "@fortawesome/free-solid-svg-icons": "5.14.0", "@fortawesome/react-fontawesome": "0.1.11", - "@labkey/api": "1.1.0", + "@labkey/api": "1.1.1", "bootstrap": "3.4.1", "classnames": "2.2.6", "font-awesome": "4.7.0", diff --git a/issues/package.json b/issues/package.json index 4145d6c71df..18ec64623d5 100644 --- a/issues/package.json +++ b/issues/package.json @@ -11,7 +11,7 @@ "clean": "rimraf resources/web/issues/gen && rimraf resources/views/*.*" }, "dependencies": { - "@labkey/components": "0.96.2" + "@labkey/components": "0.97.0" }, "devDependencies": { "@hot-loader/react-dom": "16.13.0", diff --git a/list/package-lock.json b/list/package-lock.json index d8eb16ea575..177d9df872f 100644 --- a/list/package-lock.json +++ b/list/package-lock.json @@ -172,9 +172,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" }, "@fortawesome/fontawesome-free": { "version": "5.14.0", @@ -243,21 +243,21 @@ "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, "@labkey/api": { - "version": "1.1.0", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.0.tgz", - "integrity": "sha1-tw++dJ18OzCud/XY6eEqLN5c7b4=" + "version": "1.1.1", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.1.tgz", + "integrity": "sha1-q4diKlMLLdE6QjjCAhCKfz9qhNY=" }, "@labkey/components": { - "version": "0.96.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.96.2.tgz", - "integrity": "sha1-S5Ee76gyJfrhAB8UvyCqFFHaw1o=", + "version": "0.97.0", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.97.0.tgz", + "integrity": "sha1-EeGjxLxb4h+c9AWbmJzdj/vCRw0=", "requires": { "@fortawesome/fontawesome-free": "5.14.0", "@fortawesome/fontawesome-svg-core": "1.2.30", "@fortawesome/free-regular-svg-icons": "5.14.0", "@fortawesome/free-solid-svg-icons": "5.14.0", "@fortawesome/react-fontawesome": "0.1.11", - "@labkey/api": "1.1.0", + "@labkey/api": "1.1.1", "bootstrap": "3.4.1", "classnames": "2.2.6", "font-awesome": "4.7.0", diff --git a/list/package.json b/list/package.json index 460b039bac5..71569ce13c4 100644 --- a/list/package.json +++ b/list/package.json @@ -11,7 +11,7 @@ "clean": "rimraf resources/web/list/gen && rimraf resources/views/*.*" }, "dependencies": { - "@labkey/components": "0.96.2" + "@labkey/components": "0.97.0" }, "devDependencies": { "@hot-loader/react-dom": "16.13.0", diff --git a/mothership/src/org/labkey/mothership/MothershipController.java b/mothership/src/org/labkey/mothership/MothershipController.java index bec20280541..76e6b8bea41 100644 --- a/mothership/src/org/labkey/mothership/MothershipController.java +++ b/mothership/src/org/labkey/mothership/MothershipController.java @@ -1370,7 +1370,7 @@ public class ThrowConfigurationExceptionAction extends SimpleViewAction @Override public ModelAndView getView(Object form, BindException errors) { - throw new ConfigurationException("Test Configuration exception"); + throw new ConfigurationException("This is a test message for configuration exception."); } @Override @@ -1386,7 +1386,7 @@ public class ThrowNotFoundExceptionAction extends SimpleViewAction @Override public ModelAndView getView(Object form, BindException errors) { - throw new NotFoundException("Test NotFound exception"); + throw new NotFoundException("This is a test message for not found exception."); } @Override @@ -1418,7 +1418,7 @@ public class ThrowExecutionExceptionAction extends SimpleViewAction @Override public ModelAndView getView(Object form, BindException errors) { - throw new NullPointerException("Test Execution exception"); + throw new NullPointerException("This is a test message for execution exception"); } @Override diff --git a/mothership/test/src/org/labkey/test/tests/mothership/MothershipTest.java b/mothership/test/src/org/labkey/test/tests/mothership/MothershipTest.java index 5085c4adedb..e074d890bb7 100644 --- a/mothership/test/src/org/labkey/test/tests/mothership/MothershipTest.java +++ b/mothership/test/src/org/labkey/test/tests/mothership/MothershipTest.java @@ -273,10 +273,10 @@ public void testNoErrorCodeWithReportingDisabled() private String getErrorCode() { - if (!isElementPresent(Locator.tagWithClass("table", "server-error"))) + if (!isElementPresent(Locator.tagWithClass("div", "labkey-error-instruction"))) fail("Expected to be on an error page"); - String error = Locators.labkeyError.findElement(getDriver()).getText(); - Pattern errorCodePattern = Pattern.compile(".*please refer to error code: ([^\\s]+)"); + String error = Locators.labkeyErrorInstruction.findElement(getDriver()).getText(); + Pattern errorCodePattern = Pattern.compile(".*Your unique reference code is: ([^\\s]+)"); Matcher matcher = errorCodePattern.matcher(error); if(matcher.find()) return matcher.group(1); diff --git a/query/package-lock.json b/query/package-lock.json index 2cd845d0541..2203b13369b 100644 --- a/query/package-lock.json +++ b/query/package-lock.json @@ -172,9 +172,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" }, "@fortawesome/fontawesome-free": { "version": "5.14.0", @@ -243,21 +243,21 @@ "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, "@labkey/api": { - "version": "1.1.0", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.0.tgz", - "integrity": "sha1-tw++dJ18OzCud/XY6eEqLN5c7b4=" + "version": "1.1.1", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.1.tgz", + "integrity": "sha1-q4diKlMLLdE6QjjCAhCKfz9qhNY=" }, "@labkey/components": { - "version": "0.96.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.96.2.tgz", - "integrity": "sha1-S5Ee76gyJfrhAB8UvyCqFFHaw1o=", + "version": "0.97.0", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.97.0.tgz", + "integrity": "sha1-EeGjxLxb4h+c9AWbmJzdj/vCRw0=", "requires": { "@fortawesome/fontawesome-free": "5.14.0", "@fortawesome/fontawesome-svg-core": "1.2.30", "@fortawesome/free-regular-svg-icons": "5.14.0", "@fortawesome/free-solid-svg-icons": "5.14.0", "@fortawesome/react-fontawesome": "0.1.11", - "@labkey/api": "1.1.0", + "@labkey/api": "1.1.1", "bootstrap": "3.4.1", "classnames": "2.2.6", "font-awesome": "4.7.0", diff --git a/query/package.json b/query/package.json index aca82255e4f..8ad050a203d 100644 --- a/query/package.json +++ b/query/package.json @@ -11,7 +11,7 @@ "clean": "rimraf resources/web/query/gen && rimraf resources/views/queryMetadataEditor*" }, "dependencies": { - "@labkey/components": "0.96.2" + "@labkey/components": "0.97.0" }, "devDependencies": { "@hot-loader/react-dom": "16.13.0", diff --git a/study/package-lock.json b/study/package-lock.json index d233b75b5ab..415d9f97f9b 100644 --- a/study/package-lock.json +++ b/study/package-lock.json @@ -172,9 +172,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz", + "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag==" }, "@fortawesome/fontawesome-free": { "version": "5.14.0", @@ -243,21 +243,21 @@ "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==" }, "@labkey/api": { - "version": "1.1.0", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.0.tgz", - "integrity": "sha1-tw++dJ18OzCud/XY6eEqLN5c7b4=" + "version": "1.1.1", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.1.1.tgz", + "integrity": "sha1-q4diKlMLLdE6QjjCAhCKfz9qhNY=" }, "@labkey/components": { - "version": "0.96.2", - "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.96.2.tgz", - "integrity": "sha1-S5Ee76gyJfrhAB8UvyCqFFHaw1o=", + "version": "0.97.0", + "resolved": "https://artifactory.labkey.com/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-0.97.0.tgz", + "integrity": "sha1-EeGjxLxb4h+c9AWbmJzdj/vCRw0=", "requires": { "@fortawesome/fontawesome-free": "5.14.0", "@fortawesome/fontawesome-svg-core": "1.2.30", "@fortawesome/free-regular-svg-icons": "5.14.0", "@fortawesome/free-solid-svg-icons": "5.14.0", "@fortawesome/react-fontawesome": "0.1.11", - "@labkey/api": "1.1.0", + "@labkey/api": "1.1.1", "bootstrap": "3.4.1", "classnames": "2.2.6", "font-awesome": "4.7.0", diff --git a/study/package.json b/study/package.json index ee44ef264c2..98c2d0d3694 100644 --- a/study/package.json +++ b/study/package.json @@ -11,7 +11,7 @@ "clean": "rimraf resources/web/study/gen && rimraf resources/views/datasetDesigner*" }, "dependencies": { - "@labkey/components": "0.96.2" + "@labkey/components": "0.97.0" }, "devDependencies": { "@hot-loader/react-dom": "16.13.0",