Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15414589
D20531.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
18 KB
Referenced Files
None
Subscribers
None
D20531.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -3265,6 +3265,9 @@
'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php',
'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php',
'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php',
+ 'PhabricatorFeedTransactionListController' => 'applications/feed/controller/PhabricatorFeedTransactionListController.php',
+ 'PhabricatorFeedTransactionQuery' => 'applications/feed/query/PhabricatorFeedTransactionQuery.php',
+ 'PhabricatorFeedTransactionSearchEngine' => 'applications/feed/query/PhabricatorFeedTransactionSearchEngine.php',
'PhabricatorFerretEngine' => 'applications/search/ferret/PhabricatorFerretEngine.php',
'PhabricatorFerretEngineTestCase' => 'applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php',
'PhabricatorFerretFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php',
@@ -9330,6 +9333,9 @@
'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO',
'PhabricatorFeedStoryPublisher' => 'Phobject',
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
+ 'PhabricatorFeedTransactionListController' => 'PhabricatorFeedController',
+ 'PhabricatorFeedTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorFeedTransactionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorFerretEngine' => 'Phobject',
'PhabricatorFerretEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorFerretFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
diff --git a/src/applications/feed/application/PhabricatorFeedApplication.php b/src/applications/feed/application/PhabricatorFeedApplication.php
--- a/src/applications/feed/application/PhabricatorFeedApplication.php
+++ b/src/applications/feed/application/PhabricatorFeedApplication.php
@@ -31,6 +31,10 @@
'/feed/' => array(
'(?P<id>\d+)/' => 'PhabricatorFeedDetailController',
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorFeedListController',
+ 'transactions/' => array(
+ $this->getQueryRoutePattern()
+ => 'PhabricatorFeedTransactionListController',
+ ),
),
);
}
diff --git a/src/applications/feed/controller/PhabricatorFeedController.php b/src/applications/feed/controller/PhabricatorFeedController.php
--- a/src/applications/feed/controller/PhabricatorFeedController.php
+++ b/src/applications/feed/controller/PhabricatorFeedController.php
@@ -1,24 +1,4 @@
<?php
-abstract class PhabricatorFeedController extends PhabricatorController {
-
- protected function buildSideNavView() {
- $user = $this->getRequest()->getUser();
-
- $nav = new AphrontSideNavFilterView();
- $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
-
- id(new PhabricatorFeedSearchEngine())
- ->setViewer($user)
- ->addNavigationItems($nav->getMenu());
-
- $nav->selectFilter(null);
-
- return $nav;
- }
-
- public function buildApplicationMenu() {
- return $this->buildSideNavView()->getMenu();
- }
-
-}
+abstract class PhabricatorFeedController
+ extends PhabricatorController {}
diff --git a/src/applications/feed/controller/PhabricatorFeedListController.php b/src/applications/feed/controller/PhabricatorFeedListController.php
--- a/src/applications/feed/controller/PhabricatorFeedListController.php
+++ b/src/applications/feed/controller/PhabricatorFeedListController.php
@@ -1,20 +1,27 @@
<?php
-final class PhabricatorFeedListController extends PhabricatorFeedController {
+final class PhabricatorFeedListController
+ extends PhabricatorFeedController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
- $querykey = $request->getURIData('queryKey');
+ $navigation = array();
- $controller = id(new PhabricatorApplicationSearchController())
- ->setQueryKey($querykey)
- ->setSearchEngine(new PhabricatorFeedSearchEngine())
- ->setNavigation($this->buildSideNavView());
+ $navigation[] = id(new PHUIListItemView())
+ ->setType(PHUIListItemView::TYPE_LABEL)
+ ->setName(pht('Transactions'));
- return $this->delegateToController($controller);
+ $navigation[] = id(new PHUIListItemView())
+ ->setName(pht('Transaction Logs'))
+ ->setHref($this->getApplicationURI('transactions/'));
+
+ return id(new PhabricatorFeedSearchEngine())
+ ->setController($this)
+ ->setNavigationItems($navigation)
+ ->buildResponse();
}
}
diff --git a/src/applications/feed/controller/PhabricatorFeedTransactionListController.php b/src/applications/feed/controller/PhabricatorFeedTransactionListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/feed/controller/PhabricatorFeedTransactionListController.php
@@ -0,0 +1,16 @@
+<?php
+
+final class PhabricatorFeedTransactionListController
+ extends PhabricatorFeedController {
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function handleRequest(AphrontRequest $request) {
+ return id(new PhabricatorFeedTransactionSearchEngine())
+ ->setController($this)
+ ->buildResponse();
+ }
+
+}
diff --git a/src/applications/feed/query/PhabricatorFeedTransactionQuery.php b/src/applications/feed/query/PhabricatorFeedTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/feed/query/PhabricatorFeedTransactionQuery.php
@@ -0,0 +1,178 @@
+<?php
+
+final class PhabricatorFeedTransactionQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $phids;
+ private $createdMin;
+ private $createdMax;
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withDateCreatedBetween($min, $max) {
+ $this->createdMin = $min;
+ $this->createdMax = $max;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $queries = $this->newTransactionQueries();
+
+ $xactions = array();
+
+ if ($this->shouldLimitResults()) {
+ $limit = $this->getRawResultLimit();
+ if (!$limit) {
+ $limit = null;
+ }
+ } else {
+ $limit = null;
+ }
+
+ // We're doing a bit of manual work to get paging working, because this
+ // query aggregates the results of a large number of subqueries.
+
+ // Overall, we're ordering transactions by "<dateCreated, phid>". Ordering
+ // by PHID is not very meaningful, but we don't need the ordering to be
+ // especially meaningful, just consistent. Using PHIDs is easy and does
+ // everything we need it to technically.
+
+ // To actually configure paging, if we have an external cursor, we load
+ // the internal cursor first. Then we pass it to each subquery and the
+ // subqueries pretend they just loaded a page where it was the last object.
+ // This configures their queries properly and we can aggregate a cohesive
+ // set of results by combining all the queries.
+
+ $cursor = $this->getExternalCursorString();
+ if ($cursor !== null) {
+ $cursor_object = $this->newInternalCursorFromExternalCursor($cursor);
+ } else {
+ $cursor_object = null;
+ }
+
+ $is_reversed = $this->getIsQueryOrderReversed();
+
+ $created_min = $this->createdMin;
+ $created_max = $this->createdMax;
+
+ $xaction_phids = $this->phids;
+
+ foreach ($queries as $query) {
+ $query->withDateCreatedBetween($created_min, $created_max);
+
+ if ($xaction_phids !== null) {
+ $query->withPHIDs($xaction_phids);
+ }
+
+ if ($limit !== null) {
+ $query->setLimit($limit);
+ }
+
+ if ($cursor_object !== null) {
+ $query
+ ->setAggregatePagingCursor($cursor_object)
+ ->setIsQueryOrderReversed($is_reversed);
+ }
+
+ $query->setOrder('global');
+
+ $query_xactions = $query->execute();
+ foreach ($query_xactions as $query_xaction) {
+ $xactions[] = $query_xaction;
+ }
+
+ $xactions = msortv($xactions, 'newGlobalSortVector');
+ if ($is_reversed) {
+ $xactions = array_reverse($xactions);
+ }
+
+ if ($limit !== null) {
+ $xactions = array_slice($xactions, 0, $limit);
+
+ // If we've found enough transactions to fill up the entire requested
+ // page size, we can narrow the search window: transactions after the
+ // last transaction we've found so far can't possibly be part of the
+ // result set.
+
+ if (count($xactions) === $limit) {
+ $last_date = last($xactions)->getDateCreated();
+ if ($is_reversed) {
+ if ($created_max === null) {
+ $created_max = $last_date;
+ } else {
+ $created_max = min($created_max, $last_date);
+ }
+ } else {
+ if ($created_min === null) {
+ $created_min = $last_date;
+ } else {
+ $created_min = max($created_min, $last_date);
+ }
+ }
+ }
+ }
+ }
+
+ return $xactions;
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorFeedApplication';
+ }
+
+ private function newTransactionQueries() {
+ $viewer = $this->getViewer();
+
+ $queries = id(new PhutilClassMapQuery())
+ ->setAncestorClass('PhabricatorApplicationTransactionQuery')
+ ->execute();
+
+ $type_map = array();
+
+ // If we're querying for specific transaction PHIDs, we only need to
+ // consider queries which may load transactions with subtypes present
+ // in the list.
+
+ // For example, if we're loading Maniphest Task transaction PHIDs, we know
+ // we only have to look at Maniphest Task transactions, since other types
+ // of objects will never have the right transaction PHIDs.
+
+ $xaction_phids = $this->phids;
+ if ($xaction_phids) {
+ foreach ($xaction_phids as $xaction_phid) {
+ $type_map[phid_get_subtype($xaction_phid)] = true;
+ }
+ }
+
+ $results = array();
+ foreach ($queries as $query) {
+ if ($type_map) {
+ $type = $query->getTemplateApplicationTransaction()
+ ->getApplicationTransactionType();
+ if (!isset($type_map[$type])) {
+ continue;
+ }
+ }
+
+ $results[] = id(clone $query)
+ ->setViewer($viewer)
+ ->setParentQuery($this);
+ }
+
+ return $results;
+ }
+
+ protected function newExternalCursorStringForResult($object) {
+ return (string)$object->getPHID();
+ }
+
+ protected function applyExternalCursorConstraintsToQuery(
+ PhabricatorCursorPagedPolicyAwareQuery $subquery,
+ $cursor) {
+ $subquery->withPHIDs(array($cursor));
+ }
+
+}
diff --git a/src/applications/feed/query/PhabricatorFeedTransactionSearchEngine.php b/src/applications/feed/query/PhabricatorFeedTransactionSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/feed/query/PhabricatorFeedTransactionSearchEngine.php
@@ -0,0 +1,113 @@
+<?php
+
+final class PhabricatorFeedTransactionSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function getResultTypeDescription() {
+ return pht('Transactions');
+ }
+
+ public function getApplicationClassName() {
+ return 'PhabricatorFeedApplication';
+ }
+
+ public function newQuery() {
+ return new PhabricatorFeedTransactionQuery();
+ }
+
+ protected function buildCustomSearchFields() {
+ return array();
+ }
+
+ protected function buildQueryFromParameters(array $map) {
+ $query = $this->newQuery();
+
+ return $query;
+ }
+
+ protected function getURI($path) {
+ return '/feed/transactions/'.$path;
+ }
+
+ protected function getBuiltinQueryNames() {
+ $names = array(
+ 'all' => pht('All Transactions'),
+ );
+
+ return $names;
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+ $query = $this->newSavedQuery()
+ ->setQueryKey($query_key);
+
+ switch ($query_key) {
+ case 'all':
+ return $query;
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+ protected function renderResultList(
+ array $objects,
+ PhabricatorSavedQuery $query,
+ array $handles) {
+ assert_instances_of($objects, 'PhabricatorApplicationTransaction');
+
+ $viewer = $this->requireViewer();
+
+ $handle_phids = array();
+ foreach ($objects as $object) {
+ $author_phid = $object->getAuthorPHID();
+ if ($author_phid !== null) {
+ $handle_phids[] = $author_phid;
+ }
+ $object_phid = $object->getObjectPHID();
+ if ($object_phid !== null) {
+ $handle_phids[] = $object_phid;
+ }
+ }
+
+ $handles = $viewer->loadHandles($handle_phids);
+
+ $rows = array();
+ foreach ($objects as $object) {
+ $author_phid = $object->getAuthorPHID();
+ $object_phid = $object->getObjectPHID();
+
+ try {
+ $title = $object->getTitle();
+ } catch (Exception $ex) {
+ $title = null;
+ }
+
+ $rows[] = array(
+ $handles[$author_phid]->renderLink(),
+ $handles[$object_phid]->renderLink(),
+ AphrontTableView::renderSingleDisplayLine($title),
+ phabricator_datetime($object->getDateCreated(), $viewer),
+ );
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ pht('Actor'),
+ pht('Object'),
+ pht('Transaction'),
+ pht('Date'),
+ ))
+ ->setColumnClasses(
+ array(
+ null,
+ null,
+ 'wide',
+ 'right',
+ ));
+
+ return id(new PhabricatorApplicationSearchResultView())
+ ->setTable($table);
+ }
+
+}
diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
--- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
+++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php
@@ -9,6 +9,9 @@
private $authorPHIDs;
private $transactionTypes;
private $withComments;
+ private $createdMin;
+ private $createdMax;
+ private $aggregatePagingCursor;
private $needComments = true;
private $needHandles = true;
@@ -66,6 +69,12 @@
return $this;
}
+ public function withDateCreatedBetween($min, $max) {
+ $this->createdMin = $min;
+ $this->createdMax = $max;
+ return $this;
+ }
+
public function needComments($need) {
$this->needComments = $need;
return $this;
@@ -76,6 +85,22 @@
return $this;
}
+ public function setAggregatePagingCursor(PhabricatorQueryCursor $cursor) {
+ $this->aggregatePagingCursor = $cursor;
+ return $this;
+ }
+
+ public function getAggregatePagingCursor() {
+ return $this->aggregatePagingCursor;
+ }
+
+ protected function willExecute() {
+ $cursor_object = $this->getAggregatePagingCursor();
+ if ($cursor_object) {
+ $this->nextPage(array($cursor_object->getObject()));
+ }
+ }
+
protected function loadPage() {
$table = $this->getTemplateApplicationTransaction();
@@ -206,6 +231,20 @@
}
}
+ if ($this->createdMin !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'x.dateCreated >= %d',
+ $this->createdMin);
+ }
+
+ if ($this->createdMax !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'x.dateCreated <= %d',
+ $this->createdMax);
+ }
+
return $where;
}
@@ -262,4 +301,38 @@
return 'x';
}
+ protected function newPagingMapFromPartialObject($object) {
+ return parent::newPagingMapFromPartialObject($object) + array(
+ 'created' => $object->getDateCreated(),
+ 'phid' => $object->getPHID(),
+ );
+ }
+
+ public function getBuiltinOrders() {
+ return parent::getBuiltinOrders() + array(
+ 'global' => array(
+ 'vector' => array('created', 'phid'),
+ 'name' => pht('Global'),
+ ),
+ );
+ }
+
+ public function getOrderableColumns() {
+ return parent::getOrderableColumns() + array(
+ 'created' => array(
+ 'table' => 'x',
+ 'column' => 'dateCreated',
+ 'type' => 'int',
+ ),
+ 'phid' => array(
+ 'table' => 'x',
+ 'column' => 'phid',
+ 'type' => 'string',
+ 'reverse' => true,
+ 'unique' => true,
+ ),
+ );
+ }
+
+
}
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -1711,6 +1711,12 @@
return array($done, $undone);
}
+ public function newGlobalSortVector() {
+ return id(new PhutilSortVector())
+ ->addInt(-$this->getDateCreated())
+ ->addString($this->getPHID());
+ }
+
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
--- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
+++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
@@ -104,7 +104,7 @@
}
// Now that we made sure the viewer can actually see the object the
- // external cursor identifies, return the internal cursor the query
+ // external cursor identifies, return the internal cursor the query
// generated as a side effect while loading the object.
return $query->getInternalCursorObject();
}
@@ -134,7 +134,6 @@
);
}
-
final private function getExternalCursorStringForResult($object) {
$cursor = $this->newExternalCursorStringForResult($object);
@@ -150,7 +149,7 @@
return $cursor;
}
- final private function getExternalCursorString() {
+ final protected function getExternalCursorString() {
return $this->externalCursorString;
}
@@ -159,11 +158,11 @@
return $this;
}
- final private function getIsQueryOrderReversed() {
+ final protected function getIsQueryOrderReversed() {
return $this->isQueryOrderReversed;
}
- final private function setIsQueryOrderReversed($is_reversed) {
+ final protected function setIsQueryOrderReversed($is_reversed) {
$this->isQueryOrderReversed = $is_reversed;
return $this;
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Mar 21, 1:21 AM (1 d, 13 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7705952
Default Alt Text
D20531.diff (18 KB)
Attached To
Mode
D20531: Build a rough transaction-level view of Feed
Attached
Detach File
Event Timeline
Log In to Comment