From c9760e8d643b4fda123e905a52729879b9d5f997 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 26 Jan 2019 07:29:03 -0800 Subject: [PATCH] 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 --- .../20190127.project.01.subtype.sql | 2 + .../20190127.project.02.default.sql | 2 + src/__phutil_library_map__.php | 5 +++ .../PhabricatorProjectConfigOptions.php | 33 ++++++++++++++ .../PhabricatorProjectSubtypesConfigType.php | 14 ++++++ .../PhabricatorProjectProfileController.php | 6 +++ .../project/query/PhabricatorProjectQuery.php | 13 ++++++ .../query/PhabricatorProjectSearchEngine.php | 15 +++++++ .../project/storage/PhabricatorProject.php | 33 +++++++++++++- .../PhabricatorProjectSubtypeDatasource.php | 45 +++++++++++++++++++ .../view/PhabricatorProjectListView.php | 7 +++ 11 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20190127.project.01.subtype.sql create mode 100644 resources/sql/autopatches/20190127.project.02.default.sql create mode 100644 src/applications/project/config/PhabricatorProjectSubtypesConfigType.php create mode 100644 src/applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php diff --git a/resources/sql/autopatches/20190127.project.01.subtype.sql b/resources/sql/autopatches/20190127.project.01.subtype.sql new file mode 100644 index 0000000000..107f9202d4 --- /dev/null +++ b/resources/sql/autopatches/20190127.project.01.subtype.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project + ADD subtype VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL; diff --git a/resources/sql/autopatches/20190127.project.02.default.sql b/resources/sql/autopatches/20190127.project.02.default.sql new file mode 100644 index 0000000000..1a74506cf7 --- /dev/null +++ b/resources/sql/autopatches/20190127.project.02.default.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_project.project + SET subtype = 'default' WHERE subtype = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c5723894d5..993265caa1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4114,6 +4114,8 @@ phutil_register_library_map(array( 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.php', + 'PhabricatorProjectSubtypeDatasource' => 'applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php', + 'PhabricatorProjectSubtypesConfigType' => 'applications/project/config/PhabricatorProjectSubtypesConfigType.php', 'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php', 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', @@ -10029,6 +10031,7 @@ phutil_register_library_map(array( 'PhabricatorConduitResultInterface', 'PhabricatorColumnProxyInterface', 'PhabricatorSpacesInterface', + 'PhabricatorEditEngineSubtypeInterface', ), 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectApplication' => 'PhabricatorApplication', @@ -10156,6 +10159,8 @@ phutil_register_library_map(array( 'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem', + 'PhabricatorProjectSubtypeDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorProjectSubtypesConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/project/config/PhabricatorProjectConfigOptions.php b/src/applications/project/config/PhabricatorProjectConfigOptions.php index c61faa64fb..2d87bf159f 100644 --- a/src/applications/project/config/PhabricatorProjectConfigOptions.php +++ b/src/applications/project/config/PhabricatorProjectConfigOptions.php @@ -83,6 +83,34 @@ EOTEXT $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; + + $subtype_type = 'projects.subtypes'; + $subtype_default_key = PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT; + $subtype_example = array( + array( + 'key' => $subtype_default_key, + 'name' => pht('Project'), + ), + array( + 'key' => 'team', + 'name' => pht('Team'), + ), + ); + $subtype_example = id(new PhutilJSON())->encodeAsList($subtype_example); + + $subtype_default = array( + array( + 'key' => $subtype_default_key, + 'name' => pht('Project'), + ), + ); + + $subtype_description = $this->deformat(pht(<<newOption('projects.custom-field-definitions', 'wild', array()) ->setSummary(pht('Custom Projects fields.')) @@ -102,6 +130,11 @@ EOTEXT $this->newOption('projects.colors', $colors_type, $default_colors) ->setSummary(pht('Adjust project colors.')) ->setDescription($colors_description), + $this->newOption('projects.subtypes', $subtype_type, $subtype_default) + ->setSummary(pht('Define project subtypes.')) + ->setDescription($subtype_description) + ->addExample($subtype_example, pht('Simple Subtypes')), + ); } diff --git a/src/applications/project/config/PhabricatorProjectSubtypesConfigType.php b/src/applications/project/config/PhabricatorProjectSubtypesConfigType.php new file mode 100644 index 0000000000..7603ad7683 --- /dev/null +++ b/src/applications/project/config/PhabricatorProjectSubtypesConfigType.php @@ -0,0 +1,14 @@ +renderWatchAction($project); $header->addActionLink($watch_action); + $subtype = $project->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView(); + $header->addTag($subtype_tag); + } + $milestone_list = $this->buildMilestoneList($project); $subproject_list = $this->buildSubprojectList($project); diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 6f81bc5e68..f6087f7d2a 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -24,6 +24,7 @@ final class PhabricatorProjectQuery private $maxDepth; private $minMilestoneNumber; private $maxMilestoneNumber; + private $subtypes; private $status = 'status-any'; const STATUS_ANY = 'status-any'; @@ -131,6 +132,11 @@ final class PhabricatorProjectQuery return $this; } + public function withSubtypes(array $subtypes) { + $this->subtypes = $subtypes; + return $this; + } + public function needMembers($need_members) { $this->needMembers = $need_members; return $this; @@ -618,6 +624,13 @@ final class PhabricatorProjectQuery $this->maxMilestoneNumber); } + if ($this->subtypes !== null) { + $where[] = qsprintf( + $conn, + 'subtype IN (%Ls)', + $this->subtypes); + } + return $where; } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 02e795395a..88a35676cc 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -19,6 +19,9 @@ final class PhabricatorProjectSearchEngine } protected function buildCustomSearchFields() { + $subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap(); + $hide_subtypes = ($subtype_map->getCount() == 1); + return array( id(new PhabricatorSearchTextField()) ->setLabel(pht('Name')) @@ -62,6 +65,14 @@ final class PhabricatorProjectSearchEngine pht( 'Pass true to find only milestones, or false to omit '. 'milestones.')), + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Subtypes')) + ->setKey('subtypes') + ->setAliases(array('subtype')) + ->setDescription( + pht('Search for projects with given subtypes.')) + ->setDatasource(new PhabricatorProjectSubtypeDatasource()) + ->setIsHidden($hide_subtypes), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Icons')) ->setKey('icons') @@ -134,6 +145,10 @@ final class PhabricatorProjectSearchEngine $query->withAncestorProjectPHIDs($map['ancestorPHIDs']); } + if ($map['subtypes']) { + $query->withSubtypes($map['subtypes']); + } + return $query; } diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 4dae13f3c6..5182a941bf 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -12,7 +12,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO PhabricatorFerretInterface, PhabricatorConduitResultInterface, PhabricatorColumnProxyInterface, - PhabricatorSpacesInterface { + PhabricatorSpacesInterface, + PhabricatorEditEngineSubtypeInterface { protected $name; protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; @@ -40,6 +41,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO protected $properties = array(); protected $spacePHID; + protected $subtype; private $memberPHIDs = self::ATTACHABLE; private $watcherPHIDs = self::ATTACHABLE; @@ -102,6 +104,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO ->setHasWorkboard(0) ->setHasMilestones(0) ->setHasSubprojects(0) + ->setSubtype(PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT) ->attachParentProject(null); } @@ -237,6 +240,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO 'projectPath' => 'hashpath64', 'projectDepth' => 'uint32', 'projectPathKey' => 'bytes4', + 'subtype' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( 'key_icon' => array( @@ -765,6 +769,10 @@ final class PhabricatorProject extends PhabricatorProjectDAO ->setKey('slug') ->setType('string') ->setDescription(pht('Primary slug/hashtag.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('subtype') + ->setType('string') + ->setDescription(pht('Subtype of the project.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('milestone') ->setType('int?') @@ -814,6 +822,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO return array( 'name' => $this->getName(), 'slug' => $this->getPrimarySlug(), + 'subtype' => $this->getSubtype(), 'milestone' => $milestone, 'depth' => (int)$this->getProjectDepth(), 'parent' => $parent_ref, @@ -873,4 +882,26 @@ final class PhabricatorProject extends PhabricatorProjectDAO } +/* -( PhabricatorEditEngineSubtypeInterface )------------------------------ */ + + + public function getEditEngineSubtype() { + return $this->getSubtype(); + } + + public function setEditEngineSubtype($value) { + return $this->setSubtype($value); + } + + public function newEditEngineSubtypeMap() { + $config = PhabricatorEnv::getEnvConfig('projects.subtypes'); + return PhabricatorEditEngineSubtype::newSubtypeMap($config); + } + + public function newSubtypeObject() { + $subtype_key = $this->getEditEngineSubtype(); + $subtype_map = $this->newEditEngineSubtypeMap(); + return $subtype_map->getSubtype($subtype_key); + } + } diff --git a/src/applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php b/src/applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php new file mode 100644 index 0000000000..68de11e630 --- /dev/null +++ b/src/applications/project/typeahead/PhabricatorProjectSubtypeDatasource.php @@ -0,0 +1,45 @@ +buildResults(); + return $this->filterResultsAgainstTokens($results); + } + + protected function renderSpecialTokens(array $values) { + return $this->renderTokensFromResults($this->buildResults(), $values); + } + + private function buildResults() { + $results = array(); + + $subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap(); + foreach ($subtype_map->getSubtypes() as $key => $subtype) { + + $result = id(new PhabricatorTypeaheadResult()) + ->setIcon($subtype->getIcon()) + ->setColor($subtype->getColor()) + ->setPHID($key) + ->setName($subtype->getName()); + + $results[$key] = $result; + } + + return $results; + } + +} diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 645440d831..d8fb011c2e 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -87,6 +87,13 @@ final class PhabricatorProjectListView extends AphrontView { } } + $subtype = $project->newSubtypeObject(); + if ($subtype && $subtype->hasTagView()) { + $subtype_tag = $subtype->newTagView() + ->setSlimShady(true); + $item->addAttribute($subtype_tag); + } + $list->addItem($item); }