diff --git a/src/applications/conduit/application/PhabricatorConduitApplication.php b/src/applications/conduit/application/PhabricatorConduitApplication.php index a036860aad..f9dae71a82 100644 --- a/src/applications/conduit/application/PhabricatorConduitApplication.php +++ b/src/applications/conduit/application/PhabricatorConduitApplication.php @@ -1,65 +1,69 @@ pht('Conduit API Overview'), 'href' => PhabricatorEnv::getDoclink('Conduit API Overview'), ), ); } public function getName() { return pht('Conduit'); } public function getShortDescription() { return pht('Developer API'); } public function getTitleGlyph() { return "\xE2\x87\xB5"; } public function getApplicationGroup() { return self::GROUP_DEVELOPER; } public function getApplicationOrder() { return 0.100; } public function getRoutes() { return array( '/conduit/' => array( - '(?:query/(?P[^/]+)/)?' => 'PhabricatorConduitListController', + $this->getQueryRoutePattern() => 'PhabricatorConduitListController', 'method/(?P[^/]+)/' => 'PhabricatorConduitConsoleController', - 'log/(?:query/(?P[^/]+)/)?' => - 'PhabricatorConduitLogController', - 'log/view/(?P[^/]+)/' => 'PhabricatorConduitLogController', - 'token/' => 'PhabricatorConduitTokenController', - 'token/edit/(?:(?P\d+)/)?' => - 'PhabricatorConduitTokenEditController', - 'token/terminate/(?:(?P\d+)/)?' => - 'PhabricatorConduitTokenTerminateController', + 'log/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorConduitLogController', + 'view/(?P[^/]+)/' => 'PhabricatorConduitLogController', + ), + 'token/' => array( + '' => 'PhabricatorConduitTokenController', + 'edit/(?:(?P\d+)/)?' => + 'PhabricatorConduitTokenEditController', + 'terminate/(?:(?P\d+)/)?' => + 'PhabricatorConduitTokenTerminateController', + ), 'login/' => 'PhabricatorConduitTokenHandshakeController', ), '/api/(?P[^/]+)' => 'PhabricatorConduitAPIController', ); } } diff --git a/src/applications/conduit/query/PhabricatorConduitLogQuery.php b/src/applications/conduit/query/PhabricatorConduitLogQuery.php index 0dacd9406d..6f0f6131d8 100644 --- a/src/applications/conduit/query/PhabricatorConduitLogQuery.php +++ b/src/applications/conduit/query/PhabricatorConduitLogQuery.php @@ -1,82 +1,104 @@ callerPHIDs = $phids; return $this; } public function withMethods(array $methods) { $this->methods = $methods; return $this; } public function withMethodStatuses(array $statuses) { $this->methodStatuses = $statuses; return $this; } + public function withEpochBetween($epoch_min, $epoch_max) { + $this->epochMin = $epoch_min; + $this->epochMax = $epoch_max; + return $this; + } + public function newResultObject() { return new PhabricatorConduitMethodCallLog(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->callerPHIDs !== null) { $where[] = qsprintf( $conn, 'callerPHID IN (%Ls)', $this->callerPHIDs); } if ($this->methods !== null) { $where[] = qsprintf( $conn, 'method IN (%Ls)', $this->methods); } if ($this->methodStatuses !== null) { $statuses = array_fuse($this->methodStatuses); $methods = id(new PhabricatorConduitMethodQuery()) ->setViewer($this->getViewer()) ->execute(); $method_names = array(); foreach ($methods as $method) { $status = $method->getMethodStatus(); if (isset($statuses[$status])) { $method_names[] = $method->getAPIMethodName(); } } if (!$method_names) { throw new PhabricatorEmptyQueryException(); } $where[] = qsprintf( $conn, 'method IN (%Ls)', $method_names); } + if ($this->epochMin !== null) { + $where[] = qsprintf( + $conn, + 'dateCreated >= %d', + $this->epochMin); + } + + if ($this->epochMax !== null) { + $where[] = qsprintf( + $conn, + 'dateCreated <= %d', + $this->epochMax); + } + return $where; } public function getQueryApplicationClass() { return 'PhabricatorConduitApplication'; } } diff --git a/src/applications/conduit/query/PhabricatorConduitLogSearchEngine.php b/src/applications/conduit/query/PhabricatorConduitLogSearchEngine.php index 20d034168d..ab86ae6669 100644 --- a/src/applications/conduit/query/PhabricatorConduitLogSearchEngine.php +++ b/src/applications/conduit/query/PhabricatorConduitLogSearchEngine.php @@ -1,208 +1,276 @@ newQuery(); if ($map['callerPHIDs']) { $query->withCallerPHIDs($map['callerPHIDs']); } if ($map['methods']) { $query->withMethods($map['methods']); } if ($map['statuses']) { $query->withMethodStatuses($map['statuses']); } + if ($map['epochMin'] || $map['epochMax']) { + $query->withEpochBetween( + $map['epochMin'], + $map['epochMax']); + } + return $query; } protected function buildCustomSearchFields() { return array( id(new PhabricatorUsersSearchField()) ->setKey('callerPHIDs') ->setLabel(pht('Callers')) ->setAliases(array('caller', 'callers')) ->setDescription(pht('Find calls by specific users.')), id(new PhabricatorSearchStringListField()) ->setKey('methods') ->setLabel(pht('Methods')) ->setDescription(pht('Find calls to specific methods.')), id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') ->setLabel(pht('Method Status')) ->setAliases(array('status')) ->setDescription( pht('Find calls to stable, unstable, or deprecated methods.')) ->setOptions(ConduitAPIMethod::getMethodStatusMap()), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Called After')) + ->setKey('epochMin'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Called Before')) + ->setKey('epochMax'), ); } protected function getURI($path) { return '/conduit/log/'.$path; } protected function getBuiltinQueryNames() { $names = array(); $viewer = $this->requireViewer(); if ($viewer->isLoggedIn()) { $names['viewer'] = pht('My Calls'); $names['viewerdeprecated'] = pht('My Deprecated Calls'); } $names['all'] = pht('All Call Logs'); $names['deprecated'] = pht('Deprecated Call Logs'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer = $this->requireViewer(); $viewer_phid = $viewer->getPHID(); $deprecated = array( ConduitAPIMethod::METHOD_STATUS_DEPRECATED, ); switch ($query_key) { case 'viewer': return $query ->setParameter('callerPHIDs', array($viewer_phid)); case 'viewerdeprecated': return $query ->setParameter('callerPHIDs', array($viewer_phid)) ->setParameter('statuses', $deprecated); case 'deprecated': return $query ->setParameter('statuses', $deprecated); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } + protected function newExportFields() { + $viewer = $this->requireViewer(); + + return array( + id(new PhabricatorPHIDExportField()) + ->setKey('callerPHID') + ->setLabel(pht('Caller PHID')), + id(new PhabricatorStringExportField()) + ->setKey('caller') + ->setLabel(pht('Caller')), + id(new PhabricatorStringExportField()) + ->setKey('method') + ->setLabel(pht('Method')), + id(new PhabricatorIntExportField()) + ->setKey('duration') + ->setLabel(pht('Call Duration (us)')), + id(new PhabricatorStringExportField()) + ->setKey('error') + ->setLabel(pht('Error')), + ); + } + + protected function newExportData(array $logs) { + $viewer = $this->requireViewer(); + + $phids = array(); + foreach ($logs as $log) { + if ($log->getCallerPHID()) { + $phids[] = $log->getCallerPHID(); + } + } + $handles = $viewer->loadHandles($phids); + + $export = array(); + foreach ($logs as $log) { + $caller_phid = $log->getCallerPHID(); + if ($caller_phid) { + $caller_name = $handles[$caller_phid]->getName(); + } else { + $caller_name = null; + } + + $map = array( + 'callerPHID' => $caller_phid, + 'caller' => $caller_name, + 'method' => $log->getMethod(), + 'duration' => (int)$log->getDuration(), + 'error' => $log->getError(), + ); + + $export[] = $map; + } + + return $export; + } + protected function renderResultList( array $logs, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($logs, 'PhabricatorConduitMethodCallLog'); $viewer = $this->requireViewer(); $methods = id(new PhabricatorConduitMethodQuery()) ->setViewer($viewer) ->execute(); $methods = mpull($methods, null, 'getAPIMethodName'); Javelin::initBehavior('phabricator-tooltips'); $viewer = $this->requireViewer(); $rows = array(); foreach ($logs as $log) { $caller_phid = $log->getCallerPHID(); if ($caller_phid) { $caller = $viewer->renderHandle($caller_phid); } else { $caller = null; } $method = idx($methods, $log->getMethod()); if ($method) { $method_status = $method->getMethodStatus(); } else { $method_status = null; } switch ($method_status) { case ConduitAPIMethod::METHOD_STATUS_STABLE: $status = null; break; case ConduitAPIMethod::METHOD_STATUS_UNSTABLE: $status = id(new PHUIIconView()) ->setIcon('fa-exclamation-triangle yellow') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Unstable'), )); break; case ConduitAPIMethod::METHOD_STATUS_DEPRECATED: $status = id(new PHUIIconView()) ->setIcon('fa-exclamation-triangle red') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Deprecated'), )); break; default: $status = id(new PHUIIconView()) ->setIcon('fa-question-circle') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Unknown ("%s")', $method_status), )); break; } $rows[] = array( $status, $log->getMethod(), $caller, $log->getError(), pht('%s us', new PhutilNumber($log->getDuration())), phabricator_datetime($log->getDateCreated(), $viewer), ); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( null, pht('Method'), pht('Caller'), pht('Error'), pht('Duration'), pht('Date'), )) ->setColumnClasses( array( null, 'pri', null, 'wide right', null, null, )); return id(new PhabricatorApplicationSearchResultView()) ->setTable($table) ->setNoDataString(pht('No matching calls in log.')); } }