1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 09:18:48 +02:00

Ponder - make feed stories and emails a bit better

Summary: Also some random cleanup now and again. Note reply handler stuff is kind of bojangles bad right now. It didn't work before though either so hey.

Test Plan: asked questions, answered questions, edited answers... the feed pleased my eye

Reviewers: epriestley

Reviewed By: epriestley

CC: Korvin, aran

Maniphest Tasks: T3653

Differential Revision: https://secure.phabricator.com/D7027
This commit is contained in:
Bob Trahan 2013-09-18 15:15:25 -07:00
parent 77143d5600
commit b1dfbda741
21 changed files with 326 additions and 109 deletions

View file

@ -1938,12 +1938,12 @@ phutil_register_library_map(array(
'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php',
'PonderAnswerTransactionComment' => 'applications/ponder/storage/PonderAnswerTransactionComment.php',
'PonderAnswerTransactionQuery' => 'applications/ponder/query/PonderAnswerTransactionQuery.php',
'PonderAnswerViewController' => 'applications/ponder/controller/PonderAnswerViewController.php',
'PonderComment' => 'applications/ponder/storage/PonderComment.php',
'PonderCommentQuery' => 'applications/ponder/query/PonderCommentQuery.php',
'PonderConstants' => 'applications/ponder/constants/PonderConstants.php',
'PonderController' => 'applications/ponder/controller/PonderController.php',
'PonderDAO' => 'applications/ponder/storage/PonderDAO.php',
'PonderEditor' => 'applications/ponder/editor/PonderEditor.php',
'PonderPHIDTypeAnswer' => 'applications/ponder/phid/PonderPHIDTypeAnswer.php',
'PonderPHIDTypeQuestion' => 'applications/ponder/phid/PonderPHIDTypeQuestion.php',
'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php',
@ -1964,6 +1964,7 @@ phutil_register_library_map(array(
'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php',
'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php',
'PonderSearchIndexer' => 'applications/ponder/search/PonderSearchIndexer.php',
'PonderTransactionFeedStory' => 'applications/ponder/feed/PonderTransactionFeedStory.php',
'PonderVotableInterface' => 'applications/ponder/storage/PonderVotableInterface.php',
'PonderVotableView' => 'applications/ponder/view/PonderVotableView.php',
'PonderVote' => 'applications/ponder/constants/PonderVote.php',
@ -4149,14 +4150,13 @@ phutil_register_library_map(array(
),
'PonderAnswerCommentController' => 'PonderController',
'PonderAnswerEditController' => 'PonderController',
'PonderAnswerEditor' => 'PhabricatorApplicationTransactionEditor',
'PonderAnswerEditor' => 'PonderEditor',
'PonderAnswerHistoryController' => 'PonderController',
'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PonderAnswerSaveController' => 'PonderController',
'PonderAnswerTransaction' => 'PhabricatorApplicationTransaction',
'PonderAnswerTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PonderAnswerTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PonderAnswerViewController' => 'PonderController',
'PonderComment' =>
array(
0 => 'PonderDAO',
@ -4165,6 +4165,7 @@ phutil_register_library_map(array(
'PonderCommentQuery' => 'PhabricatorQuery',
'PonderController' => 'PhabricatorController',
'PonderDAO' => 'PhabricatorLiskDAO',
'PonderEditor' => 'PhabricatorApplicationTransactionEditor',
'PonderPHIDTypeAnswer' => 'PhabricatorPHIDType',
'PonderPHIDTypeQuestion' => 'PhabricatorPHIDType',
'PonderQuestion' =>
@ -4178,7 +4179,7 @@ phutil_register_library_map(array(
),
'PonderQuestionCommentController' => 'PonderController',
'PonderQuestionEditController' => 'PonderController',
'PonderQuestionEditor' => 'PhabricatorApplicationTransactionEditor',
'PonderQuestionEditor' => 'PonderEditor',
'PonderQuestionHistoryController' => 'PonderController',
'PonderQuestionListController' =>
array(
@ -4197,6 +4198,7 @@ phutil_register_library_map(array(
'PonderQuestionViewController' => 'PonderController',
'PonderRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'PonderSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
'PonderTransactionFeedStory' => 'PhabricatorApplicationTransactionFeedStory',
'PonderVotableView' => 'AphrontView',
'PonderVote' => 'PonderConstants',
'PonderVoteEditor' => 'PhabricatorEditor',

View file

@ -92,7 +92,7 @@ final class PhabricatorMacroTransaction
return parent::getTitle();
}
public function getTitleForFeed() {
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
@ -133,7 +133,7 @@ final class PhabricatorMacroTransaction
}
}
return parent::getTitleForFeed();
return parent::getTitleForFeed($story);
}
public function getActionName() {

View file

@ -88,7 +88,7 @@ final class PhabricatorPasteTransaction
return parent::getTitle();
}
public function getTitleForFeed() {
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
@ -117,7 +117,7 @@ final class PhabricatorPasteTransaction
break;
}
return parent::getTitleForFeed();
return parent::getTitleForFeed($story);
}
public function getColor() {

View file

@ -180,7 +180,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction {
return parent::getTitle();
}
public function getTitleForFeed() {
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
@ -243,7 +243,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction {
break;
}
return parent::getTitleForFeed();
return parent::getTitleForFeed($story);
}
public function getBodyForFeed(PhabricatorFeedStory $story) {

View file

@ -31,9 +31,8 @@ final class PonderAnswerEditController extends PonderController {
$question = $answer->getQuestion();
$qid = $question->getID();
$aid = $answer->getID();
$question_uri = "/Q{$qid}#A{$aid}";
$answer_uri = $answer->getURI();
$errors = array();
if ($request->isFormPost()) {
@ -58,7 +57,7 @@ final class PonderAnswerEditController extends PonderController {
$editor->applyTransactions($answer, $xactions);
return id(new AphrontRedirectResponse())
->setURI($question_uri);
->setURI($answer_uri);
}
}
@ -84,13 +83,13 @@ final class PonderAnswerEditController extends PonderController {
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Update Answer'))
->addCancelButton($question_uri));
->addCancelButton($answer_uri));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName("Q{$qid}")
->setHref($question_uri));
->setHref($answer_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Answer')));

View file

@ -1,25 +0,0 @@
<?php
final class PonderAnswerViewController extends PonderController {
private $answerID;
public function willProcessRequest(array $data) {
$this->answerID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$answer = id(new PonderAnswer())->load($this->answerID);
if (!$answer) {
return new Aphront404Response();
}
$question_id = $answer->getQuestionID();
return id(new AphrontRedirectResponse())
->setURI('/Q'.$question_id . '#A' . $answer->getID());
}
}

View file

@ -251,6 +251,8 @@ final class PonderQuestionViewController extends PonderController {
$out[] = phutil_tag('br');
$out[] = phutil_tag('br');
$out[] = id(new PhabricatorAnchorView())
->setAnchorName("A$id");
$out[] = id(new PHUIHeaderView())
->setHeader($this->getHandle($author_phid)->getFullName())
->setImage($this->getHandle($author_phid)->getImageURI());

View file

@ -1,7 +1,6 @@
<?php
final class PonderAnswerEditor
extends PhabricatorApplicationTransactionEditor {
final class PonderAnswerEditor extends PonderEditor {
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
@ -62,13 +61,44 @@ final class PonderAnswerEditor
return parent::mergeTransactions($u, $v);
}
protected function supportsFeed() {
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array($object->getAuthorPHID());
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
$question = $object->getQuestion();
return id(new PonderQuestionReplyHandler())
->setMailReceiver($question);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$question = $object->getQuestion();
return parent::buildMailTemplate($question);
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
// If the user just gave the answer, add the answer text.
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
$new = $xaction->getNewValue();
if ($type == PonderAnswerTransaction::TYPE_CONTENT) {
$body->addRawSection($new);
}
}
$body->addTextSection(
pht('ANSWER DETAIL'),
PhabricatorEnv::getProductionURI($object->getURI()));
return $body;
}
}

View file

@ -0,0 +1,32 @@
<?php
abstract class PonderEditor
extends PhabricatorApplicationTransactionEditor {
protected function supportsFeed() {
return true;
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$title = $object->getTitle();
$original_title = $object->getOriginalTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("Q{$id}: {$title}")
->addHeader('Thread-Topic', "Q{$id}: {$original_title}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$object->getAuthorPHID(),
$this->requireActor()->getPHID(),
);
}
protected function getMailSubjectPrefix() {
return '[Ponder]';
}
}

View file

@ -1,7 +1,26 @@
<?php
final class PonderQuestionEditor
extends PhabricatorApplicationTransactionEditor {
extends PonderEditor {
private $answer;
/**
* This is used internally on @{method:applyInitialEffects} if a transaction
* of type PonderQuestionTransaction::TYPE_ANSWERS is in the mix. The value
* is set to the //last// answer in the transactions. Practically, one
* answer is given at a time in the application, though theoretically
* this is buggy.
*
* The answer is used in emails to generate proper links.
*/
private function setAnswer(PonderAnswer $answer) {
$this->answer = $answer;
return $this;
}
private function getAnswer() {
return $this->answer;
}
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
@ -32,6 +51,7 @@ final class PonderQuestionEditor
continue;
}
$answer->save();
$this->setAnswer($answer);
}
break;
}
@ -145,14 +165,27 @@ final class PonderQuestionEditor
return parent::mergeTransactions($u, $v);
}
protected function supportsFeed() {
return true;
}
protected function supportsSearch() {
return true;
}
protected function getFeedStoryType() {
return 'PonderTransactionFeedStory';
}
protected function getFeedStoryData(
PhabricatorLiskDAO $object,
array $xactions) {
$data = parent::getFeedStoryData($object, $xactions);
$answer = $this->getAnswer();
if ($answer) {
$data['answerPHID'] = $answer->getPHID();
}
return $data;
}
protected function shouldImplyCC(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
@ -176,50 +209,39 @@ final class PonderQuestionEditor
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$title = $object->getTitle();
$original_title = $object->getOriginalTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("Q{$id}: {$title}")
->addHeader('Thread-Topic', "Q{$id}: {$original_title}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$object->getAuthorPHID(),
$this->requireActor()->getPHID(),
);
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
// If the user just asked the question, add the question text.
$header = pht('QUESTION DETAIL');
$uri = '/Q'.$object->getID();
foreach ($xactions as $xaction) {
$type = $xaction->getTransactionType();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
// If the user just asked the question, add the question text.
if ($type == PonderQuestionTransaction::TYPE_CONTENT) {
if ($old === null) {
$body->addRawSection($new);
}
}
// If the user gave an answer, add the answer text. Also update
// the header and uri to be more answer-specific.
if ($type == PonderQuestionTransaction::TYPE_ANSWERS) {
$answer = $this->getAnswer();
$body->addRawSection($answer->getContent());
$header = pht('ANSWER DETAIL');
$uri = $answer->getURI();
}
}
$body->addTextSection(
pht('QUESTION DETAIL'),
PhabricatorEnv::getProductionURI('/Q'.$object->getID()));
$header,
PhabricatorEnv::getProductionURI($uri));
return $body;
}
protected function getMailSubjectPrefix() {
return '[Ponder]';
}
}

View file

@ -0,0 +1,14 @@
<?php
final class PonderTransactionFeedStory
extends PhabricatorApplicationTransactionFeedStory {
public function getRequiredObjectPHIDs() {
$phids = parent::getRequiredObjectPHIDs();
$answer_phid = $this->getValue('answerPHID');
if ($answer_phid) {
$phids[] = $answer_phid;
}
return $phids;
}
}

View file

@ -17,11 +17,6 @@ final class PonderQuestionReplyHandler extends PhabricatorMailReplyHandler {
return $this->getDefaultPublicReplyHandlerEmailAddress('Q');
}
public function getReplyHandlerDomain() {
return PhabricatorEnv::getEnvConfig(
'metamta.maniphest.reply-handler-domain');
}
public function getReplyHandlerInstructions() {
return null;
}

View file

@ -36,10 +36,9 @@ final class PonderPHIDTypeAnswer extends PhabricatorPHIDType {
$answer = $objects[$phid];
$id = $answer->getID();
$qid = $answer->getQuestionID();
$handle->setName("Answer {$id}");
$handle->setURI("/Q{$qid}#A{$id}");
$handle->setURI($answer->getURI());
}
}

View file

@ -36,11 +36,10 @@ final class PonderPHIDTypeQuestion extends PhabricatorPHIDType {
$question = $objects[$phid];
$id = $question->getID();
$title = $question->getTitle();
$handle->setName("Q{$id}");
$handle->setURI("/Q{$id}");
$handle->setFullName("Q{$id}: {$title}");
$handle->setFullName($question->getFullTitle());
}
}

View file

@ -151,7 +151,8 @@ final class PonderQuestionQuery
$answers = mgroup($answers, 'getQuestionID');
foreach ($questions as $question) {
$question->attachAnswers(idx($answers, $question->getID(), array()));
$question_answers = idx($answers, $question->getID(), array());
$question->attachAnswers(mpull($question_answers, null, 'getPHID'));
}
}

View file

@ -23,11 +23,6 @@ final class PonderAnswer extends PonderDAO
private $userVotes = array();
// TODO: Get rid of this method.
public function setQuestion($question) {
return $this->attachQuestion($question);
}
public function attachQuestion(PonderQuestion $question = null) {
$this->question = $question;
return $this;
@ -37,6 +32,10 @@ final class PonderAnswer extends PonderDAO
return $this->assertAttached($this->question);
}
public function getURI() {
return '/Q'.$this->getQuestionID().'#A'.$this->getID();
}
public function setUserVote($vote) {
$this->vote = $vote['data'];
if (!$this->vote) {

View file

@ -21,23 +21,89 @@ final class PonderAnswerTransaction
return new PonderAnswerTransactionComment();
}
public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$phids[] = $this->getObjectPHID();
break;
}
return $phids;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
// TODO: This is not so good.
return pht(
'%s edited their answer to %s',
'%s edited %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
return $this->getTitle();
return parent::getTitle();
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$answer = $story->getObject($object_phid);
$question = $answer->getQuestion();
$answer_handle = $this->getHandle($object_phid);
$link = $answer_handle->renderLink(
$question->getFullTitle());
return pht(
'%s updated their answer to %s',
$this->renderHandleLink($author_phid),
$link);
}
return parent::getTitleForFeed($story);
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$new = $this->getNewValue();
$body = null;
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return phutil_escape_html_newlines(
phutil_utf8_shorten($new, 128));
break;
}
return parent::getBodyForFeed($story);
}
public function hasChangeDetails() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return $old !== null;
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
$old = $this->getOldValue();
$new = $this->getNewValue();
$view = id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setUser($viewer)
->setOldText($old)
->setNewText($new);
return $view->render();
}
}

View file

@ -62,7 +62,7 @@ final class PonderQuestion extends PonderDAO
$this->setComments(idx($comments, $this->getPHID(), array()));
foreach ($this->answers as $answer) {
$answer->setQuestion($this);
$answer->attachQuestion($this);
$answer->setComments(idx($comments, $answer->getPHID(), array()));
}
}
@ -208,6 +208,12 @@ final class PonderQuestion extends PonderDAO
return $this->getTitle();
}
public function getFullTitle() {
$id = $this->getID();
$title = $this->getTitle();
return "Q{$id}: {$title}";
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */

View file

@ -24,8 +24,22 @@ final class PonderQuestionTransaction
return new PonderQuestionTransactionComment();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
switch ($this->getTransactionType()) {
case self::TYPE_ANSWERS:
$phids[] = $this->getNewAnswerPHID();
$phids[] = $this->getObjectPHID();
break;
}
return $phids;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
@ -48,10 +62,12 @@ final class PonderQuestionTransaction
'%s edited the question description.',
$this->renderHandleLink($author_phid));
case self::TYPE_ANSWERS:
// TODO: This could be richer.
$answer_handle = $this->getHandle($this->getNewAnswerPHID());
$question_handle = $this->getHandle($object_phid);
return pht(
'%s added an answer.',
$this->renderHandleLink($author_phid));
'%s answered %s',
$this->renderHandleLink($author_phid),
$answer_handle->renderLink($question_handle->getFullName()));
case self::TYPE_STATUS:
switch ($new) {
case PonderQuestionStatus::STATUS_OPEN:
@ -178,7 +194,7 @@ final class PonderQuestionTransaction
return parent::shouldHide();
}
public function getTitleForFeed() {
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
@ -205,11 +221,12 @@ final class PonderQuestionTransaction
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case self::TYPE_ANSWERS:
// TODO: This could be richer, too.
$answer_handle = $this->getHandle($this->getNewAnswerPHID());
$question_handle = $this->getHandle($object_phid);
return pht(
'%s answered %s',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
$answer_handle->renderLink($question_handle->getFullName()));
case self::TYPE_STATUS:
switch ($new) {
case PonderQuestionStatus::STATUS_OPEN:
@ -225,8 +242,67 @@ final class PonderQuestionTransaction
}
}
return $this->getTitle();
return parent::getTitleForFeed($story);
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$new = $this->getNewValue();
$old = $this->getOldValue();
$body = null;
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
if ($old === null) {
$question = $story->getObject($this->getObjectPHID());
return phutil_escape_html_newlines(
phutil_utf8_shorten($question->getContent(), 128));
}
case self::TYPE_ANSWERS:
$answer = $this->getNewAnswerObject($story);
if ($answer) {
return phutil_escape_html_newlines(
phutil_utf8_shorten($answer->getContent(), 128));
}
}
return parent::getBodyForFeed($story);
}
/**
* Currently the application only supports adding answers one at a time.
* This data is stored as a list of phids. Use this function to get the
* new phid.
*/
private function getNewAnswerPHID() {
$new = $this->getNewValue();
$old = $this->getOldValue();
$add = array_diff($new, $old);
if (count($add) != 1) {
throw new Exception(
'There should be only one answer added at a time.');
}
return reset($add);
}
/**
* Generally, the answer object is only available if the transaction
* type is self::TYPE_ANSWERS.
*
* Some stories - notably ones made before D7027 - will be of the more
* generic @{class:PhabricatorApplicationTransactionFeedStory}. These
* poor stories won't have the PonderAnswer loaded, and thus will have
* less cool information.
*/
private function getNewAnswerObject(PhabricatorFeedStory $story) {
if ($story instanceof PonderTransactionFeedStory) {
$answer_phid = $this->getNewAnswerPHID();
if ($answer_phid) {
return $story->getObject($answer_phid);
}
}
return null;
}
}

View file

@ -43,7 +43,7 @@ class PhabricatorApplicationTransactionFeedStory
$xaction = $this->getObject(head($xaction_phids));
$xaction->setHandles($this->getHandles());
$view->setTitle($xaction->getTitleForFeed());
$view->setTitle($xaction->getTitleForFeed($this));
$body = $xaction->getBodyForFeed($this);
if (nonempty($body)) {
$view->appendChild($body);

View file

@ -355,7 +355,7 @@ abstract class PhabricatorApplicationTransaction
}
}
public function getTitleForFeed() {
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();