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

Make various minor Harbormaster UI improvements

Summary: Ref T1049. Tweaks some of the UI and code to improve / clean it up a bit.

Test Plan: Ran build plans, browsed UI.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1049

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

+268 -195
+141 -84
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 20 20 ->withIDs(array($id)) 21 21 ->needBuildableHandles(true) 22 22 ->needContainerHandles(true) 23 - ->needBuilds(true) 24 23 ->executeOne(); 25 24 if (!$buildable) { 26 25 return new Aphront404Response(); 27 26 } 28 27 29 - $build_list = id(new PHUIObjectItemListView()) 30 - ->setUser($viewer); 31 - foreach ($buildable->getBuilds() as $build) { 32 - $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); 33 - $item = id(new PHUIObjectItemView()) 34 - ->setObjectName(pht('Build %d', $build->getID())) 35 - ->setHeader($build->getName()) 36 - ->setHref($view_uri); 37 - 38 - switch ($build->getBuildStatus()) { 39 - case HarbormasterBuild::STATUS_INACTIVE: 40 - $item->setBarColor('grey'); 41 - $item->addAttribute(pht('Inactive')); 42 - break; 43 - case HarbormasterBuild::STATUS_PENDING: 44 - $item->setBarColor('blue'); 45 - $item->addAttribute(pht('Pending')); 46 - break; 47 - case HarbormasterBuild::STATUS_WAITING: 48 - $item->setBarColor('violet'); 49 - $item->addAttribute(pht('Waiting')); 50 - break; 51 - case HarbormasterBuild::STATUS_BUILDING: 52 - $item->setBarColor('yellow'); 53 - $item->addAttribute(pht('Building')); 54 - break; 55 - case HarbormasterBuild::STATUS_PASSED: 56 - $item->setBarColor('green'); 57 - $item->addAttribute(pht('Passed')); 58 - break; 59 - case HarbormasterBuild::STATUS_FAILED: 60 - $item->setBarColor('red'); 61 - $item->addAttribute(pht('Failed')); 62 - break; 63 - case HarbormasterBuild::STATUS_ERROR: 64 - $item->setBarColor('red'); 65 - $item->addAttribute(pht('Unexpected Error')); 66 - break; 67 - case HarbormasterBuild::STATUS_STOPPED: 68 - $item->setBarColor('black'); 69 - $item->addAttribute(pht('Stopped')); 70 - break; 71 - } 72 - 73 - if ($build->isRestarting()) { 74 - $item->addIcon('backward', pht('Restarting')); 75 - } else if ($build->isStopping()) { 76 - $item->addIcon('stop', pht('Stopping')); 77 - } else if ($build->isResuming()) { 78 - $item->addIcon('play', pht('Resuming')); 79 - } 80 - 81 - $build_id = $build->getID(); 82 - 83 - $restart_uri = "build/restart/{$build_id}/buildable/"; 84 - $resume_uri = "build/resume/{$build_id}/buildable/"; 85 - $stop_uri = "build/stop/{$build_id}/buildable/"; 86 - 87 - $item->addAction( 88 - id(new PHUIListItemView()) 89 - ->setIcon('backward') 90 - ->setName(pht('Restart')) 91 - ->setHref($this->getApplicationURI($restart_uri)) 92 - ->setWorkflow(true) 93 - ->setDisabled(!$build->canRestartBuild())); 28 + // Pull builds and build targets. 29 + $builds = id(new HarbormasterBuildQuery()) 30 + ->setViewer($viewer) 31 + ->withBuildablePHIDs(array($buildable->getPHID())) 32 + ->needBuildTargets(true) 33 + ->execute(); 94 34 95 - if ($build->canResumeBuild()) { 96 - $item->addAction( 97 - id(new PHUIListItemView()) 98 - ->setIcon('play') 99 - ->setName(pht('Resume')) 100 - ->setHref($this->getApplicationURI($resume_uri)) 101 - ->setWorkflow(true)); 102 - } else { 103 - $item->addAction( 104 - id(new PHUIListItemView()) 105 - ->setIcon('stop') 106 - ->setName(pht('Stop')) 107 - ->setHref($this->getApplicationURI($stop_uri)) 108 - ->setWorkflow(true) 109 - ->setDisabled(!$build->canStopBuild())); 110 - } 35 + $buildable->attachBuilds($builds); 111 36 112 - $build_list->addItem($item); 113 - } 37 + $build_list = $this->buildBuildList($buildable); 114 38 115 39 $title = pht("Buildable %d", $id); 116 40 ··· 231 155 ? pht('Manual Buildable') 232 156 : pht('Automatic Buildable')); 233 157 158 + } 159 + 160 + private function buildBuildList(HarbormasterBuildable $buildable) { 161 + $viewer = $this->getRequest()->getUser(); 162 + 163 + $build_list = id(new PHUIObjectItemListView()) 164 + ->setUser($viewer); 165 + foreach ($buildable->getBuilds() as $build) { 166 + $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); 167 + $item = id(new PHUIObjectItemView()) 168 + ->setObjectName(pht('Build %d', $build->getID())) 169 + ->setHeader($build->getName()) 170 + ->setHref($view_uri); 171 + 172 + switch ($build->getBuildStatus()) { 173 + case HarbormasterBuild::STATUS_INACTIVE: 174 + $item->setBarColor('grey'); 175 + $item->addAttribute(pht('Inactive')); 176 + break; 177 + case HarbormasterBuild::STATUS_PENDING: 178 + $item->setBarColor('blue'); 179 + $item->addAttribute(pht('Pending')); 180 + break; 181 + case HarbormasterBuild::STATUS_WAITING: 182 + $item->setBarColor('violet'); 183 + $item->addAttribute(pht('Waiting')); 184 + break; 185 + case HarbormasterBuild::STATUS_BUILDING: 186 + $item->setBarColor('yellow'); 187 + $item->addAttribute(pht('Building')); 188 + break; 189 + case HarbormasterBuild::STATUS_PASSED: 190 + $item->setBarColor('green'); 191 + $item->addAttribute(pht('Passed')); 192 + break; 193 + case HarbormasterBuild::STATUS_FAILED: 194 + $item->setBarColor('red'); 195 + $item->addAttribute(pht('Failed')); 196 + break; 197 + case HarbormasterBuild::STATUS_ERROR: 198 + $item->setBarColor('red'); 199 + $item->addAttribute(pht('Unexpected Error')); 200 + break; 201 + case HarbormasterBuild::STATUS_STOPPED: 202 + $item->setBarColor('black'); 203 + $item->addAttribute(pht('Stopped')); 204 + break; 205 + } 206 + 207 + if ($build->isRestarting()) { 208 + $item->addIcon('backward', pht('Restarting')); 209 + } else if ($build->isStopping()) { 210 + $item->addIcon('stop', pht('Stopping')); 211 + } else if ($build->isResuming()) { 212 + $item->addIcon('play', pht('Resuming')); 213 + } 214 + 215 + $build_id = $build->getID(); 216 + 217 + $restart_uri = "build/restart/{$build_id}/buildable/"; 218 + $resume_uri = "build/resume/{$build_id}/buildable/"; 219 + $stop_uri = "build/stop/{$build_id}/buildable/"; 220 + 221 + $item->addAction( 222 + id(new PHUIListItemView()) 223 + ->setIcon('backward') 224 + ->setName(pht('Restart')) 225 + ->setHref($this->getApplicationURI($restart_uri)) 226 + ->setWorkflow(true) 227 + ->setDisabled(!$build->canRestartBuild())); 228 + 229 + if ($build->canResumeBuild()) { 230 + $item->addAction( 231 + id(new PHUIListItemView()) 232 + ->setIcon('play') 233 + ->setName(pht('Resume')) 234 + ->setHref($this->getApplicationURI($resume_uri)) 235 + ->setWorkflow(true)); 236 + } else { 237 + $item->addAction( 238 + id(new PHUIListItemView()) 239 + ->setIcon('stop') 240 + ->setName(pht('Stop')) 241 + ->setHref($this->getApplicationURI($stop_uri)) 242 + ->setWorkflow(true) 243 + ->setDisabled(!$build->canStopBuild())); 244 + } 245 + 246 + $targets = $build->getBuildTargets(); 247 + 248 + if ($targets) { 249 + $target_list = id(new PHUIStatusListView()); 250 + foreach ($targets as $target) { 251 + switch ($target->getTargetStatus()) { 252 + case HarbormasterBuildTarget::STATUS_PENDING: 253 + $icon = 'time-green'; 254 + break; 255 + case HarbormasterBuildTarget::STATUS_PASSED: 256 + $icon = 'accept-green'; 257 + break; 258 + case HarbormasterBuildTarget::STATUS_FAILED: 259 + $icon = 'reject-red'; 260 + break; 261 + default: 262 + $icon = 'question'; 263 + break; 264 + } 265 + 266 + try { 267 + $impl = $target->getImplementation(); 268 + $name = $impl->getName(); 269 + } catch (Exception $ex) { 270 + $name = $target->getClassName(); 271 + } 272 + 273 + $target_list->addItem( 274 + id(new PHUIStatusItemView()) 275 + ->setIcon($icon) 276 + ->setTarget(pht('Target %d', $target->getID())) 277 + ->setNote($name)); 278 + } 279 + 280 + $target_box = id(new PHUIBoxView()) 281 + ->addPadding(PHUI::PADDING_SMALL) 282 + ->appendChild($target_list); 283 + 284 + $item->appendChild($target_box); 285 + } 286 + 287 + $build_list->addItem($item); 288 + } 289 + 290 + return $build_list; 234 291 } 235 292 236 293 }
+18 -29
src/applications/harbormaster/controller/HarbormasterStepAddController.php
··· 22 22 ->setViewer($viewer) 23 23 ->withIDs(array($id)) 24 24 ->executeOne(); 25 - if ($plan === null) { 26 - throw new Exception("Build plan not found!"); 25 + if (!$plan) { 26 + return new Aphront404Response(); 27 27 } 28 28 29 - $implementations = 30 - HarbormasterBuildStepImplementation::getImplementations(); 31 - 32 29 $cancel_uri = $this->getApplicationURI('plan/'.$plan->getID().'/'); 33 30 34 31 if ($request->isDialogFormPost()) { 35 32 $class = $request->getStr('step-type'); 36 - if (!in_array($class, $implementations)) { 37 - return $this->createDialog($implementations, $cancel_uri); 33 + if (!HarbormasterBuildStepImplementation::getImplementation($class)) { 34 + return $this->createDialog($cancel_uri); 38 35 } 39 36 40 37 $steps = $plan->loadOrderedBuildSteps(); ··· 51 48 return id(new AphrontRedirectResponse())->setURI($edit_uri); 52 49 } 53 50 54 - return $this->createDialog($implementations, $cancel_uri); 51 + return $this->createDialog($cancel_uri); 55 52 } 56 53 57 - function createDialog(array $implementations, $cancel_uri) { 54 + private function createDialog($cancel_uri) { 58 55 $request = $this->getRequest(); 59 56 $viewer = $request->getUser(); 60 57 61 58 $control = id(new AphrontFormRadioButtonControl()) 62 59 ->setName('step-type'); 63 60 64 - foreach ($implementations as $implementation_name) { 65 - $implementation = new $implementation_name(); 66 - $control 67 - ->addButton( 68 - $implementation_name, 69 - $implementation->getName(), 70 - $implementation->getGenericDescription()); 61 + $all = HarbormasterBuildStepImplementation::getImplementations(); 62 + foreach ($all as $class => $implementation) { 63 + $control->addButton( 64 + $class, 65 + $implementation->getName(), 66 + $implementation->getGenericDescription()); 71 67 } 72 68 73 - $dialog = new AphrontDialogView(); 74 - $dialog->setTitle(pht('Add New Step')) 75 - ->setUser($viewer) 76 - ->addSubmitButton(pht('Add Build Step')) 77 - ->addCancelButton($cancel_uri); 78 - $dialog->appendChild( 79 - phutil_tag( 80 - 'p', 81 - array(), 82 - pht( 83 - 'Select what type of build step you want to add: '))); 84 - $dialog->appendChild($control); 85 - return id(new AphrontDialogResponse())->setDialog($dialog); 69 + return $this->newDialog() 70 + ->setTitle(pht('Add New Step')) 71 + ->addSubmitButton(pht('Add Build Step')) 72 + ->addCancelButton($cancel_uri) 73 + ->appendParagraph(pht('Choose a type of build step to add:')) 74 + ->appendChild($control); 86 75 } 87 76 88 77 }
+24
src/applications/harbormaster/query/HarbormasterBuildQuery.php
··· 8 8 private $buildStatuses; 9 9 private $buildablePHIDs; 10 10 private $buildPlanPHIDs; 11 + private $needBuildTargets; 11 12 12 13 public function withIDs(array $ids) { 13 14 $this->ids = $ids; ··· 31 32 32 33 public function withBuildPlanPHIDs(array $build_plan_phids) { 33 34 $this->buildPlanPHIDs = $build_plan_phids; 35 + return $this; 36 + } 37 + 38 + public function needBuildTargets($need_targets) { 39 + $this->needBuildTargets = $need_targets; 34 40 return $this; 35 41 } 36 42 ··· 100 106 foreach ($page as $build) { 101 107 $unprocessed_commands = idx($commands, $build->getPHID(), array()); 102 108 $build->attachUnprocessedCommands($unprocessed_commands); 109 + } 110 + 111 + if ($this->needBuildTargets) { 112 + $targets = id(new HarbormasterBuildTargetQuery()) 113 + ->setViewer($this->getViewer()) 114 + ->setParentQuery($this) 115 + ->withBuildPHIDs($build_phids) 116 + ->execute(); 117 + 118 + // TODO: Some day, when targets have dependencies, we should toposort 119 + // these. For now, just put them into chronological order. 120 + $targets = array_reverse($targets); 121 + 122 + $targets = mgroup($targets, 'getBuildPHID'); 123 + foreach ($page as $build) { 124 + $build_targets = idx($targets, $build->getPHID(), array()); 125 + $build->attachBuildTargets($build_targets); 126 + } 103 127 } 104 128 105 129 return $page;
+37 -4
src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
··· 3 3 abstract class HarbormasterBuildStepImplementation { 4 4 5 5 public static function getImplementations() { 6 - $symbols = id(new PhutilSymbolLoader()) 6 + return id(new PhutilSymbolLoader()) 7 7 ->setAncestorClass('HarbormasterBuildStepImplementation') 8 - ->setConcreteOnly(true) 9 - ->selectAndLoadSymbols(); 10 - return ipull($symbols, 'name'); 8 + ->loadObjects(); 9 + } 10 + 11 + public static function getImplementation($class) { 12 + $base = idx(self::getImplementations(), $class); 13 + 14 + if ($base) { 15 + return (clone $base); 16 + } 17 + 18 + return null; 19 + } 20 + 21 + public static function requireImplementation($class) { 22 + if (!$class) { 23 + throw new Exception(pht('No implementation is specified!')); 24 + } 25 + 26 + $implementation = self::getImplementation($class); 27 + if (!$implementation) { 28 + throw new Exception(pht('No such implementation "%s" exists!', $class)); 29 + } 30 + 31 + return $implementation; 11 32 } 12 33 13 34 /** ··· 161 182 162 183 public function getFieldSpecifications() { 163 184 return array(); 185 + } 186 + 187 + protected function formatSettingForDescription($key, $default = null) { 188 + return $this->formatValueForDescription($this->getSetting($key, $default)); 189 + } 190 + 191 + protected function formatValueForDescription($value) { 192 + if (strlen($value)) { 193 + return phutil_tag('strong', array(), $value); 194 + } else { 195 + return phutil_tag('em', array(), pht('(null)')); 196 + } 164 197 } 165 198 166 199 }
+3 -5
src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php
··· 12 12 } 13 13 14 14 public function getDescription() { 15 - $settings = $this->getSettings(); 16 - 17 15 return pht( 18 - 'Run \'%s\' on \'%s\'.', 19 - $settings['command'], 20 - $settings['hostartifact']); 16 + 'Run command %s on host %s.', 17 + $this->formatSettingForDescription('command'), 18 + $this->formatSettingForDescription('hostartifact')); 21 19 } 22 20 23 21 public function execute(
+9 -4
src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php
··· 12 12 } 13 13 14 14 public function getDescription() { 15 - $settings = $this->getSettings(); 15 + $domain = null; 16 + $uri = $this->getSetting('uri'); 17 + if ($uri) { 18 + $domain = id(new PhutilURI($uri))->getDomain(); 19 + } 16 20 17 - $uri = new PhutilURI($settings['uri']); 18 - $domain = $uri->getDomain(); 19 - return pht('Make an HTTP %s request to %s', $settings['method'], $domain); 21 + return pht( 22 + 'Make an HTTP %s request to %s.', 23 + $this->formatSettingForDescription('method', 'POST'), 24 + $this->formatValueForDescription($domain)); 20 25 } 21 26 22 27 public function execute(
-10
src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php
··· 11 11 return pht('Obtain a lease on a Drydock host for performing builds.'); 12 12 } 13 13 14 - public function getDescription() { 15 - $settings = $this->getSettings(); 16 - 17 - return pht( 18 - 'Obtain a lease on a Drydock host whose platform is \'%s\' and store '. 19 - 'the resulting lease in a host artifact called \'%s\'.', 20 - $settings['platform'], 21 - $settings['name']); 22 - } 23 - 24 14 public function execute( 25 15 HarbormasterBuild $build, 26 16 HarbormasterBuildTarget $build_target) {
+3 -5
src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php
··· 12 12 } 13 13 14 14 public function getDescription() { 15 - $settings = $this->getSettings(); 16 - 17 15 return pht( 18 - 'Publish file artifact \'%s\' to the fragment path \'%s\'.', 19 - $settings['artifact'], 20 - $settings['path']); 16 + 'Publish file artifact %s as fragment %s.', 17 + $this->formatSettingForDescription('artifact'), 18 + $this->formatSettingForDescription('path')); 21 19 } 22 20 23 21 public function execute(
+3 -3
src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php
··· 12 12 } 13 13 14 14 public function getDescription() { 15 - $settings = $this->getSettings(); 16 - 17 - return pht('Sleep for %s seconds.', $settings['seconds']); 15 + return pht( 16 + 'Sleep for %s seconds.', 17 + $this->formatSettingForDescription('seconds')); 18 18 } 19 19 20 20 public function execute(
-4
src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php
··· 11 11 return pht('Throw an exception.'); 12 12 } 13 13 14 - public function getDescription() { 15 - return pht('Throw an exception.'); 16 - } 17 - 18 14 public function execute( 19 15 HarbormasterBuild $build, 20 16 HarbormasterBuildTarget $build_target) {
+5 -7
src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php
··· 4 4 extends HarbormasterBuildStepImplementation { 5 5 6 6 public function getName() { 7 - return pht('Upload Artifact'); 7 + return pht('Upload File'); 8 8 } 9 9 10 10 public function getGenericDescription() { 11 - return pht('Upload an artifact from a Drydock host to Phabricator.'); 11 + return pht('Upload a file from a host to Phabricator.'); 12 12 } 13 13 14 14 public function getDescription() { 15 - $settings = $this->getSettings(); 16 - 17 15 return pht( 18 - 'Upload artifact located at \'%s\' on \'%s\'.', 19 - $settings['path'], 20 - $settings['hostartifact']); 16 + 'Upload %s from %s.', 17 + $this->formatSettingForDescription('path'), 18 + $this->formatSettingForDescription('hostartifact')); 21 19 } 22 20 23 21 public function execute(
-6
src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php
··· 13 13 'before continuing.'); 14 14 } 15 15 16 - public function getDescription() { 17 - return pht( 18 - 'Wait for previous commits to finish building the current plan '. 19 - 'before continuing.'); 20 - } 21 - 22 16 public function execute( 23 17 HarbormasterBuild $build, 24 18 HarbormasterBuildTarget $build_target) {
+10
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 9 9 10 10 private $buildable = self::ATTACHABLE; 11 11 private $buildPlan = self::ATTACHABLE; 12 + private $buildTargets = self::ATTACHABLE; 12 13 private $unprocessedCommands = self::ATTACHABLE; 13 14 14 15 /** ··· 100 101 101 102 public function getBuildPlan() { 102 103 return $this->assertAttached($this->buildPlan); 104 + } 105 + 106 + public function getBuildTargets() { 107 + return $this->assertAttached($this->buildTargets); 108 + } 109 + 110 + public function attachBuildTargets(array $targets) { 111 + $this->buildTargets = $targets; 112 + return $this; 103 113 } 104 114 105 115 public function isBuilding() {
+8 -18
src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
··· 16 16 17 17 private $build = self::ATTACHABLE; 18 18 private $buildStep = self::ATTACHABLE; 19 + private $implementation; 19 20 20 21 public static function initializeNewBuildTarget( 21 22 HarbormasterBuild $build, ··· 82 83 } 83 84 84 85 public function getImplementation() { 85 - if ($this->className === null) { 86 - throw new Exception("No implementation set for the given target."); 87 - } 88 - 89 - static $implementations = null; 90 - if ($implementations === null) { 91 - $implementations = 92 - HarbormasterBuildStepImplementation::getImplementations(); 86 + if ($this->implementation === null) { 87 + $obj = HarbormasterBuildStepImplementation::requireImplementation( 88 + $this->className); 89 + $obj->loadSettings($this); 90 + $this->implementation = $obj; 93 91 } 94 92 95 - $class = $this->className; 96 - if (!in_array($class, $implementations)) { 97 - throw new Exception( 98 - "Class name '".$class."' does not extend BuildStepImplementation."); 99 - } 100 - $implementation = newv($class, array()); 101 - $implementation->loadSettings($this); 102 - return $implementation; 93 + return $this->implementation; 103 94 } 104 95 105 96 ··· 147 138 } 148 139 149 140 public function describeAutomaticCapability($capability) { 150 - return pht( 151 - 'Users must be able to see a build to view its build targets.'); 141 + return pht('Users must be able to see a build to view its build targets.'); 152 142 } 153 143 154 144 }
+7 -16
src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
··· 12 12 13 13 private $buildPlan = self::ATTACHABLE; 14 14 private $customFields = self::ATTACHABLE; 15 + private $implementation; 15 16 16 17 public function getConfiguration() { 17 18 return array( ··· 46 47 } 47 48 48 49 public function getStepImplementation() { 49 - if ($this->className === null) { 50 - throw new Exception("No implementation set for the given step."); 51 - } 52 - 53 - static $implementations = null; 54 - if ($implementations === null) { 55 - $implementations = 56 - HarbormasterBuildStepImplementation::getImplementations(); 50 + if ($this->implementation === null) { 51 + $obj = HarbormasterBuildStepImplementation::requireImplementation( 52 + $this->className); 53 + $obj->loadSettings($this); 54 + $this->implementation = $obj; 57 55 } 58 56 59 - $class = $this->className; 60 - if (!in_array($class, $implementations)) { 61 - throw new Exception( 62 - "Class name '".$class."' does not extend BuildStepImplementation."); 63 - } 64 - $implementation = newv($class, array()); 65 - $implementation->loadSettings($this); 66 - return $implementation; 57 + return $this->implementation; 67 58 } 68 59 69 60