From cb0bb8165d8347099c61d2d9b0f7adb7fb3c80fc Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 25 Jan 2012 11:51:20 -0800 Subject: [PATCH] Add a Join / Leave button to Projects Summary: Make it easy to join or leave (well, slightly less easy) a project. Publish join/leave to feed. Fix a couple of membership editor bugs. Test Plan: Joined, left a project. Reviewers: btrahan, jungejason Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T681 Differential Revision: https://secure.phabricator.com/D1485 --- src/__celerity_resource_map__.php | 2 +- src/__phutil_library_map__.php | 2 + ...AphrontDefaultApplicationConfiguration.php | 2 + .../feed/story/base/PhabricatorFeedStory.php | 10 +- .../project/PhabricatorFeedStoryProject.php | 33 ++++- .../feed/story/project/__init__.php | 1 + .../PhabricatorProjectProfileController.php | 29 ++++- .../project/controller/profile/__init__.php | 1 + .../PhabricatorProjectUpdateController.php | 113 ++++++++++++++++++ .../project/controller/update/__init__.php | 22 ++++ .../project/PhabricatorProjectEditor.php | 3 +- .../PhabricatorProfileHeaderView.php | 9 ++ .../profile/profile-header-view.css | 4 + 13 files changed, 224 insertions(+), 7 deletions(-) create mode 100644 src/applications/project/controller/update/PhabricatorProjectUpdateController.php create mode 100644 src/applications/project/controller/update/__init__.php diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index ec56888e31..867048f043 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -1483,7 +1483,7 @@ celerity_register_resource_map(array( ), 'phabricator-profile-header-css' => array( - 'uri' => '/res/d39ef6a4/rsrc/css/application/profile/profile-header-view.css', + 'uri' => '/res/4b1cb23b/rsrc/css/application/profile/profile-header-view.css', 'type' => 'css', 'requires' => array( diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9f68af8a2c..e9699616d7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -628,6 +628,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSubproject' => 'applications/project/storage/subproject', 'PhabricatorProjectTransaction' => 'applications/project/storage/transaction', 'PhabricatorProjectTransactionType' => 'applications/project/constants/transaction', + 'PhabricatorProjectUpdateController' => 'applications/project/controller/update', 'PhabricatorRedirectController' => 'applications/base/controller/redirect', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/refresh', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', @@ -1313,6 +1314,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSubproject' => 'PhabricatorProjectDAO', 'PhabricatorProjectTransaction' => 'PhabricatorProjectDAO', 'PhabricatorProjectTransactionType' => 'PhabricatorProjectConstants', + 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 79f3a88461..1663a76aa2 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -207,6 +207,8 @@ class AphrontDefaultApplicationConfiguration 'affiliation/(?P\d+)/$' => 'PhabricatorProjectAffiliationEditController', 'create/$' => 'PhabricatorProjectCreateController', + 'update/(?P\d+)/(?P[^/]+)/$' + => 'PhabricatorProjectUpdateController', ), '/r(?P[A-Z]+)(?P[a-z0-9]+)$' diff --git a/src/applications/feed/story/base/PhabricatorFeedStory.php b/src/applications/feed/story/base/PhabricatorFeedStory.php index 7cdb665dee..15209925f6 100644 --- a/src/applications/feed/story/base/PhabricatorFeedStory.php +++ b/src/applications/feed/story/base/PhabricatorFeedStory.php @@ -1,7 +1,7 @@ getStoryData()->getEpoch(); } + final protected function renderHandleList(array $phids) { + $list = array(); + foreach ($phids as $phid) { + $list[] = ''.$this->getHandle($phid)->renderLink().''; + } + return implode(', ', $list); + } + } diff --git a/src/applications/feed/story/project/PhabricatorFeedStoryProject.php b/src/applications/feed/story/project/PhabricatorFeedStoryProject.php index 19e96ccd92..e8fb85e661 100644 --- a/src/applications/feed/story/project/PhabricatorFeedStoryProject.php +++ b/src/applications/feed/story/project/PhabricatorFeedStoryProject.php @@ -40,7 +40,9 @@ class PhabricatorFeedStoryProject extends PhabricatorFeedStory { $old = $data->getValue('old'); $new = $data->getValue('new'); $proj = $this->getHandle($data->getValue('projectPHID')); - $auth = $this->getHandle($data->getAuthorPHID()); + + $author_phid = $data->getAuthorPHID(); + $author = $this->getHandle($author_phid); switch ($type) { case PhabricatorProjectTransactionType::TYPE_NAME: @@ -58,11 +60,36 @@ class PhabricatorFeedStoryProject extends PhabricatorFeedStory { ''.phutil_escape_html($new).').'; } break; + case PhabricatorProjectTransactionType::TYPE_MEMBERS: + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ((count($add) == 1) && (count($rem) == 0) && + (head($add) == $author_phid)) { + $action = 'joined project '.$proj->renderLink().'.'; + } else if ((count($add) == 0) && (count($rem) == 1) && + (head($rem) == $author_phid)) { + $action = 'left project '.$proj->renderLink().'.'; + } else if (empty($rem)) { + $action = 'added members to project '. + ''.$proj->renderLink().': '. + $this->renderHandleList($add).'.'; + } else if (empty($add)) { + $action = 'removed members from project '. + ''.$proj->renderLink().': '. + $this->renderHandleList($rem).'.'; + } else { + $action = 'changed members of project '. + ''.$proj->renderLink().', added: '. + $this->renderHandleList($add).'; removed: '. + $this->renderHandleList($rem).'.'; + } + break; default: - $action = 'updated project '.$proj->renderLink().''; + $action = 'updated project '.$proj->renderLink().'.'; break; } - $view->setTitle(''.$auth->renderLink().' '.$action); + $view->setTitle(''.$author->renderLink().' '.$action); $view->setOneLineStory(true); return $view; diff --git a/src/applications/feed/story/project/__init__.php b/src/applications/feed/story/project/__init__.php index 457b290fdf..8aceacee51 100644 --- a/src/applications/feed/story/project/__init__.php +++ b/src/applications/feed/story/project/__init__.php @@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'applications/feed/view/story'); phutil_require_module('phabricator', 'applications/project/constants/transaction'); phutil_require_module('phutil', 'markup'); +phutil_require_module('phutil', 'utils'); phutil_require_source('PhabricatorFeedStoryProject.php'); diff --git a/src/applications/project/controller/profile/PhabricatorProjectProfileController.php b/src/applications/project/controller/profile/PhabricatorProjectProfileController.php index d0408fd054..0f340f85cc 100644 --- a/src/applications/project/controller/profile/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/profile/PhabricatorProjectProfileController.php @@ -52,7 +52,7 @@ class PhabricatorProjectProfileController $picture = null; } - + $members = mpull($project->loadAffiliations(), null, 'getUserPHID'); $nav_view = new AphrontSideNavFilterView(); $uri = new PhutilURI('/project/view/'.$project->getID().'/'); @@ -114,6 +114,33 @@ class PhabricatorProjectProfileController phutil_utf8_shorten($profile->getBlurb(), 1024)); $header->setProfilePicture($picture); + $action = null; + if (empty($members[$user->getPHID()])) { + $action = phabricator_render_form( + $user, + array( + 'action' => '/project/update/'.$project->getID().'/join/', + 'method' => 'post', + ), + phutil_render_tag( + 'button', + array( + 'class' => 'green', + ), + 'Join Project')); + } else { + $action = javelin_render_tag( + 'a', + array( + 'href' => '/project/update/'.$project->getID().'/leave/', + 'sigil' => 'workflow', + 'class' => 'grey button', + ), + 'Leave Project...'); + } + + $header->addAction($action); + $header->appendChild($nav_view); return $this->buildStandardPageResponse( diff --git a/src/applications/project/controller/profile/__init__.php b/src/applications/project/controller/profile/__init__.php index 560c9119c2..0d4a5a51d9 100644 --- a/src/applications/project/controller/profile/__init__.php +++ b/src/applications/project/controller/profile/__init__.php @@ -18,6 +18,7 @@ phutil_require_module('phabricator', 'applications/project/controller/base'); phutil_require_module('phabricator', 'applications/project/storage/profile'); phutil_require_module('phabricator', 'applications/project/storage/project'); phutil_require_module('phabricator', 'infrastructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/javelin/markup'); phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/layout/profileheader'); phutil_require_module('phabricator', 'view/layout/sidenavfilter'); diff --git a/src/applications/project/controller/update/PhabricatorProjectUpdateController.php b/src/applications/project/controller/update/PhabricatorProjectUpdateController.php new file mode 100644 index 0000000000..bbb14e76af --- /dev/null +++ b/src/applications/project/controller/update/PhabricatorProjectUpdateController.php @@ -0,0 +1,113 @@ +id = $data['id']; + $this->action = $data['action']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $project = id(new PhabricatorProject())->load($this->id); + if (!$project) { + return new Aphront404Response(); + } + + $process_action = false; + switch ($this->action) { + case 'join': + $process_action = $request->isFormPost(); + break; + case 'leave': + $process_action = $request->isDialogFormPost(); + break; + default: + return new Aphront404Response(); + } + + $project_uri = '/project/view/'.$project->getID().'/'; + + if ($process_action) { + $xactions = array(); + + switch ($this->action) { + case 'join': + $affils = $project->loadAffiliations(); + $affils = mpull($affils, null, 'getUserPHID'); + if (empty($affils[$user->getPHID()])) { + $affils[$user->getPHID()] = true; + + $xaction = new PhabricatorProjectTransaction(); + $xaction->setTransactionType( + PhabricatorProjectTransactionType::TYPE_MEMBERS); + $xaction->setNewValue(array_keys($affils)); + $xactions[] = $xaction; + } + break; + case 'leave': + $affils = $project->loadAffiliations(); + $affils = mpull($affils, null, 'getUserPHID'); + if (isset($affils[$user->getPHID()])) { + unset($affils[$user->getPHID()]); + + $xaction = new PhabricatorProjectTransaction(); + $xaction->setTransactionType( + PhabricatorProjectTransactionType::TYPE_MEMBERS); + $xaction->setNewValue(array_keys($affils)); + $xactions[] = $xaction; + } + break; + } + + if ($xactions) { + $editor = new PhabricatorProjectEditor($project); + $editor->setUser($user); + $editor->applyTransactions($xactions); + } + + return id(new AphrontRedirectResponse())->setURI($project_uri); + } + + $dialog = null; + switch ($this->action) { + case 'leave': + $dialog = new AphrontDialogView(); + $dialog->setUser($user); + $dialog->setTitle('Really leave project?'); + $dialog->appendChild( + '

Your tremendous contributions to this project will be sorely '. + 'missed. Are you sure you want to leave?

'); + $dialog->addCancelButton($project_uri); + $dialog->addSubmitButton('Leave Project'); + break; + default: + return new Aphront404Response(); + } + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} diff --git a/src/applications/project/controller/update/__init__.php b/src/applications/project/controller/update/__init__.php new file mode 100644 index 0000000000..48405bf536 --- /dev/null +++ b/src/applications/project/controller/update/__init__.php @@ -0,0 +1,22 @@ +setOldValue($affils); + $xaction->setOldValue($old_value); $new_value = $xaction->getNewValue(); $new_value = array_filter($new_value); @@ -174,6 +174,7 @@ final class PhabricatorProjectEditor { foreach ($new as $phid => $ignored) { if (empty($old[$phid])) { $affil = new PhabricatorProjectAffiliation(); + $affil->setRole(''); $affil->setUserPHID($phid); $add[] = $affil; } diff --git a/src/view/layout/profileheader/PhabricatorProfileHeaderView.php b/src/view/layout/profileheader/PhabricatorProfileHeaderView.php index 3e98189f40..9bf7c9d103 100644 --- a/src/view/layout/profileheader/PhabricatorProfileHeaderView.php +++ b/src/view/layout/profileheader/PhabricatorProfileHeaderView.php @@ -21,6 +21,7 @@ final class PhabricatorProfileHeaderView extends AphrontView { protected $profilePicture; protected $profileName; protected $profileDescription; + protected $profileActions = array(); public function setProfilePicture($picture) { $this->profilePicture = $picture; @@ -37,6 +38,11 @@ final class PhabricatorProfileHeaderView extends AphrontView { return $this; } + public function addAction($action) { + $this->profileActions[] = $action; + return $this; + } + public function render() { require_celerity_resource('phabricator-profile-header-css'); @@ -57,6 +63,9 @@ final class PhabricatorProfileHeaderView extends AphrontView { '. phutil_escape_html($this->profileName). ' + '. + self::renderSingleView($this->profileActions). + ' '. $image. ' diff --git a/webroot/rsrc/css/application/profile/profile-header-view.css b/webroot/rsrc/css/application/profile/profile-header-view.css index fc49bc87e4..4d00d88446 100644 --- a/webroot/rsrc/css/application/profile/profile-header-view.css +++ b/webroot/rsrc/css/application/profile/profile-header-view.css @@ -15,6 +15,10 @@ width: 100%; } +.phabricator-profile-header .profile-header-actions { + padding: 12px; +} + .phabricator-profile-header .profile-header-picture-frame { margin: 11px; width: 50px;