@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Add a friendlier "in flight" error page

Summary:
Ref T11589. When we hit a fatal setup issue (essentially always a connection failure) //after// we've already survived them on at least one request, we can be pretty sure a server went down and that the problem is not a setup/configuration issue.

In this case, show a friendlier error page instead of the fairly detailed technical one.

Test Plan:
- Broke MySQL config.
- Restarted Apache.
- Got the "admin/setup" error page:

{F1803268}

- Fixed the MySQL config.
- Loaded any page, to put us "in flight".
- Broke MySQL config.
- Loaded any page.
- Got the friendly "in flight" error page:

{F1803271}

If you want to design this better, easiest way to get to it is:

- Set `mysql.port` to `9999` in `conf/local/local.json`.
- Reload any page while already running (don't restart).

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11589

Differential Revision: https://secure.phabricator.com/D16503

+104 -4
+2 -2
resources/celerity/map.php
··· 41 41 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 42 42 'rsrc/css/application/config/config-options.css' => '0ede4c9b', 43 43 'rsrc/css/application/config/config-page.css' => '8798e14f', 44 - 'rsrc/css/application/config/config-template.css' => '8e6c6fcd', 44 + 'rsrc/css/application/config/config-template.css' => '8f18fa41', 45 45 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 46 46 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 47 47 'rsrc/css/application/conpherence/durable-column.css' => '86396117', ··· 777 777 'phabricator-dashboard-css' => 'bc6f2127', 778 778 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 779 779 'phabricator-draggable-list' => '5a13c79f', 780 - 'phabricator-fatal-config-template-css' => '8e6c6fcd', 780 + 'phabricator-fatal-config-template-css' => '8f18fa41', 781 781 'phabricator-feed-css' => 'ecd4ec57', 782 782 'phabricator-file-upload' => '680ea2c8', 783 783 'phabricator-filetree-view-css' => 'fccf9f82',
+2
src/__phutil_library_map__.php
··· 2685 2685 'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php', 2686 2686 'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php', 2687 2687 'PhabricatorImagemagickSetupCheck' => 'applications/config/check/PhabricatorImagemagickSetupCheck.php', 2688 + 'PhabricatorInFlightErrorView' => 'applications/config/view/PhabricatorInFlightErrorView.php', 2688 2689 'PhabricatorIndexEngine' => 'applications/search/index/PhabricatorIndexEngine.php', 2689 2690 'PhabricatorIndexEngineExtension' => 'applications/search/index/PhabricatorIndexEngineExtension.php', 2690 2691 'PhabricatorIndexEngineExtensionModule' => 'applications/search/index/PhabricatorIndexEngineExtensionModule.php', ··· 7509 7510 'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule', 7510 7511 'PhabricatorImageTransformer' => 'Phobject', 7511 7512 'PhabricatorImagemagickSetupCheck' => 'PhabricatorSetupCheck', 7513 + 'PhabricatorInFlightErrorView' => 'AphrontView', 7512 7514 'PhabricatorIndexEngine' => 'Phobject', 7513 7515 'PhabricatorIndexEngineExtension' => 'Phobject', 7514 7516 'PhabricatorIndexEngineExtensionModule' => 'PhabricatorConfigModule',
+15
src/applications/config/check/PhabricatorSetupCheck.php
··· 192 192 } 193 193 } 194 194 195 + /** 196 + * Test if we've survived through setup on at least one normal request 197 + * without fataling. 198 + * 199 + * If we've made it through setup without hitting any fatals, we switch 200 + * to render a more friendly error page when encountering issues like 201 + * database connection failures. This gives users a smoother experience in 202 + * the face of intermittent failures. 203 + * 204 + * @return bool True if we've made it through setup since the last restart. 205 + */ 206 + final public static function isInFlight() { 207 + return (self::getOpenSetupIssueKeys() !== null); 208 + } 209 + 195 210 final public static function loadAllChecks() { 196 211 return id(new PhutilClassMapQuery()) 197 212 ->setAncestorClass(__CLASS__)
+12 -2
src/applications/config/response/PhabricatorConfigResponse.php
··· 25 25 } 26 26 27 27 protected function getResponseBodyClass() { 28 - return 'setup-fatal'; 28 + if (PhabricatorSetupCheck::isInFlight()) { 29 + return 'setup-fatal in-flight'; 30 + } else { 31 + return 'setup-fatal'; 32 + } 29 33 } 30 34 31 35 protected function getResponseBody() { 32 - return $this->view->render(); 36 + $view = $this->view; 37 + 38 + if (PhabricatorSetupCheck::isInFlight()) { 39 + return $view->renderInFlight(); 40 + } else { 41 + return $view->render(); 42 + } 33 43 } 34 44 35 45 protected function buildPlainTextResponseString() {
+41
src/applications/config/view/PhabricatorInFlightErrorView.php
··· 1 + <?php 2 + 3 + final class PhabricatorInFlightErrorView extends AphrontView { 4 + 5 + private $message; 6 + 7 + public function setMessage($message) { 8 + $this->message = $message; 9 + return $this; 10 + } 11 + 12 + public function getMessage() { 13 + return $this->message; 14 + } 15 + 16 + public function render() { 17 + return phutil_tag( 18 + 'div', 19 + array( 20 + 'class' => 'in-flight-error-detail', 21 + ), 22 + array( 23 + phutil_tag( 24 + 'h1', 25 + array( 26 + 'class' => 'in-flight-error-title', 27 + ), 28 + pht('A Troublesome Encounter!')), 29 + phutil_tag( 30 + 'div', 31 + array( 32 + 'class' => 'in-flight-error-body', 33 + ), 34 + pht( 35 + 'Woe! This request had its journey cut short by unexpected '. 36 + 'circumstances (%s).', 37 + $this->getMessage())), 38 + )); 39 + } 40 + 41 + }
+8
src/applications/config/view/PhabricatorSetupIssueView.php
··· 13 13 return $this->issue; 14 14 } 15 15 16 + public function renderInFlight() { 17 + $issue = $this->getIssue(); 18 + 19 + return id(new PhabricatorInFlightErrorView()) 20 + ->setMessage($issue->getName()) 21 + ->render(); 22 + } 23 + 16 24 public function render() { 17 25 $issue = $this->getIssue(); 18 26
+24
webroot/rsrc/css/application/config/config-template.css
··· 11 11 text-align: left; 12 12 -webkit-text-size-adjust: none; 13 13 } 14 + 15 + body.in-flight { 16 + background: #41506e; 17 + color: #e0e0e0; 18 + } 19 + 20 + .in-flight-error-detail { 21 + max-width: 760px; 22 + margin: 72px auto; 23 + background: rgba(255, 255, 255, 0.25); 24 + border-radius: 3px; 25 + padding: 8px 16px; 26 + } 27 + 28 + .in-flight-error-title { 29 + padding: 12px 8px; 30 + font-size: 24px; 31 + font-weight: 500; 32 + margin: 0; 33 + } 34 + 35 + .in-flight-error-body { 36 + padding: 4px 12px 12px; 37 + }