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

Implement build simulation; convert Harbormaster to be purely dependency based

Summary:
Depends on D9806. This implements the build simulator, which is used to calculate the order of build steps in the plan editor. This includes a migration script to convert existing plans from sequential based to dependency based, and then drops the sequence column.

Because build plans are now dependency based, the grippable and re-order behaviour has been removed.

Test Plan: Tested the migration, saw the dependencies appear correctly.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: epriestley, Korvin

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

+378 -213
+54
resources/sql/autopatches/20140706.harbormasterdepend.1.php
··· 1 + <?php 2 + 3 + $plan_table = new HarbormasterBuildPlan(); 4 + $step_table = new HarbormasterBuildStep(); 5 + $conn_w = $plan_table->establishConnection('w'); 6 + foreach (new LiskMigrationIterator($plan_table) as $plan) { 7 + 8 + echo pht( 9 + "Migrating build plan %d: %s...\n", 10 + $plan->getID(), 11 + $plan->getName()); 12 + 13 + // Load all build steps in order using the step sequence. 14 + $steps = queryfx_all( 15 + $conn_w, 16 + 'SELECT id FROM %T WHERE buildPlanPHID = %s ORDER BY sequence ASC;', 17 + $step_table->getTableName(), 18 + $plan->getPHID()); 19 + 20 + $previous_step = null; 21 + foreach ($steps as $step) { 22 + $id = $step['id']; 23 + 24 + $loaded_step = id(new HarbormasterBuildStep())->load($id); 25 + 26 + $depends_on = $loaded_step->getDetail('dependsOn'); 27 + if ($depends_on !== null) { 28 + // This plan already contains steps with depends_on set, so 29 + // we skip since there's nothing to migrate. 30 + break; 31 + } 32 + 33 + if ($previous_step === null) { 34 + $depends_on = array(); 35 + } else { 36 + $depends_on = array($previous_step->getPHID()); 37 + } 38 + 39 + $loaded_step->setDetail('dependsOn', $depends_on); 40 + queryfx( 41 + $conn_w, 42 + 'UPDATE %T SET details = %s WHERE id = %d', 43 + $step_table->getTableName(), 44 + json_encode($loaded_step->getDetails()), 45 + $loaded_step->getID()); 46 + 47 + $previous_step = $loaded_step; 48 + 49 + echo pht( 50 + " Migrated build step %d.\n", 51 + $loaded_step->getID()); 52 + } 53 + 54 + }
+4 -2
src/__phutil_library_map__.php
··· 652 652 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', 653 653 'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php', 654 654 'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php', 655 + 'HarbormasterBuildDependencyDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php', 655 656 'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php', 657 + 'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php', 656 658 'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php', 657 659 'HarbormasterBuildItemPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildItemPHIDType.php', 658 660 'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php', ··· 715 717 'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php', 716 718 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', 717 719 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 718 - 'HarbormasterPlanOrderController' => 'applications/harbormaster/controller/HarbormasterPlanOrderController.php', 719 720 'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php', 720 721 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', 721 722 'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php', ··· 3391 3392 ), 3392 3393 'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3393 3394 'HarbormasterBuildCommand' => 'HarbormasterDAO', 3395 + 'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource', 3394 3396 'HarbormasterBuildEngine' => 'Phobject', 3397 + 'HarbormasterBuildGraph' => 'AbstractDirectedGraph', 3395 3398 'HarbormasterBuildItem' => 'HarbormasterDAO', 3396 3399 'HarbormasterBuildItemPHIDType' => 'PhabricatorPHIDType', 3397 3400 'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', ··· 3476 3479 'HarbormasterPlanDisableController' => 'HarbormasterPlanController', 3477 3480 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 3478 3481 'HarbormasterPlanListController' => 'HarbormasterPlanController', 3479 - 'HarbormasterPlanOrderController' => 'HarbormasterController', 3480 3482 'HarbormasterPlanRunController' => 'HarbormasterController', 3481 3483 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 3482 3484 'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+1 -23
src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
··· 179 179 ->setHref($view_uri); 180 180 181 181 $status = $build->getBuildStatus(); 182 - switch ($status) { 183 - case HarbormasterBuild::STATUS_INACTIVE: 184 - $item->setBarColor('grey'); 185 - break; 186 - case HarbormasterBuild::STATUS_PENDING: 187 - $item->setBarColor('blue'); 188 - break; 189 - case HarbormasterBuild::STATUS_BUILDING: 190 - $item->setBarColor('yellow'); 191 - break; 192 - case HarbormasterBuild::STATUS_PASSED: 193 - $item->setBarColor('green'); 194 - break; 195 - case HarbormasterBuild::STATUS_FAILED: 196 - $item->setBarColor('red'); 197 - break; 198 - case HarbormasterBuild::STATUS_ERROR: 199 - $item->setBarColor('red'); 200 - break; 201 - case HarbormasterBuild::STATUS_STOPPED: 202 - $item->setBarColor('black'); 203 - break; 204 - } 182 + $item->setBarColor(HarbormasterBuild::getBuildStatusColor($status)); 205 183 206 184 $item->addAttribute(HarbormasterBuild::getBuildStatusName($status)); 207 185
-53
src/applications/harbormaster/controller/HarbormasterPlanOrderController.php
··· 1 - <?php 2 - 3 - final class HarbormasterPlanOrderController extends HarbormasterController { 4 - 5 - private $id; 6 - 7 - public function willProcessRequest(array $data) { 8 - $this->id = idx($data, 'id'); 9 - } 10 - 11 - public function processRequest() { 12 - $request = $this->getRequest(); 13 - $user = $request->getUser(); 14 - 15 - $request->validateCSRF(); 16 - 17 - $this->requireApplicationCapability( 18 - HarbormasterManagePlansCapability::CAPABILITY); 19 - 20 - $plan = id(new HarbormasterBuildPlanQuery()) 21 - ->setViewer($user) 22 - ->withIDs(array($this->id)) 23 - ->executeOne(); 24 - if (!$plan) { 25 - return new Aphront404Response(); 26 - } 27 - 28 - // Load all steps. 29 - $order = $request->getStrList('order'); 30 - $steps = id(new HarbormasterBuildStepQuery()) 31 - ->setViewer($user) 32 - ->withIDs($order) 33 - ->execute(); 34 - $steps = array_select_keys($steps, $order); 35 - $reordered_steps = array(); 36 - 37 - // Apply sequences. 38 - $sequence = 1; 39 - foreach ($steps as $step) { 40 - $step->setSequence($sequence++); 41 - $step->save(); 42 - 43 - $reordered_steps[] = $step; 44 - } 45 - 46 - // NOTE: Reordering steps may invalidate artifacts. This is fine; the UI 47 - // will show that there are ordering issues. 48 - 49 - // Force the page to re-render. 50 - return id(new AphrontRedirectResponse()); 51 - } 52 - 53 - }
+116 -25
src/applications/harbormaster/controller/HarbormasterPlanViewController.php
··· 49 49 $crumbs = $this->buildApplicationCrumbs(); 50 50 $crumbs->addTextCrumb(pht('Plan %d', $id)); 51 51 52 - list($step_list, $has_any_conflicts) = $this->buildStepList($plan); 52 + list($step_list, $has_any_conflicts, $would_deadlock) = 53 + $this->buildStepList($plan); 53 54 54 - if ($has_any_conflicts) { 55 + if ($would_deadlock) { 56 + $box->setFormErrors( 57 + array( 58 + pht( 59 + 'This build plan will deadlock when executed, due to '. 60 + 'circular dependencies present in the build plan. '. 61 + 'Examine the step list and resolve the deadlock.'), 62 + )); 63 + } else if ($has_any_conflicts) { 64 + // A deadlocking build will also cause all the artifacts to be 65 + // invalid, so we just skip showing this message if that's the 66 + // case. 55 67 $box->setFormErrors( 56 68 array( 57 69 pht( ··· 76 88 $request = $this->getRequest(); 77 89 $viewer = $request->getUser(); 78 90 79 - $list_id = celerity_generate_unique_node_id(); 91 + $run_order = 92 + HarbormasterBuildGraph::determineDependencyExecution($plan); 80 93 81 94 $steps = id(new HarbormasterBuildStepQuery()) 82 95 ->setViewer($viewer) 83 96 ->withBuildPlanPHIDs(array($plan->getPHID())) 84 97 ->execute(); 98 + $steps = mpull($steps, null, 'getPHID'); 85 99 86 100 $can_edit = $this->hasApplicationCapability( 87 101 HarbormasterManagePlansCapability::CAPABILITY); 88 102 89 - $i = 1; 90 103 $step_list = id(new PHUIObjectItemListView()) 91 104 ->setUser($viewer) 92 105 ->setNoDataString( 93 - pht('This build plan does not have any build steps yet.')) 94 - ->setID($list_id); 95 - Javelin::initBehavior( 96 - 'harbormaster-reorder-steps', 97 - array( 98 - 'listID' => $list_id, 99 - 'orderURI' => '/harbormaster/plan/order/'.$plan->getID().'/', 100 - )); 106 + pht('This build plan does not have any build steps yet.')); 101 107 108 + $i = 1; 109 + $last_depth = 0; 102 110 $has_any_conflicts = false; 103 - foreach ($steps as $step) { 111 + $is_deadlocking = false; 112 + foreach ($run_order as $run_ref) { 113 + $step = $steps[$run_ref['node']->getPHID()]; 114 + $depth = $run_ref['depth'] + 1; 115 + if ($last_depth !== $depth) { 116 + $last_depth = $depth; 117 + $i = 1; 118 + } else { 119 + $i++; 120 + } 121 + 104 122 $implementation = null; 105 123 try { 106 124 $implementation = $step->getStepImplementation(); ··· 108 126 // We can't initialize the implementation. This might be because 109 127 // it's been renamed or no longer exists. 110 128 $item = id(new PHUIObjectItemView()) 111 - ->setObjectName(pht('Step %d', $i++)) 129 + ->setObjectName(pht('Step %d.%d', $depth, $i)) 112 130 ->setHeader(pht('Unknown Implementation')) 113 131 ->setBarColor('red') 114 132 ->addAttribute(pht( ··· 127 145 continue; 128 146 } 129 147 $item = id(new PHUIObjectItemView()) 130 - ->setObjectName('Step '.$i++) 148 + ->setObjectName(pht('Step %d.%d', $depth, $i)) 131 149 ->setHeader($step->getName()); 132 150 133 151 $item->addAttribute($implementation->getDescription()); ··· 138 156 139 157 if ($can_edit) { 140 158 $item->setHref($edit_uri); 141 - $item->setGrippable(true); 142 - $item->addSigil('build-step'); 143 - $item->setMetadata( 144 - array( 145 - 'stepID' => $step->getID(), 146 - )); 147 159 } 148 160 149 161 $item ··· 157 169 ->setHref( 158 170 $this->getApplicationURI('step/delete/'.$step->getID().'/'))); 159 171 172 + $depends = $step->getStepImplementation()->getDependencies($step); 160 173 $inputs = $step->getStepImplementation()->getArtifactInputs(); 161 174 $outputs = $step->getStepImplementation()->getArtifactOutputs(); 162 175 163 176 $has_conflicts = false; 164 - if ($inputs || $outputs) { 177 + if ($depends || $inputs || $outputs) { 165 178 $available_artifacts = 166 - HarbormasterBuildStepImplementation::loadAvailableArtifacts( 179 + HarbormasterBuildStepImplementation::getAvailableArtifacts( 167 180 $plan, 168 181 $step, 169 182 null); 170 183 184 + list($depends_ui, $has_conflicts) = $this->buildDependsOnList( 185 + $depends, 186 + pht('Depends On'), 187 + $steps); 188 + 171 189 list($inputs_ui, $has_conflicts) = $this->buildArtifactList( 172 190 $inputs, 173 191 'in', ··· 187 205 'class' => 'harbormaster-artifact-io', 188 206 ), 189 207 array( 208 + $depends_ui, 190 209 $inputs_ui, 191 210 $outputs_ui, 192 211 ))); ··· 197 216 $item->setBarColor('red'); 198 217 } 199 218 219 + if ($run_ref['cycle']) { 220 + $is_deadlocking = true; 221 + } 222 + 223 + if ($is_deadlocking) { 224 + $item->setBarColor('red'); 225 + } 226 + 200 227 $step_list->addItem($item); 201 228 } 202 229 203 - return array($step_list, $has_any_conflicts); 230 + return array($step_list, $has_any_conflicts, $is_deadlocking); 204 231 } 205 232 206 233 private function buildActionList(HarbormasterBuildPlan $plan) { ··· 290 317 if (!$artifacts) { 291 318 return array(null, $has_conflicts); 292 319 } 293 - 294 320 295 321 $this->requireResource('harbormaster-css'); 296 322 ··· 384 410 return array($ui, $has_conflicts); 385 411 } 386 412 413 + private function buildDependsOnList( 414 + array $step_phids, 415 + $name, 416 + array $steps) { 417 + $has_conflicts = false; 418 + 419 + if (count($step_phids) === 0) { 420 + return null; 421 + } 422 + 423 + $this->requireResource('harbormaster-css'); 424 + 425 + $steps = mpull($steps, null, 'getPHID'); 426 + 427 + $header = phutil_tag( 428 + 'div', 429 + array( 430 + 'class' => 'harbormaster-artifact-summary-header', 431 + ), 432 + $name); 433 + 434 + $list = new PHUIStatusListView(); 435 + foreach ($step_phids as $step_phid) { 436 + $error = null; 437 + 438 + if (idx($steps, $step_phid) === null) { 439 + $icon = PHUIStatusItemView::ICON_WARNING; 440 + $color = 'red'; 441 + $icon_label = pht('Missing Dependency'); 442 + $has_conflicts = true; 443 + $error = pht( 444 + 'This dependency specifies a build step which doesn\'t exist.'); 445 + } else { 446 + $bound = phutil_tag( 447 + 'strong', 448 + array(), 449 + idx($steps, $step_phid)->getName()); 450 + $icon = PHUIStatusItemView::ICON_ACCEPT; 451 + $color = 'green'; 452 + $icon_label = pht('Valid Input'); 453 + } 454 + 455 + if ($error) { 456 + $note = array( 457 + phutil_tag('strong', array(), pht('ERROR:')), 458 + ' ', 459 + $error); 460 + } else { 461 + $note = $bound; 462 + } 463 + 464 + $list->addItem( 465 + id(new PHUIStatusItemView()) 466 + ->setIcon($icon, $color, $icon_label) 467 + ->setTarget(pht('Build Step')) 468 + ->setNote($note)); 469 + } 470 + 471 + $ui = array( 472 + $header, 473 + $list, 474 + ); 475 + 476 + return array($ui, $has_conflicts); 477 + } 387 478 }
+28 -5
src/applications/harbormaster/controller/HarbormasterStepEditController.php
··· 65 65 66 66 $e_name = true; 67 67 $v_name = $step->getName(); 68 + $e_depends_on = true; 69 + $raw_depends_on = $step->getDetail('dependsOn', array()); 70 + 71 + $v_depends_on = id(new PhabricatorHandleQuery()) 72 + ->setViewer($viewer) 73 + ->withPHIDs($raw_depends_on) 74 + ->execute(); 68 75 69 76 $errors = array(); 70 77 $validation_exception = null; 71 78 if ($request->isFormPost()) { 72 79 $e_name = null; 73 80 $v_name = $request->getStr('name'); 81 + $e_depends_on = null; 82 + $v_depends_on = $request->getArr('dependsOn'); 74 83 75 84 $xactions = $field_list->buildFieldTransactionsFromRequest( 76 85 new HarbormasterBuildStepTransaction(), ··· 86 95 ->setNewValue($v_name); 87 96 array_unshift($xactions, $name_xaction); 88 97 98 + $depends_on_xaction = id(new HarbormasterBuildStepTransaction()) 99 + ->setTransactionType( 100 + HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON) 101 + ->setNewValue($v_depends_on); 102 + array_unshift($xactions, $depends_on_xaction); 103 + 89 104 if ($is_new) { 90 - // This is okay, but a little iffy. We should move it inside the editor 91 - // if we create plans elsewhere. 92 - $steps = $plan->loadOrderedBuildSteps(); 93 - $step->setSequence(count($steps) + 1); 94 - 95 105 // When creating a new step, make sure we have a create transaction 96 106 // so we'll apply the transactions even if the step has no 97 107 // configurable options. ··· 116 126 ->setLabel(pht('Name')) 117 127 ->setError($e_name) 118 128 ->setValue($v_name)); 129 + 130 + $form 131 + ->appendChild( 132 + id(new AphrontFormTokenizerControl()) 133 + ->setDatasource(id(new HarbormasterBuildDependencyDatasource()) 134 + ->setParameters(array( 135 + 'planPHID' => $plan->getPHID(), 136 + 'stepPHID' => $is_new ? null : $step->getPHID(), 137 + ))) 138 + ->setName('dependsOn') 139 + ->setLabel(pht('Depends On')) 140 + ->setError($e_depends_on) 141 + ->setValue($v_depends_on)); 119 142 120 143 $field_list->appendFieldsToForm($form); 121 144
+10
src/applications/harbormaster/editor/HarbormasterBuildStepEditor.php
··· 8 8 9 9 $types[] = HarbormasterBuildStepTransaction::TYPE_CREATE; 10 10 $types[] = HarbormasterBuildStepTransaction::TYPE_NAME; 11 + $types[] = HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON; 11 12 12 13 return $types; 13 14 } ··· 24 25 return null; 25 26 } 26 27 return $object->getName(); 28 + case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON: 29 + if ($this->getIsNewObject()) { 30 + return null; 31 + } 32 + return $object->getDetail('dependsOn', array()); 27 33 } 28 34 29 35 return parent::getCustomTransactionOldValue($object, $xaction); ··· 37 43 case HarbormasterBuildStepTransaction::TYPE_CREATE: 38 44 return true; 39 45 case HarbormasterBuildStepTransaction::TYPE_NAME: 46 + case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON: 40 47 return $xaction->getNewValue(); 41 48 } 42 49 ··· 52 59 return; 53 60 case HarbormasterBuildStepTransaction::TYPE_NAME: 54 61 return $object->setName($xaction->getNewValue()); 62 + case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON: 63 + return $object->setDetail('dependsOn', $xaction->getNewValue()); 55 64 } 56 65 57 66 return parent::applyCustomInternalTransaction($object, $xaction); ··· 64 73 switch ($xaction->getTransactionType()) { 65 74 case HarbormasterBuildStepTransaction::TYPE_CREATE: 66 75 case HarbormasterBuildStepTransaction::TYPE_NAME: 76 + case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON: 67 77 return; 68 78 } 69 79
+7 -17
src/applications/harbormaster/engine/HarbormasterBuildEngine.php
··· 246 246 // Identify all the steps which are ready to run (because all their 247 247 // dependencies are complete). 248 248 249 - $previous_step = null; 250 249 $runnable = array(); 251 250 foreach ($steps as $step) { 252 - // TODO: For now, we're hard coding sequential dependencies into build 253 - // steps. In the future, we can be smart about this instead. 254 - 255 - if ($previous_step) { 256 - $dependencies = array($previous_step); 257 - } else { 258 - $dependencies = array(); 259 - } 251 + $dependencies = $step->getStepImplementation()->getDependencies($step); 260 252 261 253 if (isset($queued[$step->getPHID()])) { 262 254 $can_run = true; 263 255 foreach ($dependencies as $dependency) { 264 - if (empty($complete[$dependency->getPHID()])) { 256 + if (empty($complete[$dependency])) { 265 257 $can_run = false; 266 258 break; 267 259 } ··· 271 263 $runnable[] = $step; 272 264 } 273 265 } 274 - 275 - $previous_step = $step; 276 266 } 277 267 278 268 if (!$runnable && !$waiting && !$underway) { 279 - // TODO: This means the build is deadlocked, probably? It should not 280 - // normally be possible yet, but we should communicate it more clearly. 281 - $build->setBuildStatus(HarbormasterBuild::STATUS_FAILED); 269 + // This means the build is deadlocked, and the user has configured 270 + // circular dependencies. 271 + $build->setBuildStatus(HarbormasterBuild::STATUS_DEADLOCKED); 282 272 $build->save(); 283 273 return; 284 274 } ··· 292 282 293 283 $this->queueNewBuildTarget($target); 294 284 } 295 - 296 285 } 297 286 298 287 ··· 378 367 $all_pass = false; 379 368 } 380 369 if ($build->getBuildStatus() == HarbormasterBuild::STATUS_FAILED || 381 - $build->getBuildStatus() == HarbormasterBuild::STATUS_ERROR) { 370 + $build->getBuildStatus() == HarbormasterBuild::STATUS_ERROR || 371 + $build->getBuildStatus() == HarbormasterBuild::STATUS_DEADLOCKED) { 382 372 $any_fail = true; 383 373 } 384 374 }
+60
src/applications/harbormaster/engine/HarbormasterBuildGraph.php
··· 1 + <?php 2 + 3 + /** 4 + * Directed graph representing a build plan 5 + */ 6 + final class HarbormasterBuildGraph extends AbstractDirectedGraph { 7 + 8 + private $stepMap; 9 + 10 + public static function determineDependencyExecution( 11 + HarbormasterBuildPlan $plan) { 12 + 13 + $steps = id(new HarbormasterBuildStepQuery()) 14 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 15 + ->withBuildPlanPHIDs(array($plan->getPHID())) 16 + ->execute(); 17 + 18 + $steps_by_phid = mpull($steps, null, 'getPHID'); 19 + $step_phids = mpull($steps, 'getPHID'); 20 + 21 + if (count($steps) === 0) { 22 + return array(); 23 + } 24 + 25 + $graph = id(new HarbormasterBuildGraph($steps_by_phid)) 26 + ->addNodes($step_phids); 27 + 28 + $raw_results = 29 + $graph->getBestEffortTopographicallySortedNodes(); 30 + 31 + $results = array(); 32 + foreach ($raw_results as $node) { 33 + $results[] = array( 34 + 'node' => $steps_by_phid[$node['node']], 35 + 'depth' => $node['depth'], 36 + 'cycle' => $node['cycle']); 37 + } 38 + 39 + return $results; 40 + } 41 + 42 + public function __construct($step_map) { 43 + $this->stepMap = $step_map; 44 + } 45 + 46 + protected function loadEdges(array $nodes) { 47 + $map = array(); 48 + foreach ($nodes as $node) { 49 + $deps = $this->stepMap[$node]->getDetail('dependsOn', array()); 50 + 51 + $map[$node] = array(); 52 + foreach ($deps as $dep) { 53 + $map[$node][] = $dep; 54 + } 55 + } 56 + 57 + return $map; 58 + } 59 + 60 + }
+4
src/applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php
··· 27 27 28 28 foreach ($handles as $phid => $handle) { 29 29 $build_step = $objects[$phid]; 30 + 31 + $name = $build_step->getName(); 32 + 33 + $handle->setName($name); 30 34 } 31 35 } 32 36
-8
src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
··· 22 22 return $this; 23 23 } 24 24 25 - public function getPagingColumn() { 26 - return 'sequence'; 27 - } 28 - 29 - public function getReversePaging() { 30 - return true; 31 - } 32 - 33 25 protected function loadPage() { 34 26 $table = new HarbormasterBuildStep(); 35 27 $conn_r = $table->establishConnection('r');
+18 -24
src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
··· 98 98 return array(); 99 99 } 100 100 101 - /** 102 - * Returns a list of all artifacts made available by previous build steps. 103 - */ 104 - public static function loadAvailableArtifacts( 105 - HarbormasterBuildPlan $build_plan, 106 - HarbormasterBuildStep $current_build_step, 107 - $artifact_type) { 108 - 109 - $build_steps = $build_plan->loadOrderedBuildSteps(); 110 - 111 - return self::getAvailableArtifacts( 112 - $build_plan, 113 - $build_steps, 114 - $current_build_step, 115 - $artifact_type); 101 + public function getDependencies(HarbormasterBuildStep $build_step) { 102 + return $build_step->getDetail('dependsOn', array()); 116 103 } 117 104 118 105 /** 119 - * Returns a list of all artifacts made available by previous build steps. 106 + * Returns a list of all artifacts made available in the build plan. 120 107 */ 121 108 public static function getAvailableArtifacts( 122 109 HarbormasterBuildPlan $build_plan, 123 - array $build_steps, 124 - HarbormasterBuildStep $current_build_step, 110 + $current_build_step, 125 111 $artifact_type) { 126 112 127 - $previous_implementations = array(); 128 - foreach ($build_steps as $build_step) { 129 - if ($build_step->getPHID() === $current_build_step->getPHID()) { 130 - break; 113 + $steps = id(new HarbormasterBuildStepQuery()) 114 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 115 + ->withBuildPlanPHIDs(array($build_plan->getPHID())) 116 + ->execute(); 117 + 118 + $artifact_arrays = array(); 119 + foreach ($steps as $step) { 120 + if ($current_build_step !== null && 121 + $step->getPHID() === $current_build_step->getPHID()) { 122 + 123 + continue; 131 124 } 132 - $previous_implementations[] = $build_step->getStepImplementation(); 125 + 126 + $implementation = $step->getStepImplementation(); 127 + $artifact_arrays[] = $implementation->getArtifactOutputs(); 133 128 } 134 129 135 - $artifact_arrays = mpull($previous_implementations, 'getArtifactOutputs'); 136 130 $artifacts = array(); 137 131 foreach ($artifact_arrays as $array) { 138 132 $array = ipull($array, 'type', 'key');
+10
src/applications/harbormaster/storage/build/HarbormasterBuild.php
··· 47 47 */ 48 48 const STATUS_STOPPED = 'stopped'; 49 49 50 + /** 51 + * The build has been deadlocked. 52 + */ 53 + const STATUS_DEADLOCKED = 'deadlocked'; 54 + 50 55 51 56 /** 52 57 * Get a human readable name for a build status constant. ··· 70 75 return pht('Unexpected Error'); 71 76 case self::STATUS_STOPPED: 72 77 return pht('Stopped'); 78 + case self::STATUS_DEADLOCKED: 79 + return pht('Deadlocked'); 73 80 default: 74 81 return pht('Unknown'); 75 82 } ··· 90 97 return PHUIStatusItemView::ICON_MINUS; 91 98 case self::STATUS_STOPPED: 92 99 return PHUIStatusItemView::ICON_MINUS; 100 + case self::STATUS_DEADLOCKED: 101 + return PHUIStatusItemView::ICON_WARNING; 93 102 default: 94 103 return PHUIStatusItemView::ICON_QUESTION; 95 104 } ··· 106 115 return 'green'; 107 116 case self::STATUS_FAILED: 108 117 case self::STATUS_ERROR: 118 + case self::STATUS_DEADLOCKED: 109 119 return 'red'; 110 120 case self::STATUS_STOPPED: 111 121 return 'dark';
+1
src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
··· 13 13 14 14 const TYPE_FILE = 'file'; 15 15 const TYPE_HOST = 'host'; 16 + const TYPE_BUILD_STATE = 'buildstate'; 16 17 17 18 public static function initializeNewBuildArtifact( 18 19 HarbormasterBuildTarget $build_target) {
-13
src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
··· 39 39 return $this->assertAttached($this->buildSteps); 40 40 } 41 41 42 - /** 43 - * Returns a standard, ordered list of build steps for this build plan. 44 - * 45 - * This method should be used to load build steps for a given build plan 46 - * so that the ordering is consistent. 47 - */ 48 - public function loadOrderedBuildSteps() { 49 - return id(new HarbormasterBuildStepQuery()) 50 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 51 - ->withBuildPlanPHIDs(array($this->getPHID())) 52 - ->execute(); 53 - } 54 - 55 42 public function isDisabled() { 56 43 return ($this->getPlanStatus() == self::STATUS_DISABLED); 57 44 }
+1 -1
src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
··· 9 9 protected $buildPlanPHID; 10 10 protected $className; 11 11 protected $details = array(); 12 - protected $sequence; 12 + protected $sequence = 0; 13 13 14 14 private $buildPlan = self::ATTACHABLE; 15 15 private $customFields = self::ATTACHABLE;
+1
src/applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php
··· 5 5 6 6 const TYPE_CREATE = 'harbormaster:step:create'; 7 7 const TYPE_NAME = 'harbormaster:step:name'; 8 + const TYPE_DEPENDS_ON = 'harbormaster:step:depends'; 8 9 9 10 public function getApplicationName() { 10 11 return 'harbormaster';
+45
src/applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildDependencyDatasource 4 + extends PhabricatorTypeaheadDatasource { 5 + 6 + public function getPlaceholderText() { 7 + return pht('Type another build step name...'); 8 + } 9 + 10 + public function getDatasourceApplicationClass() { 11 + return 'PhabricatorHarbormasterApplication'; 12 + } 13 + 14 + public function loadResults() { 15 + $viewer = $this->getViewer(); 16 + 17 + $plan_phid = $this->getParameter('planPHID'); 18 + $step_phid = $this->getParameter('stepPHID'); 19 + 20 + $steps = id(new HarbormasterBuildStepQuery()) 21 + ->setViewer($viewer) 22 + ->withBuildPlanPHIDs(array($plan_phid)) 23 + ->execute(); 24 + $steps = mpull($steps, null, 'getPHID'); 25 + 26 + if (count($steps) === 0) { 27 + return array(); 28 + } 29 + 30 + $results = array(); 31 + foreach ($steps as $phid => $step) { 32 + if ($step->getPHID() === $step_phid) { 33 + continue; 34 + } 35 + 36 + $results[] = id(new PhabricatorTypeaheadResult()) 37 + ->setName($step->getName()) 38 + ->setURI('/') 39 + ->setPHID($phid); 40 + } 41 + 42 + return $results; 43 + } 44 + 45 + }
+1
src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
··· 30 30 31 31 if (isset($sources[$this->class])) { 32 32 $source = $sources[$this->class]; 33 + $source->setParameters($request->getRequestData()); 33 34 34 35 $composite = new PhabricatorTypeaheadRuntimeCompositeDatasource(); 35 36 $composite->addDatasource($source);
+17 -1
src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
··· 6 6 private $query; 7 7 private $rawQuery; 8 8 private $limit; 9 + private $parameters = array(); 9 10 10 11 public function setLimit($limit) { 11 12 $this->limit = $limit; ··· 43 44 return $this->query; 44 45 } 45 46 47 + public function setParameters(array $params) { 48 + $this->parameters = $params; 49 + return $this; 50 + } 51 + 52 + public function getParameters() { 53 + return $this->parameters; 54 + } 55 + 56 + public function getParameter($name, $default = null) { 57 + return idx($this->parameters, $name, $default); 58 + } 59 + 46 60 public function getDatasourceURI() { 47 - return '/typeahead/class/'.get_class($this).'/'; 61 + $uri = new PhutilURI('/typeahead/class/'.get_class($this).'/'); 62 + $uri->setQueryParams($this->parameters); 63 + return (string)$uri; 48 64 } 49 65 50 66 abstract public function getPlaceholderText();
-41
webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js
··· 1 - /** 2 - * @provides javelin-behavior-harbormaster-reorder-steps 3 - * @requires javelin-behavior 4 - * javelin-stratcom 5 - * javelin-workflow 6 - * javelin-dom 7 - * phabricator-draggable-list 8 - */ 9 - 10 - JX.behavior('harbormaster-reorder-steps', function(config) { 11 - 12 - var root = JX.$(config.listID); 13 - 14 - var list = new JX.DraggableList('build-step', root) 15 - .setFindItemsHandler(function() { 16 - return JX.DOM.scry(root, 'li', 'build-step'); 17 - }); 18 - 19 - list.listen('didDrop', function(node) { 20 - var nodes = list.findItems(); 21 - var order = []; 22 - var key; 23 - for (var ii = 0; ii < nodes.length; ii++) { 24 - key = JX.Stratcom.getData(nodes[ii]).stepID; 25 - if (key) { 26 - order.push(key); 27 - } 28 - } 29 - 30 - list.lock(); 31 - JX.DOM.alterClass(node, 'drag-sending', true); 32 - 33 - new JX.Workflow(config.orderURI, {order: order.join()}) 34 - .setHandler(function() { 35 - JX.DOM.alterClass(node, 'drag-sending', false); 36 - list.unlock(); 37 - }) 38 - .start(); 39 - }); 40 - 41 - });