@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.

Support subtypes in Projects

Summary:
Ref T13242. See PHI1039. Maniphest subtypes generally seem to be working well. I designed them as a general capability that might be extended to other `EditEngine` objects later, and PHI1039 describes a situation where extending subtypes to projects would give us some reasonable tools.

(Some installs also already use icons/colors as a sort of lightweight version of subtypes, so I believe this is generally useful capability.)

Some of this is a little bit copy-pasted and could probably be shared, but I'd like to wait a bit longer before merging it. For example, both configs have exactly the same structure right now, but Projects should possibly have some different flags (for example: to disable creating subprojects / milestones).

This implementation is pretty basic for now: notably, subprojects/milestones don't get the nice "choose from among subtype forms" treatment that tasks do. If this ends up being part of a solution to PHI1039, I'd plan to fill that in later on.

Test Plan: Defined multiple subtypes, created subtype forms, created projects with appropriate subtypes. Filtered them by subtype. Saw subtype information on list/detail views.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13242

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

+174 -1
+2
resources/sql/autopatches/20190127.project.01.subtype.sql
··· 1 + ALTER TABLE {$NAMESPACE}_project.project 2 + ADD subtype VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL;
+2
resources/sql/autopatches/20190127.project.02.default.sql
··· 1 + UPDATE {$NAMESPACE}_project.project 2 + SET subtype = 'default' WHERE subtype = '';
+5
src/__phutil_library_map__.php
··· 4114 4114 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', 4115 4115 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', 4116 4116 'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php', 4117 + 'PhabricatorProjectSubtypeDatasource' => 'applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php', 4118 + 'PhabricatorProjectSubtypesConfigType' => 'applications/project/config/PhabricatorProjectSubtypesConfigType.php', 4117 4119 'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php', 4118 4120 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 4119 4121 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', ··· 10029 10031 'PhabricatorConduitResultInterface', 10030 10032 'PhabricatorColumnProxyInterface', 10031 10033 'PhabricatorSpacesInterface', 10034 + 'PhabricatorEditEngineSubtypeInterface', 10032 10035 ), 10033 10036 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 10034 10037 'PhabricatorProjectApplication' => 'PhabricatorApplication', ··· 10156 10159 'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController', 10157 10160 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 10158 10161 'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem', 10162 + 'PhabricatorProjectSubtypeDatasource' => 'PhabricatorTypeaheadDatasource', 10163 + 'PhabricatorProjectSubtypesConfigType' => 'PhabricatorJSONConfigType', 10159 10164 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 10160 10165 'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction', 10161 10166 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
+33
src/applications/project/config/PhabricatorProjectConfigOptions.php
··· 83 83 84 84 $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; 85 85 86 + 87 + $subtype_type = 'projects.subtypes'; 88 + $subtype_default_key = PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT; 89 + $subtype_example = array( 90 + array( 91 + 'key' => $subtype_default_key, 92 + 'name' => pht('Project'), 93 + ), 94 + array( 95 + 'key' => 'team', 96 + 'name' => pht('Team'), 97 + ), 98 + ); 99 + $subtype_example = id(new PhutilJSON())->encodeAsList($subtype_example); 100 + 101 + $subtype_default = array( 102 + array( 103 + 'key' => $subtype_default_key, 104 + 'name' => pht('Project'), 105 + ), 106 + ); 107 + 108 + $subtype_description = $this->deformat(pht(<<<EOTEXT 109 + Allows you to define project subtypes. For a more detailed description of 110 + subtype configuration, see @{config:maniphest.subtypes}. 111 + EOTEXT 112 + )); 113 + 86 114 return array( 87 115 $this->newOption('projects.custom-field-definitions', 'wild', array()) 88 116 ->setSummary(pht('Custom Projects fields.')) ··· 102 130 $this->newOption('projects.colors', $colors_type, $default_colors) 103 131 ->setSummary(pht('Adjust project colors.')) 104 132 ->setDescription($colors_description), 133 + $this->newOption('projects.subtypes', $subtype_type, $subtype_default) 134 + ->setSummary(pht('Define project subtypes.')) 135 + ->setDescription($subtype_description) 136 + ->addExample($subtype_example, pht('Simple Subtypes')), 137 + 105 138 ); 106 139 } 107 140
+14
src/applications/project/config/PhabricatorProjectSubtypesConfigType.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectSubtypesConfigType 4 + extends PhabricatorJSONConfigType { 5 + 6 + const TYPEKEY = 'projects.subtypes'; 7 + 8 + public function validateStoredValue( 9 + PhabricatorConfigOption $option, 10 + $value) { 11 + PhabricatorEditEngineSubtype::validateConfiguration($value); 12 + } 13 + 14 + }
+6
src/applications/project/controller/PhabricatorProjectProfileController.php
··· 51 51 $watch_action = $this->renderWatchAction($project); 52 52 $header->addActionLink($watch_action); 53 53 54 + $subtype = $project->newSubtypeObject(); 55 + if ($subtype && $subtype->hasTagView()) { 56 + $subtype_tag = $subtype->newTagView(); 57 + $header->addTag($subtype_tag); 58 + } 59 + 54 60 $milestone_list = $this->buildMilestoneList($project); 55 61 $subproject_list = $this->buildSubprojectList($project); 56 62
+13
src/applications/project/query/PhabricatorProjectQuery.php
··· 24 24 private $maxDepth; 25 25 private $minMilestoneNumber; 26 26 private $maxMilestoneNumber; 27 + private $subtypes; 27 28 28 29 private $status = 'status-any'; 29 30 const STATUS_ANY = 'status-any'; ··· 128 129 public function withMilestoneNumberBetween($min, $max) { 129 130 $this->minMilestoneNumber = $min; 130 131 $this->maxMilestoneNumber = $max; 132 + return $this; 133 + } 134 + 135 + public function withSubtypes(array $subtypes) { 136 + $this->subtypes = $subtypes; 131 137 return $this; 132 138 } 133 139 ··· 616 622 $conn, 617 623 'milestoneNumber <= %d', 618 624 $this->maxMilestoneNumber); 625 + } 626 + 627 + if ($this->subtypes !== null) { 628 + $where[] = qsprintf( 629 + $conn, 630 + 'subtype IN (%Ls)', 631 + $this->subtypes); 619 632 } 620 633 621 634 return $where;
+15
src/applications/project/query/PhabricatorProjectSearchEngine.php
··· 19 19 } 20 20 21 21 protected function buildCustomSearchFields() { 22 + $subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap(); 23 + $hide_subtypes = ($subtype_map->getCount() == 1); 24 + 22 25 return array( 23 26 id(new PhabricatorSearchTextField()) 24 27 ->setLabel(pht('Name')) ··· 62 65 pht( 63 66 'Pass true to find only milestones, or false to omit '. 64 67 'milestones.')), 68 + id(new PhabricatorSearchDatasourceField()) 69 + ->setLabel(pht('Subtypes')) 70 + ->setKey('subtypes') 71 + ->setAliases(array('subtype')) 72 + ->setDescription( 73 + pht('Search for projects with given subtypes.')) 74 + ->setDatasource(new PhabricatorProjectSubtypeDatasource()) 75 + ->setIsHidden($hide_subtypes), 65 76 id(new PhabricatorSearchCheckboxesField()) 66 77 ->setLabel(pht('Icons')) 67 78 ->setKey('icons') ··· 132 143 133 144 if ($map['ancestorPHIDs']) { 134 145 $query->withAncestorProjectPHIDs($map['ancestorPHIDs']); 146 + } 147 + 148 + if ($map['subtypes']) { 149 + $query->withSubtypes($map['subtypes']); 135 150 } 136 151 137 152 return $query;
+32 -1
src/applications/project/storage/PhabricatorProject.php
··· 12 12 PhabricatorFerretInterface, 13 13 PhabricatorConduitResultInterface, 14 14 PhabricatorColumnProxyInterface, 15 - PhabricatorSpacesInterface { 15 + PhabricatorSpacesInterface, 16 + PhabricatorEditEngineSubtypeInterface { 16 17 17 18 protected $name; 18 19 protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; ··· 40 41 41 42 protected $properties = array(); 42 43 protected $spacePHID; 44 + protected $subtype; 43 45 44 46 private $memberPHIDs = self::ATTACHABLE; 45 47 private $watcherPHIDs = self::ATTACHABLE; ··· 102 104 ->setHasWorkboard(0) 103 105 ->setHasMilestones(0) 104 106 ->setHasSubprojects(0) 107 + ->setSubtype(PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT) 105 108 ->attachParentProject(null); 106 109 } 107 110 ··· 237 240 'projectPath' => 'hashpath64', 238 241 'projectDepth' => 'uint32', 239 242 'projectPathKey' => 'bytes4', 243 + 'subtype' => 'text64', 240 244 ), 241 245 self::CONFIG_KEY_SCHEMA => array( 242 246 'key_icon' => array( ··· 766 770 ->setType('string') 767 771 ->setDescription(pht('Primary slug/hashtag.')), 768 772 id(new PhabricatorConduitSearchFieldSpecification()) 773 + ->setKey('subtype') 774 + ->setType('string') 775 + ->setDescription(pht('Subtype of the project.')), 776 + id(new PhabricatorConduitSearchFieldSpecification()) 769 777 ->setKey('milestone') 770 778 ->setType('int?') 771 779 ->setDescription(pht('For milestones, milestone sequence number.')), ··· 814 822 return array( 815 823 'name' => $this->getName(), 816 824 'slug' => $this->getPrimarySlug(), 825 + 'subtype' => $this->getSubtype(), 817 826 'milestone' => $milestone, 818 827 'depth' => (int)$this->getProjectDepth(), 819 828 'parent' => $parent_ref, ··· 872 881 return null; 873 882 } 874 883 884 + 885 + /* -( PhabricatorEditEngineSubtypeInterface )------------------------------ */ 886 + 887 + 888 + public function getEditEngineSubtype() { 889 + return $this->getSubtype(); 890 + } 891 + 892 + public function setEditEngineSubtype($value) { 893 + return $this->setSubtype($value); 894 + } 895 + 896 + public function newEditEngineSubtypeMap() { 897 + $config = PhabricatorEnv::getEnvConfig('projects.subtypes'); 898 + return PhabricatorEditEngineSubtype::newSubtypeMap($config); 899 + } 900 + 901 + public function newSubtypeObject() { 902 + $subtype_key = $this->getEditEngineSubtype(); 903 + $subtype_map = $this->newEditEngineSubtypeMap(); 904 + return $subtype_map->getSubtype($subtype_key); 905 + } 875 906 876 907 }
+45
src/applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectSubtypeDatasource 4 + extends PhabricatorTypeaheadDatasource { 5 + 6 + public function getBrowseTitle() { 7 + return pht('Browse Subtypes'); 8 + } 9 + 10 + public function getPlaceholderText() { 11 + return pht('Type a project subtype name...'); 12 + } 13 + 14 + public function getDatasourceApplicationClass() { 15 + return 'PhabricatorProjectApplication'; 16 + } 17 + 18 + public function loadResults() { 19 + $results = $this->buildResults(); 20 + return $this->filterResultsAgainstTokens($results); 21 + } 22 + 23 + protected function renderSpecialTokens(array $values) { 24 + return $this->renderTokensFromResults($this->buildResults(), $values); 25 + } 26 + 27 + private function buildResults() { 28 + $results = array(); 29 + 30 + $subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap(); 31 + foreach ($subtype_map->getSubtypes() as $key => $subtype) { 32 + 33 + $result = id(new PhabricatorTypeaheadResult()) 34 + ->setIcon($subtype->getIcon()) 35 + ->setColor($subtype->getColor()) 36 + ->setPHID($key) 37 + ->setName($subtype->getName()); 38 + 39 + $results[$key] = $result; 40 + } 41 + 42 + return $results; 43 + } 44 + 45 + }
+7
src/applications/project/view/PhabricatorProjectListView.php
··· 87 87 } 88 88 } 89 89 90 + $subtype = $project->newSubtypeObject(); 91 + if ($subtype && $subtype->hasTagView()) { 92 + $subtype_tag = $subtype->newTagView() 93 + ->setSlimShady(true); 94 + $item->addAttribute($subtype_tag); 95 + } 96 + 90 97 $list->addItem($item); 91 98 } 92 99