1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Make Conpherence threads update in real time, very roughly

Summary:
Ref T4083. This needs some work (mostly in the Conpherence JS itself), but is sort of functional. In particular:

  - On thread pages, add the thread as a `pageObject`.
  - After updating a thread, send a new "message" event to the server.
  - Share a little more event posting code.
  - In the browser, use event dispatch to respond to events.
  - Add a listener for the new event type.
  - Update conpherence threads (this part is really yucky).

Test Plan: With multiple browser windows / browsers open, posted a message to a thread, and saw it update everywhere.

Reviewers: joshuaspence

Reviewed By: joshuaspence

Subscribers: chad, epriestley

Maniphest Tasks: T4083

Differential Revision: https://secure.phabricator.com/D9486
This commit is contained in:
epriestley 2014-06-11 13:52:15 -07:00
parent 84d259cea2
commit 4bc561f17b
9 changed files with 152 additions and 61 deletions

View file

@ -8,7 +8,7 @@ return array(
'names' => 'names' =>
array( array(
'core.pkg.css' => 'd82d2f53', 'core.pkg.css' => 'd82d2f53',
'core.pkg.js' => '4af4aa9d', 'core.pkg.js' => '0627d27e',
'darkconsole.pkg.js' => 'ca8671ce', 'darkconsole.pkg.js' => 'ca8671ce',
'differential.pkg.css' => '4a93db37', 'differential.pkg.css' => '4a93db37',
'differential.pkg.js' => 'eca39a2c', 'differential.pkg.js' => 'eca39a2c',
@ -336,11 +336,11 @@ return array(
'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
'rsrc/js/application/aphlict/Aphlict.js' => '08be8878', 'rsrc/js/application/aphlict/Aphlict.js' => '08be8878',
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a2dba85', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '2a2dba85',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'acda9f51', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '1da67f34',
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
'rsrc/js/application/config/behavior-reorder-fields.js' => '938aed89', 'rsrc/js/application/config/behavior-reorder-fields.js' => '938aed89',
'rsrc/js/application/conpherence/behavior-menu.js' => '7ee23816', 'rsrc/js/application/conpherence/behavior-menu.js' => '7ee23816',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '53f6f2dd', 'rsrc/js/application/conpherence/behavior-pontificate.js' => 'd83a949c',
'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90',
'rsrc/js/application/countdown/timer.js' => '361e3ed3', 'rsrc/js/application/countdown/timer.js' => '361e3ed3',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
@ -528,7 +528,7 @@ return array(
'javelin-aphlict' => '08be8878', 'javelin-aphlict' => '08be8878',
'javelin-behavior' => '8a3ed18b', 'javelin-behavior' => '8a3ed18b',
'javelin-behavior-aphlict-dropdown' => '2a2dba85', 'javelin-behavior-aphlict-dropdown' => '2a2dba85',
'javelin-behavior-aphlict-listen' => 'acda9f51', 'javelin-behavior-aphlict-listen' => '1da67f34',
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
'javelin-behavior-aphront-crop' => 'b98fc918', 'javelin-behavior-aphront-crop' => 'b98fc918',
'javelin-behavior-aphront-drag-and-drop-textarea' => '4a11ea9c', 'javelin-behavior-aphront-drag-and-drop-textarea' => '4a11ea9c',
@ -540,7 +540,7 @@ return array(
'javelin-behavior-boards-filter' => '22f113af', 'javelin-behavior-boards-filter' => '22f113af',
'javelin-behavior-config-reorder-fields' => '938aed89', 'javelin-behavior-config-reorder-fields' => '938aed89',
'javelin-behavior-conpherence-menu' => '7ee23816', 'javelin-behavior-conpherence-menu' => '7ee23816',
'javelin-behavior-conpherence-pontificate' => '53f6f2dd', 'javelin-behavior-conpherence-pontificate' => 'd83a949c',
'javelin-behavior-conpherence-widget-pane' => '40b1ff90', 'javelin-behavior-conpherence-widget-pane' => '40b1ff90',
'javelin-behavior-countdown-timer' => '361e3ed3', 'javelin-behavior-countdown-timer' => '361e3ed3',
'javelin-behavior-dark-console' => 'e9fdb5e5', 'javelin-behavior-dark-console' => 'e9fdb5e5',
@ -956,6 +956,18 @@ return array(
1 => 'javelin-util', 1 => 'javelin-util',
2 => 'phabricator-keyboard-shortcut-manager', 2 => 'phabricator-keyboard-shortcut-manager',
), ),
'1da67f34' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-aphlict',
2 => 'javelin-stratcom',
3 => 'javelin-request',
4 => 'javelin-uri',
5 => 'javelin-dom',
6 => 'javelin-json',
7 => 'javelin-router',
8 => 'phabricator-notification',
),
'1e1c8a59' => '1e1c8a59' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',
@ -1192,14 +1204,6 @@ return array(
1 => 'javelin-dom', 1 => 'javelin-dom',
2 => 'phabricator-prefab', 2 => 'phabricator-prefab',
), ),
'53f6f2dd' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'javelin-workflow',
4 => 'javelin-stratcom',
),
'54b612ba' => '54b612ba' =>
array( array(
0 => 'javelin-color', 0 => 'javelin-color',
@ -1608,18 +1612,6 @@ return array(
1 => 'javelin-dom', 1 => 'javelin-dom',
2 => 'javelin-stratcom', 2 => 'javelin-stratcom',
), ),
'acda9f51' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-aphlict',
2 => 'javelin-stratcom',
3 => 'javelin-request',
4 => 'javelin-uri',
5 => 'javelin-dom',
6 => 'javelin-json',
7 => 'javelin-router',
8 => 'phabricator-notification',
),
'ad7a69ca' => 'ad7a69ca' =>
array( array(
0 => 'javelin-install', 0 => 'javelin-install',
@ -1877,6 +1869,14 @@ return array(
3 => 'javelin-dom', 3 => 'javelin-dom',
4 => 'phabricator-keyboard-shortcut', 4 => 'phabricator-keyboard-shortcut',
), ),
'd83a949c' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'javelin-workflow',
4 => 'javelin-stratcom',
),
'd8e135db' => 'd8e135db' =>
array( array(
0 => 'javelin-behavior', 0 => 'javelin-behavior',

View file

@ -9,4 +9,5 @@ final class ConpherenceUpdateActions extends ConpherenceConstants {
const REMOVE_PERSON = 'remove_person'; const REMOVE_PERSON = 'remove_person';
const NOTIFICATIONS = 'notifications'; const NOTIFICATIONS = 'notifications';
const ADD_STATUS = 'add_status'; const ADD_STATUS = 'add_status';
const LOAD = 'load';
} }

View file

@ -31,6 +31,7 @@ final class ConpherenceUpdateController
->executeOne(); ->executeOne();
$action = $request->getStr('action', ConpherenceUpdateActions::METADATA); $action = $request->getStr('action', ConpherenceUpdateActions::METADATA);
$latest_transaction_id = null; $latest_transaction_id = null;
$response_mode = $request->isAjax() ? 'ajax' : 'redirect'; $response_mode = $request->isAjax() ? 'ajax' : 'redirect';
$error_view = null; $error_view = null;
@ -38,7 +39,7 @@ final class ConpherenceUpdateController
$errors = array(); $errors = array();
$delete_draft = false; $delete_draft = false;
$xactions = array(); $xactions = array();
if ($request->isFormPost()) { if ($request->isFormPost() || ($action == ConpherenceUpdateActions::LOAD)) {
$editor = id(new ConpherenceEditor()) $editor = id(new ConpherenceEditor())
->setContinueOnNoEffect($request->isContinueRequest()) ->setContinueOnNoEffect($request->isContinueRequest())
->setContentSourceFromRequest($request) ->setContentSourceFromRequest($request)
@ -114,24 +115,32 @@ final class ConpherenceUpdateController
'That was a non-update. Try cancel.'); 'That was a non-update. Try cancel.');
} }
break; break;
case ConpherenceUpdateActions::LOAD:
$updated = false;
$response_mode = 'ajax';
break;
default: default:
throw new Exception('Unknown action: '.$action); throw new Exception('Unknown action: '.$action);
break; break;
} }
if ($xactions) {
try { if ($xactions || ($action == ConpherenceUpdateActions::LOAD)) {
$xactions = $editor->applyTransactions($conpherence, $xactions); if ($xactions) {
if ($delete_draft) { try {
$draft = PhabricatorDraft::newFromUserAndKey( $xactions = $editor->applyTransactions($conpherence, $xactions);
$user, if ($delete_draft) {
$conpherence->getPHID()); $draft = PhabricatorDraft::newFromUserAndKey(
$draft->delete(); $user,
$conpherence->getPHID());
$draft->delete();
}
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($this->getApplicationURI($conpherence_id.'/'))
->setException($ex);
} }
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($this->getApplicationURI($conpherence_id.'/'))
->setException($ex);
} }
switch ($response_mode) { switch ($response_mode) {
case 'ajax': case 'ajax':
$latest_transaction_id = $request->getInt('latest_transaction_id'); $latest_transaction_id = $request->getInt('latest_transaction_id');
@ -266,6 +275,7 @@ final class ConpherenceUpdateController
$need_transactions = false; $need_transactions = false;
switch ($action) { switch ($action) {
case ConpherenceUpdateActions::METADATA: case ConpherenceUpdateActions::METADATA:
case ConpherenceUpdateActions::LOAD:
$need_transactions = true; $need_transactions = true;
break; break;
case ConpherenceUpdateActions::MESSAGE: case ConpherenceUpdateActions::MESSAGE:

View file

@ -97,6 +97,7 @@ final class ConpherenceViewController extends
array( array(
'title' => $title, 'title' => $title,
'device' => true, 'device' => true,
'pageObjects' => array($conpherence->getPHID()),
)); ));
} }
@ -156,7 +157,11 @@ final class ConpherenceViewController extends
'type' => 'hidden', 'type' => 'hidden',
'name' => 'latest_transaction_id', 'name' => 'latest_transaction_id',
'value' => $latest_transaction_id, 'value' => $latest_transaction_id,
'sigil' => 'latest-transaction-id' 'sigil' => 'latest-transaction-id',
'meta' => array(
'threadPHID' => $conpherence->getPHID(),
'threadID' => $conpherence->getID(),
),
), ),
'')) ''))
->render(); ->render();

View file

@ -306,6 +306,17 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
$participant->save(); $participant->save();
} }
if ($xactions) {
$data = array(
'type' => 'message',
'threadPHID' => $object->getPHID(),
'messageID' => last($xactions)->getID(),
'subscribers' => array($object->getPHID()),
);
PhabricatorNotificationClient::tryToPostMessage($data);
}
return $xactions; return $xactions;
} }

View file

@ -112,9 +112,7 @@ final class PhabricatorFeedStoryPublisher {
} }
$this->insertNotifications($chrono_key); $this->insertNotifications($chrono_key);
if (PhabricatorEnv::getEnvConfig('notification.enabled')) { $this->sendNotification($chrono_key);
$this->sendNotification($chrono_key);
}
PhabricatorWorker::scheduleTask( PhabricatorWorker::scheduleTask(
'FeedPublisherWorker', 'FeedPublisherWorker',
@ -181,11 +179,7 @@ final class PhabricatorFeedStoryPublisher {
'subscribers' => $this->subscribedPHIDs, 'subscribers' => $this->subscribedPHIDs,
); );
try { PhabricatorNotificationClient::tryToPostMessage($data);
PhabricatorNotificationClient::postMessage($data);
} catch (Exception $ex) {
// Ignore, these are not critical.
}
} }
/** /**

View file

@ -25,7 +25,20 @@ final class PhabricatorNotificationClient {
return $status; return $status;
} }
public static function postMessage(array $data) { public static function tryToPostMessage(array $data) {
if (!PhabricatorEnv::getEnvConfig('notification.enabled')) {
return;
}
try {
self::postMessage($data);
} catch (Exception $ex) {
// Just ignore any issues here.
phlog($ex);
}
}
private static function postMessage(array $data) {
$server_uri = PhabricatorEnv::getEnvConfig('notification.server-uri'); $server_uri = PhabricatorEnv::getEnvConfig('notification.server-uri');
id(new HTTPSFuture($server_uri, json_encode($data))) id(new HTTPSFuture($server_uri, json_encode($data)))

View file

@ -27,6 +27,29 @@ JX.behavior('aphlict-listen', function(config) {
.start(); .start();
} }
JX.Stratcom.listen('aphlict-receive-message', null, function(e) {
var message = e.getData();
if (message.type != 'notification') {
return;
}
var request = new JX.Request(
'/notification/individual/',
onnotification);
var routable = request
.addData({key: message.key})
.getRoutable();
routable
.setType('notification')
.setPriority(250);
JX.Router.getInstance().queue(routable);
});
// Respond to a notification from the Aphlict notification server. We send // Respond to a notification from the Aphlict notification server. We send
// a request to Phabricator to get notification details. // a request to Phabricator to get notification details.
function onaphlictmessage(type, message) { function onaphlictmessage(type, message) {
@ -40,19 +63,7 @@ JX.behavior('aphlict-listen', function(config) {
break; break;
case 'receive': case 'receive':
var request = new JX.Request( JX.Stratcom.invoke('aphlict-receive-message', null, message);
'/notification/individual/',
onnotification);
var routable = request
.addData({key: message.key})
.getRoutable();
routable
.setType('notification')
.setPriority(250);
JX.Router.getInstance().queue(routable);
break; break;
default: default:

View file

@ -8,6 +8,52 @@
*/ */
JX.behavior('conpherence-pontificate', function(config) { JX.behavior('conpherence-pontificate', function(config) {
JX.Stratcom.listen('aphlict-receive-message', null, function(e) {
var message = e.getData();
if (message.type != 'message') {
// Not a message event.
return;
}
// TODO: This is really, really gross.
var infonode = JX.DOM.find(document, 'input', 'latest-transaction-id');
var data = JX.Stratcom.getData(infonode);
var latest_id = infonode.value;
var thread_phid = data.threadPHID;
var thread_id = data.threadID;
if (message.threadPHID != thread_phid) {
// Message event for some thread other than the visible one.
return;
}
if (message.messageID <= latest_id) {
// Message event for something we already know about.
return;
}
var params = {
action: 'load',
latest_transaction_id: latest_id
};
new JX.Workflow('/conpherence/update/' + thread_id + '/')
.setData(params)
.setHandler(function(r) {
var messages = JX.DOM.find(document, 'div', 'conpherence-messages');
JX.DOM.appendContent(messages, JX.$H(r.transactions));
messages.scrollTop = messages.scrollHeight;
// TODO: Continued grossness from above.
infonode.value = r.latest_transaction_id;
})
.start();
});
var onsubmit = function(e) { var onsubmit = function(e) {
e.kill(); e.kill();