@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

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

+273 -317
+21
resources/sql/autopatches/20140321.harbor.1.bxaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildsteptransaction ( 2 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 7 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 8 + commentPHID VARCHAR(64) COLLATE utf8_bin, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, 11 + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, 12 + newValue LONGTEXT NOT NULL COLLATE utf8_bin, 13 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 14 + metadata LONGTEXT NOT NULL COLLATE utf8_bin, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + 18 + UNIQUE KEY `key_phid` (phid), 19 + KEY `key_object` (objectPHID) 20 + 21 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+15
src/__phutil_library_map__.php
··· 712 712 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php', 713 713 'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php', 714 714 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php', 715 + 'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php', 716 + 'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php', 717 + 'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php', 715 718 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 719 + 'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php', 720 + 'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php', 716 721 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 717 722 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', 718 723 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', ··· 3310 3315 array( 3311 3316 0 => 'HarbormasterDAO', 3312 3317 1 => 'PhabricatorPolicyInterface', 3318 + 2 => 'PhabricatorCustomFieldInterface', 3313 3319 ), 3320 + 'HarbormasterBuildStepCoreCustomField' => 3321 + array( 3322 + 0 => 'HarbormasterBuildStepCustomField', 3323 + 1 => 'PhabricatorStandardCustomFieldInterface', 3324 + ), 3325 + 'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField', 3326 + 'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor', 3314 3327 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3328 + 'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction', 3329 + 'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3315 3330 'HarbormasterBuildTarget' => 3316 3331 array( 3317 3332 0 => 'HarbormasterDAO',
+33 -92
src/applications/harbormaster/controller/HarbormasterStepEditController.php
··· 27 27 $plan = $step->getBuildPlan(); 28 28 29 29 $implementation = $step->getStepImplementation(); 30 - $implementation->validateSettingDefinitions(); 31 - $settings = $implementation->getSettings(); 30 + 31 + $field_list = PhabricatorCustomField::getObjectFields( 32 + $step, 33 + PhabricatorCustomField::ROLE_EDIT); 34 + $field_list 35 + ->setViewer($viewer) 36 + ->readFieldsFromStorage($step); 32 37 33 38 $errors = array(); 39 + $validation_exception = null; 34 40 if ($request->isFormPost()) { 35 - foreach ($implementation->getSettingDefinitions() as $name => $opt) { 36 - $readable_name = $this->getReadableName($name, $opt); 37 - $value = $this->getValueFromRequest($request, $name, $opt['type']); 41 + $xactions = $field_list->buildFieldTransactionsFromRequest( 42 + new HarbormasterBuildStepTransaction(), 43 + $request); 38 44 39 - // TODO: This won't catch any validation issues unless the field 40 - // is missing completely. How should we check if the user is 41 - // required to enter an integer? 42 - if ($value === null) { 43 - $errors[] = $readable_name.' is not valid.'; 44 - } else { 45 - $step->setDetail($name, $value); 46 - } 47 - } 45 + $editor = id(new HarbormasterBuildStepEditor()) 46 + ->setActor($viewer) 47 + ->setContinueOnNoEffect(true) 48 + ->setContentSourceFromRequest($request); 48 49 49 - if (!$errors) { 50 - $step->save(); 50 + try { 51 + $editor->applyTransactions($step, $xactions); 51 52 return id(new AphrontRedirectResponse()) 52 53 ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/')); 54 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 55 + $validation_exception = $ex; 53 56 } 54 57 } 55 58 56 59 $form = id(new AphrontFormView()) 57 60 ->setUser($viewer); 58 61 59 - // We need to render out all of the fields for the settings that 60 - // the implementation has. 61 - foreach ($implementation->getSettingDefinitions() as $name => $opt) { 62 - if ($request->isFormPost()) { 63 - $value = $this->getValueFromRequest($request, $name, $opt['type']); 64 - } else { 65 - $value = $settings[$name]; 66 - } 67 - 68 - switch ($opt['type']) { 69 - case BuildStepImplementation::SETTING_TYPE_STRING: 70 - case BuildStepImplementation::SETTING_TYPE_INTEGER: 71 - $control = id(new AphrontFormTextControl()) 72 - ->setLabel($this->getReadableName($name, $opt)) 73 - ->setName($name) 74 - ->setValue($value); 75 - break; 76 - case BuildStepImplementation::SETTING_TYPE_BOOLEAN: 77 - $control = id(new AphrontFormCheckboxControl()) 78 - ->setLabel($this->getReadableName($name, $opt)) 79 - ->setName($name) 80 - ->setValue($value); 81 - break; 82 - case BuildStepImplementation::SETTING_TYPE_ARTIFACT: 83 - $filter = $opt['artifact_type']; 84 - $available_artifacts = 85 - BuildStepImplementation::loadAvailableArtifacts( 86 - $plan, 87 - $step, 88 - $filter); 89 - $options = array(); 90 - foreach ($available_artifacts as $key => $type) { 91 - $options[$key] = $key; 92 - } 93 - $control = id(new AphrontFormSelectControl()) 94 - ->setLabel($this->getReadableName($name, $opt)) 95 - ->setName($name) 96 - ->setValue($value) 97 - ->setOptions($options); 98 - break; 99 - default: 100 - throw new Exception("Unable to render field with unknown type."); 101 - } 102 - 103 - if (isset($opt['description'])) { 104 - $control->setCaption($opt['description']); 105 - } 106 - 107 - $form->appendChild($control); 108 - } 62 + $field_list->appendFieldsToForm($form); 109 63 110 64 $form->appendChild( 111 65 id(new AphrontFormSubmitControl()) ··· 115 69 116 70 $box = id(new PHUIObjectBoxView()) 117 71 ->setHeaderText('Edit Step: '.$implementation->getName()) 118 - ->setValidationException(null) 72 + ->setValidationException($validation_exception) 119 73 ->setForm($form); 120 74 121 75 $crumbs = $this->buildApplicationCrumbs(); ··· 127 81 128 82 $variables = $this->renderBuildVariablesTable(); 129 83 84 + $xactions = id(new HarbormasterBuildStepTransactionQuery()) 85 + ->setViewer($viewer) 86 + ->withObjectPHIDs(array($step->getPHID())) 87 + ->execute(); 88 + 89 + $xaction_view = id(new PhabricatorApplicationTransactionView()) 90 + ->setUser($viewer) 91 + ->setObjectPHID($step->getPHID()) 92 + ->setTransactions($xactions) 93 + ->setShouldTerminate(true); 94 + 130 95 return $this->buildApplicationPage( 131 96 array( 132 97 $crumbs, 133 98 $box, 134 99 $variables, 100 + $xaction_view, 135 101 ), 136 102 array( 137 103 'title' => $implementation->getName(), 138 104 'device' => true, 139 105 )); 140 - } 141 - 142 - public function getReadableName($name, $opt) { 143 - $readable_name = $name; 144 - if (isset($opt['name'])) { 145 - $readable_name = $opt['name']; 146 - } 147 - return $readable_name; 148 - } 149 - 150 - public function getValueFromRequest(AphrontRequest $request, $name, $type) { 151 - switch ($type) { 152 - case BuildStepImplementation::SETTING_TYPE_STRING: 153 - case BuildStepImplementation::SETTING_TYPE_ARTIFACT: 154 - return $request->getStr($name); 155 - break; 156 - case BuildStepImplementation::SETTING_TYPE_INTEGER: 157 - return $request->getInt($name); 158 - break; 159 - case BuildStepImplementation::SETTING_TYPE_BOOLEAN: 160 - return $request->getBool($name); 161 - break; 162 - default: 163 - throw new Exception("Unsupported setting type '".$type."'."); 164 - } 165 106 } 166 107 167 108 private function renderBuildVariablesTable() {
+41
src/applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildStepCoreCustomField 4 + extends HarbormasterBuildStepCustomField 5 + implements PhabricatorStandardCustomFieldInterface { 6 + 7 + public function getStandardCustomFieldNamespace() { 8 + return 'harbormaster:core'; 9 + } 10 + 11 + public function createFields($object) { 12 + $specs = $object->getStepImplementation()->getFieldSpecifications(); 13 + return PhabricatorStandardCustomField::buildStandardFields($this, $specs); 14 + } 15 + 16 + public function shouldUseStorage() { 17 + return false; 18 + } 19 + 20 + public function readValueFromObject(PhabricatorCustomFieldInterface $object) { 21 + $key = $this->getProxy()->getRawStandardFieldKey(); 22 + $this->setValueFromStorage($object->getDetail($key)); 23 + } 24 + 25 + public function applyApplicationTransactionInternalEffects( 26 + PhabricatorApplicationTransaction $xaction) { 27 + $object = $this->getObject(); 28 + $key = $this->getProxy()->getRawStandardFieldKey(); 29 + 30 + $this->setValueFromApplicationTransactions($xaction->getNewValue()); 31 + $value = $this->getValueForStorage(); 32 + 33 + $object->setDetail($key, $value); 34 + } 35 + 36 + public function applyApplicationTransactionExternalEffects( 37 + PhabricatorApplicationTransaction $xaction) { 38 + return; 39 + } 40 + 41 + }
+6
src/applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php
··· 1 + <?php 2 + 3 + abstract class HarbormasterBuildStepCustomField 4 + extends PhabricatorCustomField { 5 + 6 + }
+6
src/applications/harbormaster/editor/HarbormasterBuildStepEditor.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildStepEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + }
+10
src/applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildStepTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new HarbormasterBuildStepTransaction(); 8 + } 9 + 10 + }
+8 -42
src/applications/harbormaster/step/BuildStepImplementation.php
··· 2 2 3 3 abstract class BuildStepImplementation { 4 4 5 - private $settings; 6 - 7 - const SETTING_TYPE_STRING = 'string'; 8 - const SETTING_TYPE_INTEGER = 'integer'; 9 - const SETTING_TYPE_BOOLEAN = 'boolean'; 10 - const SETTING_TYPE_ARTIFACT = 'artifact'; 11 - 12 5 public static function getImplementations() { 13 6 $symbols = id(new PhutilSymbolLoader()) 14 - ->setAncestorClass("BuildStepImplementation") 7 + ->setAncestorClass('BuildStepImplementation') 15 8 ->setConcreteOnly(true) 16 9 ->selectAndLoadSymbols(); 17 10 return ipull($symbols, 'name'); ··· 33 26 * The description of the implementation, based on the current settings. 34 27 */ 35 28 public function getDescription() { 36 - return ''; 29 + return $this->getGenericDescription(); 37 30 } 38 31 39 32 /** ··· 55 48 } 56 49 57 50 /** 58 - * Validate the current settings of this build step. 59 - */ 60 - public function validateSettings() { 61 - return true; 62 - } 63 - 64 - /** 65 51 * Loads the settings for this build step implementation from a build 66 52 * step or target. 67 53 */ 68 54 public final function loadSettings($build_object) { 69 - $this->settings = array(); 70 - $this->validateSettingDefinitions(); 71 - foreach ($this->getSettingDefinitions() as $name => $opt) { 72 - $this->settings[$name] = $build_object->getDetail($name); 73 - } 74 - return $this->settings; 75 - } 76 - 77 - /** 78 - * Validates that the setting definitions for this implementation are valid. 79 - */ 80 - public final function validateSettingDefinitions() { 81 - foreach ($this->getSettingDefinitions() as $name => $opt) { 82 - if (!isset($opt['type'])) { 83 - throw new Exception( 84 - 'Setting definition \''.$name. 85 - '\' is missing type requirement.'); 86 - } 87 - } 88 - } 89 - 90 - /** 91 - * Return an array of settings for this step implementation. 92 - */ 93 - public function getSettingDefinitions() { 94 - return array(); 55 + $this->settings = $build_object->getDetails(); 56 + return $this; 95 57 } 96 58 97 59 /** ··· 195 157 $pattern = preg_replace($regexp, '%s', $pattern); 196 158 197 159 return call_user_func($function, $pattern, $argv); 160 + } 161 + 162 + public function getFieldSpecifications() { 163 + return array(); 198 164 } 199 165 200 166 }
+10 -26
src/applications/harbormaster/step/CommandBuildStepImplementation.php
··· 74 74 } 75 75 } 76 76 77 - public function validateSettings() { 78 - $settings = $this->getSettings(); 79 - 80 - if ($settings['command'] === null || !is_string($settings['command'])) { 81 - return false; 82 - } 83 - if ($settings['hostartifact'] === null || 84 - !is_string($settings['hostartifact'])) { 85 - return false; 86 - } 87 - 88 - // TODO: Check if the host artifact is provided by previous build steps. 89 - 90 - return true; 91 - } 92 - 93 77 public function getArtifactInputs() { 94 78 return array( 95 79 array( ··· 100 84 ); 101 85 } 102 86 103 - public function getSettingDefinitions() { 87 + public function getFieldSpecifications() { 104 88 return array( 105 89 'command' => array( 106 - 'name' => 'Command', 107 - 'description' => 'The command to execute on the remote machine.', 108 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 90 + 'name' => pht('Command'), 91 + 'type' => 'text', 92 + 'required' => true, 93 + ), 109 94 'hostartifact' => array( 110 - 'name' => 'Host Artifact', 111 - 'description' => 112 - 'The host artifact that determines what machine the command '. 113 - 'will run on.', 114 - 'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, 115 - 'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST)); 95 + 'name' => pht('Host'), 96 + 'type' => 'text', 97 + 'required' => true, 98 + ), 99 + ); 116 100 } 117 101 118 102 }
+8 -36
src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php
··· 34 34 $log_body = $build->createLog($build_target, $uri, 'http-body'); 35 35 $start = $log_body->start(); 36 36 37 - $method = 'POST'; 38 - if ($settings['method'] !== '') { 39 - $method = $settings['method']; 40 - } 37 + $method = nonempty(idx($settings, 'method'), 'POST'); 41 38 42 39 list($status, $body, $headers) = id(new HTTPSFuture($uri)) 43 40 ->setMethod($method) ··· 52 49 } 53 50 } 54 51 55 - public function validateSettings() { 56 - $settings = $this->getSettings(); 57 - 58 - if ($settings['uri'] === null || !is_string($settings['uri'])) { 59 - return false; 60 - } 61 - 62 - $methods = array( 63 - 'GET' => true, 64 - 'POST' => true, 65 - 'DELETE' => true, 66 - 'PUT' => true, 67 - ); 68 - 69 - $method = idx($settings, 'method'); 70 - if (strlen($method)) { 71 - if (empty($methods[$method])) { 72 - return false; 73 - } 74 - } 75 - 76 - return true; 77 - } 78 - 79 - public function getSettingDefinitions() { 52 + public function getFieldSpecifications() { 80 53 return array( 81 54 'uri' => array( 82 - 'name' => 'URI', 83 - 'description' => pht('The URI to request.'), 84 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING, 55 + 'name' => pht('URI'), 56 + 'type' => 'text', 57 + 'required' => true, 85 58 ), 86 59 'method' => array( 87 - 'name' => 'Method', 88 - 'description' => 89 - pht('Request type. Should be GET, POST, PUT, or DELETE.'), 90 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING, 60 + 'name' => pht('HTTP Method'), 61 + 'type' => 'select', 62 + 'options' => array_fuse(array('POST', 'GET', 'PUT', 'DELETE')), 91 63 ), 92 64 ); 93 65 }
+10 -21
src/applications/harbormaster/step/LeaseHostBuildStepImplementation.php
··· 51 51 $artifact->save(); 52 52 } 53 53 54 - public function validateSettings() { 55 - $settings = $this->getSettings(); 56 - 57 - if ($settings['name'] === null || !is_string($settings['name'])) { 58 - return false; 59 - } 60 - if ($settings['platform'] === null || !is_string($settings['platform'])) { 61 - return false; 62 - } 63 - 64 - return true; 65 - } 66 - 67 54 public function getArtifactOutputs() { 68 55 return array( 69 56 array( ··· 74 61 ); 75 62 } 76 63 77 - public function getSettingDefinitions() { 64 + public function getFieldSpecifications() { 78 65 return array( 79 66 'name' => array( 80 - 'name' => 'Artifact Name', 81 - 'description' => 82 - 'The name of the artifact to reference in future build steps.', 83 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 67 + 'name' => pht('Artifact Name'), 68 + 'type' => 'text', 69 + 'required' => true, 70 + ), 84 71 'platform' => array( 85 - 'name' => 'Platform', 86 - 'description' => 'The platform of the host.', 87 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING)); 72 + 'name' => pht('Platform'), 73 + 'type' => 'text', 74 + 'required' => true, 75 + ), 76 + ); 88 77 } 89 78 90 79 }
+10 -26
src/applications/harbormaster/step/PublishFragmentBuildStepImplementation.php
··· 57 57 } 58 58 } 59 59 60 - public function validateSettings() { 61 - $settings = $this->getSettings(); 62 - 63 - if ($settings['path'] === null || !is_string($settings['path'])) { 64 - return false; 65 - } 66 - if ($settings['artifact'] === null || 67 - !is_string($settings['artifact'])) { 68 - return false; 69 - } 70 - 71 - // TODO: Check if the file artifact is provided by previous build steps. 72 - 73 - return true; 74 - } 75 - 76 60 public function getArtifactInputs() { 77 61 return array( 78 62 array( ··· 83 67 ); 84 68 } 85 69 86 - public function getSettingDefinitions() { 70 + public function getFieldSpecifications() { 87 71 return array( 88 72 'path' => array( 89 - 'name' => 'Path', 90 - 'description' => 91 - 'The path of the fragment that will be published.', 92 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 73 + 'name' => pht('Path'), 74 + 'type' => 'text', 75 + 'required' => true, 76 + ), 93 77 'artifact' => array( 94 - 'name' => 'File Artifact', 95 - 'description' => 96 - 'The file artifact that will be published to Phragment.', 97 - 'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, 98 - 'artifact_type' => HarbormasterBuildArtifact::TYPE_FILE)); 78 + 'name' => pht('File Artifact'), 79 + 'type' => 'text', 80 + 'required' => true, 81 + ), 82 + ); 99 83 } 100 84 101 85 }
+7 -16
src/applications/harbormaster/step/SleepBuildStepImplementation.php
··· 25 25 sleep($settings['seconds']); 26 26 } 27 27 28 - public function validateSettings() { 29 - $settings = $this->getSettings(); 30 - 31 - if ($settings['seconds'] === null) { 32 - return false; 33 - } 34 - if (!is_int($settings['seconds'])) { 35 - return false; 36 - } 37 - return true; 38 - } 39 - 40 - public function getSettingDefinitions() { 28 + public function getFieldSpecifications() { 41 29 return array( 42 30 'seconds' => array( 43 - 'name' => 'Seconds', 44 - 'description' => 'The number of seconds to sleep for.', 45 - 'type' => BuildStepImplementation::SETTING_TYPE_INTEGER)); 31 + 'name' => pht('Seconds'), 32 + 'type' => 'int', 33 + 'required' => true, 34 + 'caption' => pht('The number of seconds to sleep for.'), 35 + ), 36 + ); 46 37 } 47 38 48 39 }
+14 -37
src/applications/harbormaster/step/UploadArtifactBuildStepImplementation.php
··· 51 51 $artifact->save(); 52 52 } 53 53 54 - public function validateSettings() { 55 - $settings = $this->getSettings(); 56 - 57 - if ($settings['path'] === null || !is_string($settings['path'])) { 58 - return false; 59 - } 60 - if ($settings['name'] === null || !is_string($settings['name'])) { 61 - return false; 62 - } 63 - if ($settings['hostartifact'] === null || 64 - !is_string($settings['hostartifact'])) { 65 - return false; 66 - } 67 - 68 - // TODO: Check if the host artifact is provided by previous build steps. 69 - 70 - return true; 71 - } 72 - 73 54 public function getArtifactInputs() { 74 55 return array( 75 56 array( ··· 90 71 ); 91 72 } 92 73 93 - public function getSettingDefinitions() { 74 + public function getFieldSpecifications() { 94 75 return array( 95 76 'path' => array( 96 - 'name' => 'Path', 97 - 'description' => 98 - 'The path of the file that should be retrieved. Note that on '. 99 - 'Windows machines running FreeSSHD, this path will be relative '. 100 - 'to the SFTP root path (configured under the SFTP tab). You can '. 101 - 'not specify an absolute path for Windows machines.', 102 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 77 + 'name' => pht('Path'), 78 + 'type' => 'text', 79 + 'required' => true, 80 + ), 103 81 'name' => array( 104 - 'name' => 'Local Name', 105 - 'description' => 106 - 'The name for the file when it is stored in Phabricator.', 107 - 'type' => BuildStepImplementation::SETTING_TYPE_STRING), 82 + 'name' => pht('Local Name'), 83 + 'type' => 'text', 84 + 'required' => true, 85 + ), 108 86 'hostartifact' => array( 109 - 'name' => 'Host Artifact', 110 - 'description' => 111 - 'The host artifact that determines what machine the command '. 112 - 'will run on.', 113 - 'type' => BuildStepImplementation::SETTING_TYPE_ARTIFACT, 114 - 'artifact_type' => HarbormasterBuildArtifact::TYPE_HOST)); 87 + 'name' => pht('Host Artifact'), 88 + 'type' => 'text', 89 + 'required' => true, 90 + ), 91 + ); 115 92 } 116 93 117 94 }
+25 -1
src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
··· 1 1 <?php 2 2 3 3 final class HarbormasterBuildStep extends HarbormasterDAO 4 - implements PhabricatorPolicyInterface { 4 + implements 5 + PhabricatorPolicyInterface, 6 + PhabricatorCustomFieldInterface { 5 7 6 8 protected $buildPlanPHID; 7 9 protected $className; ··· 9 11 protected $sequence; 10 12 11 13 private $buildPlan = self::ATTACHABLE; 14 + private $customFields = self::ATTACHABLE; 12 15 13 16 public function getConfiguration() { 14 17 return array( ··· 83 86 public function describeAutomaticCapability($capability) { 84 87 return pht('A build step has the same policies as its build plan.'); 85 88 } 89 + 90 + /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 91 + 92 + public function getCustomFieldSpecificationForRole($role) { 93 + return array(); 94 + } 95 + 96 + public function getCustomFieldBaseClass() { 97 + return 'HarbormasterBuildStepCustomField'; 98 + } 99 + 100 + public function getCustomFields() { 101 + return $this->assertAttached($this->customFields); 102 + } 103 + 104 + public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 105 + $this->customFields = $fields; 106 + return $this; 107 + } 108 + 109 + 86 110 }
+14
src/applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php
··· 1 + <?php 2 + 3 + final class HarbormasterBuildStepTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + public function getApplicationName() { 7 + return 'harbormaster'; 8 + } 9 + 10 + public function getApplicationTransactionType() { 11 + return HarbormasterPHIDTypeBuildStep::TYPECONST; 12 + } 13 + 14 + }
+3 -8
src/applications/harbormaster/worker/HarbormasterTargetWorker.php
··· 37 37 38 38 try { 39 39 $implementation = $target->getImplementation(); 40 - if (!$implementation->validateSettings()) { 41 - $target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED); 42 - $target->save(); 43 - } else { 44 - $implementation->execute($build, $target); 45 - $target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED); 46 - $target->save(); 47 - } 40 + $implementation->execute($build, $target); 41 + $target->setTargetStatus(HarbormasterBuildTarget::STATUS_PASSED); 42 + $target->save(); 48 43 } catch (Exception $ex) { 49 44 phlog($ex); 50 45
+1 -1
src/applications/maniphest/field/ManiphestConfiguredCustomField.php
··· 8 8 return 'maniphest'; 9 9 } 10 10 11 - public function createFields() { 11 + public function createFields($object) { 12 12 $config = PhabricatorEnv::getEnvConfig( 13 13 'maniphest.custom-field-definitions', 14 14 array());
+1 -1
src/applications/people/customfield/PhabricatorUserConfiguredCustomField.php
··· 8 8 return 'user'; 9 9 } 10 10 11 - public function createFields() { 11 + public function createFields($object) { 12 12 return PhabricatorStandardCustomField::buildStandardFields( 13 13 $this, 14 14 PhabricatorEnv::getEnvConfig('user.custom-field-definitions', array()));
+1 -1
src/applications/project/customfield/PhabricatorProjectConfiguredCustomField.php
··· 8 8 return 'project'; 9 9 } 10 10 11 - public function createFields() { 11 + public function createFields($object) { 12 12 return PhabricatorStandardCustomField::buildStandardFields( 13 13 $this, 14 14 PhabricatorEnv::getEnvConfig(
+1 -1
src/applications/project/customfield/PhabricatorProjectDescriptionField.php
··· 3 3 final class PhabricatorProjectDescriptionField 4 4 extends PhabricatorProjectStandardCustomField { 5 5 6 - public function createFields() { 6 + public function createFields($object) { 7 7 return PhabricatorStandardCustomField::buildStandardFields( 8 8 $this, 9 9 array(
+6 -1
src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php
··· 43 43 foreach ($faux_spec as $key => $spec) { 44 44 unset($faux_spec[$key]['disabled']); 45 45 } 46 + 47 + // TODO: We might need to build a real object here eventually. 48 + $faux_object = null; 49 + 46 50 $fields = PhabricatorCustomField::buildFieldList( 47 51 $field_base_class, 48 - $faux_spec); 52 + $faux_spec, 53 + $faux_object); 49 54 50 55 $list_id = celerity_generate_unique_node_id(); 51 56 $input_id = celerity_generate_unique_node_id();
+11 -7
src/infrastructure/customfield/field/PhabricatorCustomField.php
··· 60 60 "object of class '{$obj_class}'."); 61 61 } 62 62 63 - $fields = PhabricatorCustomField::buildFieldList($base_class, $spec); 63 + $fields = PhabricatorCustomField::buildFieldList( 64 + $base_class, 65 + $spec, 66 + $object); 64 67 65 68 foreach ($fields as $key => $field) { 66 69 if (!$field->shouldEnableForRole($role)) { ··· 97 100 /** 98 101 * @task apps 99 102 */ 100 - public static function buildFieldList($base_class, array $spec) { 103 + public static function buildFieldList($base_class, array $spec, $object) { 101 104 $field_objects = id(new PhutilSymbolLoader()) 102 105 ->setAncestorClass($base_class) 103 106 ->loadObjects(); ··· 106 109 $from_map = array(); 107 110 foreach ($field_objects as $field_object) { 108 111 $current_class = get_class($field_object); 109 - foreach ($field_object->createFields() as $field) { 112 + foreach ($field_object->createFields($object) as $field) { 110 113 $key = $field->getFieldKey(); 111 114 if (isset($fields[$key])) { 112 115 $original_class = $from_map[$key]; ··· 200 203 * For general implementations, the general field implementation can return 201 204 * multiple field instances here. 202 205 * 206 + * @param object The object to create fields for. 203 207 * @return list<PhabricatorCustomField> List of fields. 204 208 * @task core 205 209 */ 206 - public function createFields() { 210 + public function createFields($object) { 207 211 return array($this); 208 212 } 209 213 ··· 239 243 * @task core 240 244 */ 241 245 public function shouldEnableForRole($role) { 242 - if ($this->proxy) { 243 - return $this->proxy->shouldEnableForRole($role); 244 - } 246 + 247 + // NOTE: All of these calls proxy individually, so we don't need to 248 + // proxy this call as a whole. 245 249 246 250 switch ($role) { 247 251 case self::ROLE_APPLICATIONTRANSACTIONS:
+11
src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
··· 3 3 abstract class PhabricatorStandardCustomField 4 4 extends PhabricatorCustomField { 5 5 6 + private $rawKey; 6 7 private $fieldKey; 7 8 private $fieldName; 8 9 private $fieldValue; ··· 40 41 41 42 $template = clone $template; 42 43 $standard = id(clone $types[$type]) 44 + ->setRawStandardFieldKey($key) 43 45 ->setFieldKey($full_key) 44 46 ->setFieldConfig($value) 45 47 ->setApplicationField($template); ··· 140 142 141 143 public function getRequired() { 142 144 return $this->required; 145 + } 146 + 147 + public function setRawStandardFieldKey($raw_key) { 148 + $this->rawKey = $raw_key; 149 + return $this; 150 + } 151 + 152 + public function getRawStandardFieldKey() { 153 + return $this->rawKey; 143 154 } 144 155 145 156