1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

Formalize "manual" buildables in Harbormaster

Summary:
Ref T1049. Generally, it's useful to separate test/trial/manual runs from production/automatic runs.

For example, you don't want to email a bunch of people that the build is broken just because you messed something up when writing a new build plan. You'd rather try it first, then promote it into production once you have some good runs.

Similarly, test runs generally should not affect the outside world, etc. Finally, some build steps (like "wait for other buildables") may want to behave differently when run in production/automation than when run in a testing environment (where they should probably continue immediately).

So, formalize the distinction between automatic buildables (those created passively by the system in response to events) and manual buildables (those created explicitly by users). Add filtering, and stop the automated parts of the system from interacting with the manual parts (for example, we won't show manual results on revisions).

This also moves the "Apply Build Plan" to a third, new home: instead of the sidebar or Buildables, it's now on plans. I think this generally makes more sense given how things have developed. Broadly, this improves isolation of test environments.

Test Plan: Created some builds, browsed around, used filters, etc.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T1049

Differential Revision: https://secure.phabricator.com/D7824
This commit is contained in:
epriestley 2013-12-26 10:40:43 -08:00
parent 4a56e26a67
commit ac19c55822
14 changed files with 175 additions and 235 deletions

View file

@ -0,0 +1,6 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable
ADD isManualBuildable BOOL NOT NULL;
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable
ADD KEY `key_manual` (isManualBuildable);

View file

@ -707,8 +707,6 @@ phutil_register_library_map(array(
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php',
'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php',
'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php',
'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php',
'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php',
@ -730,6 +728,7 @@ phutil_register_library_map(array(
'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', 'HarbormasterPlanOrderController' => 'applications/harbormaster/controller/HarbormasterPlanOrderController.php',
'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.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',
@ -3133,8 +3132,6 @@ phutil_register_library_map(array(
0 => 'HarbormasterDAO', 0 => 'HarbormasterDAO',
1 => 'PhabricatorPolicyInterface', 1 => 'PhabricatorPolicyInterface',
), ),
'HarbormasterBuildableApplyController' => 'HarbormasterController',
'HarbormasterBuildableEditController' => 'HarbormasterController',
'HarbormasterBuildableListController' => 'HarbormasterBuildableListController' =>
array( array(
0 => 'HarbormasterController', 0 => 'HarbormasterController',
@ -3164,6 +3161,7 @@ phutil_register_library_map(array(
1 => 'PhabricatorApplicationSearchResultsControllerInterface', 1 => 'PhabricatorApplicationSearchResultsControllerInterface',
), ),
'HarbormasterPlanOrderController' => 'HarbormasterController', 'HarbormasterPlanOrderController' => 'HarbormasterController',
'HarbormasterPlanRunController' => 'HarbormasterController',
'HarbormasterPlanViewController' => 'HarbormasterPlanController', 'HarbormasterPlanViewController' => 'HarbormasterPlanController',
'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject', 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'HarbormasterScratchTable' => 'HarbormasterDAO', 'HarbormasterScratchTable' => 'HarbormasterDAO',

View file

@ -48,10 +48,6 @@ final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
'/harbormaster/' => array( '/harbormaster/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' '(?:query/(?P<queryKey>[^/]+)/)?'
=> 'HarbormasterBuildableListController', => 'HarbormasterBuildableListController',
'buildable/' => array(
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController',
'apply/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableApplyController',
),
'step/' => array( 'step/' => array(
'add/(?:(?P<id>\d+)/)?' => 'HarbormasterStepAddController', 'add/(?:(?P<id>\d+)/)?' => 'HarbormasterStepAddController',
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController', 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController',
@ -67,6 +63,7 @@ final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController', 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
'order/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanOrderController', 'order/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanOrderController',
'disable/(?P<id>\d+)/' => 'HarbormasterPlanDisableController', 'disable/(?P<id>\d+)/' => 'HarbormasterPlanDisableController',
'run/(?P<id>\d+)/' => 'HarbormasterPlanRunController',
'(?P<id>\d+)/' => 'HarbormasterPlanViewController', '(?P<id>\d+)/' => 'HarbormasterPlanViewController',
), ),
), ),

View file

@ -1,74 +0,0 @@
<?php
final class HarbormasterBuildableApplyController
extends HarbormasterController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $this->id;
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if ($buildable === null) {
throw new Exception("Buildable not found!");
}
$buildable_uri = '/B'.$buildable->getID();
if ($request->isDialogFormPost()) {
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($request->getInt('build-plan')))
->executeOne();
HarbormasterBuildable::applyBuildPlans(
$buildable->getBuildablePHID(),
$buildable->getContainerPHID(),
array($plan->getPHID()));
return id(new AphrontRedirectResponse())->setURI($buildable_uri);
}
$plans = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->execute();
$options = array();
foreach ($plans as $plan) {
$options[$plan->getID()] = $plan->getName();
}
// FIXME: I'd really like to use the dialog that "Edit Differential
// Revisions" uses, but that code is quite hard-coded for the particular
// uses, so for now we just give a single dropdown.
$dialog = new AphrontDialogView();
$dialog->setTitle(pht('Apply which plan?'))
->setUser($viewer)
->addSubmitButton(pht('Apply'))
->addCancelButton($buildable_uri);
$dialog->appendChild(
phutil_tag(
'p',
array(),
pht(
'Select what build plan you want to apply to this buildable:')));
$dialog->appendChild(
id(new AphrontFormSelectControl())
->setUser($viewer)
->setName('build-plan')
->setOptions($options));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -1,140 +0,0 @@
<?php
final class HarbormasterBuildableEditController
extends HarbormasterController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$this->requireApplicationCapability(
HarbormasterCapabilityManagePlans::CAPABILITY);
if ($this->id) {
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$buildable) {
return new Aphront404Response();
}
} else {
$buildable = HarbormasterBuildable::initializeNewBuildable($viewer);
}
$e_name = true;
$v_name = null;
$errors = array();
if ($request->isFormPost()) {
$v_name = $request->getStr('buildablePHID');
if ($v_name) {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($v_name))
->executeOne();
if ($object instanceof DifferentialRevision) {
$revision = $object;
$object = $object->loadActiveDiff();
$buildable
->setBuildablePHID($object->getPHID())
->setContainerPHID($revision->getPHID());
} else if ($object instanceof PhabricatorRepositoryCommit) {
$buildable
->setBuildablePHID($object->getPHID())
->setContainerPHID($object->getRepository()->getPHID());
} else {
$e_name = pht('Invalid');
$errors[] = pht('Enter the name of a revision or commit.');
}
} else {
$e_name = pht('Required');
$errors[] = pht('You must choose a revision or commit to build.');
}
if (!$errors) {
$buildable->save();
$buildable_uri = '/B'.$buildable->getID();
return id(new AphrontRedirectResponse())->setURI($buildable_uri);
}
}
if ($errors) {
$errors = id(new AphrontErrorView())->setErrors($errors);
}
$is_new = (!$buildable->getID());
if ($is_new) {
$title = pht('New Buildable');
$cancel_uri = $this->getApplicationURI();
$save_button = pht('Create Buildable');
} else {
$id = $buildable->getID();
$title = pht('Edit Buildable');
$cancel_uri = "/B{$id}";
$save_button = pht('Save Buildable');
}
$form = id(new AphrontFormView())
->setUser($viewer);
if ($is_new) {
$form
->appendRemarkupInstructions(
pht(
'Enter the name of a commit or revision, like `rX123456789` '.
'or `D123`.'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Buildable Name')
->setName('buildablePHID')
->setError($e_name)
->setValue($v_name));
} else {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Buildable'))
->setValue($buildable->getBuildableHandle()->renderLink()));
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue($save_button)
->addCancelButton($cancel_uri));
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormError($errors)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
if ($is_new) {
$crumbs->addTextCrumb(pht('New Buildable'));
} else {
$id = $buildable->getID();
$crumbs->addTextCrumb("B{$id}", "/B{$id}");
$crumbs->addTextCrumb(pht('Edit'));
}
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
'device' => true,
));
}
}

View file

@ -44,8 +44,14 @@ final class HarbormasterBuildableListController
$item->setHref("/B{$id}"); $item->setHref("/B{$id}");
} }
if ($buildable->getIsManualBuildable()) {
$item->addIcon('wrench-grey', pht('Manual'));
}
$list->addItem($item); $list->addItem($item);
// TODO: This is proof-of-concept for getting meaningful status // TODO: This is proof-of-concept for getting meaningful status
// information into this list, and should get an improvement pass // information into this list, and should get an improvement pass
// once we're a little farther along. // once we're a little farther along.
@ -86,8 +92,7 @@ final class HarbormasterBuildableListController
$nav->addFilter('new/', pht('New Build Plan')); $nav->addFilter('new/', pht('New Build Plan'));
} }
$nav->addLabel('Utilities'); $nav->addLabel(pht('Build Plans'));
$nav->addFilter('buildable/edit/', pht('New Manual Build'));
$nav->addFilter('plan/', pht('Manage Build Plans')); $nav->addFilter('plan/', pht('Manage Build Plans'));
$nav->selectFilter(null); $nav->selectFilter(null);

View file

@ -118,15 +118,6 @@ final class HarbormasterBuildableViewController
->setObject($buildable) ->setObject($buildable)
->setObjectURI("/B{$id}"); ->setObjectURI("/B{$id}");
$apply_uri = $this->getApplicationURI('/buildable/apply/'.$id.'/');
$list->addAction(
id(new PhabricatorActionView())
->setName(pht('Apply Build Plan'))
->setIcon('edit')
->setHref($apply_uri)
->setWorkflow(true));
return $list; return $list;
} }
@ -153,6 +144,12 @@ final class HarbormasterBuildableViewController
$buildable->getContainerHandle()->renderLink()); $buildable->getContainerHandle()->renderLink());
} }
$properties->addProperty(
pht('Origin'),
$buildable->getIsManualBuildable()
? pht('Manual Buildable')
: pht('Automatic Buildable'));
} }
} }

View file

@ -0,0 +1,103 @@
<?php
final class HarbormasterPlanRunController
extends HarbormasterController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$this->requireApplicationCapability(
HarbormasterCapabilityManagePlans::CAPABILITY);
$plan_id = $this->id;
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($plan_id))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
$e_name = true;
$v_name = null;
$errors = array();
if ($request->isFormPost()) {
$buildable = HarbormasterBuildable::initializeNewBuildable($viewer)
->setIsManualBuildable(true);
$v_name = $request->getStr('buildablePHID');
if ($v_name) {
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($v_name))
->executeOne();
if ($object instanceof DifferentialRevision) {
$revision = $object;
$object = $object->loadActiveDiff();
$buildable
->setBuildablePHID($object->getPHID())
->setContainerPHID($revision->getPHID());
} else if ($object instanceof PhabricatorRepositoryCommit) {
$buildable
->setBuildablePHID($object->getPHID())
->setContainerPHID($object->getRepository()->getPHID());
} else {
$e_name = pht('Invalid');
$errors[] = pht('Enter the name of a revision or commit.');
}
} else {
$e_name = pht('Required');
$errors[] = pht('You must choose a revision or commit to build.');
}
if (!$errors) {
$buildable->save();
$buildable_uri = '/B'.$buildable->getID();
return id(new AphrontRedirectResponse())->setURI($buildable_uri);
}
}
if ($errors) {
$errors = id(new AphrontErrorView())->setErrors($errors);
}
$title = pht('Run Build Plan Manually');
$cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
$save_button = pht('Run Plan Manually');
$form = id(new PHUIFormLayoutView())
->setUser($viewer)
->appendRemarkupInstructions(
pht(
"Enter the name of a commit or revision to run this plan on.\n\n".
"For example: `rX123456` or `D123`"))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Buildable Name')
->setName('buildablePHID')
->setError($e_name)
->setValue($v_name));
$dialog = id(new AphrontDialogView())
->setWidth(AphrontDialogView::WIDTH_FORM)
->setUser($viewer)
->setTitle($title)
->appendChild($form)
->addCancelButton($cancel_uri)
->addSubmitButton($save_button);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -200,10 +200,18 @@ final class HarbormasterPlanViewController
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Add Build Step')) ->setName(pht('Add Build Step'))
->setHref($this->getApplicationURI("step/add/{$id}/")) ->setHref($this->getApplicationURI("step/add/{$id}/"))
->setWorkflow($can_edit) ->setWorkflow(true)
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setIcon('new')); ->setIcon('new'));
$list->addAction(
id(new PhabricatorActionView())
->setName(pht('Run Plan Manually'))
->setHref($this->getApplicationURI("plan/run/{$id}/"))
->setWorkflow(true)
->setDisabled(!$can_edit)
->setIcon('start-sandcastle'));
return $list; return $list;
} }

View file

@ -7,6 +7,7 @@ final class HarbormasterBuildableQuery
private $phids; private $phids;
private $buildablePHIDs; private $buildablePHIDs;
private $containerPHIDs; private $containerPHIDs;
private $manualBuildables;
private $needContainerObjects; private $needContainerObjects;
private $needContainerHandles; private $needContainerHandles;
@ -33,6 +34,11 @@ final class HarbormasterBuildableQuery
return $this; return $this;
} }
public function withManualBuildables($manual) {
$this->manualBuildables = $manual;
return $this;
}
public function needContainerObjects($need) { public function needContainerObjects($need) {
$this->needContainerObjects = $need; $this->needContainerObjects = $need;
return $this; return $this;
@ -197,6 +203,13 @@ final class HarbormasterBuildableQuery
$this->containerPHIDs); $this->containerPHIDs);
} }
if ($this->manualBuildables !== null) {
$where[] = qsprintf(
$conn_r,
'isManualBuildable = %d',
(int)$this->manualBuildables);
}
$where[] = $this->buildPagingClause($conn_r); $where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where); return $this->formatWhereClause($where);

View file

@ -42,6 +42,9 @@ final class HarbormasterBuildableSearchEngine
$buildable_phids = array_merge($commits, $diffs); $buildable_phids = array_merge($commits, $diffs);
$saved->setParameter('buildablePHIDs', $buildable_phids); $saved->setParameter('buildablePHIDs', $buildable_phids);
$saved->setParameter(
'manual',
$this->readBoolFromRequest($request, 'manual'));
return $saved; return $saved;
} }
@ -63,6 +66,11 @@ final class HarbormasterBuildableSearchEngine
$query->withBuildablePHIDs($buildable_phids); $query->withBuildablePHIDs($buildable_phids);
} }
$manual = $saved->getParameter('manual');
if ($manual !== null) {
$query->withManualBuildables($manual);
}
return $query; return $query;
} }
@ -126,7 +134,18 @@ final class HarbormasterBuildableSearchEngine
id(new AphrontFormTextControl()) id(new AphrontFormTextControl())
->setLabel(pht('Commits')) ->setLabel(pht('Commits'))
->setName('commits') ->setName('commits')
->setValue(implode(', ', $commit_names))); ->setValue(implode(', ', $commit_names)))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Origin'))
->setName('manual')
->setValue($this->getBoolFromQuery($saved_query, 'manual'))
->setOptions(
array(
'' => pht('(All Origins)'),
'true' => pht('Manual Buildables'),
'false' => pht('Automatic Buildables'),
)));
} }
protected function getURI($path) { protected function getURI($path) {

View file

@ -102,6 +102,7 @@ final class WaitForPreviousBuildStepImplementation
$buildables = id(new HarbormasterBuildableQuery()) $buildables = id(new HarbormasterBuildableQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildablePHIDs($build_objects) ->withBuildablePHIDs($build_objects)
->withManualBuildables(false)
->execute(); ->execute();
$buildable_phids = mpull($buildables, 'getPHID'); $buildable_phids = mpull($buildables, 'getPHID');

View file

@ -7,6 +7,7 @@ final class HarbormasterBuildable extends HarbormasterDAO
protected $containerPHID; protected $containerPHID;
protected $buildStatus; protected $buildStatus;
protected $buildableStatus; protected $buildableStatus;
protected $isManualBuildable;
private $buildableObject = self::ATTACHABLE; private $buildableObject = self::ATTACHABLE;
private $containerObject = self::ATTACHABLE; private $containerObject = self::ATTACHABLE;
@ -18,6 +19,7 @@ final class HarbormasterBuildable extends HarbormasterDAO
public static function initializeNewBuildable(PhabricatorUser $actor) { public static function initializeNewBuildable(PhabricatorUser $actor) {
return id(new HarbormasterBuildable()) return id(new HarbormasterBuildable())
->setIsManualBuildable(0)
->setBuildStatus(self::STATUS_WHATEVER) ->setBuildStatus(self::STATUS_WHATEVER)
->setBuildableStatus(self::STATUS_WHATEVER); ->setBuildableStatus(self::STATUS_WHATEVER);
} }
@ -34,6 +36,7 @@ final class HarbormasterBuildable extends HarbormasterDAO
$buildable = id(new HarbormasterBuildableQuery()) $buildable = id(new HarbormasterBuildableQuery())
->setViewer($actor) ->setViewer($actor)
->withBuildablePHIDs(array($buildable_object_phid)) ->withBuildablePHIDs(array($buildable_object_phid))
->withManualBuildables(false)
->setLimit(1) ->setLimit(1)
->executeOne(); ->executeOne();
if ($buildable) { if ($buildable) {

View file

@ -1848,6 +1848,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('20131219.pxdrop.sql'), 'name' => $this->getPatchPath('20131219.pxdrop.sql'),
), ),
'20131224.harbormanual.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131224.harbormanual.sql'),
),
); );
} }
} }