Add additional statuses to Ponder

Summary: Ref T9096. This is a first cut at adding additional statuses, happy to add or subtract as needed... maybe even configurable? Also, the dialog doesn't seem to fire, I'll keep debugging.

Test Plan: Close and Reopen many questions. Test applicationSearch params by seeing resolved questions, all questions.

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin

Maniphest Tasks: T9096

Differential Revision: https://secure.phabricator.com/D13826
This commit is contained in:
Chad Little
2015-08-08 10:23:33 -07:00
parent dc687dbd92
commit d2ef273ecd
12 changed files with 147 additions and 94 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_ponder.ponder_question
MODIFY status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};

View File

@@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_ponder.ponder_question
SET status = 'open' WHERE status = 0;

View File

@@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_ponder.ponder_question
SET status = 'resolved' WHERE status = 1;

View File

@@ -71,7 +71,7 @@ final class PhabricatorPonderApplication extends PhabricatorApplication {
=> 'PonderQuestionHistoryController', => 'PonderQuestionHistoryController',
'preview/' 'preview/'
=> 'PhabricatorMarkupPreviewController', => 'PhabricatorMarkupPreviewController',
'question/(?P<status>open|close)/(?P<id>[1-9]\d*)/' 'question/status/(?P<id>[1-9]\d*)/'
=> 'PonderQuestionStatusController', => 'PonderQuestionStatusController',
'vote/' => 'PonderVoteSaveController', 'vote/' => 'PonderVoteSaveController',
), ),

View File

@@ -2,20 +2,40 @@
final class PonderQuestionStatus extends PonderConstants { final class PonderQuestionStatus extends PonderConstants {
const STATUS_OPEN = 0; const STATUS_OPEN = 'open';
const STATUS_CLOSED = 1; const STATUS_CLOSED_RESOLVED = 'resolved';
const STATUS_CLOSED_OBSOLETE = 'obsolete';
const STATUS_CLOSED_DUPLICATE = 'duplicate';
public static function getQuestionStatusMap() { public static function getQuestionStatusMap() {
return array( return array(
self::STATUS_OPEN => pht('Open'), self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED => pht('Closed'), self::STATUS_CLOSED_RESOLVED => pht('Closed, Resolved'),
self::STATUS_CLOSED_OBSOLETE => pht('Closed, Obsolete'),
self::STATUS_CLOSED_DUPLICATE => pht('Closed, Duplicate'),
); );
} }
public static function getQuestionStatusFullName($status) { public static function getQuestionStatusFullName($status) {
$map = array( $map = array(
self::STATUS_OPEN => pht('Open'), self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED => pht('Closed by author'), self::STATUS_CLOSED_RESOLVED => pht('Closed, Resolved'),
self::STATUS_CLOSED_OBSOLETE => pht('Closed, Obsolete'),
self::STATUS_CLOSED_DUPLICATE => pht('Closed, Duplicate'),
);
return idx($map, $status, pht('Unknown'));
}
public static function getQuestionStatusDescription($status) {
$map = array(
self::STATUS_OPEN =>
pht('This question is open for answers.'),
self::STATUS_CLOSED_RESOLVED =>
pht('This question has been resolved.'),
self::STATUS_CLOSED_OBSOLETE =>
pht('This question is no longer valid or out of date.'),
self::STATUS_CLOSED_DUPLICATE =>
pht('This question is a duplicate of another question.'),
); );
return idx($map, $status, pht('Unknown')); return idx($map, $status, pht('Unknown'));
} }
@@ -23,7 +43,9 @@ final class PonderQuestionStatus extends PonderConstants {
public static function getQuestionStatusTagColor($status) { public static function getQuestionStatusTagColor($status) {
$map = array( $map = array(
self::STATUS_OPEN => PHUITagView::COLOR_BLUE, self::STATUS_OPEN => PHUITagView::COLOR_BLUE,
self::STATUS_CLOSED => PHUITagView::COLOR_BLACK, self::STATUS_CLOSED_RESOLVED => PHUITagView::COLOR_BLACK,
self::STATUS_CLOSED_OBSOLETE => PHUITagView::COLOR_BLACK,
self::STATUS_CLOSED_DUPLICATE => PHUITagView::COLOR_BLACK,
); );
return idx($map, $status); return idx($map, $status);
@@ -32,10 +54,27 @@ final class PonderQuestionStatus extends PonderConstants {
public static function getQuestionStatusIcon($status) { public static function getQuestionStatusIcon($status) {
$map = array( $map = array(
self::STATUS_OPEN => 'fa-question-circle', self::STATUS_OPEN => 'fa-question-circle',
self::STATUS_CLOSED => 'fa-check-square-o', self::STATUS_CLOSED_RESOLVED => 'fa-check',
self::STATUS_CLOSED_OBSOLETE => 'fa-ban',
self::STATUS_CLOSED_DUPLICATE => 'fa-clone',
); );
return idx($map, $status); return idx($map, $status);
} }
public static function getQuestionStatusOpenMap() {
return array(
self::STATUS_OPEN,
);
}
public static function getQuestionStatusClosedMap() {
return array(
self::STATUS_CLOSED_RESOLVED,
self::STATUS_CLOSED_OBSOLETE,
self::STATUS_CLOSED_DUPLICATE,
);
}
} }

View File

@@ -33,6 +33,8 @@ final class PonderQuestionEditController extends PonderController {
$v_view = $question->getViewPolicy(); $v_view = $question->getViewPolicy();
$v_edit = $question->getEditPolicy(); $v_edit = $question->getEditPolicy();
$v_space = $question->getSpacePHID(); $v_space = $question->getSpacePHID();
$v_status = $question->getStatus();
$errors = array(); $errors = array();
$e_title = true; $e_title = true;
@@ -43,6 +45,7 @@ final class PonderQuestionEditController extends PonderController {
$v_view = $request->getStr('viewPolicy'); $v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy'); $v_edit = $request->getStr('editPolicy');
$v_space = $request->getStr('spacePHID'); $v_space = $request->getStr('spacePHID');
$v_status = $request->getStr('status');
$len = phutil_utf8_strlen($v_title); $len = phutil_utf8_strlen($v_title);
if ($len < 1) { if ($len < 1) {
@@ -65,6 +68,10 @@ final class PonderQuestionEditController extends PonderController {
->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT) ->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT)
->setNewValue($v_content); ->setNewValue($v_content);
$xactions[] = id(clone $template)
->setTransactionType(PonderQuestionTransaction::TYPE_STATUS)
->setNewValue($v_status);
$xactions[] = id(clone $template) $xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($v_view); ->setNewValue($v_view);
@@ -130,7 +137,13 @@ final class PonderQuestionEditController extends PonderController {
->setPolicyObject($question) ->setPolicyObject($question)
->setPolicies($policies) ->setPolicies($policies)
->setValue($v_edit) ->setValue($v_edit)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)); ->setCapability(PhabricatorPolicyCapability::CAN_EDIT))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Status'))
->setName('status')
->setValue($v_status)
->setOptions(PonderQuestionStatus::getQuestionStatusMap()));
$form->appendControl( $form->appendControl(
id(new AphrontFormTokenizerControl()) id(new AphrontFormTokenizerControl())
@@ -149,21 +162,23 @@ final class PonderQuestionEditController extends PonderController {
->setControlID('content') ->setControlID('content')
->setPreviewURI($this->getApplicationURI('preview/')); ->setPreviewURI($this->getApplicationURI('preview/'));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Ask New Question'))
->setFormErrors($errors)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$id = $question->getID(); $id = $question->getID();
if ($id) { if ($id) {
$crumbs->addTextCrumb("Q{$id}", "/Q{$id}"); $crumbs->addTextCrumb("Q{$id}", "/Q{$id}");
$crumbs->addTextCrumb(pht('Edit')); $crumbs->addTextCrumb(pht('Edit'));
$title = pht('Edit Question');
} else { } else {
$crumbs->addTextCrumb(pht('Ask Question')); $crumbs->addTextCrumb(pht('Ask Question'));
$title = pht('Ask New Question');
} }
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setForm($form);
return $this->buildApplicationPage( return $this->buildApplicationPage(
array( array(
$crumbs, $crumbs,
@@ -171,7 +186,7 @@ final class PonderQuestionEditController extends PonderController {
$preview, $preview,
), ),
array( array(
'title' => pht('Ask New Question'), 'title' => $title,
)); ));
} }

View File

@@ -6,7 +6,6 @@ final class PonderQuestionStatusController
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer(); $viewer = $request->getViewer();
$id = $request->getURIData('id'); $id = $request->getURIData('id');
$status = $request->getURIData('status');
$question = id(new PonderQuestionQuery()) $question = id(new PonderQuestionQuery())
->setViewer($viewer) ->setViewer($viewer)
@@ -21,29 +20,46 @@ final class PonderQuestionStatusController
return new Aphront404Response(); return new Aphront404Response();
} }
switch ($status) { $view_uri = '/Q'.$question->getID();
case 'open': $v_status = $question->getStatus();
$status = PonderQuestionStatus::STATUS_OPEN;
break; if ($request->isFormPost()) {
case 'close': $v_status = $request->getStr('status');
$status = PonderQuestionStatus::STATUS_CLOSED;
break; $xactions = array();
default: $xactions[] = id(new PonderQuestionTransaction())
return new Aphront400Response(); ->setTransactionType(PonderQuestionTransaction::TYPE_STATUS)
->setNewValue($v_status);
$editor = id(new PonderQuestionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request);
$editor->applyTransactions($question, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
} }
$xactions = array(); $radio = id(new AphrontFormRadioButtonControl())
$xactions[] = id(new PonderQuestionTransaction()) ->setLabel(pht('Status'))
->setTransactionType(PonderQuestionTransaction::TYPE_STATUS) ->setName('status')
->setNewValue($status); ->setValue($v_status);
$editor = id(new PonderQuestionEditor()) foreach (PonderQuestionStatus::getQuestionStatusMap() as $value => $name) {
->setActor($viewer) $description = PonderQuestionStatus::getQuestionStatusDescription($value);
->setContentSourceFromRequest($request); $radio->addButton($value, $name, $description);
}
$editor->applyTransactions($question, $xactions); $form = id(new AphrontFormView())
->setUser($viewer)
->appendChild($radio);
return $this->newDialog()
->setTitle(pht('Change Question Status'))
->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Submit'))
->addCancelButton($view_uri);
return id(new AphrontRedirectResponse())->setURI('/Q'.$question->getID());
} }
} }

View File

@@ -45,7 +45,11 @@ final class PonderQuestionViewController extends PonderController {
if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) {
$header->setStatus('fa-square-o', 'bluegrey', pht('Open')); $header->setStatus('fa-square-o', 'bluegrey', pht('Open'));
} else { } else {
$header->setStatus('fa-check-square-o', 'dark', pht('Closed')); $text = PonderQuestionStatus::getQuestionStatusFullName(
$question->getStatus());
$icon = PonderQuestionStatus::getQuestionStatusIcon(
$question->getStatus());
$header->setStatus($icon, 'dark', $text);
} }
$actions = $this->buildActionListView($question); $actions = $this->buildActionListView($question);
@@ -109,21 +113,18 @@ final class PonderQuestionViewController extends PonderController {
if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) {
$name = pht('Close Question'); $name = pht('Close Question');
$icon = 'fa-check-square-o'; $icon = 'fa-check-square-o';
$href = 'close';
} else { } else {
$name = pht('Reopen Question'); $name = pht('Reopen Question');
$icon = 'fa-square-o'; $icon = 'fa-square-o';
$href = 'open';
} }
$view->addAction( $view->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName($name) ->setName($name)
->setIcon($icon) ->setIcon($icon)
->setRenderAsForm($can_edit) ->setWorkflow(true)
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setHref($this->getApplicationURI("/question/{$href}/{$id}/"))); ->setHref($this->getApplicationURI("/question/status/{$id}/")));
$view->addAction( $view->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())

View File

@@ -5,17 +5,12 @@ final class PonderQuestionQuery
private $ids; private $ids;
private $phids; private $phids;
private $status;
private $authorPHIDs; private $authorPHIDs;
private $answererPHIDs; private $answererPHIDs;
private $needProjectPHIDs; private $needProjectPHIDs;
private $status = 'status-any';
const STATUS_ANY = 'status-any';
const STATUS_OPEN = 'status-open';
const STATUS_CLOSED = 'status-closed';
private $needAnswers; private $needAnswers;
private $needViewerVotes; private $needViewerVotes;
@@ -34,7 +29,7 @@ final class PonderQuestionQuery
return $this; return $this;
} }
public function withStatus($status) { public function withStatuses($status) {
$this->status = $status; $this->status = $status;
return $this; return $this;
} }
@@ -84,24 +79,10 @@ final class PonderQuestionQuery
} }
if ($this->status !== null) { if ($this->status !== null) {
switch ($this->status) { $where[] = qsprintf(
case self::STATUS_ANY: $conn,
break; 'q.status IN (%Ls)',
case self::STATUS_OPEN: $this->status);
$where[] = qsprintf(
$conn,
'q.status = %d',
PonderQuestionStatus::STATUS_OPEN);
break;
case self::STATUS_CLOSED:
$where[] = qsprintf(
$conn,
'q.status = %d',
PonderQuestionStatus::STATUS_CLOSED);
break;
default:
throw new Exception(pht("Unknown status query '%s'!", $this->status));
}
} }
return $where; return $where;

View File

@@ -27,16 +27,8 @@ final class PonderQuestionSearchEngine
$query->withAnswererPHIDs($map['answerers']); $query->withAnswererPHIDs($map['answerers']);
} }
$status = $map['status']; if ($map['statuses']) {
if ($status != null) { $query->withStatuses($map['statuses']);
switch ($status) {
case 0:
$query->withStatus(PonderQuestionQuery::STATUS_OPEN);
break;
case 1:
$query->withStatus(PonderQuestionQuery::STATUS_CLOSED);
break;
}
} }
return $query; return $query;
@@ -52,9 +44,9 @@ final class PonderQuestionSearchEngine
->setKey('answerers') ->setKey('answerers')
->setAliases(array('answerers')) ->setAliases(array('answerers'))
->setLabel(pht('Answered By')), ->setLabel(pht('Answered By')),
id(new PhabricatorSearchSelectField()) id(new PhabricatorSearchCheckboxesField())
->setLabel(pht('Status')) ->setLabel(pht('Status'))
->setKey('status') ->setKey('statuses')
->setOptions(PonderQuestionStatus::getQuestionStatusMap()), ->setOptions(PonderQuestionStatus::getQuestionStatusMap()),
); );
} }
@@ -66,6 +58,7 @@ final class PonderQuestionSearchEngine
protected function getBuiltinQueryNames() { protected function getBuiltinQueryNames() {
$names = array( $names = array(
'open' => pht('Open Questions'), 'open' => pht('Open Questions'),
'resolved' => pht('Resolved Questions'),
'all' => pht('All Questions'), 'all' => pht('All Questions'),
); );
@@ -85,7 +78,11 @@ final class PonderQuestionSearchEngine
case 'all': case 'all':
return $query; return $query;
case 'open': case 'open':
return $query->setParameter('status', PonderQuestionQuery::STATUS_OPEN); return $query->setParameter(
'statuses', array(PonderQuestionStatus::STATUS_OPEN));
case 'resolved':
return $query->setParameter(
'statuses', array(PonderQuestionStatus::STATUS_CLOSED_RESOLVED));
case 'authored': case 'authored':
return $query->setParameter( return $query->setParameter(
'authorPHIDs', 'authorPHIDs',

View File

@@ -65,7 +65,7 @@ final class PonderQuestion extends PonderDAO
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'title' => 'text255', 'title' => 'text255',
'voteCount' => 'sint32', 'voteCount' => 'sint32',
'status' => 'uint32', 'status' => 'text32',
'content' => 'text', 'content' => 'text',
'heat' => 'double', 'heat' => 'double',
'answerCount' => 'uint32', 'answerCount' => 'uint32',

View File

@@ -87,9 +87,17 @@ final class PonderQuestionTransaction
return pht( return pht(
'%s reopened this question.', '%s reopened this question.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
case PonderQuestionStatus::STATUS_CLOSED: case PonderQuestionStatus::STATUS_CLOSED_RESOLVED:
return pht( return pht(
'%s closed this question.', '%s closed this question as resolved.',
$this->renderHandleLink($author_phid));
case PonderQuestionStatus::STATUS_CLOSED_OBSOLETE:
return pht(
'%s closed this question as obsolete.',
$this->renderHandleLink($author_phid));
case PonderQuestionStatus::STATUS_CLOSED_DUPLICATE:
return pht(
'%s closed this question as a duplicate.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
} }
} }
@@ -106,12 +114,7 @@ final class PonderQuestionTransaction
case self::TYPE_CONTENT: case self::TYPE_CONTENT:
return 'fa-pencil'; return 'fa-pencil';
case self::TYPE_STATUS: case self::TYPE_STATUS:
switch ($new) { return PonderQuestionStatus::getQuestionStatusIcon($new);
case PonderQuestionStatus::STATUS_OPEN:
return 'fa-check-circle';
case PonderQuestionStatus::STATUS_CLOSED:
return 'fa-minus-circle';
}
case self::TYPE_ANSWERS: case self::TYPE_ANSWERS:
return 'fa-plus'; return 'fa-plus';
} }
@@ -130,12 +133,7 @@ final class PonderQuestionTransaction
case self::TYPE_ANSWERS: case self::TYPE_ANSWERS:
return PhabricatorTransactions::COLOR_GREEN; return PhabricatorTransactions::COLOR_GREEN;
case self::TYPE_STATUS: case self::TYPE_STATUS:
switch ($new) { return PonderQuestionStatus::getQuestionStatusTagColor($new);
case PonderQuestionStatus::STATUS_OPEN:
return PhabricatorTransactions::COLOR_GREEN;
case PonderQuestionStatus::STATUS_CLOSED:
return PhabricatorTransactions::COLOR_INDIGO;
}
} }
} }