@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Give profile menus more straightforward hide/disable/delete/default interactions

Summary:
Ref T10054.

- Just let users delete non-builtin items.
- Let users choose a default item explicitly.
- Do a better job of cleaning up items which no longer exist or belong to uninstalled applications.

(NOTE) This has one user-facing change: workboards are no longer the default on projects with workboards. I think this is probably OK since we're giving users a ton of new toys at the same time, but I'll write some docs at least.

Test Plan:
- Deleted custom items.
- Disabled/enabled builtin items.
- Made various things defaults.
- Uninstalled Maniphest, saw Workboards tab disappear entirely.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10054

Differential Revision: https://secure.phabricator.com/D15089

+251 -48
+1
src/applications/base/PhabricatorApplication.php
··· 641 641 return array( 642 642 '(?P<panelAction>view)/(?P<panelID>[^/]+)/' => $controller, 643 643 '(?P<panelAction>hide)/(?P<panelID>[^/]+)/' => $controller, 644 + '(?P<panelAction>default)/(?P<panelID>[^/]+)/' => $controller, 644 645 '(?P<panelAction>configure)/' => $controller, 645 646 '(?P<panelAction>reorder)/' => $controller, 646 647 '(?P<panelAction>edit)/'.$edit_route => $controller,
+5 -6
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 712 712 pht('Import Columns'), 713 713 pht('Import board columns from another project.')); 714 714 715 - $dialog = id(new AphrontDialogView()) 716 - ->setUser($this->getRequest()->getUser()) 715 + 716 + $cancel_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); 717 + 718 + return $this->newDialog() 717 719 ->setTitle(pht('New Workboard')) 718 720 ->addSubmitButton('Continue') 719 - ->addCancelButton($this->getApplicationURI('view/'.$project->getID().'/')) 721 + ->addCancelButton($cancel_uri) 720 722 ->appendParagraph($instructions) 721 723 ->appendChild($new_selector); 722 - 723 - return id(new AphrontDialogResponse()) 724 - ->setDialog($dialog); 725 724 } 726 725 727 726 private function noAccessDialog(PhabricatorProject $project) {
+5 -12
src/applications/project/controller/PhabricatorProjectViewController.php
··· 17 17 } 18 18 $project = $this->getProject(); 19 19 20 - $columns = id(new PhabricatorProjectColumnQuery()) 21 - ->setViewer($viewer) 22 - ->withProjectPHIDs(array($project->getPHID())) 23 - ->execute(); 24 - if ($columns) { 25 - $controller = 'board'; 26 - } else { 27 - $controller = 'profile'; 28 - } 20 + $engine = $this->getProfilePanelEngine(); 21 + $default = $engine->getDefaultPanel(); 29 22 30 - switch ($controller) { 31 - case 'board': 23 + switch ($default->getBuiltinKey()) { 24 + case PhabricatorProject::PANEL_WORKBOARD: 32 25 $controller_object = new PhabricatorProjectBoardViewController(); 33 26 break; 34 - case 'profile': 27 + case PhabricatorProject::PANEL_PROFILE: 35 28 default: 36 29 $controller_object = new PhabricatorProjectProfileController(); 37 30 break;
+5
src/applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php
··· 13 13 return pht('Project Details'); 14 14 } 15 15 16 + public function canMakeDefault( 17 + PhabricatorProfilePanelConfiguration $config) { 18 + return true; 19 + } 20 + 16 21 public function getDisplayName( 17 22 PhabricatorProfilePanelConfiguration $config) { 18 23 $name = $config->getPanelProperty('name');
+5
src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
··· 13 13 return pht('Workboard'); 14 14 } 15 15 16 + public function canMakeDefault( 17 + PhabricatorProfilePanelConfiguration $config) { 18 + return true; 19 + } 20 + 16 21 public function getDisplayName( 17 22 PhabricatorProfilePanelConfiguration $config) { 18 23 $name = $config->getPanelProperty('name');
+216 -23
src/applications/search/engine/PhabricatorProfilePanelEngine.php
··· 5 5 private $viewer; 6 6 private $profileObject; 7 7 private $panels; 8 + private $defaultPanel; 8 9 private $controller; 9 10 private $navigation; 10 11 ··· 35 36 return $this->controller; 36 37 } 37 38 39 + private function setDefaultPanel( 40 + PhabricatorProfilePanelConfiguration $default_panel) { 41 + $this->defaultPanel = $default_panel; 42 + return $this; 43 + } 44 + 45 + public function getDefaultPanel() { 46 + $this->loadPanels(); 47 + return $this->defaultPanel; 48 + } 49 + 38 50 abstract protected function getPanelURI($path); 39 51 40 52 protected function isPanelEngineConfigurable() { ··· 89 101 case 'view': 90 102 case 'info': 91 103 case 'hide': 104 + case 'default': 92 105 case 'builtin': 93 106 if (!$selected_panel) { 94 107 return new Aphront404Response(); ··· 122 135 case 'hide': 123 136 $content = $this->buildPanelHideContent($selected_panel); 124 137 break; 138 + case 'default': 139 + $content = $this->buildPanelDefaultContent( 140 + $selected_panel, 141 + $panel_list); 142 + break; 125 143 case 'edit': 126 144 $content = $this->buildPanelEditContent(); 127 145 break; ··· 225 243 foreach ($stored_panels as $stored_panel) { 226 244 $builtin_key = $stored_panel->getBuiltinKey(); 227 245 if ($builtin_key !== null) { 228 - $panels[$builtin_key] = $stored_panel; 246 + // If this builtin actually exists, replace the builtin with the 247 + // stored configuration. Otherwise, we're just going to drop the 248 + // stored config: it corresponds to an out-of-date or uninstalled 249 + // panel. 250 + if (isset($panels[$builtin_key])) { 251 + $panels[$builtin_key] = $stored_panel; 252 + } else { 253 + continue; 254 + } 229 255 } else { 230 256 $panels[] = $stored_panel; 231 257 } ··· 242 268 // Normalize keys since callers shouldn't rely on this array being 243 269 // partially keyed. 244 270 $panels = array_values($panels); 271 + 272 + 273 + // Make sure exactly one valid panel is marked as default. 274 + $default = null; 275 + $first = null; 276 + foreach ($panels as $panel) { 277 + if (!$panel->canMakeDefault()) { 278 + continue; 279 + } 280 + 281 + if ($panel->isDefault()) { 282 + $default = $panel; 283 + break; 284 + } 285 + 286 + if ($first === null) { 287 + $first = $panel; 288 + } 289 + } 290 + 291 + if (!$default) { 292 + $default = $first; 293 + } 294 + 295 + if ($default) { 296 + $this->setDefaultPanel($default); 297 + } 245 298 246 299 return $panels; 247 300 } ··· 543 596 )); 544 597 545 598 if ($id) { 599 + $default_uri = $this->getPanelURI("default/{$id}/"); 600 + } else { 601 + $default_uri = $this->getPanelURI("default/{$builtin_key}/"); 602 + } 603 + 604 + if ($panel->isDefault()) { 605 + $default_icon = 'fa-thumb-tack green'; 606 + $default_text = pht('Current Default'); 607 + } else if ($panel->canMakeDefault()) { 608 + $default_icon = 'fa-thumb-tack'; 609 + $default_text = pht('Make Default'); 610 + } else { 611 + $default_text = null; 612 + } 613 + 614 + if ($default_text !== null) { 615 + $item->addAction( 616 + id(new PHUIListItemView()) 617 + ->setHref($default_uri) 618 + ->setWorkflow(true) 619 + ->setName($default_text) 620 + ->setIcon($default_icon)); 621 + } 622 + 623 + if ($id) { 546 624 $item->setHref($this->getPanelURI("edit/{$id}/")); 547 625 $hide_uri = $this->getPanelURI("hide/{$id}/"); 548 626 } else { ··· 550 628 $hide_uri = $this->getPanelURI("hide/{$builtin_key}/"); 551 629 } 552 630 631 + if ($panel->isDisabled()) { 632 + $hide_icon = 'fa-plus'; 633 + $hide_text = pht('Show'); 634 + } else if ($panel->getBuiltinKey() !== null) { 635 + $hide_icon = 'fa-times'; 636 + $hide_text = pht('Disable'); 637 + } else { 638 + $hide_icon = 'fa-times'; 639 + $hide_text = pht('Delete'); 640 + } 641 + 553 642 $item->addAction( 554 643 id(new PHUIListItemView()) 555 644 ->setHref($hide_uri) 556 645 ->setWorkflow(true) 557 - ->setIcon(pht('fa-eye'))); 646 + ->setName($hide_text) 647 + ->setIcon($hide_icon)); 558 648 } 559 649 560 650 if ($panel->isDisabled()) { 561 651 $item->setDisabled(true); 562 - $item->addIcon('fa-times grey', pht('Disabled')); 563 652 } 564 653 565 654 $list->addItem($item); ··· 707 796 $configuration, 708 797 PhabricatorPolicyCapability::CAN_EDIT); 709 798 799 + if ($configuration->getBuiltinKey() === null) { 800 + $new_value = null; 801 + 802 + $title = pht('Delete Menu Item'); 803 + $body = pht('Delete this menu item?'); 804 + $button = pht('Delete Menu Item'); 805 + } else if ($configuration->isDisabled()) { 806 + $new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE; 807 + 808 + $title = pht('Enable Menu Item'); 809 + $body = pht( 810 + 'Enable this menu item? It will appear in the menu again.'); 811 + $button = pht('Enable Menu Item'); 812 + } else { 813 + $new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_DISABLED; 814 + 815 + $title = pht('Disable Menu Item'); 816 + $body = pht( 817 + 'Disable this menu item? It will no longer appear in the menu, but '. 818 + 'you can re-enable it later.'); 819 + $button = pht('Disable Menu Item'); 820 + } 821 + 710 822 $v_visibility = $configuration->getVisibility(); 711 823 if ($request->isFormPost()) { 712 - $v_visibility = $request->getStr('visibility'); 824 + if ($new_value === null) { 825 + $configuration->delete(); 826 + } else { 827 + $type_visibility = 828 + PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY; 829 + 830 + $xactions = array(); 831 + 832 + $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) 833 + ->setTransactionType($type_visibility) 834 + ->setNewValue($new_value); 835 + 836 + $editor = id(new PhabricatorProfilePanelEditor()) 837 + ->setContentSourceFromRequest($request) 838 + ->setActor($viewer) 839 + ->setContinueOnMissingFields(true) 840 + ->setContinueOnNoEffect(true) 841 + ->applyTransactions($configuration, $xactions); 842 + } 843 + 844 + return id(new AphrontRedirectResponse()) 845 + ->setURI($this->getConfigureURI()); 846 + } 847 + 848 + return $controller->newDialog() 849 + ->setTitle($title) 850 + ->appendParagraph($body) 851 + ->addCancelButton($this->getConfigureURI()) 852 + ->addSubmitButton($button); 853 + } 854 + 855 + private function buildPanelDefaultContent( 856 + PhabricatorProfilePanelConfiguration $configuration, 857 + array $panels) { 858 + 859 + $controller = $this->getController(); 860 + $request = $controller->getRequest(); 861 + $viewer = $this->getViewer(); 862 + 863 + PhabricatorPolicyFilter::requireCapability( 864 + $viewer, 865 + $configuration, 866 + PhabricatorPolicyCapability::CAN_EDIT); 867 + 868 + $done_uri = $this->getConfigureURI(); 869 + 870 + if (!$configuration->canMakeDefault()) { 871 + return $controller->newDialog() 872 + ->setTitle(pht('Not Defaultable')) 873 + ->appendParagraph( 874 + pht( 875 + 'This item can not be set as the default item. This is usually '. 876 + 'because the item has no page of its own, or links to an '. 877 + 'external page.')) 878 + ->addCancelButton($done_uri); 879 + } 880 + 881 + if ($configuration->isDefault()) { 882 + return $controller->newDialog() 883 + ->setTitle(pht('Already Default')) 884 + ->appendParagraph( 885 + pht( 886 + 'This item is already set as the default item for this menu.')) 887 + ->addCancelButton($done_uri); 888 + } 889 + 890 + $type_visibility = 891 + PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY; 892 + 893 + $v_visible = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE; 894 + $v_default = PhabricatorProfilePanelConfiguration::VISIBILITY_DEFAULT; 713 895 714 - $type_visibility = 715 - PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY; 896 + if ($request->isFormPost()) { 897 + // First, mark any existing default panels as merely visible. 898 + foreach ($panels as $panel) { 899 + if (!$panel->isDefault()) { 900 + continue; 901 + } 902 + 903 + $xactions = array(); 904 + 905 + $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) 906 + ->setTransactionType($type_visibility) 907 + ->setNewValue($v_visible); 908 + 909 + $editor = id(new PhabricatorProfilePanelEditor()) 910 + ->setContentSourceFromRequest($request) 911 + ->setActor($viewer) 912 + ->setContinueOnMissingFields(true) 913 + ->setContinueOnNoEffect(true) 914 + ->applyTransactions($panel, $xactions); 915 + } 716 916 917 + // Now, make this panel the default. 717 918 $xactions = array(); 718 919 719 920 $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) 720 921 ->setTransactionType($type_visibility) 721 - ->setNewValue($v_visibility); 922 + ->setNewValue($v_default); 722 923 723 924 $editor = id(new PhabricatorProfilePanelEditor()) 724 925 ->setContentSourceFromRequest($request) ··· 728 929 ->applyTransactions($configuration, $xactions); 729 930 730 931 return id(new AphrontRedirectResponse()) 731 - ->setURI($this->getConfigureURI()); 932 + ->setURI($done_uri); 732 933 } 733 934 734 - $map = PhabricatorProfilePanelConfiguration::getVisibilityNameMap(); 735 - 736 - $form = id(new AphrontFormView()) 737 - ->setUser($viewer) 738 - ->appendControl( 739 - id(new AphrontFormSelectControl()) 740 - ->setName('visibility') 741 - ->setLabel(pht('Visibility')) 742 - ->setValue($v_visibility) 743 - ->setOptions($map)); 744 - 745 935 return $controller->newDialog() 746 - ->setTitle(pht('Change Item Visibility')) 747 - ->appendForm($form) 748 - ->addCancelButton($this->getConfigureURI()) 749 - ->addSubmitButton(pht('Save Changes')); 936 + ->setTitle(pht('Make Default')) 937 + ->appendParagraph( 938 + pht( 939 + 'Set this item as the default for this menu? Users arriving on '. 940 + 'this page will be shown the content of this item by default.')) 941 + ->addCancelButton($done_uri) 942 + ->addSubmitButton(pht('Make Default')); 750 943 } 751 944 752 945 protected function newPanel() {
+5
src/applications/search/profilepanel/PhabricatorProfilePanel.php
··· 30 30 return false; 31 31 } 32 32 33 + public function canMakeDefault( 34 + PhabricatorProfilePanelConfiguration $config) { 35 + return false; 36 + } 37 + 33 38 public function setViewer(PhabricatorUser $viewer) { 34 39 $this->viewer = $viewer; 35 40 return $this;
+9 -7
src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
··· 17 17 private $profileObject = self::ATTACHABLE; 18 18 private $panel = self::ATTACHABLE; 19 19 20 + const VISIBILITY_DEFAULT = 'default'; 20 21 const VISIBILITY_VISIBLE = 'visible'; 21 22 const VISIBILITY_DISABLED = 'disabled'; 22 23 ··· 54 55 ), 55 56 ), 56 57 ) + parent::getConfiguration(); 57 - } 58 - 59 - public static function getVisibilityNameMap() { 60 - return array( 61 - self::VISIBILITY_VISIBLE => pht('Visible'), 62 - self::VISIBILITY_DISABLED => pht('Disabled'), 63 - ); 64 58 } 65 59 66 60 public function generatePHID() { ··· 107 101 return $this->getPanel()->getDisplayName($this); 108 102 } 109 103 104 + public function canMakeDefault() { 105 + return $this->getPanel()->canMakeDefault($this); 106 + } 107 + 110 108 public function getSortKey() { 111 109 $order = $this->getPanelOrder(); 112 110 if ($order === null) { ··· 123 121 124 122 public function isDisabled() { 125 123 return ($this->getVisibility() === self::VISIBILITY_DISABLED); 124 + } 125 + 126 + public function isDefault() { 127 + return ($this->getVisibility() === self::VISIBILITY_DEFAULT); 126 128 } 127 129 128 130