@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.

at recaptime-dev/main 385 lines 10 kB view raw
1<?php 2 3final class HarbormasterBuildUnitMessage 4 extends HarbormasterDAO 5 implements PhabricatorPolicyInterface { 6 7 protected $buildTargetPHID; 8 protected $engine; 9 protected $namespace; 10 protected $name; 11 protected $nameIndex; 12 protected $result; 13 protected $duration; 14 protected $properties = array(); 15 16 private $buildTarget = self::ATTACHABLE; 17 18 const FORMAT_TEXT = 'text'; 19 const FORMAT_REMARKUP = 'remarkup'; 20 21 public static function initializeNewUnitMessage( 22 HarbormasterBuildTarget $build_target) { 23 return id(new HarbormasterBuildUnitMessage()) 24 ->setBuildTargetPHID($build_target->getPHID()); 25 } 26 27 public static function getParameterSpec() { 28 return array( 29 'name' => array( 30 'type' => 'string', 31 'description' => pht( 32 'Short test name, like "ExampleTest".'), 33 ), 34 'result' => array( 35 'type' => 'string', 36 'description' => pht( 37 'Result of the test.'), 38 ), 39 'namespace' => array( 40 'type' => 'optional string', 41 'description' => pht( 42 'Optional namespace for this test. This is organizational and '. 43 'is often a class or module name, like "ExampleTestCase".'), 44 ), 45 'engine' => array( 46 'type' => 'optional string', 47 'description' => pht( 48 'Test engine running the test, like "JavascriptTestEngine". This '. 49 'primarily prevents collisions between tests with the same name '. 50 'in different test suites (for example, a Javascript test and a '. 51 'Python test).'), 52 ), 53 'duration' => array( 54 'type' => 'optional float|int', 55 'description' => pht( 56 'Runtime duration of the test, in seconds.'), 57 ), 58 'path' => array( 59 'type' => 'optional string', 60 'description' => pht( 61 'Path to the file where the test is declared, relative to the '. 62 'project root.'), 63 ), 64 'coverage' => array( 65 'type' => 'optional map<string, wild>', 66 'description' => pht( 67 'Coverage information for this test.'), 68 ), 69 'details' => array( 70 'type' => 'optional string', 71 'description' => pht( 72 'Additional human-readable information about the failure.'), 73 ), 74 'format' => array( 75 'type' => 'optional string', 76 'description' => pht( 77 'Format for the text provided in "details". Valid values are '. 78 '"text" (default) or "remarkup". This controls how test details '. 79 'are rendered when shown to users.'), 80 ), 81 ); 82 } 83 84 public static function newFromDictionary( 85 HarbormasterBuildTarget $build_target, 86 array $dict) { 87 88 $obj = self::initializeNewUnitMessage($build_target); 89 90 $spec = self::getParameterSpec(); 91 $spec = ipull($spec, 'type'); 92 93 // We're just going to ignore extra keys for now, to make it easier to 94 // add stuff here later on. 95 $dict = array_select_keys($dict, array_keys($spec)); 96 PhutilTypeSpec::checkMap($dict, $spec); 97 98 $obj->setEngine(idx($dict, 'engine', '')); 99 $obj->setNamespace(idx($dict, 'namespace', '')); 100 $obj->setName($dict['name']); 101 $obj->setResult($dict['result']); 102 $obj->setDuration((float)idx($dict, 'duration')); 103 104 $path = idx($dict, 'path'); 105 if ($path !== null && strlen($path)) { 106 $obj->setProperty('path', $path); 107 } 108 109 $coverage = idx($dict, 'coverage'); 110 if ($coverage) { 111 $obj->setProperty('coverage', $coverage); 112 } 113 114 $details = idx($dict, 'details'); 115 if ($details) { 116 $obj->setProperty('details', $details); 117 } 118 119 $format = idx($dict, 'format'); 120 if ($format) { 121 $obj->setProperty('format', $format); 122 } 123 124 return $obj; 125 } 126 127 protected function getConfiguration() { 128 return array( 129 self::CONFIG_SERIALIZATION => array( 130 'properties' => self::SERIALIZATION_JSON, 131 ), 132 self::CONFIG_COLUMN_SCHEMA => array( 133 'engine' => 'text255', 134 'namespace' => 'text255', 135 'name' => 'text255', 136 'nameIndex' => 'bytes12', 137 'result' => 'text32', 138 'duration' => 'double?', 139 ), 140 self::CONFIG_KEY_SCHEMA => array( 141 'key_target' => array( 142 'columns' => array('buildTargetPHID'), 143 ), 144 ), 145 ) + parent::getConfiguration(); 146 } 147 148 public function attachBuildTarget(HarbormasterBuildTarget $build_target) { 149 $this->buildTarget = $build_target; 150 return $this; 151 } 152 153 public function getBuildTarget() { 154 return $this->assertAttached($this->buildTarget); 155 } 156 157 public function getProperty($key, $default = null) { 158 return idx($this->properties, $key, $default); 159 } 160 161 public function setProperty($key, $value) { 162 $this->properties[$key] = $value; 163 return $this; 164 } 165 166 public function getUnitMessageDetails() { 167 return $this->getProperty('details', ''); 168 } 169 170 public function getUnitMessageDetailsFormat() { 171 return $this->getProperty('format', self::FORMAT_TEXT); 172 } 173 174 public function newUnitMessageDetailsView( 175 PhabricatorUser $viewer, 176 $summarize = false) { 177 178 $format = $this->getUnitMessageDetailsFormat(); 179 180 $is_text = ($format !== self::FORMAT_REMARKUP); 181 $is_remarkup = ($format === self::FORMAT_REMARKUP); 182 $message = null; 183 184 $full_details = $this->getUnitMessageDetails(); 185 $byte_length = strlen($full_details); 186 187 $text_limit = 1024 * 2; 188 $remarkup_limit = 1024 * 8; 189 190 if (!$byte_length) { 191 if ($summarize) { 192 return null; 193 } 194 $message = phutil_tag('em', array(), pht('No details provided.')); 195 } else if ($summarize) { 196 if ($is_text) { 197 $details = id(new PhutilUTF8StringTruncator()) 198 ->setMaximumBytes($text_limit) 199 ->truncateString($full_details); 200 $details = phutil_split_lines($details); 201 202 $limit = 3; 203 if (count($details) > $limit) { 204 $details = array_slice($details, 0, $limit); 205 } 206 207 $details = implode('', $details); 208 } else { 209 if ($byte_length > $remarkup_limit) { 210 $uri = $this->getURI(); 211 212 if ($uri) { 213 $link = phutil_tag( 214 'a', 215 array( 216 'href' => $uri, 217 'target' => '_blank', 218 ), 219 pht('View Details')); 220 } else { 221 $link = null; 222 } 223 224 $message = array(); 225 $message[] = phutil_tag( 226 'em', 227 array(), 228 pht('This test has too much data to display inline.')); 229 if ($link) { 230 $message[] = $link; 231 } 232 233 $message = phutil_implode_html(" \xC2\xB7 ", $message); 234 } else { 235 $details = $full_details; 236 } 237 } 238 } else { 239 $details = $full_details; 240 } 241 242 require_celerity_resource('harbormaster-css'); 243 244 $classes = array(); 245 $classes[] = 'harbormaster-unit-details'; 246 247 if ($message !== null) { 248 // If we have a message, show that instead of rendering any test details. 249 $details = $message; 250 } else if ($is_remarkup) { 251 $details = new PHUIRemarkupView($viewer, $details); 252 } else { 253 $classes[] = 'harbormaster-unit-details-text'; 254 $classes[] = 'PhabricatorMonospaced'; 255 } 256 257 $warning = null; 258 if (!$summarize) { 259 $warnings = array(); 260 261 if ($is_remarkup && ($byte_length > $remarkup_limit)) { 262 $warnings[] = pht( 263 'This test result has %s bytes of Remarkup test details. Remarkup '. 264 'blocks longer than %s bytes are not rendered inline when showing '. 265 'test summaries.', 266 new PhutilNumber($byte_length), 267 new PhutilNumber($remarkup_limit)); 268 } 269 270 if ($warnings) { 271 $warning = id(new PHUIInfoView()) 272 ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 273 ->setErrors($warnings); 274 } 275 } 276 277 $content = phutil_tag( 278 'div', 279 array( 280 'class' => implode(' ', $classes), 281 ), 282 $details); 283 284 return array( 285 $warning, 286 $content, 287 ); 288 } 289 290 public function getUnitMessageDisplayName() { 291 $name = $this->getName(); 292 293 $namespace = $this->getNamespace(); 294 if (strlen($namespace)) { 295 $name = $namespace.'::'.$name; 296 } 297 298 $engine = $this->getEngine(); 299 if (strlen($engine)) { 300 $name = $engine.' > '.$name; 301 } 302 303 if (!strlen($name)) { 304 return pht('Nameless Test (%d)', $this->getID()); 305 } 306 307 return $name; 308 } 309 310 public function getSortKey() { 311 $status = $this->getResult(); 312 $sort = HarbormasterUnitStatus::getUnitStatusSort($status); 313 314 $parts = array( 315 $sort, 316 $this->getEngine(), 317 $this->getNamespace(), 318 $this->getName(), 319 $this->getID(), 320 ); 321 322 return implode("\0", $parts); 323 } 324 325 public function getURI() { 326 $id = $this->getID(); 327 328 if (!$id) { 329 return null; 330 } 331 332 return urisprintf( 333 '/harbormaster/unit/view/%d/', 334 $id); 335 } 336 337 public function save() { 338 if ($this->nameIndex === null) { 339 $this->nameIndex = HarbormasterString::newIndex($this->getName()); 340 } 341 342 // See T13088. While we're letting installs do online migrations to avoid 343 // downtime, don't populate the "name" column for new writes. New writes 344 // use the "HarbormasterString" table instead. 345 $old_name = $this->name; 346 $this->name = ''; 347 348 $caught = null; 349 try { 350 $result = parent::save(); 351 } catch (Exception $ex) { 352 $caught = $ex; 353 } 354 355 $this->name = $old_name; 356 357 if ($caught) { 358 throw $caught; 359 } 360 361 return $result; 362 } 363 364 365/* -( PhabricatorPolicyInterface )----------------------------------------- */ 366 367 368 public function getCapabilities() { 369 return array( 370 PhabricatorPolicyCapability::CAN_VIEW, 371 ); 372 } 373 374 public function getPolicy($capability) { 375 switch ($capability) { 376 case PhabricatorPolicyCapability::CAN_VIEW: 377 return PhabricatorPolicies::getMostOpenPolicy(); 378 } 379 } 380 381 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 382 return false; 383 } 384 385}