mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
Implement explicit build step ordering in Harbormaster
Summary: This implements support for explicitly marking the sequence of build steps. Users can now drag and re-order build steps in plans, and artifact dependencies are re-calculated so that if you move "Run Command" before "Lease Host", the "Run Command" step has it's artifact setting cleared and thus the step becomes invalid. Test Plan: Re-ordered build steps and observed dependencies being correctly recalculated. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T1049 Differential Revision: https://secure.phabricator.com/D7715
This commit is contained in:
parent
dd01535ed6
commit
79d153b85d
13 changed files with 221 additions and 5 deletions
2
resources/sql/patches/20131205.buildsteporder.sql
Normal file
2
resources/sql/patches/20131205.buildsteporder.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildstep
|
||||||
|
ADD COLUMN sequence INT UNSIGNED NOT NULL;
|
41
resources/sql/patches/20131205.buildstepordermig.php
Normal file
41
resources/sql/patches/20131205.buildstepordermig.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$table = new HarbormasterBuildPlan();
|
||||||
|
$conn_w = $table->establishConnection('w');
|
||||||
|
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||||
|
|
||||||
|
// Since HarbormasterBuildStepQuery has been updated to handle the
|
||||||
|
// correct order, we can't use the built in database access.
|
||||||
|
|
||||||
|
foreach (new LiskMigrationIterator($table) as $plan) {
|
||||||
|
$planname = $plan->getName();
|
||||||
|
echo "Migrating steps in {$planname}...\n";
|
||||||
|
|
||||||
|
$rows = queryfx_all(
|
||||||
|
$conn_w,
|
||||||
|
"SELECT id, sequence FROM harbormaster_buildstep ".
|
||||||
|
"WHERE buildPlanPHID = %s ".
|
||||||
|
"ORDER BY id ASC",
|
||||||
|
$plan->getPHID());
|
||||||
|
|
||||||
|
$sequence = 1;
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$id = $row['id'];
|
||||||
|
$existing = $row['sequence'];
|
||||||
|
if ($existing != 0) {
|
||||||
|
echo " - {$id} (already migrated)...\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
echo " - {$id} to position {$sequence}...\n";
|
||||||
|
queryfx(
|
||||||
|
$conn_w,
|
||||||
|
"UPDATE harbormaster_buildstep ".
|
||||||
|
"SET sequence = %d ".
|
||||||
|
"WHERE id = %d",
|
||||||
|
$sequence,
|
||||||
|
$id);
|
||||||
|
$sequence++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Done.\n";
|
|
@ -723,6 +723,7 @@ phutil_register_library_map(array(
|
||||||
'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
|
'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
|
||||||
'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
|
'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
|
||||||
'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php',
|
'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php',
|
||||||
|
'HarbormasterPlanOrderController' => 'applications/harbormaster/controller/HarbormasterPlanOrderController.php',
|
||||||
'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
|
'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
|
||||||
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
|
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
|
||||||
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
|
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
|
||||||
|
@ -3111,6 +3112,7 @@ phutil_register_library_map(array(
|
||||||
0 => 'HarbormasterPlanController',
|
0 => 'HarbormasterPlanController',
|
||||||
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
|
||||||
),
|
),
|
||||||
|
'HarbormasterPlanOrderController' => 'HarbormasterController',
|
||||||
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
|
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
|
||||||
'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
||||||
'HarbormasterScratchTable' => 'HarbormasterDAO',
|
'HarbormasterScratchTable' => 'HarbormasterDAO',
|
||||||
|
|
|
@ -65,6 +65,7 @@ final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
|
||||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||||
=> 'HarbormasterPlanListController',
|
=> 'HarbormasterPlanListController',
|
||||||
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
|
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
|
||||||
|
'order/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanOrderController',
|
||||||
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
|
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group search
|
||||||
|
*/
|
||||||
|
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(
|
||||||
|
HarbormasterCapabilityManagePlans::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must ensure that steps with artifacts become invalid if they are
|
||||||
|
// placed before the steps that produce them.
|
||||||
|
foreach ($reordered_steps as $step) {
|
||||||
|
$implementation = $step->getStepImplementation();
|
||||||
|
$settings = $implementation->getSettings();
|
||||||
|
foreach ($implementation->getSettingDefinitions() as $name => $opt) {
|
||||||
|
switch ($opt['type']) {
|
||||||
|
case BuildStepImplementation::SETTING_TYPE_ARTIFACT:
|
||||||
|
$value = $settings[$name];
|
||||||
|
$filter = $opt['artifact_type'];
|
||||||
|
$available_artifacts =
|
||||||
|
BuildStepImplementation::getAvailableArtifacts(
|
||||||
|
$plan,
|
||||||
|
$reordered_steps,
|
||||||
|
$step,
|
||||||
|
$filter);
|
||||||
|
$artifact_found = false;
|
||||||
|
foreach ($available_artifacts as $key => $type) {
|
||||||
|
if ($key === $value) {
|
||||||
|
$artifact_found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$artifact_found) {
|
||||||
|
$step->setDetail($name, null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$step->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force the page to re-render.
|
||||||
|
return id(new AphrontRedirectResponse());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -74,6 +74,8 @@ final class HarbormasterPlanViewController
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
$viewer = $request->getUser();
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
|
$list_id = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
$steps = id(new HarbormasterBuildStepQuery())
|
$steps = id(new HarbormasterBuildStepQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withBuildPlanPHIDs(array($plan->getPHID()))
|
->withBuildPlanPHIDs(array($plan->getPHID()))
|
||||||
|
@ -84,7 +86,14 @@ final class HarbormasterPlanViewController
|
||||||
|
|
||||||
$i = 1;
|
$i = 1;
|
||||||
$step_list = id(new PHUIObjectItemListView())
|
$step_list = id(new PHUIObjectItemListView())
|
||||||
->setUser($viewer);
|
->setUser($viewer)
|
||||||
|
->setID($list_id);
|
||||||
|
Javelin::initBehavior(
|
||||||
|
'harbormaster-reorder-steps',
|
||||||
|
array(
|
||||||
|
'listID' => $list_id,
|
||||||
|
'orderURI' => '/harbormaster/plan/order/'.$plan->getID().'/',
|
||||||
|
));
|
||||||
foreach ($steps as $step) {
|
foreach ($steps as $step) {
|
||||||
$implementation = null;
|
$implementation = null;
|
||||||
try {
|
try {
|
||||||
|
@ -136,6 +145,12 @@ final class HarbormasterPlanViewController
|
||||||
->setName(pht("Delete"))
|
->setName(pht("Delete"))
|
||||||
->setHref(
|
->setHref(
|
||||||
$this->getApplicationURI("step/delete/".$step->getID()."/")));
|
$this->getApplicationURI("step/delete/".$step->getID()."/")));
|
||||||
|
$item->setGrippable(true);
|
||||||
|
$item->addSigil('build-step');
|
||||||
|
$item->setMetadata(
|
||||||
|
array(
|
||||||
|
'stepID' => $step->getID(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$step_list->addItem($item);
|
$step_list->addItem($item);
|
||||||
|
|
|
@ -36,10 +36,13 @@ final class HarbormasterStepAddController
|
||||||
return $this->createDialog($implementations);
|
return $this->createDialog($implementations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$steps = $plan->loadOrderedBuildSteps();
|
||||||
|
|
||||||
$step = new HarbormasterBuildStep();
|
$step = new HarbormasterBuildStep();
|
||||||
$step->setBuildPlanPHID($plan->getPHID());
|
$step->setBuildPlanPHID($plan->getPHID());
|
||||||
$step->setClassName($class);
|
$step->setClassName($class);
|
||||||
$step->setDetails(array());
|
$step->setDetails(array());
|
||||||
|
$step->setSequence(count($steps) + 1);
|
||||||
$step->save();
|
$step->save();
|
||||||
|
|
||||||
$edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/");
|
$edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/");
|
||||||
|
|
|
@ -94,7 +94,7 @@ final class HarbormasterStepEditController
|
||||||
case BuildStepImplementation::SETTING_TYPE_ARTIFACT:
|
case BuildStepImplementation::SETTING_TYPE_ARTIFACT:
|
||||||
$filter = $opt['artifact_type'];
|
$filter = $opt['artifact_type'];
|
||||||
$available_artifacts =
|
$available_artifacts =
|
||||||
BuildStepImplementation::getAvailableArtifacts(
|
BuildStepImplementation::loadAvailableArtifacts(
|
||||||
$plan,
|
$plan,
|
||||||
$step,
|
$step,
|
||||||
$filter);
|
$filter);
|
||||||
|
|
|
@ -23,7 +23,7 @@ final class HarbormasterBuildStepQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPagingColumn() {
|
public function getPagingColumn() {
|
||||||
return 'id';
|
return 'sequence';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getReversePaging() {
|
public function getReversePaging() {
|
||||||
|
|
|
@ -117,13 +117,29 @@ abstract class BuildStepImplementation {
|
||||||
/**
|
/**
|
||||||
* Returns a list of all artifacts made available by previous build steps.
|
* Returns a list of all artifacts made available by previous build steps.
|
||||||
*/
|
*/
|
||||||
public static function getAvailableArtifacts(
|
public static function loadAvailableArtifacts(
|
||||||
HarbormasterBuildPlan $build_plan,
|
HarbormasterBuildPlan $build_plan,
|
||||||
HarbormasterBuildStep $current_build_step,
|
HarbormasterBuildStep $current_build_step,
|
||||||
$artifact_type) {
|
$artifact_type) {
|
||||||
|
|
||||||
$build_steps = $build_plan->loadOrderedBuildSteps();
|
$build_steps = $build_plan->loadOrderedBuildSteps();
|
||||||
|
|
||||||
|
return self::getAvailableArtifacts(
|
||||||
|
$build_plan,
|
||||||
|
$build_steps,
|
||||||
|
$current_build_step,
|
||||||
|
$artifact_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all artifacts made available by previous build steps.
|
||||||
|
*/
|
||||||
|
public static function getAvailableArtifacts(
|
||||||
|
HarbormasterBuildPlan $build_plan,
|
||||||
|
array $build_steps,
|
||||||
|
HarbormasterBuildStep $current_build_step,
|
||||||
|
$artifact_type) {
|
||||||
|
|
||||||
$previous_implementations = array();
|
$previous_implementations = array();
|
||||||
foreach ($build_steps as $build_step) {
|
foreach ($build_steps as $build_step) {
|
||||||
if ($build_step->getPHID() === $current_build_step->getPHID()) {
|
if ($build_step->getPHID() === $current_build_step->getPHID()) {
|
||||||
|
@ -136,7 +152,7 @@ abstract class BuildStepImplementation {
|
||||||
$artifacts = array();
|
$artifacts = array();
|
||||||
foreach ($artifact_arrays as $array) {
|
foreach ($artifact_arrays as $array) {
|
||||||
foreach ($array as $name => $type) {
|
foreach ($array as $name => $type) {
|
||||||
if ($type !== $artifact_type) {
|
if ($type !== $artifact_type && $artifact_type !== null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$artifacts[$name] = $type;
|
$artifacts[$name] = $type;
|
||||||
|
|
|
@ -6,6 +6,7 @@ final class HarbormasterBuildStep extends HarbormasterDAO
|
||||||
protected $buildPlanPHID;
|
protected $buildPlanPHID;
|
||||||
protected $className;
|
protected $className;
|
||||||
protected $details = array();
|
protected $details = array();
|
||||||
|
protected $sequence;
|
||||||
|
|
||||||
private $buildPlan = self::ATTACHABLE;
|
private $buildPlan = self::ATTACHABLE;
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@ final class HarbormasterBuildStep extends HarbormasterDAO
|
||||||
return $implementation;
|
return $implementation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1804,6 +1804,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
||||||
'type' => 'sql',
|
'type' => 'sql',
|
||||||
'name' => $this->getPatchPath('20131204.pushlog.sql'),
|
'name' => $this->getPatchPath('20131204.pushlog.sql'),
|
||||||
),
|
),
|
||||||
|
'20131205.buildsteporder.sql' => array(
|
||||||
|
'type' => 'sql',
|
||||||
|
'name' => $this->getPatchPath('20131205.buildsteporder.sql'),
|
||||||
|
),
|
||||||
|
'20131205.buildstepordermig.php' => array(
|
||||||
|
'type' => 'php',
|
||||||
|
'name' => $this->getPatchPath('20131205.buildstepordermig.php'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* @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, after) {
|
||||||
|
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(e) {
|
||||||
|
JX.DOM.alterClass(node, 'drag-sending', false);
|
||||||
|
list.unlock();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue