@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rough cut of "Blueprint Authorizations"

Summary:
Ref T9519. This is like 80% of the way there and doesn't fully work yet, but roughly shows the shape of things to come. Here's how it works:

First, there's a new custom field type for blueprints which works like a normal typeahead but has some extra logic. It's implemented this way to make it easy to add to Blueprints in Drydock and Build Plans in Harbormaster. Here, I've added a "Use Blueprints" field to the "WorkingCopy" blueprint, so you can control which hosts the working copies are permitted to allocate on:

{F869865}

This control has a bit of custom rendering logic. Instead of rendering a normal list of PHIDs, it renders an annotated list with icons:

{F869866}

These icons show whether the blueprint on the other size of the authorization has approved this object. Once you have a green checkmark, you're good to go.

On the blueprint side, things look like this:

{F869867}

This table shows all the objects which have asked for access to this blueprint. In this case it's showing that one object is approved to use the blueprint since I already approved it, but by default new requests come in here as "Authorization Requested" and someone has to go approve them.

You approve them from within the authorization detail screen:

{F869868}

You can use the "Approve" or "Decline" buttons to allow or prevent use of the blueprint.

This doesn't actually do anything yet -- objects don't need to be authorized in order to use blueprints quite yet. That will come in the next diff, I just wanted to get the UI in reasonable shape first.

The authorization also has a second piece of state, which is whether the request from the object is active or inactive. We use this to keep track of the authorization if the blueprint is (maybe temporarily) deleted.

For example, you might have a Build Plan that uses Blueprints A and B. For a couple days, you only want to use A, so you remove B from the "Use Blueprints: ..." field. Later, you can add B back and it will connect to its old authorization again, so you don't need to go re-approve things (and if you're declined, you stay declined instead of being able to request authorization over and over again). This should make working with authorizations a little easier and less labor intensive.

Stuff not in this diff:

- Actually preventing any allocations (next diff).
- Probably should have transactions for approve/decline, at least, at some point, so there's a log of who did approvals and when.
- Maybe should have a more clear/loud error state when no blueprints are approved?
- Should probably restrict the typeahead to specific blueprint types.

Test Plan:
- Added the field.
- Typed some stuff into it.
- Saw the UI update properly.
- Approved an authorization.
- Declined an authorization.
- Saw active authorizations on a blueprint page.
- Didn't see any inactive authroizations there.
- Clicked "View All Authorizations", saw all authorizations.

Reviewers: chad, hach-que

Reviewed By: chad

Maniphest Tasks: T9519

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

+1059 -11
+14
resources/sql/autopatches/20151009.drydock.auth.1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_drydock.drydock_authorization ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + blueprintPHID VARBINARY(64) NOT NULL, 5 + blueprintAuthorizationState VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, 6 + objectPHID VARBINARY(64) NOT NULL, 7 + objectAuthorizationState VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, 8 + dateCreated INT UNSIGNED NOT NULL, 9 + dateModified INT UNSIGNED NOT NULL, 10 + UNIQUE KEY `key_phid` (phid), 11 + UNIQUE KEY `key_unique` (objectPHID, blueprintPHID), 12 + KEY `key_blueprint` (blueprintPHID, blueprintAuthorizationState), 13 + KEY `key_object` (objectPHID, objectAuthorizationState) 14 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+21
src/__phutil_library_map__.php
··· 799 799 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', 800 800 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 801 801 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 802 + 'DrydockAuthorization' => 'applications/drydock/storage/DrydockAuthorization.php', 803 + 'DrydockAuthorizationAuthorizeController' => 'applications/drydock/controller/DrydockAuthorizationAuthorizeController.php', 804 + 'DrydockAuthorizationListController' => 'applications/drydock/controller/DrydockAuthorizationListController.php', 805 + 'DrydockAuthorizationListView' => 'applications/drydock/view/DrydockAuthorizationListView.php', 806 + 'DrydockAuthorizationPHIDType' => 'applications/drydock/phid/DrydockAuthorizationPHIDType.php', 807 + 'DrydockAuthorizationQuery' => 'applications/drydock/query/DrydockAuthorizationQuery.php', 808 + 'DrydockAuthorizationSearchEngine' => 'applications/drydock/query/DrydockAuthorizationSearchEngine.php', 809 + 'DrydockAuthorizationViewController' => 'applications/drydock/controller/DrydockAuthorizationViewController.php', 802 810 'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php', 803 811 'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php', 804 812 'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php', ··· 2946 2954 'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php', 2947 2955 'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php', 2948 2956 'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php', 2957 + 'PhabricatorStandardCustomFieldBlueprints' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php', 2949 2958 'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php', 2950 2959 'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php', 2951 2960 'PhabricatorStandardCustomFieldDatasource' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php', ··· 4537 4546 'DoorkeeperTagsController' => 'PhabricatorController', 4538 4547 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 4539 4548 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 4549 + 'DrydockAuthorization' => array( 4550 + 'DrydockDAO', 4551 + 'PhabricatorPolicyInterface', 4552 + ), 4553 + 'DrydockAuthorizationAuthorizeController' => 'DrydockController', 4554 + 'DrydockAuthorizationListController' => 'DrydockController', 4555 + 'DrydockAuthorizationListView' => 'AphrontView', 4556 + 'DrydockAuthorizationPHIDType' => 'PhabricatorPHIDType', 4557 + 'DrydockAuthorizationQuery' => 'DrydockQuery', 4558 + 'DrydockAuthorizationSearchEngine' => 'PhabricatorApplicationSearchEngine', 4559 + 'DrydockAuthorizationViewController' => 'DrydockController', 4540 4560 'DrydockBlueprint' => array( 4541 4561 'DrydockDAO', 4542 4562 'PhabricatorApplicationTransactionInterface', ··· 7091 7111 'PhabricatorSpacesTestCase' => 'PhabricatorTestCase', 7092 7112 'PhabricatorSpacesViewController' => 'PhabricatorSpacesController', 7093 7113 'PhabricatorStandardCustomField' => 'PhabricatorCustomField', 7114 + 'PhabricatorStandardCustomFieldBlueprints' => 'PhabricatorStandardCustomFieldTokenizer', 7094 7115 'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField', 7095 7116 'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField', 7096 7117 'PhabricatorStandardCustomFieldDatasource' => 'PhabricatorStandardCustomFieldTokenizer',
+9
src/applications/drydock/application/PhabricatorDrydockApplication.php
··· 57 57 'DrydockResourceListController', 58 58 'logs/(?:query/(?P<queryKey>[^/]+)/)?' => 59 59 'DrydockLogListController', 60 + 'authorizations/(?:query/(?P<queryKey>[^/]+)/)?' => 61 + 'DrydockAuthorizationListController', 60 62 ), 61 63 'create/' => 'DrydockBlueprintCreateController', 62 64 'edit/(?:(?P<id>[1-9]\d*)/)?' => 'DrydockBlueprintEditController', ··· 79 81 'release/' => 'DrydockLeaseReleaseController', 80 82 'logs/(?:query/(?P<queryKey>[^/]+)/)?' => 81 83 'DrydockLogListController', 84 + ), 85 + ), 86 + '(?P<type>authorization)/' => array( 87 + '(?P<id>[1-9]\d*)/' => array( 88 + '' => 'DrydockAuthorizationViewController', 89 + '(?P<action>authorize|decline)/' => 90 + 'DrydockAuthorizationAuthorizeController', 82 91 ), 83 92 ), 84 93 ),
+10
src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
··· 390 390 return $lease; 391 391 } 392 392 393 + public function getFieldSpecifications() { 394 + return array( 395 + 'blueprintPHIDs' => array( 396 + 'name' => pht('Use Blueprints'), 397 + 'type' => 'blueprints', 398 + 'required' => true, 399 + ), 400 + ) + parent::getFieldSpecifications(); 401 + } 402 + 393 403 394 404 }
+95
src/applications/drydock/controller/DrydockAuthorizationAuthorizeController.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorizationAuthorizeController 4 + extends DrydockController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + $id = $request->getURIData('id'); 9 + 10 + $authorization = id(new DrydockAuthorizationQuery()) 11 + ->setViewer($viewer) 12 + ->withIDs(array($id)) 13 + ->requireCapabilities( 14 + array( 15 + PhabricatorPolicyCapability::CAN_VIEW, 16 + PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$authorization) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $authorization_uri = $this->getApplicationURI("authorization/{$id}/"); 24 + $is_authorize = ($request->getURIData('action') == 'authorize'); 25 + 26 + $state_authorized = DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED; 27 + $state_declined = DrydockAuthorization::BLUEPRINTAUTH_DECLINED; 28 + 29 + $state = $authorization->getBlueprintAuthorizationState(); 30 + $can_authorize = ($state != $state_authorized); 31 + $can_decline = ($state != $state_declined); 32 + 33 + if ($is_authorize && !$can_authorize) { 34 + return $this->newDialog() 35 + ->setTitle(pht('Already Authorized')) 36 + ->appendParagraph( 37 + pht( 38 + 'This authorization has already been approved.')) 39 + ->addCancelButton($authorization_uri); 40 + } 41 + 42 + if (!$is_authorize && !$can_decline) { 43 + return $this->newDialog() 44 + ->setTitle(pht('Already Declined')) 45 + ->appendParagraph( 46 + pht('This authorization has already been declined.')) 47 + ->addCancelButton($authorization_uri); 48 + } 49 + 50 + if ($request->isFormPost()) { 51 + if ($is_authorize) { 52 + $new_state = $state_authorized; 53 + } else { 54 + $new_state = $state_declined; 55 + } 56 + 57 + $authorization 58 + ->setBlueprintAuthorizationState($new_state) 59 + ->save(); 60 + 61 + return id(new AphrontRedirectResponse())->setURI($authorization_uri); 62 + } 63 + 64 + if ($is_authorize) { 65 + $title = pht('Approve Authorization'); 66 + $body = pht( 67 + 'Approve this authorization? The object will be able to lease and '. 68 + 'allocate resources created by this blueprint.'); 69 + $button = pht('Approve Authorization'); 70 + } else { 71 + $title = pht('Decline Authorization'); 72 + $body = pht( 73 + 'Decline this authorization? The object will not be able to lease '. 74 + 'or allocate resources created by this blueprint.'); 75 + $button = pht('Decline Authorization'); 76 + } 77 + 78 + return $this->newDialog() 79 + ->setTitle($title) 80 + ->appendParagraph($body) 81 + ->addSubmitButton($button) 82 + ->addCancelButton($authorization_uri); 83 + } 84 + 85 + public function buildSideNavView() { 86 + // TODO: Get rid of this, but it's currently required by DrydockController. 87 + return null; 88 + } 89 + 90 + public function buildApplicationMenu() { 91 + // TODO: As above. 92 + return null; 93 + } 94 + 95 + }
+87
src/applications/drydock/controller/DrydockAuthorizationListController.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorizationListController 4 + extends DrydockController { 5 + 6 + private $blueprint; 7 + 8 + public function setBlueprint(DrydockBlueprint $blueprint) { 9 + $this->blueprint = $blueprint; 10 + return $this; 11 + } 12 + 13 + public function getBlueprint() { 14 + return $this->blueprint; 15 + } 16 + 17 + public function shouldAllowPublic() { 18 + return true; 19 + } 20 + 21 + public function handleRequest(AphrontRequest $request) { 22 + $viewer = $this->getViewer(); 23 + 24 + $engine = new DrydockAuthorizationSearchEngine(); 25 + 26 + $id = $request->getURIData('id'); 27 + 28 + $blueprint = id(new DrydockBlueprintQuery()) 29 + ->setViewer($viewer) 30 + ->withIDs(array($id)) 31 + ->executeOne(); 32 + if (!$blueprint) { 33 + return new Aphront404Response(); 34 + } 35 + 36 + $this->setBlueprint($blueprint); 37 + $engine->setBlueprint($blueprint); 38 + 39 + $querykey = $request->getURIData('queryKey'); 40 + 41 + $controller = id(new PhabricatorApplicationSearchController()) 42 + ->setQueryKey($querykey) 43 + ->setSearchEngine($engine) 44 + ->setNavigation($this->buildSideNavView()); 45 + 46 + return $this->delegateToController($controller); 47 + } 48 + 49 + public function buildSideNavView() { 50 + $nav = new AphrontSideNavFilterView(); 51 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 52 + 53 + $engine = id(new DrydockAuthorizationSearchEngine()) 54 + ->setViewer($this->getViewer()); 55 + 56 + $engine->setBlueprint($this->getBlueprint()); 57 + $engine->addNavigationItems($nav->getMenu()); 58 + 59 + $nav->selectFilter(null); 60 + 61 + return $nav; 62 + } 63 + 64 + protected function buildApplicationCrumbs() { 65 + $crumbs = parent::buildApplicationCrumbs(); 66 + 67 + $blueprint = $this->getBlueprint(); 68 + if ($blueprint) { 69 + $id = $blueprint->getID(); 70 + 71 + $crumbs->addTextCrumb( 72 + pht('Blueprints'), 73 + $this->getApplicationURI('blueprint/')); 74 + 75 + $crumbs->addTextCrumb( 76 + $blueprint->getBlueprintName(), 77 + $this->getApplicationURI("blueprint/{$id}/")); 78 + 79 + $crumbs->addTextCrumb( 80 + pht('Authorizations'), 81 + $this->getApplicationURI("blueprint/{$id}/authorizations/")); 82 + } 83 + 84 + return $crumbs; 85 + } 86 + 87 + }
+141
src/applications/drydock/controller/DrydockAuthorizationViewController.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorizationViewController 4 + extends DrydockController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + $id = $request->getURIData('id'); 9 + 10 + $authorization = id(new DrydockAuthorizationQuery()) 11 + ->setViewer($viewer) 12 + ->withIDs(array($id)) 13 + ->executeOne(); 14 + if (!$authorization) { 15 + return new Aphront404Response(); 16 + } 17 + 18 + $id = $authorization->getID(); 19 + $title = pht('Authorization %d', $id); 20 + 21 + $blueprint = $authorization->getBlueprint(); 22 + $blueprint_id = $blueprint->getID(); 23 + 24 + $header = id(new PHUIHeaderView()) 25 + ->setHeader($title) 26 + ->setUser($viewer) 27 + ->setPolicyObject($authorization); 28 + 29 + 30 + $state = $authorization->getBlueprintAuthorizationState(); 31 + $icon = DrydockAuthorization::getBlueprintStateIcon($state); 32 + $name = DrydockAuthorization::getBlueprintStateName($state); 33 + 34 + $header->setStatus($icon, null, $name); 35 + 36 + $actions = $this->buildActionListView($authorization); 37 + $properties = $this->buildPropertyListView($authorization); 38 + $properties->setActionList($actions); 39 + 40 + $crumbs = $this->buildApplicationCrumbs(); 41 + $crumbs->addTextCrumb( 42 + pht('Blueprints'), 43 + $this->getApplicationURI('blueprint/')); 44 + $crumbs->addTextCrumb( 45 + $blueprint->getBlueprintName(), 46 + $this->getApplicationURI("blueprint/{$blueprint_id}/")); 47 + $crumbs->addTextCrumb($title); 48 + 49 + $object_box = id(new PHUIObjectBoxView()) 50 + ->setHeader($header) 51 + ->addPropertyList($properties); 52 + 53 + return $this->buildApplicationPage( 54 + array( 55 + $crumbs, 56 + $object_box, 57 + ), 58 + array( 59 + 'title' => $title, 60 + )); 61 + 62 + } 63 + 64 + private function buildActionListView(DrydockAuthorization $authorization) { 65 + $viewer = $this->getViewer(); 66 + $id = $authorization->getID(); 67 + 68 + $view = id(new PhabricatorActionListView()) 69 + ->setUser($viewer) 70 + ->setObjectURI($this->getRequest()->getRequestURI()) 71 + ->setObject($authorization); 72 + 73 + $can_edit = PhabricatorPolicyFilter::hasCapability( 74 + $viewer, 75 + $authorization, 76 + PhabricatorPolicyCapability::CAN_EDIT); 77 + 78 + $authorize_uri = $this->getApplicationURI("authorization/{$id}/authorize/"); 79 + $decline_uri = $this->getApplicationURI("authorization/{$id}/decline/"); 80 + 81 + $state_authorized = DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED; 82 + $state_declined = DrydockAuthorization::BLUEPRINTAUTH_DECLINED; 83 + 84 + $state = $authorization->getBlueprintAuthorizationState(); 85 + $can_authorize = $can_edit && ($state != $state_authorized); 86 + $can_decline = $can_edit && ($state != $state_declined); 87 + 88 + $view->addAction( 89 + id(new PhabricatorActionView()) 90 + ->setHref($authorize_uri) 91 + ->setName(pht('Approve Authorization')) 92 + ->setIcon('fa-check') 93 + ->setWorkflow(true) 94 + ->setDisabled(!$can_authorize)); 95 + 96 + $view->addAction( 97 + id(new PhabricatorActionView()) 98 + ->setHref($decline_uri) 99 + ->setName(pht('Decline Authorization')) 100 + ->setIcon('fa-times') 101 + ->setWorkflow(true) 102 + ->setDisabled(!$can_decline)); 103 + 104 + return $view; 105 + } 106 + 107 + private function buildPropertyListView(DrydockAuthorization $authorization) { 108 + $viewer = $this->getViewer(); 109 + 110 + $object_phid = $authorization->getObjectPHID(); 111 + $handles = $viewer->loadHandles(array($object_phid)); 112 + $handle = $handles[$object_phid]; 113 + 114 + $view = new PHUIPropertyListView(); 115 + 116 + $view->addProperty( 117 + pht('Authorized Object'), 118 + $handle->renderLink($handle->getFullName())); 119 + 120 + $view->addProperty(pht('Object Type'), $handle->getTypeName()); 121 + 122 + $object_state = $authorization->getObjectAuthorizationState(); 123 + 124 + $view->addProperty( 125 + pht('Authorization State'), 126 + DrydockAuthorization::getObjectStateName($object_state)); 127 + 128 + return $view; 129 + } 130 + 131 + public function buildSideNavView() { 132 + // TODO: Get rid of this, but it's currently required by DrydockController. 133 + return null; 134 + } 135 + 136 + public function buildApplicationMenu() { 137 + // TODO: As above. 138 + return null; 139 + } 140 + 141 + }
+70 -1
src/applications/drydock/controller/DrydockBlueprintViewController.php
··· 51 51 52 52 $resource_box = $this->buildResourceBox($blueprint); 53 53 54 + $authorizations_box = $this->buildAuthorizationsBox($blueprint); 55 + 54 56 $timeline = $this->buildTransactionTimeline( 55 57 $blueprint, 56 58 new DrydockBlueprintTransactionQuery()); ··· 68 70 $crumbs, 69 71 $object_box, 70 72 $resource_box, 73 + $authorizations_box, 71 74 $log_box, 72 75 $timeline, 73 76 ), ··· 167 170 ->setTag('a') 168 171 ->setHref($resources_uri) 169 172 ->setIconFont('fa-search') 170 - ->setText(pht('View All Resources'))); 173 + ->setText(pht('View All'))); 171 174 172 175 return id(new PHUIObjectBoxView()) 173 176 ->setHeader($resource_header) 174 177 ->setObjectList($resource_list); 178 + } 179 + 180 + private function buildAuthorizationsBox(DrydockBlueprint $blueprint) { 181 + $viewer = $this->getViewer(); 182 + 183 + $limit = 25; 184 + 185 + // If there are pending authorizations against this blueprint, make sure 186 + // we show them first. 187 + 188 + $pending_authorizations = id(new DrydockAuthorizationQuery()) 189 + ->setViewer($viewer) 190 + ->withBlueprintPHIDs(array($blueprint->getPHID())) 191 + ->withObjectStates( 192 + array( 193 + DrydockAuthorization::OBJECTAUTH_ACTIVE, 194 + )) 195 + ->withBlueprintStates( 196 + array( 197 + DrydockAuthorization::BLUEPRINTAUTH_REQUESTED, 198 + )) 199 + ->setLimit($limit) 200 + ->execute(); 201 + 202 + $all_authorizations = id(new DrydockAuthorizationQuery()) 203 + ->setViewer($viewer) 204 + ->withBlueprintPHIDs(array($blueprint->getPHID())) 205 + ->withObjectStates( 206 + array( 207 + DrydockAuthorization::OBJECTAUTH_ACTIVE, 208 + )) 209 + ->withBlueprintStates( 210 + array( 211 + DrydockAuthorization::BLUEPRINTAUTH_REQUESTED, 212 + DrydockAuthorization::BLUEPRINTAUTH_AUTHORIZED, 213 + )) 214 + ->setLimit($limit) 215 + ->execute(); 216 + 217 + $authorizations = 218 + mpull($pending_authorizations, null, 'getPHID') + 219 + mpull($all_authorizations, null, 'getPHID'); 220 + 221 + $authorization_list = id(new DrydockAuthorizationListView()) 222 + ->setUser($viewer) 223 + ->setAuthorizations($authorizations) 224 + ->setNoDataString( 225 + pht('No objects have active authorizations to use this blueprint.')); 226 + 227 + $id = $blueprint->getID(); 228 + $authorizations_uri = "blueprint/{$id}/authorizations/query/all/"; 229 + $authorizations_uri = $this->getApplicationURI($authorizations_uri); 230 + 231 + $authorizations_header = id(new PHUIHeaderView()) 232 + ->setHeader(pht('Active Authorizations')) 233 + ->addActionLink( 234 + id(new PHUIButtonView()) 235 + ->setTag('a') 236 + ->setHref($authorizations_uri) 237 + ->setIconFont('fa-search') 238 + ->setText(pht('View All'))); 239 + 240 + return id(new PHUIObjectBoxView()) 241 + ->setHeader($authorizations_header) 242 + ->setObjectList($authorization_list); 243 + 175 244 } 176 245 177 246
+1 -1
src/applications/drydock/controller/DrydockController.php
··· 105 105 ->setTag('a') 106 106 ->setHref($all_uri) 107 107 ->setIconFont('fa-search') 108 - ->setText(pht('View All Logs'))); 108 + ->setText(pht('View All'))); 109 109 110 110 return id(new PHUIObjectBoxView()) 111 111 ->setHeader($log_header)
+1 -1
src/applications/drydock/controller/DrydockResourceViewController.php
··· 170 170 ->setTag('a') 171 171 ->setHref($leases_uri) 172 172 ->setIconFont('fa-search') 173 - ->setText(pht('View All Leases'))); 173 + ->setText(pht('View All'))); 174 174 175 175 $lease_list = id(new DrydockLeaseListView()) 176 176 ->setUser($viewer)
-5
src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php
··· 41 41 $object->setDetail($key, $value); 42 42 } 43 43 44 - public function applyApplicationTransactionExternalEffects( 45 - PhabricatorApplicationTransaction $xaction) { 46 - return; 47 - } 48 - 49 44 public function getBlueprintFieldValue() { 50 45 return $this->getProxy()->getFieldValue(); 51 46 }
+37
src/applications/drydock/phid/DrydockAuthorizationPHIDType.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorizationPHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'DRYA'; 6 + 7 + public function getTypeName() { 8 + return pht('Drydock Authorization'); 9 + } 10 + 11 + public function newObject() { 12 + return new DrydockAuthorization(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new DrydockAuthorizationQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $authorization = $objects[$phid]; 30 + $id = $authorization->getID(); 31 + 32 + $handle->setName(pht('Drydock Authorization %d', $id)); 33 + $handle->setURI("/drydock/authorization/{$id}/"); 34 + } 35 + } 36 + 37 + }
+5 -2
src/applications/drydock/phid/DrydockBlueprintPHIDType.php
··· 28 28 foreach ($handles as $phid => $handle) { 29 29 $blueprint = $objects[$phid]; 30 30 $id = $blueprint->getID(); 31 + $name = $blueprint->getBlueprintName(); 31 32 32 - $handle->setName($blueprint->getBlueprintName()); 33 - $handle->setURI("/drydock/blueprint/{$id}/"); 33 + $handle 34 + ->setName($name) 35 + ->setFullName(pht('Blueprint %d: %s', $id, $name)) 36 + ->setURI("/drydock/blueprint/{$id}/"); 34 37 } 35 38 } 36 39
+146
src/applications/drydock/query/DrydockAuthorizationQuery.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorizationQuery extends DrydockQuery { 4 + 5 + private $ids; 6 + private $phids; 7 + private $blueprintPHIDs; 8 + private $objectPHIDs; 9 + private $blueprintStates; 10 + private $objectStates; 11 + 12 + public function withIDs(array $ids) { 13 + $this->ids = $ids; 14 + return $this; 15 + } 16 + 17 + public function withPHIDs(array $phids) { 18 + $this->phids = $phids; 19 + return $this; 20 + } 21 + 22 + public function withBlueprintPHIDs(array $phids) { 23 + $this->blueprintPHIDs = $phids; 24 + return $this; 25 + } 26 + 27 + public function withObjectPHIDs(array $phids) { 28 + $this->objectPHIDs = $phids; 29 + return $this; 30 + } 31 + 32 + public function withBlueprintStates(array $states) { 33 + $this->blueprintStates = $states; 34 + return $this; 35 + } 36 + 37 + public function withObjectStates(array $states) { 38 + $this->objectStates = $states; 39 + return $this; 40 + } 41 + 42 + public function newResultObject() { 43 + return new DrydockAuthorization(); 44 + } 45 + 46 + protected function loadPage() { 47 + return $this->loadStandardPage($this->newResultObject()); 48 + } 49 + 50 + protected function willFilterPage(array $authorizations) { 51 + $blueprint_phids = mpull($authorizations, 'getBlueprintPHID'); 52 + if ($blueprint_phids) { 53 + $blueprints = id(new DrydockBlueprintQuery()) 54 + ->setViewer($this->getViewer()) 55 + ->setParentQuery($this) 56 + ->withPHIDs($blueprint_phids) 57 + ->execute(); 58 + $blueprints = mpull($blueprints, null, 'getPHID'); 59 + } else { 60 + $blueprints = array(); 61 + } 62 + 63 + foreach ($authorizations as $key => $authorization) { 64 + $blueprint = idx($blueprints, $authorization->getBlueprintPHID()); 65 + if (!$blueprint) { 66 + $this->didRejectResult($authorization); 67 + unset($authorizations[$key]); 68 + continue; 69 + } 70 + $authorization->attachBlueprint($blueprint); 71 + } 72 + 73 + $object_phids = mpull($authorizations, 'getObjectPHID'); 74 + if ($object_phids) { 75 + $objects = id(new PhabricatorObjectQuery()) 76 + ->setViewer($this->getViewer()) 77 + ->setParentQuery($this) 78 + ->withPHIDs($object_phids) 79 + ->execute(); 80 + $objects = mpull($objects, null, 'getPHID'); 81 + } else { 82 + $objects = array(); 83 + } 84 + 85 + foreach ($authorizations as $key => $authorization) { 86 + $object = idx($objects, $authorization->getObjectPHID()); 87 + if (!$object) { 88 + $this->didRejectResult($authorization); 89 + unset($authorizations[$key]); 90 + continue; 91 + } 92 + $authorization->attachObject($object); 93 + } 94 + 95 + return $authorizations; 96 + } 97 + 98 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 99 + $where = parent::buildWhereClauseParts($conn); 100 + 101 + if ($this->ids !== null) { 102 + $where[] = qsprintf( 103 + $conn, 104 + 'id IN (%Ld)', 105 + $this->ids); 106 + } 107 + 108 + if ($this->phids !== null) { 109 + $where[] = qsprintf( 110 + $conn, 111 + 'phid IN (%Ls)', 112 + $this->phids); 113 + } 114 + 115 + if ($this->blueprintPHIDs !== null) { 116 + $where[] = qsprintf( 117 + $conn, 118 + 'blueprintPHID IN (%Ls)', 119 + $this->blueprintPHIDs); 120 + } 121 + 122 + if ($this->objectPHIDs !== null) { 123 + $where[] = qsprintf( 124 + $conn, 125 + 'objectPHID IN (%Ls)', 126 + $this->objectPHIDs); 127 + } 128 + 129 + if ($this->blueprintStates !== null) { 130 + $where[] = qsprintf( 131 + $conn, 132 + 'blueprintAuthorizationState IN (%Ls)', 133 + $this->blueprintStates); 134 + } 135 + 136 + if ($this->objectStates !== null) { 137 + $where[] = qsprintf( 138 + $conn, 139 + 'objectAuthorizationState IN (%Ls)', 140 + $this->objectStates); 141 + } 142 + 143 + return $where; 144 + } 145 + 146 + }
+87
src/applications/drydock/query/DrydockAuthorizationSearchEngine.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorizationSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + private $blueprint; 7 + 8 + public function setBlueprint(DrydockBlueprint $blueprint) { 9 + $this->blueprint = $blueprint; 10 + return $this; 11 + } 12 + 13 + public function getBlueprint() { 14 + return $this->blueprint; 15 + } 16 + 17 + public function getResultTypeDescription() { 18 + return pht('Drydock Authorizations'); 19 + } 20 + 21 + public function getApplicationClassName() { 22 + return 'PhabricatorDrydockApplication'; 23 + } 24 + 25 + public function canUseInPanelContext() { 26 + return false; 27 + } 28 + 29 + public function newQuery() { 30 + $query = new DrydockAuthorizationQuery(); 31 + 32 + $blueprint = $this->getBlueprint(); 33 + $query->withBlueprintPHIDs(array($blueprint->getPHID())); 34 + 35 + return $query; 36 + } 37 + 38 + protected function buildQueryFromParameters(array $map) { 39 + $query = $this->newQuery(); 40 + 41 + return $query; 42 + } 43 + 44 + protected function buildCustomSearchFields() { 45 + return array(); 46 + } 47 + 48 + protected function getURI($path) { 49 + $blueprint = $this->getBlueprint(); 50 + $id = $blueprint->getID(); 51 + return "/drydock/blueprint/{$id}/authorizations/".$path; 52 + } 53 + 54 + protected function getBuiltinQueryNames() { 55 + return array( 56 + 'all' => pht('All Authorizations'), 57 + ); 58 + } 59 + 60 + public function buildSavedQueryFromBuiltin($query_key) { 61 + $query = $this->newSavedQuery(); 62 + $query->setQueryKey($query_key); 63 + 64 + switch ($query_key) { 65 + case 'all': 66 + return $query; 67 + } 68 + 69 + return parent::buildSavedQueryFromBuiltin($query_key); 70 + } 71 + 72 + protected function renderResultList( 73 + array $authorizations, 74 + PhabricatorSavedQuery $query, 75 + array $handles) { 76 + 77 + $list = id(new DrydockAuthorizationListView()) 78 + ->setUser($this->requireViewer()) 79 + ->setAuthorizations($authorizations); 80 + 81 + $result = new PhabricatorApplicationSearchResultView(); 82 + $result->setTable($list); 83 + 84 + return $result; 85 + } 86 + 87 + }
+122
src/applications/drydock/storage/DrydockAuthorization.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorization extends DrydockDAO 4 + implements 5 + PhabricatorPolicyInterface { 6 + 7 + const OBJECTAUTH_ACTIVE = 'active'; 8 + const OBJECTAUTH_INACTIVE = 'inactive'; 9 + 10 + const BLUEPRINTAUTH_REQUESTED = 'requested'; 11 + const BLUEPRINTAUTH_AUTHORIZED = 'authorized'; 12 + const BLUEPRINTAUTH_DECLINED = 'declined'; 13 + 14 + protected $blueprintPHID; 15 + protected $blueprintAuthorizationState; 16 + protected $objectPHID; 17 + protected $objectAuthorizationState; 18 + 19 + private $blueprint = self::ATTACHABLE; 20 + private $object = self::ATTACHABLE; 21 + 22 + protected function getConfiguration() { 23 + return array( 24 + self::CONFIG_AUX_PHID => true, 25 + self::CONFIG_COLUMN_SCHEMA => array( 26 + 'blueprintAuthorizationState' => 'text32', 27 + 'objectAuthorizationState' => 'text32', 28 + ), 29 + self::CONFIG_KEY_SCHEMA => array( 30 + 'key_unique' => array( 31 + 'columns' => array('objectPHID', 'blueprintPHID'), 32 + 'unique' => true, 33 + ), 34 + 'key_blueprint' => array( 35 + 'columns' => array('blueprintPHID', 'blueprintAuthorizationState'), 36 + ), 37 + 'key_object' => array( 38 + 'columns' => array('objectPHID', 'objectAuthorizationState'), 39 + ), 40 + ), 41 + ) + parent::getConfiguration(); 42 + } 43 + 44 + public function generatePHID() { 45 + return PhabricatorPHID::generateNewPHID( 46 + DrydockAuthorizationPHIDType::TYPECONST); 47 + } 48 + 49 + public function attachBlueprint(DrydockBlueprint $blueprint) { 50 + $this->blueprint = $blueprint; 51 + return $this; 52 + } 53 + 54 + public function getBlueprint() { 55 + return $this->assertAttached($this->blueprint); 56 + } 57 + 58 + public function attachObject($object) { 59 + $this->object = $object; 60 + return $this; 61 + } 62 + 63 + public function getObject() { 64 + return $this->assertAttached($this->object); 65 + } 66 + 67 + public static function getBlueprintStateIcon($state) { 68 + $map = array( 69 + self::BLUEPRINTAUTH_REQUESTED => 'fa-exclamation-circle indigo', 70 + self::BLUEPRINTAUTH_AUTHORIZED => 'fa-check-circle green', 71 + self::BLUEPRINTAUTH_DECLINED => 'fa-times red', 72 + ); 73 + 74 + return idx($map, $state, null); 75 + } 76 + 77 + public static function getBlueprintStateName($state) { 78 + $map = array( 79 + self::BLUEPRINTAUTH_REQUESTED => pht('Requested'), 80 + self::BLUEPRINTAUTH_AUTHORIZED => pht('Authorized'), 81 + self::BLUEPRINTAUTH_DECLINED => pht('Declined'), 82 + ); 83 + 84 + return idx($map, $state, pht('<Unknown: %s>', $state)); 85 + } 86 + 87 + public static function getObjectStateName($state) { 88 + $map = array( 89 + self::OBJECTAUTH_ACTIVE => pht('Active'), 90 + self::OBJECTAUTH_INACTIVE => pht('Inactive'), 91 + ); 92 + 93 + return idx($map, $state, pht('<Unknown: %s>', $state)); 94 + } 95 + 96 + 97 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 98 + 99 + 100 + public function getCapabilities() { 101 + return array( 102 + PhabricatorPolicyCapability::CAN_VIEW, 103 + PhabricatorPolicyCapability::CAN_EDIT, 104 + ); 105 + } 106 + 107 + public function getPolicy($capability) { 108 + return $this->getBlueprint()->getPolicy($capability); 109 + } 110 + 111 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 112 + return $this->getBlueprint()->hasAutomaticCapability($capability, $viewer); 113 + } 114 + 115 + public function describeAutomaticCapability($capability) { 116 + return pht( 117 + 'An authorization inherits the policies of the blueprint it '. 118 + 'authorizes access to.'); 119 + } 120 + 121 + 122 + }
+65
src/applications/drydock/view/DrydockAuthorizationListView.php
··· 1 + <?php 2 + 3 + final class DrydockAuthorizationListView extends AphrontView { 4 + 5 + private $authorizations; 6 + private $noDataString; 7 + 8 + public function setAuthorizations(array $authorizations) { 9 + assert_instances_of($authorizations, 'DrydockAuthorization'); 10 + $this->authorizations = $authorizations; 11 + return $this; 12 + } 13 + 14 + public function setNoDataString($string) { 15 + $this->noDataString = $string; 16 + return $this; 17 + } 18 + 19 + public function getNoDataString() { 20 + return $this->noDataString; 21 + } 22 + 23 + public function render() { 24 + $viewer = $this->getUser(); 25 + 26 + $authorizations = $this->authorizations; 27 + 28 + $view = new PHUIObjectItemListView(); 29 + 30 + $nodata = $this->getNoDataString(); 31 + if ($nodata) { 32 + $view->setNoDataString($nodata); 33 + } 34 + 35 + $handles = $viewer->loadHandles(mpull($authorizations, 'getObjectPHID')); 36 + 37 + foreach ($authorizations as $authorization) { 38 + $id = $authorization->getID(); 39 + $object_phid = $authorization->getObjectPHID(); 40 + $handle = $handles[$object_phid]; 41 + 42 + $item = id(new PHUIObjectItemView()) 43 + ->setHref("/drydock/authorization/{$id}/") 44 + ->setObjectName(pht('Authorization %d', $id)) 45 + ->setHeader($handle->getFullName()); 46 + 47 + $item->addAttribute($handle->getTypeName()); 48 + 49 + $object_state = $authorization->getObjectAuthorizationState(); 50 + $item->addAttribute( 51 + DrydockAuthorization::getObjectStateName($object_state)); 52 + 53 + $state = $authorization->getBlueprintAuthorizationState(); 54 + $icon = DrydockAuthorization::getBlueprintStateIcon($state); 55 + $name = DrydockAuthorization::getBlueprintStateName($state); 56 + 57 + $item->setStatusIcon($icon, $name); 58 + 59 + $view->addItem($item); 60 + } 61 + 62 + return $view; 63 + } 64 + 65 + }
+147
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php
··· 1 + <?php 2 + 3 + final class PhabricatorStandardCustomFieldBlueprints 4 + extends PhabricatorStandardCustomFieldTokenizer { 5 + 6 + public function getFieldType() { 7 + return 'blueprints'; 8 + } 9 + 10 + public function getDatasource() { 11 + return new DrydockBlueprintDatasource(); 12 + } 13 + 14 + public function applyApplicationTransactionExternalEffects( 15 + PhabricatorApplicationTransaction $xaction) { 16 + 17 + $object_phid = $xaction->getObjectPHID(); 18 + 19 + $old = $this->decodeValue($xaction->getOldValue()); 20 + $new = $this->decodeValue($xaction->getNewValue()); 21 + 22 + $old_phids = array_fuse($old); 23 + $new_phids = array_fuse($new); 24 + 25 + $rem_phids = array_diff_key($old_phids, $new_phids); 26 + $add_phids = array_diff_key($new_phids, $old_phids); 27 + 28 + $altered_phids = $rem_phids + $add_phids; 29 + 30 + if (!$altered_phids) { 31 + return; 32 + } 33 + 34 + $authorizations = id(new DrydockAuthorizationQuery()) 35 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 36 + ->withObjectPHIDs(array($object_phid)) 37 + ->withBlueprintPHIDs($altered_phids) 38 + ->execute(); 39 + $authorizations = mpull($authorizations, null, 'getBlueprintPHID'); 40 + 41 + $state_active = DrydockAuthorization::OBJECTAUTH_ACTIVE; 42 + $state_inactive = DrydockAuthorization::OBJECTAUTH_INACTIVE; 43 + 44 + $state_requested = DrydockAuthorization::BLUEPRINTAUTH_REQUESTED; 45 + 46 + // Disable the object side of the authorization for any existing 47 + // authorizations. 48 + foreach ($rem_phids as $rem_phid) { 49 + $authorization = idx($authorizations, $rem_phid); 50 + if (!$authorization) { 51 + continue; 52 + } 53 + 54 + $authorization 55 + ->setObjectAuthorizationState($state_inactive) 56 + ->save(); 57 + } 58 + 59 + // For new authorizations, either add them or reactivate them depending 60 + // on the current state. 61 + foreach ($add_phids as $add_phid) { 62 + $needs_update = false; 63 + 64 + $authorization = idx($authorizations, $add_phid); 65 + if (!$authorization) { 66 + $authorization = id(new DrydockAuthorization()) 67 + ->setObjectPHID($object_phid) 68 + ->setObjectAuthorizationState($state_active) 69 + ->setBlueprintPHID($add_phid) 70 + ->setBlueprintAuthorizationState($state_requested); 71 + 72 + $needs_update = true; 73 + } else { 74 + $current_state = $authorization->getObjectAuthorizationState(); 75 + if ($current_state != $state_active) { 76 + $authorization->setObjectAuthorizationState($state_active); 77 + $needs_update = true; 78 + } 79 + } 80 + 81 + if ($needs_update) { 82 + $authorization->save(); 83 + } 84 + } 85 + 86 + } 87 + 88 + public function renderPropertyViewValue(array $handles) { 89 + $value = $this->getFieldValue(); 90 + if (!$value) { 91 + return phutil_tag('em', array(), pht('No authorized blueprints.')); 92 + } 93 + 94 + $object = $this->getObject(); 95 + $object_phid = $object->getPHID(); 96 + 97 + // NOTE: We're intentionally letting you see the authorization state on 98 + // blueprints you can't see because this has a tremendous potential to 99 + // be extremely confusing otherwise. You still can't see the blueprints 100 + // themselves, but you can know if the object is authorized on something. 101 + 102 + if ($value) { 103 + $handles = $this->getViewer()->loadHandles($value); 104 + 105 + $authorizations = id(new DrydockAuthorizationQuery()) 106 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 107 + ->withObjectPHIDs(array($object_phid)) 108 + ->withBlueprintPHIDs($value) 109 + ->execute(); 110 + $authorizations = mpull($authorizations, null, 'getBlueprintPHID'); 111 + } else { 112 + $handles = array(); 113 + $authorizations = array(); 114 + } 115 + 116 + $items = array(); 117 + foreach ($value as $phid) { 118 + $authorization = idx($authorizations, $phid); 119 + if (!$authorization) { 120 + continue; 121 + } 122 + 123 + $handle = $handles[$phid]; 124 + 125 + $item = id(new PHUIStatusItemView()) 126 + ->setTarget($handle->renderLink()); 127 + 128 + $state = $authorization->getBlueprintAuthorizationState(); 129 + $item->setIcon( 130 + DrydockAuthorization::getBlueprintStateIcon($state), 131 + null, 132 + DrydockAuthorization::getBlueprintStateName($state)); 133 + 134 + $items[] = $item; 135 + } 136 + 137 + $status = new PHUIStatusListView(); 138 + foreach ($items as $item) { 139 + $status->addItem($item); 140 + } 141 + 142 + return $status; 143 + } 144 + 145 + 146 + 147 + }
+1 -1
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
··· 217 217 return array(); 218 218 } 219 219 220 - private function decodeValue($value) { 220 + protected function decodeValue($value) { 221 221 $value = json_decode($value); 222 222 if (!is_array($value)) { 223 223 $value = array();