mirror of
https://we.phorge.it/source/phorge.git
synced 2025-03-28 20:18:13 +01:00
Make chatlog a bit less awful
Summary: - Default to showing the newest page of chat. - Reformat for greater readability. - Add permalinks to specific lines. - Enable jump-to-date. Test Plan: {F12200} Reviewers: Koolvin, vrana, btrahan Reviewed By: btrahan CC: kdeggelman, aran Maniphest Tasks: T837, T1065 Differential Revision: https://secure.phabricator.com/D2641
This commit is contained in:
parent
3102e15497
commit
6a8ac91599
6 changed files with 277 additions and 67 deletions
|
@ -1986,7 +1986,7 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'phabricator-chatlog-css' =>
|
'phabricator-chatlog-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/f674f526/rsrc/css/application/chatlog/chatlog.css',
|
'uri' => '/res/f6631adc/rsrc/css/application/chatlog/chatlog.css',
|
||||||
'type' => 'css',
|
'type' => 'css',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -1542,9 +1542,13 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
|
'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
|
||||||
'PhabricatorChatLogController' => 'PhabricatorController',
|
'PhabricatorChatLogController' => 'PhabricatorController',
|
||||||
'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO',
|
||||||
'PhabricatorChatLogEvent' => 'PhabricatorChatLogDAO',
|
'PhabricatorChatLogEvent' =>
|
||||||
|
array(
|
||||||
|
0 => 'PhabricatorChatLogDAO',
|
||||||
|
1 => 'PhabricatorPolicyInterface',
|
||||||
|
),
|
||||||
'PhabricatorChatLogEventType' => 'PhabricatorChatLogConstants',
|
'PhabricatorChatLogEventType' => 'PhabricatorChatLogConstants',
|
||||||
'PhabricatorChatLogQuery' => 'PhabricatorOffsetPagedQuery',
|
'PhabricatorChatLogQuery' => 'PhabricatorIDPagedPolicyQuery',
|
||||||
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
|
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
|
||||||
'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
|
'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
|
||||||
'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',
|
'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',
|
||||||
|
|
|
@ -16,36 +16,50 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final class PhabricatorChatLogQuery extends PhabricatorOffsetPagedQuery {
|
final class PhabricatorChatLogQuery extends PhabricatorIDPagedPolicyQuery {
|
||||||
|
|
||||||
private $channels;
|
private $channels;
|
||||||
|
private $maximumEpoch;
|
||||||
|
|
||||||
public function withChannels(array $channels) {
|
public function withChannels(array $channels) {
|
||||||
$this->channels = $channels;
|
$this->channels = $channels;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute() {
|
public function withMaximumEpoch($epoch) {
|
||||||
|
$this->maximumEpoch = $epoch;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadPage() {
|
||||||
$table = new PhabricatorChatLogEvent();
|
$table = new PhabricatorChatLogEvent();
|
||||||
$conn_r = $table->establishConnection('r');
|
$conn_r = $table->establishConnection('r');
|
||||||
|
|
||||||
$where_clause = $this->buildWhereClause($conn_r);
|
|
||||||
$limit_clause = $this->buildLimitClause($conn_r);
|
|
||||||
|
|
||||||
$data = queryfx_all(
|
$data = queryfx_all(
|
||||||
$conn_r,
|
$conn_r,
|
||||||
'SELECT * FROM %T e %Q ORDER BY epoch ASC %Q',
|
'SELECT * FROM %T e %Q %Q %Q',
|
||||||
$table->getTableName(),
|
$table->getTableName(),
|
||||||
$where_clause,
|
$this->buildWhereClause($conn_r),
|
||||||
$limit_clause);
|
$this->buildOrderClause($conn_r),
|
||||||
|
$this->buildLimitClause($conn_r));
|
||||||
|
|
||||||
$logs = $table->loadAllFromArray($data);
|
$logs = $table->loadAllFromArray($data);
|
||||||
|
|
||||||
return $logs;
|
return $this->processResults($logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildWhereClause($conn_r) {
|
private function buildWhereClause($conn_r) {
|
||||||
$where = array();
|
$where = array();
|
||||||
|
|
||||||
|
$where[] = $this->buildPagingClause($conn_r);
|
||||||
|
|
||||||
|
if ($this->maximumEpoch) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'epoch <= %d',
|
||||||
|
$this->maximumEpoch);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->channels) {
|
if ($this->channels) {
|
||||||
$where[] = qsprintf(
|
$where[] = qsprintf(
|
||||||
$conn_r,
|
$conn_r,
|
||||||
|
|
|
@ -26,70 +26,217 @@ final class PhabricatorChatLogChannelLogController
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processRequest() {
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
$request = $this->getRequest();
|
$uri = clone $request->getRequestURI();
|
||||||
$user = $request->getUser();
|
$uri->setQueryParams(array());
|
||||||
$offset = $request->getInt('offset', 0);
|
|
||||||
$page_size = 1000;
|
$pager = new AphrontIDPagerView();
|
||||||
$pager = new AphrontPagerView();
|
$pager->setURI($uri);
|
||||||
$request_uri = $request->getRequestURI();
|
$pager->setPageSize(250);
|
||||||
$pager->setURI($request_uri, 'offset');
|
|
||||||
$pager->setPageSize($page_size);
|
$query = id(new PhabricatorChatLogQuery())
|
||||||
$pager->setOffset($offset);
|
->setViewer($user)
|
||||||
|
->withChannels(array($this->channel));
|
||||||
|
|
||||||
|
|
||||||
|
list($after, $before, $map) = $this->getPagingParameters($request, $query);
|
||||||
|
|
||||||
|
$pager->setAfterID($after);
|
||||||
|
$pager->setBeforeID($before);
|
||||||
|
|
||||||
$query = new PhabricatorChatLogQuery();
|
|
||||||
$query->withChannels(array($this->channel));
|
|
||||||
$logs = $query->executeWithPager($pager);
|
$logs = $query->executeWithPager($pager);
|
||||||
|
|
||||||
require_celerity_resource('phabricator-chatlog-css');
|
// Show chat logs oldest-first.
|
||||||
|
$logs = array_reverse($logs);
|
||||||
|
|
||||||
|
|
||||||
|
// Divide all the logs into blocks, where a block is the same author saying
|
||||||
|
// several things in a row. A block ends when another user speaks, or when
|
||||||
|
// two minutes pass without the author speaking.
|
||||||
|
|
||||||
|
$blocks = array();
|
||||||
|
$block = null;
|
||||||
|
|
||||||
$last_author = null;
|
$last_author = null;
|
||||||
$last_epoch = null;
|
$last_epoch = null;
|
||||||
|
|
||||||
$row_idx = 0;
|
|
||||||
$row_colors = array(
|
|
||||||
'normal',
|
|
||||||
'alternate',
|
|
||||||
);
|
|
||||||
|
|
||||||
$out = array();
|
|
||||||
$out[] = '<table class="phabricator-chat-log">';
|
|
||||||
foreach ($logs as $log) {
|
foreach ($logs as $log) {
|
||||||
$this_author = $log->getAuthor();
|
$this_author = $log->getAuthor();
|
||||||
$this_epoch = $log->getEpoch();
|
$this_epoch = $log->getEpoch();
|
||||||
|
|
||||||
if (($this_author !== $last_author) ||
|
// Decide whether we should start a new block or not.
|
||||||
($this_epoch - (60 * 5) > $last_epoch)) {
|
$new_block = ($this_author !== $last_author) ||
|
||||||
++$row_idx;
|
($this_epoch - (60 * 2) > $last_epoch);
|
||||||
$out[] = '<tr class="initial '.$row_colors[$row_idx % 2].'">';
|
|
||||||
$out[] = '<td class="timestamp">'.
|
|
||||||
phabricator_datetime($log->getEpoch(), $user).'</td>';
|
|
||||||
|
|
||||||
$author = $log->getAuthor();
|
if ($new_block) {
|
||||||
$author = phutil_utf8_shorten($author, 18);
|
if ($block) {
|
||||||
$out[] = '<td class="author">'.
|
$blocks[] = $block;
|
||||||
phutil_escape_html($author).'</td>';
|
}
|
||||||
|
$block = array(
|
||||||
|
'id' => $log->getID(),
|
||||||
|
'epoch' => $this_epoch,
|
||||||
|
'author' => $this_author,
|
||||||
|
'logs' => array($log),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$out[] = '<tr class="'.$row_colors[$row_idx % 2].'">';
|
$block['logs'][] = $log;
|
||||||
$out[] = '<td class="similar" colspan="2"></td>';
|
|
||||||
}
|
}
|
||||||
$out[] = '<td class="message">'.
|
|
||||||
phutil_escape_html($log->getMessage()).'</td>';
|
|
||||||
$out[] = '</tr>';
|
|
||||||
|
|
||||||
$last_author = $this_author;
|
$last_author = $this_author;
|
||||||
$last_epoch = $this_epoch;
|
$last_epoch = $this_epoch;
|
||||||
|
}
|
||||||
|
if ($block) {
|
||||||
|
$blocks[] = $block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out CSS classes for the blocks. We alternate colors between
|
||||||
|
// lines, and highlight the entire block which contains the target ID or
|
||||||
|
// date, if applicable.
|
||||||
|
|
||||||
|
foreach ($blocks as $key => $block) {
|
||||||
|
$classes = array();
|
||||||
|
if ($key % 2) {
|
||||||
|
$classes[] = 'alternate';
|
||||||
|
}
|
||||||
|
$ids = mpull($block['logs'], 'getID', 'getID');
|
||||||
|
if (array_intersect_key($ids, $map)) {
|
||||||
|
$classes[] = 'highlight';
|
||||||
|
}
|
||||||
|
$blocks[$key]['class'] = $classes ? implode(' ', $classes) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
require_celerity_resource('phabricator-chatlog-css');
|
||||||
|
|
||||||
|
$out = array();
|
||||||
|
$out[] = '<table class="phabricator-chat-log">';
|
||||||
|
foreach ($blocks as $block) {
|
||||||
|
$author = $block['author'];
|
||||||
|
$author = phutil_utf8_shorten($author, 18);
|
||||||
|
$author = phutil_escape_html($author);
|
||||||
|
$author = phutil_render_tag('td', array('class' => 'author'), $author);
|
||||||
|
|
||||||
|
$message = mpull($block['logs'], 'getMessage');
|
||||||
|
$message = implode("\n", $message);
|
||||||
|
$message = phutil_escape_html($message);
|
||||||
|
$message = phutil_render_tag('td', array('class' => 'message'), $message);
|
||||||
|
|
||||||
|
$href = $uri->alter('at', $block['id']);
|
||||||
|
$timestamp = $block['epoch'];
|
||||||
|
$timestamp = phabricator_datetime($timestamp, $user);
|
||||||
|
$timestamp = phutil_render_tag('a', array('href' => $href), $timestamp);
|
||||||
|
$timestamp = phutil_render_tag(
|
||||||
|
'td',
|
||||||
|
array(
|
||||||
|
'class' => 'timestamp',
|
||||||
|
),
|
||||||
|
$timestamp);
|
||||||
|
|
||||||
|
$out[] = phutil_render_tag(
|
||||||
|
'tr',
|
||||||
|
array(
|
||||||
|
'class' => $block['class'],
|
||||||
|
),
|
||||||
|
$author.$message.$timestamp);
|
||||||
}
|
}
|
||||||
$out[] = '</table>';
|
$out[] = '</table>';
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($user)
|
||||||
|
->setMethod('GET')
|
||||||
|
->setAction($uri)
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel('Date')
|
||||||
|
->setName('date')
|
||||||
|
->setValue($request->getStr('date')))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSubmitControl())
|
||||||
|
->setValue('Jump'));
|
||||||
|
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
return $this->buildStandardPageResponse(
|
||||||
array(
|
array(
|
||||||
|
'<div class="phabricator-chat-log-panel">',
|
||||||
|
$form,
|
||||||
|
'<br />',
|
||||||
implode("\n", $out),
|
implode("\n", $out),
|
||||||
$pager
|
$pager,
|
||||||
|
'</div>',
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'title' => 'Channel Log',
|
'title' => 'Channel Log',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From request parameters, figure out where we should jump to in the log.
|
||||||
|
* We jump to either a date or log ID, but load a few lines of context before
|
||||||
|
* it so the user can see the nearby conversation.
|
||||||
|
*/
|
||||||
|
private function getPagingParameters(
|
||||||
|
AphrontRequest $request,
|
||||||
|
PhabricatorChatLogQuery $query) {
|
||||||
|
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$at_id = $request->getInt('at');
|
||||||
|
$at_date = $request->getStr('date');
|
||||||
|
|
||||||
|
$context_log = null;
|
||||||
|
$map = array();
|
||||||
|
|
||||||
|
$query = clone $query;
|
||||||
|
$query->setLimit(8);
|
||||||
|
|
||||||
|
if ($at_id) {
|
||||||
|
// Jump to the log in question, and load a few lines of context before
|
||||||
|
// it.
|
||||||
|
$context_logs = $query
|
||||||
|
->setAfterID($at_id)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$context_log = last($context_logs);
|
||||||
|
|
||||||
|
$map = array(
|
||||||
|
$at_id => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
} else if ($at_date) {
|
||||||
|
$timezone = new DateTimeZone($user->getTimezoneIdentifier());
|
||||||
|
try {
|
||||||
|
$date = new DateTime($at_date, $timezone);
|
||||||
|
$timestamp = $date->format('U');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$timestamp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($timestamp) {
|
||||||
|
$context_logs = $query
|
||||||
|
->withMaximumEpoch($timestamp)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$context_log = last($context_logs);
|
||||||
|
|
||||||
|
$target_log = head($context_logs);
|
||||||
|
if ($target_log) {
|
||||||
|
$map = array(
|
||||||
|
$target_log->getID() => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($context_log) {
|
||||||
|
$after = null;
|
||||||
|
$before = $context_log->getID() - 1;
|
||||||
|
} else {
|
||||||
|
$after = $request->getInt('after');
|
||||||
|
$before = $request->getInt('before');
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($after, $before, $map);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final class PhabricatorChatLogEvent extends PhabricatorChatLogDAO {
|
final class PhabricatorChatLogEvent
|
||||||
|
extends PhabricatorChatLogDAO
|
||||||
|
implements PhabricatorPolicyInterface {
|
||||||
|
|
||||||
protected $channel;
|
protected $channel;
|
||||||
protected $epoch;
|
protected $epoch;
|
||||||
|
@ -25,6 +27,23 @@ final class PhabricatorChatLogEvent extends PhabricatorChatLogDAO {
|
||||||
protected $message;
|
protected $message;
|
||||||
protected $loggedByPHID;
|
protected $loggedByPHID;
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy($capability) {
|
||||||
|
// TODO: This is sort of silly and mostly just so that we can use
|
||||||
|
// IDPagedPolicyQuery; once we implement Channel objects we should
|
||||||
|
// just delegate policy to them.
|
||||||
|
return PhabricatorPolicies::POLICY_PUBLIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
self::CONFIG_TIMESTAMPS => false,
|
self::CONFIG_TIMESTAMPS => false,
|
||||||
|
|
|
@ -2,38 +2,64 @@
|
||||||
* @provides phabricator-chatlog-css
|
* @provides phabricator-chatlog-css
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.phabricator-chat-log-panel {
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 80em;
|
||||||
|
}
|
||||||
|
|
||||||
.phabricator-chat-log {
|
.phabricator-chat-log {
|
||||||
margin: 1em 2em;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
width: 100%;
|
||||||
|
border: 1px solid #bbbbbb;
|
||||||
.phabricator-chat-log tr.initial {
|
|
||||||
border-top: 4px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phabricator-chat-log tr.normal {
|
|
||||||
background: #e9e9e9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phabricator-chat-log tr.alternate {
|
|
||||||
background: #f6f6f6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-chat-log td {
|
.phabricator-chat-log td {
|
||||||
padding: 2px 4px;
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-chat-log tr {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-chat-log tr td.author {
|
||||||
|
background: #dfdfdf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-chat-log tr.alternate {
|
||||||
|
background: #e9e9e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-chat-log tr.alternate td.author {
|
||||||
|
background: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-chat-log tr.highlight td {
|
||||||
|
background: #ffff88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-chat-log tr.highlight td.author {
|
||||||
|
background: #eeee88;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-chat-log td.timestamp {
|
.phabricator-chat-log td.timestamp {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #666666;
|
text-align: right;
|
||||||
|
width: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-chat-log td.timestamp a {
|
||||||
|
color: #555555;
|
||||||
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-chat-log td.author {
|
.phabricator-chat-log td.author {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
width: 12em;
|
||||||
|
color: #555555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phabricator-chat-log td.message {
|
.phabricator-chat-log td.message {
|
||||||
width: 100%;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue