@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

Add a "status" property to Almanac devices

Summary:
Ref T13641. Add a "status" property with most of the relevant support code.

This currently has no impact on use of the device or bindings by Diffusion or Drydock: they ignore the status of devices bound to services.

Test Plan:
- Created a new device.
- Changed the status of a device via web and API.
- Queried for devices via API.
- Searched for active and disabled devices.
- Viewed UI in list view, detail view.
- Used typeahead to add a new binding to an interface on a disabled device, got disabled hint in typeahead UI.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13641

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

+274 -2
+2
resources/sql/autopatches/20210316.almanac.03.device-status.sql
··· 1 + ALTER TABLE {$NAMESPACE}_almanac.almanac_device 2 + ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
+2
resources/sql/autopatches/20210316.almanac.04.device-status-value.sql
··· 1 + UPDATE {$NAMESPACE}_almanac.almanac_device 2 + SET status = 'active' WHERE status = '';
+4
src/__phutil_library_map__.php
··· 62 62 'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php', 63 63 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 64 64 'AlmanacDeviceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php', 65 + 'AlmanacDeviceStatus' => 'applications/almanac/constants/AlmanacDeviceStatus.php', 66 + 'AlmanacDeviceStatusTransaction' => 'applications/almanac/xaction/AlmanacDeviceStatusTransaction.php', 65 67 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 66 68 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 67 69 'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php', ··· 6089 6091 'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 6090 6092 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 6091 6093 'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType', 6094 + 'AlmanacDeviceStatus' => 'Phobject', 6095 + 'AlmanacDeviceStatusTransaction' => 'AlmanacDeviceTransactionType', 6092 6096 'AlmanacDeviceTransaction' => 'AlmanacModularTransaction', 6093 6097 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 6094 6098 'AlmanacDeviceTransactionType' => 'AlmanacTransactionType',
+89
src/applications/almanac/constants/AlmanacDeviceStatus.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceStatus 4 + extends Phobject { 5 + 6 + const ACTIVE = 'active'; 7 + const DISABLED = 'disabled'; 8 + 9 + private $value; 10 + 11 + public static function newStatusFromValue($value) { 12 + $status = new self(); 13 + $status->value = $value; 14 + return $status; 15 + } 16 + 17 + public function getValue() { 18 + return $this->value; 19 + } 20 + 21 + public function getName() { 22 + $name = $this->getDeviceStatusProperty('name'); 23 + 24 + if ($name === null) { 25 + $name = pht('Unknown Almanac Device Status ("%s")', $this->getValue()); 26 + } 27 + 28 + return $name; 29 + } 30 + 31 + public function getIconIcon() { 32 + return $this->getDeviceStatusProperty('icon.icon'); 33 + } 34 + 35 + public function getIconColor() { 36 + return $this->getDeviceStatusProperty('icon.color'); 37 + } 38 + 39 + public function isDisabled() { 40 + return ($this->getValue() === self::DISABLED); 41 + } 42 + 43 + public function hasStatusTag() { 44 + return ($this->getStatusTagIcon() !== null); 45 + } 46 + 47 + public function getStatusTagIcon() { 48 + return $this->getDeviceStatusProperty('status-tag.icon'); 49 + } 50 + 51 + public function getStatusTagColor() { 52 + return $this->getDeviceStatusProperty('status-tag.color'); 53 + } 54 + 55 + public static function getStatusMap() { 56 + $result = array(); 57 + 58 + foreach (self::newDeviceStatusMap() as $status_value => $ignored) { 59 + $result[$status_value] = self::newStatusFromValue($status_value); 60 + } 61 + 62 + return $result; 63 + } 64 + 65 + private function getDeviceStatusProperty($key, $default = null) { 66 + $map = self::newDeviceStatusMap(); 67 + $properties = idx($map, $this->getValue(), array()); 68 + return idx($properties, $key, $default); 69 + } 70 + 71 + private static function newDeviceStatusMap() { 72 + return array( 73 + self::ACTIVE => array( 74 + 'name' => pht('Active'), 75 + 'icon.icon' => 'fa-server', 76 + 'icon.color' => 'green', 77 + ), 78 + self::DISABLED => array( 79 + 'name' => pht('Disabled'), 80 + 'icon.icon' => 'fa-times', 81 + 'icon.color' => 'grey', 82 + 'status-tag.icon' => 'fa-times', 83 + 'status-tag.color' => 'indigo', 84 + ), 85 + ); 86 + } 87 + 88 + 89 + }
+8
src/applications/almanac/controller/AlmanacDeviceViewController.php
··· 31 31 ->setPolicyObject($device) 32 32 ->setHeaderIcon('fa-server'); 33 33 34 + $status = $device->getStatusObject(); 35 + if ($status->hasStatusTag()) { 36 + $header->setStatus( 37 + $status->getStatusTagIcon(), 38 + $status->getStatusTagColor(), 39 + $status->getName()); 40 + } 41 + 34 42 $issue = null; 35 43 if ($device->isClusterDevice()) { 36 44 $issue = $this->addClusterMessage(
+27
src/applications/almanac/editor/AlmanacDeviceEditEngine.php
··· 76 76 } 77 77 78 78 protected function buildCustomEditFields($object) { 79 + $status_map = $this->getDeviceStatusMap($object); 80 + 79 81 return array( 80 82 id(new PhabricatorTextEditField()) 81 83 ->setKey('name') ··· 84 86 ->setTransactionType(AlmanacDeviceNameTransaction::TRANSACTIONTYPE) 85 87 ->setIsRequired(true) 86 88 ->setValue($object->getName()), 89 + id(new PhabricatorSelectEditField()) 90 + ->setKey('status') 91 + ->setLabel(pht('Status')) 92 + ->setDescription(pht('Device status.')) 93 + ->setTransactionType(AlmanacDeviceStatusTransaction::TRANSACTIONTYPE) 94 + ->setOptions($status_map) 95 + ->setValue($object->getStatus()), 87 96 ); 97 + } 98 + 99 + 100 + private function getDeviceStatusMap(AlmanacDevice $device) { 101 + $status_map = AlmanacDeviceStatus::getStatusMap(); 102 + 103 + // If the device currently has an unknown status, add it to the list for 104 + // the dropdown. 105 + $status_value = $device->getStatus(); 106 + if (!isset($status_map[$status_value])) { 107 + $status_map = array( 108 + $status_value => AlmanacDeviceStatus::newStatusFromValue($status_value), 109 + ) + $status_map; 110 + } 111 + 112 + $status_map = mpull($status_map, 'getName'); 113 + 114 + return $status_map; 88 115 } 89 116 90 117 }
+6 -1
src/applications/almanac/phid/AlmanacInterfacePHIDType.php
··· 34 34 35 35 $id = $interface->getID(); 36 36 37 - $device_name = $interface->getDevice()->getName(); 37 + $device = $interface->getDevice(); 38 + $device_name = $device->getName(); 38 39 $address = $interface->getAddress(); 39 40 $port = $interface->getPort(); 40 41 $network = $interface->getNetwork()->getName(); ··· 48 49 49 50 $handle->setObjectName(pht('Interface %d', $id)); 50 51 $handle->setName($name); 52 + 53 + if ($device->isDisabled()) { 54 + $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); 55 + } 51 56 } 52 57 } 53 58
+13
src/applications/almanac/query/AlmanacDeviceQuery.php
··· 9 9 private $namePrefix; 10 10 private $nameSuffix; 11 11 private $isClusterDevice; 12 + private $statuses; 12 13 13 14 public function withIDs(array $ids) { 14 15 $this->ids = $ids; ··· 32 33 33 34 public function withNameSuffix($suffix) { 34 35 $this->nameSuffix = $suffix; 36 + return $this; 37 + } 38 + 39 + public function withStatuses(array $statuses) { 40 + $this->statuses = $statuses; 35 41 return $this; 36 42 } 37 43 ··· 101 107 $conn, 102 108 'device.isBoundToClusterService = %d', 103 109 (int)$this->isClusterDevice); 110 + } 111 + 112 + if ($this->statuses !== null) { 113 + $where[] = qsprintf( 114 + $conn, 115 + 'device.status IN (%Ls)', 116 + $this->statuses); 104 117 } 105 118 106 119 return $where;
+25
src/applications/almanac/query/AlmanacDeviceSearchEngine.php
··· 16 16 } 17 17 18 18 protected function buildCustomSearchFields() { 19 + $status_options = AlmanacDeviceStatus::getStatusMap(); 20 + $status_options = mpull($status_options, 'getName'); 21 + 19 22 return array( 20 23 id(new PhabricatorSearchTextField()) 21 24 ->setLabel(pht('Name Contains')) ··· 25 28 ->setLabel(pht('Exact Names')) 26 29 ->setKey('names') 27 30 ->setDescription(pht('Search for devices with specific names.')), 31 + id(new PhabricatorSearchCheckboxesField()) 32 + ->setLabel(pht('Statuses')) 33 + ->setKey('statuses') 34 + ->setDescription(pht('Search for devices with given statuses.')) 35 + ->setOptions($status_options), 28 36 id(new PhabricatorSearchThreeStateField()) 29 37 ->setLabel(pht('Cluster Device')) 30 38 ->setKey('isClusterDevice') ··· 48 56 49 57 if ($map['isClusterDevice'] !== null) { 50 58 $query->withIsClusterDevice($map['isClusterDevice']); 59 + } 60 + 61 + if ($map['statuses']) { 62 + $query->withStatuses($map['statuses']); 51 63 } 52 64 53 65 return $query; ··· 98 110 if ($device->isClusterDevice()) { 99 111 $item->addIcon('fa-sitemap', pht('Cluster Device')); 100 112 } 113 + 114 + if ($device->isDisabled()) { 115 + $item->setDisabled(true); 116 + } 117 + 118 + $status = $device->getStatusObject(); 119 + $icon_icon = $status->getIconIcon(); 120 + $icon_color = $status->getIconColor(); 121 + $icon_label = $status->getName(); 122 + 123 + $item->setStatusIcon( 124 + "{$icon_icon} {$icon_color}", 125 + $icon_label); 101 126 102 127 $list->addItem($item); 103 128 }
+25
src/applications/almanac/storage/AlmanacDevice.php
··· 17 17 protected $nameIndex; 18 18 protected $viewPolicy; 19 19 protected $editPolicy; 20 + protected $status; 20 21 protected $isBoundToClusterService; 21 22 22 23 private $almanacProperties = self::ATTACHABLE; ··· 25 26 return id(new AlmanacDevice()) 26 27 ->setViewPolicy(PhabricatorPolicies::POLICY_USER) 27 28 ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) 29 + ->setStatus(AlmanacDeviceStatus::ACTIVE) 28 30 ->attachAlmanacProperties(array()) 29 31 ->setIsBoundToClusterService(0); 30 32 } ··· 35 37 self::CONFIG_COLUMN_SCHEMA => array( 36 38 'name' => 'text128', 37 39 'nameIndex' => 'bytes12', 40 + 'status' => 'text32', 38 41 'isBoundToClusterService' => 'bool', 39 42 ), 40 43 self::CONFIG_KEY_SCHEMA => array( ··· 98 101 99 102 public function isClusterDevice() { 100 103 return $this->getIsBoundToClusterService(); 104 + } 105 + 106 + public function getStatusObject() { 107 + return $this->newStatusObject(); 108 + } 109 + 110 + private function newStatusObject() { 111 + return AlmanacDeviceStatus::newStatusFromValue($this->getStatus()); 112 + } 113 + 114 + public function isDisabled() { 115 + return $this->getStatusObject()->isDisabled(); 101 116 } 102 117 103 118 ··· 263 278 ->setKey('name') 264 279 ->setType('string') 265 280 ->setDescription(pht('The name of the device.')), 281 + id(new PhabricatorConduitSearchFieldSpecification()) 282 + ->setKey('status') 283 + ->setType('map<string, wild>') 284 + ->setDescription(pht('Device status information.')), 266 285 ); 267 286 } 268 287 269 288 public function getFieldValuesForConduit() { 289 + $status = $this->getStatusObject(); 290 + 270 291 return array( 271 292 'name' => $this->getName(), 293 + 'status' => array( 294 + 'value' => $status->getValue(), 295 + 'name' => $status->getName(), 296 + ), 272 297 ); 273 298 } 274 299
+8 -1
src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php
··· 46 46 47 47 $results = array(); 48 48 foreach ($handles as $handle) { 49 + if ($handle->isClosed()) { 50 + $closed = pht('Disabled'); 51 + } else { 52 + $closed = null; 53 + } 54 + 49 55 $results[] = id(new PhabricatorTypeaheadResult()) 50 56 ->setName($handle->getName()) 51 - ->setPHID($handle->getPHID()); 57 + ->setPHID($handle->getPHID()) 58 + ->setClosed($closed); 52 59 } 53 60 54 61 return $results;
+61
src/applications/almanac/xaction/AlmanacDeviceStatusTransaction.php
··· 1 + <?php 2 + 3 + final class AlmanacDeviceStatusTransaction 4 + extends AlmanacDeviceTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'almanac:device:status'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getStatus(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setStatus($value); 14 + } 15 + 16 + public function getTitle() { 17 + $old_value = $this->getOldValue(); 18 + $new_value = $this->getNewValue(); 19 + 20 + $old_status = AlmanacDeviceStatus::newStatusFromValue($old_value); 21 + $new_status = AlmanacDeviceStatus::newStatusFromValue($new_value); 22 + 23 + $old_name = $old_status->getName(); 24 + $new_name = $new_status->getName(); 25 + 26 + return pht( 27 + '%s changed the status of this device from %s to %s.', 28 + $this->renderAuthor(), 29 + $this->renderValue($old_name), 30 + $this->renderValue($new_name)); 31 + } 32 + 33 + public function validateTransactions($object, array $xactions) { 34 + $errors = array(); 35 + 36 + $status_map = AlmanacDeviceStatus::getStatusMap(); 37 + 38 + $old_value = $this->generateOldValue($object); 39 + foreach ($xactions as $xaction) { 40 + $new_value = $xaction->getNewValue(); 41 + 42 + if ($new_value === $old_value) { 43 + continue; 44 + } 45 + 46 + if (!isset($status_map[$new_value])) { 47 + $errors[] = $this->newInvalidError( 48 + pht( 49 + 'Almanac device status "%s" is unrecognized. Valid status '. 50 + 'values are: %s.', 51 + $new_value, 52 + implode(', ', array_keys($status_map))), 53 + $xaction); 54 + continue; 55 + } 56 + } 57 + 58 + return $errors; 59 + } 60 + 61 + }
+4
src/applications/phid/PhabricatorObjectHandle.php
··· 197 197 return $this->status; 198 198 } 199 199 200 + public function isClosed() { 201 + return ($this->status === self::STATUS_CLOSED); 202 + } 203 + 200 204 public function setFullName($full_name) { 201 205 $this->fullName = $full_name; 202 206 return $this;