From c1e8b394ccfeba79c009219aad28fb628a47739d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 15 Apr 2017 06:08:00 -0700 Subject: [PATCH 01/56] Fix join and remove policy checks for Conpherence Summary: I think these got munged when I removed CAN_JOIN. - If you can view the room, you can join it. - ~~If you can view the room, you can add others to it.~~ This rule adjustment was removed, see discussion on the revision. - If you are a participant in the room, you can remove yourself. - If you can edit a room, you can remove anyone. Test Plan: Normal feature set: - Create a new room that only I can edit, viewable by all users. - Leave room (bye k thx) - Create another room, myself only - Join room from second account - See ability to only remove myself - Remove myself - Rejoin - Add third account - Log into first account - Boot off randos - Test joining by green button, message, and by + sign. Policy consistency: - As a user who can not edit the room, tried to add other members. Received policy exception. The `+` button is currently visible and enabled for all users (even users who have not joined the room) but this is pre-existing. Reviewers: chad Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17696 --- .../controller/ConpherenceViewController.php | 3 --- .../conpherence/editor/ConpherenceEditor.php | 22 ++++++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 6b953b9de4..7a152a1e2e 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -147,9 +147,6 @@ final class ConpherenceViewController extends $user = $this->getRequest()->getUser(); $participating = $conpherence->getParticipantIfExists($user->getPHID()); - if (!$participating && $user->isLoggedIn()) { - return null; - } $draft = PhabricatorDraft::newFromUserAndKey( $user, $conpherence->getPHID()); diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index e358700348..c53aab7655 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -341,13 +341,23 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $add = array_keys(array_diff_key($new_map, $old_map)); $rem = array_keys(array_diff_key($old_map, $new_map)); - $actor_phid = $this->requireActor()->getPHID(); + $actor_phid = $this->getActingAsPHID(); - // You need CAN_EDIT to change participants other than yourself. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); + $is_join = (($add === array($actor_phid)) && !$rem); + $is_leave = (($rem === array($actor_phid)) && !$add); + + if ($is_join) { + // Anyone can join a thread they can see. + } else if ($is_leave) { + // Anyone can leave a thread. + } else { + // You need CAN_EDIT to add or remove participants. For additional + // discussion, see D17696 and T4411. + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_EDIT); + } break; case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: From a56f9a1a5599bdc48ee5cde783d0da24bf332227 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 15 Apr 2017 16:07:54 +0000 Subject: [PATCH 02/56] Clean up remove participant language in Conpherence Summary: Updates the language to use "Remove Participant" instead of "Banish User" Test Plan: Read through the various cases, test them by removing myself or others Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17697 --- .../controller/ConpherenceUpdateController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 9cc666109c..ce6e64cd7f 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -360,9 +360,9 @@ final class ConpherenceUpdateController $body[] = pht( 'Are you sure you want to leave this room?'); } else { - $title = pht('Banish User'); + $title = pht('Remove Participant'); $body[] = pht( - 'Banish %s from the realm?', + 'Remove %s from this room?', phutil_tag('strong', array(), $removed_user->getUsername())); } @@ -372,7 +372,7 @@ final class ConpherenceUpdateController 'You will be able to rejoin the room later.'); } else { $body[] = pht( - 'This user will be able to rejoin the room later.'); + 'They will be able to rejoin the room later.'); } } else { if ($is_self) { @@ -387,7 +387,7 @@ final class ConpherenceUpdateController } } else { $body[] = pht( - 'This user will not be able to rejoin the room unless invited '. + 'They will not be able to rejoin the room unless invited '. 'again.'); } } From b08b4cf0b5ffdf0a9000b86f73f30e0ea6e93db4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 07:31:44 -0700 Subject: [PATCH 03/56] Fix a bad variable in global typehaead order/limit code Summary: Ref T12538. I missed this in D17695, which renamed the variable. The logic was also a little off since `jj` is an index, not a count. Test Plan: Typed `con` in global search, which hits "Con-pherence", "Con-duit" and "Con-fig", plus a bunch of other stuff. Got results after patch. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12538 Differential Revision: https://secure.phabricator.com/D17700 --- resources/celerity/map.php | 28 +++++++++---------- .../rsrc/js/core/behavior-search-typeahead.js | 5 ++-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d9eb3a42e0..fead6aa9dc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => '437d3b5a', 'conpherence.pkg.js' => '281b1a73', 'core.pkg.css' => 'b2ad82f4', - 'core.pkg.js' => 'bf3b5cdf', + 'core.pkg.js' => 'deabcef7', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -510,7 +510,7 @@ return array( 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => '0f2a0820', + 'rsrc/js/core/behavior-search-typeahead.js' => 'eded9ee8', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -666,7 +666,7 @@ return array( 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => '0ca788bd', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => '0f2a0820', + 'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8', 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -988,17 +988,6 @@ return array( 'phuix-autocomplete', 'javelin-mask', ), - '0f2a0820' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -2155,6 +2144,17 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'eded9ee8' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index 864d45ff29..fc752a6079 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -94,7 +94,6 @@ JX.behavior('phabricator-search-typeahead', function(config) { // If we have more results than fit, limit each type of result to 3, so // we show 3 applications, then 3 users, etc. For jump items, we show only // one result. - var jj; var results = []; for (ii = 0; ii < type_order.length; ii++) { @@ -108,8 +107,8 @@ JX.behavior('phabricator-search-typeahead', function(config) { // - we have more items than will fit in the typeahead, and this // is the 4..Nth result of its type. - var skip = ((current_type == 'jump') && (jj > 1)) || - ((list.length > config.limit) && (type_count > 3)); + var skip = ((current_type == 'jump') && (jj >= 1)) || + ((list.length > config.limit) && (jj >= 3)); if (skip) { continue; } From f801c7ae29ba240e8f29fb3677763c1c341cb239 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Mon, 17 Apr 2017 10:15:24 -0700 Subject: [PATCH 04/56] Change PhabricatorPhurlURLViewController to use EditEngine for commenting Test Plan: Created a phurl, added some comments, confirmed that "Change Subscribers" and "Change Project Tags" are now available in the comment form. Reviewers: epriestley Reviewed By: epriestley Subscribers: chad, Korvin Maniphest Tasks: T11661 Differential Revision: https://secure.phabricator.com/D17686 --- src/__phutil_library_map__.php | 2 - .../PhabricatorPhurlApplication.php | 2 - .../PhabricatorPhurlURLCommentController.php | 63 ------------------- .../PhabricatorPhurlURLViewController.php | 25 ++++---- 4 files changed, 11 insertions(+), 81 deletions(-) delete mode 100644 src/applications/phurl/controller/PhabricatorPhurlURLCommentController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 44a3ca7ad3..5bc5f78d96 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3445,7 +3445,6 @@ phutil_register_library_map(array( 'PhabricatorPhurlURL' => 'applications/phurl/storage/PhabricatorPhurlURL.php', 'PhabricatorPhurlURLAccessController' => 'applications/phurl/controller/PhabricatorPhurlURLAccessController.php', 'PhabricatorPhurlURLAliasTransaction' => 'applications/phurl/xaction/PhabricatorPhurlURLAliasTransaction.php', - 'PhabricatorPhurlURLCommentController' => 'applications/phurl/controller/PhabricatorPhurlURLCommentController.php', 'PhabricatorPhurlURLCreateCapability' => 'applications/phurl/capability/PhabricatorPhurlURLCreateCapability.php', 'PhabricatorPhurlURLDatasource' => 'applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php', 'PhabricatorPhurlURLDescriptionTransaction' => 'applications/phurl/xaction/PhabricatorPhurlURLDescriptionTransaction.php', @@ -8711,7 +8710,6 @@ phutil_register_library_map(array( ), 'PhabricatorPhurlURLAccessController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURLAliasTransaction' => 'PhabricatorPhurlURLTransactionType', - 'PhabricatorPhurlURLCommentController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURLCreateCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPhurlURLDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPhurlURLDescriptionTransaction' => 'PhabricatorPhurlURLTransactionType', diff --git a/src/applications/phurl/application/PhabricatorPhurlApplication.php b/src/applications/phurl/application/PhabricatorPhurlApplication.php index 38d5d4459a..ebff17186b 100644 --- a/src/applications/phurl/application/PhabricatorPhurlApplication.php +++ b/src/applications/phurl/application/PhabricatorPhurlApplication.php @@ -48,8 +48,6 @@ final class PhabricatorPhurlApplication extends PhabricatorApplication { 'url/' => array( $this->getEditRoutePattern('edit/') => 'PhabricatorPhurlURLEditController', - 'comment/(?P[1-9]\d*)/' - => 'PhabricatorPhurlURLCommentController', ), ), ); diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLCommentController.php b/src/applications/phurl/controller/PhabricatorPhurlURLCommentController.php deleted file mode 100644 index 8726a38706..0000000000 --- a/src/applications/phurl/controller/PhabricatorPhurlURLCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -isFormPost()) { - return new Aphront400Response(); - } - - $viewer = $request->getViewer(); - $id = $request->getURIData('id'); - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $phurl = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$phurl) { - return new Aphront404Response(); - } - - $view_uri = '/'.$phurl->getMonogram(); - - $xactions = array(); - $xactions[] = id(new PhabricatorPhurlURLTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new PhabricatorPhurlURLTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new PhabricatorPhurlURLEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($phurl, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($view_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - -} diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php index 4e79670143..578e3ed9f8 100644 --- a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php +++ b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php @@ -30,6 +30,7 @@ final class PhabricatorPhurlURLViewController $timeline = $this->buildTransactionTimeline( $url, new PhabricatorPhurlURLTransactionQuery()); + $timeline->setQuoteRef($url->getMonogram()); $header = $this->buildHeaderView($url); $curtain = $this->buildCurtain($url); @@ -39,20 +40,7 @@ final class PhabricatorPhurlURLViewController ->setErrors(array(pht('This URL is invalid due to a bad protocol.'))) ->setIsHidden($url->isValid()); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('More Cowbell'); - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $url->getPHID()); - $comment_uri = $this->getApplicationURI( - '/url/comment/'.$url->getID().'/'); - $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($url->getPHID()) - ->setDraft($draft) - ->setHeaderText($add_comment_header) - ->setAction($comment_uri) - ->setSubmitButtonName(pht('Add Comment')); + $add_comment_form = $this->buildCommentForm($url, $timeline); $view = id(new PHUITwoColumnView()) ->setHeader($header) @@ -72,7 +60,16 @@ final class PhabricatorPhurlURLViewController array( $view, )); + } + private function buildCommentForm(PhabricatorPhurlURL $url, $timeline) { + $viewer = $this->getViewer(); + $box = id(new PhabricatorPhurlURLEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($url) + ->setTransactionTimeline($timeline); + + return $box; } private function buildHeaderView(PhabricatorPhurlURL $url) { From 2d00f5683786c76b29f82015e1c488d4807d54af Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Apr 2017 10:59:11 -0700 Subject: [PATCH 05/56] Use PHUIListItemView in ConpherenceThreadList Summary: Fixes T12556 Uses more common components in ConpherenceThreadList by moving to PHUIListItemView. Reduces clutter by moving privacy into the header. Gets ride of "See More" double interchanges. Test Plan: I need to test this more, doesn't seem to auto-select top room any more, also might build a lipsum generator. - Create lots of rooms with various policies - Test clicking on policy object - Click on different rooms - Post in rooms - Load up second account, see room numbers - Clear room message count by clicking on room Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12556 Differential Revision: https://secure.phabricator.com/D17698 --- resources/celerity/map.php | 52 ++--- .../controller/ConpherenceController.php | 14 +- .../controller/ConpherenceListController.php | 17 +- .../ConpherenceUpdateController.php | 10 +- .../controller/ConpherenceViewController.php | 6 +- .../view/ConpherenceThreadListView.php | 183 +++++------------- src/view/phui/PHUIListItemView.php | 17 ++ .../application/conpherence/header-pane.css | 38 +++- .../rsrc/css/application/conpherence/menu.css | 35 +--- .../application/conpherence/message-pane.css | 2 +- .../conpherence/participant-pane.css | 4 +- .../application/conpherence/behavior-menu.js | 18 +- 12 files changed, 169 insertions(+), 227 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fead6aa9dc..b5e2a15468 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '437d3b5a', - 'conpherence.pkg.js' => '281b1a73', + 'conpherence.pkg.css' => '1b8422e1', + 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => 'b2ad82f4', 'core.pkg.js' => 'deabcef7', 'darkconsole.pkg.js' => 'e7393ebb', @@ -46,11 +46,11 @@ return array( 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', - 'rsrc/css/application/conpherence/header-pane.css' => '4082233d', - 'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', - 'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1', + 'rsrc/css/application/conpherence/header-pane.css' => '92d50767', + 'rsrc/css/application/conpherence/menu.css' => '88100764', + 'rsrc/css/application/conpherence/message-pane.css' => '14199428', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', - 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', + 'rsrc/css/application/conpherence/participant-pane.css' => '26a3ce56', 'rsrc/css/application/conpherence/transaction.css' => '85129c68', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '16c52f5c', @@ -373,7 +373,7 @@ return array( 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'c8b5ee6f', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', - 'rsrc/js/application/conpherence/behavior-menu.js' => '7524fcfa', + 'rsrc/js/application/conpherence/behavior-menu.js' => '80dda04a', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -553,11 +553,11 @@ return array( 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', 'conpherence-durable-column-view' => '89ea6bef', - 'conpherence-header-pane-css' => '4082233d', - 'conpherence-menu-css' => '3d8e5c9c', - 'conpherence-message-pane-css' => 'd1fc13e1', + 'conpherence-header-pane-css' => '92d50767', + 'conpherence-menu-css' => '88100764', + 'conpherence-message-pane-css' => '14199428', 'conpherence-notification-css' => 'cef0a3fc', - 'conpherence-participant-pane-css' => '604a8b02', + 'conpherence-participant-pane-css' => '26a3ce56', 'conpherence-thread-manager' => 'c8b5ee6f', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', @@ -598,7 +598,7 @@ return array( 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => '9a6dd75c', 'javelin-behavior-config-reorder-fields' => 'b6993408', - 'javelin-behavior-conpherence-menu' => '7524fcfa', + 'javelin-behavior-conpherence-menu' => '80dda04a', 'javelin-behavior-conpherence-participant-pane' => '8604caa8', 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', @@ -1430,20 +1430,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '7524fcfa' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1503,6 +1489,20 @@ return array( 'javelin-vector', 'javelin-stratcom', ), + '80dda04a' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 09708b7148..b4fcc59f2a 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -54,17 +54,23 @@ abstract class ConpherenceController extends PhabricatorController { } protected function buildHeaderPaneContent( - ConpherenceThread $conpherence, - array $policy_objects) { - assert_instances_of($policy_objects, 'PhabricatorPolicy'); + ConpherenceThread $conpherence) { $viewer = $this->getViewer(); $header = null; if ($conpherence->getID()) { $data = $conpherence->getDisplayData($this->getViewer()); + $topic = id(new PHUITagView()) + ->setName($data['topic']) + ->setShade(PHUITagView::COLOR_VIOLET) + ->setType(PHUITagView::TYPE_SHADE) + ->addClass('conpherence-header-topic'); + $header = id(new PHUIHeaderView()) + ->setViewer($viewer) ->setHeader($data['title']) - ->setSubheader($data['topic']) + ->addTag($topic) + ->setPolicyObject($conpherence) ->setImage($data['image']); $can_edit = PhabricatorPolicyFilter::hasCapability( diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index 00968af576..6f50d2b37a 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -34,7 +34,7 @@ final class ConpherenceListController extends ConpherenceController { $title = pht('Conpherence'); $conpherence = null; - $limit = (ConpherenceThreadListView::SEE_MORE_LIMIT * 2) + 1; + $limit = ConpherenceThreadListView::SEE_ALL_LIMIT + 1; $all_participation = array(); $mode = $this->determineMode(); @@ -64,7 +64,7 @@ final class ConpherenceListController extends ConpherenceController { } // check to see if the loaded conpherence is going to show up - // within the SEE_MORE_LIMIT amount of conpherences. + // within the SEE_ALL_LIMIT amount of conpherences. // If its not there, then we just pre-pend it as the "first" // conpherence so folks have a navigation item in the menu. $count = 0; @@ -75,7 +75,7 @@ final class ConpherenceListController extends ConpherenceController { break; } $count++; - if ($count > ConpherenceThreadListView::SEE_MORE_LIMIT) { + if ($count > ConpherenceThreadListView::SEE_ALL_LIMIT) { break; } } @@ -89,11 +89,18 @@ final class ConpherenceListController extends ConpherenceController { default: $data = $this->loadDefaultParticipation($limit); $all_participation = $data['all_participation']; + + $conpherence_id = head($all_participation)->getConpherencePHID(); + $conpherence = id(new ConpherenceThreadQuery()) + ->setViewer($user) + ->withPHIDs(array($conpherence_id)) + ->needProfileImage(true) + ->executeOne(); + // If $conpherence is null, NUX state will render break; } - $threads = $this->loadConpherenceThreadData( - $all_participation); + $threads = $this->loadConpherenceThreadData($all_participation); $thread_view = id(new ConpherenceThreadListView()) ->setUser($user) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index ce6e64cd7f..d270043c76 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -522,18 +522,12 @@ final class ConpherenceUpdateController $people_widget = null; switch ($action) { case ConpherenceUpdateActions::METADATA: - $policy_objects = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - $header = $this->buildHeaderPaneContent( - $conpherence, - $policy_objects); + $header = $this->buildHeaderPaneContent($conpherence); $header = hsprintf('%s', $header); $nav_item = id(new ConpherenceThreadListView()) ->setUser($user) ->setBaseURI($this->getApplicationURI()) - ->renderSingleThread($conpherence, $policy_objects); + ->renderThreadItem($conpherence); $nav_item = hsprintf('%s', $nav_item); break; case ConpherenceUpdateActions::ADD_PERSON: diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 7a152a1e2e..e097aa376e 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -82,11 +82,7 @@ final class ConpherenceViewController extends $form = null; $content = array('transactions' => $messages); } else { - $policy_objects = id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($conpherence) - ->execute(); - $header = $this->buildHeaderPaneContent($conpherence, $policy_objects); + $header = $this->buildHeaderPaneContent($conpherence); $search = $this->buildSearchForm(); $form = $this->renderFormContent(); $content = array( diff --git a/src/applications/conpherence/view/ConpherenceThreadListView.php b/src/applications/conpherence/view/ConpherenceThreadListView.php index 21a4ed1030..1d2b9910ae 100644 --- a/src/applications/conpherence/view/ConpherenceThreadListView.php +++ b/src/applications/conpherence/view/ConpherenceThreadListView.php @@ -2,7 +2,7 @@ final class ConpherenceThreadListView extends AphrontView { - const SEE_MORE_LIMIT = 15; + const SEE_ALL_LIMIT = 16; private $baseURI; private $threads; @@ -25,84 +25,11 @@ final class ConpherenceThreadListView extends AphrontView { ->addClass('conpherence-menu') ->setID('conpherence-menu'); - $policy_objects = ConpherenceThread::loadViewPolicyObjects( - $this->getUser(), - $this->threads); - - $this->addRoomsToMenu($menu, $this->threads, $policy_objects); - - $menu = phutil_tag_div('phabricator-side-menu', $menu); - $menu = phutil_tag_div('phui-basic-nav', $menu); - - return $menu; - } - - public function renderSingleThread( - ConpherenceThread $thread, - array $policy_objects) { - assert_instances_of($policy_objects, 'PhabricatorPolicy'); - return $this->renderThread($thread, $policy_objects); - } - - private function renderThreadItem( - ConpherenceThread $thread, - array $policy_objects) { - return id(new PHUIListItemView()) - ->setType(PHUIListItemView::TYPE_CUSTOM) - ->setName($this->renderThread($thread, $policy_objects)); - } - - private function renderThread( - ConpherenceThread $thread, - array $policy_objects) { - - $user = $this->getUser(); - - $uri = '/'.$thread->getMonogram(); - $data = $thread->getDisplayData($user); - $icon = id(new PHUIIconView()) - ->addClass('msr') - ->setIcon($thread->getPolicyIconName($policy_objects)); - $title = phutil_tag( - 'span', - array(), - array( - $icon, - $data['title'], - )); - $subtitle = $data['subtitle']; - $unread_count = $data['unread_count']; - $epoch = $data['epoch']; - $image = $data['image']; - $dom_id = $thread->getPHID().'-nav-item'; - - return id(new ConpherenceMenuItemView()) - ->setUser($user) - ->setTitle($title) - ->setSubtitle($subtitle) - ->setHref($uri) - ->setEpoch($epoch) - ->setImageURI($image) - ->setUnreadCount($unread_count) - ->setID($thread->getPHID().'-nav-item') - ->addSigil('conpherence-menu-click') - ->setMetadata( - array( - 'title' => $data['title'], - 'id' => $dom_id, - 'threadID' => $thread->getID(), - )); - } - - private function addRoomsToMenu( - PHUIListView $menu, - array $rooms, - array $policy_objects) { - - $header = $this->renderMenuItemHeader(); + $header = $this->buildHeaderItemView(); $menu->addMenuItem($header); - if (empty($rooms)) { + // Blank State NUX + if (empty($this->threads)) { $join_item = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_LINK) ->setHref('/conpherence/search/') @@ -115,79 +42,69 @@ final class ConpherenceThreadListView extends AphrontView { ->setWorkflow(true) ->setName(pht('Create a Room')); $menu->addMenuItem($create_item); - - return $menu; } - $this->addThreadsToMenu($menu, $rooms, $policy_objects); + $rooms = $this->buildRoomItems($this->threads); + foreach ($rooms as $room) { + $menu->addMenuItem($room); + } + + $menu = phutil_tag_div('phabricator-side-menu', $menu); + $menu = phutil_tag_div('phui-basic-nav', $menu); + return $menu; } - private function addThreadsToMenu( - PHUIListView $menu, - array $threads, - array $policy_objects) { + private function renderThreadItem( + ConpherenceThread $thread) { - // If we have self::SEE_MORE_LIMIT or less, we can just render - // all the threads at once. Otherwise, we render a "See more" - // UI element, which toggles a show / hide on the remaining rooms + $user = $this->getUser(); + $data = $thread->getDisplayData($user); + $dom_id = $thread->getPHID().'-nav-item'; + + return id(new PHUIListItemView()) + ->setName($data['title']) + ->setHref('/'.$thread->getMonogram()) + ->setProfileImage($data['image']) + ->setCount($data['unread_count']) + ->setType(PHUIListItemView::TYPE_CUSTOM) + ->setID($thread->getPHID().'-nav-item') + ->addSigil('conpherence-menu-click') + ->setMetadata( + array( + 'title' => $data['title'], + 'id' => $dom_id, + 'threadID' => $thread->getID(), + )); + } + + private function buildRoomItems(array $threads) { + + $items = array(); $show_threads = $threads; - $more_threads = array(); - if (count($threads) > self::SEE_MORE_LIMIT) { - $show_threads = array_slice($threads, 0, self::SEE_MORE_LIMIT); - $more_threads = array_slice($threads, self::SEE_MORE_LIMIT); + $all_threads = false; + if (count($threads) > self::SEE_ALL_LIMIT) { + $show_threads = array_slice($threads, 0, self::SEE_ALL_LIMIT); + $all_threads = true; } foreach ($show_threads as $thread) { - $item = $this->renderThreadItem($thread, $policy_objects); - $menu->addMenuItem($item); + $items[] = $this->renderThreadItem($thread); } - if ($more_threads) { - $search_uri = '/conpherence/search/query/participant/'; - $sigil = 'more-room'; - - $more_item = id(new PHUIListItemView()) + // Send them to application search here + if ($all_threads) { + $items[] = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_LINK) - ->setHref($search_uri) - ->addSigil('conpherence-menu-see-more') - ->setMetadata(array('moreSigil' => $sigil)) - ->setName(pht('See More')); - $menu->addMenuItem($more_item); - $show_more_threads = $more_threads; - $even_more_threads = array(); - if (count($more_threads) > self::SEE_MORE_LIMIT) { - $show_more_threads = array_slice( - $more_threads, - 0, - self::SEE_MORE_LIMIT); - $even_more_threads = array_slice( - $more_threads, - self::SEE_MORE_LIMIT); - } - foreach ($show_more_threads as $thread) { - $item = $this->renderThreadItem($thread, $policy_objects) - ->addSigil($sigil) - ->addClass('hidden'); - $menu->addMenuItem($item); - } - - if ($even_more_threads) { - // kick them to application search here - $even_more_item = id(new PHUIListItemView()) - ->setType(PHUIListItemView::TYPE_LINK) - ->setHref($search_uri) - ->addSigil($sigil) - ->addClass('hidden') - ->setName(pht('See More')); - $menu->addMenuItem($even_more_item); - } + ->setHref('/conpherence/search/query/participant/') + ->setIcon('fa-external-link') + ->setName(pht('See All Joined')); } - return $menu; + return $items; } - private function renderMenuItemHeader() { + private function buildHeaderItemView() { $rooms = phutil_tag( 'a', array( diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 1a6d26d917..e8a1940737 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -33,6 +33,7 @@ final class PHUIListItemView extends AphrontTagView { private $tooltip; private $actionIcon; private $actionIconHref; + private $count; public function setOpenInNewWindow($open_in_new_window) { $this->openInNewWindow = $open_in_new_window; @@ -111,6 +112,11 @@ final class PHUIListItemView extends AphrontTagView { return $this->icon; } + public function setCount($count) { + $this->count = $count; + return $this; + } + public function setIndented($indented) { $this->indented = $indented; return $this; @@ -337,6 +343,16 @@ final class PHUIListItemView extends AphrontTagView { $action_icon); } + $count = null; + if ($this->count) { + $count = phutil_tag( + 'span', + array( + 'class' => 'phui-list-item-count', + ), + $this->count); + } + $icons = $this->getIcons(); $list_item = javelin_tag( @@ -354,6 +370,7 @@ final class PHUIListItemView extends AphrontTagView { $icons, $this->renderChildren(), $name, + $count, )); return array($list_item, $action_link); diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 11fe34e3f1..0ef29f93b3 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -2,6 +2,10 @@ * @provides conpherence-header-pane-css */ +.conpherence-header-pane { + background-color: #f9f9f9; +} + .conpherence-header-pane .phui-header-shell { padding: 8px 16px 10px; min-height: 38px; @@ -10,16 +14,23 @@ .conpherence-header-pane .phui-header-header { font-size: 16px; color: #000; + display: block; } .conpherence-header-pane .phui-header-subheader { - color: {$lightgreytext}; + padding: 0; + margin: 0; +} + +.conpherence-header-pane .conpherence-header-topic .phui-tag-core { + color: {$sh-indigotext}; padding: 0; font-size: 12px; - margin: 0; + margin: 0 8px 0 0; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; + font-style: italic; } .conpherence-header-pane .phui-header-col1 { @@ -36,6 +47,29 @@ left: 0; } +.conpherence-header-pane .policy-header-callout, +.conpherence-header-pane .phui-tag-core { + background: transparent; + font-size: 12px; + margin: 0 8px 0 0; + padding: 3px 0; + font-style: normal; +} + +.conpherence-header-pane .phui-header-subheader .policy-link { + color: {$lightbluetext}; +} + +.conpherence-header-pane .phui-header-subheader .policy-header-callout + .phui-icon-view { + font-size: 12px; + color: {$lightbluetext}; +} + +.conpherence-header-pane .phui-header-subheader .policy-link:hover { + color: {$darkbluetext}; +} + .conpherence-header-pane .phui-header-image-href { position: inherit; } diff --git a/webroot/rsrc/css/application/conpherence/menu.css b/webroot/rsrc/css/application/conpherence/menu.css index e0719cdcc8..e4f5f56036 100644 --- a/webroot/rsrc/css/application/conpherence/menu.css +++ b/webroot/rsrc/css/application/conpherence/menu.css @@ -21,19 +21,6 @@ background-color: {$page.sidenav}; } -.conpherence-menu-pane .phui-basic-nav .phabricator-side-menu - .phui-list-item-href { - padding: 4px 0 4px 8px; - } - -.conpherence-menu-pane.phabricator-side-menu .phui-list-item-view.hidden { - display: none; -} - -.phui-list-view.conpherence-menu { - margin-bottom: 20px; -} - .conpherence-menu-pane.phabricator-side-menu .room-list-href { padding: 10px 0 9px 8px; display: inline-block; @@ -117,20 +104,10 @@ padding: 4px; } -.conpherence-menu .conpherence-selected { - background: rgba({$alphablack},0.05); - border-left: 4px solid {$sky}; -} - .conpherence-menu .phui-list-item-type-link .phui-list-item-href { padding: 8px 0 8px 8px; } -.device-desktop .conpherence-menu - .conpherence-selected.conpherence-menu-item-view:hover { - background-color: rgba({$alphablack},0.07); -} - .conpherence-menu .loading { font-style: italic; } @@ -179,11 +156,19 @@ font-size: {$smallestfontsize}; } -.conpherence-menu .hide-unread-count .conpherence-menu-item-unread-count, -.conpherence-menu .conpherence-selected .conpherence-menu-item-unread-count { +.conpherence-menu .hide-unread-count .phui-list-item-count { display: none; } +.phui-basic-nav .phabricator-side-menu .conpherence-menu + .phui-list-item-icon.phuihead-small { + display: block; + float: left; + height: 20px; + width: 20px; + margin: 0 7px 0 0; +} + .conpherence-menu .conpherence-menu-item-view .conpherence-menu-item-date { position: absolute; top: 15px; diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index 4f83a19948..f6e23db9e8 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -57,7 +57,7 @@ position: fixed; left: 240px; right: 240px; - top: 103px; + top: 104px; bottom: 142px; overflow-x: hidden; overflow-y: auto; diff --git a/webroot/rsrc/css/application/conpherence/participant-pane.css b/webroot/rsrc/css/application/conpherence/participant-pane.css index c72d098226..f219707e7b 100644 --- a/webroot/rsrc/css/application/conpherence/participant-pane.css +++ b/webroot/rsrc/css/application/conpherence/participant-pane.css @@ -5,7 +5,7 @@ .conpherence-participant-pane { position: fixed; right: 0px; - top: 103px; + top: 106px; bottom: 0; width: 240px; border-width: 0 0 0 1px; @@ -45,7 +45,7 @@ position: fixed; overflow-y: auto; bottom: 0; - top: 102px; + top: 105px; width: 240px; } diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index 1621126a5e..a8151428e4 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -125,10 +125,10 @@ JX.behavior('conpherence-menu', function(config) { function selectThread(node, update_page_data) { if (_thread.node) { - JX.DOM.alterClass(_thread.node, 'conpherence-selected', false); + JX.DOM.alterClass(_thread.node, 'phui-list-item-selected', false); } - JX.DOM.alterClass(node, 'conpherence-selected', true); + JX.DOM.alterClass(node, 'phui-list-item-selected', true); JX.DOM.alterClass(node, 'hide-unread-count', true); _thread.node = node; @@ -425,20 +425,6 @@ JX.behavior('conpherence-menu', function(config) { } } - JX.Stratcom.listen( - ['click'], - 'conpherence-menu-see-more', - function (e) { - e.kill(); - var sigil = e.getNodeData('conpherence-menu-see-more').moreSigil; - var root = JX.$('conpherence-menu-pane'); - var more = JX.DOM.scry(root, 'li', sigil); - for (var i = 0; i < more.length; i++) { - JX.DOM.alterClass(more[i], 'hidden', false); - } - JX.DOM.hide(e.getNode('conpherence-menu-see-more')); - }); - JX.Stratcom.listen( ['keydown'], 'conpherence-pontificate', From 6052bc1933442122e946d6528de87488c752bddb Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 12:46:09 -0700 Subject: [PATCH 06/56] Extend "fulltext" and "ngrams" interfaces from "indexable" interface Summary: Ref T8788. See D17702. This allows `bin/search index` to index stuff which only implements `Ngrams`, not `Fulltext`. Test Plan: Kinda poked around `bin/search index` a bit, yell if you hit more issues deeper down the stack? Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T8788 Differential Revision: https://secure.phabricator.com/D17704 --- src/__phutil_library_map__.php | 3 +++ .../search/interface/PhabricatorFulltextInterface.php | 3 ++- .../search/interface/PhabricatorIndexableInterface.php | 3 +++ .../search/interface/PhabricatorNgramsInterface.php | 3 ++- .../management/PhabricatorSearchManagementIndexWorkflow.php | 2 +- 5 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 src/applications/search/interface/PhabricatorIndexableInterface.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5bc5f78d96..5d6bd37460 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2924,6 +2924,7 @@ phutil_register_library_map(array( 'PhabricatorIndexEngine' => 'applications/search/index/PhabricatorIndexEngine.php', 'PhabricatorIndexEngineExtension' => 'applications/search/index/PhabricatorIndexEngineExtension.php', 'PhabricatorIndexEngineExtensionModule' => 'applications/search/index/PhabricatorIndexEngineExtensionModule.php', + 'PhabricatorIndexableInterface' => 'applications/search/interface/PhabricatorIndexableInterface.php', 'PhabricatorInfrastructureTestCase' => '__tests__/PhabricatorInfrastructureTestCase.php', 'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php', 'PhabricatorInlineCommentInterface' => 'infrastructure/diff/interface/PhabricatorInlineCommentInterface.php', @@ -8025,6 +8026,7 @@ phutil_register_library_map(array( 'PhabricatorFulltextEngineExtension' => 'Phobject', 'PhabricatorFulltextEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorFulltextIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'PhabricatorFulltextInterface' => 'PhabricatorIndexableInterface', 'PhabricatorFulltextResultSet' => 'Phobject', 'PhabricatorFulltextStorageEngine' => 'Phobject', 'PhabricatorFulltextToken' => 'Phobject', @@ -8299,6 +8301,7 @@ phutil_register_library_map(array( 'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorNgramsIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'PhabricatorNgramsInterface' => 'PhabricatorIndexableInterface', 'PhabricatorNotificationBuilder' => 'Phobject', 'PhabricatorNotificationClearController' => 'PhabricatorNotificationController', 'PhabricatorNotificationClient' => 'Phobject', diff --git a/src/applications/search/interface/PhabricatorFulltextInterface.php b/src/applications/search/interface/PhabricatorFulltextInterface.php index 5cbde615b2..e8f4cca29f 100644 --- a/src/applications/search/interface/PhabricatorFulltextInterface.php +++ b/src/applications/search/interface/PhabricatorFulltextInterface.php @@ -1,6 +1,7 @@ setAncestorClass('PhabricatorFulltextInterface') + ->setAncestorClass('PhabricatorIndexableInterface') ->execute(); $normalized_type = phutil_utf8_strtolower($type); From ecbeec35b263711af721edfbdce7c90afd8241db Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Apr 2017 14:34:00 -0700 Subject: [PATCH 07/56] Tweak some icon buttons Summary: Fixes T12577. Some tweaks last week widened the default buttons, but these didn't get retouched. Test Plan: Review calendar on desktop and mobile. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12577 Differential Revision: https://secure.phabricator.com/D17709 --- resources/celerity/map.php | 10 +++++----- webroot/rsrc/css/phui/phui-button.css | 2 +- webroot/rsrc/css/phui/phui-header-view.css | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b5e2a15468..9bd0144719 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '1b8422e1', 'conpherence.pkg.js' => '5f86c17d', - 'core.pkg.css' => 'b2ad82f4', + 'core.pkg.css' => '959330a2', 'core.pkg.js' => 'deabcef7', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', @@ -133,7 +133,7 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '96787bae', + 'rsrc/css/phui/phui-button.css' => '5a5ae137', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -148,7 +148,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '6175808d', 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => '9cf828ce', + 'rsrc/css/phui/phui-header-view.css' => 'e082678d', 'rsrc/css/phui/phui-hovercard.css' => 'ae091fc5', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '12b387a1', @@ -829,7 +829,7 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '96787bae', + 'phui-button-css' => '5a5ae137', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -849,7 +849,7 @@ return array( 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => '9cf828ce', + 'phui-header-view-css' => 'e082678d', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'ae091fc5', 'phui-icon-set-selector-css' => '87db8fee', diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index f726487a2d..936a43f4bc 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -314,7 +314,7 @@ a.policy-control .phui-button-text { } .phui-button-bar .button .phui-icon-view { - left: 9px; + left: 14px; } .button.has-icon .phui-button-text { diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 14e0af986f..5c24d5575e 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -120,7 +120,7 @@ body .phui-header-shell.phui-bleed-header .device-phone .phui-header-action-link .phui-button-text { visibility: hidden; width: 0; - margin-left: 8px; + margin-left: 4px; } .device-phone .phui-header-action-link.button .phui-icon-view { From 2a5dae4fcb916ec7e62b2afe18faaf42bf69cfeb Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Apr 2017 15:03:07 -0700 Subject: [PATCH 08/56] Minor Topic CSS tweaks Summary: Cleans up the topic UI a little more, I think this feels nice for some reason. Test Plan: visit a room with and without a topic, desktop, tablet, mobile Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17711 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/application/conpherence/header-pane.css | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9bd0144719..4b6e56f118 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '1b8422e1', + 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', 'core.pkg.js' => 'deabcef7', @@ -46,7 +46,7 @@ return array( 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', - 'rsrc/css/application/conpherence/header-pane.css' => '92d50767', + 'rsrc/css/application/conpherence/header-pane.css' => '6b2dadbe', 'rsrc/css/application/conpherence/menu.css' => '88100764', 'rsrc/css/application/conpherence/message-pane.css' => '14199428', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', @@ -553,7 +553,7 @@ return array( 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', 'conpherence-durable-column-view' => '89ea6bef', - 'conpherence-header-pane-css' => '92d50767', + 'conpherence-header-pane-css' => '6b2dadbe', 'conpherence-menu-css' => '88100764', 'conpherence-message-pane-css' => '14199428', 'conpherence-notification-css' => 'cef0a3fc', diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 0ef29f93b3..56ec7ce130 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -24,7 +24,7 @@ .conpherence-header-pane .conpherence-header-topic .phui-tag-core { color: {$sh-indigotext}; - padding: 0; + padding: 0 4px; font-size: 12px; margin: 0 8px 0 0; text-overflow: ellipsis; @@ -33,6 +33,10 @@ font-style: italic; } +.device-phone .conpherence-header-pane .conpherence-header-topic { + display: none; +} + .conpherence-header-pane .phui-header-col1 { width: 46px; height: 35px; @@ -47,8 +51,7 @@ left: 0; } -.conpherence-header-pane .policy-header-callout, -.conpherence-header-pane .phui-tag-core { +.conpherence-header-pane .policy-header-callout { background: transparent; font-size: 12px; margin: 0 8px 0 0; From 3f45defd345163e43d8afb433ffa3590c047c191 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Apr 2017 22:11:08 +0000 Subject: [PATCH 09/56] Clean up Remarkup Preview on mobile Summary: Some space is bleeding in here from two-column-css. Re-scope CSS. Test Plan: Review creating a task on mobile with document preview present. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17712 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/phui/phui-remarkup-preview.css | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4b6e56f118..92106cd1cf 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -162,7 +162,7 @@ return array( 'rsrc/css/phui/phui-pager.css' => '77d8a794', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-property-list-view.css' => '2dc7993f', - 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', + 'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863', 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', @@ -871,7 +871,7 @@ return array( 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', 'phui-property-list-view-css' => '2dc7993f', - 'phui-remarkup-preview-css' => '1a8f2591', + 'phui-remarkup-preview-css' => '54a34863', 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', diff --git a/webroot/rsrc/css/phui/phui-remarkup-preview.css b/webroot/rsrc/css/phui/phui-remarkup-preview.css index c045cae703..19464a98f7 100644 --- a/webroot/rsrc/css/phui/phui-remarkup-preview.css +++ b/webroot/rsrc/css/phui/phui-remarkup-preview.css @@ -27,7 +27,8 @@ padding: 16px 0 0 16px; } -.device-phone .phui-panel-preview { +.device-phone .phui-panel-preview, +.device-phone .phui-remarkup-preview { display: none; } From f394fefe6f4b64d9063bc169c0d507a937cce163 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 10:27:26 -0700 Subject: [PATCH 10/56] Add a very basic "Realtime" log to DarkConsole Summary: Ref T12568. This begins building toward a more useful realtime debugging console for Leader/Aphlict/general realtime stuff. Test Plan: {F4911521} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12568 Differential Revision: https://secure.phabricator.com/D17701 --- src/__phutil_library_map__.php | 2 + .../plugin/DarkConsoleRealtimePlugin.php | 26 +++++++ webroot/rsrc/css/aphront/dark-console.css | 14 ++++ .../aphlict/behavior-aphlict-listen.js | 13 +++- webroot/rsrc/js/core/darkconsole/DarkLog.js | 47 ++++++++++++ .../rsrc/js/core/darkconsole/DarkMessage.js | 37 ++++++++++ .../behavior-dark-console.js | 74 +++++++++++++++++++ 7 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 src/applications/console/plugin/DarkConsoleRealtimePlugin.php create mode 100644 webroot/rsrc/js/core/darkconsole/DarkLog.js create mode 100644 webroot/rsrc/js/core/darkconsole/DarkMessage.js rename webroot/rsrc/js/core/{ => darkconsole}/behavior-dark-console.js (81%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5d6bd37460..efb5b65ffe 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -349,6 +349,7 @@ phutil_register_library_map(array( 'DarkConsoleEventPlugin' => 'applications/console/plugin/DarkConsoleEventPlugin.php', 'DarkConsoleEventPluginAPI' => 'applications/console/plugin/event/DarkConsoleEventPluginAPI.php', 'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php', + 'DarkConsoleRealtimePlugin' => 'applications/console/plugin/DarkConsoleRealtimePlugin.php', 'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php', 'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php', 'DarkConsoleStartupPlugin' => 'applications/console/plugin/DarkConsoleStartupPlugin.php', @@ -5144,6 +5145,7 @@ phutil_register_library_map(array( 'DarkConsoleEventPlugin' => 'DarkConsolePlugin', 'DarkConsoleEventPluginAPI' => 'PhabricatorEventListener', 'DarkConsolePlugin' => 'Phobject', + 'DarkConsoleRealtimePlugin' => 'DarkConsolePlugin', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleStartupPlugin' => 'DarkConsolePlugin', diff --git a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php new file mode 100644 index 0000000000..517e6ab5db --- /dev/null +++ b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php @@ -0,0 +1,26 @@ + 'dark-console-realtime-log', + 'class' => 'dark-console-log-frame', + )); + } + +} diff --git a/webroot/rsrc/css/aphront/dark-console.css b/webroot/rsrc/css/aphront/dark-console.css index 40dabd7bf6..7a9b005518 100644 --- a/webroot/rsrc/css/aphront/dark-console.css +++ b/webroot/rsrc/css/aphront/dark-console.css @@ -217,3 +217,17 @@ .dark-console-panel-error-details { display: none; } + +.dark-console-log-frame { + height: 500px; + overflow: auto; + background: #303030; + border: 1px solid #202020; + margin: 4px; +} + +.dark-console-log-message { + background: #404040; + padding: 8px; + margin: 2px; +} diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js index 2e381d018c..f280df93c2 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -114,9 +114,16 @@ JX.behavior('aphlict-listen', function(config) { config.websocketURI, config.subscriptions); - client - .setHandler(onAphlictMessage) - .start(); + var start_client = function() { + client + .setHandler(onAphlictMessage) + .start(); + }; + + // Don't start the client until other behaviors have had a chance to + // initialize. In particular, we want to capture events into the log for + // the DarkConsole "Realtime" panel. + setTimeout(start_client, 0); JX.Stratcom.listen( 'quicksand-redraw', diff --git a/webroot/rsrc/js/core/darkconsole/DarkLog.js b/webroot/rsrc/js/core/darkconsole/DarkLog.js new file mode 100644 index 0000000000..d22f433e3e --- /dev/null +++ b/webroot/rsrc/js/core/darkconsole/DarkLog.js @@ -0,0 +1,47 @@ +/** + * @provides phabricator-darklog + * @javelin + */ + +JX.install('DarkLog', { + + construct: function() { + this._messages = []; + }, + + members: { + _node: null, + _messages: null, + + addMessage: function(message) { + var node = message.getNode(); + + this._messages.push(message); + if (this._node) { + this._append([node]); + } + + return this; + }, + + setNode: function(node) { + var nodes = []; + for (var ii = 0; ii < this._messages.length; ii++) { + nodes.push(this._messages[ii].getNode()); + } + + this._node = node; + this._append(nodes); + + return this; + }, + + _append: function(nodes) { + for (var ii = 0; ii < nodes.length; ii++) { + this._node.appendChild(nodes[ii]); + } + } + + } + +}); diff --git a/webroot/rsrc/js/core/darkconsole/DarkMessage.js b/webroot/rsrc/js/core/darkconsole/DarkMessage.js new file mode 100644 index 0000000000..2fd7697706 --- /dev/null +++ b/webroot/rsrc/js/core/darkconsole/DarkMessage.js @@ -0,0 +1,37 @@ +/** + * @provides phabricator-darkmessage + * @javelin + */ + +JX.install('DarkMessage', { + + construct: function() { + + }, + + members: { + _node: null, + _message: null, + + setMessage: function(message) { + this._message = message; + + JX.DOM.setContent(this.getNode(), message); + + return this; + }, + + getNode: function() { + if (!this._node) { + this._node = JX.$N( + 'div', + { + className: 'dark-console-log-message' + }); + } + + return this._node; + } + } + +}); diff --git a/webroot/rsrc/js/core/behavior-dark-console.js b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js similarity index 81% rename from webroot/rsrc/js/core/behavior-dark-console.js rename to webroot/rsrc/js/core/darkconsole/behavior-dark-console.js index 3322d31227..7ee7ecbcca 100644 --- a/webroot/rsrc/js/core/behavior-dark-console.js +++ b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js @@ -6,6 +6,8 @@ * javelin-dom * javelin-request * phabricator-keyboard-shortcut + * phabricator-darklog + * phabricator-darkmessage */ JX.behavior('dark-console', function(config, statics) { @@ -246,6 +248,12 @@ JX.behavior('dark-console', function(config, statics) { var div = JX.$N('div', {className: 'dark-console-panel-core'}, JX.$H(html)); JX.DOM.setContent(statics.el.panel, div); + + var params = { + panel: tclass + }; + + JX.Stratcom.invoke('darkconsole.draw', null, params); } function install_shortcut() { @@ -287,4 +295,70 @@ JX.behavior('dark-console', function(config, statics) { } add_request(config); + +/* -( Realtime Panel )----------------------------------------------------- */ + + + if (!statics.realtime) { + statics.realtime = true; + + var realtime_log = new JX.DarkLog(); + var realtime_id = 'dark-console-realtime-log'; + + JX.Stratcom.listen('darkconsole.draw', null, function(e) { + var data = e.getData(); + if (data.panel != 'DarkConsoleRealtimePlugin') { + return; + } + + var node = JX.$(realtime_id); + realtime_log.setNode(node); + }); + + // If the panel is initially visible, try rendering. + try { + var node = JX.$(realtime_id); + realtime_log.setNode(node); + } catch (exception) { + // Ignore. + } + + var leader_log = function(event_name, type, is_leader, details) { + var parts = []; + if (is_leader === true) { + parts.push('+'); + } else if (is_leader === false) { + parts.push('-'); + } else { + parts.push('~'); + } + + parts.push('[Leader/' + event_name + ']'); + + if (type) { + parts.push('(' + type + ')'); + } + + if (details) { + parts.push(details); + } + + parts = parts.join(' '); + + var message = new JX.DarkMessage() + .setMessage(parts); + + realtime_log.addMessage(message); + }; + + JX.Leader.listen('onReceiveBroadcast', function(message, is_leader) { + var json = JX.JSON.stringify(message.data); + leader_log('onReceiveBroadcast', message.type, is_leader, json); + }); + + JX.Leader.listen('onBecomeLeader', function() { + leader_log('onBecomeLeader'); + }); + } + }); From 28c68eb4fd68f42affbf4db2c94bc0e27be04ea5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 12:10:29 -0700 Subject: [PATCH 11/56] Decrease JX.Leader lease duration from 16,000ms to 1,500ms and usurp more aggressively Summary: Ref T12573. `JX.Leader` synchronizes the Aphlict connection across multiple windows. Currently, we only test to see if the leader window has been closed every 16 seconds. Instead, test every 1.5 seconds. Also, make windows keep trying to become the leader forever. This was removed previously (in D15806) but I think that change decreased robustness here. Test Plan: - Opened two windows to the "Realtime" tab in DarkConsole. - Saw one become the leader and one become a follower. - (Optionally, wait for 10 seconds here to test the "keep trying to become the leader" behavior.) - Closed the leader. - Saw the follower become the leader after ~1.5 seconds. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12573 Differential Revision: https://secure.phabricator.com/D17703 --- resources/celerity/map.php | 76 +++++++++++--------- webroot/rsrc/externals/javelin/lib/Leader.js | 12 ++-- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 92106cd1cf..7bfd9cb40c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,8 +10,8 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => 'deabcef7', - 'darkconsole.pkg.js' => 'e7393ebb', + 'core.pkg.js' => '941db947', + 'darkconsole.pkg.js' => '061371d8', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', 'diffusion.pkg.css' => '91c5d3a6', @@ -20,7 +20,7 @@ return array( 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', - 'rsrc/css/aphront/dark-console.css' => 'f54bf286', + 'rsrc/css/aphront/dark-console.css' => '07dd8d38', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', @@ -234,7 +234,7 @@ return array( 'rsrc/externals/javelin/lib/DOM.js' => '805b806a', 'rsrc/externals/javelin/lib/History.js' => 'd4505101', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', - 'rsrc/externals/javelin/lib/Leader.js' => 'fea0eb47', + 'rsrc/externals/javelin/lib/Leader.js' => '7f243deb', 'rsrc/externals/javelin/lib/Mask.js' => '8a41885b', 'rsrc/externals/javelin/lib/Quicksand.js' => '6b8ef10b', 'rsrc/externals/javelin/lib/Request.js' => '94b750d2', @@ -363,7 +363,7 @@ return array( 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'd82b1ff9', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'd5a2d665', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', @@ -482,7 +482,6 @@ return array( 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', - 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', @@ -521,6 +520,9 @@ return array( 'rsrc/js/core/behavior-user-menu.js' => '31420f77', 'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d', 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', + 'rsrc/js/core/darkconsole/DarkLog.js' => 'c8e1ffe3', + 'rsrc/js/core/darkconsole/DarkMessage.js' => 'c48cccdd', + 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '3725c90c', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', @@ -536,7 +538,7 @@ return array( 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', - 'aphront-dark-console-css' => 'f54bf286', + 'aphront-dark-console-css' => '07dd8d38', 'aphront-dialog-view-css' => '685c7e2d', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', @@ -584,7 +586,7 @@ return array( 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', - 'javelin-behavior-aphlict-listen' => 'fb20ac8d', + 'javelin-behavior-aphlict-listen' => 'd82b1ff9', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -603,7 +605,7 @@ return array( 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', - 'javelin-behavior-dark-console' => 'f411b6ae', + 'javelin-behavior-dark-console' => '3725c90c', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '408bf173', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', @@ -720,7 +722,7 @@ return array( 'javelin-history' => 'd4505101', 'javelin-install' => '05270951', 'javelin-json' => '69adf288', - 'javelin-leader' => 'fea0eb47', + 'javelin-leader' => '7f243deb', 'javelin-magical-init' => '3010e992', 'javelin-mask' => '8a41885b', 'javelin-quicksand' => '6b8ef10b', @@ -774,6 +776,8 @@ return array( 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => '9f4cb463', 'phabricator-countdown-css' => '16c52f5c', + 'phabricator-darklog' => 'c8e1ffe3', + 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1118,6 +1122,16 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '3725c90c' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-util', + 'javelin-dom', + 'javelin-request', + 'phabricator-keyboard-shortcut', + 'phabricator-darklog', + 'phabricator-darkmessage', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1475,6 +1489,9 @@ return array( 'javelin-behavior', 'javelin-history', ), + '7f243deb' => array( + 'javelin-install', + ), '8018ee50' => array( 'javelin-install', 'javelin-util', @@ -2066,6 +2083,20 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'd82b1ff9' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'javelin-stratcom', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', + 'javelin-router', + 'javelin-util', + 'javelin-leader', + 'javelin-sound', + 'phabricator-notification', + ), 'd835b03a' => array( 'javelin-behavior', 'javelin-dom', @@ -2173,14 +2204,6 @@ return array( 'f12cbc9f' => array( 'phui-oi-list-view-css', ), - 'f411b6ae' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-util', - 'javelin-dom', - 'javelin-request', - 'phabricator-keyboard-shortcut', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), @@ -2203,20 +2226,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'fb20ac8d' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'javelin-leader', - 'javelin-sound', - 'phabricator-notification', - ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', @@ -2242,9 +2251,6 @@ return array( 'javelin-view-visitor', 'javelin-util', ), - 'fea0eb47' => array( - 'javelin-install', - ), ), 'packages' => array( 'conpherence.pkg.css' => array( diff --git a/webroot/rsrc/externals/javelin/lib/Leader.js b/webroot/rsrc/externals/javelin/lib/Leader.js index 80ba7fcc44..c96e95a339 100644 --- a/webroot/rsrc/externals/javelin/lib/Leader.js +++ b/webroot/rsrc/externals/javelin/lib/Leader.js @@ -33,6 +33,8 @@ JX.install('Leader', { events: ['onBecomeLeader', 'onReceiveBroadcast'], statics: { + _leaseDuration: 1500, + _interval: null, _timeout: null, _broadcastKey: 'JX.Leader.broadcast', @@ -130,8 +132,10 @@ JX.install('Leader', { // If we haven't installed an update timer yet, do so now. This will // renew our lease every 5 seconds, making sure we hold it until the // tab is closed. - if (!self._interval && lease.until > now + 10000) { - self._interval = window.setInterval(self._write, 5000); + var interval = parseInt(self._leaseDuration / 3, 10); + + if (!self._interval && lease.until > now + (interval * 2)) { + self._interval = window.setInterval(self._write, interval); } self._becomeLeader(); @@ -227,7 +231,7 @@ JX.install('Leader', { _write: function() { var self = JX.Leader; - var str = [self._id, ((+new Date()) + 16000)].join(':'); + var str = [self._id, ((+new Date()) + self._leaseDuration)].join(':'); window.localStorage.setItem(self._leaderKey, str); }, @@ -311,8 +315,8 @@ JX.install('Leader', { */ _usurp: function() { var self = JX.Leader; - self.call(JX.bag); self._timeout = null; + self.call(JX.bag); }, From 12120478433ce1e6ced9c72240939d9f19c24eeb Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 12:59:16 -0700 Subject: [PATCH 12/56] Add a "Reconnect" debugging action and show reconnect delays in the console Summary: Ref T12568. Ref T12567. Allows you to force a reconnect, and shows the reconnect delay on connection close/failure. Test Plan: {F4911879} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12568, T12567 Differential Revision: https://secure.phabricator.com/D17705 --- resources/celerity/map.php | 60 +++++++++---------- .../plugin/DarkConsoleRealtimePlugin.php | 31 +++++++++- webroot/rsrc/css/aphront/dark-console.css | 2 +- .../rsrc/externals/javelin/lib/WebSocket.js | 20 +++++++ .../rsrc/js/application/aphlict/Aphlict.js | 4 ++ .../core/darkconsole/behavior-dark-console.js | 37 ++++++++++++ 6 files changed, 122 insertions(+), 32 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 7bfd9cb40c..1e6b173496 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,8 +10,8 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => '941db947', - 'darkconsole.pkg.js' => '061371d8', + 'core.pkg.js' => 'd7ca5b9a', + 'darkconsole.pkg.js' => 'a2faee86', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', 'diffusion.pkg.css' => '91c5d3a6', @@ -20,7 +20,7 @@ return array( 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', - 'rsrc/css/aphront/dark-console.css' => '07dd8d38', + 'rsrc/css/aphront/dark-console.css' => 'e7c6e44d', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', @@ -245,7 +245,7 @@ return array( 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', - 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', + 'rsrc/externals/javelin/lib/WebSocket.js' => '0c4969d6', 'rsrc/externals/javelin/lib/Workflow.js' => '1e911d0f', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', @@ -361,7 +361,7 @@ return array( 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', + 'rsrc/js/application/aphlict/Aphlict.js' => 'ce5f793f', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'd82b1ff9', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', @@ -522,7 +522,7 @@ return array( 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/darkconsole/DarkLog.js' => 'c8e1ffe3', 'rsrc/js/core/darkconsole/DarkMessage.js' => 'c48cccdd', - 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '3725c90c', + 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '698614f9', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', @@ -538,7 +538,7 @@ return array( 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', - 'aphront-dark-console-css' => '07dd8d38', + 'aphront-dark-console-css' => 'e7c6e44d', 'aphront-dialog-view-css' => '685c7e2d', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', @@ -583,7 +583,7 @@ return array( 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => '5359e785', + 'javelin-aphlict' => 'ce5f793f', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', 'javelin-behavior-aphlict-listen' => 'd82b1ff9', @@ -605,7 +605,7 @@ return array( 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', - 'javelin-behavior-dark-console' => '3725c90c', + 'javelin-behavior-dark-console' => '698614f9', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '408bf173', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', @@ -753,7 +753,7 @@ return array( 'javelin-view-interpreter' => 'f829edb3', 'javelin-view-renderer' => '6c2b09a2', 'javelin-view-visitor' => 'efe49472', - 'javelin-websocket' => 'e292eaf4', + 'javelin-websocket' => '0c4969d6', 'javelin-workboard-board' => '8935deef', 'javelin-workboard-card' => 'c587b80f', 'javelin-workboard-column' => '21df4ff5', @@ -981,6 +981,9 @@ return array( 'javelin-dom', 'javelin-router', ), + '0c4969d6' => array( + 'javelin-install', + ), '0ca788bd' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1122,16 +1125,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - '3725c90c' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-util', - 'javelin-dom', - 'javelin-request', - 'phabricator-keyboard-shortcut', - 'phabricator-darklog', - 'phabricator-darkmessage', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1294,13 +1287,6 @@ return array( '5294060f' => array( 'phui-theme-css', ), - '5359e785' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1395,6 +1381,16 @@ return array( '6882e80a' => array( 'javelin-dom', ), + '698614f9' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-util', + 'javelin-dom', + 'javelin-request', + 'phabricator-keyboard-shortcut', + 'phabricator-darklog', + 'phabricator-darkmessage', + ), '69adf288' => array( 'javelin-install', ), @@ -2018,6 +2014,13 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), + 'ce5f793f' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', @@ -2128,9 +2131,6 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - 'e292eaf4' => array( - 'javelin-install', - ), 'e2e0a072' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php index 517e6ab5db..e73dfa10c4 100644 --- a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php +++ b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php @@ -15,12 +15,41 @@ final class DarkConsoleRealtimePlugin extends DarkConsolePlugin { } public function renderPanel() { - return phutil_tag( + $frame = phutil_tag( 'div', array( 'id' => 'dark-console-realtime-log', 'class' => 'dark-console-log-frame', )); + + $reconnect_label = pht('Reconnect'); + + $buttons = phutil_tag( + 'div', + array( + 'class' => 'dark-console-realtime-actions', + ), + array( + id(new PHUIButtonView()) + ->setIcon('fa-refresh') + ->setColor(PHUIButtonView::GREY) + ->setText($reconnect_label) + ->addSigil('dark-console-realtime-action') + ->setMetadata( + array( + 'action' => 'reconnect', + 'label' => $reconnect_label, + )), + )); + + return phutil_tag( + 'div', + array( + ), + array( + $buttons, + $frame, + )); } } diff --git a/webroot/rsrc/css/aphront/dark-console.css b/webroot/rsrc/css/aphront/dark-console.css index 7a9b005518..403237ebe7 100644 --- a/webroot/rsrc/css/aphront/dark-console.css +++ b/webroot/rsrc/css/aphront/dark-console.css @@ -223,7 +223,7 @@ overflow: auto; background: #303030; border: 1px solid #202020; - margin: 4px; + margin: 8px 0; } .dark-console-log-message { diff --git a/webroot/rsrc/externals/javelin/lib/WebSocket.js b/webroot/rsrc/externals/javelin/lib/WebSocket.js index fbb704d762..5ed35d74e7 100644 --- a/webroot/rsrc/externals/javelin/lib/WebSocket.js +++ b/webroot/rsrc/externals/javelin/lib/WebSocket.js @@ -104,6 +104,26 @@ JX.install('WebSocket', { }, + /** + * Disconnect abruptly, prompting a reconnect. + */ + reconnect: function() { + if (!this._isOpen) { + return; + } + + this._socket.close(); + }, + + + /** + * Get the current reconnect delay (in milliseconds). + */ + getReconnectDelay: function() { + return this._delayUntilReconnect; + }, + + /** * Callback for connection open. */ diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js index 78d0958c81..c9d75ce1d3 100644 --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -71,6 +71,10 @@ JX.install('Aphlict', { return this._status; }, + getWebsocket: function() { + return this._socket; + }, + _begin: function() { JX.Leader.broadcast( null, diff --git a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js index 7ee7ecbcca..a535469b5e 100644 --- a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js +++ b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js @@ -353,12 +353,49 @@ JX.behavior('dark-console', function(config, statics) { JX.Leader.listen('onReceiveBroadcast', function(message, is_leader) { var json = JX.JSON.stringify(message.data); + + if (message.type == 'aphlict.status') { + if (message.data == 'closed') { + var ws = JX.Aphlict.getInstance().getWebsocket(); + if (ws) { + var delay = ws.getReconnectDelay(); + json += ' [Reconnect: ' + delay + 'ms]'; + } + } + } + leader_log('onReceiveBroadcast', message.type, is_leader, json); }); JX.Leader.listen('onBecomeLeader', function() { leader_log('onBecomeLeader'); }); + + var action_log = function(action) { + var message = new JX.DarkMessage() + .setMessage('> ' + action); + + realtime_log.addMessage(message); + }; + + JX.Stratcom.listen('click', 'dark-console-realtime-action', function(e) { + var node = e.getNode('dark-console-realtime-action'); + var data = JX.Stratcom.getData(node); + + action_log(data.label); + + var action = data.action; + switch (action) { + case 'reconnect': + var ws = JX.Aphlict.getInstance().getWebsocket(); + if (ws) { + ws.reconnect(); + } + break; + } + + }); + } }); From 8fdc1bff5f0694911ba5496a58e6081daf6e8639 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 13:20:48 -0700 Subject: [PATCH 13/56] When disconnected from Aphlict after a successful connection, retry the first reconnect right away Summary: Fixes T12567. We currently retry after 2s, 4s, 8s, 16s, ... If we connected cleanly once, retry the first time right away. There are a bunch of reasonable cases where this will work fine and we don't need to wait. Then we fall back: 0s, 2s, 4s, 8s, ... Test Plan: {F4911905} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12567 Differential Revision: https://secure.phabricator.com/D17706 --- resources/celerity/map.php | 12 ++++++------ webroot/rsrc/externals/javelin/lib/WebSocket.js | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1e6b173496..848b577652 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => 'd7ca5b9a', + 'core.pkg.js' => 'cb50c410', 'darkconsole.pkg.js' => 'a2faee86', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -245,7 +245,7 @@ return array( 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', - 'rsrc/externals/javelin/lib/WebSocket.js' => '0c4969d6', + 'rsrc/externals/javelin/lib/WebSocket.js' => '3ffe32d6', 'rsrc/externals/javelin/lib/Workflow.js' => '1e911d0f', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', @@ -753,7 +753,7 @@ return array( 'javelin-view-interpreter' => 'f829edb3', 'javelin-view-renderer' => '6c2b09a2', 'javelin-view-visitor' => 'efe49472', - 'javelin-websocket' => '0c4969d6', + 'javelin-websocket' => '3ffe32d6', 'javelin-workboard-board' => '8935deef', 'javelin-workboard-card' => 'c587b80f', 'javelin-workboard-column' => '21df4ff5', @@ -981,9 +981,6 @@ return array( 'javelin-dom', 'javelin-router', ), - '0c4969d6' => array( - 'javelin-install', - ), '0ca788bd' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1148,6 +1145,9 @@ return array( 'javelin-workflow', 'javelin-stratcom', ), + '3ffe32d6' => array( + 'javelin-install', + ), '408bf173' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/externals/javelin/lib/WebSocket.js b/webroot/rsrc/externals/javelin/lib/WebSocket.js index 5ed35d74e7..508218cf3f 100644 --- a/webroot/rsrc/externals/javelin/lib/WebSocket.js +++ b/webroot/rsrc/externals/javelin/lib/WebSocket.js @@ -130,8 +130,14 @@ JX.install('WebSocket', { _onopen: function() { this._isOpen = true; - // Reset the reconnect delay, since we connected successfully. - this._resetDelay(); + // Since we connected successfully, reset the reconnect delay to 0. + + // This will make us try the first reconnect immediately after a + // connection failure. This limits downtime in cases like a service + // restart or a load balancer connection timeout. + + // We only do an immediate retry after a successful connection. + this._delayUntilReconnect = 0; var handler = this.getOpenHandler(); if (handler) { @@ -190,7 +196,11 @@ JX.install('WebSocket', { // Increase the reconnect delay by a factor of 2. If we fail to open the // connection, the close handler will send us back here. We'll reconnect // more and more slowly until we eventually get a valid connection. - this._delayUntilReconnect = this._delayUntilReconnect * 2; + if (!this._delayUntilReconnect) { + this._resetDelay(); + } else { + this._delayUntilReconnect = this._delayUntilReconnect * 2; + } // Max out at 5 minutes between attempts. this._delayUntilReconnect = Math.min(this._delayUntilReconnect, 300000); From 88157a94422c47c53c9e6476affdb222c94da605 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 13:46:35 -0700 Subject: [PATCH 14/56] Hold recent messages in Aphlict so they can be replayed after clients reconnect Summary: Ref T12563. Before broadcasting messages from the server, store them in a history buffer. A future change will let clients retrieve them. Test Plan: - Used the web frontend to look at the buffer, reloaded over time, sent messages. Saw buffer size go up as I sent messages and fall after 60 seconds. - Set size to 4 messages, sent a bunch of messages, saw the buffer size max out at 4 messages. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12563 Differential Revision: https://secure.phabricator.com/D17707 --- ...orConfigClusterNotificationsController.php | 13 ++++ .../aphlict/server/lib/AphlictAdminServer.js | 69 ++++++++++++++++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php index 47ab022831..cc0cc2bf45 100644 --- a/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php +++ b/src/applications/config/controller/PhabricatorConfigClusterNotificationsController.php @@ -103,10 +103,20 @@ final class PhabricatorConfigClusterNotificationsController new PhutilNumber(idx($details, 'messages.in')), new PhutilNumber(idx($details, 'messages.out'))); + if (idx($details, 'history.size')) { + $history = pht( + '%s Held / %sms', + new PhutilNumber(idx($details, 'history.size')), + new PhutilNumber(idx($details, 'history.age'))); + } else { + $history = pht('No Messages'); + } + } else { $uptime = null; $clients = null; $stats = null; + $history = null; } $status_view = array( @@ -126,6 +136,7 @@ final class PhabricatorConfigClusterNotificationsController $uptime, $clients, $stats, + $history, $messages, ); } @@ -143,6 +154,7 @@ final class PhabricatorConfigClusterNotificationsController pht('Uptime'), pht('Clients'), pht('Messages'), + pht('History'), null, )) ->setColumnClasses( @@ -155,6 +167,7 @@ final class PhabricatorConfigClusterNotificationsController null, null, null, + null, 'wide', )); diff --git a/support/aphlict/server/lib/AphlictAdminServer.js b/support/aphlict/server/lib/AphlictAdminServer.js index 3cac0be3b5..dd428063c2 100644 --- a/support/aphlict/server/lib/AphlictAdminServer.js +++ b/support/aphlict/server/lib/AphlictAdminServer.js @@ -17,6 +17,7 @@ JX.install('AphlictAdminServer', { server.on('request', JX.bind(this, this._onrequest)); this._server = server; this._clientServers = []; + this._messageHistory = []; }, properties: { @@ -30,6 +31,7 @@ JX.install('AphlictAdminServer', { _messagesOut: null, _server: null, _startTime: null, + _messageHistory: null, getListenerLists: function(instance) { var clients = this.getClientServers(); @@ -121,14 +123,24 @@ JX.install('AphlictAdminServer', { total_count += list.getTotalListenerCount(); } + var now = new Date().getTime(); + + var history_size = this._messageHistory.length; + var history_age = null; + if (history_size) { + history_age = (now - this._messageHistory[0].timestamp); + } + var server_status = { 'instance': instance, - 'uptime': (new Date().getTime() - this._startTime), + 'uptime': (now - this._startTime), 'clients.active': active_count, 'clients.total': total_count, 'messages.in': this._messagesIn, 'messages.out': this._messagesOut, - 'version': 7 + 'version': 7, + 'history.size': history_size, + 'history.age': history_age }; response.writeHead(200, {'Content-Type': 'application/json'}); @@ -140,6 +152,16 @@ JX.install('AphlictAdminServer', { * Transmits a message to all subscribed listeners. */ _transmit: function(instance, message, response) { + var now = new Date().getTime(); + + this._messageHistory.push( + { + timestamp: now, + message: message + }); + + this._purgeHistory(); + var peer_list = this.getPeerList(); message = peer_list.addFingerprint(message); @@ -191,7 +213,50 @@ JX.install('AphlictAdminServer', { error); } } + }, + + getHistory: function(min_age) { + var history = this._messageHistory; + var results = []; + + for (var ii = 0; ii < history.length; ii++) { + if (history[ii].timestamp >= min_age) { + results.push(history[ii].message); + } + } + + return results; + }, + + _purgeHistory: function() { + var messages = this._messageHistory; + + // Maximum number of messages to retain. + var size_limit = 4096; + + // Find the index of the first item we're going to keep. If we have too + // many items, this will be somewhere past the beginning of the list. + var keep = Math.max(0, messages.length - size_limit); + + // Maximum number of milliseconds of history to retain. + var age_limit = 60000; + + // Move the index forward until we find an item that is recent enough + // to retain. + var now = new Date().getTime(); + var min_age = (now - age_limit); + for (keep; keep < messages.length; keep++) { + if (messages[keep].timestamp >= min_age) { + break; + } + } + + // Throw away extra messages. + if (keep) { + this._messageHistory.splice(0, keep); + } } + } }); From 02194f0fc8e9ad51f6ca1c953d27860b81eb05d7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 14:05:29 -0700 Subject: [PATCH 15/56] After Aphlict reconnects, ask the server to replay recent messages Summary: Fixes T12563. If we've ever seen an "open", mark all future connections as reconnects. When we reconnect, replay recent history. (Until duplicate messages (T12564) are handled better this may cause some notification duplication.) Also emit a reconnect event (for T12566) but don't use it yet. Test Plan: {F4912044} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12563 Differential Revision: https://secure.phabricator.com/D17708 --- resources/celerity/map.php | 50 +++++++++---------- .../plugin/DarkConsoleRealtimePlugin.php | 12 +++++ support/aphlict/server/aphlict_server.js | 5 ++ .../aphlict/server/lib/AphlictClientServer.js | 36 +++++++++++++ webroot/rsrc/css/aphront/dark-console.css | 4 ++ .../rsrc/js/application/aphlict/Aphlict.js | 49 ++++++++++++++++-- .../core/darkconsole/behavior-dark-console.js | 3 ++ 7 files changed, 130 insertions(+), 29 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 848b577652..b1830bd29f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,8 +10,8 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => 'cb50c410', - 'darkconsole.pkg.js' => 'a2faee86', + 'core.pkg.js' => '1cedf416', + 'darkconsole.pkg.js' => '31272f61', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', 'diffusion.pkg.css' => '91c5d3a6', @@ -20,7 +20,7 @@ return array( 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', - 'rsrc/css/aphront/dark-console.css' => 'e7c6e44d', + 'rsrc/css/aphront/dark-console.css' => '53798a6d', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', @@ -361,7 +361,7 @@ return array( 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => 'ce5f793f', + 'rsrc/js/application/aphlict/Aphlict.js' => '7cacce98', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'd82b1ff9', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', @@ -522,7 +522,7 @@ return array( 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/darkconsole/DarkLog.js' => 'c8e1ffe3', 'rsrc/js/core/darkconsole/DarkMessage.js' => 'c48cccdd', - 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '698614f9', + 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '2a228a94', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', @@ -538,7 +538,7 @@ return array( 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', - 'aphront-dark-console-css' => 'e7c6e44d', + 'aphront-dark-console-css' => '53798a6d', 'aphront-dialog-view-css' => '685c7e2d', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', @@ -583,7 +583,7 @@ return array( 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => 'ce5f793f', + 'javelin-aphlict' => '7cacce98', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', 'javelin-behavior-aphlict-listen' => 'd82b1ff9', @@ -605,7 +605,7 @@ return array( 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', - 'javelin-behavior-dark-console' => '698614f9', + 'javelin-behavior-dark-console' => '2a228a94', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '408bf173', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', @@ -1089,6 +1089,16 @@ return array( 'javelin-install', 'javelin-util', ), + '2a228a94' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-util', + 'javelin-dom', + 'javelin-request', + 'phabricator-keyboard-shortcut', + 'phabricator-darklog', + 'phabricator-darkmessage', + ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1381,16 +1391,6 @@ return array( '6882e80a' => array( 'javelin-dom', ), - '698614f9' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-util', - 'javelin-dom', - 'javelin-request', - 'phabricator-keyboard-shortcut', - 'phabricator-darklog', - 'phabricator-darkmessage', - ), '69adf288' => array( 'javelin-install', ), @@ -1468,6 +1468,13 @@ return array( 'owners-path-editor', 'javelin-behavior', ), + '7cacce98' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), '7cbe244b' => array( 'javelin-install', 'javelin-util', @@ -2014,13 +2021,6 @@ return array( 'cd2b9b77' => array( 'phui-oi-list-view-css', ), - 'ce5f793f' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php index e73dfa10c4..84cad83dd3 100644 --- a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php +++ b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php @@ -23,6 +23,7 @@ final class DarkConsoleRealtimePlugin extends DarkConsolePlugin { )); $reconnect_label = pht('Reconnect'); + $replay_label = pht('Replay'); $buttons = phutil_tag( 'div', @@ -40,11 +41,22 @@ final class DarkConsoleRealtimePlugin extends DarkConsolePlugin { 'action' => 'reconnect', 'label' => $reconnect_label, )), + id(new PHUIButtonView()) + ->setIcon('fa-backward') + ->setColor(PHUIButtonView::GREY) + ->setText($replay_label) + ->addSigil('dark-console-realtime-action') + ->setMetadata( + array( + 'action' => 'replay', + 'label' => $replay_label, + )), )); return phutil_tag( 'div', array( + 'class' => 'dark-console-realtime', ), array( $buttons, diff --git a/support/aphlict/server/aphlict_server.js b/support/aphlict/server/aphlict_server.js index a4089ef54a..f162e4937c 100644 --- a/support/aphlict/server/aphlict_server.js +++ b/support/aphlict/server/aphlict_server.js @@ -197,3 +197,8 @@ for (ii = 0; ii < aphlict_admins.length; ii++) { admin_server.setClientServers(aphlict_clients); admin_server.setPeerList(peer_list); } + +for (ii = 0; ii < aphlict_clients.length; ii++) { + var client_server = aphlict_clients[ii]; + client_server.setAdminServers(aphlict_admins); +} diff --git a/support/aphlict/server/lib/AphlictClientServer.js b/support/aphlict/server/lib/AphlictClientServer.js index 1d4375cbba..b008d02bca 100644 --- a/support/aphlict/server/lib/AphlictClientServer.js +++ b/support/aphlict/server/lib/AphlictClientServer.js @@ -16,10 +16,12 @@ JX.install('AphlictClientServer', { this._server = server; this._lists = {}; + this._adminServers = []; }, properties: { logger: null, + adminServers: null }, members: { @@ -33,6 +35,20 @@ JX.install('AphlictClientServer', { return this._lists[instance]; }, + getHistory: function(age) { + var results = []; + + var servers = this.getAdminServers(); + for (var ii = 0; ii < servers.length; ii++) { + var messages = servers[ii].getHistory(age); + for (var jj = 0; jj < messages.length; jj++) { + results.push(messages[jj]); + } + } + + return results; + }, + log: function() { var logger = this.getLogger(); if (!logger) { @@ -117,6 +133,26 @@ JX.install('AphlictClientServer', { listener.unsubscribe(message.data); break; + case 'replay': + var age = message.data.age || 60000; + var min_age = (new Date().getTime() - age); + + var old_messages = self.getHistory(min_age); + for (var ii = 0; ii < old_messages.length; ii++) { + var old_message = old_messages[ii]; + + if (!listener.isSubscribedToAny(old_message.subscribers)) { + continue; + } + + try { + listener.writeMessage(old_message); + } catch (error) { + break; + } + } + break; + default: log( 'Unrecognized command "%s".', diff --git a/webroot/rsrc/css/aphront/dark-console.css b/webroot/rsrc/css/aphront/dark-console.css index 403237ebe7..e1ddc5a16d 100644 --- a/webroot/rsrc/css/aphront/dark-console.css +++ b/webroot/rsrc/css/aphront/dark-console.css @@ -231,3 +231,7 @@ padding: 8px; margin: 2px; } + +.dark-console-realtime .button { + margin-right: 8px; +} diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js index c9d75ce1d3..06abaf6fe7 100644 --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -40,6 +40,7 @@ JX.install('Aphlict', { _socket: null, _subscriptions: null, _status: null, + _isReconnect: false, start: function() { JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead)); @@ -94,10 +95,31 @@ JX.install('Aphlict', { }, _open: function() { + // If this is a reconnect, ask the server to replay recent messages + // after other tabs have had a chance to subscribe. Do this before we + // broadcast that the connection status is now open. + if (this._isReconnect) { + setTimeout(JX.bind(this, this._reconnect), 100); + } + this._broadcastStatus('open'); JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'}); }, + _reconnect: function() { + this.replay(); + + JX.Leader.broadcast(null, {type: 'aphlict.reconnect', data: null}); + }, + + replay: function() { + var replay = { + age: 60000 + }; + + JX.Leader.broadcast(null, {type: 'aphlict.replay', data: replay}); + }, + _close: function() { this._broadcastStatus('closed'); }, @@ -131,10 +153,13 @@ JX.install('Aphlict', { case 'aphlict.subscribe': if (is_leader) { - this._write({ - command: 'subscribe', - data: message.data - }); + this._writeCommand('subscribe', message.data); + } + break; + + case 'aphlict.replay': + if (is_leader) { + this._writeCommand('replay', message.data); } break; @@ -147,11 +172,27 @@ JX.install('Aphlict', { _setStatus: function(status) { this._status = status; + + // If we've ever seen an open connection, any new connection we make + // is a reconnect and should replay history. + if (status == 'open') { + this._isReconnect = true; + } + this.invoke('didChangeStatus'); }, _write: function(message) { this._socket.send(JX.JSON.stringify(message)); + }, + + _writeCommand: function(command, message) { + var frame = { + command: command, + data: message + }; + + return this._write(frame); } }, diff --git a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js index a535469b5e..2b17933340 100644 --- a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js +++ b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js @@ -392,6 +392,9 @@ JX.behavior('dark-console', function(config, statics) { ws.reconnect(); } break; + case 'replay': + JX.Aphlict.getInstance().replay(); + break; } }); From 953ab039acc0894e287304364920bc7cc4962cc0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Apr 2017 15:54:53 -0700 Subject: [PATCH 16/56] Disable auto-zoom on mobile form UIs Summary: Chrome and Safari both zoom in on form (input, select, textarea) when it thinks the text is too small (less than 16px... which is huge). This turns user-scalable off. The only drawback is double-tap to zoom will be disabled as well, but given we already responsively design, I don't think thats an issue. Test Plan: iOS simulator on secure and local test instances. Click on an input, no longer see UI zoom in. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17714 --- src/view/page/PhabricatorBarePageView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/page/PhabricatorBarePageView.php b/src/view/page/PhabricatorBarePageView.php index ca9d432286..c9a3c5c5cd 100644 --- a/src/view/page/PhabricatorBarePageView.php +++ b/src/view/page/PhabricatorBarePageView.php @@ -72,7 +72,7 @@ class PhabricatorBarePageView extends AphrontPageView { 'name' => 'viewport', 'content' => 'width=device-width, '. 'initial-scale=1, '. - 'maximum-scale=1', + 'user-scalable=no', )); } From eaecf35324b38e5ae0ddb5bbc3076b431ae9edf2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 14:42:54 -0700 Subject: [PATCH 17/56] Deduplicate application-level notifications from Aphlict Summary: Fixes T12564. We already had some code which seems to deal with this properly, it just wasn't getting used. Assign each application-level notification a unique ID, then ignore messages with duplicate IDs. Test Plan: - In browser A, loaded `/T123`. - In browser B, loaded `/T123`. - Made a comment as B. - Saw notification as A. - Mashed "Replay" a bunch. - Before patch: piles of duplicate notifications. - After patch: no duplicates. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12564 Differential Revision: https://secure.phabricator.com/D17710 --- resources/celerity/map.php | 20 +++++++++---------- .../client/PhabricatorNotificationClient.php | 5 +++++ .../rsrc/js/application/aphlict/Aphlict.js | 4 +++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b1830bd29f..9456c095c0 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => '1cedf416', + 'core.pkg.js' => '2e969052', 'darkconsole.pkg.js' => '31272f61', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -361,7 +361,7 @@ return array( 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '7cacce98', + 'rsrc/js/application/aphlict/Aphlict.js' => '9b5dda26', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'd82b1ff9', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', @@ -583,7 +583,7 @@ return array( 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => '7cacce98', + 'javelin-aphlict' => '9b5dda26', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', 'javelin-behavior-aphlict-listen' => 'd82b1ff9', @@ -1468,13 +1468,6 @@ return array( 'owners-path-editor', 'javelin-behavior', ), - '7cacce98' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '7cbe244b' => array( 'javelin-install', 'javelin-util', @@ -1686,6 +1679,13 @@ return array( 'aphront-typeahead-control-css', 'phui-tag-view-css', ), + '9b5dda26' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), '9bbf3762' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/notification/client/PhabricatorNotificationClient.php b/src/applications/notification/client/PhabricatorNotificationClient.php index ae8ac7eb34..ff5538dbcf 100644 --- a/src/applications/notification/client/PhabricatorNotificationClient.php +++ b/src/applications/notification/client/PhabricatorNotificationClient.php @@ -18,6 +18,11 @@ final class PhabricatorNotificationClient extends Phobject { } public static function tryToPostMessage(array $data) { + $unique_id = Filesystem::readRandomCharacters(32); + $data = $data + array( + 'uniqueID' => $unique_id, + ); + $servers = PhabricatorNotificationServerRef::getEnabledAdminServers(); shuffle($servers); diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js index 06abaf6fe7..d470c4b350 100644 --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -130,7 +130,9 @@ JX.install('Aphlict', { _message: function(raw) { var message = JX.JSON.parse(raw); - JX.Leader.broadcast(null, {type: 'aphlict.server', data: message}); + var id = message.uniqueID || null; + + JX.Leader.broadcast(id, {type: 'aphlict.server', data: message}); }, _receive: function(message, is_leader) { From ffed156981313101277da9cf1fdfaaf9515ce0f1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 15:08:51 -0700 Subject: [PATCH 18/56] After a reconnect, repaint Conpherence thread state Summary: Ref T12566. When we reconnect, refresh the current thread even if we replayed notifications. Test Plan: - Clicked the "Repaint" button, saw the thread refresh. - Clicked the "Reconnect" button, saw the thread reresh. - Launched `aphlict debug`, killed it, restarted it, saw the thread refresh after reconnect. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12566 Differential Revision: https://secure.phabricator.com/D17713 --- resources/celerity/map.php | 104 +++++++++--------- .../plugin/DarkConsoleRealtimePlugin.php | 11 ++ .../rsrc/js/application/aphlict/Aphlict.js | 11 +- .../aphlict/behavior-aphlict-listen.js | 4 + .../conpherence/ConpherenceThreadManager.js | 8 ++ .../core/darkconsole/behavior-dark-console.js | 3 + 6 files changed, 85 insertions(+), 56 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9456c095c0..43dade07f9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,8 +10,8 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => '2e969052', - 'darkconsole.pkg.js' => '31272f61', + 'core.pkg.js' => '349e50d5', + 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', 'diffusion.pkg.css' => '91c5d3a6', @@ -361,16 +361,16 @@ return array( 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '9b5dda26', + 'rsrc/js/application/aphlict/Aphlict.js' => '6304947a', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'd82b1ff9', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '06e7f30e', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'd5a2d665', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', 'rsrc/js/application/calendar/behavior-event-all-day.js' => 'b41537c9', 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'c8b5ee6f', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'dd84a70f', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', 'rsrc/js/application/conpherence/behavior-menu.js' => '80dda04a', @@ -522,7 +522,7 @@ return array( 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/darkconsole/DarkLog.js' => 'c8e1ffe3', 'rsrc/js/core/darkconsole/DarkMessage.js' => 'c48cccdd', - 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '2a228a94', + 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '17bb8539', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', @@ -560,7 +560,7 @@ return array( 'conpherence-message-pane-css' => '14199428', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '26a3ce56', - 'conpherence-thread-manager' => 'c8b5ee6f', + 'conpherence-thread-manager' => 'dd84a70f', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '41af6d25', @@ -583,10 +583,10 @@ return array( 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => '9b5dda26', + 'javelin-aphlict' => '6304947a', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', - 'javelin-behavior-aphlict-listen' => 'd82b1ff9', + 'javelin-behavior-aphlict-listen' => '06e7f30e', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -605,7 +605,7 @@ return array( 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', - 'javelin-behavior-dark-console' => '2a228a94', + 'javelin-behavior-dark-console' => '17bb8539', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '408bf173', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', @@ -947,6 +947,20 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), + '06e7f30e' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'javelin-stratcom', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', + 'javelin-router', + 'javelin-util', + 'javelin-leader', + 'javelin-sound', + 'phabricator-notification', + ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -1016,6 +1030,16 @@ return array( 'javelin-dom', 'javelin-history', ), + '17bb8539' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-util', + 'javelin-dom', + 'javelin-request', + 'phabricator-keyboard-shortcut', + 'phabricator-darklog', + 'phabricator-darkmessage', + ), '185bbd53' => array( 'javelin-install', ), @@ -1089,16 +1113,6 @@ return array( 'javelin-install', 'javelin-util', ), - '2a228a94' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-util', - 'javelin-dom', - 'javelin-request', - 'phabricator-keyboard-shortcut', - 'phabricator-darklog', - 'phabricator-darkmessage', - ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1377,6 +1391,13 @@ return array( 'javelin-install', 'javelin-util', ), + '6304947a' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), '635de1ec' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1679,13 +1700,6 @@ return array( 'aphront-typeahead-control-css', 'phui-tag-view-css', ), - '9b5dda26' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '9bbf3762' => array( 'javelin-behavior', 'javelin-dom', @@ -1967,17 +1981,6 @@ return array( 'c7ccd872' => array( 'phui-fontkit-css', ), - 'c8b5ee6f' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'c90a04fc' => array( 'javelin-dom', 'javelin-dynval', @@ -2086,26 +2089,23 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'd82b1ff9' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'javelin-leader', - 'javelin-sound', - 'phabricator-notification', - ), 'd835b03a' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-shaped-request', ), + 'dd84a70f' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php index 84cad83dd3..dbfec95007 100644 --- a/src/applications/console/plugin/DarkConsoleRealtimePlugin.php +++ b/src/applications/console/plugin/DarkConsoleRealtimePlugin.php @@ -24,6 +24,7 @@ final class DarkConsoleRealtimePlugin extends DarkConsolePlugin { $reconnect_label = pht('Reconnect'); $replay_label = pht('Replay'); + $repaint_label = pht('Repaint'); $buttons = phutil_tag( 'div', @@ -51,6 +52,16 @@ final class DarkConsoleRealtimePlugin extends DarkConsolePlugin { 'action' => 'replay', 'label' => $replay_label, )), + id(new PHUIButtonView()) + ->setIcon('fa-paint-brush') + ->setColor(PHUIButtonView::GREY) + ->setText($repaint_label) + ->addSigil('dark-console-realtime-action') + ->setMetadata( + array( + 'action' => 'repaint', + 'label' => $repaint_label, + )), )); return phutil_tag( diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js index d470c4b350..529a974ab8 100644 --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -99,17 +99,16 @@ JX.install('Aphlict', { // after other tabs have had a chance to subscribe. Do this before we // broadcast that the connection status is now open. if (this._isReconnect) { - setTimeout(JX.bind(this, this._reconnect), 100); + setTimeout(JX.bind(this, this._didReconnect), 100); } this._broadcastStatus('open'); JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'}); }, - _reconnect: function() { + _didReconnect: function() { this.replay(); - - JX.Leader.broadcast(null, {type: 'aphlict.reconnect', data: null}); + this.reconnect(); }, replay: function() { @@ -120,6 +119,10 @@ JX.install('Aphlict', { JX.Leader.broadcast(null, {type: 'aphlict.replay', data: replay}); }, + reconnect: function() { + JX.Leader.broadcast(null, {type: 'aphlict.reconnect', data: null}); + }, + _close: function() { this._broadcastStatus('closed'); }, diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js index f280df93c2..398b2af6df 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -53,6 +53,10 @@ JX.behavior('aphlict-listen', function(config) { case 'notification.individual': JX.Stratcom.invoke('aphlict-notification-message', null, message.data); break; + + case 'aphlict.reconnect': + JX.Stratcom.invoke('aphlict-reconnect', null, message.data); + break; } } diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index a39adb0ee0..36bc8def04 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -240,6 +240,14 @@ JX.install('ConpherenceThreadManager', { this._updateThread(); })); + // If we see a reconnect, always update the thread state. + JX.Stratcom.listen( + 'aphlict-reconnect', + null, + JX.bind(this, function() { + this._updateThread(); + })); + JX.Stratcom.listen( 'click', 'show-older-messages', diff --git a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js index 2b17933340..13949432ff 100644 --- a/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js +++ b/webroot/rsrc/js/core/darkconsole/behavior-dark-console.js @@ -395,6 +395,9 @@ JX.behavior('dark-console', function(config, statics) { case 'replay': JX.Aphlict.getInstance().replay(); break; + case 'repaint': + JX.Aphlict.getInstance().reconnect(); + break; } }); From c98be54bf4e6b801be3672db59f0448aa97884ec Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Apr 2017 16:11:02 -0700 Subject: [PATCH 19/56] Don't show tag when no topic is set Summary: Check the strlen of topic before adding a tag to the header in Conpherence. Test Plan: Remove a topic, no longer see indigo bubble. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17715 --- .../controller/ConpherenceController.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index b4fcc59f2a..8154dc3174 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -60,19 +60,22 @@ abstract class ConpherenceController extends PhabricatorController { if ($conpherence->getID()) { $data = $conpherence->getDisplayData($this->getViewer()); - $topic = id(new PHUITagView()) - ->setName($data['topic']) - ->setShade(PHUITagView::COLOR_VIOLET) - ->setType(PHUITagView::TYPE_SHADE) - ->addClass('conpherence-header-topic'); $header = id(new PHUIHeaderView()) ->setViewer($viewer) ->setHeader($data['title']) - ->addTag($topic) ->setPolicyObject($conpherence) ->setImage($data['image']); + if (strlen($data['topic'])) { + $topic = id(new PHUITagView()) + ->setName($data['topic']) + ->setShade(PHUITagView::COLOR_VIOLET) + ->setType(PHUITagView::TYPE_SHADE) + ->addClass('conpherence-header-topic'); + $header->addTag($topic); + } + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $conpherence, From 976fbee877c97362f770e57830633d68e1e1e93f Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Mon, 17 Apr 2017 17:27:03 -0700 Subject: [PATCH 20/56] Implement ngram search for File objects Summary: Follows the outline in D15656 for implementing ngram search for names of File objects. Also created FileFullTextEngine, because without implementing `PhabricatorFulltextInterface`, `./bin/search` complains that `File` is not an indexable type. Test Plan: - ran `./bin/storage upgrade` to apply the schema change - confirmed the presence of a new `file_filename_ngrams` table - added a couple file objects - ran `bin/search index --type file --force` - confirmed the presence of rows in `file_filename_ngrams` - did a few keyword searches and saw expected results Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T8788 Differential Revision: https://secure.phabricator.com/D17702 --- .../sql/autopatches/20170417.files.ngrams.sql | 7 +++++++ src/__phutil_library_map__.php | 4 ++++ .../PhabricatorFileDeleteController.php | 3 ++- .../files/editor/PhabricatorFileEditor.php | 2 +- ...habricatorFileTemporaryGarbageCollector.php | 4 +++- .../files/query/PhabricatorFileQuery.php | 6 ++++++ .../query/PhabricatorFileSearchEngine.php | 8 ++++++++ .../files/storage/PhabricatorFile.php | 16 ++++++++++++++-- .../storage/PhabricatorFileNameNgrams.php | 18 ++++++++++++++++++ 9 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 resources/sql/autopatches/20170417.files.ngrams.sql create mode 100644 src/applications/files/storage/PhabricatorFileNameNgrams.php diff --git a/resources/sql/autopatches/20170417.files.ngrams.sql b/resources/sql/autopatches/20170417.files.ngrams.sql new file mode 100644 index 0000000000..988b183323 --- /dev/null +++ b/resources/sql/autopatches/20170417.files.ngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_file.file_filename_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index efb5b65ffe..eb71aaacd3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2781,6 +2781,7 @@ phutil_register_library_map(array( 'PhabricatorFileLightboxController' => 'applications/files/controller/PhabricatorFileLightboxController.php', 'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php', 'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php', + 'PhabricatorFileNameNgrams' => 'applications/files/storage/PhabricatorFileNameNgrams.php', 'PhabricatorFileNameTransaction' => 'applications/files/xaction/PhabricatorFileNameTransaction.php', 'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php', 'PhabricatorFileROT13StorageFormat' => 'applications/files/format/PhabricatorFileROT13StorageFormat.php', @@ -7908,6 +7909,8 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorIndexableInterface', + 'PhabricatorNgramsInterface', ), 'PhabricatorFileAES256StorageFormat' => 'PhabricatorFileStorageFormat', 'PhabricatorFileBundleLoader' => 'Phobject', @@ -7954,6 +7957,7 @@ phutil_register_library_map(array( 'PhabricatorFileLightboxController' => 'PhabricatorFileController', 'PhabricatorFileLinkView' => 'AphrontTagView', 'PhabricatorFileListController' => 'PhabricatorFileController', + 'PhabricatorFileNameNgrams' => 'PhabricatorSearchNgrams', 'PhabricatorFileNameTransaction' => 'PhabricatorFileTransactionType', 'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFileROT13StorageFormat' => 'PhabricatorFileStorageFormat', diff --git a/src/applications/files/controller/PhabricatorFileDeleteController.php b/src/applications/files/controller/PhabricatorFileDeleteController.php index acca7c9b1c..1077625bb1 100644 --- a/src/applications/files/controller/PhabricatorFileDeleteController.php +++ b/src/applications/files/controller/PhabricatorFileDeleteController.php @@ -25,7 +25,8 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { } if ($request->isFormPost()) { - $file->delete(); + $engine = new PhabricatorDestructionEngine(); + $engine->destroyObject($file); return id(new AphrontRedirectResponse())->setURI('/file/'); } diff --git a/src/applications/files/editor/PhabricatorFileEditor.php b/src/applications/files/editor/PhabricatorFileEditor.php index 28b781fb37..6a2b797b40 100644 --- a/src/applications/files/editor/PhabricatorFileEditor.php +++ b/src/applications/files/editor/PhabricatorFileEditor.php @@ -71,7 +71,7 @@ final class PhabricatorFileEditor } protected function supportsSearch() { - return false; + return true; } } diff --git a/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php b/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php index c79bb9ba99..bbbdfb9850 100644 --- a/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php +++ b/src/applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php @@ -18,8 +18,10 @@ final class PhabricatorFileTemporaryGarbageCollector 'ttl < %d LIMIT 100', PhabricatorTime::getNow()); + $engine = new PhabricatorDestructionEngine(); + foreach ($files as $file) { - $file->delete(); + $engine->destroyObject($file); } return (count($files) == 100); diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index c2ac083fea..6439f75405 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -119,6 +119,12 @@ final class PhabricatorFileQuery return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + id(new PhabricatorFileNameNgrams()), + $ngrams); + } + public function showOnlyExplicitUploads($explicit_uploads) { $this->explicitUploads = $explicit_uploads; return $this; diff --git a/src/applications/files/query/PhabricatorFileSearchEngine.php b/src/applications/files/query/PhabricatorFileSearchEngine.php index 1f2f2de4b3..0d989000cf 100644 --- a/src/applications/files/query/PhabricatorFileSearchEngine.php +++ b/src/applications/files/query/PhabricatorFileSearchEngine.php @@ -38,6 +38,10 @@ final class PhabricatorFileSearchEngine id(new PhabricatorSearchDateField()) ->setKey('createdEnd') ->setLabel(pht('Created Before')), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('name') + ->setDescription(pht('Search for files by name substring.')), ); } @@ -68,6 +72,10 @@ final class PhabricatorFileSearchEngine $query->withDateCreatedBefore($map['createdEnd']); } + if ($map['name'] !== null) { + $query->withNameNgrams($map['name']); + } + return $query; } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index db15fb43e0..f697be0dc2 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -28,7 +28,9 @@ final class PhabricatorFile extends PhabricatorFileDAO PhabricatorFlaggableInterface, PhabricatorPolicyInterface, PhabricatorDestructibleInterface, - PhabricatorConduitResultInterface { + PhabricatorConduitResultInterface, + PhabricatorIndexableInterface, + PhabricatorNgramsInterface { const METADATA_IMAGE_WIDTH = 'width'; const METADATA_IMAGE_HEIGHT = 'height'; @@ -87,7 +89,7 @@ final class PhabricatorFile extends PhabricatorFileDAO 'metadata' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text255?', + 'name' => 'sort255?', 'mimeType' => 'text255?', 'byteSize' => 'uint64', 'storageEngine' => 'text32', @@ -1585,4 +1587,14 @@ final class PhabricatorFile extends PhabricatorFileDAO return array(); } +/* -( PhabricatorNgramInterface )------------------------------------------ */ + + + public function newNgrams() { + return array( + id(new PhabricatorFileNameNgrams()) + ->setValue($this->getName()), + ); + } + } diff --git a/src/applications/files/storage/PhabricatorFileNameNgrams.php b/src/applications/files/storage/PhabricatorFileNameNgrams.php new file mode 100644 index 0000000000..6e97e8f0e4 --- /dev/null +++ b/src/applications/files/storage/PhabricatorFileNameNgrams.php @@ -0,0 +1,18 @@ + Date: Mon, 17 Apr 2017 17:00:46 -0700 Subject: [PATCH 21/56] Every so often, ask the Aphict server how things are going Summary: Ref T12573. This sends a "ping" to the server, and a "pong" back to the client, every 15 seconds. This tricks ELBs into thinking we're doing something useful and productive. Test Plan: Ran `bin/aphlict debug`, loaded Phabricator, saw ping/pong in logs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12573 Differential Revision: https://secure.phabricator.com/D17717 --- resources/celerity/map.php | 20 ++++++++-------- .../aphlict/server/lib/AphlictClientServer.js | 12 ++++++++++ .../rsrc/js/application/aphlict/Aphlict.js | 23 +++++++++++++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 43dade07f9..4bf38f9e57 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => '349e50d5', + 'core.pkg.js' => '4a83713e', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -361,7 +361,7 @@ return array( 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '6304947a', + 'rsrc/js/application/aphlict/Aphlict.js' => '674e335f', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '06e7f30e', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', @@ -583,7 +583,7 @@ return array( 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => '6304947a', + 'javelin-aphlict' => '674e335f', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', 'javelin-behavior-aphlict-listen' => '06e7f30e', @@ -1391,19 +1391,19 @@ return array( 'javelin-install', 'javelin-util', ), - '6304947a' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '635de1ec' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', ), + '674e335f' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', diff --git a/support/aphlict/server/lib/AphlictClientServer.js b/support/aphlict/server/lib/AphlictClientServer.js index b008d02bca..0d23d4f4f1 100644 --- a/support/aphlict/server/lib/AphlictClientServer.js +++ b/support/aphlict/server/lib/AphlictClientServer.js @@ -153,6 +153,18 @@ JX.install('AphlictClientServer', { } break; + case 'ping': + var pong = { + type: 'pong' + }; + + try { + listener.writeMessage(pong); + } catch (error) { + // Ignore any issues here, we'll clean up elsewhere. + } + break; + default: log( 'Unrecognized command "%s".', diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js index 529a974ab8..b2d7ec3215 100644 --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -41,6 +41,7 @@ JX.install('Aphlict', { _subscriptions: null, _status: null, _isReconnect: false, + _keepaliveInterval: false, start: function() { JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead)); @@ -104,6 +105,14 @@ JX.install('Aphlict', { this._broadcastStatus('open'); JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'}); + + // By default, ELBs terminate connections after 60 seconds with no + // traffic. Other load balancers may have similar configuration. Send + // a keepalive message every 15 seconds to prevent load balancers from + // deciding they can reap this connection. + + var keepalive = JX.bind(this, this._keepalive); + this._keepaliveInterval = setInterval(keepalive, 15000); }, _didReconnect: function() { @@ -124,6 +133,11 @@ JX.install('Aphlict', { }, _close: function() { + if (this._keepaliveInterval) { + clearInterval(this._keepaliveInterval); + this._keepaliveInterval = null; + } + this._broadcastStatus('closed'); }, @@ -135,6 +149,11 @@ JX.install('Aphlict', { var message = JX.JSON.parse(raw); var id = message.uniqueID || null; + // If this is just a keepalive response, don't bother broadcasting it. + if (message.type == 'pong') { + return; + } + JX.Leader.broadcast(id, {type: 'aphlict.server', data: message}); }, @@ -198,6 +217,10 @@ JX.install('Aphlict', { }; return this._write(frame); + }, + + _keepalive: function() { + this._writeCommand('ping', null); } }, From b54adc6161c205e146fabb801ca53a44d94da444 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 18 Apr 2017 08:13:34 -0700 Subject: [PATCH 22/56] Kick off indexing for File objects on creation Summary: Ensures that newly-made `File` objects get indexed into the new ngrams index. Fixes T8788. Test Plan: - uploaded a file with daemons stopped; confirmed no new rows in ngrams table - started daemons; confirmed indexing of previously-uploaded files happened - uploaded a new file with daemons running; confirmed it got added to the index Not sure how to test the changes to `PhabricatorFileUploadSource->writeChunkedFile()` and `PhabricatorChunkedFileStorageEngine->allocateChunks()`. I spent a few minutes trying to find their callers, but the first looks like it requires a Diffusion repo and the 2nd is only accessible via Conduit. I can test that stuff if necessary, but it's such a small change that I'm not worried about it. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T8788 Differential Revision: https://secure.phabricator.com/D17718 --- .../engine/PhabricatorChunkedFileStorageEngine.php | 2 +- src/applications/files/storage/PhabricatorFile.php | 10 ++++++++-- .../files/uploadsource/PhabricatorFileUploadSource.php | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php b/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php index 1bb86cf67e..30de63b904 100644 --- a/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php @@ -129,7 +129,7 @@ final class PhabricatorChunkedFileStorageEngine foreach ($chunks as $chunk) { $chunk->save(); } - $file->save(); + $file->saveAndIndex(); $file->saveTransaction(); return $file; diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index f697be0dc2..1c9b96069d 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -148,6 +148,12 @@ final class PhabricatorFile extends PhabricatorFileDAO return parent::save(); } + public function saveAndIndex() { + $this->save(); + PhabricatorSearchWorker::queueDocumentForIndexing($this->getPHID()); + return $this; + } + public function getMonogram() { return 'F'.$this->getID(); } @@ -234,7 +240,7 @@ final class PhabricatorFile extends PhabricatorFileDAO $new_file->readPropertiesFromParameters($params); - $new_file->save(); + $new_file->saveAndIndex(); return $new_file; } @@ -390,7 +396,7 @@ final class PhabricatorFile extends PhabricatorFileDAO // Do nothing } - $file->save(); + $file->saveAndIndex(); return $file; } diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index 0ef33a2521..cda07d590b 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -145,7 +145,7 @@ abstract class PhabricatorFileUploadSource } $file = PhabricatorFile::newChunkedFile($engine, $length, $parameters); - $file->save(); + $file->saveAndIndex(); $rope = $this->getRope(); From 8377bb363710277dcfa32ed1163498db84a826cd Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Apr 2017 09:00:52 -0700 Subject: [PATCH 23/56] Raise a tailored error message on "show-outbound --id cat" Summary: Fixes T12579. Unclear why the user ran this command. Test Plan: Ran with `--id cat`. Ran with `--id 123`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12579 Differential Revision: https://secure.phabricator.com/D17719 --- .../PhabricatorMailManagementShowOutboundWorkflow.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php index b30f2685e6..54a91861ae 100644 --- a/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php +++ b/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php @@ -37,6 +37,15 @@ final class PhabricatorMailManagementShowOutboundWorkflow '--id')); } + foreach ($ids as $id) { + if (!ctype_digit($id)) { + throw new PhutilArgumentUsageException( + pht( + 'Argument "%s" is not a valid message ID.', + $id)); + } + } + $messages = id(new PhabricatorMetaMTAMail())->loadAllWhere( 'id IN (%Ld)', $ids); From e187519f00a0f26f7b886f544c73c98e72d342b9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Apr 2017 09:05:00 -0700 Subject: [PATCH 24/56] When a transaction has no quote ref, render "@user wrote:" properly Summary: Fixes T12576. In Javascript, `data.ref` is null, which is getting turned into `/quote/?ref=null`. The code already handles this case, just not with `ref=null` happening in JS: https://secure.phabricator.com/source/phabricator/browse/master/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php;b54adc6161c205e146fabb801ca53a44d94da444$47-52 Test Plan: {F4913862} - Also quoted a normal comment on a normal object in a normal way. Reviewers: amckinley, chad Reviewed By: chad Maniphest Tasks: T12576 Differential Revision: https://secure.phabricator.com/D17720 --- resources/celerity/map.php | 22 +++++++++---------- .../transactions/behavior-transaction-list.js | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4bf38f9e57..0c7c6f614b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => '4a83713e', + 'core.pkg.js' => 'e129dcd4', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -446,7 +446,7 @@ return array( 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '94c65b72', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', - 'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea', + 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', 'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '93d0c9e3', 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', @@ -672,7 +672,7 @@ return array( 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', - 'javelin-behavior-phabricator-transaction-list' => '13c739ea', + 'javelin-behavior-phabricator-transaction-list' => '1f6794f6', 'javelin-behavior-phabricator-watch-anchor' => '9f36c42d', 'javelin-behavior-pholio-mock-edit' => 'bee502c8', 'javelin-behavior-pholio-mock-view' => 'fbe497e7', @@ -1016,14 +1016,6 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), - '13c739ea' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-dom', - 'javelin-uri', - 'phabricator-textareautils', - ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1080,6 +1072,14 @@ return array( 'javelin-uri', 'javelin-routable', ), + '1f6794f6' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'javelin-uri', + 'phabricator-textareautils', + ), '1fe2510c' => array( 'javelin-install', 'javelin-dom', diff --git a/webroot/rsrc/js/application/transactions/behavior-transaction-list.js b/webroot/rsrc/js/application/transactions/behavior-transaction-list.js index 854d97635e..fdcb055872 100644 --- a/webroot/rsrc/js/application/transactions/behavior-transaction-list.js +++ b/webroot/rsrc/js/application/transactions/behavior-transaction-list.js @@ -43,8 +43,10 @@ JX.behavior('phabricator-transaction-list', function() { e.prevent(); var data = e.getNodeData('transaction-quote'); + var ref = data.ref || ''; + new JX.Workflow(data.uri) - .setData({ref: data.ref}) + .setData({ref: ref}) .setHandler(function(r) { var textarea = JX.$(data.targetID); From be00264ae74b12c0da7a6b1d27468774d103fc05 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 18 Apr 2017 11:07:24 -0700 Subject: [PATCH 25/56] Make daemons perform file deletion Summary: Deletion is a possibly time-intensive process, especially with large files that are backed by high-latency, chunked storage (such as S3). Even ~200mb objects take minutes to delete, which makes for an unhappy experience. Fixes T10828. Test Plan: Delete a large file, and stare in awe of the swiftness with which I am redirected to the main file application. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: thoughtpolice, Korvin Maniphest Tasks: T10828 Differential Revision: https://secure.phabricator.com/D15743 --- .../autopatches/20170418.files.isDeleted.sql | 2 ++ src/__phutil_library_map__.php | 2 ++ .../PhabricatorFileDataController.php | 1 + .../PhabricatorFileDeleteController.php | 12 +++++-- .../PhabricatorFileInfoController.php | 2 ++ .../editor/PhabricatorFileEditEngine.php | 4 ++- .../files/query/PhabricatorFileQuery.php | 13 ++++++++ .../query/PhabricatorFileSearchEngine.php | 4 ++- .../files/storage/PhabricatorFile.php | 2 ++ .../files/worker/FileDeletionWorker.php | 31 +++++++++++++++++++ 10 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 resources/sql/autopatches/20170418.files.isDeleted.sql create mode 100644 src/applications/files/worker/FileDeletionWorker.php diff --git a/resources/sql/autopatches/20170418.files.isDeleted.sql b/resources/sql/autopatches/20170418.files.isDeleted.sql new file mode 100644 index 0000000000..1349e3cbc7 --- /dev/null +++ b/resources/sql/autopatches/20170418.files.isDeleted.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_file.file + ADD isDeleted BOOL NOT NULL DEFAULT 0; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index eb71aaacd3..58a963b792 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1098,6 +1098,7 @@ phutil_register_library_map(array( 'FileAllocateConduitAPIMethod' => 'applications/files/conduit/FileAllocateConduitAPIMethod.php', 'FileConduitAPIMethod' => 'applications/files/conduit/FileConduitAPIMethod.php', 'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php', + 'FileDeletionWorker' => 'applications/files/worker/FileDeletionWorker.php', 'FileDownloadConduitAPIMethod' => 'applications/files/conduit/FileDownloadConduitAPIMethod.php', 'FileInfoConduitAPIMethod' => 'applications/files/conduit/FileInfoConduitAPIMethod.php', 'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php', @@ -5977,6 +5978,7 @@ phutil_register_library_map(array( 'FileAllocateConduitAPIMethod' => 'FileConduitAPIMethod', 'FileConduitAPIMethod' => 'ConduitAPIMethod', 'FileCreateMailReceiver' => 'PhabricatorMailReceiver', + 'FileDeletionWorker' => 'PhabricatorWorker', 'FileDownloadConduitAPIMethod' => 'FileConduitAPIMethod', 'FileInfoConduitAPIMethod' => 'FileConduitAPIMethod', 'FileMailReceiver' => 'PhabricatorObjectMailReceiver', diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index 31761d1244..c98c05e27b 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -143,6 +143,7 @@ final class PhabricatorFileDataController extends PhabricatorFileController { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($this->phid)) + ->withIsDeleted(false) ->executeOne(); if (!$file) { diff --git a/src/applications/files/controller/PhabricatorFileDeleteController.php b/src/applications/files/controller/PhabricatorFileDeleteController.php index 1077625bb1..3c64e11a8c 100644 --- a/src/applications/files/controller/PhabricatorFileDeleteController.php +++ b/src/applications/files/controller/PhabricatorFileDeleteController.php @@ -9,6 +9,7 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->withIsDeleted(false) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -25,8 +26,15 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { } if ($request->isFormPost()) { - $engine = new PhabricatorDestructionEngine(); - $engine->destroyObject($file); + // Mark the file for deletion, save it, and schedule a worker to + // sweep by later and pick it up. + $file->setIsDeleted(true)->save(); + + PhabricatorWorker::scheduleTask( + 'FileDeletionWorker', + array('objectPHID' => $file->getPHID()), + array('priority' => PhabricatorWorker::PRIORITY_BULK)); + return id(new AphrontRedirectResponse())->setURI('/file/'); } diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index f7e72be2aa..061790aa31 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -15,6 +15,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) + ->withIsDeleted(false) ->executeOne(); if (!$file) { @@ -25,6 +26,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->withIsDeleted(false) ->executeOne(); if (!$file) { return new Aphront404Response(); diff --git a/src/applications/files/editor/PhabricatorFileEditEngine.php b/src/applications/files/editor/PhabricatorFileEditEngine.php index 04c4753bd5..9c5eaef74a 100644 --- a/src/applications/files/editor/PhabricatorFileEditEngine.php +++ b/src/applications/files/editor/PhabricatorFileEditEngine.php @@ -36,7 +36,9 @@ final class PhabricatorFileEditEngine } protected function newObjectQuery() { - return new PhabricatorFileQuery(); + $query = new PhabricatorFileQuery(); + $query->withIsDeleted(false); + return $query; } protected function getObjectCreateTitleText($object) { diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index 6439f75405..a2de5ca6ab 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -15,6 +15,7 @@ final class PhabricatorFileQuery private $maxLength; private $names; private $isPartial; + private $isDeleted; private $needTransforms; private $builtinKeys; @@ -119,6 +120,11 @@ final class PhabricatorFileQuery return $this; } + public function withIsDeleted($deleted) { + $this->isDeleted = $deleted; + return $this; + } + public function withNameNgrams($ngrams) { return $this->withNgramsConstraint( id(new PhabricatorFileNameNgrams()), @@ -396,6 +402,13 @@ final class PhabricatorFileQuery (int)$this->isPartial); } + if ($this->isDeleted !== null) { + $where[] = qsprintf( + $conn, + 'isDeleted = %d', + (int)$this->isDeleted); + } + if ($this->builtinKeys !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/files/query/PhabricatorFileSearchEngine.php b/src/applications/files/query/PhabricatorFileSearchEngine.php index 0d989000cf..59810c830c 100644 --- a/src/applications/files/query/PhabricatorFileSearchEngine.php +++ b/src/applications/files/query/PhabricatorFileSearchEngine.php @@ -16,7 +16,9 @@ final class PhabricatorFileSearchEngine } public function newQuery() { - return new PhabricatorFileQuery(); + $query = new PhabricatorFileQuery(); + $query->withIsDeleted(false); + return $query; } protected function buildCustomSearchFields() { diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 1c9b96069d..7d314d213d 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -59,6 +59,7 @@ final class PhabricatorFile extends PhabricatorFileDAO protected $isExplicitUpload = 1; protected $viewPolicy = PhabricatorPolicies::POLICY_USER; protected $isPartial = 0; + protected $isDeleted = 0; private $objects = self::ATTACHABLE; private $objectPHIDs = self::ATTACHABLE; @@ -103,6 +104,7 @@ final class PhabricatorFile extends PhabricatorFileDAO 'mailKey' => 'bytes20', 'isPartial' => 'bool', 'builtinKey' => 'text64?', + 'isDeleted' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, diff --git a/src/applications/files/worker/FileDeletionWorker.php b/src/applications/files/worker/FileDeletionWorker.php new file mode 100644 index 0000000000..0cc89ac30e --- /dev/null +++ b/src/applications/files/worker/FileDeletionWorker.php @@ -0,0 +1,31 @@ +getTaskData(), 'objectPHID'); + if (!$phid) { + throw new PhabricatorWorkerPermanentFailureException( + pht('No "%s" in task data.', 'objectPHID')); + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($phid)) + ->executeOne(); + + if (!$file) { + throw new PhabricatorWorkerPermanentFailureException( + pht('File "%s" does not exist.', $phid)); + } + + return $file; + } + + protected function doWork() { + $file = $this->loadFile(); + $engine = new PhabricatorDestructionEngine(); + $engine->destroyObject($file); + } + +} From 5d55804e3fa6e5469162a2d928239ab51eb03fc0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Apr 2017 09:26:21 -0700 Subject: [PATCH 26/56] Play a sound when receiving a new chat message Summary: Ref T7567. Nothing fancy yet, just getting this working. Sound is lightly edited version of "Pop 6": https://www.freesound.org/people/greenvwbeetle/sounds/244656/ Test Plan: Sent chat, heard sounds. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7567 Differential Revision: https://secure.phabricator.com/D17721 --- resources/celerity/map.php | 30 ++++++++++-------- resources/celerity/packages.php | 1 + .../ConpherenceUpdateController.php | 6 ++++ webroot/rsrc/audio/basic/tap.mp3 | Bin 0 -> 1564 bytes .../conpherence/ConpherenceThreadManager.js | 8 +++++ 5 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 webroot/rsrc/audio/basic/tap.mp3 diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0c7c6f614b..c4885db350 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => 'e129dcd4', + 'core.pkg.js' => '5363ae35', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -19,6 +19,7 @@ return array( 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', + 'rsrc/audio/basic/tap.mp3' => 'fc2fd796', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/dark-console.css' => '53798a6d', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', @@ -370,7 +371,7 @@ return array( 'rsrc/js/application/calendar/behavior-event-all-day.js' => 'b41537c9', 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'dd84a70f', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '311eae46', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', 'rsrc/js/application/conpherence/behavior-menu.js' => '80dda04a', @@ -560,7 +561,7 @@ return array( 'conpherence-message-pane-css' => '14199428', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '26a3ce56', - 'conpherence-thread-manager' => 'dd84a70f', + 'conpherence-thread-manager' => '311eae46', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '41af6d25', @@ -1132,6 +1133,17 @@ return array( '2ee659ce' => array( 'javelin-install', ), + '311eae46' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '31420f77' => array( 'javelin-behavior', ), @@ -2095,17 +2107,6 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), - 'dd84a70f' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', @@ -2376,6 +2377,7 @@ return array( 'javelin-behavior-toggle-class', 'javelin-behavior-lightbox-attachments', 'phabricator-busy', + 'javelin-sound', 'javelin-aphlict', 'phabricator-notification', 'javelin-behavior-aphlict-listen', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 5f935c8620..cb5eb22b51 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -46,6 +46,7 @@ return array( 'javelin-behavior-toggle-class', 'javelin-behavior-lightbox-attachments', 'phabricator-busy', + 'javelin-sound', 'javelin-aphlict', 'phabricator-notification', 'javelin-behavior-aphlict-listen', diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index d270043c76..f1af58fb74 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -546,6 +546,9 @@ final class ConpherenceUpdateController $dropdown_query = id(new AphlictDropdownDataQuery()) ->setViewer($user); $dropdown_query->execute(); + + $receive_sound = celerity_get_resource_uri('/rsrc/audio/basic/tap.mp3'); + $content = array( 'non_update' => $non_update, 'transactions' => hsprintf('%s', $rendered_transactions), @@ -559,6 +562,9 @@ final class ConpherenceUpdateController $dropdown_query->getNotificationData(), $dropdown_query->getConpherenceData(), ), + 'sound' => array( + 'receive' => $receive_sound, + ), ); return $content; diff --git a/webroot/rsrc/audio/basic/tap.mp3 b/webroot/rsrc/audio/basic/tap.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b497cfb13f7eca43f21a5f79c701b189af7d2bba GIT binary patch literal 1564 zcmezWd%_V0bP$o5mkt!;2V!Oh1~wUnU4+03Lf}6k0JJN>$I;i-SkKb3h-D|tNu~-a ztTP-L7$1P#5im9P3ru39(Eop507ekk7U!6f&kV{<+AGV=CwMR@1kL>|wKr~k{Z>T_ z-iVzGixfMOHmS{0;t)`9Xy9RER-4zsX#9w|^&o@e{U(OC00Ewa1_g=#uUMGnH8_|U z+CMckDDeMpe(=Li{s==uf;|6IMur0|;4od9V8A2s_<+u0b|<4l0gDz~e9%yE<3`W_ z4jHM8j5I!u4CdoS-wIq^v=j^lR#tYf^<8O5v7T{c*~0>t*=!8?d)N-#X^}E!%9zPw z*6%B`S3!Bg*PvaDygR)l)-5nCR@@z0d&yjE%4ZAz)2#8lR>o1@d=F*stlE?LZ~pA5 zHUiUgzt(049LsxpO;&HU@9s~xqBor=D`H=I=xStG_32lq5?7Xbc1Z`$zW(IvqAS}{ zP3HEONp9`iv~0KU)RUn*Peoa8vzM#2y_K{3|NqvU_r)`Pi?z0{jaeO1Tb2D~Rb(2I zQLuUawbu?OvlkgHQ(CQHIe+@rhfYCj=S)v|=zMKQMhia!$HU}pGg?I@3?J!vngzE_ z>uCS_d)2Q;kKWJNx{l4);r#8HUD?=GfR}LFCY`PoeCGlWKGY_9uEYqx&D-@rcbiDpBf8(;dX?Dd98-5Eh9J^xT zJzFEfsZi(AmTMk|m)1;-m>5=gO`Ge|8IQFkXI2F!Pn!K`(PfuNPuI_lj#Vf1R`;mC zNblWVxBN<3#Qz`nKi9k|RQ>x X`S1d1eh$M2D;O9|8W Date: Tue, 18 Apr 2017 11:14:37 -0700 Subject: [PATCH 27/56] Fix several duplication/replay behaviors in Aphlict Summary: Ref T12566. Ref T12563. This fixes three bugs with Aphlict replay stuff: First, Conphernece would try to repaint the UI even if no thread was open. Only repaint when a thread is open. Second, although we deduplicate JX.Leader messages, we didn't deduplicate actual notification messages. If you browsed the leader window, then it re-elected itelf as a leader and replayed history, it could rebroadcast notifications and other windows could show doubles. Deduplicate notifications to prevent this. Third, we always replayed the last 60 seconds of history. When you browsed the leader window, whichever window became the new leader (possibly the one you just browsed) could replay messages from before it had opened, leading to duplicate messages. Particularly, after receiving a message and then browsing you could see that message again. Instead, only replay history as far back as when the window first opened. Test Plan: - Clicked "Repaint" with a thread open, saw a repaint. Clicked "Repaint" with Conpherence open but no thread, no repaint and no 404 request to `/update/null/`. - In browser A, opened three windows. In browser B, sent a notification. In browser A, browsed the leader window away twice in a row. Observed that the window which never became a leader doesn't duplicate notifications. - In browser A, opened three windows. In browser B, sent a notification. In browser A, browsed the leader window away over and over again. Observed that replay requests issued with appropriate history windows. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12566, T12563 Differential Revision: https://secure.phabricator.com/D17722 --- resources/celerity/map.php | 78 +++++++++---------- ...icatorNotificationIndividualController.php | 1 + .../rsrc/js/application/aphlict/Aphlict.js | 18 ++++- .../aphlict/behavior-aphlict-listen.js | 10 ++- .../conpherence/ConpherenceThreadManager.js | 4 + 5 files changed, 67 insertions(+), 44 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c4885db350..02db832ff4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => '5363ae35', + 'core.pkg.js' => '47a69358', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -362,16 +362,16 @@ return array( 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '674e335f', + 'rsrc/js/application/aphlict/Aphlict.js' => 'e1d4b11a', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '06e7f30e', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '3c547a81', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'd5a2d665', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', 'rsrc/js/application/calendar/behavior-event-all-day.js' => 'b41537c9', 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '311eae46', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '4d863052', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', 'rsrc/js/application/conpherence/behavior-menu.js' => '80dda04a', @@ -561,7 +561,7 @@ return array( 'conpherence-message-pane-css' => '14199428', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '26a3ce56', - 'conpherence-thread-manager' => '311eae46', + 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '41af6d25', @@ -584,10 +584,10 @@ return array( 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => '674e335f', + 'javelin-aphlict' => 'e1d4b11a', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', - 'javelin-behavior-aphlict-listen' => '06e7f30e', + 'javelin-behavior-aphlict-listen' => '3c547a81', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -948,20 +948,6 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), - '06e7f30e' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'javelin-leader', - 'javelin-sound', - 'phabricator-notification', - ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -1133,17 +1119,6 @@ return array( '2ee659ce' => array( 'javelin-install', ), - '311eae46' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '31420f77' => array( 'javelin-behavior', ), @@ -1166,6 +1141,20 @@ return array( 'javelin-dom', 'javelin-magical-init', ), + '3c547a81' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'javelin-stratcom', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', + 'javelin-router', + 'javelin-util', + 'javelin-leader', + 'javelin-sound', + 'phabricator-notification', + ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', @@ -1284,6 +1273,17 @@ return array( 'javelin-uri', 'phabricator-notification', ), + '4d863052' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1409,13 +1409,6 @@ return array( 'javelin-workflow', 'javelin-dom', ), - '674e335f' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', @@ -2127,6 +2120,13 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), + 'e1d4b11a' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), 'e1ff79b1' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php index 41dade2747..af3e8bcff0 100644 --- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php +++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php @@ -47,6 +47,7 @@ final class PhabricatorNotificationIndividualController 'title' => $data['title'], 'body' => $data['body'], 'content' => hsprintf('%s', $content), + 'uniqueID' => 'story/'.$story->getChronologicalKey(), ); return id(new AphrontAjaxResponse())->setContent($response); diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js index b2d7ec3215..e27ff1b4eb 100644 --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -29,6 +29,7 @@ JX.install('Aphlict', { this._uri = uri; this._subscriptions = subscriptions; this._setStatus('setup'); + this._startTime = new Date().getTime(); JX.Aphlict._instance = this; }, @@ -42,6 +43,7 @@ JX.install('Aphlict', { _status: null, _isReconnect: false, _keepaliveInterval: false, + _startTime: null, start: function() { JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead)); @@ -121,8 +123,22 @@ JX.install('Aphlict', { }, replay: function() { + var age = 60000; + + // If the page was loaded a few moments ago, only query for recent + // history. This keeps us from replaying events over and over again as + // a user browses normally. + + // Allow a small margin of error for the actual page load time. It's + // also fine to replay a notification which the user saw for a brief + // moment on the previous page. + var extra_time = 500; + var now = new Date().getTime(); + + age = Math.min(extra_time + (now - this._startTime), age); + var replay = { - age: 60000 + age: age }; JX.Leader.broadcast(null, {type: 'aphlict.replay', data: replay}); diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js index 398b2af6df..333e8daac1 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -66,10 +66,12 @@ JX.behavior('aphlict-listen', function(config) { return; } - JX.Leader.broadcast(null, { - type: 'notification.individual', - data: response - }); + JX.Leader.broadcast( + response.uniqueID, + { + type: 'notification.individual', + data: response + }); } JX.Stratcom.listen('aphlict-notification-message', null, function(e) { diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 7c3c749e0f..81e10bf35b 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -245,6 +245,10 @@ JX.install('ConpherenceThreadManager', { 'aphlict-reconnect', null, JX.bind(this, function() { + if (!this._loadedThreadPHID) { + return; + } + this._updateThread(); })); From ece9579d258da0b3159fb82706f1b866b7eecc93 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 18 Apr 2017 12:48:59 -0700 Subject: [PATCH 28/56] Switch File deletion to use ModularTransactions Summary: Fixes T12587. Adds a new `PhabricatorFileDeleteTransaction` that enqueues `File` delete tasks. Test Plan: - hack `PhabricatorFileQuery` to ignore isDeleted state - stop daemons - upload a file, delete it from the UI - check that the DB has updated isDeleted = 1 - check timeline rendering in `File` detail view - start daemons - confirm rows are deleted from DB Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, thoughtpolice Maniphest Tasks: T12587 Differential Revision: https://secure.phabricator.com/D17723 --- src/__phutil_library_map__.php | 2 + .../PhabricatorFileDeleteController.php | 18 +++++--- .../files/storage/PhabricatorFile.php | 3 ++ .../PhabricatorFileDeleteTransaction.php | 45 +++++++++++++++++++ 4 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/applications/files/xaction/PhabricatorFileDeleteTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 58a963b792..d5a7bfe95e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2764,6 +2764,7 @@ phutil_register_library_map(array( 'PhabricatorFileDAO' => 'applications/files/storage/PhabricatorFileDAO.php', 'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php', 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', + 'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php', 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', 'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php', 'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php', @@ -7931,6 +7932,7 @@ phutil_register_library_map(array( 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileDataController' => 'PhabricatorFileController', 'PhabricatorFileDeleteController' => 'PhabricatorFileController', + 'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 'PhabricatorFileEditController' => 'PhabricatorFileController', 'PhabricatorFileEditEngine' => 'PhabricatorEditEngine', diff --git a/src/applications/files/controller/PhabricatorFileDeleteController.php b/src/applications/files/controller/PhabricatorFileDeleteController.php index 3c64e11a8c..be9cecfcf5 100644 --- a/src/applications/files/controller/PhabricatorFileDeleteController.php +++ b/src/applications/files/controller/PhabricatorFileDeleteController.php @@ -26,14 +26,18 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { } if ($request->isFormPost()) { - // Mark the file for deletion, save it, and schedule a worker to - // sweep by later and pick it up. - $file->setIsDeleted(true)->save(); + $xactions = array(); - PhabricatorWorker::scheduleTask( - 'FileDeletionWorker', - array('objectPHID' => $file->getPHID()), - array('priority' => PhabricatorWorker::PRIORITY_BULK)); + $xactions[] = id(new PhabricatorFileTransaction()) + ->setTransactionType(PhabricatorFileDeleteTransaction::TRANSACTIONTYPE) + ->setNewValue(true); + + id(new PhabricatorFileEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($file, $xactions); return id(new AphrontRedirectResponse())->setURI('/file/'); } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 7d314d213d..9fdae4445a 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -41,6 +41,9 @@ final class PhabricatorFile extends PhabricatorFileDAO const METADATA_STORAGE = 'storage'; const METADATA_INTEGRITY = 'integrity'; + const STATUS_ACTIVE = 'active'; + const STATUS_DELETED = 'deleted'; + protected $name; protected $mimeType; protected $byteSize; diff --git a/src/applications/files/xaction/PhabricatorFileDeleteTransaction.php b/src/applications/files/xaction/PhabricatorFileDeleteTransaction.php new file mode 100644 index 0000000000..d0b2c942c4 --- /dev/null +++ b/src/applications/files/xaction/PhabricatorFileDeleteTransaction.php @@ -0,0 +1,45 @@ +setIsDeleted(true); + + PhabricatorWorker::scheduleTask( + 'FileDeletionWorker', + array('objectPHID' => $file->getPHID()), + array('priority' => PhabricatorWorker::PRIORITY_BULK)); + } + + public function getIcon() { + return 'fa-ban'; + } + + public function getColor() { + return 'red'; + } + + public function getTitle() { + return pht( + '%s deleted this file.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s deleted %s.', + $this->renderAuthor(), + $this->renderObject()); + } + +} From b479941ceb6e2034ae8d981886937916884acc06 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Apr 2017 13:17:39 -0700 Subject: [PATCH 29/56] Make "Range" HTTP header work for Celerity static resource requests Summary: Ref T7567. In T8266 I fixed a bunch of obscure "Range" issues, but only for file downloads -- not for Celerity. Extend all that stuff to Celerity, which is fortunately much easier. I believe this will fix Conpherence sounds in Safari. Test Plan: - Wrote out an HTTP request in a text file with `Range: bytes=0-1` and similar, piped it to localhost with `cat request.txt | nc localhost 80`, saw server return appropriate range responses consistent with file behavior after T8266, which all seems to work. - Also did that for files to try to make sure I wasn't breaking anything. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T7567 Differential Revision: https://secure.phabricator.com/D17724 --- src/aphront/response/AphrontFileResponse.php | 25 +++++++++++++++++ .../controller/CelerityResourceController.php | 27 ++++++++++++++++--- .../PhabricatorFileDataController.php | 20 ++------------ 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/aphront/response/AphrontFileResponse.php b/src/aphront/response/AphrontFileResponse.php index 9699c49ad4..6bae4c808f 100644 --- a/src/aphront/response/AphrontFileResponse.php +++ b/src/aphront/response/AphrontFileResponse.php @@ -139,4 +139,29 @@ final class AphrontFileResponse extends AphrontResponse { return $this->getCompressResponse(); } + public function parseHTTPRange($range) { + $begin = null; + $end = null; + + $matches = null; + if (preg_match('/^bytes=(\d+)-(\d*)$/', $range, $matches)) { + // Note that the "Range" header specifies bytes differently than + // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1). + $begin = (int)$matches[1]; + + // The "Range" may be "200-299" or "200-", meaning "until end of file". + if (strlen($matches[2])) { + $range_end = (int)$matches[2]; + $end = $range_end + 1; + } else { + $range_end = null; + } + + $this->setHTTPResponseCode(206); + $this->setRange($begin, $range_end); + } + + return array($begin, $end); + } + } diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php index 0f1478ec5c..730e5ddc90 100644 --- a/src/applications/celerity/controller/CelerityResourceController.php +++ b/src/applications/celerity/controller/CelerityResourceController.php @@ -104,9 +104,30 @@ abstract class CelerityResourceController extends PhabricatorController { } $response = id(new AphrontFileResponse()) - ->setContent($data) - ->setMimeType($type_map[$type]) - ->setCompressResponse(true); + ->setMimeType($type_map[$type]); + + $range = AphrontRequest::getHTTPHeader('Range'); + + if (strlen($range)) { + $response->setContentLength(strlen($data)); + + list($range_begin, $range_end) = $response->parseHTTPRange($range); + + if ($range_begin !== null) { + if ($range_end !== null) { + $data = substr($data, $range_begin, ($range_end - $range_begin)); + } else { + $data = substr($data, $range_begin); + } + } + + $response->setContentIterator(array($data)); + } else { + $response + ->setContent($data) + ->setCompressResponse(true); + } + // NOTE: This is a piece of magic required to make WOFF fonts work in // Firefox and IE. Possibly we should generalize this more. diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index c98c05e27b..c8bfcc488a 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -62,24 +62,8 @@ final class PhabricatorFileDataController extends PhabricatorFileController { // an initial request for bytes 0-1 of the audio file, and things go south // if we can't respond with a 206 Partial Content. $range = $request->getHTTPHeader('range'); - if ($range) { - $matches = null; - if (preg_match('/^bytes=(\d+)-(\d*)$/', $range, $matches)) { - // Note that the "Range" header specifies bytes differently than - // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1). - $begin = (int)$matches[1]; - - // The "Range" may be "200-299" or "200-", meaning "until end of file". - if (strlen($matches[2])) { - $range_end = (int)$matches[2]; - $end = $range_end + 1; - } else { - $range_end = null; - } - - $response->setHTTPResponseCode(206); - $response->setRange($begin, $range_end); - } + if (strlen($range)) { + list($begin, $end) = $response->parseHTTPRange($range); } $is_viewable = $file->isViewableInBrowser(); From df7f56d8e3b750451747c93c9b2b6ca9514b4698 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 18 Apr 2017 13:37:52 -0700 Subject: [PATCH 30/56] Minor CSS tweaks Conpherence Summary: Minor pixel shifts with new header ui in place. Test Plan: Desktop, Mobile, Tablet, with and without search and participants open Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17725 --- resources/celerity/map.php | 10 +++++----- .../rsrc/css/application/conpherence/header-pane.css | 2 +- .../css/application/conpherence/message-pane.css | 12 +++++------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 02db832ff4..0f9ab79d43 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'a34d59bd', + 'conpherence.pkg.css' => '3776e82d', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', 'core.pkg.js' => '47a69358', @@ -47,9 +47,9 @@ return array( 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', - 'rsrc/css/application/conpherence/header-pane.css' => '6b2dadbe', + 'rsrc/css/application/conpherence/header-pane.css' => 'a1104b93', 'rsrc/css/application/conpherence/menu.css' => '88100764', - 'rsrc/css/application/conpherence/message-pane.css' => '14199428', + 'rsrc/css/application/conpherence/message-pane.css' => 'b0f55ecc', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', 'rsrc/css/application/conpherence/participant-pane.css' => '26a3ce56', 'rsrc/css/application/conpherence/transaction.css' => '85129c68', @@ -556,9 +556,9 @@ return array( 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', 'conpherence-durable-column-view' => '89ea6bef', - 'conpherence-header-pane-css' => '6b2dadbe', + 'conpherence-header-pane-css' => 'a1104b93', 'conpherence-menu-css' => '88100764', - 'conpherence-message-pane-css' => '14199428', + 'conpherence-message-pane-css' => 'b0f55ecc', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '26a3ce56', 'conpherence-thread-manager' => '4d863052', diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 56ec7ce130..6a61f94c08 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -3,7 +3,7 @@ */ .conpherence-header-pane { - background-color: #f9f9f9; + background-color: {$lightbluebackground}; } .conpherence-header-pane .phui-header-shell { diff --git a/webroot/rsrc/css/application/conpherence/message-pane.css b/webroot/rsrc/css/application/conpherence/message-pane.css index f6e23db9e8..ab3c55ba22 100644 --- a/webroot/rsrc/css/application/conpherence/message-pane.css +++ b/webroot/rsrc/css/application/conpherence/message-pane.css @@ -9,7 +9,7 @@ position: fixed; left: 240px; right: 240px; - top: 102px; + top: 106px; bottom: 0px; min-width: 300px; width: auto; @@ -20,8 +20,6 @@ .device .loading .messages-loading-icon, .device .conpherence-layout .conpherence-no-threads { left: 0; - right: 0; - width: 100%; } .conpherence-layout .conpherence-content-pane .conpherence-no-threads { @@ -57,7 +55,7 @@ position: fixed; left: 240px; right: 240px; - top: 104px; + top: 106px; bottom: 142px; overflow-x: hidden; overflow-y: auto; @@ -360,7 +358,7 @@ body .conpherence-message-pane .aphront-form-control { .device .conpherence-message-pane .remarkup-assist-textarea { margin: 0; - padding: 7px 8px 6px 30px; + padding: 7px 8px 6px 38px; width: 100%; height: 34px; resize: none; @@ -447,9 +445,9 @@ body .conpherence-message-pane .aphront-form-control { .show-searchbar .conpherence-search-form-view { display: block; height: 54px; - background: {$lightbluebackground}; + background: #fff; position: absolute; - top: 1px; + top: 0; left: 0; right: 0; } From f880000eb0cd25b4b8c708aec24fed89d51d1542 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 08:56:35 -0700 Subject: [PATCH 31/56] Stem fulltext tokens before filtering them for stopwords Summary: Fixes T12596. A query for a token (like "having") which stems to a stopword (like "have") currently survives filtering. Stem it first so it gets caught. Also, for InnoDB, a custom stopword table can be configured. If it is, read that instead of the default stopword list (I configured it locally, but the default list is reasonable so we never formally recommended installs configure it). Test Plan: Queried for words that stem to stopwords, saw them filtered: {F4915843} Queried for the original problem query and saw "having" caught with "have" in the stopword list: {F4915844} Fiddled with local InnoDB stopword table config and saw the stopword list get loaded correctly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12596 Differential Revision: https://secure.phabricator.com/D17728 --- .../PhabricatorMySQLFulltextStorageEngine.php | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index 5bb9943e59..b22076cf92 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -228,6 +228,13 @@ final class PhabricatorMySQLFulltextStorageEngine $fulltext_tokens[$key] = $fulltext_token; $value = $token->getValue(); + + // If the value is unquoted, we'll stem it in the query, so stem it + // here before performing filtering tests. See T12596. + if (!$token->isQuoted()) { + $value = $stemmer->stemToken($value); + } + if (phutil_utf8_strlen($value) < $min_length) { $fulltext_token->setIsShort(true); continue; @@ -479,16 +486,32 @@ final class PhabricatorMySQLFulltextStorageEngine try { $result = queryfx_one( $conn, - 'SELECT @@innodb_ft_min_token_size innodb_max'); + 'SELECT @@innodb_ft_min_token_size innodb_max, + @@innodb_ft_server_stopword_table innodb_stopword_config'); } catch (AphrontQueryException $ex) { $result = null; } if ($result) { $min_len = $result['innodb_max']; + + $stopword_config = $result['innodb_stopword_config']; + if (preg_match('(/)', $stopword_config)) { + // If the setting is nonempty and contains a slash, query the + // table the user has configured. + $parts = explode('/', $stopword_config); + list($stopword_database, $stopword_table) = $parts; + } else { + // Otherwise, query the InnoDB default stopword table. + $stopword_database = 'INFORMATION_SCHEMA'; + $stopword_table = 'INNODB_FT_DEFAULT_STOPWORD'; + } + $stopwords = queryfx_all( $conn, - 'SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD'); + 'SELECT * FROM %T.%T', + $stopword_database, + $stopword_table); $stopwords = ipull($stopwords, 'value'); $stopwords = array_fuse($stopwords); From 305966e748ba4a393804ad552455d2f4003c55b4 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 19 Apr 2017 09:25:59 -0700 Subject: [PATCH 32/56] Fixing of the typos Test Plan: doitlive Reviewers: epriestley, chad Reviewed By: epriestley, chad Subscribers: cspeckmim, Korvin Differential Revision: https://secure.phabricator.com/D17727 --- src/docs/contributor/database.diviner | 24 +++++++++---------- src/docs/flavor/project_history.diviner | 6 ++--- .../flavor/things_you_should_do_now.diviner | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/docs/contributor/database.diviner b/src/docs/contributor/database.diviner index 59a7dc2b2c..aaea485dc6 100644 --- a/src/docs/contributor/database.diviner +++ b/src/docs/contributor/database.diviner @@ -10,7 +10,7 @@ Database System Phabricator uses MySQL or another MySQL-compatible database (like MariaDB or Amazon RDS). -Phabricator the InnoDB table engine. The only exception is the +Phabricator uses the InnoDB table engine. The only exception is the `search_documentfield` table which uses MyISAM because MySQL doesn't support fulltext search in InnoDB (recent versions do, but we haven't added support yet). @@ -102,7 +102,7 @@ An example of such usage can be found in column Primary Keys ============ -Most tables have auto-increment column named `id`. Adding an ID column is +Most tables have an auto-increment column named `id`. Adding an ID column is appropriate for most tables (even tables that have another natural unique key), as it improves consistency and makes it easier to perform generic operations on objects. @@ -134,12 +134,12 @@ eventually, but there isn't a strong case for them at the present time. PHIDs ===== -Each globally referencable object in Phabricator has its associated PHID +Each globally referencable object in Phabricator has an associated PHID ("Phabricator ID") which serves as a global identifier, similar to a GUID. We use PHIDs for referencing data in different databases. -We use both autoincrementing IDs and global PHIDs because each is useful in -different contexts. Autoincrementing IDs are meaningfully ordered and allow +We use both auto-incrementing IDs and global PHIDs because each is useful in +different contexts. Auto-incrementing IDs are meaningfully ordered and allow us to construct short, human-readable object names (like `D2258`) and URIs. Global PHIDs allow us to represent relationships between different types of objects in a homogeneous way. @@ -154,7 +154,7 @@ Transactions ============ Transactional code should be written using transactions. Example of such code is -inserting multiple records where one doesn't make sense without the other or +inserting multiple records where one doesn't make sense without the other, or selecting data later used for update. See chapter in @{class:LiskDAO}. Advanced Features @@ -195,12 +195,12 @@ set names: | Variable | Meaning | Notes | |---|---|---| -| {$NAMESPACE} | Storage Namespace | Defaults to `phabricator` | -| {$CHARSET} | Default Charset | Mostly used to specify table charset | -| {$COLLATE_TEXT} | Text Collation | For most text (case-sensitive) | -| {$COLLATE_SORT} | Sort Collation | For sortable text (case-insensitive) | -| {$CHARSET_FULLTEXT} | Fulltext Charset | Specify explicitly for fulltext | -| {$COLLATE_FULLTEXT} | Fulltext Collate | Specify explicitly for fulltext | +| `{$NAMESPACE}` | Storage Namespace | Defaults to `phabricator` | +| `{$CHARSET}` | Default Charset | Mostly used to specify table charset | +| `{$COLLATE_TEXT}` | Text Collation | For most text (case-sensitive) | +| `{$COLLATE_SORT}` | Sort Collation | For sortable text (case-insensitive) | +| `{$CHARSET_FULLTEXT}` | Fulltext Charset | Specify explicitly for fulltext | +| `{$COLLATE_FULLTEXT}` | Fulltext Collate | Specify explicitly for fulltext | **Test your patch**. Run `bin/storage upgrade` to test your patch. diff --git a/src/docs/flavor/project_history.diviner b/src/docs/flavor/project_history.diviner index bfdbe2682e..c3b5363d50 100644 --- a/src/docs/flavor/project_history.diviner +++ b/src/docs/flavor/project_history.diviner @@ -52,9 +52,9 @@ problems. I joined the new Dev Tools team around February 2010 and took over Diffcamp. I renamed it to Differential, moved it to a new Alite-based infrastructure with Javelin, and started making it somewhat less terrible. I eventually wrote -Diffusion and build Herald to replace a very difficult-to-use predecessor. These +Diffusion and built Herald to replace a very difficult-to-use predecessor. These tools were less negatively received than the older versions. By December 2010 I started open sourcing them; Haste became //Celerity// and Alite became //Aphront//. I wrote Maniphest to track open issues with the project in January -or February and we open sourced Phabricator in late April, shortly after I left -Facebook. +or February, left Facebook in April, and shortly after, we open sourced +Phabricator. diff --git a/src/docs/flavor/things_you_should_do_now.diviner b/src/docs/flavor/things_you_should_do_now.diviner index b4681bd0ca..0d3b4135ba 100644 --- a/src/docs/flavor/things_you_should_do_now.diviner +++ b/src/docs/flavor/things_you_should_do_now.diviner @@ -134,5 +134,5 @@ query escaping system the rest of the application does. Hopefully, whatever language you're writing in has good query libraries that can handle escaping for you. If so, use them. If you're using PHP and don't have -a solution in place yet, the Phabricator implementation of qsprintf() is similar -to Facebook's system and was successful there. +a solution in place yet, the Phabricator implementation of `qsprintf()` is +similar to Facebook's system and was successful there. From 8e813f4f1f59da832471f1f962d242a17050052a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Apr 2017 11:12:56 -0700 Subject: [PATCH 33/56] Add some basic sound preferences Summary: Ref T7567. This adds some constants (for adding new sounds), global setting for turning on and off sound (setting) and per thread preference for sound choice. Also specc'd out Mentions, if added. Test Plan: I tested all the preference wiring, but need to set up notifications locally to verify if this works. Feel free to test. Reviewers: epriestley Reviewed By: epriestley Subscribers: amckinley, Korvin Maniphest Tasks: T7567 Differential Revision: https://secure.phabricator.com/D17726 --- src/__phutil_library_map__.php | 4 + .../constants/ConpherenceRoomSettings.php | 31 +++++++ .../ConpherenceUpdateController.php | 88 +++++++++++++++---- .../PhabricatorConpherenceSoundSetting.php | 81 +++++++++++++++++ 4 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 src/applications/conpherence/constants/ConpherenceRoomSettings.php create mode 100644 src/applications/settings/setting/PhabricatorConpherenceSoundSetting.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d5a7bfe95e..8a333594bb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -311,6 +311,7 @@ phutil_register_library_map(array( 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', 'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php', 'ConpherenceRoomPictureController' => 'applications/conpherence/controller/ConpherenceRoomPictureController.php', + 'ConpherenceRoomSettings' => 'applications/conpherence/constants/ConpherenceRoomSettings.php', 'ConpherenceRoomTestCase' => 'applications/conpherence/__tests__/ConpherenceRoomTestCase.php', 'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php', 'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php', @@ -2398,6 +2399,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceNotificationsSetting' => 'applications/settings/setting/PhabricatorConpherenceNotificationsSetting.php', 'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php', 'PhabricatorConpherenceProfileMenuItem' => 'applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php', + 'PhabricatorConpherenceSoundSetting' => 'applications/settings/setting/PhabricatorConpherenceSoundSetting.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', 'PhabricatorConpherenceWidgetVisibleSetting' => 'applications/settings/setting/PhabricatorConpherenceWidgetVisibleSetting.php', 'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php', @@ -5103,6 +5105,7 @@ phutil_register_library_map(array( 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', 'ConpherenceRoomListController' => 'ConpherenceController', 'ConpherenceRoomPictureController' => 'ConpherenceController', + 'ConpherenceRoomSettings' => 'ConpherenceConstants', 'ConpherenceRoomTestCase' => 'ConpherenceTestCase', 'ConpherenceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'ConpherenceTestCase' => 'PhabricatorTestCase', @@ -7505,6 +7508,7 @@ phutil_register_library_map(array( 'PhabricatorConpherenceNotificationsSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', 'PhabricatorConpherenceProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorConpherenceSoundSetting' => 'PhabricatorSelectSetting', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConpherenceWidgetVisibleSetting' => 'PhabricatorInternalSetting', 'PhabricatorConsoleApplication' => 'PhabricatorApplication', diff --git a/src/applications/conpherence/constants/ConpherenceRoomSettings.php b/src/applications/conpherence/constants/ConpherenceRoomSettings.php new file mode 100644 index 0000000000..18a621d34b --- /dev/null +++ b/src/applications/conpherence/constants/ConpherenceRoomSettings.php @@ -0,0 +1,31 @@ + array( + 'name' => pht('No Sound'), + 'rsrc' => '', + ), + 'tap' => array( + 'name' => pht('Tap'), + 'rsrc' => celerity_get_resource_uri('/rsrc/audio/basic/tap.mp3'), + ), + ); + } + + public static function getDropdownSoundMap() { + $map = self::getSoundMap(); + return ipull($map, 'name'); + } + + +} diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index f1af58fb74..7b41d5bb2d 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -115,11 +115,13 @@ final class ConpherenceUpdateController break; case ConpherenceUpdateActions::NOTIFICATIONS: $notifications = $request->getStr('notifications'); + $sounds = $request->getArr('sounds'); $participant = $conpherence->getParticipantIfExists($user->getPHID()); if (!$participant) { return id(new Aphront404Response()); } $participant->setSettings(array('notifications' => $notifications)); + $participant->setSettings(array('sounds' => $sounds)); $participant->save(); return id(new AphrontRedirectResponse()) ->setURI('/'.$conpherence->getMonogram()); @@ -266,29 +268,53 @@ final class ConpherenceUpdateController $notification_key = PhabricatorConpherenceNotificationsSetting::SETTINGKEY; $notification_default = $user->getUserSetting($notification_key); + $sound_key = PhabricatorConpherenceSoundSetting::SETTINGKEY; + $sound_default = $user->getUserSetting($sound_key); + $settings = $participant->getSettings(); - $notifications = idx( - $settings, - 'notifications', - $notification_default); + $notifications = idx($settings, 'notifications', $notification_default); + + $sounds = idx($settings, 'sounds', array()); + $map = PhabricatorConpherenceSoundSetting::getDefaultSound($sound_default); + $receive = idx($sounds, + ConpherenceRoomSettings::SOUND_RECEIVE, + $map[ConpherenceRoomSettings::SOUND_RECEIVE]); + $mention = idx($sounds, + ConpherenceRoomSettings::SOUND_MENTION, + $map[ConpherenceRoomSettings::SOUND_MENTION]); $form = id(new AphrontFormView()) ->setUser($user) - ->setFullWidth(true) ->appendControl( - id(new AphrontFormRadioButtonControl()) - ->addButton( + id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Notify')) + ->addButton( PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL, PhabricatorConpherenceNotificationsSetting::getSettingLabel( PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL), - '') - ->addButton( + '') + ->addButton( PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY, PhabricatorConpherenceNotificationsSetting::getSettingLabel( PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY), - '') - ->setName('notifications') - ->setValue($notifications)); + '') + ->setName('notifications') + ->setValue($notifications)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Message Received')) + ->setName('sounds['.ConpherenceRoomSettings::SOUND_RECEIVE.']') + ->setOptions(ConpherenceRoomSettings::getDropdownSoundMap()) + ->setValue($receive)); + + // TODO: Future Adventure! Expansion Pack + // + // ->appendChild( + // id(new AphrontFormSelectControl()) + // ->setLabel(pht('Username Mentioned')) + // ->setName('sounds['.ConpherenceRoomSettings::SOUND_MENTION.']') + // ->setOptions(ConpherenceRoomSettings::getDropdownSoundMap()) + // ->setValue($mention)); return id(new AphrontDialogView()) ->setTitle(pht('Room Preferences')) @@ -496,6 +522,8 @@ final class ConpherenceUpdateController ->executeOne(); $non_update = false; + $participant = $conpherence->getParticipant($user->getPHID()); + if ($need_transactions && $conpherence->getTransactions()) { $data = ConpherenceTransactionRenderer::renderTransactions( $user, @@ -503,9 +531,7 @@ final class ConpherenceUpdateController $key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY; $minimized = $user->getUserSetting($key); if (!$minimized) { - $participant_obj = $conpherence->getParticipant($user->getPHID()); - $participant_obj - ->markUpToDate($conpherence, $data['latest_transaction']); + $participant->markUpToDate($conpherence, $data['latest_transaction']); } } else if ($need_transactions) { $non_update = true; @@ -547,7 +573,9 @@ final class ConpherenceUpdateController ->setViewer($user); $dropdown_query->execute(); - $receive_sound = celerity_get_resource_uri('/rsrc/audio/basic/tap.mp3'); + $sounds = $this->getSoundForParticipant($user, $participant); + $receive_sound = $sounds[ConpherenceRoomSettings::SOUND_RECEIVE]; + $mention_sound = $sounds[ConpherenceRoomSettings::SOUND_MENTION]; $content = array( 'non_update' => $non_update, @@ -564,10 +592,38 @@ final class ConpherenceUpdateController ), 'sound' => array( 'receive' => $receive_sound, + 'mention' => $mention_sound, ), ); return $content; } + protected function getSoundForParticipant( + PhabricatorUser $user, + ConpherenceParticipant $participant) { + + $sound_key = PhabricatorConpherenceSoundSetting::SETTINGKEY; + $sound_default = $user->getUserSetting($sound_key); + + $settings = $participant->getSettings(); + $sounds = idx($settings, 'sounds', array()); + $map = PhabricatorConpherenceSoundSetting::getDefaultSound($sound_default); + + $receive = idx($sounds, + ConpherenceRoomSettings::SOUND_RECEIVE, + $map[ConpherenceRoomSettings::SOUND_RECEIVE]); + $mention = idx($sounds, + ConpherenceRoomSettings::SOUND_MENTION, + $map[ConpherenceRoomSettings::SOUND_MENTION]); + + $sound_map = ConpherenceRoomSettings::getSoundMap(); + + return array( + ConpherenceRoomSettings::SOUND_RECEIVE => $sound_map[$receive]['rsrc'], + ConpherenceRoomSettings::SOUND_MENTION => $sound_map[$mention]['rsrc'], + ); + + } + } diff --git a/src/applications/settings/setting/PhabricatorConpherenceSoundSetting.php b/src/applications/settings/setting/PhabricatorConpherenceSoundSetting.php new file mode 100644 index 0000000000..77ff6eac46 --- /dev/null +++ b/src/applications/settings/setting/PhabricatorConpherenceSoundSetting.php @@ -0,0 +1,81 @@ + + ConpherenceRoomSettings::DEFAULT_RECEIVE_SOUND, + ConpherenceRoomSettings::SOUND_MENTION => + ConpherenceRoomSettings::DEFAULT_MENTION_SOUND, + ); + break; + case self::VALUE_CONPHERENCE_MENTION: + return array( + ConpherenceRoomSettings::SOUND_RECEIVE => + ConpherenceRoomSettings::DEFAULT_NO_SOUND, + ConpherenceRoomSettings::SOUND_MENTION => + ConpherenceRoomSettings::DEFAULT_MENTION_SOUND, + ); + break; + case self::VALUE_CONPHERENCE_SILENT: + return array( + ConpherenceRoomSettings::SOUND_RECEIVE => + ConpherenceRoomSettings::DEFAULT_NO_SOUND, + ConpherenceRoomSettings::SOUND_MENTION => + ConpherenceRoomSettings::DEFAULT_NO_SOUND, + ); + break; + } + } + + private static function getOptionsMap() { + return array( + self::VALUE_CONPHERENCE_SILENT => pht('No Sounds'), + // self::VALUE_CONPHERENCE_MENTION => pht('Mentions Only'), + self::VALUE_CONPHERENCE_ALL => pht('All Messages'), + ); + } + +} From 15e7624a17c100cb394ae2b1f8d1b97d687f5bcd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Apr 2017 13:35:56 -0700 Subject: [PATCH 34/56] Add a few more sounds Summary: A few more mp3s to choose from for Conpherence. Test Plan: Test each sound in a new room. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17734 --- resources/celerity/map.php | 4 ++++ .../constants/ConpherenceRoomSettings.php | 18 +++++++++++++++++- webroot/rsrc/audio/basic/alert.mp3 | Bin 0 -> 11051 bytes webroot/rsrc/audio/basic/bing.mp3 | Bin 0 -> 9531 bytes webroot/rsrc/audio/basic/pock.mp3 | Bin 0 -> 3827 bytes webroot/rsrc/audio/basic/ting.mp3 | Bin 0 -> 14160 bytes 6 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 webroot/rsrc/audio/basic/alert.mp3 create mode 100644 webroot/rsrc/audio/basic/bing.mp3 create mode 100644 webroot/rsrc/audio/basic/pock.mp3 create mode 100644 webroot/rsrc/audio/basic/ting.mp3 diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0f9ab79d43..6016fc09cb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -19,7 +19,11 @@ return array( 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', + 'rsrc/audio/basic/alert.mp3' => '98461568', + 'rsrc/audio/basic/bing.mp3' => 'ab8603a5', + 'rsrc/audio/basic/pock.mp3' => '0cc772f5', 'rsrc/audio/basic/tap.mp3' => 'fc2fd796', + 'rsrc/audio/basic/ting.mp3' => '17660001', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/dark-console.css' => '53798a6d', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', diff --git a/src/applications/conpherence/constants/ConpherenceRoomSettings.php b/src/applications/conpherence/constants/ConpherenceRoomSettings.php index 18a621d34b..c6a18d261e 100644 --- a/src/applications/conpherence/constants/ConpherenceRoomSettings.php +++ b/src/applications/conpherence/constants/ConpherenceRoomSettings.php @@ -6,7 +6,7 @@ final class ConpherenceRoomSettings extends ConpherenceConstants { const SOUND_MENTION = 'mention'; const DEFAULT_RECEIVE_SOUND = 'tap'; - const DEFAULT_MENTION_SOUND = 'tap'; // Upload a new sound + const DEFAULT_MENTION_SOUND = 'alert'; const DEFAULT_NO_SOUND = 'none'; public static function getSoundMap() { @@ -15,10 +15,26 @@ final class ConpherenceRoomSettings extends ConpherenceConstants { 'name' => pht('No Sound'), 'rsrc' => '', ), + 'alert' => array( + 'name' => pht('Alert'), + 'rsrc' => celerity_get_resource_uri('/rsrc/audio/basic/alert.mp3'), + ), + 'bing' => array( + 'name' => pht('Bing'), + 'rsrc' => celerity_get_resource_uri('/rsrc/audio/basic/bing.mp3'), + ), + 'pock' => array( + 'name' => pht('Pock'), + 'rsrc' => celerity_get_resource_uri('/rsrc/audio/basic/pock.mp3'), + ), 'tap' => array( 'name' => pht('Tap'), 'rsrc' => celerity_get_resource_uri('/rsrc/audio/basic/tap.mp3'), ), + 'ting' => array( + 'name' => pht('Ting'), + 'rsrc' => celerity_get_resource_uri('/rsrc/audio/basic/ting.mp3'), + ), ); } diff --git a/webroot/rsrc/audio/basic/alert.mp3 b/webroot/rsrc/audio/basic/alert.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f2965683816ac3fcaaecf1ac91ce64d29db0ae9e GIT binary patch literal 11051 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 L*MBSXKlT3~NPLYg literal 0 HcmV?d00001 diff --git a/webroot/rsrc/audio/basic/pock.mp3 b/webroot/rsrc/audio/basic/pock.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..009139bf634e8d53826dc4f689202648a6685388 GIT binary patch literal 3827 zcmezWdx8rC0T7Xymkw0I55y7-41Av%%z(fP2qJ+X83?k0pcn|ML7)i;x`1F35X=IC zML@6$1U3S}P9Qi41Sf&uA`sjFfd@eF0th|;!4Dw#4+KEF0(=~OU5)iDEsI!o!dz&o zpu#lQmx1vC$Q_#(G2Mkp3|spD?*l0e3`|S{esi-KIDoP6VF3f92v57^&bJK?3=H;% zfj;NeV_-PgY@p!aaM~=+^j$NXXW)UJn#%d}o*&n|m?tyif`ip`zAXwT*w)LhU~Aej z=e%u5@9CFsZ?oSE{v*I*v-=>>492VbcKrhy$iQ$QMZ8DE$%{#V$*Fz@b8}!D$3lsz zB9@H~G7@s^Kbs^NXXN!Z&hv1`&766^4S;N4f#lXxU_HL>JLj=YEZX_hldHGZZ%S#-fogDOU0CIK6hqYj(+Wt{U8b_JN_z4k19x+C4X z&eFH+dTEqu{jR&;zZp-9j9Rv3@#-++>X)mtt?p{g$~S}AU-#GkZ~es%=KQamXO3Pt zaC{DTk5SrLbpeq}cO)Jjy=>&zQ^sknnsWNsW)5cVwh28Gw>gB!y>YmBbkCC&;NQ?TcEB|jPJ6)R%H2Lp+An~&|_vTmQ`il|Mr?PnT15>=^ZEl{qY9fw{ zqL`McTv5>G6W~{2$^FdYq<7kCz0JSXIj!$zDkd0De|63DwWgx%mfMpa_84)d+5^o3 zf!qK8|B~u_7%ku-?3+2obph85jdXq1xjZR~93m?;98?W+#VvhK#2o*Yzc#B>`|tM` zyT0vxS-%{j`Ty%T_qSD@O?mfBNqOm;RL?2NexXV-*5}yN6Am!grY+u4tT)l3lBtSu ziHq8)D65PAzb?AEX_DW(>G}UJ|6cz7|Ki_SAl-G=(e=D5IuXX?@?FS5u+#B*$*A|>k04D7N z3=Aybl7j>S$#@cUp(>@T8UKF=q`YPPlqBO-?J1)H%HP1e$R+w*dSydQ2jM`Vt6Ncn z355rD$N%37DPQ_ObyOZXxj@eV>K~w&{sFVF0@OdIC|XJ4!0o|p#{b^}E_eHZ!C2qG z04Xtn)}y(Apq-=kKvLppxIx+=z~TxQ7)@_jLS{7ojOHIK$pV`YRq_uugVDt3Xio~o S^$(icv2jQJgDq4Diva-7h~(A) literal 0 HcmV?d00001 diff --git a/webroot/rsrc/audio/basic/ting.mp3 b/webroot/rsrc/audio/basic/ting.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..311e57642b75a8c032ce84b98a7fc2bf228c0cef GIT binary patch literal 14160 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 M5;`yX|Ad4816&p-0ssI2 literal 0 HcmV?d00001 From c96e17f5ea4230ab26688e818e8f957ceadd7ae1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 12:01:46 -0700 Subject: [PATCH 35/56] Remove "behindTransactionPHID" from ConpherenceParticipant Summary: Pathway to D17685. Nothing reads this field and it has no use or value. Test Plan: - Ran migration. - Grepped for `behindTransactionPHID`. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17729 --- resources/sql/autopatches/20170419.thread.01.behind.sql | 2 ++ src/applications/conpherence/editor/ConpherenceEditor.php | 7 +------ .../conpherence/storage/ConpherenceParticipant.php | 2 -- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 resources/sql/autopatches/20170419.thread.01.behind.sql diff --git a/resources/sql/autopatches/20170419.thread.01.behind.sql b/resources/sql/autopatches/20170419.thread.01.behind.sql new file mode 100644 index 0000000000..08d27337fd --- /dev/null +++ b/resources/sql/autopatches/20170419.thread.01.behind.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_participant + DROP behindTransactionPHID; diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index c53aab7655..3a870f935b 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -168,8 +168,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { // participation data asap to pass policy checks. For existing // ConpherenceThreads, the existing participation is correct // at this stage. Note that later in applyCustomExternalTransaction - // this participation data will be updated, particularly the - // behindTransactionPHID which is just a generated dummy for now. + // this participation data will be updated. $participants = array(); $phids = $this->getPHIDTransactionNewValue($xaction, array()); foreach ($phids as $phid) { @@ -186,7 +185,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { ->setParticipantPHID($phid) ->setParticipationStatus($status) ->setDateTouched(time()) - ->setBehindTransactionPHID($xaction->generatePHID()) ->setSeenMessageCount($message_count) ->save(); $object->attachParticipants($participants); @@ -257,7 +255,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { ->setParticipantPHID($phid) ->setParticipationStatus($status) ->setDateTouched(time()) - ->setBehindTransactionPHID($xaction->getPHID()) ->setSeenMessageCount($message_count) ->save(); } @@ -290,7 +287,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { foreach ($participants as $phid => $participant) { if ($phid != $user->getPHID()) { if ($participant->getParticipationStatus() != $behind) { - $participant->setBehindTransactionPHID($xaction_phid); $participant->setSeenMessageCount( $object->getMessageCount() - $message_count); } @@ -298,7 +294,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $participant->setDateTouched($time); } else { $participant->setSeenMessageCount($object->getMessageCount()); - $participant->setBehindTransactionPHID($xaction_phid); $participant->setParticipationStatus($up_to_date); $participant->setDateTouched($time); } diff --git a/src/applications/conpherence/storage/ConpherenceParticipant.php b/src/applications/conpherence/storage/ConpherenceParticipant.php index 32c3fb38e1..ddce989e3f 100644 --- a/src/applications/conpherence/storage/ConpherenceParticipant.php +++ b/src/applications/conpherence/storage/ConpherenceParticipant.php @@ -5,7 +5,6 @@ final class ConpherenceParticipant extends ConpherenceDAO { protected $participantPHID; protected $conpherencePHID; protected $participationStatus; - protected $behindTransactionPHID; protected $seenMessageCount; protected $dateTouched; protected $settings = array(); @@ -44,7 +43,6 @@ final class ConpherenceParticipant extends ConpherenceDAO { ConpherenceTransaction $xaction) { if (!$this->isUpToDate($conpherence)) { $this->setParticipationStatus(ConpherenceParticipationStatus::UP_TO_DATE); - $this->setBehindTransactionPHID($xaction->getPHID()); $this->setSeenMessageCount($conpherence->getMessageCount()); $this->save(); From 0a335f91cdfbf85fd89e26d933c412424ccc581d Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 12:17:37 -0700 Subject: [PATCH 36/56] Remove "participationStatus" from ConpherenceParticipant Summary: Pathway to D17685. This column is a very complicated cache of: is participant.messageCount equal to thread.messageCount? We can just ask this question with a JOIN instead and simplify things dramatically. Test Plan: - Ran migration. - Browsed around. - Sent a message, saw unread count go up. - Read the message, saw unread count go down. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17730 --- .../autopatches/20170419.thread.02.status.sql | 2 + src/__phutil_library_map__.php | 2 - .../ConpherenceParticipationStatus.php | 8 --- ...ConpherenceNotificationPanelController.php | 5 +- .../conpherence/editor/ConpherenceEditor.php | 12 +--- .../ConpherenceParticipantCountQuery.php | 64 +++++++++---------- .../storage/ConpherenceParticipant.php | 16 ++--- .../PhabricatorUserMessageCountCacheType.php | 3 +- 8 files changed, 41 insertions(+), 71 deletions(-) create mode 100644 resources/sql/autopatches/20170419.thread.02.status.sql delete mode 100644 src/applications/conpherence/constants/ConpherenceParticipationStatus.php diff --git a/resources/sql/autopatches/20170419.thread.02.status.sql b/resources/sql/autopatches/20170419.thread.02.status.sql new file mode 100644 index 0000000000..5f854a4b96 --- /dev/null +++ b/resources/sql/autopatches/20170419.thread.02.status.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_participant + DROP participationStatus; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8a333594bb..33481ee880 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -305,7 +305,6 @@ phutil_register_library_map(array( 'ConpherenceParticipantCountQuery' => 'applications/conpherence/query/ConpherenceParticipantCountQuery.php', 'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php', 'ConpherenceParticipantView' => 'applications/conpherence/view/ConpherenceParticipantView.php', - 'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php', 'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php', 'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php', 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', @@ -5099,7 +5098,6 @@ phutil_register_library_map(array( 'ConpherenceParticipantCountQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceParticipantView' => 'AphrontView', - 'ConpherenceParticipationStatus' => 'ConpherenceConstants', 'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', diff --git a/src/applications/conpherence/constants/ConpherenceParticipationStatus.php b/src/applications/conpherence/constants/ConpherenceParticipationStatus.php deleted file mode 100644 index e8f22620cb..0000000000 --- a/src/applications/conpherence/constants/ConpherenceParticipationStatus.php +++ /dev/null @@ -1,8 +0,0 @@ -getUser(); $conpherences = array(); require_celerity_resource('conpherence-notification-css'); - $unread_status = ConpherenceParticipationStatus::BEHIND; $participant_data = id(new ConpherenceParticipantQuery()) ->withParticipantPHIDs(array($user->getPHID())) @@ -37,7 +36,7 @@ final class ConpherenceNotificationPanelController 'conpherence-notification', ); - if ($p_data->getParticipationStatus() == $unread_status) { + if (!$p_data->isUpToDate($conpherence)) { $classes[] = 'phabricator-notification-unread'; } $uri = $this->getApplicationURI($conpherence->getID().'/'); @@ -95,7 +94,7 @@ final class ConpherenceNotificationPanelController $unread = id(new ConpherenceParticipantCountQuery()) ->withParticipantPHIDs(array($user->getPHID())) - ->withParticipationStatus($unread_status) + ->withUnread(true) ->execute(); $unread_count = idx($unread, $user->getPHID(), 0); diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 3a870f935b..6668b3324f 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -173,17 +173,14 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $phids = $this->getPHIDTransactionNewValue($xaction, array()); foreach ($phids as $phid) { if ($phid == $this->getActor()->getPHID()) { - $status = ConpherenceParticipationStatus::UP_TO_DATE; $message_count = 1; } else { - $status = ConpherenceParticipationStatus::BEHIND; $message_count = 0; } $participants[$phid] = id(new ConpherenceParticipant()) ->setConpherencePHID($object->getPHID()) ->setParticipantPHID($phid) - ->setParticipationStatus($status) ->setDateTouched(time()) ->setSeenMessageCount($message_count) ->save(); @@ -243,17 +240,14 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $add = array_keys(array_diff_key($new_map, $old_map)); foreach ($add as $phid) { if ($phid == $this->getActor()->getPHID()) { - $status = ConpherenceParticipationStatus::UP_TO_DATE; $message_count = $object->getMessageCount(); } else { - $status = ConpherenceParticipationStatus::BEHIND; $message_count = 0; } $participants[$phid] = id(new ConpherenceParticipant()) ->setConpherencePHID($object->getPHID()) ->setParticipantPHID($phid) - ->setParticipationStatus($status) ->setDateTouched(time()) ->setSeenMessageCount($message_count) ->save(); @@ -279,22 +273,18 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { // update everyone's participation status on a message -only- $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) { + if ($participant->isUpToDate($object)) { $participant->setSeenMessageCount( $object->getMessageCount() - $message_count); } - $participant->setParticipationStatus($behind); $participant->setDateTouched($time); } else { $participant->setSeenMessageCount($object->getMessageCount()); - $participant->setParticipationStatus($up_to_date); $participant->setDateTouched($time); } $participant->save(); diff --git a/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php b/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php index 7a4f170c3a..268af0ccf1 100644 --- a/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php +++ b/src/applications/conpherence/query/ConpherenceParticipantCountQuery.php @@ -1,73 +1,69 @@ withParticipantPHIDs(array($my_phid)) - * ->withParticipationStatus(ConpherenceParticipationStatus::BEHIND) - * ->execute(); - */ final class ConpherenceParticipantCountQuery extends PhabricatorOffsetPagedQuery { private $participantPHIDs; - private $participationStatus; + private $unread; public function withParticipantPHIDs(array $phids) { $this->participantPHIDs = $phids; return $this; } - public function withParticipationStatus($participation_status) { - $this->participationStatus = $participation_status; + public function withUnread($unread) { + $this->unread = $unread; return $this; } public function execute() { + $thread = new ConpherenceThread(); $table = new ConpherenceParticipant(); - $conn_r = $table->establishConnection('r'); + $conn = $table->establishConnection('r'); $rows = queryfx_all( - $conn_r, - 'SELECT COUNT(*) as count, participantPHID '. - 'FROM %T participant %Q %Q %Q', + $conn, + 'SELECT COUNT(*) as count, participantPHID + FROM %T participant JOIN %T thread + ON participant.conpherencePHID = thread.phid %Q %Q %Q', $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildGroupByClause($conn_r), - $this->buildLimitClause($conn_r)); + $thread->getTableName(), + $this->buildWhereClause($conn), + $this->buildGroupByClause($conn), + $this->buildLimitClause($conn)); return ipull($rows, 'count', 'participantPHID'); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - if ($this->participantPHIDs) { + if ($this->participantPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'participantPHID IN (%Ls)', + $conn, + 'participant.participantPHID IN (%Ls)', $this->participantPHIDs); } - if ($this->participationStatus !== null) { - $where[] = qsprintf( - $conn_r, - 'participationStatus = %d', - $this->participationStatus); + if ($this->unread !== null) { + if ($this->unread) { + $where[] = qsprintf( + $conn, + 'participant.seenMessageCount < thread.messageCount'); + } else { + $where[] = qsprintf( + $conn, + 'participant.seenMessageCount >= thread.messageCount'); + } } return $this->formatWhereClause($where); } - private function buildGroupByClause(AphrontDatabaseConnection $conn_r) { - $group_by = qsprintf( - $conn_r, + private function buildGroupByClause(AphrontDatabaseConnection $conn) { + return qsprintf( + $conn, 'GROUP BY participantPHID'); - - return $group_by; } } diff --git a/src/applications/conpherence/storage/ConpherenceParticipant.php b/src/applications/conpherence/storage/ConpherenceParticipant.php index ddce989e3f..cb4f7f401d 100644 --- a/src/applications/conpherence/storage/ConpherenceParticipant.php +++ b/src/applications/conpherence/storage/ConpherenceParticipant.php @@ -4,7 +4,6 @@ final class ConpherenceParticipant extends ConpherenceDAO { protected $participantPHID; protected $conpherencePHID; - protected $participationStatus; protected $seenMessageCount; protected $dateTouched; protected $settings = array(); @@ -15,7 +14,6 @@ final class ConpherenceParticipant extends ConpherenceDAO { 'settings' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( - 'participationStatus' => 'uint32', 'dateTouched' => 'epoch', 'seenMessageCount' => 'uint64', ), @@ -24,12 +22,12 @@ final class ConpherenceParticipant extends ConpherenceDAO { 'columns' => array('conpherencePHID', 'participantPHID'), 'unique' => true, ), - 'unreadCount' => array( - 'columns' => array('participantPHID', 'participationStatus'), - ), 'participationIndex' => array( 'columns' => array('participantPHID', 'dateTouched', 'id'), ), + 'key_thread' => array( + 'columns' => array('participantPHID', 'conpherencePHID'), + ), ), ) + parent::getConfiguration(); } @@ -41,8 +39,8 @@ final class ConpherenceParticipant extends ConpherenceDAO { public function markUpToDate( ConpherenceThread $conpherence, ConpherenceTransaction $xaction) { + if (!$this->isUpToDate($conpherence)) { - $this->setParticipationStatus(ConpherenceParticipationStatus::UP_TO_DATE); $this->setSeenMessageCount($conpherence->getMessageCount()); $this->save(); @@ -55,11 +53,7 @@ final class ConpherenceParticipant extends ConpherenceDAO { } public function isUpToDate(ConpherenceThread $conpherence) { - return - ($this->getSeenMessageCount() == $conpherence->getMessageCount()) - && - ($this->getParticipationStatus() == - ConpherenceParticipationStatus::UP_TO_DATE); + return ($this->getSeenMessageCount() == $conpherence->getMessageCount()); } } diff --git a/src/applications/people/cache/PhabricatorUserMessageCountCacheType.php b/src/applications/people/cache/PhabricatorUserMessageCountCacheType.php index b6ff7b7263..1c253e19ab 100644 --- a/src/applications/people/cache/PhabricatorUserMessageCountCacheType.php +++ b/src/applications/people/cache/PhabricatorUserMessageCountCacheType.php @@ -28,10 +28,9 @@ final class PhabricatorUserMessageCountCacheType $user_phids = mpull($users, 'getPHID'); - $unread_status = ConpherenceParticipationStatus::BEHIND; $unread = id(new ConpherenceParticipantCountQuery()) ->withParticipantPHIDs($user_phids) - ->withParticipationStatus($unread_status) + ->withUnread(true) ->execute(); $empty = array_fill_keys($user_phids, 0); From 76d0b67d91cf33aef2469ab291b531c9e9eabbf1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 12:34:30 -0700 Subject: [PATCH 37/56] Remove "dateTouched" from ConpherenceParticipant Summary: Pathway to D17685. This column is (mostly) a denormalization of `dateModified` on the thread. Just use a JOIN instead. This isn't //exactly// the same: we'll bump threads to the top now for non-message changes (e.g., a topic or title change). That seems fine, but we could put a `lastMessageDate` on Thread later if we want to refine it. Also got rid of a lot of other unused stuff. There's a big garbage TODO here, I'll fix that in the next change. Test Plan: - Grepped for `dateTouched`. - Grepped for `participantCursor`. - Grepped for `ConpherenceParticipantQuery::LIMIT`. - Looked for callsites to `setOrder()`, found none. - Added a message to an older thread, saw it bump up to the top. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17731 --- .../20170419.thread.03.touched.sql | 2 + .../conpherence/editor/ConpherenceEditor.php | 4 - .../query/ConpherenceParticipantQuery.php | 119 +++++------------- .../storage/ConpherenceParticipant.php | 5 - 4 files changed, 30 insertions(+), 100 deletions(-) create mode 100644 resources/sql/autopatches/20170419.thread.03.touched.sql diff --git a/resources/sql/autopatches/20170419.thread.03.touched.sql b/resources/sql/autopatches/20170419.thread.03.touched.sql new file mode 100644 index 0000000000..f6fee00272 --- /dev/null +++ b/resources/sql/autopatches/20170419.thread.03.touched.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_participant + DROP dateTouched; diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 6668b3324f..59d854cdc3 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -181,7 +181,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { id(new ConpherenceParticipant()) ->setConpherencePHID($object->getPHID()) ->setParticipantPHID($phid) - ->setDateTouched(time()) ->setSeenMessageCount($message_count) ->save(); $object->attachParticipants($participants); @@ -248,7 +247,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { id(new ConpherenceParticipant()) ->setConpherencePHID($object->getPHID()) ->setParticipantPHID($phid) - ->setDateTouched(time()) ->setSeenMessageCount($message_count) ->save(); } @@ -282,10 +280,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $participant->setSeenMessageCount( $object->getMessageCount() - $message_count); } - $participant->setDateTouched($time); } else { $participant->setSeenMessageCount($object->getMessageCount()); - $participant->setDateTouched($time); } $participant->save(); } diff --git a/src/applications/conpherence/query/ConpherenceParticipantQuery.php b/src/applications/conpherence/query/ConpherenceParticipantQuery.php index bb879be4e4..e538ded2c2 100644 --- a/src/applications/conpherence/query/ConpherenceParticipantQuery.php +++ b/src/applications/conpherence/query/ConpherenceParticipantQuery.php @@ -1,128 +1,65 @@ 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}. - */ final class ConpherenceParticipantQuery extends PhabricatorOffsetPagedQuery { - const LIMIT = 100; - const ORDER_NEWER = 'newer'; - const ORDER_OLDER = 'older'; - private $participantPHIDs; - private $participantCursor; - private $order = self::ORDER_OLDER; public function withParticipantPHIDs(array $phids) { $this->participantPHIDs = $phids; return $this; } - public function withParticipantCursor(ConpherenceParticipant $participant) { - $this->participantCursor = $participant; - return $this; - } - - public function setOrder($order) { - $this->order = $order; - return $this; - } - public function execute() { $table = new ConpherenceParticipant(); - $conn_r = $table->establishConnection('r'); + $thread = new ConpherenceThread(); + + $conn = $table->establishConnection('r'); $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T participant %Q %Q %Q', + $conn, + 'SELECT * FROM %T participant JOIN %T thread + ON participant.conpherencePHID = thread.phid %Q %Q %Q', $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + $thread->getTableName(), + $this->buildWhereClause($conn), + $this->buildOrderClause($conn), + $this->buildLimitClause($conn)); $participants = $table->loadAllFromArray($data); - $participants = mpull($participants, null, 'getConpherencePHID'); - - if ($this->order == self::ORDER_NEWER) { - $participants = array_reverse($participants); + // TODO: Fix this, it's bogus. + if ('garbage') { + if (count($this->participantPHIDs) !== 1) { + throw new Exception( + pht( + 'This query only works when querying for exactly one participant '. + 'PHID!')); + } + // This will throw results away if we aren't doing a query for exactly + // one participant PHID. + $participants = mpull($participants, null, 'getConpherencePHID'); } return $participants; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - if ($this->participantPHIDs) { + if ($this->participantPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'participantPHID IN (%Ls)', $this->participantPHIDs); } - 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, - '(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) { - $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; + private function buildOrderClause(AphrontDatabaseConnection $conn) { + return qsprintf( + $conn, + 'ORDER BY thread.dateModified DESC, thread.id DESC, participant.id DESC'); } } diff --git a/src/applications/conpherence/storage/ConpherenceParticipant.php b/src/applications/conpherence/storage/ConpherenceParticipant.php index cb4f7f401d..1c4178c2dc 100644 --- a/src/applications/conpherence/storage/ConpherenceParticipant.php +++ b/src/applications/conpherence/storage/ConpherenceParticipant.php @@ -5,7 +5,6 @@ final class ConpherenceParticipant extends ConpherenceDAO { protected $participantPHID; protected $conpherencePHID; protected $seenMessageCount; - protected $dateTouched; protected $settings = array(); protected function getConfiguration() { @@ -14,7 +13,6 @@ final class ConpherenceParticipant extends ConpherenceDAO { 'settings' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( - 'dateTouched' => 'epoch', 'seenMessageCount' => 'uint64', ), self::CONFIG_KEY_SCHEMA => array( @@ -22,9 +20,6 @@ final class ConpherenceParticipant extends ConpherenceDAO { 'columns' => array('conpherencePHID', 'participantPHID'), 'unique' => true, ), - 'participationIndex' => array( - 'columns' => array('participantPHID', 'dateTouched', 'id'), - ), 'key_thread' => array( 'columns' => array('participantPHID', 'conpherencePHID'), ), From f46263903cbe9b57e867ce29686afaba6353cf27 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 12:49:14 -0700 Subject: [PATCH 38/56] Fix improper filtering behavior in ConpherenceParticipantQuery Summary: Pathway to D17685. This fixes an issue idenified in D17731: if any caller ever queried for more than one participant, some results could get thrown away by re-keying the results on thread PHID: two different participants can be members of the same thread! This also fixes an issue from D17683, where a `needParticipantCache()` callsite was overlooked. Test Plan: - Viewed Conpherence dropdown. - Sent messages, saw unread count / thread order still work properly. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17732 --- .../ConpherenceQueryThreadConduitAPIMethod.php | 5 ++--- .../controller/ConpherenceListController.php | 1 + .../ConpherenceNotificationPanelController.php | 1 + .../query/ConpherenceParticipantQuery.php | 17 +---------------- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php index fd602058a3..0d177e502c 100644 --- a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php @@ -36,8 +36,7 @@ final class ConpherenceQueryThreadConduitAPIMethod $offset = $request->getValue('offset'); $query = id(new ConpherenceThreadQuery()) - ->setViewer($user) - ->needParticipantCache(true); + ->setViewer($user); if ($ids) { $conpherences = $query @@ -57,7 +56,7 @@ final class ConpherenceQueryThreadConduitAPIMethod ->setLimit($limit) ->setOffset($offset) ->execute(); - $conpherence_phids = array_keys($participation); + $conpherence_phids = mpull($participation, 'getConpherencePHID'); $query->withPHIDs($conpherence_phids); $conpherences = $query->execute(); $conpherences = array_select_keys($conpherences, $conpherence_phids); diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index 6f50d2b37a..fc73d98341 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -151,6 +151,7 @@ final class ConpherenceListController extends ConpherenceController { ->withParticipantPHIDs(array($viewer->getPHID())) ->setLimit($limit) ->execute(); + $all_participation = mpull($all_participation, null, 'getConpherencePHID'); return array( 'all_participation' => $all_participation, diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php index 3dda87b1b4..8458a9d5c4 100644 --- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php +++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php @@ -12,6 +12,7 @@ final class ConpherenceNotificationPanelController ->withParticipantPHIDs(array($user->getPHID())) ->setLimit(5) ->execute(); + $participant_data = mpull($participant_data, null, 'getConpherencePHID'); if ($participant_data) { $conpherences = id(new ConpherenceThreadQuery()) diff --git a/src/applications/conpherence/query/ConpherenceParticipantQuery.php b/src/applications/conpherence/query/ConpherenceParticipantQuery.php index e538ded2c2..fb4c3eff0f 100644 --- a/src/applications/conpherence/query/ConpherenceParticipantQuery.php +++ b/src/applications/conpherence/query/ConpherenceParticipantQuery.php @@ -25,22 +25,7 @@ final class ConpherenceParticipantQuery extends PhabricatorOffsetPagedQuery { $this->buildOrderClause($conn), $this->buildLimitClause($conn)); - $participants = $table->loadAllFromArray($data); - - // TODO: Fix this, it's bogus. - if ('garbage') { - if (count($this->participantPHIDs) !== 1) { - throw new Exception( - pht( - 'This query only works when querying for exactly one participant '. - 'PHID!')); - } - // This will throw results away if we aren't doing a query for exactly - // one participant PHID. - $participants = mpull($participants, null, 'getConpherencePHID'); - } - - return $participants; + return $table->loadAllFromArray($data); } protected function buildWhereClause(AphrontDatabaseConnection $conn) { From b9868f4f05789eca4ce0228bfe44886ee0cd9e3f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 13:05:33 -0700 Subject: [PATCH 39/56] Don't require a transaction to mark a participant up-to-date Summary: Pathway to D17685. We no longer have "behindTransactionPHID", so we no longer need the latest transaction. This allows some code to be removed. Test Plan: - Grepped for callsites to `markUpToDate()` and variables used in the calls. - Sent messages in a couple threads, viewed them, saw unread counts go away. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17733 --- .../ConpherenceColumnViewController.php | 2 +- .../ConpherenceUpdateController.php | 2 +- .../controller/ConpherenceViewController.php | 33 +------------------ .../storage/ConpherenceParticipant.php | 4 +-- 4 files changed, 4 insertions(+), 37 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php index aa4f94edfc..3bd0c6b111 100644 --- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php +++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php @@ -67,7 +67,7 @@ final class ConpherenceColumnViewController extends $transactions = $conpherence->getTransactions(); $latest_transaction = head($transactions); $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); - $participant->markUpToDate($conpherence, $latest_transaction); + $participant->markUpToDate($conpherence); unset($write_guard); $draft = PhabricatorDraft::newFromUserAndKey( diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 7b41d5bb2d..d95696b68f 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -531,7 +531,7 @@ final class ConpherenceUpdateController $key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY; $minimized = $user->getUserSetting($key); if (!$minimized) { - $participant->markUpToDate($conpherence, $data['latest_transaction']); + $participant->markUpToDate($conpherence); } } else if ($need_transactions) { $non_update = true; diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index e097aa376e..68d2d3d145 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -55,15 +55,11 @@ final class ConpherenceViewController extends } $this->setConpherence($conpherence); - $transactions = $this->getNeededTransactions( - $conpherence, - $old_message_id); - $latest_transaction = head($transactions); $participant = $conpherence->getParticipantIfExists($user->getPHID()); if ($participant) { if (!$participant->isUpToDate($conpherence)) { $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); - $participant->markUpToDate($conpherence, $latest_transaction); + $participant->markUpToDate($conpherence); $user->clearCacheData(PhabricatorUserMessageCountCacheType::KEY_COUNT); unset($write_guard); } @@ -199,33 +195,6 @@ final class ConpherenceViewController extends } } - private function getNeededTransactions( - ConpherenceThread $conpherence, - $message_id) { - - if ($message_id) { - $newer_transactions = $conpherence->getTransactions(); - $query = id(new ConpherenceTransactionQuery()) - ->setViewer($this->getRequest()->getUser()) - ->withObjectPHIDs(array($conpherence->getPHID())) - ->setAfterID($message_id) - ->needHandles(true) - ->setLimit(self::OLDER_FETCH_LIMIT); - $older_transactions = $query->execute(); - $handles = array(); - foreach ($older_transactions as $transaction) { - $handles += $transaction->getHandles(); - } - $conpherence->attachHandles($conpherence->getHandles() + $handles); - $transactions = array_merge($newer_transactions, $older_transactions); - $conpherence->attachTransactions($transactions); - } else { - $transactions = $conpherence->getTransactions(); - } - - return $transactions; - } - private function getMainQueryLimit() { $request = $this->getRequest(); $base_limit = ConpherenceThreadQuery::TRANSACTION_LIMIT; diff --git a/src/applications/conpherence/storage/ConpherenceParticipant.php b/src/applications/conpherence/storage/ConpherenceParticipant.php index 1c4178c2dc..1edf7940e9 100644 --- a/src/applications/conpherence/storage/ConpherenceParticipant.php +++ b/src/applications/conpherence/storage/ConpherenceParticipant.php @@ -31,9 +31,7 @@ final class ConpherenceParticipant extends ConpherenceDAO { return nonempty($this->settings, array()); } - public function markUpToDate( - ConpherenceThread $conpherence, - ConpherenceTransaction $xaction) { + public function markUpToDate(ConpherenceThread $conpherence) { if (!$this->isUpToDate($conpherence)) { $this->setSeenMessageCount($conpherence->getMessageCount()); From 51485a1f82578d9e8ef7877e13e2c2ed03b9d1bb Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Apr 2017 11:43:40 -0700 Subject: [PATCH 40/56] Add participants ModularTransactions to Conpherence Summary: Moves participants over to ModularTransactions, simplified a lot of the code. Fixes T12550 Test Plan: Create a new room with just myself and myself + fake accounts. Remove a person. Remove myself. Edit a room, topic. Type some messages. ??? Profit Reviewers: chad Reviewed By: chad Subscribers: Korvin Maniphest Tasks: T12550 Differential Revision: https://secure.phabricator.com/D17685 --- src/__phutil_library_map__.php | 2 + .../__tests__/ConpherenceRoomTestCase.php | 3 +- .../__tests__/ConpherenceTestCase.php | 6 +- ...onpherenceUpdateThreadConduitAPIMethod.php | 4 +- .../ConpherenceNewRoomController.php | 3 +- .../ConpherenceUpdateController.php | 6 +- .../conpherence/editor/ConpherenceEditor.php | 224 ++---------------- .../mail/ConpherenceReplyHandler.php | 3 +- .../storage/ConpherenceTransaction.php | 79 ------ ...npherenceThreadParticipantsTransaction.php | 117 +++++++++ ...habricatorApplicationTransactionEditor.php | 5 +- .../PhabricatorModularTransactionType.php | 6 + 12 files changed, 158 insertions(+), 300 deletions(-) create mode 100644 src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 33481ee880..3b56e463b7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -321,6 +321,7 @@ phutil_register_library_map(array( 'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php', 'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php', 'ConpherenceThreadMembersPolicyRule' => 'applications/conpherence/policyrule/ConpherenceThreadMembersPolicyRule.php', + 'ConpherenceThreadParticipantsTransaction' => 'applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php', 'ConpherenceThreadPictureTransaction' => 'applications/conpherence/xaction/ConpherenceThreadPictureTransaction.php', 'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php', 'ConpherenceThreadRemarkupRule' => 'applications/conpherence/remarkup/ConpherenceThreadRemarkupRule.php', @@ -5121,6 +5122,7 @@ phutil_register_library_map(array( 'ConpherenceThreadListView' => 'AphrontView', 'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver', 'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule', + 'ConpherenceThreadParticipantsTransaction' => 'ConpherenceThreadTransactionType', 'ConpherenceThreadPictureTransaction' => 'ConpherenceThreadTransactionType', 'ConpherenceThreadQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ConpherenceThreadRemarkupRule' => 'PhabricatorObjectRemarkupRule', diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php index 85c4b9c1f1..7ef908c315 100644 --- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php @@ -107,7 +107,8 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => $participant_phids)); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( diff --git a/src/applications/conpherence/__tests__/ConpherenceTestCase.php b/src/applications/conpherence/__tests__/ConpherenceTestCase.php index 1ad87d8af8..6c8397c7d8 100644 --- a/src/applications/conpherence/__tests__/ConpherenceTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceTestCase.php @@ -9,7 +9,8 @@ abstract class ConpherenceTestCase extends PhabricatorTestCase { $xactions = array( id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => $participant_phids)), ); $editor = id(new ConpherenceEditor()) @@ -26,7 +27,8 @@ abstract class ConpherenceTestCase extends PhabricatorTestCase { $xactions = array( id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('-' => $participant_phids)), ); $editor = id(new ConpherenceEditor()) diff --git a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php index c92cc464ed..e7d927905d 100644 --- a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php @@ -69,7 +69,7 @@ final class ConpherenceUpdateThreadConduitAPIMethod if ($add_participant_phids) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransaction::TYPE_PARTICIPANTS) + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => $add_participant_phids)); } if ($remove_participant_phid) { @@ -78,7 +78,7 @@ final class ConpherenceUpdateThreadConduitAPIMethod } $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransaction::TYPE_PARTICIPANTS) + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('-' => array($remove_participant_phid))); } if ($title) { diff --git a/src/applications/conpherence/controller/ConpherenceNewRoomController.php b/src/applications/conpherence/controller/ConpherenceNewRoomController.php index d70395dbc9..6cbe86aaa2 100644 --- a/src/applications/conpherence/controller/ConpherenceNewRoomController.php +++ b/src/applications/conpherence/controller/ConpherenceNewRoomController.php @@ -23,7 +23,8 @@ final class ConpherenceNewRoomController extends ConpherenceController { $participants[] = $user->getPHID(); $participants = array_unique($participants); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => $participants)); $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType(ConpherenceThreadTopicTransaction::TRANSACTIONTYPE) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index d95696b68f..1d17134a53 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -61,7 +61,7 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::JOIN_ROOM: $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransaction::TYPE_PARTICIPANTS) + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => array($user->getPHID()))); $delete_draft = true; $message = $request->getStr('text'); @@ -95,7 +95,7 @@ final class ConpherenceUpdateController if (!empty($person_phids)) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransaction::TYPE_PARTICIPANTS) + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => $person_phids)); } break; @@ -108,7 +108,7 @@ final class ConpherenceUpdateController if ($person_phid) { $xactions[] = id(new ConpherenceTransaction()) ->setTransactionType( - ConpherenceTransaction::TYPE_PARTICIPANTS) + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('-' => array($person_phid))); $response_mode = 'go-home'; } diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 59d854cdc3..29ffc22251 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -37,7 +37,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { if (!$errors) { $xactions = array(); $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => $participant_phids)); if ($title) { $xactions[] = id(new ConpherenceTransaction()) @@ -87,8 +88,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { public function getTransactionTypes() { $types = parent::getTransactionTypes(); - $types[] = ConpherenceTransaction::TYPE_PARTICIPANTS; - $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -100,29 +99,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return pht('%s created this room.', $author); } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: - if ($this->getIsNewObject()) { - return array(); - } - return $object->getParticipantPHIDs(); - } - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: - return $this->getPHIDTransactionNewValue($xaction); - } - } - /** * We really only need a read lock if we have a comment. In that case, we * must update the messagesCount field on the conpherence and @@ -142,66 +118,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return $lock; } - /** - * We need to apply initial effects IFF the conpherence is new. We must - * save the conpherence first thing to make sure we have an id and a phid, as - * well as create the initial set of participants so that we pass policy - * checks. - */ - protected function shouldApplyInitialEffects( - PhabricatorLiskDAO $object, - array $xactions) { - - return $this->getIsNewObject(); - } - - protected function applyInitialEffects( - PhabricatorLiskDAO $object, - array $xactions) { - - $object->save(); - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: - // Since this is a new ConpherenceThread, we have to create the - // participation data asap to pass policy checks. For existing - // ConpherenceThreads, the existing participation is correct - // at this stage. Note that later in applyCustomExternalTransaction - // this participation data will be updated. - $participants = array(); - $phids = $this->getPHIDTransactionNewValue($xaction, array()); - foreach ($phids as $phid) { - if ($phid == $this->getActor()->getPHID()) { - $message_count = 1; - } else { - $message_count = 0; - } - $participants[$phid] = - id(new ConpherenceParticipant()) - ->setConpherencePHID($object->getPHID()) - ->setParticipantPHID($phid) - ->setSeenMessageCount($message_count) - ->save(); - $object->attachParticipants($participants); - } - break; - } - } - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: - if (!$this->getIsNewObject()) {} - break; - } - - } - protected function applyBuiltinInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -215,83 +131,23 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return parent::applyBuiltinInternalTransaction($object, $xaction); } - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: - if ($this->getIsNewObject()) { - continue; - } - $participants = $object->getParticipants(); - - $old_map = array_fuse($xaction->getOldValue()); - $new_map = array_fuse($xaction->getNewValue()); - - $remove = array_keys(array_diff_key($old_map, $new_map)); - foreach ($remove as $phid) { - $remove_participant = $participants[$phid]; - $remove_participant->delete(); - unset($participants[$phid]); - } - - $add = array_keys(array_diff_key($new_map, $old_map)); - foreach ($add as $phid) { - if ($phid == $this->getActor()->getPHID()) { - $message_count = $object->getMessageCount(); - } else { - $message_count = 0; - } - $participants[$phid] = - id(new ConpherenceParticipant()) - ->setConpherencePHID($object->getPHID()) - ->setParticipantPHID($phid) - ->setSeenMessageCount($message_count) - ->save(); - } - $object->attachParticipants($participants); - break; - } - } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { - if (!$xactions) { - return $xactions; + $acting_phid = $this->getActingAsPHID(); + $participants = $object->getParticipants(); + foreach ($participants as $participant) { + if ($participant->getParticipantPHID() == $acting_phid) { + $participant->markUpToDate($object); + } } - $message_count = 0; - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorTransactions::TYPE_COMMENT: - $message_count++; - - // update everyone's participation status on a message -only- - $xaction_phid = $xaction->getPHID(); - $participants = $object->getParticipants(); - $user = $this->getActor(); - $time = time(); - foreach ($participants as $phid => $participant) { - if ($phid != $user->getPHID()) { - if ($participant->isUpToDate($object)) { - $participant->setSeenMessageCount( - $object->getMessageCount() - $message_count); - } - } else { - $participant->setSeenMessageCount($object->getMessageCount()); - } - $participant->save(); - } - - PhabricatorUserCache::clearCaches( - PhabricatorUserMessageCountCacheType::KEY_COUNT, - array_keys($participants)); - - break; - } + if ($participants) { + PhabricatorUserCache::clearCaches( + PhabricatorUserMessageCountCacheType::KEY_COUNT, + array_keys($participants)); } if ($xactions) { @@ -315,7 +171,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { parent::requireCapabilities($object, $xaction); switch ($xaction->getTransactionType()) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: + case ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE: $old_map = array_fuse($xaction->getOldValue()); $new_map = array_fuse($xaction->getNewValue()); @@ -339,8 +195,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { $object, PhabricatorPolicyCapability::CAN_EDIT); } - break; + case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( @@ -351,19 +207,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { } } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: - return $this->mergePHIDOrEdgeTransactions($u, $v); - } - - return parent::mergeTransactions($u, $v); - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { @@ -466,43 +309,4 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return true; } - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case ConpherenceTransaction::TYPE_PARTICIPANTS: - foreach ($xactions as $xaction) { - $new_phids = $this->getPHIDTransactionNewValue($xaction, array()); - $old_phids = nonempty($object->getParticipantPHIDs(), array()); - $phids = array_diff($new_phids, $old_phids); - - if (!$phids) { - continue; - } - - $users = id(new PhabricatorPeopleQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($phids) - ->execute(); - $users = mpull($users, null, 'getPHID'); - foreach ($phids as $phid) { - if (isset($users[$phid])) { - continue; - } - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht('New room participant "%s" is not a valid user.', $phid), - $xaction); - } - } - break; - } - - return $errors; - } } diff --git a/src/applications/conpherence/mail/ConpherenceReplyHandler.php b/src/applications/conpherence/mail/ConpherenceReplyHandler.php index 946501c8b8..a462030fdd 100644 --- a/src/applications/conpherence/mail/ConpherenceReplyHandler.php +++ b/src/applications/conpherence/mail/ConpherenceReplyHandler.php @@ -55,7 +55,8 @@ final class ConpherenceReplyHandler extends PhabricatorMailReplyHandler { $xactions = array(); if ($this->getMailAddedParticipantPHIDs()) { $xactions[] = id(new ConpherenceTransaction()) - ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS) + ->setTransactionType( + ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE) ->setNewValue(array('+' => $this->getMailAddedParticipantPHIDs())); } diff --git a/src/applications/conpherence/storage/ConpherenceTransaction.php b/src/applications/conpherence/storage/ConpherenceTransaction.php index 2fb3fca380..f23882d69b 100644 --- a/src/applications/conpherence/storage/ConpherenceTransaction.php +++ b/src/applications/conpherence/storage/ConpherenceTransaction.php @@ -3,8 +3,6 @@ final class ConpherenceTransaction extends PhabricatorModularTransaction { - const TYPE_PARTICIPANTS = 'participants'; - public function getApplicationName() { return 'conpherence'; } @@ -21,81 +19,4 @@ final class ConpherenceTransaction return 'ConpherenceThreadTransactionType'; } - public function getNoEffectDescription() { - switch ($this->getTransactionType()) { - case self::TYPE_PARTICIPANTS: - return pht( - 'You can not add a participant who has already been added.'); - break; - } - - return parent::getNoEffectDescription(); - } - - public function shouldHide() { - $old = $this->getOldValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_PARTICIPANTS: - return ($old === null); - } - - return parent::shouldHide(); - } - - public function getTitle() { - $author_phid = $this->getAuthorPHID(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - switch ($this->getTransactionType()) { - case self::TYPE_PARTICIPANTS: - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - if ($add && $rem) { - $title = pht( - '%s edited participant(s), added %d: %s; removed %d: %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add), - count($rem), - $this->renderHandleList($rem)); - } else if ($add) { - $title = pht( - '%s added %d participant(s): %s.', - $this->renderHandleLink($author_phid), - count($add), - $this->renderHandleList($add)); - } else { - $title = pht( - '%s removed %d participant(s): %s.', - $this->renderHandleLink($author_phid), - count($rem), - $this->renderHandleList($rem)); - } - return $title; - break; - } - - return parent::getTitle(); - } - - public function getRequiredHandlePHIDs() { - $phids = parent::getRequiredHandlePHIDs(); - - $old = $this->getOldValue(); - $new = $this->getNewValue(); - - $phids[] = $this->getAuthorPHID(); - switch ($this->getTransactionType()) { - case self::TYPE_PARTICIPANTS: - $phids = array_merge($phids, $this->getOldValue()); - $phids = array_merge($phids, $this->getNewValue()); - break; - } - - return $phids; - } } diff --git a/src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php new file mode 100644 index 0000000000..681c6d639c --- /dev/null +++ b/src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php @@ -0,0 +1,117 @@ +getParticipantPHIDs(); + } + + public function generateNewValue($object, $value) { + $old = $this->generateOldValue($object); + return $this->getPHIDList($old, $value); + } + + public function applyExternalEffects($object, $value) { + $participants = $object->getParticipants(); + + $old = array_keys($participants); + $new = $value; + + $add_map = array_fuse(array_diff($new, $old)); + $rem_map = array_fuse(array_diff($old, $new)); + + foreach ($rem_map as $phid) { + $remove_participant = $participants[$phid]; + $remove_participant->delete(); + unset($participants[$phid]); + } + + foreach ($add_map as $phid) { + if (isset($participants[$phid])) { + continue; + } + + $participants[$phid] = id(new ConpherenceParticipant()) + ->setConpherencePHID($object->getPHID()) + ->setParticipantPHID($phid) + ->setSeenMessageCount(0) + ->save(); + } + + $object->attachParticipants($participants); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $author_phid = $this->getAuthorPHID(); + + if ($add && $rem) { + return pht( + '%s edited participant(s), added %d: %s; removed %d: %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } else if ((in_array($author_phid, $add)) && (count($add) == 1)) { + return pht( + '%s joined the room.', + $this->renderAuthor()); + } else if ((in_array($author_phid, $rem)) && (count($rem) == 1)) { + return pht( + '%s left the room.', + $this->renderAuthor()); + } else if ($add) { + return pht( + '%s added %d participant(s): %s.', + $this->renderAuthor(), + count($add), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed %d participant(s): %s.', + $this->renderAuthor(), + count($rem), + $this->renderHandleList($rem)); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $old = $object->getParticipantPHIDs(); + + $new = $xaction->getNewValue(); + $new = $this->getPHIDList($old, $new); + + $add_map = array_fuse(array_diff($new, $old)); + $rem_map = array_fuse(array_diff($old, $new)); + + foreach ($add_map as $user_phid) { + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($user_phid)) + ->executeOne(); + if (!$user) { + $errors[] = $this->newInvalidError( + pht( + 'Participant PHID "%s" is not a valid user PHID.', + $user_phid)); + continue; + } + } + } + + return $errors; + } + +} diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index eb6f88d65b..8362d1328d 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1801,7 +1801,10 @@ abstract class PhabricatorApplicationTransactionEditor $old = array_fuse($xaction->getOldValue()); } - $new = $xaction->getNewValue(); + return $this->getPHIDList($old, $xaction->getNewValue()); + } + + public function getPHIDList(array $old, array $new) { $new_add = idx($new, '+', array()); unset($new['+']); $new_rem = idx($new, '-', array()); diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index a315a5b9cf..9d8510cdc9 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -309,4 +309,10 @@ abstract class PhabricatorModularTransactionType return $this->getStorage()->getIsCreateTransaction(); } + final protected function getPHIDList(array $old, array $new) { + $editor = $this->getEditor(); + + return $editor->getPHIDList($old, $new); + } + } From db60af7ea57ce7bd637dbf475aa86ad164fd57e6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Apr 2017 14:18:43 -0700 Subject: [PATCH 41/56] Set all room settings at once Summary: Sets notification and sound preferences in a single array() Test Plan: Change email preference, save, set sound preference, save. Email preference still OK. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17735 --- .../conpherence/controller/ConpherenceUpdateController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 1d17134a53..51f40fef92 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -120,8 +120,10 @@ final class ConpherenceUpdateController if (!$participant) { return id(new Aphront404Response()); } - $participant->setSettings(array('notifications' => $notifications)); - $participant->setSettings(array('sounds' => $sounds)); + $participant->setSettings(array( + 'notifications' => $notifications, + 'sounds' => $sounds, + )); $participant->save(); return id(new AphrontRedirectResponse()) ->setURI('/'.$conpherence->getMonogram()); From febd68039fdcb2be22fcd4bf4c51d0dc76f8edff Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 19 Apr 2017 15:17:10 -0700 Subject: [PATCH 42/56] Add initial infrastructure for adding ModularTransaction support to Application config changes Summary: Part of the groundwork for T11476. Test Plan: ran `./bin/storage upgrade` and observed expected DB tables Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T11476 Differential Revision: https://secure.phabricator.com/D17736 --- .../20170418.1.application.01.xaction.sql | 19 ++++++++++++++ .../20170418.1.application.02.edge.sql | 16 ++++++++++++ .../base/PhabricatorApplication.php | 25 ++++++++++++++++++- ...ApplicationApplicationTransactionQuery.php | 10 ++++++++ ...plicationApplicationTransactionComment.php | 10 ++++++++ ...catorApplicationApplicationTransaction.php | 22 ++++++++++++++++ .../PhabricatorApplicationTransactionType.php | 4 +++ .../patch/PhabricatorBuiltinPatchList.php | 1 + 8 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20170418.1.application.01.xaction.sql create mode 100644 resources/sql/autopatches/20170418.1.application.02.edge.sql create mode 100644 src/applications/meta/query/PhabricatorApplicationApplicationTransactionQuery.php create mode 100644 src/applications/meta/storage/PhabricatorApplicationApplicationTransactionComment.php create mode 100644 src/applications/meta/xactions/PhabricatorApplicationApplicationTransaction.php create mode 100644 src/applications/meta/xactions/PhabricatorApplicationTransactionType.php diff --git a/resources/sql/autopatches/20170418.1.application.01.xaction.sql b/resources/sql/autopatches/20170418.1.application.01.xaction.sql new file mode 100644 index 0000000000..70868ef2f4 --- /dev/null +++ b/resources/sql/autopatches/20170418.1.application.01.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_application.application_applicationtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170418.1.application.02.edge.sql b/resources/sql/autopatches/20170418.1.application.02.edge.sql new file mode 100644 index 0000000000..a8f3e1e332 --- /dev/null +++ b/resources/sql/autopatches/20170418.1.application.02.edge.sql @@ -0,0 +1,16 @@ +CREATE TABLE {$NAMESPACE}_application.edge ( + src VARBINARY(64) NOT NULL, + type INT UNSIGNED NOT NULL, + dst VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + seq INT UNSIGNED NOT NULL, + dataID INT UNSIGNED, + PRIMARY KEY (src, type, dst), + KEY `src` (src, type, dateCreated, seq), + UNIQUE KEY `key_dst` (dst, type, src) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_application.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 93b738d9e0..8bf784fb1e 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -10,7 +10,9 @@ */ abstract class PhabricatorApplication extends Phobject - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorApplicationTransactionInterface { const GROUP_CORE = 'core'; const GROUP_UTILITIES = 'util'; @@ -613,4 +615,25 @@ abstract class PhabricatorApplication ); } +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorApplicationEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorApplicationApplicationTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + + return $timeline; + } } diff --git a/src/applications/meta/query/PhabricatorApplicationApplicationTransactionQuery.php b/src/applications/meta/query/PhabricatorApplicationApplicationTransactionQuery.php new file mode 100644 index 0000000000..77843f713d --- /dev/null +++ b/src/applications/meta/query/PhabricatorApplicationApplicationTransactionQuery.php @@ -0,0 +1,10 @@ + array(), 'db.badges' => array(), 'db.packages' => array(), + 'db.application' => array(), '0000.legacy.sql' => array( 'legacy' => 0, ), From d58b808f04e6b7b39b1c62d504272dc5413ddeef Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 19 Apr 2017 15:46:27 -0700 Subject: [PATCH 43/56] Fixing copy/paste mistake Test Plan: doitlive Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17737 --- src/applications/packages/editor/PhabricatorPackagesEditor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/packages/editor/PhabricatorPackagesEditor.php b/src/applications/packages/editor/PhabricatorPackagesEditor.php index 0065791ad7..492b14643a 100644 --- a/src/applications/packages/editor/PhabricatorPackagesEditor.php +++ b/src/applications/packages/editor/PhabricatorPackagesEditor.php @@ -4,7 +4,7 @@ abstract class PhabricatorPackagesEditor extends PhabricatorApplicationTransactionEditor { public function getEditorApplicationClass() { - return 'PhabricatorPasteApplication'; + return 'PhabricatorPackagesApplication'; } protected function supportsSearch() { From 95dd9dbf437a4ec71e5ff6a68d84df769cd78e01 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 15:49:19 -0700 Subject: [PATCH 44/56] Make Applications extend LiskDAO Summary: Ref T11476. This is a bit hacky, but makes `Application` extend `LiskDAO` so we can apply transactions to it with an `Editor` class. Also fixes schema stuff so builds should produce a clean bill of health again. This might only get you slightly further, yell if you run into more trouble. Test Plan: - Ran `bin/storage upgrade -f` and got no warnings. - Browsed around, nothing exploded? Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11476 Differential Revision: https://secure.phabricator.com/D17738 --- .../sql/autopatches/20170419.app.01.table.sql | 7 +++++ src/__phutil_library_map__.php | 11 +++++++- .../base/PhabricatorApplication.php | 26 ++++++++++++++++++- .../PhabricatorConfigDatabaseSchema.php | 7 +++++ ...catorApplicationApplicationTransaction.php | 0 ...plicationApplicationTransactionComment.php | 10 ------- .../PhabricatorApplicationSchemaSpec.php | 10 +++++++ 7 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 resources/sql/autopatches/20170419.app.01.table.sql rename src/applications/meta/{xactions => storage}/PhabricatorApplicationApplicationTransaction.php (100%) delete mode 100644 src/applications/meta/storage/PhabricatorApplicationApplicationTransactionComment.php create mode 100644 src/applications/meta/storage/PhabricatorApplicationSchemaSpec.php diff --git a/resources/sql/autopatches/20170419.app.01.table.sql b/resources/sql/autopatches/20170419.app.01.table.sql new file mode 100644 index 0000000000..257e0b3eb8 --- /dev/null +++ b/resources/sql/autopatches/20170419.app.01.table.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_application.application_application ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3b56e463b7..fccc38e5cf 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1838,6 +1838,8 @@ phutil_register_library_map(array( 'PhabricatorAppSearchEngine' => 'applications/meta/query/PhabricatorAppSearchEngine.php', 'PhabricatorApplication' => 'applications/base/PhabricatorApplication.php', 'PhabricatorApplicationApplicationPHIDType' => 'applications/meta/phid/PhabricatorApplicationApplicationPHIDType.php', + 'PhabricatorApplicationApplicationTransaction' => 'applications/meta/storage/PhabricatorApplicationApplicationTransaction.php', + 'PhabricatorApplicationApplicationTransactionQuery' => 'applications/meta/query/PhabricatorApplicationApplicationTransactionQuery.php', 'PhabricatorApplicationConfigOptions' => 'applications/config/option/PhabricatorApplicationConfigOptions.php', 'PhabricatorApplicationConfigurationPanel' => 'applications/meta/panel/PhabricatorApplicationConfigurationPanel.php', 'PhabricatorApplicationConfigurationPanelTestCase' => 'applications/meta/panel/__tests__/PhabricatorApplicationConfigurationPanelTestCase.php', @@ -1849,6 +1851,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', 'PhabricatorApplicationProfileMenuItem' => 'applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php', 'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php', + 'PhabricatorApplicationSchemaSpec' => 'applications/meta/storage/PhabricatorApplicationSchemaSpec.php', 'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php', 'PhabricatorApplicationSearchEngine' => 'applications/search/engine/PhabricatorApplicationSearchEngine.php', 'PhabricatorApplicationSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorApplicationSearchEngineTestCase.php', @@ -1881,6 +1884,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionTemplatedCommentQuery.php', 'PhabricatorApplicationTransactionTextDiffDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php', 'PhabricatorApplicationTransactionTransactionPHIDType' => 'applications/transactions/phid/PhabricatorApplicationTransactionTransactionPHIDType.php', + 'PhabricatorApplicationTransactionType' => 'applications/meta/xactions/PhabricatorApplicationTransactionType.php', 'PhabricatorApplicationTransactionValidationError' => 'applications/transactions/error/PhabricatorApplicationTransactionValidationError.php', 'PhabricatorApplicationTransactionValidationException' => 'applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php', 'PhabricatorApplicationTransactionValidationResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionValidationResponse.php', @@ -6847,10 +6851,13 @@ phutil_register_library_map(array( 'PhabricatorAphrontViewTestCase' => 'PhabricatorTestCase', 'PhabricatorAppSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorApplication' => array( - 'Phobject', + 'PhabricatorLiskDAO', 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorApplicationApplicationPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorApplicationApplicationTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorApplicationApplicationTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorApplicationConfigOptions' => 'Phobject', 'PhabricatorApplicationConfigurationPanel' => 'Phobject', 'PhabricatorApplicationConfigurationPanelTestCase' => 'PhabricatorTestCase', @@ -6862,6 +6869,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorApplicationSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorApplicationSearchEngine' => 'Phobject', 'PhabricatorApplicationSearchEngineTestCase' => 'PhabricatorTestCase', @@ -6902,6 +6910,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery', 'PhabricatorApplicationTransactionTextDiffDetailView' => 'AphrontView', 'PhabricatorApplicationTransactionTransactionPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorApplicationTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorApplicationTransactionValidationError' => 'Phobject', 'PhabricatorApplicationTransactionValidationException' => 'Exception', 'PhabricatorApplicationTransactionValidationResponse' => 'AphrontProxyResponse', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 8bf784fb1e..885cdad9cf 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -9,7 +9,7 @@ * @task meta Application Management */ abstract class PhabricatorApplication - extends Phobject + extends PhabricatorLiskDAO implements PhabricatorPolicyInterface, PhabricatorApplicationTransactionInterface { @@ -28,6 +28,30 @@ abstract class PhabricatorApplication ); } + final public function getApplicationName() { + return 'application'; + } + + final public function getTableName() { + return 'application_application'; + } + + final protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + final public function generatePHID() { + return $this->getPHID(); + } + + final public function save() { + // When "save()" is called on applications, we just return without + // actually writing anything to the database. + return $this; + } + /* -( Application Information )-------------------------------------------- */ diff --git a/src/applications/config/schema/PhabricatorConfigDatabaseSchema.php b/src/applications/config/schema/PhabricatorConfigDatabaseSchema.php index 216a778e56..b9b407ee70 100644 --- a/src/applications/config/schema/PhabricatorConfigDatabaseSchema.php +++ b/src/applications/config/schema/PhabricatorConfigDatabaseSchema.php @@ -11,6 +11,13 @@ final class PhabricatorConfigDatabaseSchema public function addTable(PhabricatorConfigTableSchema $table) { $key = $table->getName(); if (isset($this->tables[$key])) { + + if ($key == 'application_application') { + // NOTE: This is a terrible hack to allow Application subclasses to + // extend LiskDAO so we can apply transactions to them. + return $this; + } + throw new Exception( pht('Trying to add duplicate table "%s"!', $key)); } diff --git a/src/applications/meta/xactions/PhabricatorApplicationApplicationTransaction.php b/src/applications/meta/storage/PhabricatorApplicationApplicationTransaction.php similarity index 100% rename from src/applications/meta/xactions/PhabricatorApplicationApplicationTransaction.php rename to src/applications/meta/storage/PhabricatorApplicationApplicationTransaction.php diff --git a/src/applications/meta/storage/PhabricatorApplicationApplicationTransactionComment.php b/src/applications/meta/storage/PhabricatorApplicationApplicationTransactionComment.php deleted file mode 100644 index ed0e1dd55e..0000000000 --- a/src/applications/meta/storage/PhabricatorApplicationApplicationTransactionComment.php +++ /dev/null @@ -1,10 +0,0 @@ -buildEdgeSchemata(new PhabricatorApplicationsApplication()); + } + +} From d2560b2462015b9e32037a614ee51373bf3910f7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 16:10:48 -0700 Subject: [PATCH 45/56] Make "Locate File" trigger when data loads if the user typed/pasted real fast Summary: Fixes T12599. If you're faster than the network request, we don't actually resolve your query when the data arrives. Instead, when the data shows up, run the query if the user has typed something. Test Plan: Pasted a filename in real fast, got results. (Previously: no results until you press something else.) Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12599 Differential Revision: https://secure.phabricator.com/D17739 --- resources/celerity/map.php | 16 ++++++++-------- .../diffusion/DiffusionLocateFileSource.js | 5 +++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6016fc09cb..c31dc9855b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -401,7 +401,7 @@ return array( 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', - 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7', + 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '49ae8328', @@ -719,7 +719,7 @@ return array( 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', - 'javelin-diffusion-locate-file-source' => 'b42eddc7', + 'javelin-diffusion-locate-file-source' => 'c93358e3', 'javelin-dom' => '805b806a', 'javelin-dynval' => 'f6555212', 'javelin-event' => '2ee659ce', @@ -1878,12 +1878,6 @@ return array( 'b3e7d692' => array( 'javelin-install', ), - 'b42eddc7' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-typeahead-preloaded-source', - 'javelin-util', - ), 'b59e1e96' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1998,6 +1992,12 @@ return array( 'javelin-install', 'javelin-util', ), + 'c93358e3' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-typeahead-preloaded-source', + 'javelin-util', + ), 'c989ade3' => array( 'javelin-install', 'javelin-util', diff --git a/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js b/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js index f824410cfb..14894f6cc0 100644 --- a/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js +++ b/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js @@ -23,6 +23,11 @@ JX.install('DiffusionLocateFileSource', { ondata: function(results) { this.tree = results.tree; + + if (this.lastValue !== null) { + this.matchResults(this.lastValue); + } + this.setReady(true); }, From 6969e9389f23894e901eabdaee32f30a653cbc61 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 19 Apr 2017 16:20:32 -0700 Subject: [PATCH 46/56] Move room preferences into own controller Summary: Ref T12591. These preferences are per user and we need to reload the whole UI on a change anyways. Simplifies future expansion. Test Plan: Option click into new window, set preferences. View in Conpherence, see saved settings, change sound. Hear new sound. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12591 Differential Revision: https://secure.phabricator.com/D17740 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConpherenceApplication.php | 2 + .../constants/ConpherenceUpdateActions.php | 1 - .../controller/ConpherenceController.php | 14 +-- .../ConpherenceRoomPreferencesController.php | 104 +++++++++++++++++ .../ConpherenceUpdateController.php | 106 ------------------ .../conpherence/storage/ConpherenceThread.php | 4 + 7 files changed, 118 insertions(+), 115 deletions(-) create mode 100644 src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fccc38e5cf..5f5623cb84 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -310,6 +310,7 @@ phutil_register_library_map(array( 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', 'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php', 'ConpherenceRoomPictureController' => 'applications/conpherence/controller/ConpherenceRoomPictureController.php', + 'ConpherenceRoomPreferencesController' => 'applications/conpherence/controller/ConpherenceRoomPreferencesController.php', 'ConpherenceRoomSettings' => 'applications/conpherence/constants/ConpherenceRoomSettings.php', 'ConpherenceRoomTestCase' => 'applications/conpherence/__tests__/ConpherenceRoomTestCase.php', 'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php', @@ -5108,6 +5109,7 @@ phutil_register_library_map(array( 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', 'ConpherenceRoomListController' => 'ConpherenceController', 'ConpherenceRoomPictureController' => 'ConpherenceController', + 'ConpherenceRoomPreferencesController' => 'ConpherenceController', 'ConpherenceRoomSettings' => 'ConpherenceConstants', 'ConpherenceRoomTestCase' => 'ConpherenceTestCase', 'ConpherenceSchemaSpec' => 'PhabricatorConfigSchemaSpec', diff --git a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php index 5faaf40b9c..e969b48447 100644 --- a/src/applications/conpherence/application/PhabricatorConpherenceApplication.php +++ b/src/applications/conpherence/application/PhabricatorConpherenceApplication.php @@ -55,6 +55,8 @@ final class PhabricatorConpherenceApplication extends PhabricatorApplication { => 'ConpherenceNotificationPanelController', 'participant/(?P[1-9]\d*)/' => 'ConpherenceParticipantController', + 'preferences/(?P[1-9]\d*)/' + => 'ConpherenceRoomPreferencesController', 'update/(?P[1-9]\d*)/' => 'ConpherenceUpdateController', ), diff --git a/src/applications/conpherence/constants/ConpherenceUpdateActions.php b/src/applications/conpherence/constants/ConpherenceUpdateActions.php index e2d788f5c6..a2b97f9c6a 100644 --- a/src/applications/conpherence/constants/ConpherenceUpdateActions.php +++ b/src/applications/conpherence/constants/ConpherenceUpdateActions.php @@ -8,7 +8,6 @@ final class ConpherenceUpdateActions extends ConpherenceConstants { const JOIN_ROOM = 'join_room'; const ADD_PERSON = 'add_person'; const REMOVE_PERSON = 'remove_person'; - const NOTIFICATIONS = 'notifications'; const ADD_STATUS = 'add_status'; const LOAD = 'load'; } diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 8154dc3174..f7fd7ef1b4 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -57,8 +57,9 @@ abstract class ConpherenceController extends PhabricatorController { ConpherenceThread $conpherence) { $viewer = $this->getViewer(); $header = null; + $id = $conpherence->getID(); - if ($conpherence->getID()) { + if ($id) { $data = $conpherence->getDisplayData($this->getViewer()); $header = id(new PHUIHeaderView()) @@ -83,15 +84,14 @@ abstract class ConpherenceController extends PhabricatorController { if ($can_edit) { $header->setImageURL( - $this->getApplicationURI('picture/'.$conpherence->getID().'/')); + $this->getApplicationURI("picture/{$id}/")); } $participating = $conpherence->getParticipantIfExists($viewer->getPHID()); $header->addActionItem( id(new PHUIIconCircleView()) - ->setHref( - $this->getApplicationURI('update/'.$conpherence->getID()).'/') + ->setHref($this->getApplicationURI("update/{$id}/")) ->setIcon('fa-pencil') ->addClass('hide-on-device') ->setColor('violet') @@ -99,9 +99,7 @@ abstract class ConpherenceController extends PhabricatorController { $header->addActionItem( id(new PHUIIconCircleView()) - ->setHref( - $this->getApplicationURI('update/'.$conpherence->getID()).'/'. - '?action='.ConpherenceUpdateActions::NOTIFICATIONS) + ->setHref($this->getApplicationURI("preferences/{$id}/")) ->setIcon('fa-gear') ->addClass('hide-on-device') ->setColor('pink') @@ -136,7 +134,7 @@ abstract class ConpherenceController extends PhabricatorController { if (!$participating) { $action = ConpherenceUpdateActions::JOIN_ROOM; - $uri = $this->getApplicationURI('update/'.$conpherence->getID().'/'); + $uri = $this->getApplicationURI("update/{$id}/"); $button = phutil_tag( 'button', array( diff --git a/src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php b/src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php new file mode 100644 index 0000000000..1d330e6704 --- /dev/null +++ b/src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php @@ -0,0 +1,104 @@ +getViewer(); + $conpherence_id = $request->getURIData('id'); + + $conpherence = id(new ConpherenceThreadQuery()) + ->setViewer($viewer) + ->withIDs(array($conpherence_id)) + ->executeOne(); + if (!$conpherence) { + return new Aphront404Response(); + } + + $view_uri = $conpherence->getURI(); + + $participant = $conpherence->getParticipantIfExists($viewer->getPHID()); + if (!$participant) { + if ($viewer->isLoggedIn()) { + $text = pht( + 'Notification settings are available after joining the room.'); + } else { + $text = pht( + 'Notification settings are available after logging in and joining '. + 'the room.'); + } + return $this->newDialog() + ->setTitle(pht('Room Preferences')) + ->addCancelButton($view_uri) + ->appendParagraph($text); + } + + // Save the data and redirect + if ($request->isFormPost()) { + $notifications = $request->getStr('notifications'); + $sounds = $request->getArr('sounds'); + + $participant->setSettings(array( + 'notifications' => $notifications, + 'sounds' => $sounds, + )); + $participant->save(); + + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); + } + + $notification_key = PhabricatorConpherenceNotificationsSetting::SETTINGKEY; + $notification_default = $viewer->getUserSetting($notification_key); + + $sound_key = PhabricatorConpherenceSoundSetting::SETTINGKEY; + $sound_default = $viewer->getUserSetting($sound_key); + + $settings = $participant->getSettings(); + $notifications = idx($settings, 'notifications', $notification_default); + + $sounds = idx($settings, 'sounds', array()); + $map = PhabricatorConpherenceSoundSetting::getDefaultSound($sound_default); + $receive = idx($sounds, + ConpherenceRoomSettings::SOUND_RECEIVE, + $map[ConpherenceRoomSettings::SOUND_RECEIVE]); + $mention = idx($sounds, + ConpherenceRoomSettings::SOUND_MENTION, + $map[ConpherenceRoomSettings::SOUND_MENTION]); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendControl( + id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Notify')) + ->addButton( + PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL, + PhabricatorConpherenceNotificationsSetting::getSettingLabel( + PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL), + '') + ->addButton( + PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY, + PhabricatorConpherenceNotificationsSetting::getSettingLabel( + PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY), + '') + ->setName('notifications') + ->setValue($notifications)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('New Message')) + ->setName('sounds['.ConpherenceRoomSettings::SOUND_RECEIVE.']') + ->setOptions(ConpherenceRoomSettings::getDropdownSoundMap()) + ->setValue($receive)); + + return $this->newDialog() + ->setTitle(pht('Room Preferences')) + ->appendForm($form) + ->addCancelButton($view_uri) + ->addSubmitButton(pht('Save')); + } + +} diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 51f40fef92..728d917589 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -24,9 +24,6 @@ final class ConpherenceUpdateController case ConpherenceUpdateActions::METADATA: $needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT; break; - case ConpherenceUpdateActions::NOTIFICATIONS: - $need_participants = true; - break; case ConpherenceUpdateActions::LOAD: break; } @@ -112,22 +109,6 @@ final class ConpherenceUpdateController ->setNewValue(array('-' => array($person_phid))); $response_mode = 'go-home'; } - break; - case ConpherenceUpdateActions::NOTIFICATIONS: - $notifications = $request->getStr('notifications'); - $sounds = $request->getArr('sounds'); - $participant = $conpherence->getParticipantIfExists($user->getPHID()); - if (!$participant) { - return id(new Aphront404Response()); - } - $participant->setSettings(array( - 'notifications' => $notifications, - 'sounds' => $sounds, - )); - $participant->save(); - return id(new AphrontRedirectResponse()) - ->setURI('/'.$conpherence->getMonogram()); - break; case ConpherenceUpdateActions::METADATA: $title = $request->getStr('title'); @@ -221,9 +202,6 @@ final class ConpherenceUpdateController } switch ($action) { - case ConpherenceUpdateActions::NOTIFICATIONS: - $dialog = $this->renderPreferencesDialog($conpherence); - break; case ConpherenceUpdateActions::ADD_PERSON: $dialog = $this->renderAddPersonDialog($conpherence); break; @@ -246,88 +224,6 @@ final class ConpherenceUpdateController } - private function renderPreferencesDialog( - ConpherenceThread $conpherence) { - - $request = $this->getRequest(); - $user = $request->getUser(); - - $participant = $conpherence->getParticipantIfExists($user->getPHID()); - if (!$participant) { - if ($user->isLoggedIn()) { - $text = pht( - 'Notification settings are available after joining the room.'); - } else { - $text = pht( - 'Notification settings are available after logging in and joining '. - 'the room.'); - } - return id(new AphrontDialogView()) - ->setTitle(pht('Room Preferences')) - ->appendParagraph($text); - } - - $notification_key = PhabricatorConpherenceNotificationsSetting::SETTINGKEY; - $notification_default = $user->getUserSetting($notification_key); - - $sound_key = PhabricatorConpherenceSoundSetting::SETTINGKEY; - $sound_default = $user->getUserSetting($sound_key); - - $settings = $participant->getSettings(); - $notifications = idx($settings, 'notifications', $notification_default); - - $sounds = idx($settings, 'sounds', array()); - $map = PhabricatorConpherenceSoundSetting::getDefaultSound($sound_default); - $receive = idx($sounds, - ConpherenceRoomSettings::SOUND_RECEIVE, - $map[ConpherenceRoomSettings::SOUND_RECEIVE]); - $mention = idx($sounds, - ConpherenceRoomSettings::SOUND_MENTION, - $map[ConpherenceRoomSettings::SOUND_MENTION]); - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendControl( - id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Notify')) - ->addButton( - PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL, - PhabricatorConpherenceNotificationsSetting::getSettingLabel( - PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL), - '') - ->addButton( - PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY, - PhabricatorConpherenceNotificationsSetting::getSettingLabel( - PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY), - '') - ->setName('notifications') - ->setValue($notifications)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Message Received')) - ->setName('sounds['.ConpherenceRoomSettings::SOUND_RECEIVE.']') - ->setOptions(ConpherenceRoomSettings::getDropdownSoundMap()) - ->setValue($receive)); - - // TODO: Future Adventure! Expansion Pack - // - // ->appendChild( - // id(new AphrontFormSelectControl()) - // ->setLabel(pht('Username Mentioned')) - // ->setName('sounds['.ConpherenceRoomSettings::SOUND_MENTION.']') - // ->setOptions(ConpherenceRoomSettings::getDropdownSoundMap()) - // ->setValue($mention)); - - return id(new AphrontDialogView()) - ->setTitle(pht('Room Preferences')) - ->addHiddenInput('action', 'notifications') - ->addHiddenInput( - 'latest_transaction_id', - $request->getInt('latest_transaction_id')) - ->appendForm($form); - - } - private function renderAddPersonDialog( ConpherenceThread $conpherence) { @@ -508,7 +404,6 @@ final class ConpherenceUpdateController $need_transactions = true; break; case ConpherenceUpdateActions::REMOVE_PERSON: - case ConpherenceUpdateActions::NOTIFICATIONS: default: break; @@ -566,7 +461,6 @@ final class ConpherenceUpdateController $people_widget = hsprintf('%s', $people_widget->render()); break; case ConpherenceUpdateActions::REMOVE_PERSON: - case ConpherenceUpdateActions::NOTIFICATIONS: default: break; } diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index f0d0e60bb3..6f18729e03 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -72,6 +72,10 @@ final class ConpherenceThread extends ConpherenceDAO return 'Z'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + public function attachParticipants(array $participants) { assert_instances_of($participants, 'ConpherenceParticipant'); $this->participants = $participants; From 7a992b5488986cd704c01266ac420321fc4e7e4a Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 16:34:23 -0700 Subject: [PATCH 47/56] When a package or project has been accepted or rejected, show who did it ("Accepted (by dog)") Summary: Makes it more clear whose authority actions have been taken under. Test Plan: {F4916376} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17741 --- .../base/PhabricatorApplication.php | 2 +- .../view/DifferentialReviewersView.php | 101 +++++++++++------- 2 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 885cdad9cf..29f5ed35be 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -643,7 +643,7 @@ abstract class PhabricatorApplication public function getApplicationTransactionEditor() { - return new PhabricatorApplicationEditor(); + return new PhutilMethodNotImplementedException(pht('Coming Soon!')); } public function getApplicationTransactionObject() { diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 291f859d08..034bf99edb 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -55,80 +55,101 @@ final class DifferentialReviewersView extends AphrontView { $item->setHighlighted($reviewer->hasAuthority($viewer)); + // If someone other than the reviewer acted on the reviewer's behalf, + // show who is responsible for the current state. This is usually a + // user accepting for a package or project. + $authority_phid = $reviewer->getLastActorPHID(); + if ($authority_phid && ($authority_phid !== $phid)) { + $authority_name = $viewer->renderHandle($authority_phid) + ->setAsText(true); + } else { + $authority_name = null; + } + switch ($reviewer->getReviewerStatus()) { case DifferentialReviewerStatus::STATUS_ADDED: if ($comment_phid) { if ($is_current_comment) { - $item->setIcon( - 'fa-comment', - 'blue', - pht('Commented')); + $icon = 'fa-comment'; + $color = 'blue'; + $label = pht('Commented'); } else { - $item->setIcon( - 'fa-comment-o', - 'bluegrey', - pht('Commented Previously')); + $icon = 'fa-comment-o'; + $color = 'bluegrey'; + $label = pht('Commented Previously'); } } else { - $item->setIcon( - PHUIStatusItemView::ICON_OPEN, - 'bluegrey', - pht('Review Requested')); + $icon = PHUIStatusItemView::ICON_OPEN; + $color = 'bluegrey'; + $label = pht('Review Requested'); } break; case DifferentialReviewerStatus::STATUS_ACCEPTED: if ($is_current_action) { - $item->setIcon( - PHUIStatusItemView::ICON_ACCEPT, - 'green', - pht('Accepted')); + $icon = PHUIStatusItemView::ICON_ACCEPT; + $color = 'green'; + if ($authority_name !== null) { + $label = pht('Accepted (by %s)', $authority_name); + } else { + $label = pht('Accepted'); + } } else { - $item->setIcon( - 'fa-check-circle-o', - 'bluegrey', - pht('Accepted Prior Diff')); + $icon = 'fa-check-circle-o'; + $color = 'bluegrey'; + if ($authority_name !== null) { + $label = pht('Accepted Prior Diff (by %s)', $authority_name); + } else { + $label = pht('Accepted Prior Diff'); + } } break; case DifferentialReviewerStatus::STATUS_REJECTED: if ($is_current_action) { - $item->setIcon( - PHUIStatusItemView::ICON_REJECT, - 'red', - pht('Requested Changes')); + $icon = PHUIStatusItemView::ICON_REJECT; + $color = 'red'; + if ($authority_name !== null) { + $label = pht('Requested Changes (by %s)', $authority_name); + } else { + $label = pht('Requested Changes'); + } } else { - $item->setIcon( - 'fa-times-circle-o', - 'bluegrey', - pht('Requested Changes to Prior Diff')); + $icon = 'fa-times-circle-o'; + $color = 'bluegrey'; + if ($authority_name !== null) { + $label = pht( + 'Requested Changes to Prior Diff (by %s)', + $authority_name); + } else { + $label = pht('Requested Changes to Prior Diff'); + } } break; case DifferentialReviewerStatus::STATUS_BLOCKING: - $item->setIcon( - PHUIStatusItemView::ICON_MINUS, - 'red', - pht('Blocking Review')); + $icon = PHUIStatusItemView::ICON_MINUS; + $color = 'red'; + $label = pht('Blocking Review'); break; case DifferentialReviewerStatus::STATUS_RESIGNED: - $item->setIcon( - 'fa-times', - 'grey', - pht('Resigned')); + $icon = 'fa-times'; + $color = 'grey'; + $label = pht('Resigned'); break; default: - $item->setIcon( - PHUIStatusItemView::ICON_QUESTION, - 'bluegrey', - pht('%s?', $reviewer->getReviewerStatus())); + $icon = PHUIStatusItemView::ICON_QUESTION; + $color = 'bluegrey'; + $label = pht('Unknown ("%s")', $reviewer->getReviewerStatus()); break; } + $item->setIcon($icon, $color, $label); $item->setTarget($handle->renderHovercardLink()); + $view->addItem($item); } From 1c222c88349eefbeafa46271e8b400a42aa955f2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 17:02:52 -0700 Subject: [PATCH 48/56] In Conpherence, stop throwing away stuff users have typed when a reply arrives Summary: Ref T12562. I think the pre-send-on-enter behavior was: disable textarea, send message, clear area on response? That got changed but not completely, maybe. There's currently an issue here: - Add a `sleep(3)` to `UpdateController`. - Type "AAA". - Press enter. - Real fast, type "BBB". - When the "AAA" arrives, your "BBB" is lost. Sad! Test Plan: - Did the thing described above; no longer lost "BBB". - Switched threads, sent messages, couldn't find anything else this breaks. It dates from a long time ago so I think it's just pre-SOE stuff. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12562 Differential Revision: https://secure.phabricator.com/D17742 --- resources/celerity/map.php | 34 +++++++++---------- .../application/conpherence/behavior-menu.js | 1 - 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c31dc9855b..e431b931d4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => '3776e82d', - 'conpherence.pkg.js' => '5f86c17d', + 'conpherence.pkg.js' => '7b6aa581', 'core.pkg.css' => '959330a2', 'core.pkg.js' => '47a69358', 'darkconsole.pkg.js' => '1f9a31bc', @@ -378,7 +378,7 @@ return array( 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '4d863052', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', - 'rsrc/js/application/conpherence/behavior-menu.js' => '80dda04a', + 'rsrc/js/application/conpherence/behavior-menu.js' => '31ab6d0f', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -605,7 +605,7 @@ return array( 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => '9a6dd75c', 'javelin-behavior-config-reorder-fields' => 'b6993408', - 'javelin-behavior-conpherence-menu' => '80dda04a', + 'javelin-behavior-conpherence-menu' => '31ab6d0f', 'javelin-behavior-conpherence-participant-pane' => '8604caa8', 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', @@ -1126,6 +1126,20 @@ return array( '31420f77' => array( 'javelin-behavior', ), + '31ab6d0f' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '320810c8' => array( 'javelin-install', 'javelin-dom', @@ -1532,20 +1546,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '80dda04a' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index a8151428e4..4c7fbd3479 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -70,7 +70,6 @@ JX.behavior('conpherence-menu', function(config) { var textarea = JX.DOM.find(form_root, 'textarea'); if (!non_update) { _scrollMessageWindow(); - textarea.value = ''; } markThreadLoading(false); From ed9afa18cca517ce37ac77648e1e1e7fda4347be Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 20 Apr 2017 14:23:23 -0700 Subject: [PATCH 49/56] Allow users to choo choo choose a room color Summary: This adds some basic per user / per room theming for Conpherence, which should hopefully let users identify rooms from just the sidebar color. Test Plan: Lots of threads with different colors. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17747 --- resources/celerity/map.php | 47 +++++++++--------- resources/celerity/packages.php | 1 + .../constants/ConpherenceRoomSettings.php | 22 +++++++++ .../ConpherenceRoomPreferencesController.php | 11 ++++- .../controller/ConpherenceViewController.php | 5 ++ .../conpherence/storage/ConpherenceThread.php | 6 +++ .../view/ConpherenceLayoutView.php | 8 ++++ .../view/ConpherenceThreadListView.php | 1 + .../css/application/conpherence/color.css | 48 +++++++++++++++++++ .../application/conpherence/header-pane.css | 4 -- .../rsrc/css/application/conpherence/menu.css | 4 ++ .../application/conpherence/behavior-menu.js | 9 ++++ 12 files changed, 139 insertions(+), 27 deletions(-) create mode 100644 webroot/rsrc/css/application/conpherence/color.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e431b931d4..d19dc81c45 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '3776e82d', - 'conpherence.pkg.js' => '7b6aa581', + 'conpherence.pkg.css' => 'ff161f2d', + 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '959330a2', 'core.pkg.js' => '47a69358', 'darkconsole.pkg.js' => '1f9a31bc', @@ -50,9 +50,10 @@ return array( 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', + 'rsrc/css/application/conpherence/color.css' => 'abb4c358', 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', - 'rsrc/css/application/conpherence/header-pane.css' => 'a1104b93', - 'rsrc/css/application/conpherence/menu.css' => '88100764', + 'rsrc/css/application/conpherence/header-pane.css' => 'cb6f4e19', + 'rsrc/css/application/conpherence/menu.css' => '6953e7ec', 'rsrc/css/application/conpherence/message-pane.css' => 'b0f55ecc', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', 'rsrc/css/application/conpherence/participant-pane.css' => '26a3ce56', @@ -378,7 +379,7 @@ return array( 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '4d863052', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', - 'rsrc/js/application/conpherence/behavior-menu.js' => '31ab6d0f', + 'rsrc/js/application/conpherence/behavior-menu.js' => 'c9b99b77', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -559,9 +560,10 @@ return array( 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', + 'conpherence-color-css' => 'abb4c358', 'conpherence-durable-column-view' => '89ea6bef', - 'conpherence-header-pane-css' => 'a1104b93', - 'conpherence-menu-css' => '88100764', + 'conpherence-header-pane-css' => 'cb6f4e19', + 'conpherence-menu-css' => '6953e7ec', 'conpherence-message-pane-css' => 'b0f55ecc', 'conpherence-notification-css' => 'cef0a3fc', 'conpherence-participant-pane-css' => '26a3ce56', @@ -605,7 +607,7 @@ return array( 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => '9a6dd75c', 'javelin-behavior-config-reorder-fields' => 'b6993408', - 'javelin-behavior-conpherence-menu' => '31ab6d0f', + 'javelin-behavior-conpherence-menu' => 'c9b99b77', 'javelin-behavior-conpherence-participant-pane' => '8604caa8', 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', @@ -1126,20 +1128,6 @@ return array( '31420f77' => array( 'javelin-behavior', ), - '31ab6d0f' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), '320810c8' => array( 'javelin-install', 'javelin-dom', @@ -2003,6 +1991,20 @@ return array( 'javelin-util', 'javelin-stratcom', ), + 'c9b99b77' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), 'ca3f91eb' => array( 'javelin-behavior', 'javelin-dom', @@ -2261,6 +2263,7 @@ return array( 'conpherence.pkg.css' => array( 'conpherence-durable-column-view', 'conpherence-menu-css', + 'conpherence-color-css', 'conpherence-message-pane-css', 'conpherence-notification-css', 'conpherence-transaction-css', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index cb5eb22b51..d906c738da 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -161,6 +161,7 @@ return array( 'conpherence.pkg.css' => array( 'conpherence-durable-column-view', 'conpherence-menu-css', + 'conpherence-color-css', 'conpherence-message-pane-css', 'conpherence-notification-css', 'conpherence-transaction-css', diff --git a/src/applications/conpherence/constants/ConpherenceRoomSettings.php b/src/applications/conpherence/constants/ConpherenceRoomSettings.php index c6a18d261e..f2e51af2f3 100644 --- a/src/applications/conpherence/constants/ConpherenceRoomSettings.php +++ b/src/applications/conpherence/constants/ConpherenceRoomSettings.php @@ -9,6 +9,13 @@ final class ConpherenceRoomSettings extends ConpherenceConstants { const DEFAULT_MENTION_SOUND = 'alert'; const DEFAULT_NO_SOUND = 'none'; + const COLOR_LIGHT = 'light'; + const COLOR_BLUE = 'blue'; + const COLOR_INDIGO = 'indigo'; + const COLOR_PEACH = 'peach'; + const COLOR_GREEN = 'green'; + const COLOR_PINK = 'pink'; + public static function getSoundMap() { return array( 'none' => array( @@ -43,5 +50,20 @@ final class ConpherenceRoomSettings extends ConpherenceConstants { return ipull($map, 'name'); } + public static function getThemeMap() { + return array( + self::COLOR_LIGHT => pht('Light'), + self::COLOR_BLUE => pht('Blue'), + self::COLOR_INDIGO => pht('Indigo'), + self::COLOR_PEACH => pht('Peach'), + self::COLOR_GREEN => pht('Green'), + self::COLOR_PINK => pht('Pink'), + ); + } + + public static function getThemeClass($theme) { + return 'conpherence-theme-'.$theme; + } + } diff --git a/src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php b/src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php index 1d330e6704..c868684389 100644 --- a/src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php +++ b/src/applications/conpherence/controller/ConpherenceRoomPreferencesController.php @@ -41,10 +41,12 @@ final class ConpherenceRoomPreferencesController if ($request->isFormPost()) { $notifications = $request->getStr('notifications'); $sounds = $request->getArr('sounds'); + $theme = $request->getStr('theme'); $participant->setSettings(array( 'notifications' => $notifications, 'sounds' => $sounds, + 'theme' => $theme, )); $participant->save(); @@ -60,6 +62,7 @@ final class ConpherenceRoomPreferencesController $settings = $participant->getSettings(); $notifications = idx($settings, 'notifications', $notification_default); + $theme = idx($settings, 'theme', ConpherenceRoomSettings::COLOR_LIGHT); $sounds = idx($settings, 'sounds', array()); $map = PhabricatorConpherenceSoundSetting::getDefaultSound($sound_default); @@ -92,7 +95,13 @@ final class ConpherenceRoomPreferencesController ->setLabel(pht('New Message')) ->setName('sounds['.ConpherenceRoomSettings::SOUND_RECEIVE.']') ->setOptions(ConpherenceRoomSettings::getDropdownSoundMap()) - ->setValue($receive)); + ->setValue($receive)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Theme')) + ->setName('theme') + ->setOptions(ConpherenceRoomSettings::getThemeMap()) + ->setValue($theme)); return $this->newDialog() ->setTitle(pht('Room Preferences')) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 68d2d3d145..792a7f8182 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -56,7 +56,11 @@ final class ConpherenceViewController extends $this->setConpherence($conpherence); $participant = $conpherence->getParticipantIfExists($user->getPHID()); + $theme = ConpherenceRoomSettings::COLOR_LIGHT; + if ($participant) { + $settings = $participant->getSettings(); + $theme = idx($settings, 'theme', ConpherenceRoomSettings::COLOR_LIGHT); if (!$participant->isUpToDate($conpherence)) { $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); $participant->markUpToDate($conpherence); @@ -118,6 +122,7 @@ final class ConpherenceViewController extends ->setSearch($search) ->setMessages($messages) ->setReplyForm($form) + ->setTheme($theme) ->setLatestTransactionID($data['latest_transaction_id']) ->setRole('thread'); diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 6f18729e03..7e4c3dc09f 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -204,8 +204,13 @@ final class ConpherenceThread extends ConpherenceDAO } $user_participation = $this->getParticipantIfExists($viewer->getPHID()); + $theme = ConpherenceRoomSettings::COLOR_LIGHT; if ($user_participation) { $user_seen_count = $user_participation->getSeenMessageCount(); + $participant = $this->getParticipant($viewer->getPHID()); + $settings = $participant->getSettings(); + $theme = idx($settings, 'theme', $theme); + $theme_class = ConpherenceRoomSettings::getThemeClass($theme); } else { $user_seen_count = 0; } @@ -221,6 +226,7 @@ final class ConpherenceThread extends ConpherenceDAO 'unread_count' => $unread_count, 'epoch' => $this->getDateModified(), 'image' => $img_src, + 'theme' => $theme_class, ); } diff --git a/src/applications/conpherence/view/ConpherenceLayoutView.php b/src/applications/conpherence/view/ConpherenceLayoutView.php index 6699d61994..7dbc00a325 100644 --- a/src/applications/conpherence/view/ConpherenceLayoutView.php +++ b/src/applications/conpherence/view/ConpherenceLayoutView.php @@ -10,6 +10,7 @@ final class ConpherenceLayoutView extends AphrontTagView { private $search; private $messages; private $replyForm; + private $theme = ConpherenceRoomSettings::COLOR_LIGHT; private $latestTransactionID; public function setMessages($messages) { @@ -56,6 +57,11 @@ final class ConpherenceLayoutView extends AphrontTagView { return $this; } + public function setTheme($theme) { + $this->theme = $theme; + return $this; + } + public function setLatestTransactionID($id) { $this->latestTransactionID = $id; return $this; @@ -66,6 +72,7 @@ final class ConpherenceLayoutView extends AphrontTagView { $classes[] = 'conpherence-layout'; $classes[] = 'hide-widgets'; $classes[] = 'conpherence-role-'.$this->role; + $classes[] = ConpherenceRoomSettings::getThemeClass($this->theme); return array( 'id' => 'conpherence-main-layout', @@ -105,6 +112,7 @@ final class ConpherenceLayoutView extends AphrontTagView { 'canEditSelectedThread' => $can_edit_selected, 'latestTransactionID' => $this->latestTransactionID, 'role' => $this->role, + 'theme' => ConpherenceRoomSettings::getThemeClass($this->theme), 'hasThreadList' => (bool)$this->threadView, 'hasThread' => (bool)$this->messages, 'hasWidgets' => false, diff --git a/src/applications/conpherence/view/ConpherenceThreadListView.php b/src/applications/conpherence/view/ConpherenceThreadListView.php index 1d2b9910ae..af15f82a6e 100644 --- a/src/applications/conpherence/view/ConpherenceThreadListView.php +++ b/src/applications/conpherence/view/ConpherenceThreadListView.php @@ -75,6 +75,7 @@ final class ConpherenceThreadListView extends AphrontView { 'title' => $data['title'], 'id' => $dom_id, 'threadID' => $thread->getID(), + 'theme' => $data['theme'], )); } diff --git a/webroot/rsrc/css/application/conpherence/color.css b/webroot/rsrc/css/application/conpherence/color.css new file mode 100644 index 0000000000..5350afebec --- /dev/null +++ b/webroot/rsrc/css/application/conpherence/color.css @@ -0,0 +1,48 @@ +/** + * @provides conpherence-color-css + */ + +.conpherence-theme-blue .conpherence-menu-pane { + background-color: {$lightblue}; +} + +.conpherence-theme-blue .phui-basic-nav .phabricator-side-menu + .phui-list-item-selected { + border-left-color: {$blue}; +} + +.conpherence-theme-indigo .conpherence-menu-pane { + background-color: {$lightindigo}; +} + +.conpherence-theme-indigo .phui-basic-nav .phabricator-side-menu + .phui-list-item-selected { + border-left-color: {$indigo}; +} + +.conpherence-theme-peach .conpherence-menu-pane { + background-color: {$lightred}; +} + +.conpherence-theme-peach .phui-basic-nav .phabricator-side-menu + .phui-list-item-selected { + border-left-color: rgba(0,0,0,.25); +} + +.conpherence-theme-green .conpherence-menu-pane { + background-color: {$lightgreen}; +} + +.conpherence-theme-green .phui-basic-nav .phabricator-side-menu + .phui-list-item-selected { + border-left-color: {$green}; +} + +.conpherence-theme-pink .conpherence-menu-pane { + background-color: {$lightpink}; +} + +.conpherence-theme-pink .phui-basic-nav .phabricator-side-menu + .phui-list-item-selected { + border-left-color: {$pink}; +} diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index 6a61f94c08..54f58682da 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -2,10 +2,6 @@ * @provides conpherence-header-pane-css */ -.conpherence-header-pane { - background-color: {$lightbluebackground}; -} - .conpherence-header-pane .phui-header-shell { padding: 8px 16px 10px; min-height: 38px; diff --git a/webroot/rsrc/css/application/conpherence/menu.css b/webroot/rsrc/css/application/conpherence/menu.css index e4f5f56036..b2702d94fd 100644 --- a/webroot/rsrc/css/application/conpherence/menu.css +++ b/webroot/rsrc/css/application/conpherence/menu.css @@ -21,6 +21,10 @@ background-color: {$page.sidenav}; } +.conpherence-menu-pane .phui-basic-nav .phabricator-side-menu { + background-color: transparent; +} + .conpherence-menu-pane.phabricator-side-menu .room-list-href { padding: 10px 0 9px 8px; display: inline-block; diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index 4c7fbd3479..a5566dc5a4 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -25,6 +25,7 @@ JX.behavior('conpherence-menu', function(config) { }; var scrollbar = null; + var cur_theme = config.theme; // TODO - move more logic into the ThreadManager var threadManager = new JX.ConpherenceThreadManager(); @@ -144,6 +145,14 @@ JX.behavior('conpherence-menu', function(config) { function updatePageData(data) { var uri = '/Z' + _thread.selected; + var new_theme = data.theme; + + if (new_theme != cur_theme) { + var root = JX.$('conpherence-main-layout'); + JX.DOM.alterClass(root, cur_theme, false); + JX.DOM.alterClass(root, new_theme, true); + cur_theme = new_theme; + } JX.History.replace(uri); if (data.title) { JX.Title.setTitle(data.title); From 3a608bae7b7127b937b143cd1b0ab27eefe009a9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Apr 2017 08:51:43 -0700 Subject: [PATCH 50/56] Better built in images Summary: iam Test Plan: iam Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17751 --- resources/builtin/image-100x100.png | Bin 993 -> 293 bytes resources/builtin/image-200x200.png | Bin 1261 -> 360 bytes resources/builtin/image-220x220.png | Bin 1322 -> 375 bytes resources/builtin/image-280x210.png | Bin 1349 -> 370 bytes resources/builtin/image-400x400.png | Bin 0 -> 456 bytes resources/builtin/image-526x526.png | Bin 2435 -> 546 bytes resources/builtin/image-800x800.png | Bin 0 -> 666 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/builtin/image-400x400.png create mode 100644 resources/builtin/image-800x800.png diff --git a/resources/builtin/image-100x100.png b/resources/builtin/image-100x100.png index c56a9aa0835ccbe7ab631b372d07a2b406d33872..d77e1d4584e8eabbaa7a8a3c3468815ce685fa7f 100644 GIT binary patch delta 278 zcmV+x0qOqX2c-g#7=Ho-0000DjD5!d000S4OjJeU=k)*p0RR90C{ACw0002jNklDuaWq|`3Z9mUj8~{Nw48V%@(-iJLL5t>7F1K`IyR+*U4tW$WKmCW= zLshGVpn$c=Zjl%#F2v8e(|HD-1466m+ats9w!I_zZFL-mcYn=71Fa)x7*FT5*DRu_ zy(@<>#<;#rSc8Z!Yw{Z7GOpvoRfmvdN&j4BNNcm`UxbTH+^qXWR=q6}=W^@6Nb9@^ z;pmOpqtP#!z#8cjiMpmWdqg+Eiw19tEPRT3q*sTSp2X7_Z{e;4CFdwdX}{EJ cwMXd#hx0}h77y6<1^@s607*qoM6N<$g7^1;9smFU literal 993 zcmeAS@N?(olHy`uVBq!ia0vp^DImzW1*#POj@;_H{wbPqwyZUxwrsH8~R|PQhM9MnTbEQ?}2|>G{JsVeYQqHk^}Z z-Yjo_qkn#n@#Y7ffB%>|@~Sb<=YLqvaxwh5`1QCTiI^7G2nZ5M6cmFXm`IB&Oe8T7 zRaxfr#OKp(?d<;j__sE0y}iACzum1TPf{#o{{4BnI#GhJh1F+kRBm;3bXKR$*{Ph7E6?^0Qj~m~ycv~j@*hNWj3O_$ztLk~pWfoReRa2+4d=Zma zyLPST2ZfLMqSx-HE?vBI>C!OwPX`#(|JLU3-?d9h<&5P9k-0%DORB58(-Z_EVq!wV zcYB^$!8$EmS%>4~>ZrATBo;7FIL3pCAaN65m}Ze^<{nAfySF6y>5xxFTX^TR8@5` z%$%K}^qsxgaqsGd3k}mbbzUcX+n;h#Dy*#RR1K2t30fJlF(M{DKHTtK6z^4~6-%m$ zcFwus^|bDSrM2~BiP&g%=kB#t{;DxYe{Q^))Ac*xP<+Os;Jb_13SFDT1+Hy9zv}GS zv#qVHDSzF-ZekFs$@Bb>#I^svzUAwyGiJ`5+1tw-bNALQr{etl^?&^jv{Y}k@V=e3 zWB>m7=b!K1=`3U~w8w;>kMDv0)IFD8me|OZUv!Le_%g%yMSWG(q*zuhA+?V?uE*xf zCJ5x*tZr`%)Dd%j`tM)dipbjMVi8Gms`lP9fA7(7wbpF^hu`{UOHVjX^-Aqf;SJgm zetKER`Rvpw?d|Q0_cTxU$=`f*i?4GwpVY4dPYTi_@2B+I^_@NOrcL$t`$F60QfFsw z*x-8W=FPzUFM1y7+_-GwYkf9VTW9|H{~s^Ex_kF-gwWhn)L=G*2iEPk-r~#5-Wm{gKxBq`m he;sF%z+bThhMcIW_g7`zUk}W)44$rjF6*2UngDUs!>s@S diff --git a/resources/builtin/image-200x200.png b/resources/builtin/image-200x200.png index 53bc1e785c395c4c5882c1aec3bfc9eac13d4159..520526a2d9e22c7a33721af4a3ca553124c9dad3 100644 GIT binary patch delta 345 zcmV-f0jB=#3Frcl7=Ho-0002cHbxBq000S4OjJeU=k)*p0RR90C{ACw0003RNkl%IKa?u(VElGrI3 z7f)tw69o0Z?06E^#J||AqD|r#5~N2Wli9eq*~aV%%$E=L`7Rw2*_Pk0B`H_JuMQ@@ zu`My*Sofs)D_w%f}nLOn@W68a>KX8mv001vW-`Z`1?er9NPp6pDsbAz6+#B rVy0*X>?Isa+zFCXA|fIpA|j#>xL_8F#GvAU00000NkvXXu0mjff@7QH literal 1261 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)K$^k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+So%F(978H@y}9l0EnO*b;NvP`ZAXzFZmwIQH=1vJekW)1 zev>-?KEC&_E>7OppL)Hy>Z@!AyLgUw=I#s5N9`4Qm6|R}{eR}w}If+ExGS@ z=H})f?G`Uy{PD-r?hPwUC$^pa^{eXewo8oPyXUE?sa-mFzi7{fsAq1=(kd!`>{2@_ zIC1G;{(Uv+>CdD2gu}GkrcU3oe}8>RiOB5Q+FEC4=b!oSC$-JlP;;jsJwj0|T3uZ| z*7T!_Tj1XpyS#lY5;<2FMhLpIgy>CA-aECZQ@y>toj+{qB%x^=-tC^V^lJIOOJ0jt z+s+*X=n{!AGf#ivszff#c7?YYufD# zJvM6{l9HBw{qm*d+D`S2lGee|A9w8BIrFNFjLebHPuHYQv-7^=lJ;NYp}sET@#Dva z>n@$!W|e<`UoZ2euZCvHvGxWA57q@l@UPz(6!Eol$={DVcJ11=cdzXBuAkHGrKP2- ztE+>9gMS+OE%r`)y!+6fG=u5yEC2til?wm4%g(Fn`@2}HW5;>b!X6iv{(q`d7p)

A!*r2F`cXrOC8Pk)l%RG%2HdoS4J>-UDQX=!P{e*aE(t&bD-PcklZ^U*ZzE*Dg}eKKkbkFz zKh86oK7D#}-qY^2KydP;=kmY^-a}p&?-aXs{{_mtoZL|{iO>7^@oe3DtyeA9NZ4G> z`ugwiqUF=|FCBPRVxNnl(00^Y2~Oy7xlpTJG2EWyjCjJP(N2-dyqP`0Ra#BG(>4J`SVZxGn>?;qc^mtdbLg4Fi(_guKoW{uRne)3>Lc^IqmD$sq^gL z3x%!L7VFlJ`(t5YabwNXsF11kZbP0l+XkKkFP_? diff --git a/resources/builtin/image-220x220.png b/resources/builtin/image-220x220.png index 928b5b05eb16f1d0d6565191ab41342d6d8fd3ce..f9fe4ade489ebb98837cd73615ea53d38a81b11b 100644 GIT binary patch delta 360 zcmV-u0hj)&3ikq#7=Ho-0001HG$#@O000S4OjJeU=k)*p0RR90C{ACw0003gNkl5Kq~RQ6RR3LPOQ?J|N>lesFgQfryBR zh=_=Y$Qxk7!oo^Uk9X<1DHW`;O9$(GXuNT1u~b3U$NqeN$bWH+t{<ejsyu@(B-1! z+F4-Hc`@$a-C+t+2N0;)t^X@lkV%}otN7BPt8+)PSm34Ii09520IXzJ@+knX*thec z#im1Uh&c%-OLn;I+ZkDY^Skrdw@Yu{( zJaZG%Q-e|yQz{EjrrIztu$=L9aSW-r_2#y`wLDfYpfl6KiFsef+ut`Hu z6;8QyYB)~m5CT&QUM`%AT&BP&xF(+|d9 z%$*w>A8-Hvu9TEiRdsd!-?!hL4$c%$U8{8b^5x5&ot=9Y&6zc8*Nz<-nVE%ou92UY z?Afz(r>0ojHd`f%>NdGlslYp&rid0YSGskODWza>v#UHp{D*5AK>ySuw@ym{~5 zy}Nhk{{3c`xufgT%H_8-f2`ARz7sQV&K#4wo$7{6tKFZ~CT;2pn)1_KGSRi>X_4hS z{ot&RPdlYRIRCA zT}Jbcq?uV9e)a0ruU}Pn3f-owE!Bt*kZS)Z5%}%<_wC!Z&AZpBzERRTc>2d3d-lZa zC@v@v2>-VJoo1~>%GbI7m-M`Oq-A4ZAdqbIcFpQd8#fBFFUhXTIVQDY^XAL1qNnIr z`_Ejo=A8E1{^LJ?{(SrPZN#MXcn>{YT_Cf!rzhpOMX%odpX>K+b}d)Cy?N)(na5JL zv2}6T*Zi2!_i^F`&zb*c)cg>V*{>_sJ$?H0%vr~h&z!!|Gxw(VCi8QDKZ++SDD)SY8wd>EI?72JKb*7&-)d^d4qGD}mpT=jI2`>xt^4?kgUpzU? z#%-C#=aAa|h}&yQ`CMn-ZvE{mCtv%UEoRD2&-=%pTn+w~*RiS0*VlLQeaSXWB$^JoIgLOe?NZw_)eVwu2!e&>X&bCtEq=g*_xe~SNHwh@pbFg?aA|B zeB#Y&50}W>OLVqv-D=7IUp>A?5s^b_oY~0E-8(gooImbAe=bK{e?6-N!`VI4&gZ`1 RwFVY`44$rjF6*2UngIF4TSou@ diff --git a/resources/builtin/image-280x210.png b/resources/builtin/image-280x210.png index 48237b045e722ee3ca84586aeb70139f3be57741..ddece3327d3dcc64ef5f78be6ae8d9219c486057 100644 GIT binary patch delta 355 zcmX@g^@(YML_HHT0|UdGC+3wviZj3`#P!khuR!Mi|Nk}o;&(DIFsgXEIEGZjy}h=v zx5Yr9^`SYV?3&+&oWef@b2zVTj&5RJ@hQQf`uSdNe}(n;+9y1HGQEeB?XHj@3_Luw zQF=ns$x}I(Hm$lJ-O_Djc6DQ)x1&VQnZgxY*47(Lvorqw{MVH2v#ah-eadJ2SZLB9$me}&*S0_KS5JKu z;NDiaVxIBa_u^b<^v|{^2MgM%CSP8jmbTyQvY!b5AG0U_&uJJ>IL37PcZu__eY%{Q z->0v%G&&%5Gsft+MnUsV!PvC4iEBhjaDG}zd16s2gJVj5 zQmTSyZen_BP-AFu^F4jn&i(o8&-=k(=Z{}2KeMYdy?gt&y}$Gvlg*q74ju|B0zx3n!P&wB z!cI(zj6mE7lxn~#4OY@PfdQ;UK?Pz02Peb?Cnl`YBw3(n$qG@AV&uW%X~kUgymrR? zdwt%XkDveixpQ*=^1gokI(P2e{r|pI>pf4`T9PHiIn`_F!i5Wub7iKaJo)is$I6u_ zcZvpu`YxIL`Sa%*yZswC28M@+M@Luh|Gsm_j*jmyU%l!&?mxdpWyvdH-X)1fwzhw- zPP@doy}Rz-zS`M*|E4__y!1JweCguFkNOVy?IO2gu9wc1A{XY6BDztUPZ;;v{q1=US_&8 zW?h8Nw9`*Tf^0g^<~X$e{Q2|f(W5(>yQcZ5D1Nlhd~+nZ<;vZ=ckkSZ*(KzkWL&mv z+M~jT2tCzt-JVj{H#<74~-D^iGkLM?|Ci{r!6uZ`r)<(4|X3G85K)Pb#jh z-Me<}+GDHcPS{b_KgDU4kJ8)z<0ns^+`4tE)4~b0GcwZB(u#{W@8554eJryiGhOLw z>T9mAqMK~BwY4*4QeO)Mp8NEv$m-aq?VT>ZyNY%{{iL&R_W9@jetws(bRD1Jy;0J7 z^YoPbrp}P7-MpHUPMPlVVrFL6kKd;=)$4fpBhQ>;=Q`AvrfxjwVl+|D)Wl>-^^)~+ zmv7wo@$T;KAMYMKNXY5y>x=uNF4T2>mWR`$qIo^Ny=&L4yJr%5Ny%klfXD2|j~^RO z-!yya&Ye5=?Xz1R=;8EB=A>?T#>W3&zL>1-IX<~0!pbf#d!wUA&A+LEJ7f0!-SA@h z_U-2LWMpNRF4B5F^|FSF9{ah64u5==N(-U_|<`A+^a%XX(#uex{5I{u?{mH!>im8+JXSG5Ua zzPVd*$*Uj@k+$~s&HrV-Oz0FzUK^n!<>j=>>SxX2^ZWPQynnxbifdDh-t}8iH6Ir} zIKs7Zmx-C#zPi7USBI~cdtIijb;{1IWx}d-rKizRQFGd#A9VK~Qu(%f{dajgE5Q!} d|G5kplA5ROcH1z`99S+gc)I$ztaD0e0suc+Lz(~p diff --git a/resources/builtin/image-400x400.png b/resources/builtin/image-400x400.png new file mode 100644 index 0000000000000000000000000000000000000000..db23eb01c7017adac3ec5505c567e92e5868d808 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKLOw2%$zZdVt11Zh`pAgqa&%Xkh|NsBj@QdFG zR8i&W;uum9_x9$-wMz^{90ECF4t;UvWZA=6#|fl`*Q~zt^;iGA8w|cXp3N7IkTU$_ ztCREapmu2pyHtjDr59;XRTyzzQ_#HIqvuRjDQT4vR{UQwR6Rqpl5eN%oU zpS^RZ{@EfKOQoWkzg;)}PrG*dy4THR^+$}-T$b2myJgkpicd5C+$A$tE`mSCod;>^6}xn%1V{f{(SiHAtGN? zP*CvYb%tW;Uu)ie zJSXqFo?k}r+N-&%fD~tdPl)TI=U;)$|NsAM_{HyJ zU|_uB>Eakt!T9zTW7c8^k%mO(CmhA{{R$7F6Z$uNKPkv{B{=WQVjI^9LT#y&>*NJa zZ7Hi`n0sv|BhV16;M*df!-2D}&!4$n`Qv&emkxo8Nt*J8Q-TE5LG1gg|JDQ_U;ig! z+T)yDxpf}{?pD6M`|o3+d6&RPKf#aHJC9y>KXX6YwfM)1vp~%+#3uH)={6f!29~_vV3}a?k%36~FIeRa)j;k@!IG1={cEBOq+ zcY}=4U63wlTAEet`lele-Sgx7fQEnkJALKWuKDg)X3e!M_1XM(p72YM?>gF7{;r<+ p%1+g(rGxL6JJ1nKSi>JE_>U>WZpUR4MFR(r3Qt!*mvv4FO#qNbUr(SQadTTvFZr20(BV`AhCuwx}3MyviWbNkvxzTyTzlqz} z=hoO=Ffg8bCh^y$;$Q@f^4of@~^+|KUZJtfhA87q_C zKX|YpYVEeDwM9E)w5Fabn|t{1;T`Ax{Hd|2`=ui4!7Adt=hX7X2NxH+yI)h9Td+yv z$fHL|RzI`Pv9D}+?_ZfvR`xAHVecJxvr8X?=ikdQ+yBgD0@sgy!4tPXe0zJlwYBv| zYg1EGW8=y7-z_o)CUUvGi|>s-kg-f@OVrwadHZW@Ny^7sAAWpfVc6=y8pL?i-*wL& zccE?Dw`))J;&RB@a{Fzt^+Y8_kV|6zsRcz%KNqfZX@|j^mzS4kXJ^NMQQpr4l&W0S zaa`xLRIl6q`~E9CdT*R<@A>fL_dBcdzWtQQa-=(`s?la_xs-K>FOSR_v+QFhyRs?KE?zC!`4-P>FKA{ z-{0lt=E|B&E_wPcYVEYL_xJb9Z+6|6@$tvU$Ft4zcl828(?(R2S$$8x$no#rzh8cy zk*qv-+PVJcQPTY_w#F_L>?XMmEO2<_wL!V3~h>cfb_-iu;0!5e&uUh z$)OKV9-fy}6g3c1V%Qs(UtRs1$NTcd&Wd%Wxl5S3YZLmU={tWLK7|LJu*zr0?GrPb+8D_0(SdfL=nY-Piv71v*1U-!4->#M8WJEP7B zU+nxdy}lvD!EjmEt0zyE{ANgh@Ld>NNdhd6Nh*=C*8xCL>=M{32kbVEI%Q-;P-`?1 rN$&nEoqcips@}UX6wLY0BEcYgc&4%S6!SU2RuzM%tDnm{r-UW|Uft$t diff --git a/resources/builtin/image-800x800.png b/resources/builtin/image-800x800.png new file mode 100644 index 0000000000000000000000000000000000000000..9755de33f802f63e47277ef51fcffffdccf96db0 GIT binary patch literal 666 zcmeAS@N?(olHy`uVBq!ia0y~yU{(NOCT5_>GP!rZffQ$ePl)TI=U;)$|NsAM_{HyJ zU|{m`ba4!+V0?SGu}#@g;P^v_d+!%KxTjy>U%Afp)snRTX18X&`DM4J(=+Wg`xehH zF+Vtf+Q|aftI9f8)!nr(E74T!7Ep9*;SemEwCaBSytRSNpWDQgTslC4PZHlvzP^8P z^wn2U?vWao zagUM;$K?BN;rrj@OP*7K*vN6xdy2rOxN~qNPwsw+s61-4Pf7sl;yoJ$F0H-a2sL5J zb$O7_fN~S$Whd3kC_s&={B14pd-Y`DvP+^+eK6OzTseGWf8D$8=+g(~e%3CszgTa! zx|Kul637&ln#C7_`xXldnO&ak9;*R0>u1-5(t~>^$QAV6|FjfhnSi3S-S0m)3qmUU z|7;NbSHaf@bx+D+Q~L#a?N5%M?mx3w)k_%UFdtw@-`rC&&Dj6n$ptW<3oAWw0lS=q etk?x=`p0x!=aATp=dqa}6Fgo0T-G@yGywouTI3=C literal 0 HcmV?d00001 From 050538cf7e938876389afbd27f1357e539db8c44 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Apr 2017 10:53:56 -0700 Subject: [PATCH 51/56] Center icons on DiffusionCloneURIView Summary: These icons are off center currently. Test Plan: Review a clone uri in local repository. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17753 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/application/diffusion/diffusion-icons.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d19dc81c45..2a4b7ee272 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -14,7 +14,7 @@ return array( 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', - 'diffusion.pkg.css' => '91c5d3a6', + 'diffusion.pkg.css' => '30dd8a58', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => 'd678600a', + 'rsrc/css/application/diffusion/diffusion-icons.css' => '60c21c3b', 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', @@ -578,7 +578,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => 'd678600a', + 'diffusion-icons-css' => '60c21c3b', 'diffusion-readme-css' => '297373eb', 'diffusion-source-css' => '750add59', 'diviner-shared-css' => '896f1d43', diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index 0e00f6e46f..a645f5cb84 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -44,6 +44,6 @@ input.diffusion-clone-uri { } .diffusion-clone-uri-table th a.button .phui-icon-view { - left: 12px; + left: 15px; top: 7px; } From 7c61ace086fa634a67c822c2b35208f15a417834 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Apr 2017 11:22:06 -0700 Subject: [PATCH 52/56] Attach Diffusion Pagers to their ObjectBoxView Summary: Adds the ability to set a pager onto an object box directly and pick up appropriate styles. Test Plan: grep for renderTablePagerBox, test layouts with and without a pager. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12604 Differential Revision: https://secure.phabricator.com/D17754 --- resources/celerity/map.php | 12 ++++++------ .../DiffusionBranchTableController.php | 6 ++---- .../controller/DiffusionBrowseController.php | 18 +++++++----------- .../controller/DiffusionCompareController.php | 12 ++++-------- .../controller/DiffusionController.php | 6 ------ .../controller/DiffusionHistoryController.php | 6 ++---- .../controller/DiffusionLintController.php | 5 ++--- .../DiffusionRepositoryController.php | 13 ++++--------- .../controller/DiffusionTagListController.php | 6 ++---- src/view/phui/PHUIObjectBoxView.php | 11 +++++++++++ .../application/diffusion/diffusion-icons.css | 5 +++++ webroot/rsrc/css/phui/phui-object-box.css | 12 ++++++++++++ 12 files changed, 57 insertions(+), 55 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2a4b7ee272..9774488c4b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,12 +9,12 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '959330a2', + 'core.pkg.css' => 'd23d18e6', 'core.pkg.js' => '47a69358', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', - 'diffusion.pkg.css' => '30dd8a58', + 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', @@ -71,7 +71,7 @@ return array( 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => '60c21c3b', + 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', @@ -164,7 +164,7 @@ return array( 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', 'rsrc/css/phui/phui-list.css' => '12eb8ce6', - 'rsrc/css/phui/phui-object-box.css' => '8b289e3d', + 'rsrc/css/phui/phui-object-box.css' => '9cff003c', 'rsrc/css/phui/phui-pager.css' => '77d8a794', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-property-list-view.css' => '2dc7993f', @@ -578,7 +578,7 @@ return array( 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => '60c21c3b', + 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '297373eb', 'diffusion-source-css' => '750add59', 'diviner-shared-css' => '896f1d43', @@ -872,7 +872,7 @@ return array( 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', 'phui-list-view-css' => '12eb8ce6', - 'phui-object-box-css' => '8b289e3d', + 'phui-object-box-css' => '9cff003c', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => 'f12cbc9f', diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index 5c76469b5e..e5e033f416 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -57,7 +57,8 @@ final class DiffusionBranchTableController extends DiffusionController { $content = id(new PHUIObjectBoxView()) ->setHeaderText($repository->getName()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + ->setTable($table) + ->setPager($pager); } $crumbs = $this->buildCrumbs( @@ -66,8 +67,6 @@ final class DiffusionBranchTableController extends DiffusionController { )); $crumbs->setBorder(true); - $pager_box = $this->renderTablePagerBox($pager); - $header = id(new PHUIHeaderView()) ->setHeader(pht('Branches')) ->setHeaderIcon('fa-code-fork'); @@ -76,7 +75,6 @@ final class DiffusionBranchTableController extends DiffusionController { ->setHeader($header) ->setFooter(array( $content, - $pager_box, )); return $this->newPage() diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index b29dcf3d1e..4eb6144ede 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -370,7 +370,8 @@ final class DiffusionBrowseController extends DiffusionController { $browse_panel = id(new PHUIObjectBoxView()) ->setHeader($browse_header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($browse_table); + ->setTable($browse_table) + ->setPager($pager); $browse_panel->setShowHide( array(pht('Show Search')), @@ -395,7 +396,6 @@ final class DiffusionBrowseController extends DiffusionController { 'view' => 'browse', )); - $pager_box = $this->renderTablePagerBox($pager); $crumbs->setBorder(true); $view = id(new PHUITwoColumnView()) @@ -411,7 +411,6 @@ final class DiffusionBrowseController extends DiffusionController { array( $open_revisions, $readme, - $pager_box, )); if ($details) { @@ -489,14 +488,12 @@ final class DiffusionBrowseController extends DiffusionController { nonempty($drequest->getPath(), '/')); } - $box = id(new PHUIObjectBoxView()) + return id(new PHUIObjectBoxView()) ->setHeaderText($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + ->setTable($table) + ->setPager($pager); - $pager_box = $this->renderTablePagerBox($pager); - - return array($box, $pager_box); } private function renderGrepResults(array $results, $pattern) { @@ -1775,9 +1772,8 @@ final class DiffusionBrowseController extends DiffusionController { } $header = id(new PHUIHeaderView()) - ->setHeader(pht('Open Revisions')) - ->setSubheader( - pht('Recently updated open revisions affecting this file.')); + ->setHeader(pht('Recently Open Revisions')) + ->setHeaderIcon('fa-gear'); $view = id(new DifferentialRevisionListView()) ->setHeader($header) diff --git a/src/applications/diffusion/controller/DiffusionCompareController.php b/src/applications/diffusion/controller/DiffusionCompareController.php index 884e740309..a3104e6da4 100644 --- a/src/applications/diffusion/controller/DiffusionCompareController.php +++ b/src/applications/diffusion/controller/DiffusionCompareController.php @@ -92,6 +92,7 @@ final class DiffusionCompareController extends DiffusionController { array( 'view' => 'compare', )); + $crumbs->setBorder(true); $pager = id(new PHUIPagerView()) ->readFromRequest($request); @@ -310,16 +311,11 @@ final class DiffusionCompareController extends DiffusionController { $header = id(new PHUIHeaderView()) ->setHeader(pht('Commits')); - $object_box = id(new PHUIObjectBoxView()) + return id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($history_table); + ->setTable($history_table) + ->setPager($pager); - $pager_box = $this->renderTablePagerBox($pager); - - return array( - $object_box, - $pager_box, - ); } } diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index eb46211d99..a018eb3dbb 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -317,12 +317,6 @@ abstract class DiffusionController extends PhabricatorController { ->appendChild($body); } - protected function renderTablePagerBox(PHUIPagerView $pager) { - return id(new PHUIBoxView()) - ->addMargin(PHUI::MARGIN_LARGE) - ->appendChild($pager); - } - protected function renderCommitHashTag(DiffusionRequest $drequest) { $stable_commit = $drequest->getStableCommit(); $commit = phutil_tag( diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index c9df551c43..1a29a4263a 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -57,7 +57,8 @@ final class DiffusionHistoryController extends DiffusionController { $history_panel = id(new PHUIObjectBoxView()) ->setHeader($history_header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($history_table); + ->setTable($history_table) + ->setPager($pager); $header = $this->buildHeader($drequest); @@ -69,13 +70,10 @@ final class DiffusionHistoryController extends DiffusionController { )); $crumbs->setBorder(true); - $pager_box = $this->renderTablePagerBox($pager); - $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( $history_panel, - $pager_box, )); return $this->newPage() diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index fbd059796a..980dc8d27c 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -456,7 +456,8 @@ final class DiffusionLintController extends DiffusionController { $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lint Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($table); + ->setTable($table) + ->setPager($pager); $crumbs = $this->buildCrumbs( array( @@ -465,7 +466,6 @@ final class DiffusionLintController extends DiffusionController { 'view' => 'lint', )); - $pager_box = $this->renderTablePagerBox($pager); $header = id(new PHUIHeaderView()) ->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName())) ->setHeaderIcon('fa-code'); @@ -474,7 +474,6 @@ final class DiffusionLintController extends DiffusionController { ->setHeader($header) ->setFooter(array( $content, - $pager_box, )); return $this->newPage() diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index a18f2da1d7..91b33bcaaa 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -354,9 +354,9 @@ final class DiffusionRepositoryController extends DiffusionController { } if ($repository->isSVN()) { - $label = pht('Checkout'); + $label = phutil_tag_div('diffusion-clone-label', pht('Checkout')); } else { - $label = pht('Clone'); + $label = phutil_tag_div('diffusion-clone-label', pht('Clone')); } $view->addProperty( @@ -686,15 +686,10 @@ final class DiffusionRepositoryController extends DiffusionController { $pager->setURI($browse_uri, 'offset'); if ($pager->willShowPagingControls()) { - $pager_box = $this->renderTablePagerBox($pager); - } else { - $pager_box = null; + $browse_panel->setPager($pager); } - return array( - $browse_panel, - $pager_box, - ); + return $browse_panel; } private function renderCloneURI( diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index 5abd93c37d..df3b356f5d 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -87,15 +87,13 @@ final class DiffusionTagListController extends DiffusionController { $box = id(new PHUIObjectBoxView()) ->setHeaderText($repository->getDisplayName()) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setTable($view); - - $pager_box = $this->renderTablePagerBox($pager); + ->setTable($view) + ->setPager($pager); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( $box, - $pager_box, )); return $this->newPage() diff --git a/src/view/phui/PHUIObjectBoxView.php b/src/view/phui/PHUIObjectBoxView.php index 1e1d9898ec..26044ef3a0 100644 --- a/src/view/phui/PHUIObjectBoxView.php +++ b/src/view/phui/PHUIObjectBoxView.php @@ -18,6 +18,7 @@ final class PHUIObjectBoxView extends AphrontTagView { private $table; private $collapsed = false; private $anchor; + private $pager; private $showAction; private $hideAction; @@ -126,6 +127,11 @@ final class PHUIObjectBoxView extends AphrontTagView { return $this; } + public function setPager(PHUIPagerView $pager) { + $this->pager = $pager; + return $this; + } + public function setAnchor(PhabricatorAnchorView $anchor) { $this->anchor = $anchor; return $this; @@ -294,6 +300,10 @@ final class PHUIObjectBoxView extends AphrontTagView { $lists = null; } + $pager = null; + if ($this->pager) { + $pager = phutil_tag_div('phui-object-box-pager', $this->pager); + } $content = array( ($this->showHideOpen == false ? $this->anchor : null), @@ -308,6 +318,7 @@ final class PHUIObjectBoxView extends AphrontTagView { ($this->showHideOpen == true ? $this->anchor : null), $lists, $this->table, + $pager, $this->renderChildren(), ); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index a645f5cb84..8edc034975 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -13,6 +13,11 @@ input.diffusion-clone-uri { color: {$lightgreytext}; } +.diffusion-clone-label { + height: 30px; + line-height: 28px; +} + .diffusion-browse-name { margin-left: 8px; } diff --git a/webroot/rsrc/css/phui/phui-object-box.css b/webroot/rsrc/css/phui/phui-object-box.css index dee0525944..4999a4c2c2 100644 --- a/webroot/rsrc/css/phui/phui-object-box.css +++ b/webroot/rsrc/css/phui/phui-object-box.css @@ -146,3 +146,15 @@ div.phui-object-box.phui-object-box-flush { padding: 4px 8px; background-color: {$lightgreybackground}; } + +/* - Pager at the bottom ---------------------------------------------------- */ + +.phui-object-box-pager { + background-color: {$bluebackground}; + border-top: 1px solid {$lightblueborder}; +} + +.phui-object-box-pager a.button { + margin-top: 8px; + margin-bottom: 8px; +} From d3546f94c15f27bab707d44d6b8a40e2fdeef6a2 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Apr 2017 10:44:11 -0700 Subject: [PATCH 53/56] Improve diffusion readme layout Summary: Uses more standard objects and more padding for reading. Removes the ToC, which is visually broken anyways. Test Plan: Review a README.md in a local repository. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17752 --- resources/celerity/map.php | 4 ++-- .../diffusion/view/DiffusionReadmeView.php | 18 ++---------------- .../application/diffusion/diffusion-readme.css | 5 +++++ 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9774488c4b..e63f121f14 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -72,7 +72,7 @@ return array( 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', - 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', + 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', @@ -579,7 +579,7 @@ return array( 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', 'diffusion-icons-css' => 'a6a1e2ba', - 'diffusion-readme-css' => '297373eb', + 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', diff --git a/src/applications/diffusion/view/DiffusionReadmeView.php b/src/applications/diffusion/view/DiffusionReadmeView.php index 16924ddd98..b475fa88ac 100644 --- a/src/applications/diffusion/view/DiffusionReadmeView.php +++ b/src/applications/diffusion/view/DiffusionReadmeView.php @@ -73,18 +73,6 @@ final class DiffusionReadmeView extends DiffusionView { ->getOutput($markup_object, $markup_field); $engine = $markup_object->newMarkupEngine($markup_field); - $toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); - if ($toc) { - $toc = phutil_tag_div( - 'phabricator-remarkup-toc', - array( - phutil_tag_div( - 'phabricator-remarkup-toc-header', - pht('Table of Contents')), - $toc, - )); - $content = array($toc, $content); - } $readme_content = $content; $class = null; @@ -106,15 +94,13 @@ final class DiffusionReadmeView extends DiffusionView { } $readme_content = phutil_tag_div($class, $readme_content); - $header = id(new PHUIHeaderView()) - ->setHeader($readme_name); - $document = id(new PHUIDocumentViewPro()) ->setFluid(true) ->appendChild($readme_content); return id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText($readme_name) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($document) ->addClass('diffusion-readme-view'); } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-readme.css b/webroot/rsrc/css/application/diffusion/diffusion-readme.css index bb661bb2c8..b8e20af5cb 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-readme.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-readme.css @@ -11,4 +11,9 @@ .diffusion-readme-view .phui-document-container { border: none; + padding: 24px 32px; +} + +.diffusion-readme-view .phabricator-remarkup-toc { + display: none; } From ede23efcc73dce3a461c3c9ee2421bd06be8b96d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Apr 2017 11:30:15 -0700 Subject: [PATCH 54/56] Punch up grey button border grey 10% Summary: Visually these are hard to see on blue backgrounds, adds a touch more contrast. Fixes T12604 Test Plan: View as pager and dialog Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12604 Differential Revision: https://secure.phabricator.com/D17755 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-button.css | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e63f121f14..7937f6556e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'd23d18e6', + 'core.pkg.css' => '005d943f', 'core.pkg.js' => '47a69358', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', @@ -139,7 +139,7 @@ return array( 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '5a5ae137', + 'rsrc/css/phui/phui-button.css' => '8d23596a', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -840,7 +840,7 @@ return array( 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '5a5ae137', + 'phui-button-css' => '8d23596a', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 936a43f4bc..09f2233a6c 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -82,7 +82,7 @@ a.grey, a.grey:visited { background-color: #F7F7F9; background-image: linear-gradient(to bottom, #ffffff, #f1f0f1); - border: 1px solid rgba({$alphablue},.2); + border: 1px solid rgba({$alphablue}, 0.3); color: {$darkgreytext}; } @@ -131,7 +131,7 @@ button:hover { a.button.grey:hover, button.grey:hover { background-image: linear-gradient(to bottom, #ffffff, #eeebec); - border-color: rgba({$alphablue}, 0.3); + border-color: rgba({$alphablue}, 0.4); transition: 0.1s; } From 5c1e4488dedafda08684b33a8a4786cf687d2811 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 21 Apr 2017 12:42:50 -0700 Subject: [PATCH 55/56] Remove all "Phabricator Bot" code Summary: Closes T7829 as wontfix. Closes T7965 as wontfix. Closes T7800 as wontfix. Closes T2731 as wontfix. Closes T1271 as wontfix. We aren't maintaining this at all (see, e.g., T7829) and a user reported a technically accurate security issue via HackerOne: Just throw it away until we get to the eventual Conphernece bot/API update and can do this stuff correctly. Test Plan: Grepped for `phabricatorbot`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7965, T7829, T7800, T2731, T1271 Differential Revision: https://secure.phabricator.com/D17756 --- src/__phutil_library_map__.php | 36 --- src/docs/tech/chatbot.diviner | 87 ------ .../daemon/bot/PhabricatorBot.php | 170 ----------- .../daemon/bot/PhabricatorBotMessage.php | 52 ---- .../PhabricatorBotFlowdockProtocolAdapter.php | 93 ------ .../PhabricatorCampfireProtocolAdapter.php | 114 ------- .../adapter/PhabricatorIRCProtocolAdapter.php | 282 ------------------ .../adapter/PhabricatorProtocolAdapter.php | 62 ---- .../PhabricatorStreamingProtocolAdapter.php | 170 ----------- .../handler/PhabricatorBotDebugLogHandler.php | 17 -- .../PhabricatorBotFeedNotificationHandler.php | 180 ----------- .../bot/handler/PhabricatorBotHandler.php | 72 ----- .../bot/handler/PhabricatorBotLogHandler.php | 77 ----- .../handler/PhabricatorBotMacroHandler.php | 176 ----------- .../PhabricatorBotObjectNameHandler.php | 206 ------------- .../handler/PhabricatorBotSymbolHandler.php | 50 ---- .../handler/PhabricatorBotWhatsNewHandler.php | 43 --- .../bot/target/PhabricatorBotChannel.php | 12 - .../bot/target/PhabricatorBotTarget.php | 22 -- .../daemon/bot/target/PhabricatorBotUser.php | 12 - 20 files changed, 1933 deletions(-) delete mode 100644 src/docs/tech/chatbot.diviner delete mode 100644 src/infrastructure/daemon/bot/PhabricatorBot.php delete mode 100644 src/infrastructure/daemon/bot/PhabricatorBotMessage.php delete mode 100644 src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php delete mode 100644 src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php delete mode 100644 src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php delete mode 100644 src/infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php delete mode 100644 src/infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php delete mode 100644 src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php delete mode 100644 src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php delete mode 100644 src/infrastructure/daemon/bot/target/PhabricatorBotTarget.php delete mode 100644 src/infrastructure/daemon/bot/target/PhabricatorBotUser.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5f5623cb84..1efe9749e7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2089,20 +2089,6 @@ phutil_register_library_map(array( 'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php', 'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php', 'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php', - 'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php', - 'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php', - 'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php', - 'PhabricatorBotFeedNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php', - 'PhabricatorBotFlowdockProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php', - 'PhabricatorBotHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotHandler.php', - 'PhabricatorBotLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php', - 'PhabricatorBotMacroHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php', - 'PhabricatorBotMessage' => 'infrastructure/daemon/bot/PhabricatorBotMessage.php', - 'PhabricatorBotObjectNameHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php', - 'PhabricatorBotSymbolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php', - 'PhabricatorBotTarget' => 'infrastructure/daemon/bot/target/PhabricatorBotTarget.php', - 'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php', - 'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php', 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', @@ -2261,7 +2247,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', - 'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php', 'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php', 'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php', 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', @@ -2921,7 +2906,6 @@ phutil_register_library_map(array( 'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php', 'PhabricatorIDsSearchEngineExtension' => 'applications/search/engineextension/PhabricatorIDsSearchEngineExtension.php', 'PhabricatorIDsSearchField' => 'applications/search/field/PhabricatorIDsSearchField.php', - 'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php', 'PhabricatorIconDatasource' => 'applications/files/typeahead/PhabricatorIconDatasource.php', 'PhabricatorIconRemarkupRule' => 'applications/macro/markup/PhabricatorIconRemarkupRule.php', 'PhabricatorIconSet' => 'applications/files/iconset/PhabricatorIconSet.php', @@ -3654,7 +3638,6 @@ phutil_register_library_map(array( 'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php', 'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php', - 'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php', 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', 'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php', @@ -3978,7 +3961,6 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php', 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', - 'PhabricatorStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php', 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php', 'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php', @@ -7150,20 +7132,6 @@ phutil_register_library_map(array( 'PhabricatorBoardRenderingEngine' => 'Phobject', 'PhabricatorBoardResponseEngine' => 'Phobject', 'PhabricatorBoolEditField' => 'PhabricatorEditField', - 'PhabricatorBot' => 'PhabricatorDaemon', - 'PhabricatorBotChannel' => 'PhabricatorBotTarget', - 'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotFlowdockProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter', - 'PhabricatorBotHandler' => 'Phobject', - 'PhabricatorBotLogHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotMessage' => 'Phobject', - 'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotTarget' => 'Phobject', - 'PhabricatorBotUser' => 'PhabricatorBotTarget', - 'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler', 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', @@ -7358,7 +7326,6 @@ phutil_register_library_map(array( 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PhabricatorCampfireProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter', 'PhabricatorCelerityApplication' => 'PhabricatorApplication', 'PhabricatorCelerityTestCase' => 'PhabricatorTestCase', 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', @@ -8115,7 +8082,6 @@ phutil_register_library_map(array( 'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorIDsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorIDsSearchField' => 'PhabricatorSearchField', - 'PhabricatorIRCProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorIconDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorIconRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorIconSet' => 'Phobject', @@ -8973,7 +8939,6 @@ phutil_register_library_map(array( 'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorPronounSetting' => 'PhabricatorSelectSetting', - 'PhabricatorProtocolAdapter' => 'Phobject', 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorQuery' => 'Phobject', 'PhabricatorQueryConstraint' => 'Phobject', @@ -9377,7 +9342,6 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'Phobject', 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', - 'PhabricatorStreamingProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorStringSetting' => 'PhabricatorSetting', 'PhabricatorSubmitEditField' => 'PhabricatorEditField', diff --git a/src/docs/tech/chatbot.diviner b/src/docs/tech/chatbot.diviner deleted file mode 100644 index 6f79e2e465..0000000000 --- a/src/docs/tech/chatbot.diviner +++ /dev/null @@ -1,87 +0,0 @@ -@title Chat Bot Technical Documentation -@group bot - -Configuring and extending the chat bot. - -= Overview = - -Phabricator includes a simple chat bot daemon, which is primarily intended as -an example of how you can write an external script that interfaces with -Phabricator over Conduit and does some kind of useful work. If you use IRC or -another supported chat protocol, you can also have the bot hang out in your -channel. - -NOTE: The chat bot is somewhat experimental and not very mature. - -= Configuring the Bot = - -The bot reads a JSON configuration file. You can find an example in: - - resources/chatbot/example_config.json - -These are the configuration values it reads: - - - `server` String, required, the server to connect to. - - `port` Int, optional, the port to connect to (defaults to 6667). - - `ssl` Bool, optional, whether to connect via SSL or not (defaults to - false). - - `nick` String, nickname to use. - - `user` String, optional, username to use (defaults to `nick`). - - `pass` String, optional, password for server. - - `nickpass` String, optional, password for NickServ. - - `join` Array, list of channels to join. - - `handlers` Array, list of handlers to run. These are like plugins for the - bot. - - `conduit.uri`, `conduit.token` Conduit configuration, - see below. - - `notification.channels` Notification configuration, see below. - -= Handlers = - -You specify a list of "handlers", which are basically plugins or modules for -the bot. These are the default handlers available: - - - @{class:PhabricatorBotObjectNameHandler} This handler looks for users - mentioning Phabricator objects like "T123" and "D345" in chat, looks them - up, and says their name with a link to the object. Requires conduit. - - @{class:PhabricatorBotFeedNotificationHandler} This handler posts - notifications about changes to revisions to the channels listed in - `notification.channels`. - - @{class:PhabricatorBotLogHandler} This handler records chatlogs which can - be browsed in the Phabricator web interface. - - @{class:PhabricatorBotSymbolHandler} This handler posts responses to lookups - for symbols in Diffusion - - @{class:PhabricatorBotMacroHandler} This handler looks for users mentioning - macros, if found will convert image to ASCII and output in chat. Configure - with `macro.size` and `macro.aspect` - -You can also write your own handlers, by extending -@{class:PhabricatorBotHandler}. - -= Conduit = - -Some handlers (e.g., @{class:PhabricatorBotObjectNameHandler}) need to read data -from Phabricator over Conduit, Phabricator's HTTP API. You can use this method -to allow other scripts or programs to access Phabricator's data from different -servers and in different languages. - -To allow the bot to access Conduit, you need to create a user that it can login -with. To do this, login to Phabricator as an administrator and go to -`People -> Create New Account`. Create a new account and flag them as a -"Bot/Script". Then in your configuration file, set these parameters: - - - `conduit.uri` The URI for your Phabricator install, like - `http://phabricator.example.com/` - - `conduit.token` The user's conduit API token, from the "Conduit API Tokens" - tab in the user's administrative view. - -Now the bot should be able to connect to Phabricator via Conduit. - -= Starting the Bot = - -The bot is a Phabricator daemon, so start it with `phd`: - - ./bin/phd launch phabricatorbot - -If you have issues you can try `debug` instead of `launch`, see -@{article:Managing Daemons with phd} for more information. diff --git a/src/infrastructure/daemon/bot/PhabricatorBot.php b/src/infrastructure/daemon/bot/PhabricatorBot.php deleted file mode 100644 index b6efdb8b6d..0000000000 --- a/src/infrastructure/daemon/bot/PhabricatorBot.php +++ /dev/null @@ -1,170 +0,0 @@ -getArgv(); - if (count($argv) !== 1) { - throw new Exception( - pht( - 'Usage: %s %s', - __CLASS__, - '')); - } - - $json_raw = Filesystem::readFile($argv[0]); - try { - $config = phutil_json_decode($json_raw); - } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( - pht("File '%s' is not valid JSON!", $argv[0]), - $ex); - } - - $nick = idx($config, 'nick', 'phabot'); - $handlers = idx($config, 'handlers', array()); - $protocol_adapter_class = idx( - $config, - 'protocol-adapter', - 'PhabricatorIRCProtocolAdapter'); - $this->pollFrequency = idx($config, 'poll-frequency', 1); - - $this->config = $config; - - foreach ($handlers as $handler) { - $obj = newv($handler, array($this)); - $this->handlers[] = $obj; - } - - $ca_bundle = idx($config, 'https.cabundle'); - if ($ca_bundle) { - HTTPSFuture::setGlobalCABundleFromPath($ca_bundle); - } - - $conduit_uri = idx($config, 'conduit.uri'); - if ($conduit_uri) { - $conduit_token = idx($config, 'conduit.token'); - - // Normalize the path component of the URI so users can enter the - // domain without the "/api/" part. - $conduit_uri = new PhutilURI($conduit_uri); - - $conduit_host = (string)$conduit_uri->setPath('/'); - $conduit_uri = (string)$conduit_uri->setPath('/api/'); - - $conduit = new ConduitClient($conduit_uri); - if ($conduit_token) { - $conduit->setConduitToken($conduit_token); - } else { - $conduit_user = idx($config, 'conduit.user'); - $conduit_cert = idx($config, 'conduit.cert'); - - $response = $conduit->callMethodSynchronous( - 'conduit.connect', - array( - 'client' => __CLASS__, - 'clientVersion' => '1.0', - 'clientDescription' => php_uname('n').':'.$nick, - 'host' => $conduit_host, - 'user' => $conduit_user, - 'certificate' => $conduit_cert, - )); - } - - $this->conduit = $conduit; - } - - // Instantiate Protocol Adapter, for now follow same technique as - // handler instantiation - $this->protocolAdapter = newv($protocol_adapter_class, array()); - $this->protocolAdapter - ->setConfig($this->config) - ->connect(); - - $this->runLoop(); - - $this->protocolAdapter->disconnect(); - } - - public function getConfig($key, $default = null) { - return idx($this->config, $key, $default); - } - - private function runLoop() { - do { - PhabricatorCaches::destroyRequestCache(); - - $this->stillWorking(); - - $messages = $this->protocolAdapter->getNextMessages($this->pollFrequency); - if (count($messages) > 0) { - foreach ($messages as $message) { - $this->routeMessage($message); - } - } - - foreach ($this->handlers as $handler) { - $handler->runBackgroundTasks(); - } - } while (!$this->shouldExit()); - - } - - public function writeMessage(PhabricatorBotMessage $message) { - return $this->protocolAdapter->writeMessage($message); - } - - private function routeMessage(PhabricatorBotMessage $message) { - $ignore = $this->getConfig('ignore'); - if ($ignore) { - $sender = $message->getSender(); - if ($sender && in_array($sender->getName(), $ignore)) { - return; - } - } - - if ($message->getCommand() == 'LOG') { - $this->log('[LOG] '.$message->getBody()); - } - - foreach ($this->handlers as $handler) { - try { - $handler->receiveMessage($message); - } catch (Exception $ex) { - phlog($ex); - } - } - } - - public function getAdapter() { - return $this->protocolAdapter; - } - - public function getConduit() { - if (empty($this->conduit)) { - throw new Exception( - pht( - "This bot is not configured with a Conduit uplink. Set '%s' and ". - "'%s' in the configuration to connect.", - 'conduit.uri', - 'conduit.token')); - } - return $this->conduit; - } - -} diff --git a/src/infrastructure/daemon/bot/PhabricatorBotMessage.php b/src/infrastructure/daemon/bot/PhabricatorBotMessage.php deleted file mode 100644 index 64de22f2cf..0000000000 --- a/src/infrastructure/daemon/bot/PhabricatorBotMessage.php +++ /dev/null @@ -1,52 +0,0 @@ -public = true; - } - - public function setSender(PhabricatorBotTarget $sender = null) { - $this->sender = $sender; - return $this; - } - - public function getSender() { - return $this->sender; - } - - public function setCommand($command) { - $this->command = $command; - return $this; - } - - public function getCommand() { - return $this->command; - } - - public function setBody($body) { - $this->body = $body; - return $this; - } - - public function getBody() { - return $this->body; - } - - public function setTarget(PhabricatorBotTarget $target = null) { - $this->target = $target; - return $this; - } - - public function getTarget() { - return $this->target; - } - -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php deleted file mode 100644 index eede9d5142..0000000000 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php +++ /dev/null @@ -1,93 +0,0 @@ -getConfig('flowdock.organization'); - if (empty($organization)) { - $this->getConfig('organization'); - } - if (empty($organization)) { - throw new Exception( - '"flowdock.organization" configuration variable not set'); - } - - - $ssl = $this->getConfig('ssl'); - - $url = ($ssl) ? 'https://' : 'http://'; - $url .= "{$this->authtoken}@stream.flowdock.com"; - $url .= "/flows/{$organization}/{$channel}"; - return $url; - } - - protected function processMessage(array $m_obj) { - $command = null; - switch ($m_obj['event']) { - case 'message': - $command = 'MESSAGE'; - break; - default: - // For now, ignore anything which we don't otherwise know about. - break; - } - - if ($command === null) { - return false; - } - - // TODO: These should be usernames, not user IDs. - $sender = id(new PhabricatorBotUser()) - ->setName($m_obj['user']); - - $target = id(new PhabricatorBotChannel()) - ->setName($m_obj['flow']); - - return id(new PhabricatorBotMessage()) - ->setCommand($command) - ->setSender($sender) - ->setTarget($target) - ->setBody($m_obj['content']); - } - - public function writeMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - $this->speak( - $message->getBody(), - $message->getTarget()); - break; - } - } - - private function speak( - $body, - PhabricatorBotTarget $flow) { - // The $flow->getName() returns the flow's UUID, - // as such, the Flowdock API does not require the organization - // to be specified in the URI - $this->performPost( - '/messages', - array( - 'flow' => $flow->getName(), - 'event' => 'message', - 'content' => $body, - )); - } - - public function __destruct() { - if ($this->readHandles) { - foreach ($this->readHandles as $read_handle) { - curl_multi_remove_handle($this->multiHandle, $read_handle); - curl_close($read_handle); - } - } - - curl_multi_close($this->multiHandle); - } -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php deleted file mode 100644 index eee257880b..0000000000 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php +++ /dev/null @@ -1,114 +0,0 @@ -getConfig('ssl'); - - $url = ($ssl) ? 'https://' : 'http://'; - $url .= "streaming.campfirenow.com/room/{$channel}/live.json"; - - return $url; - } - - protected function processMessage(array $m_obj) { - $command = null; - switch ($m_obj['type']) { - case 'TextMessage': - $command = 'MESSAGE'; - break; - case 'PasteMessage': - $command = 'PASTE'; - break; - default: - // For now, ignore anything which we don't otherwise know about. - break; - } - - if ($command === null) { - return false; - } - - // TODO: These should be usernames, not user IDs. - $sender = id(new PhabricatorBotUser()) - ->setName($m_obj['user_id']); - - $target = id(new PhabricatorBotChannel()) - ->setName($m_obj['room_id']); - - return id(new PhabricatorBotMessage()) - ->setCommand($command) - ->setSender($sender) - ->setTarget($target) - ->setBody($m_obj['body']); - } - - public function writeMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - $this->speak( - $message->getBody(), - $message->getTarget()); - break; - case 'SOUND': - $this->speak( - $message->getBody(), - $message->getTarget(), - 'SoundMessage'); - break; - case 'PASTE': - $this->speak( - $message->getBody(), - $message->getTarget(), - 'PasteMessage'); - break; - } - } - - protected function joinRoom($room_id) { - $this->performPost("/room/{$room_id}/join.json"); - $this->inRooms[$room_id] = true; - } - - private function leaveRoom($room_id) { - $this->performPost("/room/{$room_id}/leave.json"); - unset($this->inRooms[$room_id]); - } - - private function speak( - $message, - PhabricatorBotTarget $channel, - $type = 'TextMessage') { - - $room_id = $channel->getName(); - - $this->performPost( - "/room/{$room_id}/speak.json", - array( - 'message' => array( - 'type' => $type, - 'body' => $message, - ), - )); - } - - public function __destruct() { - foreach ($this->inRooms as $room_id => $ignored) { - $this->leaveRoom($room_id); - } - - if ($this->readHandles) { - foreach ($this->readHandles as $read_handle) { - curl_multi_remove_handle($this->multiHandle, $read_handle); - curl_close($read_handle); - } - } - - curl_multi_close($this->multiHandle); - } -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php deleted file mode 100644 index eee4974877..0000000000 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php +++ /dev/null @@ -1,282 +0,0 @@ -getConfig('network', $this->getConfig('server')); - } - - // Hash map of command translations - public static $commandTranslations = array( - 'PRIVMSG' => 'MESSAGE', - ); - - public function connect() { - $nick = $this->getConfig('nick', 'phabot'); - $server = $this->getConfig('server'); - $port = $this->getConfig('port', 6667); - $pass = $this->getConfig('pass'); - $ssl = $this->getConfig('ssl', false); - $user = $this->getConfig('user', $nick); - - if (!preg_match('/^[A-Za-z0-9_`[{}^|\]\\-]+$/', $nick)) { - throw new Exception( - pht( - "Nickname '%s' is invalid!", - $nick)); - } - - $errno = null; - $error = null; - if (!$ssl) { - $socket = fsockopen($server, $port, $errno, $error); - } else { - $socket = fsockopen('ssl://'.$server, $port, $errno, $error); - } - if (!$socket) { - throw new Exception(pht('Failed to connect, #%d: %s', $errno, $error)); - } - $ok = stream_set_blocking($socket, false); - if (!$ok) { - throw new Exception(pht('Failed to set stream nonblocking.')); - } - - $this->socket = $socket; - if ($pass) { - $this->write("PASS {$pass}"); - } - $this->write("NICK {$nick}"); - $this->write("USER {$user} 0 * :{$user}"); - } - - public function getNextMessages($poll_frequency) { - $messages = array(); - - $read = array($this->socket); - if (strlen($this->writeBuffer)) { - $write = array($this->socket); - } else { - $write = array(); - } - $except = array(); - - $ok = @stream_select($read, $write, $except, $timeout_sec = 1); - if ($ok === false) { - // We may have been interrupted by a signal, like a SIGINT. Try - // selecting again. If the second select works, conclude that the failure - // was most likely because we were signaled. - $ok = @stream_select($read, $write, $except, $timeout_sec = 0); - if ($ok === false) { - throw new Exception(pht('%s failed!', 'stream_select()')); - } - } - - if ($read) { - // Test for connection termination; in PHP, fread() off a nonblocking, - // closed socket is empty string. - if (feof($this->socket)) { - // This indicates the connection was terminated on the other side, - // just exit via exception and let the overseer restart us after a - // delay so we can reconnect. - throw new Exception(pht('Remote host closed connection.')); - } - do { - $data = fread($this->socket, 4096); - if ($data === false) { - throw new Exception(pht('%s failed!', 'fread()')); - } else { - $messages[] = id(new PhabricatorBotMessage()) - ->setCommand('LOG') - ->setBody('>>> '.$data); - $this->readBuffer .= $data; - } - } while (strlen($data)); - } - - if ($write) { - do { - $len = fwrite($this->socket, $this->writeBuffer); - if ($len === false) { - throw new Exception(pht('%s failed!', 'fwrite()')); - } else if ($len === 0) { - break; - } else { - $messages[] = id(new PhabricatorBotMessage()) - ->setCommand('LOG') - ->setBody('>>> '.substr($this->writeBuffer, 0, $len)); - $this->writeBuffer = substr($this->writeBuffer, $len); - } - } while (strlen($this->writeBuffer)); - } - - while (($m = $this->processReadBuffer()) !== false) { - if ($m !== null) { - $messages[] = $m; - } - } - - return $messages; - } - - private function write($message) { - $this->writeBuffer .= $message."\r\n"; - return $this; - } - - public function writeMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - case 'PASTE': - $name = $message->getTarget()->getName(); - $body = $message->getBody(); - $this->write("PRIVMSG {$name} :{$body}"); - return true; - default: - return false; - } - } - - private function processReadBuffer() { - $until = strpos($this->readBuffer, "\r\n"); - if ($until === false) { - return false; - } - - $message = substr($this->readBuffer, 0, $until); - $this->readBuffer = substr($this->readBuffer, $until + 2); - - $pattern = - '/^'. - '(?::(?P(\S+?))(?:!\S*)? )?'. // This may not be present. - '(?P[A-Z0-9]+) '. - '(?P.*)'. - '$/'; - - $matches = null; - if (!preg_match($pattern, $message, $matches)) { - throw new Exception("Unexpected message from server: {$message}"); - } - - if ($this->handleIRCProtocol($matches)) { - return null; - } - - $command = $this->getBotCommand($matches['command']); - list($target, $body) = $this->parseMessageData($command, $matches['data']); - - if (!strlen($matches['sender'])) { - $sender = null; - } else { - $sender = id(new PhabricatorBotUser()) - ->setName($matches['sender']); - } - - $bot_message = id(new PhabricatorBotMessage()) - ->setSender($sender) - ->setCommand($command) - ->setTarget($target) - ->setBody($body); - - return $bot_message; - } - - private function handleIRCProtocol(array $matches) { - $data = $matches['data']; - switch ($matches['command']) { - case '433': // Nickname already in use - // If we receive this error, try appending "-1", "-2", etc. to the nick - $this->nickIncrement++; - $nick = $this->getConfig('nick', 'phabot').'-'.$this->nickIncrement; - $this->write("NICK {$nick}"); - return true; - case '422': // Error - no MOTD - case '376': // End of MOTD - $nickpass = $this->getConfig('nickpass'); - if ($nickpass) { - $this->write("PRIVMSG nickserv :IDENTIFY {$nickpass}"); - } - $join = $this->getConfig('join'); - if (!$join) { - throw new Exception(pht('Not configured to join any channels!')); - } - foreach ($join as $channel) { - $this->write("JOIN {$channel}"); - } - return true; - case 'PING': - $this->write("PONG {$data}"); - return true; - } - - return false; - } - - private function getBotCommand($irc_command) { - if (isset(self::$commandTranslations[$irc_command])) { - return self::$commandTranslations[$irc_command]; - } - - // We have no translation for this command, use as-is - return $irc_command; - } - - private function parseMessageData($command, $data) { - switch ($command) { - case 'MESSAGE': - $matches = null; - if (preg_match('/^(\S+)\s+:?(.*)$/', $data, $matches)) { - - $target_name = $matches[1]; - if (strncmp($target_name, '#', 1) === 0) { - $target = id(new PhabricatorBotChannel()) - ->setName($target_name); - } else { - $target = id(new PhabricatorBotUser()) - ->setName($target_name); - } - - return array( - $target, - rtrim($matches[2], "\r\n"), - ); - } - break; - } - - // By default we assume there is no target, only a body - return array( - null, - $data, - ); - } - - public function disconnect() { - // NOTE: FreeNode doesn't show quit messages if you've recently joined a - // channel, presumably to prevent some kind of abuse. If you're testing - // this, you may need to stay connected to the network for a few minutes - // before it works. If you disconnect too quickly, the server will replace - // your message with a "Client Quit" message. - - $quit = $this->getConfig('quit', pht('Shutting down.')); - $this->write("QUIT :{$quit}"); - - // Flush the write buffer. - while (strlen($this->writeBuffer)) { - $this->getNextMessages(0); - } - - @fclose($this->socket); - $this->socket = null; - } -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php deleted file mode 100644 index 89283264b8..0000000000 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php +++ /dev/null @@ -1,62 +0,0 @@ -config = $config; - return $this; - } - - public function getConfig($key, $default = null) { - return idx($this->config, $key, $default); - } - - /** - * Performs any connection logic necessary for the protocol - */ - abstract public function connect(); - - /** - * Disconnect from the service. - */ - public function disconnect() { - return; - } - - /** - * This is the spout for messages coming in from the protocol. - * This will be called in the main event loop of the bot daemon - * So if if doesn't implement some sort of blocking timeout - * (e.g. select-based socket polling), it should at least sleep - * for some period of time in order to not overwhelm the processor. - * - * @param Int $poll_frequency The number of seconds between polls - */ - abstract public function getNextMessages($poll_frequency); - - /** - * This is the output mechanism for the protocol. - * - * @param PhabricatorBotMessage $message The message to write - */ - abstract public function writeMessage(PhabricatorBotMessage $message); - - /** - * String identifying the service type the adapter provides access to, like - * "irc", "campfire", "flowdock", "hipchat", etc. - */ - abstract public function getServiceType(); - - /** - * String identifying the service name the adapter is connecting to. This is - * used to distinguish between instances of a service. For example, for IRC, - * this should return the IRC network the client is connecting to. - */ - abstract public function getServiceName(); - -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php deleted file mode 100644 index 95955c1c2a..0000000000 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php +++ /dev/null @@ -1,170 +0,0 @@ -server); - return $uri->getDomain(); - } - - public function connect() { - $this->server = $this->getConfig('server'); - $this->authtoken = $this->getConfig('authtoken'); - $rooms = $this->getConfig('join'); - - // First, join the room - if (!$rooms) { - throw new Exception(pht('Not configured to join any rooms!')); - } - - $this->readBuffers = array(); - - // Set up our long poll in a curl multi request so we can - // continue running while it executes in the background - $this->multiHandle = curl_multi_init(); - $this->readHandles = array(); - - foreach ($rooms as $room_id) { - $this->joinRoom($room_id); - - // Set up the curl stream for reading - $url = $this->buildStreamingUrl($room_id); - $ch = $this->readHandles[$url] = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt( - $ch, - CURLOPT_USERPWD, - $this->authtoken.':x'); - - curl_setopt( - $ch, - CURLOPT_HTTPHEADER, - array('Content-type: application/json')); - curl_setopt( - $ch, - CURLOPT_WRITEFUNCTION, - array($this, 'read')); - curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); - curl_setopt($ch, CURLOPT_TIMEOUT, 0); - - curl_multi_add_handle($this->multiHandle, $ch); - - // Initialize read buffer - $this->readBuffers[$url] = ''; - } - - $this->active = null; - $this->blockingMultiExec(); - } - - protected function joinRoom($room_id) { - // Optional hook, by default, do nothing - } - - // This is our callback for the background curl multi-request. - // Puts the data read in on the readBuffer for processing. - private function read($ch, $data) { - $info = curl_getinfo($ch); - $length = strlen($data); - $this->readBuffers[$info['url']] .= $data; - return $length; - } - - private function blockingMultiExec() { - do { - $status = curl_multi_exec($this->multiHandle, $this->active); - } while ($status == CURLM_CALL_MULTI_PERFORM); - - // Check for errors - if ($status != CURLM_OK) { - throw new Exception( - pht('Phabricator Bot had a problem reading from stream.')); - } - } - - public function getNextMessages($poll_frequency) { - $messages = array(); - - if (!$this->active) { - throw new Exception(pht('Phabricator Bot stopped reading from stream.')); - } - - // Prod our http request - curl_multi_select($this->multiHandle, $poll_frequency); - $this->blockingMultiExec(); - - // Process anything waiting on the read buffer - while ($m = $this->processReadBuffer()) { - $messages[] = $m; - } - - return $messages; - } - - private function processReadBuffer() { - foreach ($this->readBuffers as $url => &$buffer) { - $until = strpos($buffer, "}\r"); - if ($until == false) { - continue; - } - - $message = substr($buffer, 0, $until + 1); - $buffer = substr($buffer, $until + 2); - - $m_obj = phutil_json_decode($message); - if ($message = $this->processMessage($m_obj)) { - return $message; - } - } - - // If we're here, there's nothing to process - return false; - } - - protected function performPost($endpoint, $data = null) { - $uri = new PhutilURI($this->server); - $uri->setPath($endpoint); - - $payload = json_encode($data); - - list($output) = id(new HTTPSFuture($uri)) - ->setMethod('POST') - ->addHeader('Content-Type', 'application/json') - ->addHeader('Authorization', $this->getAuthorizationHeader()) - ->setData($payload) - ->resolvex(); - - $output = trim($output); - if (strlen($output)) { - return phutil_json_decode($output); - } - - return true; - } - - protected function getAuthorizationHeader() { - return 'Basic '.$this->getEncodedAuthToken(); - } - - protected function getEncodedAuthToken() { - return base64_encode($this->authtoken.':x'); - } - - abstract protected function buildStreamingUrl($channel); - - abstract protected function processMessage(array $raw_object); - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php deleted file mode 100644 index eb4f9b24a9..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php +++ /dev/null @@ -1,17 +0,0 @@ -getCommand()) { - case 'LOG': - echo addcslashes( - $message->getBody(), - "\0..\37\177..\377"); - echo "\n"; - break; - } - } -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php deleted file mode 100644 index 21eadf1569..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php +++ /dev/null @@ -1,180 +0,0 @@ -getConfig('notification.types'); - - if ($show) { - $obj_type = phid_get_type($story_objectphid); - if (!in_array(strtolower($obj_type), $show)) { - return false; - } - } - - $verbosity = $this->getConfig('notification.verbosity', 3); - - $verbs = array(); - - switch ($verbosity) { - case 2: - $verbs[] = array( - 'commented', - 'added', - 'changed', - 'resigned', - 'explained', - 'modified', - 'attached', - 'edited', - 'joined', - 'left', - 'removed', - ); - // fallthrough - case 1: - $verbs[] = array( - 'updated', - 'accepted', - 'requested', - 'planned', - 'claimed', - 'summarized', - 'commandeered', - 'assigned', - ); - // fallthrough - case 0: - $verbs[] = array( - 'created', - 'closed', - 'raised', - 'committed', - 'abandoned', - 'reclaimed', - 'reopened', - 'deleted', - ); - break; - - case 3: - default: - return true; - } - - $verbs = '/('.implode('|', array_mergev($verbs)).')/'; - - if (preg_match($verbs, $story_text)) { - return true; - } - - return false; - } - - public function receiveMessage(PhabricatorBotMessage $message) { - return; - } - - public function runBackgroundTasks() { - if ($this->startupDelay > 0) { - // the event loop runs every 1s so delay enough to fully conenct - $this->startupDelay--; - - return; - } - if ($this->lastSeenChronoKey == 0) { - // Since we only want to post notifications about new stories, skip - // everything that's happened in the past when we start up so we'll - // only process real-time stories. - $latest = $this->getConduit()->callMethodSynchronous( - 'feed.query', - array( - 'limit' => 1, - )); - - foreach ($latest as $story) { - if ($story['chronologicalKey'] > $this->lastSeenChronoKey) { - $this->lastSeenChronoKey = $story['chronologicalKey']; - } - } - - return; - } - - $config_max_pages = $this->getConfig('notification.max_pages', 5); - $config_page_size = $this->getConfig('notification.page_size', 10); - - $last_seen_chrono_key = $this->lastSeenChronoKey; - $chrono_key_cursor = 0; - - // Not efficient but works due to feed.query API - for ($max_pages = $config_max_pages; $max_pages > 0; $max_pages--) { - $stories = $this->getConduit()->callMethodSynchronous( - 'feed.query', - array( - 'limit' => $config_page_size, - 'after' => $chrono_key_cursor, - 'view' => 'text', - )); - - foreach ($stories as $story) { - if ($story['chronologicalKey'] == $last_seen_chrono_key) { - // Caught up on feed - return; - } - if ($story['chronologicalKey'] > $this->lastSeenChronoKey) { - // Keep track of newest seen story - $this->lastSeenChronoKey = $story['chronologicalKey']; - } - if (!$chrono_key_cursor || - $story['chronologicalKey'] < $chrono_key_cursor) { - // Keep track of oldest story on this page - $chrono_key_cursor = $story['chronologicalKey']; - } - - if (!$story['text'] || - !$this->shouldShowStory($story)) { - continue; - } - - $message = $story['text']; - - $story_object_type = phid_get_type($story['objectPHID']); - if (in_array($story_object_type, $this->typesNeedURI)) { - $objects = $this->getConduit()->callMethodSynchronous( - 'phid.lookup', - array( - 'names' => array($story['objectPHID']), - )); - $message .= ' '.$objects[$story['objectPHID']]['uri']; - } - - $channels = $this->getConfig('join'); - foreach ($channels as $channel_name) { - - $channel = id(new PhabricatorBotChannel()) - ->setName($channel_name); - - $this->writeMessage( - id(new PhabricatorBotMessage()) - ->setCommand('MESSAGE') - ->setTarget($channel) - ->setBody($message)); - } - } - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php deleted file mode 100644 index dad7cbd1be..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php +++ /dev/null @@ -1,72 +0,0 @@ -bot = $irc_bot; - } - - final protected function writeMessage(PhabricatorBotMessage $message) { - $this->bot->writeMessage($message); - return $this; - } - - final protected function getConduit() { - return $this->bot->getConduit(); - } - - final protected function getConfig($key, $default = null) { - return $this->bot->getConfig($key, $default); - } - - final protected function getURI($path) { - $base_uri = new PhutilURI($this->bot->getConfig('conduit.uri')); - $base_uri->setPath($path); - return (string)$base_uri; - } - - final protected function getServiceName() { - return $this->bot->getAdapter()->getServiceName(); - } - - final protected function getServiceType() { - return $this->bot->getAdapter()->getServiceType(); - } - - abstract public function receiveMessage(PhabricatorBotMessage $message); - - public function runBackgroundTasks() { - return; - } - - public function replyTo(PhabricatorBotMessage $original_message, $body) { - if ($original_message->getCommand() != 'MESSAGE') { - throw new Exception( - pht('Handler is trying to reply to something which is not a message!')); - } - - $reply = id(new PhabricatorBotMessage()) - ->setCommand('MESSAGE'); - - if ($original_message->getTarget()->isPublic()) { - // This is a public target, like a chatroom. Send the response to the - // chatroom. - $reply->setTarget($original_message->getTarget()); - } else { - // This is a private target, like a private message. Send the response - // back to the sender (presumably, we are the target). - $reply->setTarget($original_message->getSender()); - } - - $reply->setBody($body); - - return $this->writeMessage($reply); - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php deleted file mode 100644 index 4f0a9dee35..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php +++ /dev/null @@ -1,77 +0,0 @@ -getCommand()) { - case 'MESSAGE': - $target = $message->getTarget(); - if (!$target->isPublic()) { - // Don't log private messages, although maybe we should for debugging? - break; - } - - $target_name = $target->getName(); - - $logs = array( - array( - 'channel' => $target_name, - 'type' => 'mesg', - 'epoch' => time(), - 'author' => $message->getSender()->getName(), - 'message' => $message->getBody(), - 'serviceName' => $this->getServiceName(), - 'serviceType' => $this->getServiceType(), - ), - ); - - $this->futures[] = $this->getConduit()->callMethod( - 'chatlog.record', - array( - 'logs' => $logs, - )); - - $prompts = array( - '/where is the (chat)?log\?/i', - '/where am i\?/i', - '/what year is (this|it)\?/i', - ); - - $tell = false; - foreach ($prompts as $prompt) { - if (preg_match($prompt, $message->getBody())) { - $tell = true; - break; - } - } - - if ($tell) { - $response = $this->getURI( - '/chatlog/channel/'.phutil_escape_uri($target_name).'/'); - - $this->replyTo($message, $response); - } - - break; - } - } - - public function runBackgroundTasks() { - foreach ($this->futures as $key => $future) { - try { - if ($future->isReady()) { - unset($this->futures[$key]); - } - } catch (Exception $ex) { - unset($this->futures[$key]); - phlog($ex); - } - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php deleted file mode 100644 index 9e7a051a8d..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php +++ /dev/null @@ -1,176 +0,0 @@ -macros === false) { - return false; - } - - if ($this->macros !== null) { - return true; - } - - $macros = $this->getConduit()->callMethodSynchronous( - 'macro.query', - array()); - - // If we have no macros, cache `false` (meaning "no macros") and return - // immediately. - if (!$macros) { - $this->macros = false; - return false; - } - - $regexp = array(); - foreach ($macros as $macro_name => $macro) { - $regexp[] = preg_quote($macro_name, '/'); - } - $regexp = '/^('.implode('|', $regexp).')\z/'; - - $this->macros = $macros; - $this->regexp = $regexp; - - return true; - } - - public function receiveMessage(PhabricatorBotMessage $message) { - if (!$this->init()) { - return; - } - - switch ($message->getCommand()) { - case 'MESSAGE': - $message_body = $message->getBody(); - - $matches = null; - if (!preg_match($this->regexp, trim($message_body), $matches)) { - return; - } - - $macro = $matches[1]; - - $ascii = idx($this->macros[$macro], 'ascii'); - if ($ascii === false) { - return; - } - - if (!$ascii) { - $this->macros[$macro]['ascii'] = $this->rasterize( - $this->macros[$macro], - $this->getConfig('macro.size', 48), - $this->getConfig('macro.aspect', 0.66)); - $ascii = $this->macros[$macro]['ascii']; - } - - if ($ascii === false) { - // If we failed to rasterize the macro, bail out. - return; - } - - $target_name = $message->getTarget()->getName(); - foreach ($ascii as $line) { - $this->replyTo($message, $line); - } - break; - } - } - - public function rasterize($macro, $size, $aspect) { - try { - $image = $this->getConduit()->callMethodSynchronous( - 'file.download', - array( - 'phid' => $macro['filePHID'], - )); - $image = base64_decode($image); - } catch (Exception $ex) { - return false; - } - - if (!$image) { - return false; - } - - $img = @imagecreatefromstring($image); - if (!$img) { - return false; - } - - $sx = imagesx($img); - $sy = imagesy($img); - - if ($sx > $size || $sy > $size) { - $scale = max($sx, $sy) / $size; - $dx = floor($sx / $scale); - $dy = floor($sy / $scale); - } else { - $dx = $sx; - $dy = $sy; - } - - $dy = floor($dy * $aspect); - - $dst = imagecreatetruecolor($dx, $dy); - if (!$dst) { - return false; - } - imagealphablending($dst, false); - - $ok = imagecopyresampled( - $dst, $img, - 0, 0, - 0, 0, - $dx, $dy, - $sx, $sy); - - if (!$ok) { - return false; - } - - $map = array( - ' ', - '.', - ',', - ':', - ';', - '!', - '|', - '*', - '=', - '@', - '$', - '#', - ); - - $lines = array(); - - for ($ii = 0; $ii < $dy; $ii++) { - $buf = ''; - for ($jj = 0; $jj < $dx; $jj++) { - $c = imagecolorat($dst, $jj, $ii); - - $a = ($c >> 24) & 0xFF; - $r = ($c >> 16) & 0xFF; - $g = ($c >> 8) & 0xFF; - $b = ($c) & 0xFF; - - $luma = (255 - ((0.30 * $r) + (0.59 * $g) + (0.11 * $b))) / 256; - $luma *= ((127 - $a) / 127); - - $char = $map[max(0, floor($luma * count($map)))]; - $buf .= $char; - } - - $lines[] = $buf; - } - - return $lines; - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php deleted file mode 100644 index 11c92af2cf..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php +++ /dev/null @@ -1,206 +0,0 @@ -getCommand()) { - case 'MESSAGE': - $message = $original_message->getBody(); - $matches = null; - - $paste_ids = array(); - $commit_names = array(); - $vote_ids = array(); - $file_ids = array(); - $object_names = array(); - $output = array(); - - $pattern = - '@'. - '(?getConduit()->callMethodSynchronous( - 'phid.lookup', - array( - 'names' => $object_names, - )); - foreach ($objects as $object) { - $output[$object['phid']] = $object['fullName'].' - '.$object['uri']; - } - } - - if ($vote_ids) { - foreach ($vote_ids as $vote_id) { - $vote = $this->getConduit()->callMethodSynchronous( - 'slowvote.info', - array( - 'poll_id' => $vote_id, - )); - $output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question']. - ' '.pht('Come Vote').' '.$vote['uri']; - } - } - - if ($file_ids) { - foreach ($file_ids as $file_id) { - $file = $this->getConduit()->callMethodSynchronous( - 'file.info', - array( - 'id' => $file_id, - )); - $output[$file['phid']] = $file['objectName'].': '. - $file['uri'].' - '.$file['name']; - } - } - - if ($paste_ids) { - foreach ($paste_ids as $paste_id) { - $paste = $this->getConduit()->callMethodSynchronous( - 'paste.query', - array( - 'ids' => array($paste_id), - )); - $paste = head($paste); - - $output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '. - $paste['title']; - - if ($paste['language']) { - $output[$paste['phid']] .= ' ('.$paste['language'].')'; - } - - $user = $this->getConduit()->callMethodSynchronous( - 'user.query', - array( - 'phids' => array($paste['authorPHID']), - )); - $user = head($user); - if ($user) { - $output[$paste['phid']] .= ' by '.$user['userName']; - } - } - } - - if ($commit_names) { - $commits = $this->getConduit()->callMethodSynchronous( - 'diffusion.querycommits', - array( - 'names' => $commit_names, - )); - foreach ($commits['data'] as $commit) { - $output[$commit['phid']] = $commit['uri']; - } - } - - foreach ($output as $phid => $description) { - - // Don't mention the same object more than once every 10 minutes - // in public channels, so we avoid spamming the chat over and over - // again for discussions of a specific revision, for example. - - $target_name = $original_message->getTarget()->getName(); - if (empty($this->recentlyMentioned[$target_name])) { - $this->recentlyMentioned[$target_name] = array(); - } - - $quiet_until = idx( - $this->recentlyMentioned[$target_name], - $phid, - 0) + (60 * 10); - - if (time() < $quiet_until) { - // Remain quiet on this channel. - continue; - } - - $this->recentlyMentioned[$target_name][$phid] = time(); - $this->replyTo($original_message, $description); - } - break; - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php deleted file mode 100644 index 2e7ff01bef..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php +++ /dev/null @@ -1,50 +0,0 @@ -?" - */ -final class PhabricatorBotSymbolHandler extends PhabricatorBotHandler { - - public function receiveMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - $text = $message->getBody(); - - $matches = null; - if (!preg_match('/where(?: in the world)? is (\S+?)\?/i', - $text, $matches)) { - break; - } - - $symbol = $matches[1]; - $results = $this->getConduit()->callMethodSynchronous( - 'diffusion.findsymbols', - array( - 'name' => $symbol, - )); - - $default_uri = $this->getURI('/diffusion/symbol/'.$symbol.'/'); - - if (count($results) > 1) { - $response = pht( - "Multiple symbols named '%s': %s", - $symbol, - $default_uri); - } else if (count($results) == 1) { - $result = head($results); - $response = - $result['type'].' '. - $result['name'].' '. - '('.$result['language'].'): '. - nonempty($result['uri'], $default_uri); - } else { - $response = pht("No symbol '%s' found anywhere.", $symbol); - } - - $this->replyTo($message, $response); - - break; - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php deleted file mode 100644 index a7d7ad9051..0000000000 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php +++ /dev/null @@ -1,43 +0,0 @@ -getCommand()) { - case 'MESSAGE': - $message_body = $message->getBody(); - $now = time(); - - $prompt = '~what( i|\')?s new\?~i'; - if (preg_match($prompt, $message_body)) { - if ($now < $this->floodblock) { - return; - } - $this->floodblock = $now + 60; - $this->reportNew($message); - } - break; - } - } - - public function reportNew(PhabricatorBotMessage $message) { - $latest = $this->getConduit()->callMethodSynchronous( - 'feed.query', - array( - 'limit' => 5, - 'view' => 'text', - )); - - foreach ($latest as $feed_item) { - if (isset($feed_item['text'])) { - $this->replyTo($message, html_entity_decode($feed_item['text'])); - } - } - } - -} diff --git a/src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php b/src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php deleted file mode 100644 index 030df925b6..0000000000 --- a/src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php +++ /dev/null @@ -1,12 +0,0 @@ -name = $name; - return $this; - } - - public function getName() { - return $this->name; - } - - abstract public function isPublic(); - -} diff --git a/src/infrastructure/daemon/bot/target/PhabricatorBotUser.php b/src/infrastructure/daemon/bot/target/PhabricatorBotUser.php deleted file mode 100644 index 1bc0c82219..0000000000 --- a/src/infrastructure/daemon/bot/target/PhabricatorBotUser.php +++ /dev/null @@ -1,12 +0,0 @@ - Date: Fri, 21 Apr 2017 14:46:51 -0700 Subject: [PATCH 56/56] Fix fatal in Conpherence NUX state Summary: Fixes T12619. Test Plan: Faked `return array()` in ConpherneceThreadQuery, got a NUX instead of fatal. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12619 Differential Revision: https://secure.phabricator.com/D17764 --- .../controller/ConpherenceListController.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index fc73d98341..d993a1bd5e 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -89,13 +89,14 @@ final class ConpherenceListController extends ConpherenceController { default: $data = $this->loadDefaultParticipation($limit); $all_participation = $data['all_participation']; - - $conpherence_id = head($all_participation)->getConpherencePHID(); - $conpherence = id(new ConpherenceThreadQuery()) - ->setViewer($user) - ->withPHIDs(array($conpherence_id)) - ->needProfileImage(true) - ->executeOne(); + if ($all_participation) { + $conpherence_id = head($all_participation)->getConpherencePHID(); + $conpherence = id(new ConpherenceThreadQuery()) + ->setViewer($user) + ->withPHIDs(array($conpherence_id)) + ->needProfileImage(true) + ->executeOne(); + } // If $conpherence is null, NUX state will render break; }