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

Improve formality of "HarbormasterBuild" states

Summary:
Ref T13072. Currently, Builds have basic states (like "passed" and "failed") and pending states where a command has been issued but not yet executed (pausing, resuming, restarting, and aborting).

These are handled in a bit of an ad-hoc way, and not everything treats them the same way. In particular, the build page can concurrently report a build as "Aborting" and "Pausing", with different icons and colors.

Make everything use the same logic so that a Build can only be in exactly zero or one pending state, and use the same icons and colors.

Also tighten up which transitions are allowed: for example, it doesn't make sense to pause an aborting build.

The tighter rules don't all produce great UX right now (like "You can't pause this build.", when it would be better as "You can't pause a build which is already aborting." or similar), but just leave that alone for now.

Test Plan: Viewed builds, applied various state changes, ran BuildWorker to effect the state changes, grepped for affected methods, tried to issue various out-of-sequence build commands.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13072

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

+161 -106
+81
src/applications/harbormaster/constants/HarbormasterBuildStatus.php
··· 12 12 const STATUS_PAUSED = 'paused'; 13 13 const STATUS_DEADLOCKED = 'deadlocked'; 14 14 15 + const PENDING_PAUSING = 'x-pausing'; 16 + const PENDING_RESUMING = 'x-resuming'; 17 + const PENDING_RESTARTING = 'x-restarting'; 18 + const PENDING_ABORTING = 'x-aborting'; 19 + 15 20 private $key; 16 21 private $properties; 17 22 ··· 48 53 return $this->getProperty('isComplete'); 49 54 } 50 55 56 + public function isPending() { 57 + return $this->getProperty('isPending'); 58 + } 59 + 51 60 public function isPassed() { 52 61 return ($this->key === self::STATUS_PASSED); 53 62 } ··· 56 65 return ($this->key === self::STATUS_FAILED); 57 66 } 58 67 68 + public function isAborting() { 69 + return ($this->key === self::PENDING_ABORTING); 70 + } 71 + 72 + public function isRestarting() { 73 + return ($this->key === self::PENDING_RESTARTING); 74 + } 75 + 76 + public function isResuming() { 77 + return ($this->key === self::PENDING_RESUMING); 78 + } 79 + 80 + public function isPausing() { 81 + return ($this->key === self::PENDING_PAUSING); 82 + } 83 + 84 + public function getIconIcon() { 85 + return $this->getProperty('icon'); 86 + } 87 + 88 + public function getIconColor() { 89 + return $this->getProperty('color'); 90 + } 91 + 92 + public function getName() { 93 + return $this->getProperty('name'); 94 + } 59 95 60 96 /** 61 97 * Get a human readable name for a build status constant. ··· 134 170 'color.ansi' => 'yellow', 135 171 'isBuilding' => false, 136 172 'isComplete' => false, 173 + 'isPending' => false, 137 174 ), 138 175 self::STATUS_PENDING => array( 139 176 'name' => pht('Pending'), ··· 142 179 'color.ansi' => 'yellow', 143 180 'isBuilding' => true, 144 181 'isComplete' => false, 182 + 'isPending' => false, 145 183 ), 146 184 self::STATUS_BUILDING => array( 147 185 'name' => pht('Building'), ··· 150 188 'color.ansi' => 'yellow', 151 189 'isBuilding' => true, 152 190 'isComplete' => false, 191 + 'isPending' => false, 153 192 ), 154 193 self::STATUS_PASSED => array( 155 194 'name' => pht('Passed'), ··· 158 197 'color.ansi' => 'green', 159 198 'isBuilding' => false, 160 199 'isComplete' => true, 200 + 'isPending' => false, 161 201 ), 162 202 self::STATUS_FAILED => array( 163 203 'name' => pht('Failed'), ··· 166 206 'color.ansi' => 'red', 167 207 'isBuilding' => false, 168 208 'isComplete' => true, 209 + 'isPending' => false, 169 210 ), 170 211 self::STATUS_ABORTED => array( 171 212 'name' => pht('Aborted'), ··· 174 215 'color.ansi' => 'red', 175 216 'isBuilding' => false, 176 217 'isComplete' => true, 218 + 'isPending' => false, 177 219 ), 178 220 self::STATUS_ERROR => array( 179 221 'name' => pht('Unexpected Error'), ··· 182 224 'color.ansi' => 'red', 183 225 'isBuilding' => false, 184 226 'isComplete' => true, 227 + 'isPending' => false, 185 228 ), 186 229 self::STATUS_PAUSED => array( 187 230 'name' => pht('Paused'), ··· 190 233 'color.ansi' => 'yellow', 191 234 'isBuilding' => false, 192 235 'isComplete' => false, 236 + 'isPending' => false, 193 237 ), 194 238 self::STATUS_DEADLOCKED => array( 195 239 'name' => pht('Deadlocked'), ··· 198 242 'color.ansi' => 'red', 199 243 'isBuilding' => false, 200 244 'isComplete' => true, 245 + 'isPending' => false, 246 + ), 247 + self::PENDING_PAUSING => array( 248 + 'name' => pht('Pausing'), 249 + 'icon' => 'fa-exclamation-triangle', 250 + 'color' => 'red', 251 + 'color.ansi' => 'red', 252 + 'isBuilding' => false, 253 + 'isComplete' => false, 254 + 'isPending' => true, 255 + ), 256 + self::PENDING_RESUMING => array( 257 + 'name' => pht('Resuming'), 258 + 'icon' => 'fa-exclamation-triangle', 259 + 'color' => 'red', 260 + 'color.ansi' => 'red', 261 + 'isBuilding' => false, 262 + 'isComplete' => false, 263 + 'isPending' => true, 264 + ), 265 + self::PENDING_RESTARTING => array( 266 + 'name' => pht('Restarting'), 267 + 'icon' => 'fa-exclamation-triangle', 268 + 'color' => 'red', 269 + 'color.ansi' => 'red', 270 + 'isBuilding' => false, 271 + 'isComplete' => false, 272 + 'isPending' => true, 273 + ), 274 + self::PENDING_ABORTING => array( 275 + 'name' => pht('Aborting'), 276 + 'icon' => 'fa-exclamation-triangle', 277 + 'color' => 'red', 278 + 'color.ansi' => 'red', 279 + 'isBuilding' => false, 280 + 'isComplete' => false, 281 + 'isPending' => true, 201 282 ), 202 283 ); 203 284 }
+7 -44
src/applications/harbormaster/controller/HarbormasterBuildViewController.php
··· 32 32 ->setPolicyObject($build) 33 33 ->setHeaderIcon('fa-cubes'); 34 34 35 - $is_restarting = $build->isRestarting(); 35 + $status = $build->getBuildPendingStatusObject(); 36 36 37 - if ($is_restarting) { 38 - $page_header->setStatus( 39 - 'fa-exclamation-triangle', 'red', pht('Restarting')); 40 - } else if ($build->isPausing()) { 41 - $page_header->setStatus( 42 - 'fa-exclamation-triangle', 'red', pht('Pausing')); 43 - } else if ($build->isResuming()) { 44 - $page_header->setStatus( 45 - 'fa-exclamation-triangle', 'red', pht('Resuming')); 46 - } else if ($build->isAborting()) { 47 - $page_header->setStatus( 48 - 'fa-exclamation-triangle', 'red', pht('Aborting')); 49 - } 37 + $status_icon = $status->getIconIcon(); 38 + $status_color = $status->getIconColor(); 39 + $status_name = $status->getName(); 40 + 41 + $page_header->setStatus($status_icon, $status_color, $status_name); 50 42 51 43 $max_generation = (int)$build->getBuildGeneration(); 52 44 if ($max_generation === 0) { ··· 55 47 $min_generation = 1; 56 48 } 57 49 58 - if ($is_restarting) { 50 + if ($build->isRestarting()) { 59 51 $max_generation = $max_generation + 1; 60 52 } 61 53 ··· 624 616 pht('Build Plan'), 625 617 $handles[$build->getBuildPlanPHID()]->renderLink()); 626 618 627 - $properties->addProperty( 628 - pht('Status'), 629 - $this->getStatus($build)); 630 - 631 619 return id(new PHUIObjectBoxView()) 632 620 ->setHeaderText(pht('Properties')) 633 621 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ··· 679 667 ->setHeaderText(pht('History')) 680 668 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 681 669 ->setTable($table); 682 - } 683 - 684 - 685 - private function getStatus(HarbormasterBuild $build) { 686 - $status_view = new PHUIStatusListView(); 687 - 688 - $item = new PHUIStatusItemView(); 689 - 690 - if ($build->isPausing()) { 691 - $status_name = pht('Pausing'); 692 - $icon = PHUIStatusItemView::ICON_RIGHT; 693 - $color = 'dark'; 694 - } else { 695 - $status = $build->getBuildStatus(); 696 - $status_name = 697 - HarbormasterBuildStatus::getBuildStatusName($status); 698 - $icon = HarbormasterBuildStatus::getBuildStatusIcon($status); 699 - $color = HarbormasterBuildStatus::getBuildStatusColor($status); 700 - } 701 - 702 - $item->setTarget($status_name); 703 - $item->setIcon($icon, $color); 704 - $status_view->addItem($item); 705 - 706 - return $status_view; 707 670 } 708 671 709 672 private function buildMessages(array $messages) {
+73 -62
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 212 212 return "/harbormaster/build/{$id}/"; 213 213 } 214 214 215 + public function getBuildPendingStatusObject() { 216 + 217 + // NOTE: If a build has multiple unprocessed messages, we'll ignore 218 + // messages that are obsoleted by a later or stronger message. 219 + // 220 + // For example, if a build has both "pause" and "abort" messages in queue, 221 + // we just ignore the "pause" message and perform an "abort", since pausing 222 + // first wouldn't affect the final state, so we can just skip it. 223 + // 224 + // Likewise, if a build has both "restart" and "abort" messages, the most 225 + // recent message is controlling: we'll take whichever action a command 226 + // was most recently issued for. 227 + 228 + $is_restarting = false; 229 + $is_aborting = false; 230 + $is_pausing = false; 231 + $is_resuming = false; 232 + 233 + foreach ($this->getUnprocessedMessages() as $message_object) { 234 + $message_type = $message_object->getType(); 235 + switch ($message_type) { 236 + case HarbormasterBuildCommand::COMMAND_RESTART: 237 + $is_restarting = true; 238 + $is_aborting = false; 239 + break; 240 + case HarbormasterBuildCommand::COMMAND_ABORT: 241 + $is_aborting = true; 242 + $is_restarting = false; 243 + break; 244 + case HarbormasterBuildCommand::COMMAND_PAUSE: 245 + $is_pausing = true; 246 + $is_resuming = false; 247 + break; 248 + case HarbormasterBuildCommand::COMMAND_RESUME: 249 + $is_resuming = true; 250 + $is_pausing = false; 251 + break; 252 + } 253 + } 254 + 255 + $pending_status = null; 256 + if ($is_restarting) { 257 + $pending_status = HarbormasterBuildStatus::PENDING_RESTARTING; 258 + } else if ($is_aborting) { 259 + $pending_status = HarbormasterBuildStatus::PENDING_ABORTING; 260 + } else if ($is_pausing) { 261 + $pending_status = HarbormasterBuildStatus::PENDING_PAUSING; 262 + } else if ($is_resuming) { 263 + $pending_status = HarbormasterBuildStatus::PENDING_RESUMING; 264 + } 265 + 266 + if ($pending_status !== null) { 267 + return HarbormasterBuildStatus::newBuildStatusObject($pending_status); 268 + } 269 + 270 + return $this->getBuildStatusObject(); 271 + } 272 + 215 273 protected function getBuildStatusObject() { 216 274 $status_key = $this->getBuildStatus(); 217 275 return HarbormasterBuildStatus::newBuildStatusObject($status_key); ··· 309 367 310 368 return !$this->isComplete() && 311 369 !$this->isPaused() && 312 - !$this->isPausing(); 370 + !$this->isPausing() && 371 + !$this->isRestarting() && 372 + !$this->isAborting(); 313 373 } 314 374 315 375 public function canAbortBuild() { ··· 317 377 return false; 318 378 } 319 379 320 - return !$this->isComplete(); 380 + return 381 + !$this->isComplete() && 382 + !$this->isAborting(); 321 383 } 322 384 323 385 public function canResumeBuild() { ··· 325 387 return false; 326 388 } 327 389 328 - return $this->isPaused() && 329 - !$this->isResuming(); 390 + return 391 + $this->isPaused() && 392 + !$this->isResuming() && 393 + !$this->isRestarting() && 394 + !$this->isAborting(); 330 395 } 331 396 332 397 public function isPausing() { 333 - $is_pausing = false; 334 - foreach ($this->getUnprocessedMessages() as $message_object) { 335 - $message_type = $message_object->getType(); 336 - switch ($message_type) { 337 - case HarbormasterBuildCommand::COMMAND_PAUSE: 338 - $is_pausing = true; 339 - break; 340 - case HarbormasterBuildCommand::COMMAND_RESUME: 341 - case HarbormasterBuildCommand::COMMAND_RESTART: 342 - $is_pausing = false; 343 - break; 344 - case HarbormasterBuildCommand::COMMAND_ABORT: 345 - $is_pausing = true; 346 - break; 347 - } 348 - } 349 - 350 - return $is_pausing; 398 + return $this->getBuildPendingStatusObject()->isPausing(); 351 399 } 352 400 353 401 public function isResuming() { 354 - $is_resuming = false; 355 - foreach ($this->getUnprocessedMessages() as $message_object) { 356 - $message_type = $message_object->getType(); 357 - switch ($message_type) { 358 - case HarbormasterBuildCommand::COMMAND_RESTART: 359 - case HarbormasterBuildCommand::COMMAND_RESUME: 360 - $is_resuming = true; 361 - break; 362 - case HarbormasterBuildCommand::COMMAND_PAUSE: 363 - $is_resuming = false; 364 - break; 365 - case HarbormasterBuildCommand::COMMAND_ABORT: 366 - $is_resuming = false; 367 - break; 368 - } 369 - } 370 - 371 - return $is_resuming; 402 + return $this->getBuildPendingStatusObject()->isResuming(); 372 403 } 373 404 374 405 public function isRestarting() { 375 - $is_restarting = false; 376 - foreach ($this->getUnprocessedMessages() as $message_object) { 377 - $message_type = $message_object->getType(); 378 - switch ($message_type) { 379 - case HarbormasterBuildCommand::COMMAND_RESTART: 380 - $is_restarting = true; 381 - break; 382 - } 383 - } 384 - 385 - return $is_restarting; 406 + return $this->getBuildPendingStatusObject()->isRestarting(); 386 407 } 387 408 388 409 public function isAborting() { 389 - $is_aborting = false; 390 - foreach ($this->getUnprocessedMessages() as $message_object) { 391 - $message_type = $message_object->getType(); 392 - switch ($message_type) { 393 - case HarbormasterBuildCommand::COMMAND_ABORT: 394 - $is_aborting = true; 395 - break; 396 - } 397 - } 398 - 399 - return $is_aborting; 410 + return $this->getBuildPendingStatusObject()->isAborting(); 400 411 } 401 412 402 413 public function markUnprocessedMessagesAsProcessed() {