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

Modularize almost all Harbormaster build message workflows and UI/UX

Summary: Ref T13072. Push nearly all Harbormaster build message logic into the new per-message transaction classes.

Test Plan:
- Issued every message to Buildables.
- Issued every message to Builds.
- Looked at a big pile of error messages, couldn't find any typos.
- Grepped for affected symbols, etc.
- Ran `bin/harbormaster restart ...`.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13072

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

+713 -684
+2 -2
src/applications/harbormaster/constants/HarbormasterBuildStatus.php
··· 221 221 ), 222 222 self::STATUS_PAUSED => array( 223 223 'name' => pht('Paused'), 224 - 'icon' => 'fa-minus-circle', 225 - 'color' => 'dark', 224 + 'icon' => 'fa-pause', 225 + 'color' => 'yellow', 226 226 'color.ansi' => 'yellow', 227 227 'isBuilding' => false, 228 228 'isComplete' => false,
+23 -105
src/applications/harbormaster/controller/HarbormasterBuildActionController.php
··· 22 22 return new Aphront404Response(); 23 23 } 24 24 25 - switch ($action) { 26 - case HarbormasterBuildCommand::COMMAND_RESTART: 27 - $can_issue = $build->canRestartBuild(); 28 - break; 29 - case HarbormasterBuildCommand::COMMAND_PAUSE: 30 - $can_issue = $build->canPauseBuild(); 31 - break; 32 - case HarbormasterBuildCommand::COMMAND_RESUME: 33 - $can_issue = $build->canResumeBuild(); 34 - break; 35 - case HarbormasterBuildCommand::COMMAND_ABORT: 36 - $can_issue = $build->canAbortBuild(); 37 - break; 38 - default: 39 - return new Aphront400Response(); 40 - } 25 + $xaction = 26 + HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType( 27 + $action); 41 28 42 - $build->assertCanIssueCommand($viewer, $action); 29 + if (!$xaction) { 30 + return new Aphront404Response(); 31 + } 43 32 44 33 switch ($via) { 45 34 case 'buildable': ··· 50 39 break; 51 40 } 52 41 53 - if ($request->isDialogFormPost() && $can_issue) { 54 - $build->sendMessage($viewer, $action); 55 - return id(new AphrontRedirectResponse())->setURI($return_uri); 42 + try { 43 + $xaction->assertCanSendMessage($viewer, $build); 44 + } catch (HarbormasterRestartException $ex) { 45 + return $this->newDialog() 46 + ->setTitle($ex->getTitle()) 47 + ->appendChild($ex->getBody()) 48 + ->addCancelButton($return_uri); 56 49 } 57 50 58 - switch ($action) { 59 - case HarbormasterBuildCommand::COMMAND_RESTART: 60 - if ($can_issue) { 61 - $title = pht('Really restart build?'); 62 - $body = pht( 63 - 'Progress on this build will be discarded and the build will '. 64 - 'restart. Side effects of the build will occur again. Really '. 65 - 'restart build?'); 66 - $submit = pht('Restart Build'); 67 - } else { 68 - try { 69 - $build->assertCanRestartBuild(); 70 - throw new Exception(pht('Expected to be unable to restart build.')); 71 - } catch (HarbormasterRestartException $ex) { 72 - $title = $ex->getTitle(); 73 - $body = $ex->getBody(); 74 - } 75 - } 76 - break; 77 - case HarbormasterBuildCommand::COMMAND_ABORT: 78 - if ($can_issue) { 79 - $title = pht('Really abort build?'); 80 - $body = pht( 81 - 'Progress on this build will be discarded. Really '. 82 - 'abort build?'); 83 - $submit = pht('Abort Build'); 84 - } else { 85 - $title = pht('Unable to Abort Build'); 86 - $body = pht('You can not abort this build.'); 87 - } 88 - break; 89 - case HarbormasterBuildCommand::COMMAND_PAUSE: 90 - if ($can_issue) { 91 - $title = pht('Really pause build?'); 92 - $body = pht( 93 - 'If you pause this build, work will halt once the current steps '. 94 - 'complete. You can resume the build later.'); 95 - $submit = pht('Pause Build'); 96 - } else { 97 - $title = pht('Unable to Pause Build'); 98 - if ($build->isComplete()) { 99 - $body = pht( 100 - 'This build is already complete. You can not pause a completed '. 101 - 'build.'); 102 - } else if ($build->isPaused()) { 103 - $body = pht( 104 - 'This build is already paused. You can not pause a build which '. 105 - 'has already been paused.'); 106 - } else if ($build->isPausing()) { 107 - $body = pht( 108 - 'This build is already pausing. You can not reissue a pause '. 109 - 'command to a pausing build.'); 110 - } else { 111 - $body = pht( 112 - 'This build can not be paused.'); 113 - } 114 - } 115 - break; 116 - case HarbormasterBuildCommand::COMMAND_RESUME: 117 - if ($can_issue) { 118 - $title = pht('Really resume build?'); 119 - $body = pht( 120 - 'Work will continue on the build. Really resume?'); 121 - $submit = pht('Resume Build'); 122 - } else { 123 - $title = pht('Unable to Resume Build'); 124 - if ($build->isResuming()) { 125 - $body = pht( 126 - 'This build is already resuming. You can not reissue a resume '. 127 - 'command to a resuming build.'); 128 - } else if (!$build->isPaused()) { 129 - $body = pht( 130 - 'This build is not paused. You can only resume a paused '. 131 - 'build.'); 132 - } 133 - } 134 - break; 51 + if ($request->isDialogFormPost()) { 52 + $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType()); 53 + return id(new AphrontRedirectResponse())->setURI($return_uri); 135 54 } 136 55 137 - $dialog = $this->newDialog() 56 + $title = $xaction->newConfirmPromptTitle(); 57 + $body = $xaction->newConfirmPromptBody(); 58 + $submit = $xaction->getHarbormasterBuildMessageName(); 59 + 60 + return $this->newDialog() 138 61 ->setTitle($title) 139 62 ->appendChild($body) 140 - ->addCancelButton($return_uri); 141 - 142 - if ($can_issue) { 143 - $dialog->addSubmitButton($submit); 144 - } 145 - 146 - return $dialog; 63 + ->addCancelButton($return_uri) 64 + ->addSubmitButton($submit); 147 65 } 148 66 149 67 }
+20 -52
src/applications/harbormaster/controller/HarbormasterBuildViewController.php
··· 533 533 534 534 $curtain = $this->newCurtainView($build); 535 535 536 - $can_restart = 537 - $build->canRestartBuild() && 538 - $build->canIssueCommand( 539 - $viewer, 540 - HarbormasterBuildCommand::COMMAND_RESTART); 541 - 542 - $can_pause = 543 - $build->canPauseBuild() && 544 - $build->canIssueCommand( 545 - $viewer, 546 - HarbormasterBuildCommand::COMMAND_PAUSE); 536 + $messages = array( 537 + new HarbormasterBuildMessageRestartTransaction(), 538 + new HarbormasterBuildMessagePauseTransaction(), 539 + new HarbormasterBuildMessageResumeTransaction(), 540 + new HarbormasterBuildMessageAbortTransaction(), 541 + ); 547 542 548 - $can_resume = 549 - $build->canResumeBuild() && 550 - $build->canIssueCommand( 551 - $viewer, 552 - HarbormasterBuildCommand::COMMAND_RESUME); 543 + foreach ($messages as $message) { 544 + $can_send = $message->canSendMessage($viewer, $build); 553 545 554 - $can_abort = 555 - $build->canAbortBuild() && 556 - $build->canIssueCommand( 557 - $viewer, 558 - HarbormasterBuildCommand::COMMAND_ABORT); 546 + $message_uri = urisprintf( 547 + '/build/%s/%d/', 548 + $message->getHarbormasterBuildMessageType(), 549 + $id); 550 + $message_uri = $this->getApplicationURI($message_uri); 559 551 560 - $curtain->addAction( 561 - id(new PhabricatorActionView()) 562 - ->setName(pht('Restart Build')) 563 - ->setIcon('fa-repeat') 564 - ->setHref($this->getApplicationURI('/build/restart/'.$id.'/')) 565 - ->setDisabled(!$can_restart) 566 - ->setWorkflow(true)); 552 + $action = id(new PhabricatorActionView()) 553 + ->setName($message->getHarbormasterBuildMessageName()) 554 + ->setIcon($message->getIcon()) 555 + ->setHref($message_uri) 556 + ->setDisabled(!$can_send) 557 + ->setWorkflow(true); 567 558 568 - if ($build->canResumeBuild()) { 569 - $curtain->addAction( 570 - id(new PhabricatorActionView()) 571 - ->setName(pht('Resume Build')) 572 - ->setIcon('fa-play') 573 - ->setHref($this->getApplicationURI('/build/resume/'.$id.'/')) 574 - ->setDisabled(!$can_resume) 575 - ->setWorkflow(true)); 576 - } else { 577 - $curtain->addAction( 578 - id(new PhabricatorActionView()) 579 - ->setName(pht('Pause Build')) 580 - ->setIcon('fa-pause') 581 - ->setHref($this->getApplicationURI('/build/pause/'.$id.'/')) 582 - ->setDisabled(!$can_pause) 583 - ->setWorkflow(true)); 559 + $curtain->addAction($action); 584 560 } 585 - 586 - $curtain->addAction( 587 - id(new PhabricatorActionView()) 588 - ->setName(pht('Abort Build')) 589 - ->setIcon('fa-exclamation-triangle') 590 - ->setHref($this->getApplicationURI('/build/abort/'.$id.'/')) 591 - ->setDisabled(!$can_abort) 592 - ->setWorkflow(true)); 593 561 594 562 return $curtain; 595 563 }
+115 -248
src/applications/harbormaster/controller/HarbormasterBuildableActionController.php
··· 22 22 return new Aphront404Response(); 23 23 } 24 24 25 - $issuable = array(); 25 + $message = 26 + HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType( 27 + $action); 28 + if (!$message) { 29 + return new Aphront404Response(); 30 + } 31 + 32 + $return_uri = '/'.$buildable->getMonogram(); 33 + 34 + // See T13348. Actions may apply to only a subset of builds, so give the 35 + // user a preview of what will happen. 36 + 37 + $can_send = array(); 26 38 39 + $rows = array(); 27 40 $builds = $buildable->getBuilds(); 28 41 foreach ($builds as $key => $build) { 29 - switch ($action) { 30 - case HarbormasterBuildCommand::COMMAND_RESTART: 31 - if ($build->canRestartBuild()) { 32 - $issuable[$key] = $build; 33 - } 34 - break; 35 - case HarbormasterBuildCommand::COMMAND_PAUSE: 36 - if ($build->canPauseBuild()) { 37 - $issuable[$key] = $build; 38 - } 39 - break; 40 - case HarbormasterBuildCommand::COMMAND_RESUME: 41 - if ($build->canResumeBuild()) { 42 - $issuable[$key] = $build; 43 - } 44 - break; 45 - case HarbormasterBuildCommand::COMMAND_ABORT: 46 - if ($build->canAbortBuild()) { 47 - $issuable[$key] = $build; 48 - } 49 - break; 50 - default: 51 - return new Aphront400Response(); 42 + $exception = null; 43 + try { 44 + $message->assertCanSendMessage($viewer, $build); 45 + $can_send[$key] = $build; 46 + } catch (HarbormasterRestartException $ex) { 47 + $exception = $ex; 52 48 } 53 - } 49 + 50 + if (!$exception) { 51 + $icon_icon = $message->getIcon(); 52 + $icon_color = 'green'; 54 53 55 - $restricted = false; 56 - foreach ($issuable as $key => $build) { 57 - if (!$build->canIssueCommand($viewer, $action)) { 58 - $restricted = true; 59 - unset($issuable[$key]); 54 + $title = $message->getHarbormasterBuildMessageName(); 55 + $body = $message->getHarbormasterBuildableMessageEffect(); 56 + } else { 57 + $icon_icon = 'fa-times'; 58 + $icon_color = 'red'; 59 + 60 + $title = $ex->getTitle(); 61 + $body = $ex->getBody(); 60 62 } 61 - } 63 + 64 + $icon = id(new PHUIIconView()) 65 + ->setIcon($icon_icon) 66 + ->setColor($icon_color); 67 + 68 + $build_name = phutil_tag( 69 + 'a', 70 + array( 71 + 'href' => $build->getURI(), 72 + 'target' => '_blank', 73 + ), 74 + pht('%s %s', $build->getObjectName(), $build->getName())); 62 75 63 - $building = false; 64 - foreach ($issuable as $key => $build) { 65 - if ($build->isBuilding()) { 66 - $building = true; 67 - break; 68 - } 76 + $rows[] = array( 77 + $icon, 78 + $build_name, 79 + $title, 80 + $body, 81 + ); 69 82 } 70 83 71 - $return_uri = '/'.$buildable->getMonogram(); 72 - if ($request->isDialogFormPost() && $issuable) { 84 + $table = id(new AphrontTableView($rows)) 85 + ->setHeaders( 86 + array( 87 + null, 88 + pht('Build'), 89 + pht('Action'), 90 + pht('Details'), 91 + )) 92 + ->setColumnClasses( 93 + array( 94 + null, 95 + null, 96 + 'pri', 97 + 'wide', 98 + )); 99 + 100 + $table = phutil_tag( 101 + 'div', 102 + array( 103 + 'class' => 'mlt mlb', 104 + ), 105 + $table); 106 + 107 + if ($request->isDialogFormPost() && $can_send) { 73 108 $editor = id(new HarbormasterBuildableTransactionEditor()) 74 109 ->setActor($viewer) 75 110 ->setContentSourceFromRequest($request) ··· 88 123 ->setContinueOnNoEffect(true) 89 124 ->setContinueOnMissingFields(true); 90 125 91 - foreach ($issuable as $build) { 92 - $xaction = id(new HarbormasterBuildTransaction()) 93 - ->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND) 94 - ->setNewValue($action); 95 - $build_editor->applyTransactions($build, array($xaction)); 126 + foreach ($can_send as $build) { 127 + $build->sendMessage( 128 + $viewer, 129 + $message->getHarbormasterBuildMessageType()); 96 130 } 97 131 98 132 return id(new AphrontRedirectResponse())->setURI($return_uri); 99 133 } 100 134 101 - $width = AphrontDialogView::WIDTH_DEFAULT; 135 + if (!$builds) { 136 + $title = pht('No Builds'); 137 + $body = pht( 138 + 'This buildable has no builds, so you can not issue any commands.'); 139 + } else { 140 + if ($can_send) { 141 + $title = $message->newBuildableConfirmPromptTitle( 142 + $builds, 143 + $can_send); 102 144 103 - switch ($action) { 104 - case HarbormasterBuildCommand::COMMAND_RESTART: 105 - // See T13348. The "Restart Builds" action may restart only a subset 106 - // of builds, so show the user a preview of which builds will actually 107 - // restart. 145 + $body = $message->newBuildableConfirmPromptBody( 146 + $builds, 147 + $can_send); 148 + } else { 149 + $title = pht('Unable to Send Command'); 150 + $body = pht( 151 + 'You can not send this command to any of the current builds '. 152 + 'for this buildable.'); 153 + } 108 154 109 - $body = array(); 155 + $body = array( 156 + pht('Builds for this buildable:'), 157 + $table, 158 + $body, 159 + ); 160 + } 110 161 111 - if ($issuable) { 112 - $title = pht('Restart Builds'); 113 - $submit = pht('Restart Builds'); 114 - } else { 115 - $title = pht('Unable to Restart Builds'); 116 - } 162 + $warnings = $message->newBuildableConfirmPromptWarnings( 163 + $builds, 164 + $can_send); 117 165 118 - if ($builds) { 119 - $width = AphrontDialogView::WIDTH_FORM; 120 - 121 - $body[] = pht('Builds for this buildable:'); 166 + if ($warnings) { 167 + $body[] = id(new PHUIInfoView()) 168 + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 169 + ->setErrors($warnings); 170 + } 122 171 123 - $rows = array(); 124 - foreach ($builds as $key => $build) { 125 - if (isset($issuable[$key])) { 126 - $icon = id(new PHUIIconView()) 127 - ->setIcon('fa-repeat green'); 128 - $build_note = pht('Will Restart'); 129 - } else { 130 - $icon = null; 172 + $submit = $message->getHarbormasterBuildableMessageName(); 131 173 132 - try { 133 - $build->assertCanRestartBuild(); 134 - } catch (HarbormasterRestartException $ex) { 135 - $icon = id(new PHUIIconView()) 136 - ->setIcon('fa-times red'); 137 - $build_note = pht( 138 - '%s: %s', 139 - phutil_tag('strong', array(), pht('Not Restartable')), 140 - $ex->getTitle()); 141 - } 142 - 143 - if (!$icon) { 144 - try { 145 - $build->assertCanIssueCommand($viewer, $action); 146 - } catch (PhabricatorPolicyException $ex) { 147 - $icon = id(new PHUIIconView()) 148 - ->setIcon('fa-lock red'); 149 - $build_note = pht( 150 - '%s: %s', 151 - phutil_tag('strong', array(), pht('Not Restartable')), 152 - pht('You do not have permission to restart this build.')); 153 - } 154 - } 155 - 156 - if (!$icon) { 157 - $icon = id(new PHUIIconView()) 158 - ->setIcon('fa-times red'); 159 - $build_note = pht('Will Not Restart'); 160 - } 161 - } 162 - 163 - $build_name = phutil_tag( 164 - 'a', 165 - array( 166 - 'href' => $build->getURI(), 167 - 'target' => '_blank', 168 - ), 169 - pht('%s %s', $build->getObjectName(), $build->getName())); 170 - 171 - $rows[] = array( 172 - $icon, 173 - $build_name, 174 - $build_note, 175 - ); 176 - } 177 - 178 - $table = id(new AphrontTableView($rows)) 179 - ->setHeaders( 180 - array( 181 - null, 182 - pht('Build'), 183 - pht('Action'), 184 - )) 185 - ->setColumnClasses( 186 - array( 187 - null, 188 - 'pri', 189 - 'wide', 190 - )); 191 - 192 - $table = phutil_tag( 193 - 'div', 194 - array( 195 - 'class' => 'mlt mlb', 196 - ), 197 - $table); 198 - 199 - $body[] = $table; 200 - } 201 - 202 - if ($issuable) { 203 - $warnings = array(); 204 - 205 - if ($restricted) { 206 - $warnings[] = pht( 207 - 'You only have permission to restart some builds.'); 208 - } 209 - 210 - if ($building) { 211 - $warnings[] = pht( 212 - 'Progress on running builds will be discarded.'); 213 - } 214 - 215 - $warnings[] = pht( 216 - 'When a build is restarted, side effects associated with '. 217 - 'the build may occur again.'); 218 - 219 - $body[] = id(new PHUIInfoView()) 220 - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 221 - ->setErrors($warnings); 222 - 223 - $body[] = pht('Really restart builds?'); 224 - } else { 225 - if ($restricted) { 226 - $body[] = pht('You do not have permission to restart any builds.'); 227 - } else { 228 - $body[] = pht('No builds can be restarted.'); 229 - } 230 - } 231 - 232 - break; 233 - case HarbormasterBuildCommand::COMMAND_PAUSE: 234 - if ($issuable) { 235 - $title = pht('Really pause builds?'); 236 - 237 - if ($restricted) { 238 - $body = pht( 239 - 'You only have permission to pause some builds. Once the '. 240 - 'current steps complete, work will halt on builds you have '. 241 - 'permission to pause. You can resume the builds later.'); 242 - } else { 243 - $body = pht( 244 - 'If you pause all builds, work will halt once the current steps '. 245 - 'complete. You can resume the builds later.'); 246 - } 247 - $submit = pht('Pause Builds'); 248 - } else { 249 - $title = pht('Unable to Pause Builds'); 250 - 251 - if ($restricted) { 252 - $body = pht('You do not have permission to pause any builds.'); 253 - } else { 254 - $body = pht('No builds can be paused.'); 255 - } 256 - } 257 - break; 258 - case HarbormasterBuildCommand::COMMAND_ABORT: 259 - if ($issuable) { 260 - $title = pht('Really abort builds?'); 261 - if ($restricted) { 262 - $body = pht( 263 - 'You only have permission to abort some builds. Work will '. 264 - 'halt immediately on builds you have permission to abort. '. 265 - 'Progress will be discarded, and builds must be completely '. 266 - 'restarted if you want them to complete.'); 267 - } else { 268 - $body = pht( 269 - 'If you abort all builds, work will halt immediately. Work '. 270 - 'will be discarded, and builds must be completely restarted.'); 271 - } 272 - $submit = pht('Abort Builds'); 273 - } else { 274 - $title = pht('Unable to Abort Builds'); 275 - 276 - if ($restricted) { 277 - $body = pht('You do not have permission to abort any builds.'); 278 - } else { 279 - $body = pht('No builds can be aborted.'); 280 - } 281 - } 282 - break; 283 - case HarbormasterBuildCommand::COMMAND_RESUME: 284 - if ($issuable) { 285 - $title = pht('Really resume builds?'); 286 - if ($restricted) { 287 - $body = pht( 288 - 'You only have permission to resume some builds. Work will '. 289 - 'continue on builds you have permission to resume.'); 290 - } else { 291 - $body = pht('Work will continue on all builds. Really resume?'); 292 - } 293 - 294 - $submit = pht('Resume Builds'); 295 - } else { 296 - $title = pht('Unable to Resume Builds'); 297 - if ($restricted) { 298 - $body = pht('You do not have permission to resume any builds.'); 299 - } else { 300 - $body = pht('No builds can be resumed.'); 301 - } 302 - } 303 - break; 304 - } 305 - 306 - $dialog = id(new AphrontDialogView()) 307 - ->setUser($viewer) 308 - ->setWidth($width) 174 + $dialog = $this->newDialog() 175 + ->setWidth(AphrontDialogView::WIDTH_FULL) 309 176 ->setTitle($title) 310 177 ->appendChild($body) 311 178 ->addCancelButton($return_uri); 312 179 313 - if ($issuable) { 180 + if ($can_send) { 314 181 $dialog->addSubmitButton($submit); 315 182 } 316 183 317 - return id(new AphrontDialogResponse())->setDialog($dialog); 184 + return $dialog; 318 185 } 319 186 320 187 }
+31 -106
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 87 87 $buildable, 88 88 PhabricatorPolicyCapability::CAN_EDIT); 89 89 90 - $can_restart = false; 91 - $can_resume = false; 92 - $can_pause = false; 93 - $can_abort = false; 90 + $messages = array( 91 + new HarbormasterBuildMessageRestartTransaction(), 92 + new HarbormasterBuildMessagePauseTransaction(), 93 + new HarbormasterBuildMessageResumeTransaction(), 94 + new HarbormasterBuildMessageAbortTransaction(), 95 + ); 94 96 95 - $command_restart = HarbormasterBuildCommand::COMMAND_RESTART; 96 - $command_resume = HarbormasterBuildCommand::COMMAND_RESUME; 97 - $command_pause = HarbormasterBuildCommand::COMMAND_PAUSE; 98 - $command_abort = HarbormasterBuildCommand::COMMAND_ABORT; 97 + foreach ($messages as $message) { 99 98 100 - foreach ($buildable->getBuilds() as $build) { 101 - if ($build->canRestartBuild()) { 102 - if ($build->canIssueCommand($viewer, $command_restart)) { 103 - $can_restart = true; 104 - } 105 - } 106 - if ($build->canResumeBuild()) { 107 - if ($build->canIssueCommand($viewer, $command_resume)) { 108 - $can_resume = true; 109 - } 110 - } 111 - if ($build->canPauseBuild()) { 112 - if ($build->canIssueCommand($viewer, $command_pause)) { 113 - $can_pause = true; 99 + // Messages are enabled if they can be sent to at least one build. 100 + $can_send = false; 101 + foreach ($buildable->getBuilds() as $build) { 102 + $can_send = $message->canSendMessage($viewer, $build); 103 + if ($can_send) { 104 + break; 114 105 } 115 106 } 116 - if ($build->canAbortBuild()) { 117 - if ($build->canIssueCommand($viewer, $command_abort)) { 118 - $can_abort = true; 119 - } 120 - } 121 - } 122 107 123 - $restart_uri = "buildable/{$id}/restart/"; 124 - $pause_uri = "buildable/{$id}/pause/"; 125 - $resume_uri = "buildable/{$id}/resume/"; 126 - $abort_uri = "buildable/{$id}/abort/"; 108 + $message_uri = urisprintf( 109 + '/buildable/%d/%s/', 110 + $id, 111 + $message->getHarbormasterBuildMessageType()); 112 + $message_uri = $this->getApplicationURI($message_uri); 127 113 128 - $curtain->addAction( 129 - id(new PhabricatorActionView()) 130 - ->setIcon('fa-repeat') 131 - ->setName(pht('Restart Builds')) 132 - ->setHref($this->getApplicationURI($restart_uri)) 133 - ->setWorkflow(true) 134 - ->setDisabled(!$can_restart || !$can_edit)); 114 + $action = id(new PhabricatorActionView()) 115 + ->setName($message->getHarbormasterBuildableMessageName()) 116 + ->setIcon($message->getIcon()) 117 + ->setHref($message_uri) 118 + ->setDisabled(!$can_send || !$can_edit) 119 + ->setWorkflow(true); 135 120 136 - $curtain->addAction( 137 - id(new PhabricatorActionView()) 138 - ->setIcon('fa-pause') 139 - ->setName(pht('Pause Builds')) 140 - ->setHref($this->getApplicationURI($pause_uri)) 141 - ->setWorkflow(true) 142 - ->setDisabled(!$can_pause || !$can_edit)); 143 - 144 - $curtain->addAction( 145 - id(new PhabricatorActionView()) 146 - ->setIcon('fa-play') 147 - ->setName(pht('Resume Builds')) 148 - ->setHref($this->getApplicationURI($resume_uri)) 149 - ->setWorkflow(true) 150 - ->setDisabled(!$can_resume || !$can_edit)); 151 - 152 - $curtain->addAction( 153 - id(new PhabricatorActionView()) 154 - ->setIcon('fa-exclamation-triangle') 155 - ->setName(pht('Abort Builds')) 156 - ->setHref($this->getApplicationURI($abort_uri)) 157 - ->setWorkflow(true) 158 - ->setDisabled(!$can_abort || !$can_edit)); 121 + $curtain->addAction($action); 122 + } 159 123 160 124 return $curtain; 161 125 } ··· 198 162 ->setUser($viewer); 199 163 foreach ($buildable->getBuilds() as $build) { 200 164 $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); 165 + 201 166 $item = id(new PHUIObjectItemView()) 202 167 ->setObjectName(pht('Build %d', $build->getID())) 203 168 ->setHeader($build->getName()) 204 169 ->setHref($view_uri); 205 170 206 - $status = $build->getBuildStatus(); 207 - $status_color = HarbormasterBuildStatus::getBuildStatusColor($status); 208 - $status_name = HarbormasterBuildStatus::getBuildStatusName($status); 209 - $item->setStatusIcon('fa-dot-circle-o '.$status_color, $status_name); 210 - $item->addAttribute($status_name); 211 - 212 - if ($build->isRestarting()) { 213 - $item->addIcon('fa-repeat', pht('Restarting')); 214 - } else if ($build->isPausing()) { 215 - $item->addIcon('fa-pause', pht('Pausing')); 216 - } else if ($build->isResuming()) { 217 - $item->addIcon('fa-play', pht('Resuming')); 218 - } 219 - 220 - $build_id = $build->getID(); 171 + $status = $build->getBuildPendingStatusObject(); 221 172 222 - $restart_uri = "build/restart/{$build_id}/buildable/"; 223 - $resume_uri = "build/resume/{$build_id}/buildable/"; 224 - $pause_uri = "build/pause/{$build_id}/buildable/"; 225 - $abort_uri = "build/abort/{$build_id}/buildable/"; 226 - 227 - $item->addAction( 228 - id(new PHUIListItemView()) 229 - ->setIcon('fa-repeat') 230 - ->setName(pht('Restart')) 231 - ->setHref($this->getApplicationURI($restart_uri)) 232 - ->setWorkflow(true) 233 - ->setDisabled(!$build->canRestartBuild())); 234 - 235 - if ($build->canResumeBuild()) { 236 - $item->addAction( 237 - id(new PHUIListItemView()) 238 - ->setIcon('fa-play') 239 - ->setName(pht('Resume')) 240 - ->setHref($this->getApplicationURI($resume_uri)) 241 - ->setWorkflow(true)); 242 - } else { 243 - $item->addAction( 244 - id(new PHUIListItemView()) 245 - ->setIcon('fa-pause') 246 - ->setName(pht('Pause')) 247 - ->setHref($this->getApplicationURI($pause_uri)) 248 - ->setWorkflow(true) 249 - ->setDisabled(!$build->canPauseBuild())); 250 - } 173 + $item->setStatusIcon( 174 + $status->getIconIcon().' '.$status->getIconColor(), 175 + $status->getName()); 251 176 252 177 $targets = $build->getBuildTargets(); 253 178
+9
src/applications/harbormaster/exception/HarbormasterRestartException.php
··· 30 30 return $this->body; 31 31 } 32 32 33 + public function newDisplayString() { 34 + $title = $this->getTitle(); 35 + 36 + $body = $this->getBody(); 37 + $body = implode("\n\n", $body); 38 + 39 + return pht('%s: %s', $title, $body); 40 + } 41 + 33 42 }
+7 -4
src/applications/harbormaster/management/HarbormasterManagementRestartWorkflow.php
··· 61 61 throw new ArcanistUserAbortException(); 62 62 } 63 63 64 + $message = new HarbormasterBuildMessageRestartTransaction(); 65 + 64 66 foreach ($builds as $build) { 65 67 $this->logInfo( 66 68 pht('RESTARTING'), 67 69 pht('Build %d: %s', $build->getID(), $build->getName())); 68 70 69 - if (!$build->canRestartBuild()) { 71 + try { 72 + $message->assertCanSendMessage($viewer, $build); 73 + } catch (HarbormasterRestartException $ex) { 70 74 $this->logWarn( 71 75 pht('INVALID'), 72 - pht('Build can not be restarted.')); 73 - continue; 76 + $ex->newDisplayString()); 74 77 } 75 78 76 79 $build->sendMessage( 77 80 $viewer, 78 - HarbormasterBuildCommand::COMMAND_RESTART); 81 + $message->getHarbormasterBuildMessageType()); 79 82 80 83 $this->logOkay( 81 84 pht('QUEUED'),
-146
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 315 315 return $this; 316 316 } 317 317 318 - public function canRestartBuild() { 319 - try { 320 - $this->assertCanRestartBuild(); 321 - return true; 322 - } catch (HarbormasterRestartException $ex) { 323 - return false; 324 - } 325 - } 326 - 327 - public function assertCanRestartBuild() { 328 - if ($this->isAutobuild()) { 329 - throw new HarbormasterRestartException( 330 - pht('Can Not Restart Autobuild'), 331 - pht( 332 - 'This build can not be restarted because it is an automatic '. 333 - 'build.')); 334 - } 335 - 336 - $restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE; 337 - $plan = $this->getBuildPlan(); 338 - 339 - // See T13526. Users who can't see the "BuildPlan" can end up here with 340 - // no object. This is highly questionable. 341 - if (!$plan) { 342 - throw new HarbormasterRestartException( 343 - pht('No Build Plan Permission'), 344 - pht( 345 - 'You can not restart this build because you do not have '. 346 - 'permission to access the build plan.')); 347 - } 348 - 349 - $option = HarbormasterBuildPlanBehavior::getBehavior($restartable) 350 - ->getPlanOption($plan); 351 - $option_key = $option->getKey(); 352 - 353 - $never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER; 354 - $is_never = ($option_key === $never_restartable); 355 - if ($is_never) { 356 - throw new HarbormasterRestartException( 357 - pht('Build Plan Prevents Restart'), 358 - pht( 359 - 'This build can not be restarted because the build plan is '. 360 - 'configured to prevent the build from restarting.')); 361 - } 362 - 363 - $failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED; 364 - $is_failed = ($option_key === $failed_restartable); 365 - if ($is_failed) { 366 - if (!$this->isFailed()) { 367 - throw new HarbormasterRestartException( 368 - pht('Only Restartable if Failed'), 369 - pht( 370 - 'This build can not be restarted because the build plan is '. 371 - 'configured to prevent the build from restarting unless it '. 372 - 'has failed, and it has not failed.')); 373 - } 374 - } 375 - 376 - if ($this->isRestarting()) { 377 - throw new HarbormasterRestartException( 378 - pht('Already Restarting'), 379 - pht( 380 - 'This build is already restarting. You can not reissue a restart '. 381 - 'command to a restarting build.')); 382 - } 383 - } 384 - 385 - public function canPauseBuild() { 386 - if ($this->isAutobuild()) { 387 - return false; 388 - } 389 - 390 - return !$this->isComplete() && 391 - !$this->isPaused() && 392 - !$this->isPausing() && 393 - !$this->isRestarting() && 394 - !$this->isAborting(); 395 - } 396 - 397 - public function canAbortBuild() { 398 - if ($this->isAutobuild()) { 399 - return false; 400 - } 401 - 402 - return 403 - !$this->isComplete() && 404 - !$this->isAborting(); 405 - } 406 - 407 - public function canResumeBuild() { 408 - if ($this->isAutobuild()) { 409 - return false; 410 - } 411 - 412 - return 413 - $this->isPaused() && 414 - !$this->isResuming() && 415 - !$this->isRestarting() && 416 - !$this->isAborting(); 417 - } 418 - 419 318 public function isPausing() { 420 319 return $this->getBuildPendingStatusObject()->isPausing(); 421 320 } ··· 449 348 } 450 349 451 350 return $this; 452 - } 453 - 454 - public function canIssueCommand(PhabricatorUser $viewer, $command) { 455 - try { 456 - $this->assertCanIssueCommand($viewer, $command); 457 - return true; 458 - } catch (Exception $ex) { 459 - return false; 460 - } 461 - } 462 - 463 - public function assertCanIssueCommand(PhabricatorUser $viewer, $command) { 464 - $plan = $this->getBuildPlan(); 465 - 466 - // See T13526. Users without permission to access the build plan can 467 - // currently end up here with no "BuildPlan" object. 468 - if (!$plan) { 469 - return false; 470 - } 471 - 472 - $need_edit = true; 473 - switch ($command) { 474 - case HarbormasterBuildCommand::COMMAND_RESTART: 475 - case HarbormasterBuildCommand::COMMAND_PAUSE: 476 - case HarbormasterBuildCommand::COMMAND_RESUME: 477 - case HarbormasterBuildCommand::COMMAND_ABORT: 478 - if ($plan->canRunWithoutEditCapability()) { 479 - $need_edit = false; 480 - } 481 - break; 482 - default: 483 - throw new Exception( 484 - pht( 485 - 'Invalid Harbormaster build command "%s".', 486 - $command)); 487 - } 488 - 489 - // Issuing these commands requires that you be able to edit the build, to 490 - // prevent enemy engineers from sabotaging your builds. See T9614. 491 - if ($need_edit) { 492 - PhabricatorPolicyFilter::requireCapability( 493 - $viewer, 494 - $plan, 495 - PhabricatorPolicyCapability::CAN_EDIT); 496 - } 497 351 } 498 352 499 353 public function sendMessage(PhabricatorUser $viewer, $message_type) {
+74 -2
src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php
··· 4 4 extends HarbormasterBuildMessageTransaction { 5 5 6 6 const TRANSACTIONTYPE = 'message/abort'; 7 + const MESSAGETYPE = 'abort'; 7 8 8 - public function getMessageType() { 9 - return 'abort'; 9 + public function getHarbormasterBuildMessageName() { 10 + return pht('Abort Build'); 11 + } 12 + 13 + public function getHarbormasterBuildableMessageName() { 14 + return pht('Abort Builds'); 15 + } 16 + 17 + public function newConfirmPromptTitle() { 18 + return pht('Really abort build?'); 19 + } 20 + 21 + public function getHarbormasterBuildableMessageEffect() { 22 + return pht('Build will abort.'); 23 + } 24 + 25 + public function newConfirmPromptBody() { 26 + return pht( 27 + 'Progress on this build will be discarded. Really abort build?'); 28 + } 29 + 30 + public function newBuildableConfirmPromptTitle( 31 + array $builds, 32 + array $sendable) { 33 + return pht( 34 + 'Really abort %s build(s)?', 35 + phutil_count($builds)); 36 + } 37 + 38 + public function newBuildableConfirmPromptBody( 39 + array $builds, 40 + array $sendable) { 41 + 42 + if (count($sendable) === count($builds)) { 43 + return pht( 44 + 'If you abort all builds, work will halt immediately. Work '. 45 + 'will be discarded, and builds must be completely restarted.'); 46 + } else { 47 + return pht( 48 + 'You can only abort some builds. Work will halt immediately on '. 49 + 'builds you can abort. Progress will be discarded, and builds must '. 50 + 'be completely restarted if you want them to complete.'); 51 + } 10 52 } 11 53 12 54 public function getTitle() { ··· 37 79 $build->releaseAllArtifacts($actor); 38 80 } 39 81 82 + protected function newCanApplyMessageAssertion( 83 + PhabricatorUser $viewer, 84 + HarbormasterBuild $build) { 85 + 86 + if ($build->isAutobuild()) { 87 + throw new HarbormasterRestartException( 88 + pht('Unable to Abort Build'), 89 + pht( 90 + 'You can not abort a build that uses an autoplan.')); 91 + } 92 + 93 + if ($build->isComplete()) { 94 + throw new HarbormasterRestartException( 95 + pht('Unable to Abort Build'), 96 + pht( 97 + 'You can not abort this biuld because it is already complete.')); 98 + } 99 + } 100 + 101 + protected function newCanSendMessageAssertion( 102 + PhabricatorUser $viewer, 103 + HarbormasterBuild $build) { 104 + 105 + if ($build->isAborting()) { 106 + throw new HarbormasterRestartException( 107 + pht('Unable to Abort Build'), 108 + pht( 109 + 'You can not abort this build because it is already aborting.')); 110 + } 111 + } 40 112 41 113 }
+90 -2
src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php
··· 4 4 extends HarbormasterBuildMessageTransaction { 5 5 6 6 const TRANSACTIONTYPE = 'message/pause'; 7 + const MESSAGETYPE = 'pause'; 7 8 8 - public function getMessageType() { 9 - return 'pause'; 9 + public function getHarbormasterBuildMessageName() { 10 + return pht('Pause Build'); 11 + } 12 + 13 + public function getHarbormasterBuildableMessageName() { 14 + return pht('Pause Builds'); 15 + } 16 + 17 + public function newConfirmPromptTitle() { 18 + return pht('Really pause build?'); 19 + } 20 + 21 + public function getHarbormasterBuildableMessageEffect() { 22 + return pht('Build will pause.'); 23 + } 24 + 25 + public function newConfirmPromptBody() { 26 + return pht( 27 + 'If you pause this build, work will halt once the current steps '. 28 + 'complete. You can resume the build later.'); 29 + } 30 + 31 + public function newBuildableConfirmPromptTitle( 32 + array $builds, 33 + array $sendable) { 34 + return pht( 35 + 'Really pause %s build(s)?', 36 + phutil_count($builds)); 37 + } 38 + 39 + public function newBuildableConfirmPromptBody( 40 + array $builds, 41 + array $sendable) { 42 + 43 + if (count($sendable) === count($builds)) { 44 + return pht( 45 + 'If you pause all builds, work will halt once the current steps '. 46 + 'complete. You can resume the builds later.'); 47 + } else { 48 + return pht( 49 + 'You can only pause some builds. Once the current steps complete, '. 50 + 'work will halt on builds you can pause. You can resume the builds '. 51 + 'later.'); 52 + } 10 53 } 11 54 12 55 public function getTitle() { ··· 30 73 $build->setBuildStatus(HarbormasterBuildStatus::STATUS_PAUSED); 31 74 } 32 75 76 + protected function newCanApplyMessageAssertion( 77 + PhabricatorUser $viewer, 78 + HarbormasterBuild $build) { 79 + 80 + if ($build->isAutobuild()) { 81 + throw new HarbormasterRestartException( 82 + pht('Unable to Pause Build'), 83 + pht('You can not pause a build that uses an autoplan.')); 84 + } 85 + 86 + if ($build->isPaused()) { 87 + throw new HarbormasterRestartException( 88 + pht('Unable to Pause Build'), 89 + pht('You can not pause this build because it is already paused.')); 90 + } 91 + 92 + if ($build->isComplete()) { 93 + throw new HarbormasterRestartException( 94 + pht('Unable to Pause Build'), 95 + pht('You can not pause this build because it has already completed.')); 96 + } 97 + } 98 + 99 + protected function newCanSendMessageAssertion( 100 + PhabricatorUser $viewer, 101 + HarbormasterBuild $build) { 102 + 103 + if ($build->isPausing()) { 104 + throw new HarbormasterRestartException( 105 + pht('Unable to Pause Build'), 106 + pht('You can not pause this build because it is already pausing.')); 107 + } 108 + 109 + if ($build->isRestarting()) { 110 + throw new HarbormasterRestartException( 111 + pht('Unable to Pause Build'), 112 + pht('You can not pause this build because it is already restarting.')); 113 + } 114 + 115 + if ($build->isAborting()) { 116 + throw new HarbormasterRestartException( 117 + pht('Unable to Pause Build'), 118 + pht('You can not pause this build because it is already aborting.')); 119 + } 120 + } 33 121 }
+139 -3
src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php
··· 4 4 extends HarbormasterBuildMessageTransaction { 5 5 6 6 const TRANSACTIONTYPE = 'message/restart'; 7 + const MESSAGETYPE = 'restart'; 7 8 8 - public function getMessageType() { 9 - return 'restart'; 9 + public function getHarbormasterBuildMessageName() { 10 + return pht('Restart Build'); 11 + } 12 + 13 + public function getHarbormasterBuildableMessageName() { 14 + return pht('Restart Builds'); 15 + } 16 + 17 + public function getHarbormasterBuildableMessageEffect() { 18 + return pht('Build will restart.'); 19 + } 20 + 21 + public function newConfirmPromptTitle() { 22 + return pht('Really restart build?'); 23 + } 24 + 25 + public function newConfirmPromptBody() { 26 + return pht( 27 + 'Progress on this build will be discarded and the build will restart. '. 28 + 'Side effects of the build will occur again. Really restart build?'); 29 + } 30 + 31 + public function newBuildableConfirmPromptTitle( 32 + array $builds, 33 + array $sendable) { 34 + return pht( 35 + 'Really restart %s build(s)?', 36 + phutil_count($builds)); 37 + } 38 + 39 + public function newBuildableConfirmPromptBody( 40 + array $builds, 41 + array $sendable) { 42 + 43 + if (count($sendable) === count($builds)) { 44 + return pht( 45 + 'All builds will restart.'); 46 + } else { 47 + return pht( 48 + 'You can only restart some builds.'); 49 + } 50 + } 51 + 52 + public function newBuildableConfirmPromptWarnings( 53 + array $builds, 54 + array $sendable) { 55 + 56 + $building = false; 57 + foreach ($sendable as $build) { 58 + if ($build->isBuilding()) { 59 + $building = true; 60 + break; 61 + } 62 + } 63 + 64 + $warnings = array(); 65 + 66 + if ($building) { 67 + $warnings[] = pht( 68 + 'Progress on running builds will be discarded.'); 69 + } 70 + 71 + if ($sendable) { 72 + $warnings[] = pht( 73 + 'When a build is restarted, side effects associated with '. 74 + 'the build may occur again.'); 75 + } 76 + 77 + return $warnings; 10 78 } 11 79 12 80 public function getTitle() { ··· 16 84 } 17 85 18 86 public function getIcon() { 19 - return 'fa-backward'; 87 + return 'fa-repeat'; 20 88 } 21 89 22 90 public function applyInternalEffects($object, $value) { ··· 25 93 26 94 $build->restartBuild($actor); 27 95 $build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING); 96 + } 97 + 98 + protected function newCanApplyMessageAssertion( 99 + PhabricatorUser $viewer, 100 + HarbormasterBuild $build) { 101 + 102 + if ($build->isAutobuild()) { 103 + throw new HarbormasterRestartException( 104 + pht('Can Not Restart Autobuild'), 105 + pht( 106 + 'This build can not be restarted because it is an automatic '. 107 + 'build.')); 108 + } 109 + 110 + $restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE; 111 + $plan = $build->getBuildPlan(); 112 + 113 + // See T13526. Users who can't see the "BuildPlan" can end up here with 114 + // no object. This is highly questionable. 115 + if (!$plan) { 116 + throw new HarbormasterRestartException( 117 + pht('No Build Plan Permission'), 118 + pht( 119 + 'You can not restart this build because you do not have '. 120 + 'permission to access the build plan.')); 121 + } 122 + 123 + $option = HarbormasterBuildPlanBehavior::getBehavior($restartable) 124 + ->getPlanOption($plan); 125 + $option_key = $option->getKey(); 126 + 127 + $never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER; 128 + $is_never = ($option_key === $never_restartable); 129 + if ($is_never) { 130 + throw new HarbormasterRestartException( 131 + pht('Build Plan Prevents Restart'), 132 + pht( 133 + 'This build can not be restarted because the build plan is '. 134 + 'configured to prevent the build from restarting.')); 135 + } 136 + 137 + $failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED; 138 + $is_failed = ($option_key === $failed_restartable); 139 + if ($is_failed) { 140 + if (!$this->isFailed()) { 141 + throw new HarbormasterRestartException( 142 + pht('Only Restartable if Failed'), 143 + pht( 144 + 'This build can not be restarted because the build plan is '. 145 + 'configured to prevent the build from restarting unless it '. 146 + 'has failed, and it has not failed.')); 147 + } 148 + } 149 + 150 + } 151 + 152 + protected function newCanSendMessageAssertion( 153 + PhabricatorUser $viewer, 154 + HarbormasterBuild $build) { 155 + 156 + if ($build->isRestarting()) { 157 + throw new HarbormasterRestartException( 158 + pht('Already Restarting'), 159 + pht( 160 + 'This build is already restarting. You can not reissue a restart '. 161 + 'command to a restarting build.')); 162 + } 163 + 28 164 } 29 165 30 166 }
+88 -2
src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php
··· 4 4 extends HarbormasterBuildMessageTransaction { 5 5 6 6 const TRANSACTIONTYPE = 'message/resume'; 7 + const MESSAGETYPE = 'resume'; 7 8 8 - public function getMessageType() { 9 - return 'resume'; 9 + public function getHarbormasterBuildMessageName() { 10 + return pht('Resume Build'); 11 + } 12 + 13 + public function getHarbormasterBuildableMessageName() { 14 + return pht('Resume Builds'); 15 + } 16 + 17 + public function getHarbormasterBuildableMessageEffect() { 18 + return pht('Build will resume.'); 19 + } 20 + 21 + public function newConfirmPromptTitle() { 22 + return pht('Really resume build?'); 23 + } 24 + 25 + public function newConfirmPromptBody() { 26 + return pht( 27 + 'Work will continue on the build. Really resume?'); 28 + } 29 + 30 + public function newBuildableConfirmPromptTitle( 31 + array $builds, 32 + array $sendable) { 33 + return pht( 34 + 'Really resume %s build(s)?', 35 + phutil_count($builds)); 36 + } 37 + 38 + public function newBuildableConfirmPromptBody( 39 + array $builds, 40 + array $sendable) { 41 + 42 + if (count($sendable) === count($builds)) { 43 + return pht( 44 + 'Work will continue on all builds. Really resume?'); 45 + } else { 46 + return pht( 47 + 'You can only resume some builds. Work will continue on builds '. 48 + 'you have permission to resume.'); 49 + } 10 50 } 11 51 12 52 public function getTitle() { ··· 24 64 $build = $object; 25 65 26 66 $build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING); 67 + } 68 + 69 + protected function newCanApplyMessageAssertion( 70 + PhabricatorUser $viewer, 71 + HarbormasterBuild $build) { 72 + 73 + if ($build->isAutobuild()) { 74 + throw new HarbormasterRestartException( 75 + pht('Unable to Resume Build'), 76 + pht( 77 + 'You can not resume a build that uses an autoplan.')); 78 + } 79 + 80 + if (!$build->isPaused()) { 81 + throw new HarbormasterRestartException( 82 + pht('Unable to Resume Build'), 83 + pht( 84 + 'You can not resume this build because it is not paused. You can '. 85 + 'only resume a paused build.')); 86 + } 87 + 88 + } 89 + 90 + protected function newCanSendMessageAssertion( 91 + PhabricatorUser $viewer, 92 + HarbormasterBuild $build) { 93 + 94 + if ($build->isResuming()) { 95 + throw new HarbormasterRestartException( 96 + pht('Unable to Resume Build'), 97 + pht( 98 + 'You can not resume this build beacuse it is already resuming.')); 99 + } 100 + 101 + if ($build->isRestarting()) { 102 + throw new HarbormasterRestartException( 103 + pht('Unable to Resume Build'), 104 + pht('You can not resume this build because it is already restarting.')); 105 + } 106 + 107 + if ($build->isAborting()) { 108 + throw new HarbormasterRestartException( 109 + pht('Unable to Resume Build'), 110 + pht('You can not resume this build because it is already aborting.')); 111 + } 112 + 27 113 } 28 114 29 115 }
+115 -12
src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php
··· 3 3 abstract class HarbormasterBuildMessageTransaction 4 4 extends HarbormasterBuildTransactionType { 5 5 6 + final public function getHarbormasterBuildMessageType() { 7 + return $this->getPhobjectClassConstant('MESSAGETYPE'); 8 + } 9 + 10 + abstract public function getHarbormasterBuildMessageName(); 11 + abstract public function getHarbormasterBuildableMessageName(); 12 + abstract public function getHarbormasterBuildableMessageEffect(); 13 + 14 + abstract public function newConfirmPromptTitle(); 15 + abstract public function newConfirmPromptBody(); 16 + 17 + abstract public function newBuildableConfirmPromptTitle( 18 + array $builds, 19 + array $sendable); 20 + 21 + abstract public function newBuildableConfirmPromptBody( 22 + array $builds, 23 + array $sendable); 24 + 25 + public function newBuildableConfirmPromptWarnings( 26 + array $builds, 27 + array $sendable) { 28 + return array(); 29 + } 30 + 6 31 final public function generateOldValue($object) { 7 32 return null; 8 33 } ··· 17 42 ); 18 43 } 19 44 20 - final public static function getTransactionTypeForMessageType($message_type) { 45 + final public static function getTransactionObjectForMessageType( 46 + $message_type) { 21 47 $message_xactions = id(new PhutilClassMapQuery()) 22 48 ->setAncestorClass(__CLASS__) 23 49 ->execute(); 24 50 25 51 foreach ($message_xactions as $message_xaction) { 26 - if ($message_xaction->getMessageType() === $message_type) { 27 - return $message_xaction->getTransactionTypeConstant(); 52 + $xaction_type = $message_xaction->getHarbormasterBuildMessageType(); 53 + if ($xaction_type === $message_type) { 54 + return $message_xaction; 28 55 } 29 56 } 30 57 31 58 return null; 32 59 } 33 60 34 - abstract public function getMessageType(); 61 + final public static function getTransactionTypeForMessageType($message_type) { 62 + $message_xaction = self::getTransactionObjectForMessageType($message_type); 63 + 64 + if ($message_xaction) { 65 + return $message_xaction->getTransactionTypeConstant(); 66 + } 67 + 68 + return null; 69 + } 70 + 71 + final public function getTransactionHasEffect($object, $old, $new) { 72 + return $this->canApplyMessage($this->getActor(), $object); 73 + } 74 + 75 + final public function canApplyMessage( 76 + PhabricatorUser $viewer, 77 + HarbormasterBuild $build) { 78 + 79 + try { 80 + $this->assertCanApplyMessage($viewer, $build); 81 + return true; 82 + } catch (HarbormasterRestartException $ex) { 83 + return false; 84 + } 85 + } 86 + 87 + final public function canSendMessage( 88 + PhabricatorUser $viewer, 89 + HarbormasterBuild $build) { 90 + 91 + try { 92 + $this->assertCanSendMessage($viewer, $build); 93 + return true; 94 + } catch (HarbormasterRestartException $ex) { 95 + return false; 96 + } 97 + } 35 98 36 - public function validateTransactions($object, array $xactions) { 37 - $errors = array(); 99 + final public function assertCanApplyMessage( 100 + PhabricatorUser $viewer, 101 + HarbormasterBuild $build) { 102 + $this->newCanApplyMessageAssertion($viewer, $build); 103 + } 38 104 39 - // TODO: Restore logic that tests if the command can issue without causing 40 - // anything to lapse into an invalid state. This should not be the same 41 - // as the logic which powers the web UI: for example, if an "abort" is 42 - // queued we want to disable "Abort" in the web UI, but should obviously 43 - // process it here. 105 + final public function assertCanSendMessage( 106 + PhabricatorUser $viewer, 107 + HarbormasterBuild $build) { 108 + $plan = $build->getBuildPlan(); 109 + 110 + // See T13526. Users without permission to access the build plan can 111 + // currently end up here with no "BuildPlan" object. 112 + if (!$plan) { 113 + throw new HarbormasterRestartException( 114 + pht('No Build Plan Permission'), 115 + pht( 116 + 'You can not issue this command because you do not have '. 117 + 'permission to access the build plan for this build.')); 118 + } 119 + 120 + // Issuing these commands requires that you be able to edit the build, to 121 + // prevent enemy engineers from sabotaging your builds. See T9614. 122 + if (!$plan->canRunWithoutEditCapability()) { 123 + try { 124 + PhabricatorPolicyFilter::requireCapability( 125 + $viewer, 126 + $plan, 127 + PhabricatorPolicyCapability::CAN_EDIT); 128 + } catch (PhabricatorPolicyException $ex) { 129 + throw new HarbormasterRestartException( 130 + pht('Insufficent Build Plan Permission'), 131 + pht( 132 + 'The build plan for this build is configured to prevent '. 133 + 'users who can not edit it from issuing commands to the '. 134 + 'build, and you do not have permission to edit the build '. 135 + 'plan.')); 136 + } 137 + } 44 138 45 - return $errors; 139 + $this->newCanSendMessageAssertion($viewer, $build); 140 + $this->assertCanApplyMessage($viewer, $build); 46 141 } 142 + 143 + abstract protected function newCanSendMessageAssertion( 144 + PhabricatorUser $viewer, 145 + HarbormasterBuild $build); 146 + 147 + abstract protected function newCanApplyMessageAssertion( 148 + PhabricatorUser $viewer, 149 + HarbormasterBuild $build); 47 150 48 151 }