diff --git a/resources/sql/autopatches/20150930.drydock.log.1.sql b/resources/sql/autopatches/20150930.drydock.log.1.sql new file mode 100644 index 0000000000..e84859b718 --- /dev/null +++ b/resources/sql/autopatches/20150930.drydock.log.1.sql @@ -0,0 +1,25 @@ +TRUNCATE {$NAMESPACE}_drydock.drydock_log; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + DROP resourceID; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + DROP leaseID; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + DROP message; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD blueprintPHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD resourcePHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD leasePHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD type VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; + +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 5df54593ee..e662fea9e6 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -1,102 +1,105 @@ pht('Drydock User Guide'), 'href' => PhabricatorEnv::getDoclink('Drydock User Guide'), ), ); } public function getRoutes() { return array( '/drydock/' => array( '' => 'DrydockConsoleController', - 'blueprint/' => array( + '(?Pblueprint)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockBlueprintListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockBlueprintViewController', '(?Pdisable|enable)/' => 'DrydockBlueprintDisableController', 'resources/(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', + 'logs/(?:query/(?P[^/]+)/)?' => + 'DrydockLogListController', ), 'create/' => 'DrydockBlueprintCreateController', 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', ), - 'resource/' => array( + '(?Presource)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockResourceViewController', 'release/' => 'DrydockResourceReleaseController', 'leases/(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', + 'logs/(?:query/(?P[^/]+)/)?' => + 'DrydockLogListController', ), ), - 'lease/' => array( + '(?Please)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockLeaseListController', '(?P[1-9]\d*)/' => array( '' => 'DrydockLeaseViewController', 'release/' => 'DrydockLeaseReleaseController', + 'logs/(?:query/(?P[^/]+)/)?' => + 'DrydockLogListController', ), ), - 'log/' => array( - '(?:query/(?P[^/]+)/)?' => 'DrydockLogListController', - ), ), ); } protected function getCustomCapabilities() { return array( DrydockDefaultViewCapability::CAPABILITY => array( 'template' => DrydockBlueprintPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), DrydockDefaultEditCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'template' => DrydockBlueprintPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), DrydockCreateBlueprintsCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index f58767c4fc..b7e39a49a4 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -1,339 +1,299 @@ log($ex->getMessage()); - } - - - /** - * @task log - */ - protected function log($message) { - self::writeLog(null, null, $message); - } - - - /** - * @task log - */ - public static function writeLog( - DrydockResource $resource = null, - DrydockLease $lease = null, - $message = null) { - - $log = id(new DrydockLog()) - ->setEpoch(time()) - ->setMessage($message); - - if ($resource) { - $log->setResourceID($resource->getID()); - } - - if ($lease) { - $log->setLeaseID($lease->getID()); - } - - $log->save(); - } - - public static function getAllBlueprintImplementations() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->execute(); } public static function getNamedImplementation($class) { return idx(self::getAllBlueprintImplementations(), $class); } protected function newResourceTemplate( DrydockBlueprint $blueprint, $name) { $resource = id(new DrydockResource()) ->setBlueprintPHID($blueprint->getPHID()) ->attachBlueprint($blueprint) ->setType($this->getType()) ->setStatus(DrydockResourceStatus::STATUS_PENDING) ->setName($name); // Pre-allocate the resource PHID. $resource->setPHID($resource->generatePHID()); return $resource; } protected function newLease(DrydockBlueprint $blueprint) { return id(new DrydockLease()); } protected function requireActiveLease(DrydockLease $lease) { $lease_status = $lease->getStatus(); switch ($lease_status) { case DrydockLeaseStatus::STATUS_ACQUIRED: // TODO: Temporary failure. throw new Exception(pht('Lease still activating.')); case DrydockLeaseStatus::STATUS_ACTIVE: return; default: // TODO: Permanent failure. throw new Exception(pht('Lease in bad state.')); } } } diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 6991e18fa2..7102962600 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -1,170 +1,178 @@ getViewer(); $id = $request->getURIData('id'); $blueprint = id(new DrydockBlueprintQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$blueprint) { return new Aphront404Response(); } $title = $blueprint->getBlueprintName(); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) ->setPolicyObject($blueprint); if ($blueprint->getIsDisabled()) { $header->setStatus('fa-ban', 'red', pht('Disabled')); } else { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } $actions = $this->buildActionListView($blueprint); $properties = $this->buildPropertyListView($blueprint, $actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID())); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $field_list = PhabricatorCustomField::getObjectFields( $blueprint, PhabricatorCustomField::ROLE_VIEW); $field_list ->setViewer($viewer) ->readFieldsFromStorage($blueprint); $field_list->appendFieldsToPropertyList( $blueprint, $viewer, $properties); $resource_box = $this->buildResourceBox($blueprint); $timeline = $this->buildTransactionTimeline( $blueprint, new DrydockBlueprintTransactionQuery()); $timeline->setShouldTerminate(true); + $log_query = id(new DrydockLogQuery()) + ->withBlueprintPHIDs(array($blueprint->getPHID())); + + $log_box = $this->buildLogBox( + $log_query, + $this->getApplicationURI("blueprint/{$id}/logs/query/all/")); + return $this->buildApplicationPage( array( $crumbs, $object_box, $resource_box, + $log_box, $timeline, ), array( 'title' => $title, )); } private function buildActionListView(DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $id = $blueprint->getID(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($blueprint); $edit_uri = $this->getApplicationURI("blueprint/edit/{$id}/"); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $blueprint, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setHref($edit_uri) ->setName(pht('Edit Blueprint')) ->setIcon('fa-pencil') ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); if (!$blueprint->getIsDisabled()) { $disable_name = pht('Disable Blueprint'); $disable_icon = 'fa-ban'; $disable_uri = $this->getApplicationURI("blueprint/{$id}/disable/"); } else { $disable_name = pht('Enable Blueprint'); $disable_icon = 'fa-check'; $disable_uri = $this->getApplicationURI("blueprint/{$id}/enable/"); } $view->addAction( id(new PhabricatorActionView()) ->setHref($disable_uri) ->setName($disable_name) ->setIcon($disable_icon) ->setWorkflow(true) ->setDisabled(!$can_edit)); return $view; } private function buildPropertyListView( DrydockBlueprint $blueprint, PhabricatorActionListView $actions) { $view = new PHUIPropertyListView(); $view->setActionList($actions); $view->addProperty( pht('Type'), $blueprint->getImplementation()->getBlueprintName()); return $view; } private function buildResourceBox(DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $resources = id(new DrydockResourceQuery()) ->setViewer($viewer) ->withBlueprintPHIDs(array($blueprint->getPHID())) ->withStatuses( array( DrydockResourceStatus::STATUS_PENDING, DrydockResourceStatus::STATUS_ACTIVE, )) ->setLimit(100) ->execute(); $resource_list = id(new DrydockResourceListView()) ->setUser($viewer) ->setResources($resources) ->render() ->setNoDataString(pht('This blueprint has no active resources.')); $id = $blueprint->getID(); $resources_uri = "blueprint/{$id}/resources/query/all/"; $resources_uri = $this->getApplicationURI($resources_uri); $resource_header = id(new PHUIHeaderView()) ->setHeader(pht('Active Resources')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setHref($resources_uri) ->setIconFont('fa-search') ->setText(pht('View All Resources'))); return id(new PHUIObjectBoxView()) ->setHeader($resource_header) ->setObjectList($resource_list); } } diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index e0130bdf56..760334cbdf 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -1,88 +1,115 @@ buildSideNavView()->getMenu(); } protected function buildLocksTab($owner_phid) { $locks = DrydockSlotLock::loadLocks($owner_phid); $rows = array(); foreach ($locks as $lock) { $rows[] = array( $lock->getID(), $lock->getLockKey(), ); } $table = id(new AphrontTableView($rows)) ->setNoDataString(pht('No slot locks held.')) ->setHeaders( array( pht('ID'), pht('Lock Key'), )) ->setColumnClasses( array( null, 'wide', )); return id(new PHUIPropertyListView()) ->addRawContent($table); } protected function buildCommandsTab($target_phid) { $viewer = $this->getViewer(); $commands = id(new DrydockCommandQuery()) ->setViewer($viewer) ->withTargetPHIDs(array($target_phid)) ->execute(); $consumed_yes = id(new PHUIIconView()) ->setIconFont('fa-check green'); $consumed_no = id(new PHUIIconView()) ->setIconFont('fa-clock-o grey'); $rows = array(); foreach ($commands as $command) { $rows[] = array( $command->getID(), $viewer->renderHandle($command->getAuthorPHID()), $command->getCommand(), ($command->getIsConsumed() ? $consumed_yes : $consumed_no), phabricator_datetime($command->getDateCreated(), $viewer), ); } $table = id(new AphrontTableView($rows)) ->setNoDataString(pht('No commands issued.')) ->setHeaders( array( pht('ID'), pht('From'), pht('Command'), null, pht('Date'), )) ->setColumnClasses( array( null, null, 'wide', null, null, )); return id(new PHUIPropertyListView()) ->addRawContent($table); } + protected function buildLogBox(DrydockLogQuery $query, $all_uri) { + $viewer = $this->getViewer(); + + $logs = $query + ->setViewer($viewer) + ->setLimit(100) + ->execute(); + + $log_table = id(new DrydockLogListView()) + ->setUser($viewer) + ->setLogs($logs) + ->render(); + + $log_header = id(new PHUIHeaderView()) + ->setHeader(pht('Logs')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref($all_uri) + ->setIconFont('fa-search') + ->setText(pht('View All Logs'))); + + return id(new PHUIObjectBoxView()) + ->setHeader($log_header) + ->setTable($log_table); + } + } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index af893ca49b..b9cf592313 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -1,158 +1,147 @@ getViewer(); $id = $request->getURIData('id'); $lease = id(new DrydockLeaseQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needUnconsumedCommands(true) ->executeOne(); if (!$lease) { return new Aphront404Response(); } - $lease_uri = $this->getApplicationURI('lease/'.$lease->getID().'/'); + $id = $lease->getID(); + $lease_uri = $this->getApplicationURI("lease/{$id}/"); $title = pht('Lease %d', $lease->getID()); $header = id(new PHUIHeaderView()) ->setHeader($title); if ($lease->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } $actions = $this->buildActionListView($lease); $properties = $this->buildPropertyListView($lease, $actions); - $pager = new PHUIPagerView(); - $pager->setURI(new PhutilURI($lease_uri), 'offset'); - $pager->setOffset($request->getInt('offset')); + $log_query = id(new DrydockLogQuery()) + ->withLeasePHIDs(array($lease->getPHID())); - $logs = id(new DrydockLogQuery()) - ->setViewer($viewer) - ->withLeaseIDs(array($lease->getID())) - ->executeWithOffsetPager($pager); - - $log_table = id(new DrydockLogListView()) - ->setUser($viewer) - ->setLogs($logs) - ->render(); - $log_table->appendChild($pager); + $log_box = $this->buildLogBox( + $log_query, + $this->getApplicationURI("lease/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $lease_uri); $locks = $this->buildLocksTab($lease->getPHID()); $commands = $this->buildCommandsTab($lease->getPHID()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - $log_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Lease Logs')) - ->setTable($log_table); - return $this->buildApplicationPage( array( $crumbs, $object_box, $log_box, ), array( 'title' => $title, )); } private function buildActionListView(DrydockLease $lease) { $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($lease); $id = $lease->getID(); $can_release = $lease->canRelease(); if ($lease->isReleasing()) { $can_release = false; } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $lease, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Release Lease')) ->setIcon('fa-times') ->setHref($this->getApplicationURI("/lease/{$id}/release/")) ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); return $view; } private function buildPropertyListView( DrydockLease $lease, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); $view->setActionList($actions); $view->addProperty( pht('Status'), DrydockLeaseStatus::getNameForStatus($lease->getStatus())); $view->addProperty( pht('Resource Type'), $lease->getResourceType()); $owner_phid = $lease->getOwnerPHID(); if ($owner_phid) { $owner_display = $viewer->renderHandle($owner_phid); } else { $owner_display = phutil_tag('em', array(), pht('No Owner')); } $view->addProperty(pht('Owner'), $owner_display); $resource_phid = $lease->getResourcePHID(); if ($resource_phid) { $resource_display = $viewer->renderHandle($resource_phid); } else { $resource_display = phutil_tag('em', array(), pht('No Resource')); } $view->addProperty(pht('Resource'), $resource_display); $until = $lease->getUntil(); if ($until) { $until_display = phabricator_datetime($until, $viewer); } else { $until_display = phutil_tag('em', array(), pht('Never')); } $view->addProperty(pht('Expires'), $until_display); $attributes = $lease->getAttributes(); if ($attributes) { $view->addSectionHeader( pht('Attributes'), 'fa-list-ul'); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } } return $view; } } diff --git a/src/applications/drydock/controller/DrydockLogController.php b/src/applications/drydock/controller/DrydockLogController.php index 0e28c62cb4..5ae87e6aad 100644 --- a/src/applications/drydock/controller/DrydockLogController.php +++ b/src/applications/drydock/controller/DrydockLogController.php @@ -1,27 +1,119 @@ blueprint = $blueprint; + return $this; + } + + public function getBlueprint() { + return $this->blueprint; + } + + public function setResource(DrydockResource $resource) { + $this->resource = $resource; + return $this; + } + + public function getResource() { + return $this->resource; + } + + public function setLease(DrydockLease $lease) { + $this->lease = $lease; + return $this; + } + + public function getLease() { + return $this->lease; + } + public function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new DrydockLogSearchEngine()) - ->setViewer($this->getRequest()->getUser()) - ->addNavigationItems($nav->getMenu()); + $engine = id(new DrydockLogSearchEngine()) + ->setViewer($this->getRequest()->getUser()); + + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $engine->setBlueprint($blueprint); + } + + $resource = $this->getResource(); + if ($resource) { + $engine->setResource($resource); + } + + $lease = $this->getLease(); + if ($lease) { + $engine->setLease($lease); + } + + $engine->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Logs'), - $this->getApplicationURI('log/')); + + $blueprint = $this->getBlueprint(); + $resource = $this->getResource(); + $lease = $this->getLease(); + if ($blueprint) { + $id = $blueprint->getID(); + + $crumbs->addTextCrumb( + pht('Blueprints'), + $this->getApplicationURI('blueprint/')); + + $crumbs->addTextCrumb( + $blueprint->getBlueprintName(), + $this->getApplicationURI("blueprint/{$id}/")); + + $crumbs->addTextCrumb( + pht('Logs'), + $this->getApplicationURI("blueprint/{$id}/logs/")); + } else if ($resource) { + $id = $resource->getID(); + + $crumbs->addTextCrumb( + pht('Resources'), + $this->getApplicationURI('resource/')); + + $crumbs->addTextCrumb( + $resource->getName(), + $this->getApplicationURI("resource/{$id}/")); + + $crumbs->addTextCrumb( + pht('Logs'), + $this->getApplicationURI("resource/{$id}/logs/")); + } else if ($lease) { + $id = $lease->getID(); + + $crumbs->addTextCrumb( + pht('Leases'), + $this->getApplicationURI('lease/')); + + $crumbs->addTextCrumb( + $lease->getLeaseName(), + $this->getApplicationURI("lease/{$id}/")); + + $crumbs->addTextCrumb( + pht('Logs'), + $this->getApplicationURI("lease/{$id}/logs/")); + } + return $crumbs; } } diff --git a/src/applications/drydock/controller/DrydockLogListController.php b/src/applications/drydock/controller/DrydockLogListController.php index aecf77dc77..b5e4d4ff0c 100644 --- a/src/applications/drydock/controller/DrydockLogListController.php +++ b/src/applications/drydock/controller/DrydockLogListController.php @@ -1,21 +1,63 @@ getViewer(); - $querykey = $request->getURIData('queryKey'); + $engine = new DrydockLogSearchEngine(); + + $id = $request->getURIData('id'); + $type = $request->getURIData('type'); + switch ($type) { + case 'blueprint': + $blueprint = id(new DrydockBlueprintQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$blueprint) { + return new Aphront404Response(); + } + $engine->setBlueprint($blueprint); + $this->setBlueprint($blueprint); + break; + case 'resource': + $resource = id(new DrydockResourceQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$resource) { + return new Aphront404Response(); + } + $engine->setResource($resource); + $this->setResource($resource); + break; + case 'lease': + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$lease) { + return new Aphront404Response(); + } + $engine->setLease($lease); + $this->setLease($lease); + break; + default: + return new Aphront404Response(); + } + + $query_key = $request->getURIData('queryKey'); $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new DrydockLogSearchEngine()) + ->setQueryKey($query_key) + ->setSearchEngine($engine) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } } diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 23f81c5225..f97081e673 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -1,195 +1,183 @@ getViewer(); $id = $request->getURIData('id'); $resource = id(new DrydockResourceQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needUnconsumedCommands(true) ->executeOne(); if (!$resource) { return new Aphront404Response(); } $title = pht('Resource %s %s', $resource->getID(), $resource->getName()); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($resource) ->setHeader($title); if ($resource->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } $actions = $this->buildActionListView($resource); $properties = $this->buildPropertyListView($resource, $actions); - $resource_uri = 'resource/'.$resource->getID().'/'; - $resource_uri = $this->getApplicationURI($resource_uri); + $id = $resource->getID(); + $resource_uri = $this->getApplicationURI("resource/{$id}/"); - $pager = new PHUIPagerView(); - $pager->setURI(new PhutilURI($resource_uri), 'offset'); - $pager->setOffset($request->getInt('offset')); + $log_query = id(new DrydockLogQuery()) + ->withResourcePHIDs(array($resource->getPHID())); - $logs = id(new DrydockLogQuery()) - ->setViewer($viewer) - ->withResourceIDs(array($resource->getID())) - ->executeWithOffsetPager($pager); - - $log_table = id(new DrydockLogListView()) - ->setUser($viewer) - ->setLogs($logs) - ->render(); - $log_table->appendChild($pager); + $log_box = $this->buildLogBox( + $log_query, + $this->getApplicationURI("resource/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); $locks = $this->buildLocksTab($resource->getPHID()); $commands = $this->buildCommandsTab($resource->getPHID()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); $lease_box = $this->buildLeaseBox($resource); - $log_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Resource Logs')) - ->setTable($log_table); - return $this->buildApplicationPage( array( $crumbs, $object_box, $lease_box, $log_box, ), array( 'title' => $title, )); } private function buildActionListView(DrydockResource $resource) { $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($resource); $can_release = $resource->canRelease(); if ($resource->isReleasing()) { $can_release = false; } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $resource, PhabricatorPolicyCapability::CAN_EDIT); $uri = '/resource/'.$resource->getID().'/release/'; $uri = $this->getApplicationURI($uri); $view->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Release Resource')) ->setIcon('fa-times') ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); return $view; } private function buildPropertyListView( DrydockResource $resource, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setActionList($actions); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); $view->addProperty( pht('Status'), $status); $until = $resource->getUntil(); if ($until) { $until_display = phabricator_datetime($until, $viewer); } else { $until_display = phutil_tag('em', array(), pht('Never')); } $view->addProperty(pht('Expires'), $until_display); $view->addProperty( pht('Resource Type'), $resource->getType()); $view->addProperty( pht('Blueprint'), $viewer->renderHandle($resource->getBlueprintPHID())); $attributes = $resource->getAttributes(); if ($attributes) { $view->addSectionHeader( pht('Attributes'), 'fa-list-ul'); foreach ($attributes as $key => $value) { $view->addProperty($key, $value); } } return $view; } private function buildLeaseBox(DrydockResource $resource) { $viewer = $this->getViewer(); $leases = id(new DrydockLeaseQuery()) ->setViewer($viewer) ->withResourcePHIDs(array($resource->getPHID())) ->withStatuses( array( DrydockLeaseStatus::STATUS_PENDING, DrydockLeaseStatus::STATUS_ACQUIRED, DrydockLeaseStatus::STATUS_ACTIVE, )) ->setLimit(100) ->execute(); $id = $resource->getID(); $leases_uri = "resource/{$id}/leases/query/all/"; $leases_uri = $this->getApplicationURI($leases_uri); $lease_header = id(new PHUIHeaderView()) ->setHeader(pht('Active Leases')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setHref($leases_uri) ->setIconFont('fa-search') ->setText(pht('View All Leases'))); $lease_list = id(new DrydockLeaseListView()) ->setUser($viewer) ->setLeases($leases) ->render() ->setNoDataString(pht('This resource has no active leases.')); return id(new PHUIObjectBoxView()) ->setHeader($lease_header) ->setObjectList($lease_list); } } diff --git a/src/applications/drydock/query/DrydockLogQuery.php b/src/applications/drydock/query/DrydockLogQuery.php index 47a6795463..00980edb4d 100644 --- a/src/applications/drydock/query/DrydockLogQuery.php +++ b/src/applications/drydock/query/DrydockLogQuery.php @@ -1,113 +1,126 @@ resourceIDs = $ids; + public function withBlueprintPHIDs(array $phids) { + $this->blueprintPHIDs = $phids; return $this; } - public function withLeaseIDs(array $ids) { - $this->leaseIDs = $ids; + public function withResourcePHIDs(array $phids) { + $this->resourcePHIDs = $phids; return $this; } + public function withLeasePHIDs(array $phids) { + $this->leasePHIDs = $phids; + return $this; + } + + public function newResultObject() { + return new DrydockLog(); + } + protected function loadPage() { - $table = new DrydockLog(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT log.* FROM %T log %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } - protected function willFilterPage(array $logs) { - $resource_ids = array_filter(mpull($logs, 'getResourceID')); - if ($resource_ids) { + protected function didFilterPage(array $logs) { + $blueprint_phids = array_filter(mpull($logs, 'getBlueprintPHID')); + if ($blueprint_phids) { + $blueprints = id(new DrydockBlueprintQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($blueprint_phids) + ->execute(); + $blueprints = mpull($blueprints, null, 'getPHID'); + } else { + $blueprints = array(); + } + + foreach ($logs as $key => $log) { + $blueprint = null; + $blueprint_phid = $log->getBlueprintPHID(); + if ($blueprint_phid) { + $blueprint = idx($blueprints, $blueprint_phid); + } + $log->attachBlueprint($blueprint); + } + + $resource_phids = array_filter(mpull($logs, 'getResourcePHID')); + if ($resource_phids) { $resources = id(new DrydockResourceQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) - ->withIDs(array_unique($resource_ids)) + ->withPHIDs($resource_phids) ->execute(); + $resources = mpull($resources, null, 'getPHID'); } else { $resources = array(); } foreach ($logs as $key => $log) { $resource = null; - if ($log->getResourceID()) { - $resource = idx($resources, $log->getResourceID()); - if (!$resource) { - unset($logs[$key]); - continue; - } + $resource_phid = $log->getResourcePHID(); + if ($resource_phid) { + $resource = idx($resources, $resource_phid); } $log->attachResource($resource); } - $lease_ids = array_filter(mpull($logs, 'getLeaseID')); - if ($lease_ids) { + $lease_phids = array_filter(mpull($logs, 'getLeasePHID')); + if ($lease_phids) { $leases = id(new DrydockLeaseQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) - ->withIDs(array_unique($lease_ids)) + ->withPHIDs($lease_phids) ->execute(); + $leases = mpull($leases, null, 'getPHID'); } else { $leases = array(); } foreach ($logs as $key => $log) { $lease = null; - if ($log->getLeaseID()) { - $lease = idx($leases, $log->getLeaseID()); - if (!$lease) { - unset($logs[$key]); - continue; - } + $lease_phid = $log->getLeasePHID(); + if ($lease_phid) { + $lease = idx($leases, $lease_phid); } $log->attachLease($lease); } - // These logs are meaningless and their policies aren't computable. They - // shouldn't exist, but throw them away if they do. - foreach ($logs as $key => $log) { - if (!$log->getResource() && !$log->getLease()) { - unset($logs[$key]); - } - } - return $logs; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->resourceIDs !== null) { + if ($this->blueprintPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'resourceID IN (%Ld)', - $this->resourceIDs); + $conn, + 'blueprintPHID IN (%Ls)', + $this->blueprintPHIDs); } - if ($this->leaseIDs !== null) { + if ($this->resourcePHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'leaseID IN (%Ld)', - $this->leaseIDs); + $conn, + 'resourcePHID IN (%Ls)', + $this->resourcePHIDs); } - $where[] = $this->buildPagingClause($conn_r); + if ($this->leasePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'leasePHID IN (%Ls)', + $this->leasePHIDs); + } - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/drydock/query/DrydockLogSearchEngine.php b/src/applications/drydock/query/DrydockLogSearchEngine.php index 13777031d6..43b1511c01 100644 --- a/src/applications/drydock/query/DrydockLogSearchEngine.php +++ b/src/applications/drydock/query/DrydockLogSearchEngine.php @@ -1,117 +1,138 @@ blueprint = $blueprint; + return $this; } - public function getApplicationClassName() { - return 'PhabricatorDrydockApplication'; + public function getBlueprint() { + return $this->blueprint; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $query = new PhabricatorSavedQuery(); + public function setResource(DrydockResource $resource) { + $this->resource = $resource; + return $this; + } - $query->setParameter( - 'resourcePHIDs', - $this->readListFromRequest($request, 'resources')); - $query->setParameter( - 'leasePHIDs', - $this->readListFromRequest($request, 'leases')); + public function getResource() { + return $this->resource; + } - return $query; + public function setLease(DrydockLease $lease) { + $this->lease = $lease; + return $this; } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $resource_phids = $saved->getParameter('resourcePHIDs', array()); - $lease_phids = $saved->getParameter('leasePHIDs', array()); + public function getLease() { + return $this->lease; + } - // TODO: Change logs to use PHIDs instead of IDs. - $resource_ids = array(); - $lease_ids = array(); + public function canUseInPanelContext() { + // Prevent use on Dashboard panels since all log queries currently need a + // parent object and these don't seem particularly useful in any case. + return false; + } - if ($resource_phids) { - $resource_ids = id(new DrydockResourceQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($resource_phids) - ->execute(); - $resource_ids = mpull($resource_ids, 'getID'); - } + public function getResultTypeDescription() { + return pht('Drydock Logs'); + } - if ($lease_phids) { - $lease_ids = id(new DrydockLeaseQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($lease_phids) - ->execute(); - $lease_ids = mpull($lease_ids, 'getID'); - } + public function getApplicationClassName() { + return 'PhabricatorDrydockApplication'; + } + public function newQuery() { $query = new DrydockLogQuery(); - if ($resource_ids) { - $query->withResourceIDs($resource_ids); + + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $query->withBlueprintPHIDs(array($blueprint->getPHID())); + } + + $resource = $this->getResource(); + if ($resource) { + $query->withResourcePHIDs(array($resource->getPHID())); } - if ($lease_ids) { - $query->withLeaseIDs($lease_ids); + + $lease = $this->getLease(); + if ($lease) { + $query->withLeasePHIDs(array($lease->getPHID())); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new DrydockResourceDatasource()) - ->setName('resources') - ->setLabel(pht('Resources')) - ->setValue($saved->getParameter('resourcePHIDs', array()))) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new DrydockLeaseDatasource()) - ->setName('leases') - ->setLabel(pht('Leases')) - ->setValue($saved->getParameter('leasePHIDs', array()))); + return $query; + } + + protected function buildCustomSearchFields() { + return array(); } protected function getURI($path) { - return '/drydock/log/'.$path; + $blueprint = $this->getBlueprint(); + if ($blueprint) { + $id = $blueprint->getID(); + return "/drydock/blueprint/{$id}/logs/{$path}"; + } + + $resource = $this->getResource(); + if ($resource) { + $id = $resource->getID(); + return "/drydock/resource/{$id}/logs/{$path}"; + } + + $lease = $this->getLease(); + if ($lease) { + $id = $lease->getID(); + return "/drydock/lease/{$id}/logs/{$path}"; + } + + throw new Exception( + pht( + 'Search engine has no blueprint, resource, or lease.')); } protected function getBuiltinQueryNames() { return array( 'all' => pht('All Logs'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $logs, PhabricatorSavedQuery $query, array $handles) { $list = id(new DrydockLogListView()) ->setUser($this->requireViewer()) ->setLogs($logs); $result = new PhabricatorApplicationSearchResultView(); $result->setTable($list); return $result; } } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index af0b322b62..b16efbabbb 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -1,406 +1,427 @@ releaseOnDestruction = true; return $this; } public function __destruct() { if (!$this->releaseOnDestruction) { return; } if (!$this->canRelease()) { return; } $actor = PhabricatorUser::getOmnipotentUser(); $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); $command = DrydockCommand::initializeNewCommand($actor) ->setTargetPHID($this->getPHID()) ->setAuthorPHID($drydock_phid) ->setCommand(DrydockCommand::COMMAND_RELEASE) ->save(); $this->scheduleUpdate(); } public function getLeaseName() { return pht('Lease %d', $this->getID()); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'attributes' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'status' => 'text32', 'until' => 'epoch?', 'resourceType' => 'text128', 'ownerPHID' => 'phid?', 'resourcePHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_resource' => array( 'columns' => array('resourcePHID', 'status'), ), ), ) + parent::getConfiguration(); } public function setAttribute($key, $value) { $this->attributes[$key] = $value; return $this; } public function getAttribute($key, $default = null) { return idx($this->attributes, $key, $default); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(DrydockLeasePHIDType::TYPECONST); } public function getInterface($type) { return $this->getResource()->getInterface($this, $type); } public function getResource() { return $this->assertAttached($this->resource); } public function attachResource(DrydockResource $resource = null) { $this->resource = $resource; return $this; } public function hasAttachedResource() { return ($this->resource !== null); } public function getUnconsumedCommands() { return $this->assertAttached($this->unconsumedCommands); } public function attachUnconsumedCommands(array $commands) { $this->unconsumedCommands = $commands; return $this; } public function isReleasing() { foreach ($this->getUnconsumedCommands() as $command) { if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) { return true; } } return false; } public function queueForActivation() { if ($this->getID()) { throw new Exception( pht('Only new leases may be queued for activation!')); } $this ->setStatus(DrydockLeaseStatus::STATUS_PENDING) ->save(); $task = PhabricatorWorker::scheduleTask( 'DrydockAllocatorWorker', array( 'leasePHID' => $this->getPHID(), ), array( 'objectPHID' => $this->getPHID(), )); return $this; } public function isActivating() { switch ($this->getStatus()) { case DrydockLeaseStatus::STATUS_PENDING: case DrydockLeaseStatus::STATUS_ACQUIRED: return true; } return false; } public function isActive() { switch ($this->getStatus()) { case DrydockLeaseStatus::STATUS_ACTIVE: return true; } return false; } public function waitUntilActive() { while (true) { $lease = $this->reload(); if (!$lease) { throw new Exception(pht('Failed to reload lease.')); } $status = $lease->getStatus(); switch ($status) { case DrydockLeaseStatus::STATUS_ACTIVE: return; case DrydockLeaseStatus::STATUS_RELEASED: throw new Exception(pht('Lease has already been released!')); case DrydockLeaseStatus::STATUS_DESTROYED: throw new Exception(pht('Lease has already been destroyed!')); case DrydockLeaseStatus::STATUS_BROKEN: throw new Exception(pht('Lease has been broken!')); case DrydockLeaseStatus::STATUS_PENDING: case DrydockLeaseStatus::STATUS_ACQUIRED: break; default: throw new Exception( pht( 'Lease has unknown status "%s".', $status)); } sleep(1); } } public function setActivateWhenAcquired($activate) { $this->activateWhenAcquired = true; return $this; } public function needSlotLock($key) { $this->slotLocks[] = $key; return $this; } public function acquireOnResource(DrydockResource $resource) { $expect_status = DrydockLeaseStatus::STATUS_PENDING; $actual_status = $this->getStatus(); if ($actual_status != $expect_status) { throw new Exception( pht( 'Trying to acquire a lease on a resource which is in the wrong '. 'state: status must be "%s", actually "%s".', $expect_status, $actual_status)); } if ($this->activateWhenAcquired) { $new_status = DrydockLeaseStatus::STATUS_ACTIVE; } else { $new_status = DrydockLeaseStatus::STATUS_ACQUIRED; } if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) { if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { throw new Exception( pht( 'Trying to acquire an active lease on a pending resource. '. 'You can not immediately activate leases on resources which '. 'need time to start up.')); } } $this->openTransaction(); $this ->setResourcePHID($resource->getPHID()) ->setStatus($new_status) ->save(); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); $this->slotLocks = array(); $this->saveTransaction(); $this->isAcquired = true; if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) { $this->didActivate(); } return $this; } public function isAcquiredLease() { return $this->isAcquired; } public function activateOnResource(DrydockResource $resource) { $expect_status = DrydockLeaseStatus::STATUS_ACQUIRED; $actual_status = $this->getStatus(); if ($actual_status != $expect_status) { throw new Exception( pht( 'Trying to activate a lease which has the wrong status: status '. 'must be "%s", actually "%s".', $expect_status, $actual_status)); } if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) { // TODO: Be stricter about this? throw new Exception( pht( 'Trying to activate a lease on a pending resource.')); } $this->openTransaction(); $this ->setStatus(DrydockLeaseStatus::STATUS_ACTIVE) ->save(); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); $this->slotLocks = array(); $this->saveTransaction(); $this->isActivated = true; $this->didActivate(); return $this; } public function isActivatedLease() { return $this->isActivated; } public function canRelease() { if (!$this->getID()) { return false; } switch ($this->getStatus()) { case DrydockLeaseStatus::STATUS_RELEASED: case DrydockLeaseStatus::STATUS_DESTROYED: return false; default: return true; } } public function canUpdate() { switch ($this->getStatus()) { case DrydockLeaseStatus::STATUS_ACTIVE: return true; default: return false; } } public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockLeaseUpdateWorker', array( 'leasePHID' => $this->getPHID(), 'isExpireTask' => ($epoch !== null), ), array( 'objectPHID' => $this->getPHID(), 'delayUntil' => ($epoch ? (int)$epoch : null), )); } public function setAwakenTaskIDs(array $ids) { $this->setAttribute('internal.awakenTaskIDs', $ids); return $this; } private function didActivate() { $viewer = PhabricatorUser::getOmnipotentUser(); $need_update = false; + // TODO: This is just a placeholder to get some data in the table. + $this->logEvent('activated'); + $commands = id(new DrydockCommandQuery()) ->setViewer($viewer) ->withTargetPHIDs(array($this->getPHID())) ->withConsumed(false) ->execute(); if ($commands) { $need_update = true; } if ($need_update) { $this->scheduleUpdate(); } $expires = $this->getUntil(); if ($expires) { $this->scheduleUpdate($expires); } $awaken_ids = $this->getAttribute('internal.awakenTaskIDs'); if (is_array($awaken_ids) && $awaken_ids) { PhabricatorWorker::awakenTaskIDs($awaken_ids); } } + public function logEvent($type, array $data = array()) { + $log = id(new DrydockLog()) + ->setEpoch(PhabricatorTime::getNow()) + ->setType($type) + ->setData($data); + + $log->setLeasePHID($this->getPHID()); + + $resource = $this->getResource(); + if ($resource) { + $log->setResourcePHID($resource->getPHID()); + $log->setBlueprintPHID($resource->getBlueprintPHID()); + } + + return $log->save(); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { if ($this->getResource()) { return $this->getResource()->getPolicy($capability); } // TODO: Implement reasonable policies. return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { if ($this->getResource()) { return $this->getResource()->hasAutomaticCapability($capability, $viewer); } return false; } public function describeAutomaticCapability($capability) { return pht('Leases inherit policies from the resources they lease.'); } } diff --git a/src/applications/drydock/storage/DrydockLog.php b/src/applications/drydock/storage/DrydockLog.php index 36d310c510..5d75d82c65 100644 --- a/src/applications/drydock/storage/DrydockLog.php +++ b/src/applications/drydock/storage/DrydockLog.php @@ -1,82 +1,115 @@ false, + self::CONFIG_SERIALIZATION => array( + 'data' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( - 'resourceID' => 'id?', - 'leaseID' => 'id?', - 'message' => 'text', + 'blueprintPHID' => 'phid?', + 'resourcePHID' => 'phid?', + 'leasePHID' => 'phid?', + 'type' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( - 'resourceID' => array( - 'columns' => array('resourceID', 'epoch'), + 'key_blueprint' => array( + 'columns' => array('blueprintPHID', 'type'), + ), + 'key_resource' => array( + 'columns' => array('resourcePHID', 'type'), ), - 'leaseID' => array( - 'columns' => array('leaseID', 'epoch'), + 'key_lease' => array( + 'columns' => array('leasePHID', 'type'), ), 'epoch' => array( 'columns' => array('epoch'), ), ), ) + parent::getConfiguration(); } + public function attachBlueprint(DrydockBlueprint $blueprint = null) { + $this->blueprint = $blueprint; + return $this; + } + + public function getBlueprint() { + return $this->assertAttached($this->blueprint); + } + public function attachResource(DrydockResource $resource = null) { $this->resource = $resource; return $this; } public function getResource() { return $this->assertAttached($this->resource); } public function attachLease(DrydockLease $lease = null) { $this->lease = $lease; return $this; } public function getLease() { return $this->assertAttached($this->lease); } + public function isComplete() { + if ($this->getBlueprintPHID() && !$this->getBlueprint()) { + return false; + } + + if ($this->getResourcePHID() && !$this->getResource()) { + return false; + } + + if ($this->getLeasePHID() && !$this->getLease()) { + return false; + } + + return true; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { - if ($this->getResource()) { - return $this->getResource()->getPolicy($capability); - } - return $this->getLease()->getPolicy($capability); + // NOTE: We let you see that logs exist no matter what, but don't actually + // show you log content unless you can see all of the associated objects. + return PhabricatorPolicies::getMostOpenPolicy(); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - if ($this->getResource()) { - return $this->getResource()->hasAutomaticCapability($capability, $viewer); - } - return $this->getLease()->hasAutomaticCapability($capability, $viewer); + return false; } public function describeAutomaticCapability($capability) { - return pht('Logs inherit the policy of their resources.'); + return pht( + 'To view log details, you must be able to view the associated '. + 'blueprint, resource and lease.'); } } diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php index 22e280939b..f6560270a0 100644 --- a/src/applications/drydock/view/DrydockLogListView.php +++ b/src/applications/drydock/view/DrydockLogListView.php @@ -1,74 +1,88 @@ logs = $logs; return $this; } public function render() { $logs = $this->logs; $viewer = $this->getUser(); $view = new PHUIObjectItemListView(); $rows = array(); foreach ($logs as $log) { - $resource_uri = '/drydock/resource/'.$log->getResourceID().'/'; - $lease_uri = '/drydock/lease/'.$log->getLeaseID().'/'; + $blueprint_phid = $log->getBlueprintPHID(); + if ($blueprint_phid) { + $blueprint = $viewer->renderHandle($blueprint_phid); + } else { + $blueprint = null; + } + + $resource_phid = $log->getResourcePHID(); + if ($resource_phid) { + $resource = $viewer->renderHandle($resource_phid); + } else { + $resource = null; + } + + $lease_phid = $log->getLeasePHID(); + if ($lease_phid) { + $lease = $viewer->renderHandle($lease_phid); + } else { + $lease = null; + } - $resource_name = $log->getResourceID(); - if ($log->getResourceID() !== null) { - $resource_name = $log->getResource()->getName(); + if ($log->isComplete()) { + // TODO: This is a placeholder. + $type = $log->getType(); + $data = print_r($log->getData(), true); + } else { + $type = phutil_tag('em', array(), pht('Restricted')); + $data = phutil_tag( + 'em', + array(), + pht('You do not have permission to view this log event.')); } $rows[] = array( - phutil_tag( - 'a', - array( - 'href' => $resource_uri, - ), - $resource_name), - phutil_tag( - 'a', - array( - 'href' => $lease_uri, - ), - $log->getLeaseID()), - $log->getMessage(), + $blueprint, + $resource, + $lease, + $type, + $data, phabricator_datetime($log->getEpoch(), $viewer), ); } $table = new AphrontTableView($rows); $table->setDeviceReadyTable(true); $table->setHeaders( array( + pht('Blueprint'), pht('Resource'), pht('Lease'), - pht('Message'), + pht('Type'), + pht('Data'), pht('Date'), )); - $table->setShortHeaders( - array( - pht('R'), - pht('L'), - pht('Message'), - '', - )); $table->setColumnClasses( array( + '', + '', '', '', 'wide', '', )); return $table; } }