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

Use ApplicationTransactions and CustomField to implement build steps

Summary:
Ref T1049. Fixes T4602. Moves all the funky field stuff to CustomField. Uses ApplicationTransactions to apply and record edits.

This makes "artifact" fields a little less nice (but still perfectly usable). With D8599, I think they're reasonable overall. We can improve this in the future.

All other field types are better (e.g., fixes weird bugs with "bool", fixes lots of weird behavior around required fields), and this gives us access to many new field types.

Test Plan:
Made a bunch of step edits. Here's an example:

{F133694}

Note that:

  - "Required" fields work correctly.
  - the transaction record is shown at the bottom of the page.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T4602, T1049

Differential Revision: https://secure.phabricator.com/D8600
This commit is contained in:
epriestley 2014-03-25 16:08:40 -07:00
parent 72337dedaf
commit a246c85c6b
24 changed files with 273 additions and 317 deletions

View file

@ -0,0 +1,21 @@
CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildsteptransaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
metadata LONGTEXT NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid),
KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE utf8_general_ci;

View file

@ -712,7 +712,12 @@ phutil_register_library_map(array(
'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php', 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php', 'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php', 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php',
'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php',
'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php',
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php',
'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php',
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
@ -3310,8 +3315,18 @@ phutil_register_library_map(array(
array( array(
0 => 'HarbormasterDAO', 0 => 'HarbormasterDAO',
1 => 'PhabricatorPolicyInterface', 1 => 'PhabricatorPolicyInterface',
2 => 'PhabricatorCustomFieldInterface',
), ),
'HarbormasterBuildStepCoreCustomField' =>
array(
0 => 'HarbormasterBuildStepCustomField',
1 => 'PhabricatorStandardCustomFieldInterface',
),
'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField',
'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction',
'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildTarget' => 'HarbormasterBuildTarget' =>
array( array(
0 => 'HarbormasterDAO', 0 => 'HarbormasterDAO',

View file

@ -27,85 +27,39 @@ final class HarbormasterStepEditController
$plan = $step->getBuildPlan(); $plan = $step->getBuildPlan();
$implementation = $step->getStepImplementation(); $implementation = $step->getStepImplementation();
$implementation->validateSettingDefinitions();
$settings = $implementation->getSettings(); $field_list = PhabricatorCustomField::getObjectFields(
$step,
PhabricatorCustomField::ROLE_EDIT);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($step);
$errors = array(); $errors = array();
$validation_exception = null;
if ($request->isFormPost()) { if ($request->isFormPost()) {
foreach ($implementation->getSettingDefinitions() as $name => $opt) { $xactions = $field_list->buildFieldTransactionsFromRequest(
$readable_name = $this->getReadableName($name, $opt); new HarbormasterBuildStepTransaction(),
$value = $this->getValueFromRequest($request, $name, $opt['type']); $request);
// TODO: This won't catch any validation issues unless the field $editor = id(new HarbormasterBuildStepEditor())
// is missing completely. How should we check if the user is ->setActor($viewer)
// required to enter an integer? ->setContinueOnNoEffect(true)
if ($value === null) { ->setContentSourceFromRequest($request);
$errors[] = $readable_name.' is not valid.';
} else {
$step->setDetail($name, $value);
}
}
if (!$errors) { try {
$step->save(); $editor->applyTransactions($step, $xactions);
return id(new AphrontRedirectResponse()) return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI('plan/'.$plan->getID().'/')); ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
} }
} }
$form = id(new AphrontFormView()) $form = id(new AphrontFormView())
->setUser($viewer); ->setUser($viewer);
// We need to render out all of the fields for the settings that $field_list->appendFieldsToForm($form);
// the implementation has.
foreach ($implementation->getSettingDefinitions() as $name => $opt) {
if ($request->isFormPost()) {
$value = $this->getValueFromRequest($request, $name, $opt['type']);
} else {
$value = $settings[$name];
}
switch ($opt['type']) {
case BuildStepImplementation::SETTING_TYPE_STRING:
case BuildStepImplementation::SETTING_TYPE_INTEGER:
$control = id(new AphrontFormTextControl())
->setLabel($this->getReadableName($name, $opt))
->setName($name)
->setValue($value);
break;
case BuildStepImplementation::SETTING_TYPE_BOOLEAN:
$control = id(new AphrontFormCheckboxControl())
->setLabel($this->getReadableName($name, $opt))
->setName($name)
->setValue($value);
break;
case BuildStepImplementation::SETTING_TYPE_ARTIFACT:
$filter = $opt['artifact_type'];
$available_artifacts =
BuildStepImplementation::loadAvailableArtifacts(
$plan,
$step,
$filter);
$options = array();
foreach ($available_artifacts as $key => $type) {
$options[$key] = $key;
}
$control = id(new AphrontFormSelectControl())
->setLabel($this->getReadableName($name, $opt))
->setName($name)
->setValue($value)
->setOptions($options);
break;
default:
throw new Exception("Unable to render field with unknown type.");
}
if (isset($opt['description'])) {
$control->setCaption($opt['description']);
}
$form->appendChild($control);
}
$form->appendChild( $form->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
@ -115,7 +69,7 @@ final class HarbormasterStepEditController
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
->setHeaderText('Edit Step: '.$implementation->getName()) ->setHeaderText('Edit Step: '.$implementation->getName())
->setValidationException(null) ->setValidationException($validation_exception)
->setForm($form); ->setForm($form);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
@ -127,11 +81,23 @@ final class HarbormasterStepEditController
$variables = $this->renderBuildVariablesTable(); $variables = $this->renderBuildVariablesTable();
$xactions = id(new HarbormasterBuildStepTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($step->getPHID()))
->execute();
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($step->getPHID())
->setTransactions($xactions)
->setShouldTerminate(true);
return $this->buildApplicationPage( return $this->buildApplicationPage(
array( array(
$crumbs, $crumbs,
$box, $box,
$variables, $variables,
$xaction_view,
), ),
array( array(
'title' => $implementation->getName(), 'title' => $implementation->getName(),
@ -139,31 +105,6 @@ final class HarbormasterStepEditController
)); ));
} }
public function getReadableName($name, $opt) {
$readable_name = $name;
if (isset($opt['name'])) {
$readable_name = $opt['name'];
}
return $readable_name;
}
public function getValueFromRequest(AphrontRequest $request, $name, $type) {
switch ($type) {
case BuildStepImplementation::SETTING_TYPE_STRING:
case BuildStepImplementation::SETTING_TYPE_ARTIFACT:
return $request->getStr($name);
break;
case BuildStepImplementation::SETTING_TYPE_INTEGER:
return $request->getInt($name);
break;
case BuildStepImplementation::SETTING_TYPE_BOOLEAN:
return $request->getBool($name);
break;
default:
throw new Exception("Unsupported setting type '".$type."'.");
}
}
private function renderBuildVariablesTable() { private function renderBuildVariablesTable() {
$viewer = $this->getRequest()->getUser(); $viewer = $this->getRequest()->getUser();

View file

@ -0,0 +1,41 @@
<?php
final class HarbormasterBuildStepCoreCustomField
extends HarbormasterBuildStepCustomField
implements PhabricatorStandardCustomFieldInterface {
public function getStandardCustomFieldNamespace() {
return 'harbormaster:core';
}
public function createFields($object) {
$specs = $object->getStepImplementation()->getFieldSpecifications();
return PhabricatorStandardCustomField::buildStandardFields($this, $specs);
}
public function shouldUseStorage() {
return false;
}
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
$key = $this->getProxy()->getRawStandardFieldKey();
$this->setValueFromStorage($object->getDetail($key));
}
public function applyApplicationTransactionInternalEffects(
PhabricatorApplicationTransaction $xaction) {
$object = $this->getObject();
$key = $this->getProxy()->getRawStandardFieldKey();
$this->setValueFromApplicationTransactions($xaction->getNewValue());
$value = $this->getValueForStorage();
$object->setDetail($key, $value);
}
public function applyApplicationTransactionExternalEffects(
PhabricatorApplicationTransaction $xaction) {
return;
}
}

View file

@ -0,0 +1,6 @@
<?php
abstract class HarbormasterBuildStepCustomField
extends PhabricatorCustomField {
}

View file

@ -0,0 +1,6 @@
<?php
final class HarbormasterBuildStepEditor
extends PhabricatorApplicationTransactionEditor {
}

View file

@ -0,0 +1,10 @@
<?php
final class HarbormasterBuildStepTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new HarbormasterBuildStepTransaction();
}
}

View file

@ -2,16 +2,9 @@
abstract class BuildStepImplementation { abstract class BuildStepImplementation {
private $settings;
const SETTING_TYPE_STRING = 'string';
const SETTING_TYPE_INTEGER = 'integer';
const SETTING_TYPE_BOOLEAN = 'boolean';
const SETTING_TYPE_ARTIFACT = 'artifact';
public static function getImplementations() { public static function getImplementations() {
$symbols = id(new PhutilSymbolLoader()) $symbols = id(new PhutilSymbolLoader())
->setAncestorClass("BuildStepImplementation") ->setAncestorClass('BuildStepImplementation')
->setConcreteOnly(true) ->setConcreteOnly(true)
->selectAndLoadSymbols(); ->selectAndLoadSymbols();
return ipull($symbols, 'name'); return ipull($symbols, 'name');
@ -33,7 +26,7 @@ abstract class BuildStepImplementation {
* The description of the implementation, based on the current settings. * The description of the implementation, based on the current settings.
*/ */
public function getDescription() { public function getDescription() {
return ''; return $this->getGenericDescription();
} }
/** /**
@ -54,44 +47,13 @@ abstract class BuildStepImplementation {
return idx($this->settings, $key, $default); return idx($this->settings, $key, $default);
} }
/**
* Validate the current settings of this build step.
*/
public function validateSettings() {
return true;
}
/** /**
* Loads the settings for this build step implementation from a build * Loads the settings for this build step implementation from a build
* step or target. * step or target.
*/ */
public final function loadSettings($build_object) { public final function loadSettings($build_object) {
$this->settings = array(); $this->settings = $build_object->getDetails();
$this->validateSettingDefinitions(); return $this;
foreach ($this->getSettingDefinitions() as $name => $opt) {
$this->settings[$name] = $build_object->getDetail($name);
}
return $this->settings;
}
/**
* Validates that the setting definitions for this implementation are valid.
*/
public final function validateSettingDefinitions() {
foreach ($this->getSettingDefinitions() as $name => $opt) {
if (!isset($opt['type'])) {
throw new Exception(
'Setting definition \''.$name.
'\' is missing type requirement.');
}
}
}
/**
* Return an array of settings for this step implementation.
*/
public function getSettingDefinitions() {
return array();
} }
/** /**
@ -197,4 +159,8 @@ abstract class BuildStepImplementation {
return call_user_func($function, $pattern, $argv); return call_user_func($function, $pattern, $argv);
} }
public function getFieldSpecifications() {
return array();
}
} }

View file

@ -74,22 +74,6 @@ final class CommandBuildStepImplementation
} }
} }
public function validateSettings() {
$settings = $this->getSettings();
if ($settings['command'] === null || !is_string($settings['command'])) {
return false;
}
if ($settings['hostartifact'] === null ||
!is_string($settings['hostartifact'])) {
return false;
}
// TODO: Check if the host artifact is provided by previous build steps.
return true;
}
public function getArtifactInputs() { public function getArtifactInputs() {
return array( return array(
array( array(
@ -100,19 +84,19 @@ final class CommandBuildStepImplementation
); );
} }
public function getSettingDefinitions() { public function getFieldSpecifications() {
return array( return array(
'command' => array( 'command' => array(
'name' => 'Command', 'name' => pht('Command'),
'description' => 'The command to execute on the remote machine.', 'type' => 'text',
'type' => BuildStepImplementation::SETTING_TYPE_STRING), 'required' => true,
),
'hostartifact' => array( 'hostartifact' => array(
'name' => 'Host Artifact', 'name' => pht('Host'),
'description' => 'type' => 'text',
'The host artifact that determines what machine the command '. 'required' => true,
'will run on.', ),
'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, );
'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST));
} }
} }

View file

@ -34,10 +34,7 @@ final class HarbormasterHTTPRequestBuildStepImplementation
$log_body = $build->createLog($build_target, $uri, 'http-body'); $log_body = $build->createLog($build_target, $uri, 'http-body');
$start = $log_body->start(); $start = $log_body->start();
$method = 'POST'; $method = nonempty(idx($settings, 'method'), 'POST');
if ($settings['method'] !== '') {
$method = $settings['method'];
}
list($status, $body, $headers) = id(new HTTPSFuture($uri)) list($status, $body, $headers) = id(new HTTPSFuture($uri))
->setMethod($method) ->setMethod($method)
@ -52,42 +49,17 @@ final class HarbormasterHTTPRequestBuildStepImplementation
} }
} }
public function validateSettings() { public function getFieldSpecifications() {
$settings = $this->getSettings();
if ($settings['uri'] === null || !is_string($settings['uri'])) {
return false;
}
$methods = array(
'GET' => true,
'POST' => true,
'DELETE' => true,
'PUT' => true,
);
$method = idx($settings, 'method');
if (strlen($method)) {
if (empty($methods[$method])) {
return false;
}
}
return true;
}
public function getSettingDefinitions() {
return array( return array(
'uri' => array( 'uri' => array(
'name' => 'URI', 'name' => pht('URI'),
'description' => pht('The URI to request.'), 'type' => 'text',
'type' => BuildStepImplementation::SETTING_TYPE_STRING, 'required' => true,
), ),
'method' => array( 'method' => array(
'name' => 'Method', 'name' => pht('HTTP Method'),
'description' => 'type' => 'select',
pht('Request type. Should be GET, POST, PUT, or DELETE.'), 'options' => array_fuse(array('POST', 'GET', 'PUT', 'DELETE')),
'type' => BuildStepImplementation::SETTING_TYPE_STRING,
), ),
); );
} }

View file

@ -51,19 +51,6 @@ final class LeaseHostBuildStepImplementation
$artifact->save(); $artifact->save();
} }
public function validateSettings() {
$settings = $this->getSettings();
if ($settings['name'] === null || !is_string($settings['name'])) {
return false;
}
if ($settings['platform'] === null || !is_string($settings['platform'])) {
return false;
}
return true;
}
public function getArtifactOutputs() { public function getArtifactOutputs() {
return array( return array(
array( array(
@ -74,17 +61,19 @@ final class LeaseHostBuildStepImplementation
); );
} }
public function getSettingDefinitions() { public function getFieldSpecifications() {
return array( return array(
'name' => array( 'name' => array(
'name' => 'Artifact Name', 'name' => pht('Artifact Name'),
'description' => 'type' => 'text',
'The name of the artifact to reference in future build steps.', 'required' => true,
'type' => BuildStepImplementation::SETTING_TYPE_STRING), ),
'platform' => array( 'platform' => array(
'name' => 'Platform', 'name' => pht('Platform'),
'description' => 'The platform of the host.', 'type' => 'text',
'type' => BuildStepImplementation::SETTING_TYPE_STRING)); 'required' => true,
),
);
} }
} }

View file

@ -57,22 +57,6 @@ final class PublishFragmentBuildStepImplementation
} }
} }
public function validateSettings() {
$settings = $this->getSettings();
if ($settings['path'] === null || !is_string($settings['path'])) {
return false;
}
if ($settings['artifact'] === null ||
!is_string($settings['artifact'])) {
return false;
}
// TODO: Check if the file artifact is provided by previous build steps.
return true;
}
public function getArtifactInputs() { public function getArtifactInputs() {
return array( return array(
array( array(
@ -83,19 +67,19 @@ final class PublishFragmentBuildStepImplementation
); );
} }
public function getSettingDefinitions() { public function getFieldSpecifications() {
return array( return array(
'path' => array( 'path' => array(
'name' => 'Path', 'name' => pht('Path'),
'description' => 'type' => 'text',
'The path of the fragment that will be published.', 'required' => true,
'type' => BuildStepImplementation::SETTING_TYPE_STRING), ),
'artifact' => array( 'artifact' => array(
'name' => 'File Artifact', 'name' => pht('File Artifact'),
'description' => 'type' => 'text',
'The file artifact that will be published to Phragment.', 'required' => true,
'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, ),
'artifact_type' => HarbormasterBuildArtifact::TYPE_FILE)); );
} }
} }

View file

@ -25,24 +25,15 @@ final class SleepBuildStepImplementation extends BuildStepImplementation {
sleep($settings['seconds']); sleep($settings['seconds']);
} }
public function validateSettings() { public function getFieldSpecifications() {
$settings = $this->getSettings();
if ($settings['seconds'] === null) {
return false;
}
if (!is_int($settings['seconds'])) {
return false;
}
return true;
}
public function getSettingDefinitions() {
return array( return array(
'seconds' => array( 'seconds' => array(
'name' => 'Seconds', 'name' => pht('Seconds'),
'description' => 'The number of seconds to sleep for.', 'type' => 'int',
'type' => BuildStepImplementation::SETTING_TYPE_INTEGER)); 'required' => true,
'caption' => pht('The number of seconds to sleep for.'),
),
);
} }
} }

View file

@ -51,25 +51,6 @@ final class UploadArtifactBuildStepImplementation
$artifact->save(); $artifact->save();
} }
public function validateSettings() {
$settings = $this->getSettings();
if ($settings['path'] === null || !is_string($settings['path'])) {
return false;
}
if ($settings['name'] === null || !is_string($settings['name'])) {
return false;
}
if ($settings['hostartifact'] === null ||
!is_string($settings['hostartifact'])) {
return false;
}
// TODO: Check if the host artifact is provided by previous build steps.
return true;
}
public function getArtifactInputs() { public function getArtifactInputs() {
return array( return array(
array( array(
@ -90,28 +71,24 @@ final class UploadArtifactBuildStepImplementation
); );
} }
public function getSettingDefinitions() { public function getFieldSpecifications() {
return array( return array(
'path' => array( 'path' => array(
'name' => 'Path', 'name' => pht('Path'),
'description' => 'type' => 'text',
'The path of the file that should be retrieved. Note that on '. 'required' => true,
'Windows machines running FreeSSHD, this path will be relative '. ),
'to the SFTP root path (configured under the SFTP tab). You can '.
'not specify an absolute path for Windows machines.',
'type' => BuildStepImplementation::SETTING_TYPE_STRING),
'name' => array( 'name' => array(
'name' => 'Local Name', 'name' => pht('Local Name'),
'description' => 'type' => 'text',
'The name for the file when it is stored in Phabricator.', 'required' => true,
'type' => BuildStepImplementation::SETTING_TYPE_STRING), ),
'hostartifact' => array( 'hostartifact' => array(
'name' => 'Host Artifact', 'name' => pht('Host Artifact'),
'description' => 'type' => 'text',
'The host artifact that determines what machine the command '. 'required' => true,
'will run on.', ),
'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, );
'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST));
} }
} }

View file

@ -1,7 +1,9 @@
<?php <?php
final class HarbormasterBuildStep extends HarbormasterDAO final class HarbormasterBuildStep extends HarbormasterDAO
implements PhabricatorPolicyInterface { implements
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface {
protected $buildPlanPHID; protected $buildPlanPHID;
protected $className; protected $className;
@ -9,6 +11,7 @@ final class HarbormasterBuildStep extends HarbormasterDAO
protected $sequence; protected $sequence;
private $buildPlan = self::ATTACHABLE; private $buildPlan = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
public function getConfiguration() { public function getConfiguration() {
return array( return array(
@ -83,4 +86,25 @@ final class HarbormasterBuildStep extends HarbormasterDAO
public function describeAutomaticCapability($capability) { public function describeAutomaticCapability($capability) {
return pht('A build step has the same policies as its build plan.'); return pht('A build step has the same policies as its build plan.');
} }
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return array();
}
public function getCustomFieldBaseClass() {
return 'HarbormasterBuildStepCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
} }

View file

@ -0,0 +1,14 @@
<?php
final class HarbormasterBuildStepTransaction
extends PhabricatorApplicationTransaction {
public function getApplicationName() {
return 'harbormaster';
}
public function getApplicationTransactionType() {
return HarbormasterPHIDTypeBuildStep::TYPECONST;
}
}

View file

@ -37,14 +37,9 @@ final class HarbormasterTargetWorker extends HarbormasterWorker {
try { try {
$implementation = $target->getImplementation(); $implementation = $target->getImplementation();
if (!$implementation->validateSettings()) {
$target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED);
$target->save();
} else {
$implementation->execute($build, $target); $implementation->execute($build, $target);
$target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED); $target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED);
$target->save(); $target->save();
}
} catch (Exception $ex) { } catch (Exception $ex) {
phlog($ex); phlog($ex);

View file

@ -8,7 +8,7 @@ final class ManiphestConfiguredCustomField
return 'maniphest'; return 'maniphest';
} }
public function createFields() { public function createFields($object) {
$config = PhabricatorEnv::getEnvConfig( $config = PhabricatorEnv::getEnvConfig(
'maniphest.custom-field-definitions', 'maniphest.custom-field-definitions',
array()); array());

View file

@ -8,7 +8,7 @@ final class PhabricatorUserConfiguredCustomField
return 'user'; return 'user';
} }
public function createFields() { public function createFields($object) {
return PhabricatorStandardCustomField::buildStandardFields( return PhabricatorStandardCustomField::buildStandardFields(
$this, $this,
PhabricatorEnv::getEnvConfig('user.custom-field-definitions', array())); PhabricatorEnv::getEnvConfig('user.custom-field-definitions', array()));

View file

@ -8,7 +8,7 @@ final class PhabricatorProjectConfiguredCustomField
return 'project'; return 'project';
} }
public function createFields() { public function createFields($object) {
return PhabricatorStandardCustomField::buildStandardFields( return PhabricatorStandardCustomField::buildStandardFields(
$this, $this,
PhabricatorEnv::getEnvConfig( PhabricatorEnv::getEnvConfig(

View file

@ -3,7 +3,7 @@
final class PhabricatorProjectDescriptionField final class PhabricatorProjectDescriptionField
extends PhabricatorProjectStandardCustomField { extends PhabricatorProjectStandardCustomField {
public function createFields() { public function createFields($object) {
return PhabricatorStandardCustomField::buildStandardFields( return PhabricatorStandardCustomField::buildStandardFields(
$this, $this,
array( array(

View file

@ -43,9 +43,14 @@ final class PhabricatorCustomFieldConfigOptionType
foreach ($faux_spec as $key => $spec) { foreach ($faux_spec as $key => $spec) {
unset($faux_spec[$key]['disabled']); unset($faux_spec[$key]['disabled']);
} }
// TODO: We might need to build a real object here eventually.
$faux_object = null;
$fields = PhabricatorCustomField::buildFieldList( $fields = PhabricatorCustomField::buildFieldList(
$field_base_class, $field_base_class,
$faux_spec); $faux_spec,
$faux_object);
$list_id = celerity_generate_unique_node_id(); $list_id = celerity_generate_unique_node_id();
$input_id = celerity_generate_unique_node_id(); $input_id = celerity_generate_unique_node_id();

View file

@ -60,7 +60,10 @@ abstract class PhabricatorCustomField {
"object of class '{$obj_class}'."); "object of class '{$obj_class}'.");
} }
$fields = PhabricatorCustomField::buildFieldList($base_class, $spec); $fields = PhabricatorCustomField::buildFieldList(
$base_class,
$spec,
$object);
foreach ($fields as $key => $field) { foreach ($fields as $key => $field) {
if (!$field->shouldEnableForRole($role)) { if (!$field->shouldEnableForRole($role)) {
@ -97,7 +100,7 @@ abstract class PhabricatorCustomField {
/** /**
* @task apps * @task apps
*/ */
public static function buildFieldList($base_class, array $spec) { public static function buildFieldList($base_class, array $spec, $object) {
$field_objects = id(new PhutilSymbolLoader()) $field_objects = id(new PhutilSymbolLoader())
->setAncestorClass($base_class) ->setAncestorClass($base_class)
->loadObjects(); ->loadObjects();
@ -106,7 +109,7 @@ abstract class PhabricatorCustomField {
$from_map = array(); $from_map = array();
foreach ($field_objects as $field_object) { foreach ($field_objects as $field_object) {
$current_class = get_class($field_object); $current_class = get_class($field_object);
foreach ($field_object->createFields() as $field) { foreach ($field_object->createFields($object) as $field) {
$key = $field->getFieldKey(); $key = $field->getFieldKey();
if (isset($fields[$key])) { if (isset($fields[$key])) {
$original_class = $from_map[$key]; $original_class = $from_map[$key];
@ -200,10 +203,11 @@ abstract class PhabricatorCustomField {
* For general implementations, the general field implementation can return * For general implementations, the general field implementation can return
* multiple field instances here. * multiple field instances here.
* *
* @param object The object to create fields for.
* @return list<PhabricatorCustomField> List of fields. * @return list<PhabricatorCustomField> List of fields.
* @task core * @task core
*/ */
public function createFields() { public function createFields($object) {
return array($this); return array($this);
} }
@ -239,9 +243,9 @@ abstract class PhabricatorCustomField {
* @task core * @task core
*/ */
public function shouldEnableForRole($role) { public function shouldEnableForRole($role) {
if ($this->proxy) {
return $this->proxy->shouldEnableForRole($role); // NOTE: All of these calls proxy individually, so we don't need to
} // proxy this call as a whole.
switch ($role) { switch ($role) {
case self::ROLE_APPLICATIONTRANSACTIONS: case self::ROLE_APPLICATIONTRANSACTIONS:

View file

@ -3,6 +3,7 @@
abstract class PhabricatorStandardCustomField abstract class PhabricatorStandardCustomField
extends PhabricatorCustomField { extends PhabricatorCustomField {
private $rawKey;
private $fieldKey; private $fieldKey;
private $fieldName; private $fieldName;
private $fieldValue; private $fieldValue;
@ -40,6 +41,7 @@ abstract class PhabricatorStandardCustomField
$template = clone $template; $template = clone $template;
$standard = id(clone $types[$type]) $standard = id(clone $types[$type])
->setRawStandardFieldKey($key)
->setFieldKey($full_key) ->setFieldKey($full_key)
->setFieldConfig($value) ->setFieldConfig($value)
->setApplicationField($template); ->setApplicationField($template);
@ -142,6 +144,15 @@ abstract class PhabricatorStandardCustomField
return $this->required; return $this->required;
} }
public function setRawStandardFieldKey($raw_key) {
$this->rawKey = $raw_key;
return $this;
}
public function getRawStandardFieldKey() {
return $this->rawKey;
}
/* -( PhabricatorCustomField )--------------------------------------------- */ /* -( PhabricatorCustomField )--------------------------------------------- */