From 67d3ff4b46e38627585f776d13a65e0b8b8256fc Mon Sep 17 00:00:00 2001 From: Tom Robinson <tlrobinson@gmail.com> Date: Mon, 19 Oct 2015 15:46:21 -0700 Subject: [PATCH] Server/timeout error screens + admin email setting --- frontend/src/card/card.controllers.js | 3 +- frontend/src/css/query_builder.css | 35 ++++++++++++++++ frontend/src/lib/settings.js | 3 ++ .../src/query_builder/QueryVisualization.jsx | 42 +++++++++++++------ .../frontend_client/app/img/stopwatch.svg | 12 ++++++ src/metabase/models/setting.clj | 3 +- 6 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 resources/frontend_client/app/img/stopwatch.svg diff --git a/frontend/src/card/card.controllers.js b/frontend/src/card/card.controllers.js index 2c9f87d587e..e9dea044e01 100644 --- a/frontend/src/card/card.controllers.js +++ b/frontend/src/card/card.controllers.js @@ -409,6 +409,7 @@ CardControllers.controller('CardDetail', [ renderAll(); + let startTime = new Date(); // make our api call Metabase.dataset(dataset_query, function (result) { queryResult = result; @@ -484,7 +485,7 @@ CardControllers.controller('CardDetail', [ }, function (error) { isRunning = false; - queryResult = { error: error }; + queryResult = { error: error, duration: new Date() - startTime }; renderAll(); }); diff --git a/frontend/src/css/query_builder.css b/frontend/src/css/query_builder.css index 4f5ffc8bf47..0cf66effb8c 100644 --- a/frontend/src/css/query_builder.css +++ b/frontend/src/css/query_builder.css @@ -254,10 +254,45 @@ background-image: url('/app/img/no_understand.svg'); } +.QueryError-image--serverError { + width: 120px; + height: 120px; + background-image: url('/app/img/stopwatch.svg'); +} + +.QueryError-image--timeout { + width: 120px; + height: 120px; + background-image: url('/app/img/stopwatch.svg'); +} + .QueryError-messageText { line-height: 1.4; } +.QueryError-adminEmail { + position: relative; + display: inline-block; + border-radius: var(--default-border-radius); + border: 1px solid rgb(197, 197, 197); + margin-top: var(--margin-2); + padding: var(--padding-1) var(--padding-4) var(--padding-1) var(--padding-4); +} + +.QueryError-adminEmail:before { + content: "Admin Email"; + font-size: 10px; + text-align: center; + text-transform: uppercase; + background-color: white; + padding-left: var(--padding-1); + padding-right: var(--padding-1); + + position: absolute; + top: -0.75em; + left: 50%; + margin-left: -41px; /* ugh */ +} /* GUI BUILDER */ diff --git a/frontend/src/lib/settings.js b/frontend/src/lib/settings.js index 1d846ebca35..94db204f769 100644 --- a/frontend/src/lib/settings.js +++ b/frontend/src/lib/settings.js @@ -19,6 +19,9 @@ const MetabaseSettings = { }, // these are all special accessors which provide a lookup of a property plus some additional help + adminEmail: function() { + return mb_settings.admin_email; + }, isEmailConfigured: function() { return mb_settings.email_configured; diff --git a/frontend/src/query_builder/QueryVisualization.jsx b/frontend/src/query_builder/QueryVisualization.jsx index 814dd67366b..e5d4155ebe5 100644 --- a/frontend/src/query_builder/QueryVisualization.jsx +++ b/frontend/src/query_builder/QueryVisualization.jsx @@ -8,6 +8,7 @@ import QueryVisualizationObjectDetailTable from './QueryVisualizationObjectDetai import RunButton from './RunButton.jsx'; import VisualizationSettings from './VisualizationSettings.jsx'; +import MetabaseSettings from "metabase/lib/settings"; import Query from "metabase/lib/query"; import cx from "classnames"; @@ -167,23 +168,39 @@ export default class QueryVisualization extends Component { </div> ); } else { - let error = this.props.result.error; + let { result } = this.props; + let error = result.error; if (error) { let message; - // transform HTTP errors to plain text error messages, and always show them if (typeof error.status === "number") { - if (error.status === 503) { - error = "I'm sorry, the server timed out while asking your question."; + let adminEmail = MetabaseSettings.adminEmail(); + // Assume if the request took more than 15 seconds it was due to a timeout + // Some platforms like Heroku return a 503 for numerous types of errors so we can't use the status code to distinguish between timeouts and other failures. + if (result.duration > 15*1000) { + viz = ( + <div className="QueryError flex full align-center"> + <div className="QueryError-image QueryError-image--serverError"></div> + <div className="QueryError-message text-centered"> + <h1 className="text-bold">Your question took too long</h1> + <p className="QueryError-messageText">We didn't get an answer back from your database in time, so we had to stop. You can try again in a minute, or if the problem persists, you can email an admin to let them know.</p> + {adminEmail && <span className="QueryError-adminEmail"><a className="no-decoration" href={"mailto:"+adminEmail}>{adminEmail}</a></span>} + </div> + </div> + ); } else { - error = "I'm sorry, an error occured: " + error.statusText; + viz = ( + <div className="QueryError flex full align-center"> + <div className="QueryError-image QueryError-image--timeout"></div> + <div className="QueryError-message text-centered"> + <h1 className="text-bold">We're experiencing server issues</h1> + <p className="QueryError-messageText">Try refreshing the page after waiting a minute or two. If the problem persists we'd recommend you contact an admin.</p> + {adminEmail && <span className="QueryError-adminEmail"><a className="no-decoration" href={"mailto:"+adminEmail}>{adminEmail}</a></span>} + </div> + </div> + ); } - message = error; - } - // always show errors for native queries - if (this.props.card.dataset_query && this.props.card.dataset_query.type === 'native') { - message = error; - } - if (message) { + } else if (this.props.card.dataset_query && this.props.card.dataset_query.type === 'native') { + // always show errors for native queries viz = ( <div className="QueryError flex full align-center text-error"> <div className="QueryError-iconWrapper"> @@ -194,7 +211,6 @@ export default class QueryVisualization extends Component { <span className="QueryError-message">{message}</span> </div> ); - } else { viz = ( <div className="QueryError flex full align-center"> diff --git a/resources/frontend_client/app/img/stopwatch.svg b/resources/frontend_client/app/img/stopwatch.svg new file mode 100644 index 00000000000..c5f1b1cf13e --- /dev/null +++ b/resources/frontend_client/app/img/stopwatch.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="108px" height="118px" viewBox="0 0 108 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <circle stroke="#E3E3E3" stroke-width="3" fill="#FFFFFF" cx="53" cy="65" r="52"></circle> + <path d="M54.9857911,57.2483919 C54.9951751,57.1698571 55,57.0899169 55,57.008845 L55,38.991155 C55,37.8896739 54.1045695,37 53,37 C51.8877296,37 51,37.8914705 51,38.991155 L51,57.008845 C51,57.0899255 51.0048519,57.1698584 51.0142819,57.2483732 C47.5565832,58.1314817 45,61.2671799 45,65 C45,69.418278 48.581722,73 53,73 C57.418278,73 61,69.418278 61,65 C61,61.2672062 58.4434528,58.1315259 54.9857911,57.2483919 Z" fill="#E3E3E3"></path> + <rect stroke="#E3E3E3" stroke-width="3" fill="#F8F8F8" x="45" y="1" width="16" height="15" rx="6"></rect> + <path d="M52,101 L54,101 L54,108 L52,108 L52,101 Z M52,22 L54,22 L54,29 L52,29 L52,22 Z M89,66 L89,64 L95.7338129,64 L95.7338129,66 L89,66 Z M9.52844494,66 L9.52844494,64 L16.2622579,64 L16.2622579,66 L9.52844494,66 Z M78.0473135,92.2999886 L79.4615271,90.8857751 L84.2230519,95.6472999 L82.8088383,97.0615134 L78.0473135,92.2999886 Z M22.0473135,34.2999886 L23.4615271,32.8857751 L28.2230519,37.6472999 L26.8088383,39.0615134 L22.0473135,34.2999886 Z M26.8088383,90.8857751 L28.2230519,92.2999886 L23.4615271,97.0615134 L22.0473135,95.6472999 L26.8088383,90.8857751 Z M82.8088383,32.8857751 L84.2230519,34.2999886 L79.4615271,39.0615134 L78.0473135,37.6472999 L82.8088383,32.8857751 Z" stroke="#E3E3E3" stroke-width="3" fill="#F8F8F8"></path> + <path d="M89,7 C91.2273181,4.42229162 94.9764573,4.42395737 97,7 L105,15 C107.579027,17.0283807 107.575629,20.7737606 105,23 L103,25 C100.772682,27.5777084 97.0235427,27.5760426 95,25 L87,17 C84.4209732,14.9716193 84.4243711,11.2262394 87,9 L89,7 L89,7 Z" stroke="#E3E3E3" stroke-width="3" fill="#F8F8F8"></path> + <rect stroke="#E3E3E3" stroke-width="3" fill="#F8F8F8" transform="translate(89.038462, 22.726619) rotate(-45.000000) translate(-89.038462, -22.726619) " x="84.8461538" y="17.676259" width="8.38461538" height="10.1007194"></rect> + <circle fill="#F8F8F8" cx="53" cy="65" r="5"></circle> + </g> +</svg> diff --git a/src/metabase/models/setting.clj b/src/metabase/models/setting.clj index b123d70c970..5a2a78d5a3d 100644 --- a/src/metabase/models/setting.clj +++ b/src/metabase/models/setting.clj @@ -164,7 +164,8 @@ :anon_tracking_enabled (let [tracking? (get :anon-tracking-enabled)] (or (nil? tracking?) (= "true" tracking?))) :site_name (get :site-name) - :email_configured (not (s/blank? (get :email-smtp-host)))}) + :email_configured (not (s/blank? (get :email-smtp-host))) + :admin_email (sel :one :field ['User :email] (k/where {:is_superuser true}))}) ;; # IMPLEMENTATION -- GitLab