EBernhardson (WMF) has uploaded a new change for review. https://gerrit.wikimedia.org/r/79011
Change subject: New topic action: thread-history ...................................................................... New topic action: thread-history New index on PostRevision indexes revisions by the root post of their topic tree. Creates a query-only 'topic_root' field which when queried with options matching the index will return the sorted history of that topic. Change-Id: I6f0553f06a98e42f739bcad388cbec340db3718d --- M Flow.php M container.php M includes/Block/Summary.php M includes/Block/Topic.php M includes/Data/ObjectManager.php M includes/Data/RevisionStorage.php M includes/Model/AbstractRevision.php M includes/Repository/TreeRepository.php A templates/topic-history.html.php M templates/topic.html.php 10 files changed, 158 insertions(+), 12 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow refs/changes/11/79011/1 diff --git a/Flow.php b/Flow.php index f5be5aa..b22ced1 100755 --- a/Flow.php +++ b/Flow.php @@ -74,8 +74,9 @@ $wgAutoloadClasses['Flow\Data\ObjectManager'] = $dir . 'includes/Data/ObjectManager.php'; $wgAutoloadClasses['Flow\Data\LifecycleHandler'] = $dir . 'includes/Data/ObjectManager.php'; $wgAutoloadClasses['Flow\Data\Index'] = $dir . 'includes/Data/ObjectManager.php'; -$wgAutoloadClasses['Flow\Data\UniqueIndex'] = $dir . 'includes/Data/ObjectManager.php'; -$wgAutoloadClasses['Flow\Data\SecondaryIndex'] = $dir . 'includes/Data/ObjectManager.php'; +$wgAutoloadClasses['Flow\Data\UniqueFeatureIndex'] = $dir . 'includes/Data/ObjectManager.php'; +$wgAutoloadClasses['Flow\Data\TopKIndex'] = $dir . 'includes/Data/ObjectManager.php'; +$wgAutoloadClasses['Flow\Data\TopicHistoryIndex'] = $dir . 'includes/Data/ObjectManager.php'; $wgAutoloadClasses['Flow\Data\ObjectStorage'] = $dir . 'includes/Data/ObjectManager.php'; $wgAutoloadClasses['Flow\Data\BasicDbStorage'] = $dir . 'includes/Data/ObjectManager.php'; $wgAutoloadClasses['Flow\Data\ObjectMapper'] = $dir . 'includes/Data/ObjectManager.php'; diff --git a/container.php b/container.php index 401f24f..1e9a0f3 100644 --- a/container.php +++ b/container.php @@ -56,6 +56,7 @@ use Flow\Data\SummaryRevisionStorage; use Flow\Data\UniqueFeatureIndex; use Flow\Data\TopKIndex; +use Flow\Data\TopicHistoryIndex; use Flow\Data\ObjectMapper; use Flow\Data\ObjectManager; @@ -182,6 +183,7 @@ $pk = new UniqueFeatureIndex( $cache, $storage, 'flow_revision:pk', array( 'rev_id' ) ); $indexes = array( $pk, + // revision history new TopKIndex( $cache, $storage, 'flow_revision:descendant', array( 'tree_rev_descendant_id' ), array( @@ -194,6 +196,7 @@ return $row['rev_parent_id'] === null; }, ) ), + // most recent revision new TopKIndex( $cache, $storage, 'flow_revision:latest_post', array( 'tree_rev_descendant_id' ), array( @@ -205,6 +208,21 @@ return $row['rev_parent_id'] === null; }, ) ), + // thread history -- to keep a history by thread we have to know what thread every post + // belongs to, not just its parent. TopicHistoryIndex is a slight tweak to TopKIndex + // using TreeRepository for extra information and stuffing it into thread_root while indexing + new TopicHistoryIndex( $cache, $storage, $c['repository.tree'], 'flow_revision:thread', + array( 'thread_root' ), + array( + 'limit' => 500, + 'sort' => 'rev_id', + 'order' => 'DESC', + 'shallow' => $pk, + 'create' => function( array $row ) { + // if the post has no parent it is a root of a new thread + return $row['tree_parent_id'] === null; + }, + ) ) ); return new ObjectManager( $mapper, $storage, $indexes ); diff --git a/includes/Block/Summary.php b/includes/Block/Summary.php index 9080b6e..5496e72 100644 --- a/includes/Block/Summary.php +++ b/includes/Block/Summary.php @@ -47,7 +47,7 @@ $this->errors['prev_revision'] = wfMessage( 'flow-prev-revision-mismatch' )->params( $this->submitted['prev_revision'], $this->summary->getRevisionId()->getHex() ); } // this isnt really part of validate, but we want the error-rendering template to see the users edited summary - $this->summary = $this->summary->newNextRevision( $this->user, $this->submitted['content'] ); + $this->summary = $this->summary->newNextRevision( $this->user, $this->submitted['content'], 'flow-edit-summary' ); } else { if ( empty( $this->submitted['prev_revision'] ) ) { // this isnt really part of validate either, should validate be renamed or should this logic be redone? diff --git a/includes/Block/Topic.php b/includes/Block/Topic.php index b40622d..9f67572 100644 --- a/includes/Block/Topic.php +++ b/includes/Block/Topic.php @@ -19,6 +19,8 @@ protected $newRevision; protected $requestedPost; + // POST actions, GET do not need to be listed + // unrecognized GET actions fallback to 'view' protected $supportedActions = array( 'edit-post', 'delete-post', 'restore-post', 'reply', 'delete-topic', @@ -80,7 +82,7 @@ throw new \Exception( 'No revision associated with workflow?' ); } - $this->topicTitle = $topicTitle->newNextRevision( $this->user, $this->submitted['content'] ); + $this->topicTitle = $topicTitle->newNextRevision( $this->user, $this->submitted['content'], 'flow-edit-title' ); } } @@ -177,7 +179,7 @@ } $post = $this->loadRequestedPost( $this->submitted['postId'] ); if ( $post ) { - $this->newRevision = $post->newNextRevision( $this->user, $this->parsedContent ); + $this->newRevision = $post->newNextRevision( $this->user, $this->parsedContent, 'flow-edit-post' ); } else { $this->errors['edit-post'] = wfMessage( 'flow-post-not-found' ); } @@ -256,6 +258,13 @@ case 'post-history': return $this->renderPostHistory( $templating, $options, $return ); + case 'topic-history': + return $templating->render( "flow:topic-history.html.php", array( + 'block' => $this, + 'topic' => $this->workflow, + 'history' => $this->loadThreadHistory(), + ) ); + case 'edit-post': return $this->renderEditPost( $templating, $options, $return ); @@ -282,7 +291,7 @@ return $templating->render( "flow:post-history.html.php", array( 'block' => $this, 'topic' => $this->workflow, - 'history' => $$this->getHistory( $options['postId'] ), + 'history' => $this->getHistory( $options['postId'] ), ), $return ); } @@ -449,6 +458,19 @@ return $this->topicTitle; } + protected function loadThreadHistory() { + $found = $this->storage->find( + 'PostRevision', + array( 'topic_root' => $this->workflow->getId() ), + array( 'sort' => 'rev_id', 'order' => 'DESC', 'limit' => 100 ) + ); + if ( $found ) { + return $found; + } else { + die( 'kersplat' ); + } + } + protected function loadRequestedPost( $postId ) { if ( !isset( $this->requestedPost[$postId] ) ) { $found = $this->storage->find( diff --git a/includes/Data/ObjectManager.php b/includes/Data/ObjectManager.php index b11f91c..b460160 100644 --- a/includes/Data/ObjectManager.php +++ b/includes/Data/ObjectManager.php @@ -765,6 +765,11 @@ if ( count( $queries ) === 0 ) { return $results; } + + return $this->backingStoreFindMulti( $queries, $idxToKey, $results ); + } + + protected function backingStoreFindMulti( array $queries, array $idxToKey, array $results = array() ) { // query backing store $stored = $this->storage->findMulti( $queries, $this->queryOptions() ); // map store results to cache key diff --git a/includes/Data/RevisionStorage.php b/includes/Data/RevisionStorage.php index 334e4fc..f628cb1 100644 --- a/includes/Data/RevisionStorage.php +++ b/includes/Data/RevisionStorage.php @@ -52,7 +52,7 @@ } $retval = array(); foreach ( $res as $row ) { - $retval[$row->rev_id] = (array) $row; + $retval[UUID::create( $row->rev_id )->getHex()] = (array) $row; } return $retval; } @@ -441,6 +441,75 @@ } /** + * Slight tweak to the TopKIndex uses additional info from TreeRepository to build the cache + */ +class TopicHistoryIndex extends TopKIndex { + + protected $treeRepository; + + public function __construct( BufferedCache $cache, PostRevisionStorage $storage, TreeRepository $treeRepo, $prefix, array $indexed, array $options = array() ) { + if ( $indexed !== array( 'topic_root' ) ) { + throw new \Exception( __CLASS__ . ' is hardcoded to only index topic_root' ); + } + parent::__construct( $cache, $storage, $prefix, $indexed, $options ); + $this->treeRepository = $treeRepo; + } + + public function onAfterInsert( $object, array $new ) { + $new['topic_root'] = $this->treeRepository->findRoot( UUID::create( $new['tree_rev_descendant_id'] ) ); + parent::onAfterInsert( $object, $new ); + } + + public function onAfterUpdate( $object, array $old, array $new ) { + $old['topic_root'] = $new['thread_root'] = $this->treeRepository->findRoot( UUID::create( $old['tree_rev_descendant_id'] ) ); + parent::onAfterUpdate( $object, $old, $new ); + } + + public function onAfterRemove( $object, array $old ) { + $old['topic_root'] = $this->treeRepository->findRoot( UUID::create( $old['tree_rev_descendant_id'] ) ); + parent::onAfterRemove( $object, $old ); + } + + public function backingStoreFindMulti( array $queries, array $idxToKey, array $retval = array() ) { + // all queries are for roots( guaranteed by constructor), so anything that falls + // through and has to be queried from storage will actually need to be doing a + // special condition either joining against flow_tree_node or first collecting the + // subtree node lists and then doing a big IN condition + + // This isn't a hot path(should be pre-populated into index) but we still dont want + // horrible performance + + $roots = array(); + foreach ( $queries as $idx => $features ) { + $roots[] = UUID::create( $features['topic_root'] ); + } + $nodeList = $this->treeRepository->fetchSubtreeNodeList( $roots ); + + $descendantQueries = array(); + foreach ( $queries as $idx => $features ) { + $list = array(); + $nodes = $nodeList[$features['topic_root']->getHex()]; + $descendantQueries[$idx] = array( + 'tree_rev_descendant_id' => UUID::convertUUIDs( $nodes ), + ); + } + + $res = $this->storage->findMulti( $descendantQueries, $this->queryOptions() ); + if ( !$res ) { + return false; + } + foreach ( $res as $idx => $rows ) { + $retval[$idx] = $rows; + $this->cache->add( $idxToKey[$idx], $this->rowCompactor->compactRows( $rows ) ); + unset( $queries[$idx] ); + } + if ( $queries ) { + // Log something about not finding everything? + } + return $retval; + } +} +/** * This assists in performing client-side 1-to-1 joins. It collects the foreign key * from a multi-dimensional array, queries a callable for the foreign key values and * then returns the source data with related data merged in. diff --git a/includes/Model/AbstractRevision.php b/includes/Model/AbstractRevision.php index 1e1f341..7b87cfc 100644 --- a/includes/Model/AbstractRevision.php +++ b/includes/Model/AbstractRevision.php @@ -70,9 +70,10 @@ return $obj; } - public function newNextRevision( User $user, $content ) { + public function newNextRevision( User $user, $content, $comment ) { $obj = $this->newNullRevision( $user ); $obj->setContent( $content ); + $obj->comment = $comment; return $obj; } diff --git a/includes/Repository/TreeRepository.php b/includes/Repository/TreeRepository.php index 6c91115..105233f 100644 --- a/includes/Repository/TreeRepository.php +++ b/includes/Repository/TreeRepository.php @@ -142,8 +142,12 @@ if ( !$res ) { return null; } + $path = array(); foreach ( $res as $row ) { $path[$row->tree_depth] = UUID::create( $row->tree_ancestor_id ); + } + if ( !$path ) { + throw new \Exception( 'No root path found? Is this a root already? ' . $descendant->getHex() ); } ksort( $path ); $path = array_reverse( $path ); @@ -211,7 +215,13 @@ $roots, array( $this, 'fetchSubtreeNodeListFromDb' ) ); - return $res; + + // $idx is a binary UUID + $retval = array(); + foreach ( $res as $idx => $val ) { + $retval[UUID::create( $idx )->getHex()] = $val; + } + return $retval; } public function fetchSubtreeNodeListFromDb( array $roots ) { diff --git a/templates/topic-history.html.php b/templates/topic-history.html.php new file mode 100644 index 0000000..f741204 --- /dev/null +++ b/templates/topic-history.html.php @@ -0,0 +1,12 @@ +<?php + +echo wfMessage( 'flow-topic-history' )->escaped(); +echo '<ul>'; +foreach ( $history as $revision ) { + echo '<li>' + . $revision->getRevisionId()->getHex() . ' : ' + . $revision->getUserText() . ' : ' + . $revision->getComment() + . '</li>'; +} +echo '</ul>'; diff --git a/templates/topic.html.php b/templates/topic.html.php index fd18ce3..106f0b2 100644 --- a/templates/topic.html.php +++ b/templates/topic.html.php @@ -91,9 +91,17 @@ }; echo Html::element( 'h4', array(), $root->getContent() ), - Html::rawElement( 'a', array( - 'href' => $this->generateUrl( $root->getPostId(), 'edit-title' ) - ), wfMessage( 'flow-action-edit-title' ) ); + '<ul>', + '<li>', + Html::rawElement( 'a', array( + 'href' => $this->generateUrl( $root->getPostId(), 'topic-history' ) + ), wfMessage( 'flow-action-topic-history' ) ), + '</li><li>', + Html::rawElement( 'a', array( + 'href' => $this->generateUrl( $root->getPostId(), 'edit-title' ) + ), wfMessage( 'flow-action-edit-title' ) ), + '</li>', + '<ul>'; foreach( $root->getChildren() as $child ) { $renderPost( $child ); -- To view, visit https://gerrit.wikimedia.org/r/79011 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I6f0553f06a98e42f739bcad388cbec340db3718d Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Flow Gerrit-Branch: master Gerrit-Owner: EBernhardson (WMF) <ebernhard...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits