diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 778c419638..176bf6509c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -373,6 +373,7 @@ return array( 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', + 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/herald/HeraldRuleEditor.js' => '91a6031b', @@ -576,6 +577,7 @@ return array( 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', + 'javelin-behavior-drydock-live-operation-status' => '901935ef', 'javelin-behavior-durable-column' => 'c72aa091', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => '38dcf3c8', @@ -1518,6 +1520,11 @@ return array( 'javelin-dom', 'javelin-stratcom', ), + '901935ef' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-request', + ), '91a6031b' => array( 'multirow-row-manager', 'javelin-install', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5c1c071c44..d00b3a377d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -885,6 +885,8 @@ phutil_register_library_map(array( 'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php', 'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php', 'DrydockRepositoryOperationSearchEngine' => 'applications/drydock/query/DrydockRepositoryOperationSearchEngine.php', + 'DrydockRepositoryOperationStatusController' => 'applications/drydock/controller/DrydockRepositoryOperationStatusController.php', + 'DrydockRepositoryOperationStatusView' => 'applications/drydock/view/DrydockRepositoryOperationStatusView.php', 'DrydockRepositoryOperationType' => 'applications/drydock/operation/DrydockRepositoryOperationType.php', 'DrydockRepositoryOperationUpdateWorker' => 'applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php', 'DrydockRepositoryOperationViewController' => 'applications/drydock/controller/DrydockRepositoryOperationViewController.php', @@ -4672,6 +4674,8 @@ phutil_register_library_map(array( 'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType', 'DrydockRepositoryOperationQuery' => 'DrydockQuery', 'DrydockRepositoryOperationSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'DrydockRepositoryOperationStatusController' => 'DrydockController', + 'DrydockRepositoryOperationStatusView' => 'AphrontView', 'DrydockRepositoryOperationType' => 'Phobject', 'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker', 'DrydockRepositoryOperationViewController' => 'DrydockController', diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 2e1cb4c931..f6c90362df 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1060,30 +1060,13 @@ final class DifferentialRevisionViewController extends DifferentialController { $operation = head(msort($operations, 'getID')); - // TODO: This is completely made up for now, give it useful information and - // a sweet progress bar. + $box_view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Active Operations')); - switch ($operation->getOperationState()) { - case DrydockRepositoryOperation::STATE_WAIT: - case DrydockRepositoryOperation::STATE_WORK: - $severity = PHUIInfoView::SEVERITY_NOTICE; - $text = pht( - 'Some sort of repository operation is currently running.'); - break; - default: - $severity = PHUIInfoView::SEVERITY_ERROR; - $text = pht( - 'Some sort of repository operation failed.'); - break; - } - - $info_view = id(new PHUIInfoView()) - ->setSeverity($severity) - ->appendChild($text); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Active Operations (EXPERIMENTAL!)')) - ->setInfoView($info_view); + return id(new DrydockRepositoryOperationStatusView()) + ->setUser($viewer) + ->setBoxView($box_view) + ->setOperation($operation); } } diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 237e4afd9a..0b109e68da 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -95,6 +95,7 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { => 'DrydockRepositoryOperationListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockRepositoryOperationViewController', + 'status/' => 'DrydockRepositoryOperationStatusController', ), ), ), diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php new file mode 100644 index 0000000000..6503886481 --- /dev/null +++ b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php @@ -0,0 +1,59 @@ +getViewer(); + $id = $request->getURIData('id'); + + $operation = id(new DrydockRepositoryOperationQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$operation) { + return new Aphront404Response(); + } + + $id = $operation->getID(); + + $status_view = id(new DrydockRepositoryOperationStatusView()) + ->setUser($viewer) + ->setOperation($operation); + + if ($request->isAjax()) { + $payload = array( + 'markup' => $status_view->renderUnderwayState(), + 'isUnderway' => $operation->isUnderway(), + ); + + return id(new AphrontAjaxResponse()) + ->setContent($payload); + } + + $title = pht('Repository Operation %d', $id); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Operations'), + $this->getApplicationURI('operation/')); + $crumbs->addTextCrumb($title); + + return $this->buildApplicationPage( + array( + $crumbs, + $status_view, + ), + array( + 'title' => array( + $title, + pht('Status'), + ), + )); + } + +} diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 050aaeeb36..df888f3f6f 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -11,6 +11,30 @@ final class DrydockLandRepositoryOperation return pht('Land Revision'); } + public function getOperationCurrentStatus( + DrydockRepositoryOperation $operation, + PhabricatorUser $viewer) { + + $target = $operation->getRepositoryTarget(); + $repository = $operation->getRepository(); + switch ($operation->getOperationState()) { + case DrydockRepositoryOperation::STATE_WAIT: + return pht( + 'Waiting to land revision into %s on %s...', + $repository->getMonogram(), + $target); + case DrydockRepositoryOperation::STATE_WORK: + return pht( + 'Landing revision into %s on %s...', + $repository->getMonogram(), + $target); + case DrydockRepositoryOperation::STATE_DONE: + return pht( + 'Revision landed into %s.', + $repository->getMonogram()); + } + } + public function applyOperation( DrydockRepositoryOperation $operation, DrydockInterface $interface) { diff --git a/src/applications/drydock/operation/DrydockRepositoryOperationType.php b/src/applications/drydock/operation/DrydockRepositoryOperationType.php index a78ed3173e..1ae6d6e4f8 100644 --- a/src/applications/drydock/operation/DrydockRepositoryOperationType.php +++ b/src/applications/drydock/operation/DrydockRepositoryOperationType.php @@ -12,6 +12,10 @@ abstract class DrydockRepositoryOperationType extends Phobject { DrydockRepositoryOperation $operation, PhabricatorUser $viewer); + abstract public function getOperationCurrentStatus( + DrydockRepositoryOperation $operation, + PhabricatorUser $viewer); + final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; diff --git a/src/applications/drydock/storage/DrydockRepositoryOperation.php b/src/applications/drydock/storage/DrydockRepositoryOperation.php index 7a8e35ea68..2ee09f3227 100644 --- a/src/applications/drydock/storage/DrydockRepositoryOperation.php +++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php @@ -142,6 +142,22 @@ final class DrydockRepositoryOperation extends DrydockDAO $viewer); } + public function getOperationCurrentStatus(PhabricatorUser $viewer) { + return $this->getImplementation()->getOperationCurrentStatus( + $this, + $viewer); + } + + public function isUnderway() { + switch ($this->getOperationState()) { + case self::STATE_WAIT: + case self::STATE_WORK: + return true; + } + + return false; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php new file mode 100644 index 0000000000..3bdb529c20 --- /dev/null +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -0,0 +1,84 @@ +operation = $operation; + return $this; + } + + public function getOperation() { + return $this->operation; + } + + public function setBoxView(PHUIObjectBoxView $box_view) { + $this->boxView = $box_view; + return $this; + } + + public function getBoxView() { + return $this->boxView; + } + + public function render() { + $viewer = $this->getUser(); + $operation = $this->getOperation(); + + $list = $this->renderUnderwayState(); + + // If the operation is currently underway, refresh the status view. + if ($operation->isUnderway()) { + $status_id = celerity_generate_unique_node_id(); + $id = $operation->getID(); + + $list->setID($status_id); + + Javelin::initBehavior( + 'drydock-live-operation-status', + array( + 'statusID' => $status_id, + 'updateURI' => "/drydock/operation/{$id}/status/", + )); + } + + $box_view = $this->getBoxView(); + if (!$box_view) { + $box_view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Operation Status')); + } + $box_view->setObjectList($list); + + return $box_view; + } + + public function renderUnderwayState() { + $viewer = $this->getUser(); + $operation = $this->getOperation(); + + $id = $operation->getID(); + + $state = $operation->getOperationState(); + $icon = DrydockRepositoryOperation::getOperationStateIcon($state); + $name = DrydockRepositoryOperation::getOperationStateName($state); + + $item = id(new PHUIObjectItemView()) + ->setHref("/drydock/operation/{$id}/") + ->setHeader($operation->getOperationDescription($viewer)) + ->setStatusIcon($icon, $name); + + if ($state != DrydockRepositoryOperation::STATE_FAIL) { + $item->addAttribute($operation->getOperationCurrentStatus($viewer)); + } else { + // TODO: Make this more useful. + $item->addAttribute(pht('Operation encountered an error.')); + } + + return id(new PHUIObjectItemListView()) + ->addItem($item); + } + +} diff --git a/webroot/rsrc/js/application/drydock/drydock-live-operation-status.js b/webroot/rsrc/js/application/drydock/drydock-live-operation-status.js new file mode 100644 index 0000000000..dec4e81796 --- /dev/null +++ b/webroot/rsrc/js/application/drydock/drydock-live-operation-status.js @@ -0,0 +1,32 @@ +/** + * @provides javelin-behavior-drydock-live-operation-status + * @requires javelin-behavior + * javelin-dom + * javelin-request + * @javelin + */ + +JX.behavior('drydock-live-operation-status', function(config) { + var node = JX.$(config.statusID); + + function update() { + new JX.Request(config.updateURI, onresponse) + .send(); + } + + function onresponse(r) { + var new_node = JX.$H(r.markup).getNode(); + JX.DOM.replace(node, new_node); + node = new_node; + + if (r.isUnderway) { + poll(); + } + } + + function poll() { + setTimeout(update, 1000); + } + + poll(); +});