mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
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
This commit is contained in:
parent
13e4aeb590
commit
c9760e8d64
11 changed files with 174 additions and 1 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_project.project
|
||||||
|
ADD subtype VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL;
|
|
@ -0,0 +1,2 @@
|
||||||
|
UPDATE {$NAMESPACE}_project.project
|
||||||
|
SET subtype = 'default' WHERE subtype = '';
|
|
@ -4114,6 +4114,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
|
'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php',
|
||||||
'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php',
|
'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php',
|
||||||
'PhabricatorProjectSubprojectsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectSubprojectsProfileMenuItem.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',
|
'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php',
|
||||||
'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
|
'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php',
|
||||||
'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php',
|
'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php',
|
||||||
|
@ -10029,6 +10031,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorConduitResultInterface',
|
'PhabricatorConduitResultInterface',
|
||||||
'PhabricatorColumnProxyInterface',
|
'PhabricatorColumnProxyInterface',
|
||||||
'PhabricatorSpacesInterface',
|
'PhabricatorSpacesInterface',
|
||||||
|
'PhabricatorEditEngineSubtypeInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
|
||||||
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
'PhabricatorProjectApplication' => 'PhabricatorApplication',
|
||||||
|
@ -10156,6 +10159,8 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController',
|
'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController',
|
'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
'PhabricatorProjectSubprojectsProfileMenuItem' => 'PhabricatorProfileMenuItem',
|
||||||
|
'PhabricatorProjectSubtypeDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
'PhabricatorProjectSubtypesConfigType' => 'PhabricatorJSONConfigType',
|
||||||
'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator',
|
||||||
'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction',
|
'PhabricatorProjectTransaction' => 'PhabricatorModularTransaction',
|
||||||
'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
|
|
|
@ -83,6 +83,34 @@ EOTEXT
|
||||||
|
|
||||||
$custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
|
$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(<<<EOTEXT
|
||||||
|
Allows you to define project subtypes. For a more detailed description of
|
||||||
|
subtype configuration, see @{config:maniphest.subtypes}.
|
||||||
|
EOTEXT
|
||||||
|
));
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
$this->newOption('projects.custom-field-definitions', 'wild', array())
|
$this->newOption('projects.custom-field-definitions', 'wild', array())
|
||||||
->setSummary(pht('Custom Projects fields.'))
|
->setSummary(pht('Custom Projects fields.'))
|
||||||
|
@ -102,6 +130,11 @@ EOTEXT
|
||||||
$this->newOption('projects.colors', $colors_type, $default_colors)
|
$this->newOption('projects.colors', $colors_type, $default_colors)
|
||||||
->setSummary(pht('Adjust project colors.'))
|
->setSummary(pht('Adjust project colors.'))
|
||||||
->setDescription($colors_description),
|
->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')),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectSubtypesConfigType
|
||||||
|
extends PhabricatorJSONConfigType {
|
||||||
|
|
||||||
|
const TYPEKEY = 'projects.subtypes';
|
||||||
|
|
||||||
|
public function validateStoredValue(
|
||||||
|
PhabricatorConfigOption $option,
|
||||||
|
$value) {
|
||||||
|
PhabricatorEditEngineSubtype::validateConfiguration($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -51,6 +51,12 @@ final class PhabricatorProjectProfileController
|
||||||
$watch_action = $this->renderWatchAction($project);
|
$watch_action = $this->renderWatchAction($project);
|
||||||
$header->addActionLink($watch_action);
|
$header->addActionLink($watch_action);
|
||||||
|
|
||||||
|
$subtype = $project->newSubtypeObject();
|
||||||
|
if ($subtype && $subtype->hasTagView()) {
|
||||||
|
$subtype_tag = $subtype->newTagView();
|
||||||
|
$header->addTag($subtype_tag);
|
||||||
|
}
|
||||||
|
|
||||||
$milestone_list = $this->buildMilestoneList($project);
|
$milestone_list = $this->buildMilestoneList($project);
|
||||||
$subproject_list = $this->buildSubprojectList($project);
|
$subproject_list = $this->buildSubprojectList($project);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ final class PhabricatorProjectQuery
|
||||||
private $maxDepth;
|
private $maxDepth;
|
||||||
private $minMilestoneNumber;
|
private $minMilestoneNumber;
|
||||||
private $maxMilestoneNumber;
|
private $maxMilestoneNumber;
|
||||||
|
private $subtypes;
|
||||||
|
|
||||||
private $status = 'status-any';
|
private $status = 'status-any';
|
||||||
const STATUS_ANY = 'status-any';
|
const STATUS_ANY = 'status-any';
|
||||||
|
@ -131,6 +132,11 @@ final class PhabricatorProjectQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withSubtypes(array $subtypes) {
|
||||||
|
$this->subtypes = $subtypes;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function needMembers($need_members) {
|
public function needMembers($need_members) {
|
||||||
$this->needMembers = $need_members;
|
$this->needMembers = $need_members;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -618,6 +624,13 @@ final class PhabricatorProjectQuery
|
||||||
$this->maxMilestoneNumber);
|
$this->maxMilestoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->subtypes !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'subtype IN (%Ls)',
|
||||||
|
$this->subtypes);
|
||||||
|
}
|
||||||
|
|
||||||
return $where;
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ final class PhabricatorProjectSearchEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildCustomSearchFields() {
|
protected function buildCustomSearchFields() {
|
||||||
|
$subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap();
|
||||||
|
$hide_subtypes = ($subtype_map->getCount() == 1);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
id(new PhabricatorSearchTextField())
|
id(new PhabricatorSearchTextField())
|
||||||
->setLabel(pht('Name'))
|
->setLabel(pht('Name'))
|
||||||
|
@ -62,6 +65,14 @@ final class PhabricatorProjectSearchEngine
|
||||||
pht(
|
pht(
|
||||||
'Pass true to find only milestones, or false to omit '.
|
'Pass true to find only milestones, or false to omit '.
|
||||||
'milestones.')),
|
'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())
|
id(new PhabricatorSearchCheckboxesField())
|
||||||
->setLabel(pht('Icons'))
|
->setLabel(pht('Icons'))
|
||||||
->setKey('icons')
|
->setKey('icons')
|
||||||
|
@ -134,6 +145,10 @@ final class PhabricatorProjectSearchEngine
|
||||||
$query->withAncestorProjectPHIDs($map['ancestorPHIDs']);
|
$query->withAncestorProjectPHIDs($map['ancestorPHIDs']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($map['subtypes']) {
|
||||||
|
$query->withSubtypes($map['subtypes']);
|
||||||
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
PhabricatorFerretInterface,
|
PhabricatorFerretInterface,
|
||||||
PhabricatorConduitResultInterface,
|
PhabricatorConduitResultInterface,
|
||||||
PhabricatorColumnProxyInterface,
|
PhabricatorColumnProxyInterface,
|
||||||
PhabricatorSpacesInterface {
|
PhabricatorSpacesInterface,
|
||||||
|
PhabricatorEditEngineSubtypeInterface {
|
||||||
|
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
|
||||||
|
@ -40,6 +41,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
|
|
||||||
protected $properties = array();
|
protected $properties = array();
|
||||||
protected $spacePHID;
|
protected $spacePHID;
|
||||||
|
protected $subtype;
|
||||||
|
|
||||||
private $memberPHIDs = self::ATTACHABLE;
|
private $memberPHIDs = self::ATTACHABLE;
|
||||||
private $watcherPHIDs = self::ATTACHABLE;
|
private $watcherPHIDs = self::ATTACHABLE;
|
||||||
|
@ -102,6 +104,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
->setHasWorkboard(0)
|
->setHasWorkboard(0)
|
||||||
->setHasMilestones(0)
|
->setHasMilestones(0)
|
||||||
->setHasSubprojects(0)
|
->setHasSubprojects(0)
|
||||||
|
->setSubtype(PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT)
|
||||||
->attachParentProject(null);
|
->attachParentProject(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +240,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
'projectPath' => 'hashpath64',
|
'projectPath' => 'hashpath64',
|
||||||
'projectDepth' => 'uint32',
|
'projectDepth' => 'uint32',
|
||||||
'projectPathKey' => 'bytes4',
|
'projectPathKey' => 'bytes4',
|
||||||
|
'subtype' => 'text64',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_icon' => array(
|
'key_icon' => array(
|
||||||
|
@ -765,6 +769,10 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
->setKey('slug')
|
->setKey('slug')
|
||||||
->setType('string')
|
->setType('string')
|
||||||
->setDescription(pht('Primary slug/hashtag.')),
|
->setDescription(pht('Primary slug/hashtag.')),
|
||||||
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
|
->setKey('subtype')
|
||||||
|
->setType('string')
|
||||||
|
->setDescription(pht('Subtype of the project.')),
|
||||||
id(new PhabricatorConduitSearchFieldSpecification())
|
id(new PhabricatorConduitSearchFieldSpecification())
|
||||||
->setKey('milestone')
|
->setKey('milestone')
|
||||||
->setType('int?')
|
->setType('int?')
|
||||||
|
@ -814,6 +822,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
|
||||||
return array(
|
return array(
|
||||||
'name' => $this->getName(),
|
'name' => $this->getName(),
|
||||||
'slug' => $this->getPrimarySlug(),
|
'slug' => $this->getPrimarySlug(),
|
||||||
|
'subtype' => $this->getSubtype(),
|
||||||
'milestone' => $milestone,
|
'milestone' => $milestone,
|
||||||
'depth' => (int)$this->getProjectDepth(),
|
'depth' => (int)$this->getProjectDepth(),
|
||||||
'parent' => $parent_ref,
|
'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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectSubtypeDatasource
|
||||||
|
extends PhabricatorTypeaheadDatasource {
|
||||||
|
|
||||||
|
public function getBrowseTitle() {
|
||||||
|
return pht('Browse Subtypes');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlaceholderText() {
|
||||||
|
return pht('Type a project subtype name...');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatasourceApplicationClass() {
|
||||||
|
return 'PhabricatorProjectApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadResults() {
|
||||||
|
$results = $this->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
$list->addItem($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue