1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

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
This commit is contained in:
James Rhodes 2014-07-31 11:39:49 +10:00
parent 31343e61ce
commit cad41ea294
21 changed files with 378 additions and 213 deletions

View file

@ -0,0 +1,54 @@
<?php
$plan_table = new HarbormasterBuildPlan();
$step_table = new HarbormasterBuildStep();
$conn_w = $plan_table->establishConnection('w');
foreach (new LiskMigrationIterator($plan_table) as $plan) {
echo pht(
"Migrating build plan %d: %s...\n",
$plan->getID(),
$plan->getName());
// Load all build steps in order using the step sequence.
$steps = queryfx_all(
$conn_w,
'SELECT id FROM %T WHERE buildPlanPHID = %s ORDER BY sequence ASC;',
$step_table->getTableName(),
$plan->getPHID());
$previous_step = null;
foreach ($steps as $step) {
$id = $step['id'];
$loaded_step = id(new HarbormasterBuildStep())->load($id);
$depends_on = $loaded_step->getDetail('dependsOn');
if ($depends_on !== null) {
// This plan already contains steps with depends_on set, so
// we skip since there's nothing to migrate.
break;
}
if ($previous_step === null) {
$depends_on = array();
} else {
$depends_on = array($previous_step->getPHID());
}
$loaded_step->setDetail('dependsOn', $depends_on);
queryfx(
$conn_w,
'UPDATE %T SET details = %s WHERE id = %d',
$step_table->getTableName(),
json_encode($loaded_step->getDetails()),
$loaded_step->getID());
$previous_step = $loaded_step;
echo pht(
" Migrated build step %d.\n",
$loaded_step->getID());
}
}

View file

@ -652,7 +652,9 @@ phutil_register_library_map(array(
'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php',
'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php',
'HarbormasterBuildDependencyDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php',
'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php',
'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php',
'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php',
'HarbormasterBuildItemPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildItemPHIDType.php',
'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php',
@ -715,7 +717,6 @@ phutil_register_library_map(array(
'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php',
'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php',
'HarbormasterPlanOrderController' => 'applications/harbormaster/controller/HarbormasterPlanOrderController.php',
'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php',
'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php',
@ -3391,7 +3392,9 @@ phutil_register_library_map(array(
),
'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildCommand' => 'HarbormasterDAO',
'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildEngine' => 'Phobject',
'HarbormasterBuildGraph' => 'AbstractDirectedGraph',
'HarbormasterBuildItem' => 'HarbormasterDAO',
'HarbormasterBuildItemPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
@ -3476,7 +3479,6 @@ phutil_register_library_map(array(
'HarbormasterPlanDisableController' => 'HarbormasterPlanController',
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
'HarbormasterPlanListController' => 'HarbormasterPlanController',
'HarbormasterPlanOrderController' => 'HarbormasterController',
'HarbormasterPlanRunController' => 'HarbormasterController',
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation',

View file

@ -179,29 +179,7 @@ final class HarbormasterBuildableViewController
->setHref($view_uri);
$status = $build->getBuildStatus();
switch ($status) {
case HarbormasterBuild::STATUS_INACTIVE:
$item->setBarColor('grey');
break;
case HarbormasterBuild::STATUS_PENDING:
$item->setBarColor('blue');
break;
case HarbormasterBuild::STATUS_BUILDING:
$item->setBarColor('yellow');
break;
case HarbormasterBuild::STATUS_PASSED:
$item->setBarColor('green');
break;
case HarbormasterBuild::STATUS_FAILED:
$item->setBarColor('red');
break;
case HarbormasterBuild::STATUS_ERROR:
$item->setBarColor('red');
break;
case HarbormasterBuild::STATUS_STOPPED:
$item->setBarColor('black');
break;
}
$item->setBarColor(HarbormasterBuild::getBuildStatusColor($status));
$item->addAttribute(HarbormasterBuild::getBuildStatusName($status));

View file

@ -1,53 +0,0 @@
<?php
final class HarbormasterPlanOrderController extends HarbormasterController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$request->validateCSRF();
$this->requireApplicationCapability(
HarbormasterManagePlansCapability::CAPABILITY);
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
// Load all steps.
$order = $request->getStrList('order');
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($user)
->withIDs($order)
->execute();
$steps = array_select_keys($steps, $order);
$reordered_steps = array();
// Apply sequences.
$sequence = 1;
foreach ($steps as $step) {
$step->setSequence($sequence++);
$step->save();
$reordered_steps[] = $step;
}
// NOTE: Reordering steps may invalidate artifacts. This is fine; the UI
// will show that there are ordering issues.
// Force the page to re-render.
return id(new AphrontRedirectResponse());
}
}

View file

@ -49,9 +49,21 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Plan %d', $id));
list($step_list, $has_any_conflicts) = $this->buildStepList($plan);
list($step_list, $has_any_conflicts, $would_deadlock) =
$this->buildStepList($plan);
if ($has_any_conflicts) {
if ($would_deadlock) {
$box->setFormErrors(
array(
pht(
'This build plan will deadlock when executed, due to '.
'circular dependencies present in the build plan. '.
'Examine the step list and resolve the deadlock.'),
));
} else if ($has_any_conflicts) {
// A deadlocking build will also cause all the artifacts to be
// invalid, so we just skip showing this message if that's the
// case.
$box->setFormErrors(
array(
pht(
@ -76,31 +88,37 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
$request = $this->getRequest();
$viewer = $request->getUser();
$list_id = celerity_generate_unique_node_id();
$run_order =
HarbormasterBuildGraph::determineDependencyExecution($plan);
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($viewer)
->withBuildPlanPHIDs(array($plan->getPHID()))
->execute();
$steps = mpull($steps, null, 'getPHID');
$can_edit = $this->hasApplicationCapability(
HarbormasterManagePlansCapability::CAPABILITY);
$i = 1;
$step_list = id(new PHUIObjectItemListView())
->setUser($viewer)
->setNoDataString(
pht('This build plan does not have any build steps yet.'))
->setID($list_id);
Javelin::initBehavior(
'harbormaster-reorder-steps',
array(
'listID' => $list_id,
'orderURI' => '/harbormaster/plan/order/'.$plan->getID().'/',
));
pht('This build plan does not have any build steps yet.'));
$i = 1;
$last_depth = 0;
$has_any_conflicts = false;
foreach ($steps as $step) {
$is_deadlocking = false;
foreach ($run_order as $run_ref) {
$step = $steps[$run_ref['node']->getPHID()];
$depth = $run_ref['depth'] + 1;
if ($last_depth !== $depth) {
$last_depth = $depth;
$i = 1;
} else {
$i++;
}
$implementation = null;
try {
$implementation = $step->getStepImplementation();
@ -108,7 +126,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
// We can't initialize the implementation. This might be because
// it's been renamed or no longer exists.
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Step %d', $i++))
->setObjectName(pht('Step %d.%d', $depth, $i))
->setHeader(pht('Unknown Implementation'))
->setBarColor('red')
->addAttribute(pht(
@ -127,7 +145,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
continue;
}
$item = id(new PHUIObjectItemView())
->setObjectName('Step '.$i++)
->setObjectName(pht('Step %d.%d', $depth, $i))
->setHeader($step->getName());
$item->addAttribute($implementation->getDescription());
@ -138,12 +156,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
if ($can_edit) {
$item->setHref($edit_uri);
$item->setGrippable(true);
$item->addSigil('build-step');
$item->setMetadata(
array(
'stepID' => $step->getID(),
));
}
$item
@ -157,17 +169,23 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
->setHref(
$this->getApplicationURI('step/delete/'.$step->getID().'/')));
$depends = $step->getStepImplementation()->getDependencies($step);
$inputs = $step->getStepImplementation()->getArtifactInputs();
$outputs = $step->getStepImplementation()->getArtifactOutputs();
$has_conflicts = false;
if ($inputs || $outputs) {
if ($depends || $inputs || $outputs) {
$available_artifacts =
HarbormasterBuildStepImplementation::loadAvailableArtifacts(
HarbormasterBuildStepImplementation::getAvailableArtifacts(
$plan,
$step,
null);
list($depends_ui, $has_conflicts) = $this->buildDependsOnList(
$depends,
pht('Depends On'),
$steps);
list($inputs_ui, $has_conflicts) = $this->buildArtifactList(
$inputs,
'in',
@ -187,6 +205,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
'class' => 'harbormaster-artifact-io',
),
array(
$depends_ui,
$inputs_ui,
$outputs_ui,
)));
@ -197,10 +216,18 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
$item->setBarColor('red');
}
if ($run_ref['cycle']) {
$is_deadlocking = true;
}
if ($is_deadlocking) {
$item->setBarColor('red');
}
$step_list->addItem($item);
}
return array($step_list, $has_any_conflicts);
return array($step_list, $has_any_conflicts, $is_deadlocking);
}
private function buildActionList(HarbormasterBuildPlan $plan) {
@ -291,7 +318,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
return array(null, $has_conflicts);
}
$this->requireResource('harbormaster-css');
$header = phutil_tag(
@ -384,4 +410,69 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
return array($ui, $has_conflicts);
}
private function buildDependsOnList(
array $step_phids,
$name,
array $steps) {
$has_conflicts = false;
if (count($step_phids) === 0) {
return null;
}
$this->requireResource('harbormaster-css');
$steps = mpull($steps, null, 'getPHID');
$header = phutil_tag(
'div',
array(
'class' => 'harbormaster-artifact-summary-header',
),
$name);
$list = new PHUIStatusListView();
foreach ($step_phids as $step_phid) {
$error = null;
if (idx($steps, $step_phid) === null) {
$icon = PHUIStatusItemView::ICON_WARNING;
$color = 'red';
$icon_label = pht('Missing Dependency');
$has_conflicts = true;
$error = pht(
'This dependency specifies a build step which doesn\'t exist.');
} else {
$bound = phutil_tag(
'strong',
array(),
idx($steps, $step_phid)->getName());
$icon = PHUIStatusItemView::ICON_ACCEPT;
$color = 'green';
$icon_label = pht('Valid Input');
}
if ($error) {
$note = array(
phutil_tag('strong', array(), pht('ERROR:')),
' ',
$error);
} else {
$note = $bound;
}
$list->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $color, $icon_label)
->setTarget(pht('Build Step'))
->setNote($note));
}
$ui = array(
$header,
$list,
);
return array($ui, $has_conflicts);
}
}

View file

@ -65,12 +65,21 @@ final class HarbormasterStepEditController extends HarbormasterController {
$e_name = true;
$v_name = $step->getName();
$e_depends_on = true;
$raw_depends_on = $step->getDetail('dependsOn', array());
$v_depends_on = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($raw_depends_on)
->execute();
$errors = array();
$validation_exception = null;
if ($request->isFormPost()) {
$e_name = null;
$v_name = $request->getStr('name');
$e_depends_on = null;
$v_depends_on = $request->getArr('dependsOn');
$xactions = $field_list->buildFieldTransactionsFromRequest(
new HarbormasterBuildStepTransaction(),
@ -86,12 +95,13 @@ final class HarbormasterStepEditController extends HarbormasterController {
->setNewValue($v_name);
array_unshift($xactions, $name_xaction);
if ($is_new) {
// This is okay, but a little iffy. We should move it inside the editor
// if we create plans elsewhere.
$steps = $plan->loadOrderedBuildSteps();
$step->setSequence(count($steps) + 1);
$depends_on_xaction = id(new HarbormasterBuildStepTransaction())
->setTransactionType(
HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON)
->setNewValue($v_depends_on);
array_unshift($xactions, $depends_on_xaction);
if ($is_new) {
// When creating a new step, make sure we have a create transaction
// so we'll apply the transactions even if the step has no
// configurable options.
@ -117,6 +127,19 @@ final class HarbormasterStepEditController extends HarbormasterController {
->setError($e_name)
->setValue($v_name));
$form
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource(id(new HarbormasterBuildDependencyDatasource())
->setParameters(array(
'planPHID' => $plan->getPHID(),
'stepPHID' => $is_new ? null : $step->getPHID(),
)))
->setName('dependsOn')
->setLabel(pht('Depends On'))
->setError($e_depends_on)
->setValue($v_depends_on));
$field_list->appendFieldsToForm($form);
if ($is_new) {

View file

@ -8,6 +8,7 @@ final class HarbormasterBuildStepEditor
$types[] = HarbormasterBuildStepTransaction::TYPE_CREATE;
$types[] = HarbormasterBuildStepTransaction::TYPE_NAME;
$types[] = HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON;
return $types;
}
@ -24,6 +25,11 @@ final class HarbormasterBuildStepEditor
return null;
}
return $object->getName();
case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON:
if ($this->getIsNewObject()) {
return null;
}
return $object->getDetail('dependsOn', array());
}
return parent::getCustomTransactionOldValue($object, $xaction);
@ -37,6 +43,7 @@ final class HarbormasterBuildStepEditor
case HarbormasterBuildStepTransaction::TYPE_CREATE:
return true;
case HarbormasterBuildStepTransaction::TYPE_NAME:
case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON:
return $xaction->getNewValue();
}
@ -52,6 +59,8 @@ final class HarbormasterBuildStepEditor
return;
case HarbormasterBuildStepTransaction::TYPE_NAME:
return $object->setName($xaction->getNewValue());
case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON:
return $object->setDetail('dependsOn', $xaction->getNewValue());
}
return parent::applyCustomInternalTransaction($object, $xaction);
@ -64,6 +73,7 @@ final class HarbormasterBuildStepEditor
switch ($xaction->getTransactionType()) {
case HarbormasterBuildStepTransaction::TYPE_CREATE:
case HarbormasterBuildStepTransaction::TYPE_NAME:
case HarbormasterBuildStepTransaction::TYPE_DEPENDS_ON:
return;
}

View file

@ -246,22 +246,14 @@ final class HarbormasterBuildEngine extends Phobject {
// Identify all the steps which are ready to run (because all their
// dependencies are complete).
$previous_step = null;
$runnable = array();
foreach ($steps as $step) {
// TODO: For now, we're hard coding sequential dependencies into build
// steps. In the future, we can be smart about this instead.
if ($previous_step) {
$dependencies = array($previous_step);
} else {
$dependencies = array();
}
$dependencies = $step->getStepImplementation()->getDependencies($step);
if (isset($queued[$step->getPHID()])) {
$can_run = true;
foreach ($dependencies as $dependency) {
if (empty($complete[$dependency->getPHID()])) {
if (empty($complete[$dependency])) {
$can_run = false;
break;
}
@ -271,14 +263,12 @@ final class HarbormasterBuildEngine extends Phobject {
$runnable[] = $step;
}
}
$previous_step = $step;
}
if (!$runnable && !$waiting && !$underway) {
// TODO: This means the build is deadlocked, probably? It should not
// normally be possible yet, but we should communicate it more clearly.
$build->setBuildStatus(HarbormasterBuild::STATUS_FAILED);
// This means the build is deadlocked, and the user has configured
// circular dependencies.
$build->setBuildStatus(HarbormasterBuild::STATUS_DEADLOCKED);
$build->save();
return;
}
@ -292,7 +282,6 @@ final class HarbormasterBuildEngine extends Phobject {
$this->queueNewBuildTarget($target);
}
}
@ -378,7 +367,8 @@ final class HarbormasterBuildEngine extends Phobject {
$all_pass = false;
}
if ($build->getBuildStatus() == HarbormasterBuild::STATUS_FAILED ||
$build->getBuildStatus() == HarbormasterBuild::STATUS_ERROR) {
$build->getBuildStatus() == HarbormasterBuild::STATUS_ERROR ||
$build->getBuildStatus() == HarbormasterBuild::STATUS_DEADLOCKED) {
$any_fail = true;
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* Directed graph representing a build plan
*/
final class HarbormasterBuildGraph extends AbstractDirectedGraph {
private $stepMap;
public static function determineDependencyExecution(
HarbormasterBuildPlan $plan) {
$steps = id(new HarbormasterBuildStepQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildPlanPHIDs(array($plan->getPHID()))
->execute();
$steps_by_phid = mpull($steps, null, 'getPHID');
$step_phids = mpull($steps, 'getPHID');
if (count($steps) === 0) {
return array();
}
$graph = id(new HarbormasterBuildGraph($steps_by_phid))
->addNodes($step_phids);
$raw_results =
$graph->getBestEffortTopographicallySortedNodes();
$results = array();
foreach ($raw_results as $node) {
$results[] = array(
'node' => $steps_by_phid[$node['node']],
'depth' => $node['depth'],
'cycle' => $node['cycle']);
}
return $results;
}
public function __construct($step_map) {
$this->stepMap = $step_map;
}
protected function loadEdges(array $nodes) {
$map = array();
foreach ($nodes as $node) {
$deps = $this->stepMap[$node]->getDetail('dependsOn', array());
$map[$node] = array();
foreach ($deps as $dep) {
$map[$node][] = $dep;
}
}
return $map;
}
}

View file

@ -27,6 +27,10 @@ final class HarbormasterBuildStepPHIDType extends PhabricatorPHIDType {
foreach ($handles as $phid => $handle) {
$build_step = $objects[$phid];
$name = $build_step->getName();
$handle->setName($name);
}
}

View file

@ -22,14 +22,6 @@ final class HarbormasterBuildStepQuery
return $this;
}
public function getPagingColumn() {
return 'sequence';
}
public function getReversePaging() {
return true;
}
protected function loadPage() {
$table = new HarbormasterBuildStep();
$conn_r = $table->establishConnection('r');

View file

@ -98,41 +98,35 @@ abstract class HarbormasterBuildStepImplementation {
return array();
}
/**
* Returns a list of all artifacts made available by previous build steps.
*/
public static function loadAvailableArtifacts(
HarbormasterBuildPlan $build_plan,
HarbormasterBuildStep $current_build_step,
$artifact_type) {
$build_steps = $build_plan->loadOrderedBuildSteps();
return self::getAvailableArtifacts(
$build_plan,
$build_steps,
$current_build_step,
$artifact_type);
public function getDependencies(HarbormasterBuildStep $build_step) {
return $build_step->getDetail('dependsOn', array());
}
/**
* Returns a list of all artifacts made available by previous build steps.
* Returns a list of all artifacts made available in the build plan.
*/
public static function getAvailableArtifacts(
HarbormasterBuildPlan $build_plan,
array $build_steps,
HarbormasterBuildStep $current_build_step,
$current_build_step,
$artifact_type) {
$previous_implementations = array();
foreach ($build_steps as $build_step) {
if ($build_step->getPHID() === $current_build_step->getPHID()) {
break;
$steps = id(new HarbormasterBuildStepQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildPlanPHIDs(array($build_plan->getPHID()))
->execute();
$artifact_arrays = array();
foreach ($steps as $step) {
if ($current_build_step !== null &&
$step->getPHID() === $current_build_step->getPHID()) {
continue;
}
$previous_implementations[] = $build_step->getStepImplementation();
$implementation = $step->getStepImplementation();
$artifact_arrays[] = $implementation->getArtifactOutputs();
}
$artifact_arrays = mpull($previous_implementations, 'getArtifactOutputs');
$artifacts = array();
foreach ($artifact_arrays as $array) {
$array = ipull($array, 'type', 'key');

View file

@ -47,6 +47,11 @@ final class HarbormasterBuild extends HarbormasterDAO
*/
const STATUS_STOPPED = 'stopped';
/**
* The build has been deadlocked.
*/
const STATUS_DEADLOCKED = 'deadlocked';
/**
* Get a human readable name for a build status constant.
@ -70,6 +75,8 @@ final class HarbormasterBuild extends HarbormasterDAO
return pht('Unexpected Error');
case self::STATUS_STOPPED:
return pht('Stopped');
case self::STATUS_DEADLOCKED:
return pht('Deadlocked');
default:
return pht('Unknown');
}
@ -90,6 +97,8 @@ final class HarbormasterBuild extends HarbormasterDAO
return PHUIStatusItemView::ICON_MINUS;
case self::STATUS_STOPPED:
return PHUIStatusItemView::ICON_MINUS;
case self::STATUS_DEADLOCKED:
return PHUIStatusItemView::ICON_WARNING;
default:
return PHUIStatusItemView::ICON_QUESTION;
}
@ -106,6 +115,7 @@ final class HarbormasterBuild extends HarbormasterDAO
return 'green';
case self::STATUS_FAILED:
case self::STATUS_ERROR:
case self::STATUS_DEADLOCKED:
return 'red';
case self::STATUS_STOPPED:
return 'dark';

View file

@ -13,6 +13,7 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
const TYPE_FILE = 'file';
const TYPE_HOST = 'host';
const TYPE_BUILD_STATE = 'buildstate';
public static function initializeNewBuildArtifact(
HarbormasterBuildTarget $build_target) {

View file

@ -39,19 +39,6 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
return $this->assertAttached($this->buildSteps);
}
/**
* Returns a standard, ordered list of build steps for this build plan.
*
* This method should be used to load build steps for a given build plan
* so that the ordering is consistent.
*/
public function loadOrderedBuildSteps() {
return id(new HarbormasterBuildStepQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildPlanPHIDs(array($this->getPHID()))
->execute();
}
public function isDisabled() {
return ($this->getPlanStatus() == self::STATUS_DISABLED);
}

View file

@ -9,7 +9,7 @@ final class HarbormasterBuildStep extends HarbormasterDAO
protected $buildPlanPHID;
protected $className;
protected $details = array();
protected $sequence;
protected $sequence = 0;
private $buildPlan = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;

View file

@ -5,6 +5,7 @@ final class HarbormasterBuildStepTransaction
const TYPE_CREATE = 'harbormaster:step:create';
const TYPE_NAME = 'harbormaster:step:name';
const TYPE_DEPENDS_ON = 'harbormaster:step:depends';
public function getApplicationName() {
return 'harbormaster';

View file

@ -0,0 +1,45 @@
<?php
final class HarbormasterBuildDependencyDatasource
extends PhabricatorTypeaheadDatasource {
public function getPlaceholderText() {
return pht('Type another build step name...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
public function loadResults() {
$viewer = $this->getViewer();
$plan_phid = $this->getParameter('planPHID');
$step_phid = $this->getParameter('stepPHID');
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($viewer)
->withBuildPlanPHIDs(array($plan_phid))
->execute();
$steps = mpull($steps, null, 'getPHID');
if (count($steps) === 0) {
return array();
}
$results = array();
foreach ($steps as $phid => $step) {
if ($step->getPHID() === $step_phid) {
continue;
}
$results[] = id(new PhabricatorTypeaheadResult())
->setName($step->getName())
->setURI('/')
->setPHID($phid);
}
return $results;
}
}

View file

@ -30,6 +30,7 @@ final class PhabricatorTypeaheadModularDatasourceController
if (isset($sources[$this->class])) {
$source = $sources[$this->class];
$source->setParameters($request->getRequestData());
$composite = new PhabricatorTypeaheadRuntimeCompositeDatasource();
$composite->addDatasource($source);

View file

@ -6,6 +6,7 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject {
private $query;
private $rawQuery;
private $limit;
private $parameters = array();
public function setLimit($limit) {
$this->limit = $limit;
@ -43,8 +44,23 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject {
return $this->query;
}
public function setParameters(array $params) {
$this->parameters = $params;
return $this;
}
public function getParameters() {
return $this->parameters;
}
public function getParameter($name, $default = null) {
return idx($this->parameters, $name, $default);
}
public function getDatasourceURI() {
return '/typeahead/class/'.get_class($this).'/';
$uri = new PhutilURI('/typeahead/class/'.get_class($this).'/');
$uri->setQueryParams($this->parameters);
return (string)$uri;
}
abstract public function getPlaceholderText();

View file

@ -1,41 +0,0 @@
/**
* @provides javelin-behavior-harbormaster-reorder-steps
* @requires javelin-behavior
* javelin-stratcom
* javelin-workflow
* javelin-dom
* phabricator-draggable-list
*/
JX.behavior('harbormaster-reorder-steps', function(config) {
var root = JX.$(config.listID);
var list = new JX.DraggableList('build-step', root)
.setFindItemsHandler(function() {
return JX.DOM.scry(root, 'li', 'build-step');
});
list.listen('didDrop', function(node) {
var nodes = list.findItems();
var order = [];
var key;
for (var ii = 0; ii < nodes.length; ii++) {
key = JX.Stratcom.getData(nodes[ii]).stepID;
if (key) {
order.push(key);
}
}
list.lock();
JX.DOM.alterClass(node, 'drag-sending', true);
new JX.Workflow(config.orderURI, {order: order.join()})
.setHandler(function() {
JX.DOM.alterClass(node, 'drag-sending', false);
list.unlock();
})
.start();
});
});