@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Show additional details for failed builds in Harbormaster

Summary:
Ref T10457. When tests fail, it currently takes several clicks to figure out //why// they failed.

In this project, map rebuilds and `liberate` are fairly common failure conditions, but verifying that they were the root issue requires jumping into a build, then scrolling through a log.

Instead, display details if they're available.

Test Plan:
Before:

{F1135453}

After:

{F1135454}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9951, T10457

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

+263 -22
+5 -5
resources/celerity/map.php
··· 7 7 */ 8 8 return array( 9 9 'names' => array( 10 - 'core.pkg.css' => 'ecdca229', 10 + 'core.pkg.css' => 'f7a91f6a', 11 11 'core.pkg.js' => '7d8faf57', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '2de124c9', ··· 25 25 'rsrc/css/aphront/notification.css' => '7f684b62', 26 26 'rsrc/css/aphront/panel-view.css' => '8427b78d', 27 27 'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758', 28 - 'rsrc/css/aphront/table-view.css' => '6d01d468', 28 + 'rsrc/css/aphront/table-view.css' => 'ec078a76', 29 29 'rsrc/css/aphront/tokenizer.css' => '056da01b', 30 30 'rsrc/css/aphront/tooltip.css' => '1a07aea8', 31 31 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', ··· 70 70 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 71 71 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 72 72 'rsrc/css/application/flag/flag.css' => '5337623f', 73 - 'rsrc/css/application/harbormaster/harbormaster.css' => 'b0758ca5', 73 + 'rsrc/css/application/harbormaster/harbormaster.css' => '834879db', 74 74 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 75 75 'rsrc/css/application/herald/herald.css' => '826075fa', 76 76 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', ··· 523 523 'aphront-list-filter-view-css' => '5d6f0526', 524 524 'aphront-multi-column-view-css' => 'fd18389d', 525 525 'aphront-panel-view-css' => '8427b78d', 526 - 'aphront-table-view-css' => '6d01d468', 526 + 'aphront-table-view-css' => 'ec078a76', 527 527 'aphront-tokenizer-control-css' => '056da01b', 528 528 'aphront-tooltip-css' => '1a07aea8', 529 529 'aphront-typeahead-control-css' => 'd4f16145', ··· 558 558 'font-fontawesome' => 'c43323c5', 559 559 'font-lato' => 'c7ccd872', 560 560 'global-drag-and-drop-css' => '5c1b47c2', 561 - 'harbormaster-css' => 'b0758ca5', 561 + 'harbormaster-css' => '834879db', 562 562 'herald-css' => '826075fa', 563 563 'herald-rule-editor' => '746ca158', 564 564 'herald-test-css' => 'a52e323e',
+4 -2
src/__phutil_library_map__.php
··· 1145 1145 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 1146 1146 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 1147 1147 'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php', 1148 - 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', 1148 + 'HarbormasterUnitMessageListController' => 'applications/harbormaster/controller/HarbormasterUnitMessageListController.php', 1149 + 'HarbormasterUnitMessageViewController' => 'applications/harbormaster/controller/HarbormasterUnitMessageViewController.php', 1149 1150 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 1150 1151 'HarbormasterUnitStatus' => 'applications/harbormaster/constants/HarbormasterUnitStatus.php', 1151 1152 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', ··· 5313 5314 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 5314 5315 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 5315 5316 'HarbormasterURIArtifact' => 'HarbormasterArtifact', 5316 - 'HarbormasterUnitMessagesController' => 'HarbormasterController', 5317 + 'HarbormasterUnitMessageListController' => 'HarbormasterController', 5318 + 'HarbormasterUnitMessageViewController' => 'HarbormasterController', 5317 5319 'HarbormasterUnitPropertyView' => 'AphrontView', 5318 5320 'HarbormasterUnitStatus' => 'Phobject', 5319 5321 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+2 -1
src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
··· 85 85 '(?P<id>\d+)/' => 'HarbormasterPlanViewController', 86 86 ), 87 87 'unit/' => array( 88 - '(?P<id>\d+)/' => 'HarbormasterUnitMessagesController', 88 + '(?P<id>\d+)/' => 'HarbormasterUnitMessageListController', 89 + 'view/(?P<id>\d+)/' => 'HarbormasterUnitMessageViewController', 89 90 ), 90 91 'lint/' => array( 91 92 '(?P<id>\d+)/' => 'HarbormasterLintMessagesController',
+122
src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php
··· 1 + <?php 2 + 3 + final class HarbormasterUnitMessageViewController 4 + extends HarbormasterController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + 9 + $message_id = $request->getURIData('id'); 10 + 11 + $message = id(new HarbormasterBuildUnitMessage())->load($message_id); 12 + if (!$message) { 13 + return new Aphront404Response(); 14 + } 15 + 16 + $build_target = id(new HarbormasterBuildTargetQuery()) 17 + ->setViewer($viewer) 18 + ->withPHIDs(array($message->getBuildTargetPHID())) 19 + ->executeOne(); 20 + if (!$build_target) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + $build = $build_target->getBuild(); 25 + $buildable = $build->getBuildable(); 26 + $buildable_id = $buildable->getID(); 27 + 28 + $id = $message->getID(); 29 + $display_name = $message->getUnitMessageDisplayName(); 30 + 31 + $status = $message->getResult(); 32 + $status_icon = HarbormasterUnitStatus::getUnitStatusIcon($status); 33 + $status_color = HarbormasterUnitStatus::getUnitStatusColor($status); 34 + $status_label = HarbormasterUnitStatus::getUnitStatusLabel($status); 35 + 36 + $header = id(new PHUIHeaderView()) 37 + ->setHeader($display_name) 38 + ->setStatus($status_icon, $status_color, $status_label); 39 + 40 + $properties = $this->buildPropertyListView($message); 41 + $actions = $this->buildActionView($message, $build); 42 + 43 + $properties->setActionList($actions); 44 + 45 + $unit = id(new PHUIObjectBoxView()) 46 + ->setHeader($header) 47 + ->addPropertyList($properties); 48 + 49 + $crumbs = $this->buildApplicationCrumbs(); 50 + $this->addBuildableCrumb($crumbs, $buildable); 51 + 52 + $crumbs->addTextCrumb( 53 + pht('Unit Tests'), 54 + "/harbormaster/unit/{$buildable_id}/"); 55 + 56 + $crumbs->addTextCrumb(pht('Unit %d', $id)); 57 + 58 + $title = array( 59 + $display_name, 60 + $buildable->getMonogram(), 61 + ); 62 + 63 + return $this->newPage() 64 + ->setTitle($title) 65 + ->setCrumbs($crumbs) 66 + ->appendChild($unit); 67 + } 68 + 69 + private function buildPropertyListView( 70 + HarbormasterBuildUnitMessage $message) { 71 + $request = $this->getRequest(); 72 + $viewer = $request->getUser(); 73 + 74 + $view = id(new PHUIPropertyListView()) 75 + ->setUser($viewer); 76 + 77 + $view->addProperty( 78 + pht('Run At'), 79 + phabricator_datetime($message->getDateCreated(), $viewer)); 80 + 81 + $details = $message->getUnitMessageDetails(); 82 + if (strlen($details)) { 83 + // TODO: Use the log view here, once it gets cleaned up. 84 + $details = phutil_tag( 85 + 'div', 86 + array( 87 + 'class' => 'PhabricatorMonospaced', 88 + 'style' => 89 + 'white-space: pre-wrap; '. 90 + 'color: #666666; '. 91 + 'overflow-x: auto;', 92 + ), 93 + $details); 94 + } else { 95 + $details = phutil_tag('em', array(), pht('No details provided.')); 96 + } 97 + 98 + $view->addSectionHeader( 99 + pht('Details'), 100 + PHUIPropertyListView::ICON_TESTPLAN); 101 + $view->addTextContent($details); 102 + 103 + return $view; 104 + } 105 + 106 + private function buildActionView( 107 + HarbormasterBuildUnitMessage $message, 108 + HarbormasterBuild $build) { 109 + $viewer = $this->getViewer(); 110 + 111 + $view = id(new PhabricatorActionListView()) 112 + ->setUser($viewer); 113 + 114 + $view->addAction( 115 + id(new PhabricatorActionView()) 116 + ->setName(pht('View Build')) 117 + ->setHref($build->getURI()) 118 + ->setIcon('fa-wrench')); 119 + 120 + return $view; 121 + } 122 + }
+1 -1
src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php
··· 1 1 <?php 2 2 3 - final class HarbormasterUnitMessagesController 3 + final class HarbormasterUnitMessageListController 4 4 extends HarbormasterController { 5 5 6 6 public function handleRequest(AphrontRequest $request) {
+5
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 323 323 return ($this->getBuildStatus() == self::STATUS_PAUSED); 324 324 } 325 325 326 + public function getURI() { 327 + $id = $this->getID(); 328 + return "/harbormaster/build/{$id}/"; 329 + } 330 + 326 331 327 332 /* -( Build Commands )----------------------------------------------------- */ 328 333
+34
src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php
··· 61 61 'description' => pht( 62 62 'Coverage information for this test.'), 63 63 ), 64 + 'details' => array( 65 + 'type' => 'optional string', 66 + 'description' => pht( 67 + 'Additional human-readable information about the failure.'), 68 + ), 64 69 ); 65 70 } 66 71 ··· 94 99 $obj->setProperty('coverage', $coverage); 95 100 } 96 101 102 + $details = idx($dict, 'details'); 103 + if ($details) { 104 + $obj->setProperty('details', $details); 105 + } 106 + 97 107 return $obj; 98 108 } 99 109 ··· 133 143 public function setProperty($key, $value) { 134 144 $this->properties[$key] = $value; 135 145 return $this; 146 + } 147 + 148 + public function getUnitMessageDetails() { 149 + return $this->getProperty('details', ''); 150 + } 151 + 152 + public function getUnitMessageDisplayName() { 153 + $name = $this->getName(); 154 + 155 + $namespace = $this->getNamespace(); 156 + if (strlen($namespace)) { 157 + $name = $namespace.'::'.$name; 158 + } 159 + 160 + $engine = $this->getEngine(); 161 + if (strlen($engine)) { 162 + $name = $engine.' > '.$name; 163 + } 164 + 165 + if (!strlen($name)) { 166 + return pht('Nameless Test (%d)', $this->getID()); 167 + } 168 + 169 + return $name; 136 170 } 137 171 138 172 public function getSortKey() {
+45 -11
src/applications/harbormaster/view/HarbormasterUnitPropertyView.php
··· 29 29 } 30 30 31 31 public function render() { 32 + require_celerity_resource('harbormaster-css'); 33 + 32 34 $messages = $this->unitMessages; 33 35 $messages = msort($messages, 'getSortKey'); 34 36 ··· 63 65 $duration = pht('%s ms', new PhutilNumber((int)(1000 * $duration))); 64 66 } 65 67 66 - $name = $message->getName(); 68 + $name = $message->getUnitMessageDisplayName(); 69 + $id = $message->getID(); 67 70 68 - $namespace = $message->getNamespace(); 69 - if (strlen($namespace)) { 70 - $name = $namespace.'::'.$name; 71 - } 71 + $name = phutil_tag( 72 + 'a', 73 + array( 74 + 'href' => "/harbormaster/unit/view/{$id}/", 75 + ), 76 + $name); 72 77 73 - $engine = $message->getEngine(); 74 - if (strlen($engine)) { 75 - $name = $engine.' > '.$name; 78 + $details = $message->getUnitMessageDetails(); 79 + if (strlen($details)) { 80 + $name = array( 81 + $name, 82 + $this->renderUnitTestDetails($details), 83 + ); 76 84 } 77 85 78 86 $rows[] = array( ··· 119 127 )) 120 128 ->setColumnClasses( 121 129 array( 122 - null, 123 - null, 124 - 'pri wide', 130 + 'top', 131 + 'top', 132 + 'top wide', 133 + )) 134 + ->setColumnWidths( 135 + array( 136 + '32px', 137 + '64px', 125 138 )) 126 139 ->setColumnVisibility( 127 140 array( ··· 130 143 )); 131 144 132 145 return $table; 146 + } 147 + 148 + private function renderUnitTestDetails($full_details) { 149 + $details = id(new PhutilUTF8StringTruncator()) 150 + ->setMaximumBytes(2048) 151 + ->truncateString($full_details); 152 + $details = phutil_split_lines($details); 153 + 154 + $limit = 3; 155 + if (count($details) > $limit) { 156 + $details = array_slice($details, 0, $limit); 157 + } 158 + 159 + $details = implode('', $details); 160 + 161 + return phutil_tag( 162 + 'div', 163 + array( 164 + 'class' => 'PhabricatorMonospaced harbormaster-unit-details', 165 + ), 166 + $details); 133 167 } 134 168 135 169 }
+33 -2
src/view/control/AphrontTableView.php
··· 15 15 protected $columnVisibility = array(); 16 16 private $deviceVisibility = array(); 17 17 18 + private $columnWidths = array(); 19 + 18 20 protected $sortURI; 19 21 protected $sortParam; 20 22 protected $sortSelected; ··· 43 45 44 46 public function setCellClasses(array $cell_classes) { 45 47 $this->cellClasses = $cell_classes; 48 + return $this; 49 + } 50 + 51 + public function setColumnWidths(array $widths) { 52 + $this->columnWidths = $widths; 46 53 return $this; 47 54 } 48 55 ··· 130 137 131 138 $visibility = array_values($this->columnVisibility); 132 139 $device_visibility = array_values($this->deviceVisibility); 140 + 141 + $column_widths = $this->columnWidths; 133 142 134 143 $headers = $this->headers; 135 144 $short_headers = $this->shortHeaders; ··· 236 245 $header = hsprintf('%s %s', $header_nodevice, $header_device); 237 246 } 238 247 239 - $tr[] = phutil_tag('th', array('class' => $class), $header); 248 + $style = null; 249 + if (isset($column_widths[$col_num])) { 250 + $style = 'width: '.$column_widths[$col_num].';'; 251 + } 252 + 253 + $tr[] = phutil_tag( 254 + 'th', 255 + array( 256 + 'class' => $class, 257 + 'style' => $style, 258 + ), 259 + $header); 240 260 } 241 261 $table[] = phutil_tag('tr', array(), $tr); 242 262 } ··· 283 303 if (!empty($this->cellClasses[$row_num][$col_num])) { 284 304 $class = trim($class.' '.$this->cellClasses[$row_num][$col_num]); 285 305 } 286 - $tr[] = phutil_tag('td', array('class' => $class), $value); 306 + 307 + $tr[] = phutil_tag( 308 + 'td', 309 + array( 310 + 'class' => $class, 311 + ), 312 + $value); 287 313 ++$col_num; 288 314 } 289 315 ··· 315 341 if ($this->className !== null) { 316 342 $classes[] = $this->className; 317 343 } 344 + 318 345 if ($this->deviceReadyTable) { 319 346 $classes[] = 'aphront-table-view-device-ready'; 347 + } 348 + 349 + if ($this->columnWidths) { 350 + $classes[] = 'aphront-table-view-fixed'; 320 351 } 321 352 322 353 $html = phutil_tag(
+4
webroot/rsrc/css/aphront/table-view.css
··· 15 15 border-bottom: 1px solid {$blueborder}; 16 16 } 17 17 18 + .aphront-table-view-fixed { 19 + table-layout: fixed; 20 + } 21 + 18 22 .aphront-table-view td.aphront-table-notice { 19 23 padding: 12px 16px; 20 24 font-size: {$normalfontsize};
+8
webroot/rsrc/css/application/harbormaster/harbormaster.css
··· 20 20 padding: 12px; 21 21 color: {$darkgreytext}; 22 22 } 23 + 24 + .harbormaster-unit-details { 25 + margin: 8px 0 4px; 26 + overflow: hidden; 27 + white-space: pre; 28 + text-overflow: ellipsis; 29 + color: {$lightgreytext}; 30 + }