mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 14:52:41 +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',
|
||||
'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',
|
||||
|
|
|
@ -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(<<<EOTEXT
|
||||
Allows you to define project subtypes. For a more detailed description of
|
||||
subtype configuration, see @{config:maniphest.subtypes}.
|
||||
EOTEXT
|
||||
));
|
||||
|
||||
return array(
|
||||
$this->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')),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
$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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue