1
0
Fork 0
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:
epriestley 2019-01-26 07:29:03 -08:00
parent 13e4aeb590
commit c9760e8d64
11 changed files with 174 additions and 1 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_project.project
ADD subtype VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL;

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_project.project
SET subtype = 'default' WHERE subtype = '';

View file

@ -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',

View file

@ -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')),
);
}

View file

@ -0,0 +1,14 @@
<?php
final class PhabricatorProjectSubtypesConfigType
extends PhabricatorJSONConfigType {
const TYPEKEY = 'projects.subtypes';
public function validateStoredValue(
PhabricatorConfigOption $option,
$value) {
PhabricatorEditEngineSubtype::validateConfiguration($value);
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}