diff --git a/resources/sql/autopatches/20151226.reop.1.sql b/resources/sql/autopatches/20151226.reop.1.sql new file mode 100644 index 0000000000..4daca60aeb --- /dev/null +++ b/resources/sql/autopatches/20151226.reop.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_repositoryoperation + ADD isDismissed BOOL NOT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d3d33adb15..a024d493e8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -911,6 +911,7 @@ phutil_register_library_map(array( 'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php', + 'DrydockRepositoryOperationDismissController' => 'applications/drydock/controller/DrydockRepositoryOperationDismissController.php', 'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php', 'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php', 'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php', @@ -4900,6 +4901,7 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), + 'DrydockRepositoryOperationDismissController' => 'DrydockController', 'DrydockRepositoryOperationListController' => 'DrydockController', 'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType', 'DrydockRepositoryOperationQuery' => 'DrydockQuery', diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php index 99067f9f2e..feeb6770b8 100644 --- a/src/applications/differential/controller/DifferentialRevisionOperationController.php +++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php @@ -94,8 +94,7 @@ final class DifferentialRevisionOperationController ->setUser($viewer) ->appendRemarkupInstructions( pht( - 'In theory, this will do approximately what `arc land` would do. '. - 'In practice, you will have a riveting adventure instead.')) + '(NOTE) This feature is new and experimental.')) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Onto Branch')) @@ -103,11 +102,7 @@ final class DifferentialRevisionOperationController ->setLimit(1) ->setError($e_ref) ->setValue($v_ref) - ->setDatasource($ref_datasource)) - ->appendRemarkupInstructions( - pht( - '(WARNING) THIS FEATURE IS EXPERIMENTAL AND DANGEROUS! USE IT AT '. - 'YOUR OWN RISK!')); + ->setDatasource($ref_datasource)); return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FORM) @@ -115,7 +110,7 @@ final class DifferentialRevisionOperationController ->setErrors($errors) ->appendForm($form) ->addCancelButton($detail_uri) - ->addSubmitButton(pht('Mutate Repository Unpredictably')); + ->addSubmitButton(pht('Land Revision')); } private function newRefQuery(PhabricatorRepository $repository) { diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 621464cc9b..71f4932fed 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1047,6 +1047,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $operations = id(new DrydockRepositoryOperationQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($revision->getPHID())) + ->withIsDismissed(false) ->withOperationTypes( array( DrydockLandRepositoryOperation::OPCONST, diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 0b109e68da..6267c26f1e 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -96,6 +96,7 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { '(?P[1-9]\d*)/' => array( '' => 'DrydockRepositoryOperationViewController', 'status/' => 'DrydockRepositoryOperationStatusController', + 'dismiss/' => 'DrydockRepositoryOperationDismissController', ), ), ), diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php new file mode 100644 index 0000000000..28e14bcb90 --- /dev/null +++ b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php @@ -0,0 +1,56 @@ +getViewer(); + $id = $request->getURIData('id'); + + $operation = id(new DrydockRepositoryOperationQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$operation) { + return new Aphront404Response(); + } + + $object_phid = $operation->getObjectPHID(); + $handles = $viewer->loadHandles(array($object_phid)); + $done_uri = $handles[$object_phid]->getURI(); + + if ($operation->getIsDismissed()) { + return $this->newDialog() + ->setTitle(pht('Already Dismissed')) + ->appendParagraph( + pht( + 'This operation has already been dismissed, and can not be '. + 'dismissed any further.')) + ->addCancelButton($done_uri); + } + + + if ($request->isFormPost()) { + $operation + ->setIsDismissed(1) + ->save(); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + return $this->newDialog() + ->setTitle(pht('Dismiss Operation')) + ->appendParagraph( + pht( + 'Dismiss this operation? It will no longer be shown, but logs '. + 'can be found in Drydock.')) + ->addSubmitButton(pht('Dismiss')) + ->addCancelButton($done_uri); + } + +} diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 48a3e8075f..af2f9f271f 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -235,12 +235,29 @@ final class DrydockLandRepositoryOperation $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; if ($revision->getStatus() != $status_accepted) { - return array( - 'title' => pht('Revision Not Accepted'), - 'body' => pht( - 'This revision is still under review. Only revisions which have '. - 'been accepted may land.'), - ); + switch ($revision->getStatus()) { + case ArcanistDifferentialRevisionStatus::CLOSED: + return array( + 'title' => pht('Revision Closed'), + 'body' => pht( + 'This revision has already been closed. Only open, accepted '. + 'revisions may land.'), + ); + case ArcanistDifferentialRevisionStatus::ABANDONED: + return array( + 'title' => pht('Revision Abandoned'), + 'body' => pht( + 'This revision has been abandoned. Only accepted revisions '. + 'may land.'), + ); + default: + return array( + 'title' => pht('Revision Not Accepted'), + 'body' => pht( + 'This revision is still under review. Only revisions which '. + 'have been accepted may land.'), + ); + } } // Check for other operations. Eventually this should probably be more diff --git a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php index 38e1b4787b..570e68e825 100644 --- a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php +++ b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php @@ -8,6 +8,7 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { private $repositoryPHIDs; private $operationStates; private $operationTypes; + private $isDismissed; public function withIDs(array $ids) { $this->ids = $ids; @@ -39,6 +40,11 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { return $this; } + public function withIsDismissed($dismissed) { + $this->isDismissed = $dismissed; + return $this; + } + public function newResultObject() { return new DrydockRepositoryOperation(); } @@ -152,6 +158,13 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { $this->operationTypes); } + if ($this->isDismissed !== null) { + $where[] = qsprintf( + $conn, + 'isDismissed = %d', + (int)$this->isDismissed); + } + return $where; } diff --git a/src/applications/drydock/storage/DrydockRepositoryOperation.php b/src/applications/drydock/storage/DrydockRepositoryOperation.php index 83e660ffa9..87565edc86 100644 --- a/src/applications/drydock/storage/DrydockRepositoryOperation.php +++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php @@ -20,6 +20,7 @@ final class DrydockRepositoryOperation extends DrydockDAO protected $operationType; protected $operationState; protected $properties = array(); + protected $isDismissed; private $repository = self::ATTACHABLE; private $object = self::ATTACHABLE; @@ -30,7 +31,8 @@ final class DrydockRepositoryOperation extends DrydockDAO return id(new DrydockRepositoryOperation()) ->setOperationState(self::STATE_WAIT) - ->setOperationType($op->getOperationConstant()); + ->setOperationType($op->getOperationConstant()) + ->setIsDismissed(0); } protected function getConfiguration() { @@ -43,6 +45,7 @@ final class DrydockRepositoryOperation extends DrydockDAO 'repositoryTarget' => 'bytes', 'operationType' => 'text32', 'operationState' => 'text32', + 'isDismissed' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_object' => array( @@ -196,11 +199,17 @@ final class DrydockRepositoryOperation extends DrydockDAO } public function getPolicy($capability) { - return $this->getRepository()->getPolicy($capability); + $need_capability = $this->getRequiredRepositoryCapability($capability); + + return $this->getRepository() + ->getPolicy($need_capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getRepository()->hasAutomaticCapability($capability, $viewer); + $need_capability = $this->getRequiredRepositoryCapability($capability); + + return $this->getRepository() + ->hasAutomaticCapability($need_capability, $viewer); } public function describeAutomaticCapability($capability) { @@ -209,4 +218,17 @@ final class DrydockRepositoryOperation extends DrydockDAO 'affects.'); } + private function getRequiredRepositoryCapability($capability) { + // To edit a RepositoryOperation, require that the user be able to push + // to the repository. + + $map = array( + PhabricatorPolicyCapability::CAN_EDIT => + DiffusionPushCapability::CAPABILITY, + ); + + return idx($map, $capability, $capability); + } + + } diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php index bf1040afe6..07c5a7b706 100644 --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -67,6 +67,7 @@ final class DrydockRepositoryOperationStatusView $item = id(new PHUIObjectItemView()) ->setHref("/drydock/operation/{$id}/") + ->setObjectName(pht('Operation %d', $id)) ->setHeader($operation->getOperationDescription($viewer)) ->setStatusIcon($icon, $name); @@ -98,6 +99,16 @@ final class DrydockRepositoryOperationStatusView } else { $item->addAttribute(pht('Operation encountered an error.')); } + + $is_dismissed = $operation->getIsDismissed(); + + $item->addAction( + id(new PHUIListItemView()) + ->setName('Dismiss') + ->setIcon('fa-times') + ->setDisabled($is_dismissed) + ->setWorkflow(true) + ->setHref("/drydock/operation/{$id}/dismiss/")); } return id(new PHUIObjectItemListView())