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) <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits