1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +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:
epriestley 2012-06-02 14:00:08 -07:00
parent 3102e15497
commit 6a8ac91599
6 changed files with 277 additions and 67 deletions

View file

@ -1986,7 +1986,7 @@ celerity_register_resource_map(array(
),
'phabricator-chatlog-css' =>
array(
'uri' => '/res/f674f526/rsrc/css/application/chatlog/chatlog.css',
'uri' => '/res/f6631adc/rsrc/css/application/chatlog/chatlog.css',
'type' => 'css',
'requires' =>
array(

View file

@ -1542,9 +1542,13 @@ phutil_register_library_map(array(
'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController',
'PhabricatorChatLogController' => 'PhabricatorController',
'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO',
'PhabricatorChatLogEvent' => 'PhabricatorChatLogDAO',
'PhabricatorChatLogEvent' =>
array(
0 => 'PhabricatorChatLogDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhabricatorChatLogEventType' => 'PhabricatorChatLogConstants',
'PhabricatorChatLogQuery' => 'PhabricatorOffsetPagedQuery',
'PhabricatorChatLogQuery' => 'PhabricatorIDPagedPolicyQuery',
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',

View file

@ -16,36 +16,50 @@
* limitations under the License.
*/
final class PhabricatorChatLogQuery extends PhabricatorOffsetPagedQuery {
final class PhabricatorChatLogQuery extends PhabricatorIDPagedPolicyQuery {
private $channels;
private $maximumEpoch;
public function withChannels(array $channels) {
$this->channels = $channels;
return $this;
}
public function execute() {
public function withMaximumEpoch($epoch) {
$this->maximumEpoch = $epoch;
return $this;
}
public function loadPage() {
$table = new PhabricatorChatLogEvent();
$conn_r = $table->establishConnection('r');
$where_clause = $this->buildWhereClause($conn_r);
$limit_clause = $this->buildLimitClause($conn_r);
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T e %Q ORDER BY epoch ASC %Q',
'SELECT * FROM %T e %Q %Q %Q',
$table->getTableName(),
$where_clause,
$limit_clause);
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
$logs = $table->loadAllFromArray($data);
return $logs;
return $this->processResults($logs);
}
private function buildWhereClause($conn_r) {
$where = array();
$where[] = $this->buildPagingClause($conn_r);
if ($this->maximumEpoch) {
$where[] = qsprintf(
$conn_r,
'epoch <= %d',
$this->maximumEpoch);
}
if ($this->channels) {
$where[] = qsprintf(
$conn_r,

View file

@ -26,70 +26,217 @@ final class PhabricatorChatLogChannelLogController
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$request = $this->getRequest();
$user = $request->getUser();
$offset = $request->getInt('offset', 0);
$page_size = 1000;
$pager = new AphrontPagerView();
$request_uri = $request->getRequestURI();
$pager->setURI($request_uri, 'offset');
$pager->setPageSize($page_size);
$pager->setOffset($offset);
$uri = clone $request->getRequestURI();
$uri->setQueryParams(array());
$pager = new AphrontIDPagerView();
$pager->setURI($uri);
$pager->setPageSize(250);
$query = id(new PhabricatorChatLogQuery())
->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);
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_epoch = null;
$row_idx = 0;
$row_colors = array(
'normal',
'alternate',
);
$out = array();
$out[] = '<table class="phabricator-chat-log">';
foreach ($logs as $log) {
$this_author = $log->getAuthor();
$this_epoch = $log->getEpoch();
if (($this_author !== $last_author) ||
($this_epoch - (60 * 5) > $last_epoch)) {
++$row_idx;
$out[] = '<tr class="initial '.$row_colors[$row_idx % 2].'">';
$out[] = '<td class="timestamp">'.
phabricator_datetime($log->getEpoch(), $user).'</td>';
// Decide whether we should start a new block or not.
$new_block = ($this_author !== $last_author) ||
($this_epoch - (60 * 2) > $last_epoch);
$author = $log->getAuthor();
$author = phutil_utf8_shorten($author, 18);
$out[] = '<td class="author">'.
phutil_escape_html($author).'</td>';
if ($new_block) {
if ($block) {
$blocks[] = $block;
}
$block = array(
'id' => $log->getID(),
'epoch' => $this_epoch,
'author' => $this_author,
'logs' => array($log),
);
} else {
$out[] = '<tr class="'.$row_colors[$row_idx % 2].'">';
$out[] = '<td class="similar" colspan="2"></td>';
$block['logs'][] = $log;
}
$out[] = '<td class="message">'.
phutil_escape_html($log->getMessage()).'</td>';
$out[] = '</tr>';
$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>';
$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(
array(
'<div class="phabricator-chat-log-panel">',
$form,
'<br />',
implode("\n", $out),
$pager
$pager,
'</div>',
),
array(
'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);
}
}

View file

@ -16,7 +16,9 @@
* limitations under the License.
*/
final class PhabricatorChatLogEvent extends PhabricatorChatLogDAO {
final class PhabricatorChatLogEvent
extends PhabricatorChatLogDAO
implements PhabricatorPolicyInterface {
protected $channel;
protected $epoch;
@ -25,6 +27,23 @@ final class PhabricatorChatLogEvent extends PhabricatorChatLogDAO {
protected $message;
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() {
return array(
self::CONFIG_TIMESTAMPS => false,

View file

@ -2,38 +2,64 @@
* @provides phabricator-chatlog-css
*/
.phabricator-chat-log-panel {
margin: 1em auto;
width: 80em;
}
.phabricator-chat-log {
margin: 1em 2em;
font-size: 12px;
}
.phabricator-chat-log tr.initial {
border-top: 4px solid white;
}
.phabricator-chat-log tr.normal {
background: #e9e9e9;
}
.phabricator-chat-log tr.alternate {
background: #f6f6f6;
width: 100%;
border: 1px solid #bbbbbb;
}
.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 {
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 {
white-space: nowrap;
text-align: right;
font-weight: bold;
width: 12em;
color: #555555;
}
.phabricator-chat-log td.message {
width: 100%;
white-space: pre-wrap;
}