@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

Edit Interfaces in Almanac with EditEngine

Summary:
Depends on D19323. Ref T13120. Ref T12414.

Move editing to modern stuff and fix some implementation errors from D19323 (mostly copy/paste stuff).

Test Plan:
- Created and edited interfaces.
- Tried to create/edit an interface with a bogus/empty address/port, got errors.
- Tried to create an interface on a bogus device, got an error.
- Tried to create an interface on a device I could not edit, got an error.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13120, T12414

Differential Revision: https://secure.phabricator.com/D19324

+175 -151
+6 -4
src/__phutil_library_map__.php
··· 62 62 'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php', 63 63 'AlmanacInterfaceDeviceTransaction' => 'applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php', 64 64 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', 65 + 'AlmanacInterfaceEditEngine' => 'applications/almanac/editor/AlmanacInterfaceEditEngine.php', 65 66 'AlmanacInterfaceEditor' => 'applications/almanac/editor/AlmanacInterfaceEditor.php', 66 67 'AlmanacInterfaceNetworkTransaction' => 'applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php', 67 68 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', ··· 5252 5253 'PhabricatorExtendedPolicyInterface', 5253 5254 'PhabricatorApplicationTransactionInterface', 5254 5255 ), 5255 - 'AlmanacInterfaceAddressTransaction' => 'AlmanacNetworkTransactionType', 5256 + 'AlmanacInterfaceAddressTransaction' => 'AlmanacInterfaceTransactionType', 5256 5257 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 5257 5258 'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController', 5258 - 'AlmanacInterfaceDeviceTransaction' => 'AlmanacNetworkTransactionType', 5259 + 'AlmanacInterfaceDeviceTransaction' => 'AlmanacInterfaceTransactionType', 5259 5260 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', 5261 + 'AlmanacInterfaceEditEngine' => 'PhabricatorEditEngine', 5260 5262 'AlmanacInterfaceEditor' => 'PhabricatorApplicationTransactionEditor', 5261 - 'AlmanacInterfaceNetworkTransaction' => 'AlmanacNetworkTransactionType', 5263 + 'AlmanacInterfaceNetworkTransaction' => 'AlmanacInterfaceTransactionType', 5262 5264 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', 5263 - 'AlmanacInterfacePortTransaction' => 'AlmanacNetworkTransactionType', 5265 + 'AlmanacInterfacePortTransaction' => 'AlmanacInterfaceTransactionType', 5264 5266 'AlmanacInterfaceQuery' => 'AlmanacQuery', 5265 5267 'AlmanacInterfaceTableView' => 'AphrontView', 5266 5268 'AlmanacInterfaceTransaction' => 'PhabricatorModularTransaction',
+10 -141
src/applications/almanac/controller/AlmanacInterfaceEditController.php
··· 4 4 extends AlmanacDeviceController { 5 5 6 6 public function handleRequest(AphrontRequest $request) { 7 - $viewer = $request->getViewer(); 7 + $viewer = $this->getViewer(); 8 8 9 - $id = $request->getURIData('id'); 10 - if ($id) { 11 - $interface = id(new AlmanacInterfaceQuery()) 12 - ->setViewer($viewer) 13 - ->withIDs(array($id)) 14 - ->requireCapabilities( 15 - array( 16 - PhabricatorPolicyCapability::CAN_VIEW, 17 - PhabricatorPolicyCapability::CAN_EDIT, 18 - )) 19 - ->executeOne(); 20 - if (!$interface) { 21 - return new Aphront404Response(); 22 - } 23 - 24 - $device = $interface->getDevice(); 9 + $engine = id(new AlmanacInterfaceEditEngine()) 10 + ->setController($this); 25 11 26 - $is_new = false; 27 - $title = pht('Edit Interface'); 28 - $save_button = pht('Save Changes'); 29 - } else { 12 + $id = $request->getURIData('id'); 13 + if (!$id) { 30 14 $device = id(new AlmanacDeviceQuery()) 31 15 ->setViewer($viewer) 32 - ->withIDs(array($request->getStr('deviceID'))) 16 + ->withIDs(array($request->getInt('deviceID'))) 33 17 ->requireCapabilities( 34 18 array( 35 19 PhabricatorPolicyCapability::CAN_VIEW, ··· 40 24 return new Aphront404Response(); 41 25 } 42 26 43 - $interface = AlmanacInterface::initializeNewInterface(); 44 - $is_new = true; 45 - 46 - $title = pht('Create Interface'); 47 - $save_button = pht('Create Interface'); 27 + $engine 28 + ->addContextParameter('deviceID', $device->getID()) 29 + ->setDevice($device); 48 30 } 49 31 50 - $device_uri = $device->getURI(); 51 - $cancel_uri = $device_uri; 52 - 53 - $v_network = $interface->getNetworkPHID(); 54 - 55 - $v_address = $interface->getAddress(); 56 - $e_address = true; 57 - 58 - $v_port = $interface->getPort(); 59 - 60 - $validation_exception = null; 61 - 62 - if ($request->isFormPost()) { 63 - $v_network = $request->getStr('networkPHID'); 64 - $v_address = $request->getStr('address'); 65 - $v_port = $request->getStr('port'); 66 - 67 - $type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE; 68 - 69 - $address = AlmanacAddress::newFromParts($v_network, $v_address, $v_port); 70 - 71 - $xaction = id(new AlmanacDeviceTransaction()) 72 - ->setTransactionType($type_interface) 73 - ->setNewValue($address->toDictionary()); 74 - 75 - if ($interface->getID()) { 76 - $xaction->setOldValue(array( 77 - 'id' => $interface->getID(), 78 - ) + $interface->toAddress()->toDictionary()); 79 - } else { 80 - $xaction->setOldValue(array()); 81 - } 82 - 83 - $xactions = array(); 84 - $xactions[] = $xaction; 85 - 86 - $editor = id(new AlmanacDeviceEditor()) 87 - ->setActor($viewer) 88 - ->setContentSourceFromRequest($request) 89 - ->setContinueOnNoEffect(true) 90 - ->setContinueOnMissingFields(true); 91 - 92 - try { 93 - $editor->applyTransactions($device, $xactions); 94 - 95 - $device_uri = $device->getURI(); 96 - return id(new AphrontRedirectResponse())->setURI($device_uri); 97 - } catch (PhabricatorApplicationTransactionValidationException $ex) { 98 - $validation_exception = $ex; 99 - $e_address = $ex->getShortMessage($type_interface); 100 - } 101 - } 102 - 103 - $networks = id(new AlmanacNetworkQuery()) 104 - ->setViewer($viewer) 105 - ->execute(); 106 - 107 - $form = id(new AphrontFormView()) 108 - ->setUser($viewer) 109 - ->appendChild( 110 - id(new AphrontFormSelectControl()) 111 - ->setLabel(pht('Network')) 112 - ->setName('networkPHID') 113 - ->setValue($v_network) 114 - ->setOptions(mpull($networks, 'getName', 'getPHID'))) 115 - ->appendChild( 116 - id(new AphrontFormTextControl()) 117 - ->setLabel(pht('Address')) 118 - ->setName('address') 119 - ->setValue($v_address) 120 - ->setError($e_address)) 121 - ->appendChild( 122 - id(new AphrontFormTextControl()) 123 - ->setLabel(pht('Port')) 124 - ->setName('port') 125 - ->setValue($v_port) 126 - ->setError($e_address)) 127 - ->appendChild( 128 - id(new AphrontFormSubmitControl()) 129 - ->addCancelButton($cancel_uri) 130 - ->setValue($save_button)); 131 - 132 - $box = id(new PHUIObjectBoxView()) 133 - ->setValidationException($validation_exception) 134 - ->setHeaderText(pht('Interface')) 135 - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 136 - ->setForm($form); 137 - 138 - $crumbs = $this->buildApplicationCrumbs(); 139 - $crumbs->addTextCrumb($device->getName(), $device_uri); 140 - if ($is_new) { 141 - $crumbs->addTextCrumb(pht('Create Interface')); 142 - $header = id(new PHUIHeaderView()) 143 - ->setHeader(pht('Create Interface')) 144 - ->setHeaderIcon('fa-plus-square'); 145 - } else { 146 - $crumbs->addTextCrumb(pht('Edit Interface')); 147 - $header = id(new PHUIHeaderView()) 148 - ->setHeader(pht('Edit Interface')) 149 - ->setHeaderIcon('fa-pencil'); 150 - } 151 - $crumbs->setBorder(true); 152 - 153 - $view = id(new PHUITwoColumnView()) 154 - ->setHeader($header) 155 - ->setFooter(array( 156 - $box, 157 - )); 158 - 159 - return $this->newPage() 160 - ->setTitle($title) 161 - ->setCrumbs($crumbs) 162 - ->appendChild($view); 163 - 32 + return $engine->buildResponse(); 164 33 } 165 34 166 35 }
+139
src/applications/almanac/editor/AlmanacInterfaceEditEngine.php
··· 1 + <?php 2 + 3 + final class AlmanacInterfaceEditEngine 4 + extends PhabricatorEditEngine { 5 + 6 + const ENGINECONST = 'almanac.interface'; 7 + 8 + private $device; 9 + 10 + public function setDevice(AlmanacDevice $device) { 11 + $this->device = $device; 12 + return $this; 13 + } 14 + 15 + public function getDevice() { 16 + if (!$this->device) { 17 + throw new PhutilInvalidStateException('setDevice'); 18 + } 19 + return $this->device; 20 + } 21 + 22 + public function isEngineConfigurable() { 23 + return false; 24 + } 25 + 26 + public function getEngineName() { 27 + return pht('Almanac Interfaces'); 28 + } 29 + 30 + public function getSummaryHeader() { 31 + return pht('Edit Almanac Interface Configurations'); 32 + } 33 + 34 + public function getSummaryText() { 35 + return pht('This engine is used to edit Almanac interfaces.'); 36 + } 37 + 38 + public function getEngineApplicationClass() { 39 + return 'PhabricatorAlmanacApplication'; 40 + } 41 + 42 + protected function newEditableObject() { 43 + $interface = AlmanacInterface::initializeNewInterface(); 44 + 45 + $device = $this->getDevice(); 46 + $interface 47 + ->setDevicePHID($device->getPHID()) 48 + ->attachDevice($device); 49 + 50 + return $interface; 51 + } 52 + 53 + protected function newObjectQuery() { 54 + return new AlmanacInterfaceQuery(); 55 + } 56 + 57 + protected function getObjectCreateTitleText($object) { 58 + return pht('Create Interface'); 59 + } 60 + 61 + protected function getObjectCreateButtonText($object) { 62 + return pht('Create Interface'); 63 + } 64 + 65 + protected function getObjectEditTitleText($object) { 66 + return pht('Edit Interface'); 67 + } 68 + 69 + protected function getObjectEditShortText($object) { 70 + return pht('Edit Interface'); 71 + } 72 + 73 + protected function getObjectCreateShortText() { 74 + return pht('Create Interface'); 75 + } 76 + 77 + protected function getObjectName() { 78 + return pht('Interface'); 79 + } 80 + 81 + protected function getEditorURI() { 82 + return '/almanac/interface/edit/'; 83 + } 84 + 85 + protected function getObjectCreateCancelURI($object) { 86 + return '/almanac/interface/'; 87 + } 88 + 89 + protected function getObjectViewURI($object) { 90 + return $object->getDevice()->getURI(); 91 + } 92 + 93 + protected function buildCustomEditFields($object) { 94 + $viewer = $this->getViewer(); 95 + 96 + // TODO: Some day, this should be a datasource. 97 + $networks = id(new AlmanacNetworkQuery()) 98 + ->setViewer($viewer) 99 + ->execute(); 100 + $network_map = mpull($networks, 'getName', 'getPHID'); 101 + 102 + return array( 103 + id(new PhabricatorTextEditField()) 104 + ->setKey('device') 105 + ->setLabel(pht('Device')) 106 + ->setIsConduitOnly(true) 107 + ->setTransactionType( 108 + AlmanacInterfaceDeviceTransaction::TRANSACTIONTYPE) 109 + ->setDescription(pht('When creating an interface, set the device.')) 110 + ->setConduitDescription(pht('Set the device.')) 111 + ->setConduitTypeDescription(pht('Device PHID.')) 112 + ->setValue($object->getDevicePHID()), 113 + id(new PhabricatorSelectEditField()) 114 + ->setKey('network') 115 + ->setLabel(pht('Network')) 116 + ->setDescription(pht('Network for the interface.')) 117 + ->setTransactionType( 118 + AlmanacInterfaceNetworkTransaction::TRANSACTIONTYPE) 119 + ->setValue($object->getNetworkPHID()) 120 + ->setOptions($network_map), 121 + id(new PhabricatorTextEditField()) 122 + ->setKey('address') 123 + ->setLabel(pht('Address')) 124 + ->setDescription(pht('Address of the service.')) 125 + ->setTransactionType( 126 + AlmanacInterfaceAddressTransaction::TRANSACTIONTYPE) 127 + ->setIsRequired(true) 128 + ->setValue($object->getAddress()), 129 + id(new PhabricatorTextEditField()) 130 + ->setKey('port') 131 + ->setLabel(pht('Port')) 132 + ->setDescription(pht('Port of the service.')) 133 + ->setTransactionType(AlmanacInterfacePortTransaction::TRANSACTIONTYPE) 134 + ->setIsRequired(true) 135 + ->setValue($object->getPort()), 136 + ); 137 + } 138 + 139 + }
+1 -1
src/applications/almanac/xaction/AlmanacInterfaceAddressTransaction.php
··· 1 1 <?php 2 2 3 3 final class AlmanacInterfaceAddressTransaction 4 - extends AlmanacNetworkTransactionType { 4 + extends AlmanacInterfaceTransactionType { 5 5 6 6 const TRANSACTIONTYPE = 'almanac:interface:address'; 7 7
+16 -2
src/applications/almanac/xaction/AlmanacInterfaceDeviceTransaction.php
··· 1 1 <?php 2 2 3 3 final class AlmanacInterfaceDeviceTransaction 4 - extends AlmanacNetworkTransactionType { 4 + extends AlmanacInterfaceTransactionType { 5 5 6 6 const TRANSACTIONTYPE = 'almanac:interface:device'; 7 7 ··· 24 24 public function validateTransactions($object, array $xactions) { 25 25 $errors = array(); 26 26 27 - if ($this->isEmptyTextTransaction($object->getAddress(), $xactions)) { 27 + $device_phid = $object->getDevicePHID(); 28 + if ($this->isEmptyTextTransaction($device_phid, $xactions)) { 28 29 $errors[] = $this->newRequiredError( 29 30 pht('Interfaces must have a device.')); 30 31 } ··· 50 51 'You can not attach an interface to a nonexistent or restricted '. 51 52 'device.'), 52 53 $xaction); 54 + continue; 55 + } 56 + 57 + $device = head($devices); 58 + $can_edit = PhabricatorPolicyFilter::hasCapability( 59 + $this->getActor(), 60 + $device, 61 + PhabricatorPolicyCapability::CAN_EDIT); 62 + if (!$can_edit) { 63 + $errors[] = $this->newInvalidError( 64 + pht( 65 + 'You can not attach an interface to a device which you do not '. 66 + 'have permission to edit.')); 53 67 continue; 54 68 } 55 69 }
+1 -1
src/applications/almanac/xaction/AlmanacInterfaceNetworkTransaction.php
··· 1 1 <?php 2 2 3 3 final class AlmanacInterfaceNetworkTransaction 4 - extends AlmanacNetworkTransactionType { 4 + extends AlmanacInterfaceTransactionType { 5 5 6 6 const TRANSACTIONTYPE = 'almanac:interface:network'; 7 7
+2 -2
src/applications/almanac/xaction/AlmanacInterfacePortTransaction.php
··· 1 1 <?php 2 2 3 3 final class AlmanacInterfacePortTransaction 4 - extends AlmanacNetworkTransactionType { 4 + extends AlmanacInterfaceTransactionType { 5 5 6 6 const TRANSACTIONTYPE = 'almanac:interface:port'; 7 7 ··· 30 30 public function validateTransactions($object, array $xactions) { 31 31 $errors = array(); 32 32 33 - if ($this->isEmptyTextTransaction($object->getName(), $xactions)) { 33 + if ($this->isEmptyTextTransaction($object->getPort(), $xactions)) { 34 34 $errors[] = $this->newRequiredError( 35 35 pht('Interfaces must have a port number.')); 36 36 }