mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-22 21:40:55 +01:00
Conpherence - paginate thread list
Summary: this is D5750 but just the conpherence part. fixes a few random conpherence bugs / quirks as well. Also messes with ApplicationTransactionEditor to expose the xactions so Conpherence doesn't over-update participation rows. Fixes T2429. Test Plan: set LIMIT to 3. verified I could scroll down all conpherences. next, picked a conpherence "in the middle" to load. verified I could page up and down. next, picked a conpherence in the middle then had another user update that conpherence. verified as I paged up the conpherence re-loaded properly selected Reviewers: epriestley Reviewed By: epriestley CC: chad, aran, Korvin, vrana Maniphest Tasks: T2429 Differential Revision: https://secure.phabricator.com/D5783
This commit is contained in:
parent
664fe7ef73
commit
11cb2f4f6c
14 changed files with 574 additions and 197 deletions
4
resources/sql/patches/20130423.conpherenceindices.sql
Normal file
4
resources/sql/patches/20130423.conpherenceindices.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE {$NAMESPACE}_conpherence.conpherence_participant
|
||||
DROP KEY participantPHID,
|
||||
ADD KEY unreadCount (participantPHID, participationStatus),
|
||||
ADD KEY participationIndex (participantPHID, dateTouched, id);
|
|
@ -1289,7 +1289,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-conpherence-menu' =>
|
||||
array(
|
||||
'uri' => '/res/ce7bfa44/rsrc/js/application/conpherence/behavior-menu.js',
|
||||
'uri' => '/res/06bfc1a3/rsrc/js/application/conpherence/behavior-menu.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1301,6 +1301,7 @@ celerity_register_resource_map(array(
|
|||
5 => 'javelin-workflow',
|
||||
6 => 'javelin-behavior-device',
|
||||
7 => 'javelin-history',
|
||||
8 => 'javelin-vector',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/conpherence/behavior-menu.js',
|
||||
),
|
||||
|
|
|
@ -238,6 +238,7 @@ phutil_register_library_map(array(
|
|||
'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
|
||||
'ConpherenceNewController' => 'applications/conpherence/controller/ConpherenceNewController.php',
|
||||
'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php',
|
||||
'ConpherenceParticipantCountQuery' => 'applications/conpherence/query/ConpherenceParticipantCountQuery.php',
|
||||
'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php',
|
||||
'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php',
|
||||
'ConpherencePeopleMenuEventListener' => 'applications/conpherence/events/ConpherencePeopleMenuEventListener.php',
|
||||
|
@ -2001,6 +2002,7 @@ phutil_register_library_map(array(
|
|||
'ConpherenceMenuItemView' => 'AphrontTagView',
|
||||
'ConpherenceNewController' => 'ConpherenceController',
|
||||
'ConpherenceParticipant' => 'ConpherenceDAO',
|
||||
'ConpherenceParticipantCountQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'ConpherenceParticipationStatus' => 'ConpherenceConstants',
|
||||
'ConpherencePeopleMenuEventListener' => 'PhutilEventListener',
|
||||
|
|
|
@ -6,62 +6,6 @@
|
|||
abstract class ConpherenceController extends PhabricatorController {
|
||||
private $conpherences;
|
||||
|
||||
/**
|
||||
* Try for a full set of unread conpherences, and if we fail
|
||||
* load read conpherences. Additional conpherences in either category
|
||||
* are loaded asynchronously.
|
||||
*/
|
||||
public function loadStartingConpherences($current_selection_epoch = null) {
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$read_participant_query = id(new ConpherenceParticipantQuery())
|
||||
->withParticipantPHIDs(array($user->getPHID()));
|
||||
$read_status = ConpherenceParticipationStatus::UP_TO_DATE;
|
||||
if ($current_selection_epoch) {
|
||||
$read_one = $read_participant_query
|
||||
->withParticipationStatus($read_status)
|
||||
->withDateTouched($current_selection_epoch, '>')
|
||||
->execute();
|
||||
|
||||
$read_two = $read_participant_query
|
||||
->withDateTouched($current_selection_epoch, '<=')
|
||||
->execute();
|
||||
|
||||
$read = array_merge($read_one, $read_two);
|
||||
|
||||
} else {
|
||||
$read = $read_participant_query
|
||||
->withParticipationStatus($read_status)
|
||||
->execute();
|
||||
}
|
||||
|
||||
$unread_status = ConpherenceParticipationStatus::BEHIND;
|
||||
$unread = id(new ConpherenceParticipantQuery())
|
||||
->withParticipantPHIDs(array($user->getPHID()))
|
||||
->withParticipationStatus($unread_status)
|
||||
->execute();
|
||||
|
||||
$all_participation = $unread + $read;
|
||||
$all_conpherence_phids = array_keys($all_participation);
|
||||
$all_conpherences = array();
|
||||
if ($all_conpherence_phids) {
|
||||
$all_conpherences = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs($all_conpherence_phids)
|
||||
->needParticipantCache(true)
|
||||
->execute();
|
||||
}
|
||||
$unread_conpherences = array_select_keys(
|
||||
$all_conpherences,
|
||||
array_keys($unread));
|
||||
|
||||
$read_conpherences = array_select_keys(
|
||||
$all_conpherences,
|
||||
array_keys($read));
|
||||
|
||||
return array($unread_conpherences, $read_conpherences);
|
||||
}
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
$nav = new PhabricatorMenuView();
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
final class ConpherenceListController
|
||||
extends ConpherenceController {
|
||||
|
||||
const SELECTED_MODE = 'selected';
|
||||
const UNSELECTED_MODE = 'unselected';
|
||||
const PAGING_MODE = 'paging';
|
||||
|
||||
private $conpherenceID;
|
||||
|
||||
public function setConpherenceID($conpherence_id) {
|
||||
|
@ -20,59 +24,227 @@ final class ConpherenceListController
|
|||
$this->setConpherenceID(idx($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Three main modes of operation...
|
||||
*
|
||||
* 1 - /conpherence/ - UNSELECTED_MODE
|
||||
* 2 - /conpherence/<id>/ - SELECTED_MODE
|
||||
* 3 - /conpherence/?direction='up'&... - PAGING_MODE
|
||||
*
|
||||
* UNSELECTED_MODE is not an Ajax request while the other two are Ajax
|
||||
* requests.
|
||||
*/
|
||||
private function determineMode() {
|
||||
$request = $this->getRequest();
|
||||
|
||||
$mode = self::UNSELECTED_MODE;
|
||||
if ($request->isAjax()) {
|
||||
if ($request->getStr('direction')) {
|
||||
$mode = self::PAGING_MODE;
|
||||
} else {
|
||||
$mode = self::SELECTED_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
return $mode;
|
||||
}
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
$title = pht('Conpherence');
|
||||
|
||||
$conpherence_id = $this->getConpherenceID();
|
||||
$current_selection_epoch = null;
|
||||
$conpherence = null;
|
||||
if ($conpherence_id) {
|
||||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->executeOne();
|
||||
if (!$conpherence) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($conpherence->getTitle()) {
|
||||
$title = $conpherence->getTitle();
|
||||
}
|
||||
$scroll_up_participant = $this->getEmptyParticipant();
|
||||
$scroll_down_participant = $this->getEmptyParticipant();
|
||||
$too_many = ConpherenceParticipantQuery::LIMIT + 1;
|
||||
$all_participation = array();
|
||||
|
||||
$participant = $conpherence->getParticipant($user->getPHID());
|
||||
$current_selection_epoch = $participant->getDateTouched();
|
||||
$mode = $this->determineMode();
|
||||
switch ($mode) {
|
||||
case self::SELECTED_MODE:
|
||||
$conpherence_id = $this->getConpherenceID();
|
||||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->executeOne();
|
||||
if (!$conpherence) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
if ($conpherence->getTitle()) {
|
||||
$title = $conpherence->getTitle();
|
||||
}
|
||||
$cursor = $conpherence->getParticipant($user->getPHID());
|
||||
$data = $this->loadParticipationWithMidCursor($cursor);
|
||||
$all_participation = $data['participation'];
|
||||
$scroll_up_participant = $data['scroll_up_participant'];
|
||||
$scroll_down_participant = $data['scroll_down_participant'];
|
||||
break;
|
||||
case self::PAGING_MODE:
|
||||
$direction = $request->getStr('direction');
|
||||
$id = $request->getInt('participant_id');
|
||||
$date_touched = $request->getInt('date_touched');
|
||||
$conpherence_phid = $request->getStr('conpherence_phid');
|
||||
if ($direction == 'up') {
|
||||
$order = ConpherenceParticipantQuery::ORDER_NEWER;
|
||||
} else {
|
||||
$order = ConpherenceParticipantQuery::ORDER_OLDER;
|
||||
}
|
||||
$scroller_participant = id(new ConpherenceParticipant())
|
||||
->makeEphemeral()
|
||||
->setID($id)
|
||||
->setDateTouched($date_touched)
|
||||
->setConpherencePHID($conpherence_phid);
|
||||
$participation = id(new ConpherenceParticipantQuery())
|
||||
->withParticipantPHIDs(array($user->getPHID()))
|
||||
->withParticipantCursor($scroller_participant)
|
||||
->setOrder($order)
|
||||
->setLimit($too_many)
|
||||
->execute();
|
||||
if (count($participation) == $too_many) {
|
||||
if ($direction == 'up') {
|
||||
$node = $scroll_up_participant = reset($participation);
|
||||
} else {
|
||||
$node = $scroll_down_participant = end($participation);
|
||||
}
|
||||
unset($participation[$node->getConpherencePHID()]);
|
||||
}
|
||||
$all_participation = $participation;
|
||||
break;
|
||||
case self::UNSELECTED_MODE:
|
||||
default:
|
||||
$too_many = ConpherenceParticipantQuery::LIMIT + 1;
|
||||
$all_participation = id(new ConpherenceParticipantQuery())
|
||||
->withParticipantPHIDs(array($user->getPHID()))
|
||||
->setLimit($too_many)
|
||||
->execute();
|
||||
if (count($all_participation) == $too_many) {
|
||||
$node = end($participation);
|
||||
unset($all_participation[$node->getConpherencePHID()]);
|
||||
$scroll_down_participant = $node;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
list($unread, $read) = $this->loadStartingConpherences(
|
||||
$current_selection_epoch);
|
||||
$threads = $this->loadConpherenceThreadData(
|
||||
$all_participation);
|
||||
|
||||
$thread_view = id(new ConpherenceThreadListView())
|
||||
->setUser($user)
|
||||
->setBaseURI($this->getApplicationURI())
|
||||
->setUnreadThreads($unread)
|
||||
->setReadThreads($read);
|
||||
->setThreads($threads)
|
||||
->setScrollUpParticipant($scroll_up_participant)
|
||||
->setScrollDownParticipant($scroll_down_participant);
|
||||
|
||||
if ($request->isAjax()) {
|
||||
return id(new AphrontAjaxResponse())->setContent($thread_view);
|
||||
switch ($mode) {
|
||||
case self::SELECTED_MODE:
|
||||
$response = id(new AphrontAjaxResponse())->setContent($thread_view);
|
||||
break;
|
||||
case self::PAGING_MODE:
|
||||
$thread_html = $thread_view->renderThreadsHTML();
|
||||
$phids = array_keys($participation);
|
||||
$content = array(
|
||||
'html' => $thread_html,
|
||||
'phids' => $phids);
|
||||
$response = id(new AphrontAjaxResponse())->setContent($content);
|
||||
break;
|
||||
case self::UNSELECTED_MODE:
|
||||
default:
|
||||
$layout = id(new ConpherenceLayoutView())
|
||||
->setBaseURI($this->getApplicationURI())
|
||||
->setThreadView($thread_view)
|
||||
->setRole('list');
|
||||
if ($conpherence) {
|
||||
$layout->setThread($conpherence);
|
||||
}
|
||||
$response = $this->buildApplicationPage(
|
||||
$layout,
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
$layout = id(new ConpherenceLayoutView())
|
||||
->setBaseURI($this->getApplicationURI())
|
||||
->setThreadView($thread_view)
|
||||
->setRole('list');
|
||||
return $response;
|
||||
|
||||
if ($conpherence) {
|
||||
$layout->setThread($conpherence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the curious case when we are visiting a conpherence directly
|
||||
* by issuing two separate queries. Otherwise, additional conpherences
|
||||
* are fetched asynchronously. Note these can be earlier or later
|
||||
* (up or down), depending on what conpherence was selected on initial
|
||||
* load.
|
||||
*/
|
||||
private function loadParticipationWithMidCursor(
|
||||
ConpherenceParticipant $cursor) {
|
||||
|
||||
$user = $this->getRequest()->getUser();
|
||||
|
||||
$scroll_up_participant = $this->getEmptyParticipant();
|
||||
$scroll_down_participant = $this->getEmptyParticipant();
|
||||
|
||||
// Note this is a bit dodgy since there may be less than this
|
||||
// amount in either the up or down direction, thus having us fail
|
||||
// to fetch LIMIT in total. Whatevs for now and re-visit if we're
|
||||
// fine-tuning this loading process.
|
||||
$too_many = ceil(ConpherenceParticipantQuery::LIMIT / 2) + 1;
|
||||
$participant_query = id(new ConpherenceParticipantQuery())
|
||||
->withParticipantPHIDs(array($user->getPHID()))
|
||||
->setLimit($too_many);
|
||||
$current_selection_epoch = $cursor->getDateTouched();
|
||||
$set_one = $participant_query
|
||||
->withParticipantCursor($cursor)
|
||||
->setOrder(ConpherenceParticipantQuery::ORDER_NEWER)
|
||||
->execute();
|
||||
|
||||
if (count($set_one) == $too_many) {
|
||||
$node = reset($set_one);
|
||||
unset($set_one[$node->getConpherencePHID()]);
|
||||
$scroll_up_participant = $node;
|
||||
}
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$layout,
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
$set_two = $participant_query
|
||||
->withParticipantCursor($cursor)
|
||||
->setOrder(ConpherenceParticipantQuery::ORDER_OLDER)
|
||||
->execute();
|
||||
|
||||
if (count($set_two) == $too_many) {
|
||||
$node = end($set_two);
|
||||
unset($set_two[$node->getConpherencePHID()]);
|
||||
$scroll_down_participant = $node;
|
||||
}
|
||||
|
||||
$participation = array_merge(
|
||||
$set_one,
|
||||
$set_two);
|
||||
|
||||
return array(
|
||||
'scroll_up_participant' => $scroll_up_participant,
|
||||
'scroll_down_participant' => $scroll_down_participant,
|
||||
'participation' => $participation);
|
||||
}
|
||||
|
||||
private function loadConpherenceThreadData($participation) {
|
||||
$user = $this->getRequest()->getUser();
|
||||
$conpherence_phids = array_keys($participation);
|
||||
if ($conpherence_phids) {
|
||||
$conpherences = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs($conpherence_phids)
|
||||
->needParticipantCache(true)
|
||||
->execute();
|
||||
}
|
||||
|
||||
// this will re-sort by participation data
|
||||
$conpherences = array_select_keys($conpherences, $conpherence_phids);
|
||||
|
||||
return $conpherences;
|
||||
}
|
||||
|
||||
private function getEmptyParticipant() {
|
||||
return id(new ConpherenceParticipant())
|
||||
->makeEphemeral();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,6 +50,9 @@ final class ConpherenceViewController extends
|
|||
->setBeforeTransactionID($before_transaction_id);
|
||||
}
|
||||
$conpherence = $query->executeOne();
|
||||
if (!$conpherence) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$this->setConpherence($conpherence);
|
||||
|
||||
$participant = $conpherence->getParticipant($user->getPHID());
|
||||
|
|
|
@ -140,9 +140,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$object->setRecentParticipantPHIDs($participants);
|
||||
}
|
||||
|
||||
/**
|
||||
* For now this only supports adding more files and participants.
|
||||
*/
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
@ -169,33 +166,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$file_phid);
|
||||
}
|
||||
$editor->save();
|
||||
// fallthrough
|
||||
case PhabricatorTransactions::TYPE_COMMENT:
|
||||
$xaction_phid = $xaction->getPHID();
|
||||
$behind = ConpherenceParticipationStatus::BEHIND;
|
||||
$up_to_date = ConpherenceParticipationStatus::UP_TO_DATE;
|
||||
$participants = $object->getParticipants();
|
||||
$user = $this->getActor();
|
||||
$time = time();
|
||||
foreach ($participants as $phid => $participant) {
|
||||
if ($phid != $user->getPHID()) {
|
||||
if ($participant->getParticipationStatus() != $behind) {
|
||||
$participant->setBehindTransactionPHID($xaction_phid);
|
||||
// decrement one as this is the message putting them behind!
|
||||
$participant->setSeenMessageCount($object->getMessageCount() - 1);
|
||||
}
|
||||
$participant->setParticipationStatus($behind);
|
||||
$participant->setDateTouched($time);
|
||||
} else {
|
||||
$participant->setSeenMessageCount($object->getMessageCount());
|
||||
$participant->setParticipationStatus($up_to_date);
|
||||
$participant->setDateTouched($time);
|
||||
}
|
||||
$participant->save();
|
||||
}
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
|
||||
$participants = $object->getParticipants();
|
||||
|
||||
$old_map = array_fuse($xaction->getOldValue());
|
||||
|
@ -229,7 +201,37 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
}
|
||||
$object->attachParticipants($participants);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyFinalEffects(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
||||
// update everyone's participation status on the last xaction -only-
|
||||
$xaction = end($xactions);
|
||||
$xaction_phid = $xaction->getPHID();
|
||||
$behind = ConpherenceParticipationStatus::BEHIND;
|
||||
$up_to_date = ConpherenceParticipationStatus::UP_TO_DATE;
|
||||
$participants = $object->getParticipants();
|
||||
$user = $this->getActor();
|
||||
$time = time();
|
||||
foreach ($participants as $phid => $participant) {
|
||||
if ($phid != $user->getPHID()) {
|
||||
if ($participant->getParticipationStatus() != $behind) {
|
||||
$participant->setBehindTransactionPHID($xaction_phid);
|
||||
// decrement one as this is the message putting them behind!
|
||||
$participant->setSeenMessageCount($object->getMessageCount() - 1);
|
||||
}
|
||||
$participant->setParticipationStatus($behind);
|
||||
$participant->setDateTouched($time);
|
||||
} else {
|
||||
$participant->setSeenMessageCount($object->getMessageCount());
|
||||
$participant->setParticipationStatus($up_to_date);
|
||||
$participant->setDateTouched($time);
|
||||
}
|
||||
$participant->save();
|
||||
}
|
||||
}
|
||||
|
||||
protected function mergeTransactions(
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Query class that answers the question:
|
||||
*
|
||||
* - Q: How many unread conpherences am I participating in?
|
||||
* - A:
|
||||
* id(new ConpherenceParticipantCountQuery())
|
||||
* ->withParticipantPHIDs(array($my_phid))
|
||||
* ->withParticipationStatus(ConpherenceParticipationStatus::BEHIND)
|
||||
* ->execute();
|
||||
*
|
||||
* @group conpherence
|
||||
*/
|
||||
final class ConpherenceParticipantCountQuery
|
||||
extends PhabricatorOffsetPagedQuery {
|
||||
|
||||
private $participantPHIDs;
|
||||
private $participationStatus;
|
||||
|
||||
public function withParticipantPHIDs(array $phids) {
|
||||
$this->participantPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withParticipationStatus($participation_status) {
|
||||
$this->participationStatus = $participation_status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$table = new ConpherenceParticipant();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$rows = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT COUNT(*) as count, participantPHID '.
|
||||
'FROM %T participant %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r),
|
||||
$this->buildGroupByClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
return ipull($rows, 'count', 'participantPHID');
|
||||
}
|
||||
|
||||
private function buildWhereClause($conn_r) {
|
||||
$where = array();
|
||||
|
||||
if ($this->participantPHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'participantPHID IN (%Ls)',
|
||||
$this->participantPHIDs);
|
||||
}
|
||||
|
||||
if ($this->participationStatus !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'participationStatus = %d',
|
||||
$this->participationStatus);
|
||||
}
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
private function buildGroupByClause(AphrontDatabaseConnection $conn_r) {
|
||||
|
||||
$group_by = qsprintf(
|
||||
$conn_r,
|
||||
'GROUP BY participantPHID');
|
||||
|
||||
return $group_by;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,35 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Query class that answers these questions:
|
||||
*
|
||||
* - Q: What are the conpherences to show when I land on /conpherence/ ?
|
||||
* - A:
|
||||
*
|
||||
* id(new ConpherenceParticipantQuery())
|
||||
* ->withParticipantPHIDs(array($my_phid))
|
||||
* ->execute();
|
||||
*
|
||||
* - Q: What are the next set of conpherences as I scroll up (more recent) or
|
||||
* down (less recent) this list of conpherences?
|
||||
* - A:
|
||||
*
|
||||
* id(new ConpherenceParticipantQuery())
|
||||
* ->withParticipantPHIDs(array($my_phid))
|
||||
* ->withParticipantCursor($top_participant)
|
||||
* ->setOrder(ConpherenceParticipantQuery::ORDER_NEWER)
|
||||
* ->execute();
|
||||
*
|
||||
* -or-
|
||||
*
|
||||
* id(new ConpherenceParticipantQuery())
|
||||
* ->withParticipantPHIDs(array($my_phid))
|
||||
* ->withParticipantCursor($bottom_participant)
|
||||
* ->setOrder(ConpherenceParticipantQuery::ORDER_OLDER)
|
||||
* ->execute();
|
||||
*
|
||||
* For counts of read, un-read, or all conpherences by participant, see
|
||||
* @{class:ConpherenceParticipantCountQuery}.
|
||||
*
|
||||
* @group conpherence
|
||||
*/
|
||||
final class ConpherenceParticipantQuery
|
||||
extends PhabricatorOffsetPagedQuery {
|
||||
|
||||
private $conpherencePHIDs;
|
||||
private $participantPHIDs;
|
||||
private $dateTouched;
|
||||
private $dateTouchedSort;
|
||||
private $participationStatus;
|
||||
const LIMIT = 100;
|
||||
const ORDER_NEWER = 'newer';
|
||||
const ORDER_OLDER = 'older';
|
||||
|
||||
public function withConpherencePHIDs(array $phids) {
|
||||
$this->conpherencePHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
private $participantPHIDs;
|
||||
private $participantCursor;
|
||||
private $order = self::ORDER_OLDER;
|
||||
|
||||
public function withParticipantPHIDs(array $phids) {
|
||||
$this->participantPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withDateTouched($date, $sort = null) {
|
||||
$this->dateTouched = $date;
|
||||
$this->dateTouchedSort = $sort ? $sort : '<';
|
||||
public function withParticipantCursor(ConpherenceParticipant $participant) {
|
||||
$this->participantCursor = $participant;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withParticipationStatus($participation_status) {
|
||||
$this->participationStatus = $participation_status;
|
||||
public function setOrder($order) {
|
||||
$this->order = $order;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -49,19 +75,16 @@ final class ConpherenceParticipantQuery
|
|||
|
||||
$participants = mpull($participants, null, 'getConpherencePHID');
|
||||
|
||||
if ($this->order == self::ORDER_NEWER) {
|
||||
$participants = array_reverse($participants);
|
||||
}
|
||||
|
||||
return $participants;
|
||||
}
|
||||
|
||||
private function buildWhereClause($conn_r) {
|
||||
$where = array();
|
||||
|
||||
if ($this->conpherencePHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'conpherencePHID IN (%Ls)',
|
||||
$this->conpherencePHIDs);
|
||||
}
|
||||
|
||||
if ($this->participantPHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
|
@ -69,28 +92,41 @@ final class ConpherenceParticipantQuery
|
|||
$this->participantPHIDs);
|
||||
}
|
||||
|
||||
if ($this->participationStatus !== null) {
|
||||
if ($this->participantCursor) {
|
||||
$date_touched = $this->participantCursor->getDateTouched();
|
||||
$id = $this->participantCursor->getID();
|
||||
if ($this->order == self::ORDER_OLDER) {
|
||||
$compare_date = '<';
|
||||
$compare_id = '<=';
|
||||
} else {
|
||||
$compare_date = '>';
|
||||
$compare_id = '>=';
|
||||
}
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'participationStatus = %d',
|
||||
$this->participationStatus);
|
||||
}
|
||||
|
||||
if ($this->dateTouched) {
|
||||
if ($this->dateTouchedSort) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'dateTouched %Q %d',
|
||||
$this->dateTouchedSort,
|
||||
$this->dateTouched);
|
||||
}
|
||||
'(dateTouched %Q %d OR (dateTouched = %d AND id %Q %d))',
|
||||
$compare_date,
|
||||
$date_touched,
|
||||
$date_touched,
|
||||
$compare_id,
|
||||
$id);
|
||||
}
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
private function buildOrderClause(AphrontDatabaseConnection $conn_r) {
|
||||
return 'ORDER BY dateTouched DESC';
|
||||
|
||||
$order_word = ($this->order == self::ORDER_OLDER) ? 'DESC' : 'ASC';
|
||||
// if these are different direction we won't get as efficient a query
|
||||
// see http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html
|
||||
$order = qsprintf(
|
||||
$conn_r,
|
||||
'ORDER BY dateTouched %Q, id %Q',
|
||||
$order_word,
|
||||
$order_word);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,26 +3,33 @@
|
|||
final class ConpherenceThreadListView extends AphrontView {
|
||||
|
||||
private $baseURI;
|
||||
private $unreadThreads;
|
||||
private $readThreads;
|
||||
private $threads;
|
||||
private $scrollUpParticipant;
|
||||
private $scrollDownParticipant;
|
||||
|
||||
public function setThreads(array $threads) {
|
||||
assert_instances_of($threads, 'ConpherenceThread');
|
||||
$this->threads = $threads;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setScrollUpParticipant(
|
||||
ConpherenceParticipant $participant) {
|
||||
$this->scrollUpParticipant = $participant;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setScrollDownParticipant(
|
||||
ConpherenceParticipant $participant) {
|
||||
$this->scrollDownParticipant = $participant;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBaseURI($base_uri) {
|
||||
$this->baseURI = $base_uri;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUnreadThreads(array $unread_threads) {
|
||||
assert_instances_of($unread_threads, 'ConpherenceThread');
|
||||
$this->unreadThreads = $unread_threads;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setReadThreads(array $read_threads) {
|
||||
assert_instances_of($read_threads, 'ConpherenceThread');
|
||||
$this->readThreads = $read_threads;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
require_celerity_resource('conpherence-menu-css');
|
||||
|
||||
|
@ -39,10 +46,8 @@ final class ConpherenceThreadListView extends AphrontView {
|
|||
->setHref($this->baseURI.'new/')
|
||||
->setType(PhabricatorMenuItemView::TYPE_BUTTON));
|
||||
|
||||
$menu->newLabel(pht('Unread'));
|
||||
$this->addThreadsToMenu($menu, $this->unreadThreads, $read = false);
|
||||
$menu->newLabel(pht('Read'));
|
||||
$this->addThreadsToMenu($menu, $this->readThreads, $read = true);
|
||||
$menu->newLabel('');
|
||||
$this->addThreadsToMenu($menu, $this->threads);
|
||||
|
||||
return $menu;
|
||||
}
|
||||
|
@ -51,6 +56,28 @@ final class ConpherenceThreadListView extends AphrontView {
|
|||
return $this->renderThread($thread);
|
||||
}
|
||||
|
||||
public function renderThreadsHTML() {
|
||||
$thread_html = array();
|
||||
|
||||
if ($this->scrollUpParticipant->getID()) {
|
||||
$thread_html[] = $this->getScrollMenuItem(
|
||||
$this->scrollUpParticipant,
|
||||
'up');
|
||||
}
|
||||
|
||||
foreach ($this->threads as $thread) {
|
||||
$thread_html[] = $this->renderSingleThread($thread);
|
||||
}
|
||||
|
||||
if ($this->scrollDownParticipant->getID()) {
|
||||
$thread_html[] = $this->getScrollMenuItem(
|
||||
$this->scrollDownParticipant,
|
||||
'down');
|
||||
}
|
||||
|
||||
return phutil_implode_html('', $thread_html);
|
||||
}
|
||||
|
||||
private function renderThreadItem(ConpherenceThread $thread) {
|
||||
return id(new PhabricatorMenuItemView())
|
||||
->setType(PhabricatorMenuItemView::TYPE_CUSTOM)
|
||||
|
@ -87,28 +114,59 @@ final class ConpherenceThreadListView extends AphrontView {
|
|||
|
||||
private function addThreadsToMenu(
|
||||
PhabricatorMenuView $menu,
|
||||
array $conpherences,
|
||||
$read = false) {
|
||||
array $conpherences) {
|
||||
|
||||
if ($this->scrollUpParticipant->getID()) {
|
||||
$item = $this->getScrollMenuItem($this->scrollUpParticipant, 'up');
|
||||
$menu->addMenuItem($item);
|
||||
}
|
||||
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$item = $this->renderThreadItem($conpherence);
|
||||
$menu->addMenuItem($item);
|
||||
}
|
||||
|
||||
if (empty($conpherences) || $read) {
|
||||
$menu->addMenuItem($this->getNoConpherencesBlock());
|
||||
if (empty($conpherences)) {
|
||||
$menu->addMenuItem($this->getNoConpherencesMenuItem());
|
||||
}
|
||||
|
||||
if ($this->scrollDownParticipant->getID()) {
|
||||
$item = $this->getScrollMenuItem($this->scrollDownParticipant, 'down');
|
||||
$menu->addMenuItem($item);
|
||||
}
|
||||
|
||||
return $menu;
|
||||
}
|
||||
|
||||
private function getNoConpherencesBlock() {
|
||||
public function getScrollMenuItem(
|
||||
ConpherenceParticipant $participant,
|
||||
$direction) {
|
||||
|
||||
if ($direction == 'up') {
|
||||
$name = pht('Load Newer Threads');
|
||||
} else {
|
||||
$name = pht('Load Older Threads');
|
||||
}
|
||||
$item = id(new PhabricatorMenuItemView())
|
||||
->addSigil('conpherence-menu-scroller')
|
||||
->setName($name)
|
||||
->setHref($this->baseURI)
|
||||
->setType(PhabricatorMenuItemView::TYPE_BUTTON)
|
||||
->setMetadata(array(
|
||||
'participant_id' => $participant->getID(),
|
||||
'conpherence_phid' => $participant->getConpherencePHID(),
|
||||
'date_touched' => $participant->getDateTouched(),
|
||||
'direction' => $direction));
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function getNoConpherencesMenuItem() {
|
||||
$message = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'no-conpherences-menu-item'
|
||||
),
|
||||
pht('No more conpherences.'));
|
||||
pht('No conpherences.'));
|
||||
|
||||
return id(new PhabricatorMenuItemView())
|
||||
->setType(PhabricatorMenuItemView::TYPE_CUSTOM)
|
||||
|
|
|
@ -257,6 +257,11 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
throw new Exception("Capability not supported!");
|
||||
}
|
||||
|
||||
protected function applyFinalEffects(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
}
|
||||
|
||||
public function setContentSource(PhabricatorContentSource $content_source) {
|
||||
$this->contentSource = $content_source;
|
||||
return $this;
|
||||
|
@ -386,6 +391,8 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
$this->applyExternalEffects($object, $xaction);
|
||||
}
|
||||
|
||||
$this->applyFinalEffects($object, $xactions);
|
||||
|
||||
if ($read_locking) {
|
||||
$object->endReadLocking();
|
||||
$read_locking = false;
|
||||
|
|
|
@ -1250,6 +1250,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20130423.phortunepaymentrevised.sql'),
|
||||
),
|
||||
'20130423.conpherenceindices.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20130423.conpherenceindices.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -267,11 +267,11 @@ final class PhabricatorMainMenuView extends AphrontView {
|
|||
$message_count_id = celerity_generate_unique_node_id();
|
||||
|
||||
$unread_status = ConpherenceParticipationStatus::BEHIND;
|
||||
$unread = id(new ConpherenceParticipantQuery())
|
||||
$unread = id(new ConpherenceParticipantCountQuery())
|
||||
->withParticipantPHIDs(array($user->getPHID()))
|
||||
->withParticipationStatus($unread_status)
|
||||
->execute();
|
||||
$message_count_number = count($unread);
|
||||
$message_count_number = $unread[$user->getPHID()];
|
||||
if ($message_count_number > 999) {
|
||||
$message_count_number = "\xE2\x88\x9E";
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
* javelin-workflow
|
||||
* javelin-behavior-device
|
||||
* javelin-history
|
||||
* javelin-vector
|
||||
*/
|
||||
|
||||
JX.behavior('conpherence-menu', function(config) {
|
||||
|
@ -53,6 +54,15 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
redrawthread();
|
||||
}
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'conpherence-selectthread',
|
||||
null,
|
||||
function (e) {
|
||||
var node = JX.$(e.getData().id);
|
||||
selectthread(node);
|
||||
}
|
||||
);
|
||||
|
||||
function updatepagedata(data) {
|
||||
var uri_suffix = thread.selected + '/';
|
||||
if (data.use_base_uri) {
|
||||
|
@ -75,15 +85,6 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
}
|
||||
);
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'conpherence-selectthread',
|
||||
null,
|
||||
function (e) {
|
||||
var node = JX.$(e.getData().id);
|
||||
selectthread(node);
|
||||
}
|
||||
);
|
||||
|
||||
function redrawthread() {
|
||||
if (!thread.node) {
|
||||
return;
|
||||
|
@ -96,9 +97,9 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
var data = JX.Stratcom.getData(thread.node);
|
||||
|
||||
if (thread.visible !== null || !config.hasThread) {
|
||||
var uri = config.base_uri + data.id + '/';
|
||||
var uri = config.base_uri + data.id + '/';
|
||||
new JX.Workflow(uri, {})
|
||||
.setHandler(onresponse)
|
||||
.setHandler(onloadthreadresponse)
|
||||
.start();
|
||||
} else {
|
||||
didredrawthread();
|
||||
|
@ -154,7 +155,7 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
}
|
||||
}
|
||||
|
||||
function onresponse(response) {
|
||||
function onloadthreadresponse(response) {
|
||||
var header = JX.$H(response.header);
|
||||
var messages = JX.$H(response.messages);
|
||||
var form = JX.$H(response.form);
|
||||
|
@ -252,11 +253,9 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
}).setData({ oldest_transaction_id : oldest_transaction_id }).send();
|
||||
});
|
||||
|
||||
|
||||
// On mobile, we just show a thread list, so we don't want to automatically
|
||||
// select or load any threads. On Desktop, we automatically select the first
|
||||
// thread.
|
||||
|
||||
var old_device = null;
|
||||
function ondevicechange() {
|
||||
var new_device = JX.Device.getDevice();
|
||||
|
@ -284,16 +283,18 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
function loadthreads() {
|
||||
var uri = config.base_uri + 'thread/' + config.selectedID + '/';
|
||||
new JX.Workflow(uri)
|
||||
.setHandler(onthreadresponse)
|
||||
.setHandler(onloadthreadsresponse)
|
||||
.start();
|
||||
}
|
||||
|
||||
function onthreadresponse(r) {
|
||||
function onloadthreadsresponse(r) {
|
||||
var layout = JX.$(config.layoutID);
|
||||
var menu = JX.DOM.find(layout, 'div', 'conpherence-menu-pane');
|
||||
JX.DOM.setContent(menu, JX.$H(r));
|
||||
|
||||
config.selectedID && selectthreadid(config.selectedID);
|
||||
|
||||
thread.node.scrollIntoView();
|
||||
}
|
||||
|
||||
function didloadthreads() {
|
||||
|
@ -316,4 +317,71 @@ JX.behavior('conpherence-menu', function(config) {
|
|||
redrawthread();
|
||||
}
|
||||
|
||||
var handlethreadscrollers = function (e) {
|
||||
e.kill();
|
||||
|
||||
var data = e.getNodeData('conpherence-menu-scroller');
|
||||
var scroller = e.getNode('conpherence-menu-scroller');
|
||||
new JX.Workflow(scroller.href, data)
|
||||
.setHandler(
|
||||
JX.bind(null, threadscrollerresponse, scroller, data.direction))
|
||||
.start();
|
||||
};
|
||||
|
||||
var threadscrollerresponse = function (scroller, direction, r) {
|
||||
var html = JX.$H(r.html);
|
||||
|
||||
var threadPhids = r.phids;
|
||||
var reselectId = null;
|
||||
// remove any threads that are in the list that we just got back
|
||||
// in the result set; things have changed and they'll be in the
|
||||
// right place soon
|
||||
for (var ii = 0; ii < threadPhids.length; ii++) {
|
||||
try {
|
||||
var nodeId = threadPhids[ii] + '-nav-item';
|
||||
var node = JX.$(nodeId);
|
||||
var nodeData = JX.Stratcom.getData(node);
|
||||
if (nodeData.id == thread.selected) {
|
||||
reselectId = nodeId;
|
||||
}
|
||||
JX.DOM.remove(node);
|
||||
} catch (ex) {
|
||||
// ignore , just haven't seen this thread yet
|
||||
}
|
||||
}
|
||||
|
||||
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
|
||||
var menuRoot = JX.DOM.find(root, 'div', 'conpherence-menu-pane');
|
||||
var scrollY = 0;
|
||||
// we have to do some hyjinx in the up case to make the menu scroll to
|
||||
// where it should
|
||||
if (direction == 'up') {
|
||||
var style = {
|
||||
position: 'absolute',
|
||||
left: '-10000px'
|
||||
};
|
||||
var test_size = JX.$N('div', {style: style}, html);
|
||||
document.body.appendChild(test_size);
|
||||
var html_size = JX.Vector.getDim(test_size);
|
||||
JX.DOM.remove(test_size);
|
||||
scrollY = html_size.y;
|
||||
}
|
||||
JX.DOM.replace(scroller, html);
|
||||
menuRoot.scrollTop += scrollY;
|
||||
|
||||
if (reselectId) {
|
||||
JX.Stratcom.invoke(
|
||||
'conpherence-selectthread',
|
||||
null,
|
||||
{ id : reselectId }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
JX.Stratcom.listen(
|
||||
['click'],
|
||||
'conpherence-menu-scroller',
|
||||
handlethreadscrollers
|
||||
);
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue