From 23dc68604561e87738b98b1ee4a55166f886692d Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Tue, 26 Mar 2013 13:30:35 -0700 Subject: [PATCH] Conpherence - add per thread notification setting Summary: Introduces a new settings panel for Conpherence specific settings. Test Plan: started a thread with a test user, thus two participants total. Replied to conpherence, toggling notification settings in between. Verified 1 or 2 emails were sent as appropos to the current toggle. Toggled global setting and verified setting was updated in conpherences where nothing was specified. Verified setting conpherence setting overrides global setting. Reviewers: epriestley, chad Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T2521 Differential Revision: https://secure.phabricator.com/D5391 --- .../sql/patches/20130319.conpherence.sql | 2 + src/__celerity_resource_map__.php | 9 +- src/__phutil_library_map__.php | 4 + .../constants/ConpherenceSettings.php | 22 +++++ .../ConpherenceUpdateController.php | 11 +++ .../ConpherenceWidgetController.php | 71 +++++++++++++++- .../conpherence/editor/ConpherenceEditor.php | 30 ++++++- .../storage/ConpherenceParticipant.php | 13 +++ ...torSettingsPanelConpherencePreferences.php | 84 +++++++++++++++++++ .../storage/PhabricatorUserPreferences.php | 2 + .../patch/PhabricatorBuiltinPatchList.php | 6 +- .../application/conpherence/widget-pane.css | 10 +++ .../conpherence/behavior-pontificate.js | 7 -- .../conpherence/behavior-widget-pane.js | 30 ++++++- 14 files changed, 284 insertions(+), 17 deletions(-) create mode 100644 resources/sql/patches/20130319.conpherence.sql create mode 100644 src/applications/conpherence/constants/ConpherenceSettings.php create mode 100644 src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php diff --git a/resources/sql/patches/20130319.conpherence.sql b/resources/sql/patches/20130319.conpherence.sql new file mode 100644 index 0000000000..e18590e6bd --- /dev/null +++ b/resources/sql/patches/20130319.conpherence.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_conpherence.conpherence_participant + ADD settings LONGTEXT NOT NULL COLLATE utf8_bin AFTER behindTransactionPHID; diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index f7b035838e..580cc14da1 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -840,7 +840,7 @@ celerity_register_resource_map(array( ), 'conpherence-widget-pane-css' => array( - 'uri' => '/res/0e4a8ded/rsrc/css/application/conpherence/widget-pane.css', + 'uri' => '/res/bd8ca250/rsrc/css/application/conpherence/widget-pane.css', 'type' => 'css', 'requires' => array( @@ -1206,7 +1206,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-conpherence-pontificate' => array( - 'uri' => '/res/15263692/rsrc/js/application/conpherence/behavior-pontificate.js', + 'uri' => '/res/fe634761/rsrc/js/application/conpherence/behavior-pontificate.js', 'type' => 'js', 'requires' => array( @@ -1219,13 +1219,16 @@ celerity_register_resource_map(array( ), 'javelin-behavior-conpherence-widget-pane' => array( - 'uri' => '/res/43a0fe1b/rsrc/js/application/conpherence/behavior-widget-pane.js', + 'uri' => '/res/52b80633/rsrc/js/application/conpherence/behavior-widget-pane.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-behavior', 1 => 'javelin-dom', 2 => 'javelin-stratcom', + 3 => 'javelin-workflow', + 4 => 'javelin-util', + 5 => 'phabricator-notification', ), 'disk' => '/rsrc/js/application/conpherence/behavior-widget-pane.js', ), diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fe54457a79..5a6886f6c6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -240,6 +240,7 @@ phutil_register_library_map(array( 'ConpherencePeopleMenuEventListener' => 'applications/conpherence/events/ConpherencePeopleMenuEventListener.php', 'ConpherencePontificateControl' => 'applications/conpherence/view/ConpherencePontificateControl.php', 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', + 'ConpherenceSettings' => 'applications/conpherence/constants/ConpherenceSettings.php', 'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php', 'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php', 'ConpherenceTransaction' => 'applications/conpherence/storage/ConpherenceTransaction.php', @@ -1301,6 +1302,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php', 'PhabricatorSettingsPanelAccount' => 'applications/settings/panel/PhabricatorSettingsPanelAccount.php', 'PhabricatorSettingsPanelConduit' => 'applications/settings/panel/PhabricatorSettingsPanelConduit.php', + 'PhabricatorSettingsPanelConpherencePreferences' => 'applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php', 'PhabricatorSettingsPanelDiffPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelDiffPreferences.php', 'PhabricatorSettingsPanelDisplayPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php', 'PhabricatorSettingsPanelEmailAddresses' => 'applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php', @@ -1907,6 +1909,7 @@ phutil_register_library_map(array( 'ConpherencePeopleMenuEventListener' => 'PhutilEventListener', 'ConpherencePontificateControl' => 'AphrontFormControl', 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', + 'ConpherenceSettings' => 'ConpherenceConstants', 'ConpherenceThread' => array( 0 => 'ConpherenceDAO', @@ -2907,6 +2910,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsMainController' => 'PhabricatorController', 'PhabricatorSettingsPanelAccount' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelConduit' => 'PhabricatorSettingsPanel', + 'PhabricatorSettingsPanelConpherencePreferences' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelDiffPreferences' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelDisplayPreferences' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelEmailAddresses' => 'PhabricatorSettingsPanel', diff --git a/src/applications/conpherence/constants/ConpherenceSettings.php b/src/applications/conpherence/constants/ConpherenceSettings.php new file mode 100644 index 0000000000..0413ba536a --- /dev/null +++ b/src/applications/conpherence/constants/ConpherenceSettings.php @@ -0,0 +1,22 @@ +getStr('notifications'); + $participant = $conpherence->getParticipant($user->getPHID()); + $participant->setSettings(array('notifications' => $notifications)); + $participant->save(); + $result = pht( + 'Updated notification settings to "%s".', + ConpherenceSettings::getHumanString($notifications)); + return id(new AphrontAjaxResponse()) + ->setContent($result); + break; case 'metadata': $xactions = array(); $top = $request->getInt('image_y'); diff --git a/src/applications/conpherence/controller/ConpherenceWidgetController.php b/src/applications/conpherence/controller/ConpherenceWidgetController.php index 65a55e599b..c343269faa 100644 --- a/src/applications/conpherence/controller/ConpherenceWidgetController.php +++ b/src/applications/conpherence/controller/ConpherenceWidgetController.php @@ -8,6 +8,15 @@ final class ConpherenceWidgetController extends private $conpherenceID; private $conpherence; + private $userPreferences; + + public function setUserPreferences(PhabricatorUserPreferences $pref) { + $this->userPreferences = $pref; + return $this; + } + public function getUserPreferences() { + return $this->userPreferences; + } public function setConpherence(ConpherenceThread $conpherence) { $this->conpherence = $conpherence; @@ -44,6 +53,8 @@ final class ConpherenceWidgetController extends ->executeOne(); $this->setConpherence($conpherence); + $this->setUserPreferences($user->loadPreferences()); + $widgets = $this->renderWidgetPaneContent(); $content = $widgets; return id(new AphrontAjaxResponse())->setContent($content); @@ -57,8 +68,8 @@ final class ConpherenceWidgetController extends Javelin::initBehavior( 'conpherence-widget-pane', array( - 'form_pane' => 'conpherence-form', 'file_widget' => 'widgets-files', + 'settings_widget' => 'widgets-settings', 'widgetRegistery' => array( 'widgets-conpherence-list' => $cant_toggle, 'widgets-conversation' => $cant_toggle, @@ -201,7 +212,63 @@ final class ConpherenceWidgetController extends } private function renderSettingsWidgetPaneContent() { - return 'TODO - settings'; + $user = $this->getRequest()->getUser(); + $conpherence = $this->getConpherence(); + $participants = $conpherence->getParticipants(); + $participant = $participants[$user->getPHID()]; + $default = ConpherenceSettings::EMAIL_ALWAYS; + $preference = $this->getUserPreferences(); + if ($preference) { + $default = $preference->getPreference( + PhabricatorUserPreferences::PREFERENCE_CONPH_NOTIFICATIONS, + ConpherenceSettings::EMAIL_ALWAYS); + } + $settings = $participant->getSettings(); + $notifications = idx( + $settings, + 'notifications', + $default); + $options = id(new AphrontFormRadioButtonControl()) + ->addButton( + ConpherenceSettings::EMAIL_ALWAYS, + ConpherenceSettings::getHumanString( + ConpherenceSettings::EMAIL_ALWAYS), + '') + ->addButton( + ConpherenceSettings::NOTIFICATIONS_ONLY, + ConpherenceSettings::getHumanString( + ConpherenceSettings::NOTIFICATIONS_ONLY), + '') + ->setName('notifications') + ->setValue($notifications); + + $href = $this->getApplicationURI( + 'update/'.$conpherence->getID().'/'); + $layout = array( + $options, + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'action', + 'value' => 'notifications' + )), + javelin_tag( + 'button', + array( + 'sigil' => 'notifications-update', + 'class' => 'notifications-update grey', + ), + pht('Update Notifications')) + ); + + return phabricator_form( + $user, + array( + 'method' => 'POST', + 'action' => $href, + ), + $layout); } private function renderCalendarWidgetPaneContent() { diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 1a1426ea44..292a369851 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -159,13 +159,15 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { } break; case ConpherenceTransactionType::TYPE_PARTICIPANTS: + $participants = array(); foreach ($xaction->getNewValue() as $participant) { if ($participant == $this->getActor()->getPHID()) { $status = ConpherenceParticipationStatus::UP_TO_DATE; } else { $status = ConpherenceParticipationStatus::BEHIND; } - id(new ConpherenceParticipant()) + $participants[] = + id(new ConpherenceParticipant()) ->setConpherencePHID($object->getPHID()) ->setParticipantPHID($participant) ->setParticipationStatus($status) @@ -173,6 +175,8 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { ->setBehindTransactionPHID($xaction->getPHID()) ->save(); } + $participants = mpull($participants, null, 'getParticipantPHID'); + $object->attachParticipants($participants); break; } } @@ -221,7 +225,28 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { protected function getMailTo(PhabricatorLiskDAO $object) { $participants = $object->getParticipants(); - return array_keys($participants); + $preferences = id(new PhabricatorUserPreferences()) + ->loadAllWhere('userPHID in (%Ls)', array_keys($participants)); + $preferences = mpull($preferences, null, 'getUserPHID'); + $to_phids = array(); + foreach ($participants as $phid => $participant) { + $default = ConpherenceSettings::EMAIL_ALWAYS; + $preference = idx($preferences, $phid); + if ($preference) { + $default = $preference->getPreference( + PhabricatorUserPreferences::PREFERENCE_CONPH_NOTIFICATIONS, + ConpherenceSettings::EMAIL_ALWAYS); + } + $settings = $participant->getSettings(); + $notifications = idx( + $settings, + 'notifications', + $default); + if ($notifications == ConpherenceSettings::EMAIL_ALWAYS) { + $to_phids[] = $phid; + } + } + return $to_phids; } protected function getMailCC(PhabricatorLiskDAO $object) { @@ -251,5 +276,4 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { protected function supportsSearch() { return false; } - } diff --git a/src/applications/conpherence/storage/ConpherenceParticipant.php b/src/applications/conpherence/storage/ConpherenceParticipant.php index be897c11fe..075a00502f 100644 --- a/src/applications/conpherence/storage/ConpherenceParticipant.php +++ b/src/applications/conpherence/storage/ConpherenceParticipant.php @@ -10,6 +10,19 @@ final class ConpherenceParticipant extends ConpherenceDAO { protected $participationStatus; protected $behindTransactionPHID; protected $dateTouched; + protected $settings = array(); + + public function getConfiguration() { + return array( + self::CONFIG_SERIALIZATION => array( + 'settings' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function getSettings() { + return nonempty($this->settings, array()); + } public function markUpToDate(ConpherenceTransaction $xaction) { if (!$this->isUpToDate()) { diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php new file mode 100644 index 0000000000..778f0c3888 --- /dev/null +++ b/src/applications/settings/panel/PhabricatorSettingsPanelConpherencePreferences.php @@ -0,0 +1,84 @@ +isBeta(); + $allow_beta = + PhabricatorEnv::getEnvConfig('phabricator.show-beta-applications'); + return ($is_prod || $allow_beta) && $app->isInstalled(); + } + + public function getPanelKey() { + return 'conpherence'; + } + + public function getPanelName() { + return pht('Conpherence Preferences'); + } + + public function getPanelGroup() { + return pht('Application Settings'); + } + + public function processRequest(AphrontRequest $request) { + $user = $request->getUser(); + $preferences = $user->loadPreferences(); + + $pref = PhabricatorUserPreferences::PREFERENCE_CONPH_NOTIFICATIONS; + + if ($request->isFormPost()) { + $notifications = $request->getInt($pref); + $preferences->setPreference($pref, $notifications); + $preferences->save(); + return id(new AphrontRedirectResponse()) + ->setURI($this->getPanelURI('?saved=true')); + } + + $form = id(new AphrontFormView()) + ->setUser($user) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Conpherence Notifications')) + ->setName($pref) + ->setValue($preferences->getPreference($pref)) + ->setOptions( + array( + ConpherenceSettings::EMAIL_ALWAYS + => pht('Email Always'), + ConpherenceSettings::NOTIFICATIONS_ONLY + => pht('Notifications Only'), + )) + ->setCaption( + pht('Should Conpherence send emails for updates or '. + 'notifications only? This global setting can be overridden '. + 'on a per-thread basis within Conpherence.'))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save Preferences'))); + + $panel = new AphrontPanelView(); + $panel->setHeader(pht('Conpherence Preferences')); + $panel->appendChild($form); + $panel->setNoBackground(); + + $error_view = null; + if ($request->getBool('saved')) { + $error_view = id(new AphrontErrorView()) + ->setTitle(pht('Preferences Saved')) + ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) + ->setErrors(array(pht('Your preferences have been saved.'))); + } + + return array( + $error_view, + $panel, + ); + } +} + diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index 9f5196a215..2f695bd40d 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -25,6 +25,8 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { const PREFERENCE_DIFF_FILETREE = 'diff-filetree'; + const PREFERENCE_CONPH_NOTIFICATIONS = 'conph-notifications'; + protected $userPHID; protected $preferences = array(); diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index acf82627a2..803fa1dfbf 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1180,7 +1180,11 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { '20130319.phabricatorfileexplicitupload.sql' => array( 'type' => 'sql', 'name' => $this->getPatchPath( - '20130319.phabricatorfileexplicitupload.sql'), + '20130319.phabricatorfileexplicitupload.sql') + ), + '20130319.conpherence.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130319.conpherence.sql'), ), '20130320.phlux.sql' => array( 'type' => 'sql', diff --git a/webroot/rsrc/css/application/conpherence/widget-pane.css b/webroot/rsrc/css/application/conpherence/widget-pane.css index 6484157637..7c85d7e868 100644 --- a/webroot/rsrc/css/application/conpherence/widget-pane.css +++ b/webroot/rsrc/css/application/conpherence/widget-pane.css @@ -26,6 +26,11 @@ width: 100%; } +.conpherence-widget-pane .aphront-form-inset { + border: 0; + background: url('/rsrc/image/texture/dust_background.jpg'); +} + .conpherence-widget-pane .widgets-header { background-color: #d8dce2; box-shadow: 0px 2px 2px rgba(0,0,0,0.15); @@ -186,3 +191,8 @@ .conpherence-widget-pane .phabricator-remarkup-embed-layout-link { padding-bottom: 1px; } + +/* settings widget */ +.conpherence-widget-pane .notifications-update { + margin: 2px 0px 0px 8px; +} diff --git a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js index b1fc9df43d..9425a23f41 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js +++ b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js @@ -50,13 +50,6 @@ JX.behavior('conpherence-pontificate', function(config) { .start(); }; - JX.DOM.listen( - root, - ['submit', 'didSyntheticSubmit'], - null, - onsubmit - ); - JX.DOM.listen( root, ['click'], diff --git a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js index ec3af523b6..036dee5417 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js +++ b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js @@ -1,8 +1,11 @@ /** - * @provides javelin-behavior-conpherence-widget-pane * @requires javelin-behavior * javelin-dom * javelin-stratcom + * javelin-workflow + * javelin-util + * phabricator-notification + * @provides javelin-behavior-conpherence-widget-pane */ JX.behavior('conpherence-widget-pane', function(config) { @@ -36,4 +39,29 @@ JX.behavior('conpherence-widget-pane', function(config) { } ); + var settingsRoot = JX.$(config.settings_widget); + + var onsubmitSettings = function (e) { + e.kill(); + var form = JX.DOM.find(settingsRoot, 'form'); + var button = JX.DOM.find(form, 'button'); + JX.Workflow.newFromForm(form) + .setHandler(JX.bind(this, function (r) { + new JX.Notification() + .setDuration(6000) + .setContent(r) + .show(); + button.disabled = ''; + JX.DOM.alterClass(button, 'disabled', false); + })) + .start(); + }; + + JX.DOM.listen( + settingsRoot, + ['click'], + 'notifications-update', + onsubmitSettings + ); + });