Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F21773714
D14904.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
32 KB
Referenced Files
None
Subscribers
None
D14904.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,7 +7,7 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => 'a419cf4b',
+ 'core.pkg.css' => '3ea6dc33',
'core.pkg.js' => '57dff7df',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
@@ -114,7 +114,7 @@
'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2',
'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52',
- 'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e',
+ 'rsrc/css/layout/phabricator-side-menu-view.css' => '91b7a42c',
'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983',
'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338',
@@ -762,7 +762,7 @@
'phabricator-remarkup-css' => '7afb543c',
'phabricator-search-results-css' => '7dea472c',
'phabricator-shaped-request' => '7cbe244b',
- 'phabricator-side-menu-view-css' => 'bec2458e',
+ 'phabricator-side-menu-view-css' => '91b7a42c',
'phabricator-slowvote-css' => 'da0afb1b',
'phabricator-source-code-view-css' => 'cbeef983',
'phabricator-standard-page-view' => '3c99cdf4',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2855,6 +2855,7 @@
'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php',
'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php',
'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php',
+ 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php',
'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php',
'PhabricatorProjectLogicalAndDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php',
'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php',
@@ -7206,6 +7207,7 @@
'PhabricatorProjectHeraldAction' => 'HeraldAction',
'PhabricatorProjectIconSet' => 'PhabricatorIconSet',
'PhabricatorProjectListController' => 'PhabricatorProjectController',
+ 'PhabricatorProjectListView' => 'AphrontView',
'PhabricatorProjectLockController' => 'PhabricatorProjectController',
'PhabricatorProjectLogicalAndDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
--- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
+++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
@@ -748,15 +748,15 @@
->setNewValue($name);
if ($parent) {
- $xactions[] = id(new PhabricatorProjectTransaction())
- ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT)
- ->setNewValue($parent->getPHID());
- }
-
- if ($is_milestone) {
- $xactions[] = id(new PhabricatorProjectTransaction())
- ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE)
- ->setNewValue(true);
+ if ($is_milestone) {
+ $xactions[] = id(new PhabricatorProjectTransaction())
+ ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE)
+ ->setNewValue($parent->getPHID());
+ } else {
+ $xactions[] = id(new PhabricatorProjectTransaction())
+ ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT)
+ ->setNewValue($parent->getPHID());
+ }
}
$this->applyTransactions($project, $user, $xactions);
diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php
--- a/src/applications/project/controller/PhabricatorProjectController.php
+++ b/src/applications/project/controller/PhabricatorProjectController.php
@@ -99,7 +99,6 @@
$nav->addFilter("board/{$id}/", pht('Workboard'));
$nav->addFilter("members/{$id}/", pht('Members'));
$nav->addFilter("feed/{$id}/", pht('Feed'));
- $nav->addFilter("details/{$id}/", pht('Edit Details'));
}
$nav->addFilter('create', pht('Create Project'));
}
@@ -149,11 +148,29 @@
$nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o');
$nav->addIcon("members/{$id}/", pht('Members'), 'fa-group');
- $nav->addIcon("details/{$id}/", pht('Edit Details'), 'fa-pencil');
if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
- $nav->addIcon("subprojects/{$id}/", pht('Subprojects'), 'fa-sitemap');
- $nav->addIcon("milestones/{$id}/", pht('Milestones'), 'fa-map-marker');
+ if ($project->supportsSubprojects()) {
+ $subprojects_icon = 'fa-sitemap';
+ } else {
+ $subprojects_icon = 'fa-sitemap grey';
+ }
+
+ if ($project->supportsMilestones()) {
+ $milestones_icon = 'fa-map-marker';
+ } else {
+ $milestones_icon = 'fa-map-marker grey';
+ }
+
+ $nav->addIcon(
+ "subprojects/{$id}/",
+ pht('Subprojects'),
+ $subprojects_icon);
+
+ $nav->addIcon(
+ "milestones/{$id}/",
+ pht('Milestones'),
+ $milestones_icon);
}
@@ -170,8 +187,8 @@
$ancestors[] = $project;
foreach ($ancestors as $ancestor) {
$crumbs->addTextCrumb(
- $project->getName(),
- $project->getURI());
+ $ancestor->getName(),
+ $ancestor->getURI());
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectEditController.php b/src/applications/project/controller/PhabricatorProjectEditController.php
--- a/src/applications/project/controller/PhabricatorProjectEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectEditController.php
@@ -3,10 +3,111 @@
final class PhabricatorProjectEditController
extends PhabricatorProjectController {
+ private $engine;
+
+ public function setEngine(PhabricatorProjectEditEngine $engine) {
+ $this->engine = $engine;
+ return $this;
+ }
+
+ public function getEngine() {
+ return $this->engine;
+ }
+
public function handleRequest(AphrontRequest $request) {
- return id(new PhabricatorProjectEditEngine())
- ->setController($this)
- ->buildResponse();
+ $viewer = $this->getViewer();
+
+ $engine = id(new PhabricatorProjectEditEngine())
+ ->setController($this);
+
+ $this->setEngine($engine);
+
+ $id = $request->getURIData('id');
+ if (!$id) {
+ $parent_id = head($request->getArr('parent'));
+ if (!$parent_id) {
+ $parent_id = $request->getStr('parent');
+ }
+
+ if ($parent_id) {
+ $is_milestone = false;
+ } else {
+ $parent_id = head($request->getArr('milestone'));
+ if (!$parent_id) {
+ $parent_id = $request->getStr('milestone');
+ }
+ $is_milestone = true;
+ }
+
+ if ($parent_id) {
+ $query = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ));
+
+ if (ctype_digit($parent_id)) {
+ $query->withIDs(array($parent_id));
+ } else {
+ $query->withPHIDs(array($parent_id));
+ }
+
+ $parent = $query->executeOne();
+
+ if ($is_milestone) {
+ if (!$parent->supportsMilestones()) {
+ $cancel_uri = "/project/milestones/{$parent_id}/";
+ return $this->newDialog()
+ ->setTitle(pht('No Milestones'))
+ ->appendParagraph(
+ pht('You can not add milestones to this project.'))
+ ->addCancelButton($cancel_uri);
+ }
+ $engine->setMilestoneProject($parent);
+ } else {
+ if (!$parent->supportsSubprojects()) {
+ $cancel_uri = "/project/subprojects/{$parent_id}/";
+ return $this->newDialog()
+ ->setTitle(pht('No Subprojects'))
+ ->appendParagraph(
+ pht('You can not add subprojects to this project.'))
+ ->addCancelButton($cancel_uri);
+ }
+ $engine->setParentProject($parent);
+ }
+
+ $this->setProject($parent);
+ }
+ }
+
+ return $engine->buildResponse();
+ }
+
+ protected function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ $engine = $this->getEngine();
+ if ($engine) {
+ $parent = $engine->getParentProject();
+ if ($parent) {
+ $id = $parent->getID();
+ $crumbs->addTextCrumb(
+ pht('Subprojects'),
+ $this->getApplicationURI("subprojects/{$id}/"));
+ }
+
+ $milestone = $engine->getMilestoneProject();
+ if ($milestone) {
+ $id = $milestone->getID();
+ $crumbs->addTextCrumb(
+ pht('Milestones'),
+ $this->getApplicationURI("milestones/{$id}/"));
+ }
+ }
+
+ return $crumbs;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
--- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
@@ -68,9 +68,11 @@
$project,
PhabricatorPolicyCapability::CAN_EDIT);
+ $supports_edit = $project->supportsEditMembers();
+
$form_box = null;
$title = pht('Add Members');
- if ($can_edit) {
+ if ($can_edit && $supports_edit) {
$header_name = pht('Edit Members');
$view_uri = $this->getApplicationURI('profile/'.$project->getID().'/');
diff --git a/src/applications/project/controller/PhabricatorProjectMilestonesController.php b/src/applications/project/controller/PhabricatorProjectMilestonesController.php
--- a/src/applications/project/controller/PhabricatorProjectMilestonesController.php
+++ b/src/applications/project/controller/PhabricatorProjectMilestonesController.php
@@ -18,6 +18,64 @@
$project = $this->getProject();
$id = $project->getID();
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $project,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $has_support = $project->supportsMilestones();
+ if ($has_support) {
+ $milestones = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->withParentProjectPHIDs(array($project->getPHID()))
+ ->needImages(true)
+ ->withIsMilestone(true)
+ ->setOrder('newest')
+ ->execute();
+ } else {
+ $milestones = array();
+ }
+
+ $can_create = $can_edit && $has_support;
+
+ if ($project->getHasMilestones()) {
+ $button_text = pht('Create Next Milestone');
+ } else {
+ $button_text = pht('Add Milestones');
+ }
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(pht('Milestones'))
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setTag('a')
+ ->setHref("/project/edit/?milestone={$id}")
+ ->setIconFont('fa-plus')
+ ->setDisabled(!$can_create)
+ ->setWorkflow(!$can_create)
+ ->setText($button_text));
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header);
+
+ if (!$has_support) {
+ $no_support = pht(
+ 'This project is a milestone. Milestones can not have their own '.
+ 'milestones.');
+
+ $info_view = id(new PHUIInfoView())
+ ->setErrors(array($no_support))
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING);
+
+ $box->setInfoView($info_view);
+ }
+
+ $box->setObjectList(
+ id(new PhabricatorProjectListView())
+ ->setUser($viewer)
+ ->setProjects($milestones)
+ ->renderList());
+
$nav = $this->buildIconNavView($project);
$nav->selectFilter("milestones/{$id}/");
@@ -27,7 +85,8 @@
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
- ->setTitle(array($project->getName(), pht('Milestones')));
+ ->setTitle(array($project->getName(), pht('Milestones')))
+ ->appendChild($box);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php
--- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php
+++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php
@@ -18,6 +18,63 @@
$project = $this->getProject();
$id = $project->getID();
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $project,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $has_support = $project->supportsSubprojects();
+
+ if ($has_support) {
+ $subprojects = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->withParentProjectPHIDs(array($project->getPHID()))
+ ->needImages(true)
+ ->withIsMilestone(false)
+ ->execute();
+ } else {
+ $subprojects = array();
+ }
+
+ $can_create = $can_edit && $has_support;
+
+ if ($project->getHasSubprojects()) {
+ $button_text = pht('Create Subproject');
+ } else {
+ $button_text = pht('Add Subprojects');
+ }
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(pht('Subprojects'))
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setTag('a')
+ ->setHref("/project/edit/?parent={$id}")
+ ->setIconFont('fa-plus')
+ ->setDisabled(!$can_create)
+ ->setWorkflow(!$can_create)
+ ->setText($button_text));
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header);
+
+ if (!$has_support) {
+ $no_support = pht(
+ 'This project is a milestone. Milestones can not have subprojects.');
+
+ $info_view = id(new PHUIInfoView())
+ ->setErrors(array($no_support))
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING);
+
+ $box->setInfoView($info_view);
+ }
+
+ $box->setObjectList(
+ id(new PhabricatorProjectListView())
+ ->setUser($viewer)
+ ->setProjects($subprojects)
+ ->renderList());
+
$nav = $this->buildIconNavView($project);
$nav->selectFilter("subprojects/{$id}/");
@@ -27,7 +84,8 @@
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
- ->setTitle(array($project->getName(), pht('Subprojects')));
+ ->setTitle(array($project->getName(), pht('Subprojects')))
+ ->appendChild($box);
}
}
diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
--- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
+++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
@@ -74,23 +74,10 @@
case PhabricatorProjectTransaction::TYPE_COLOR:
case PhabricatorProjectTransaction::TYPE_LOCKED:
case PhabricatorProjectTransaction::TYPE_PARENT:
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
return $xaction->getNewValue();
case PhabricatorProjectTransaction::TYPE_SLUGS:
return $this->normalizeSlugs($xaction->getNewValue());
- case PhabricatorProjectTransaction::TYPE_MILESTONE:
- $current = queryfx_one(
- $object->establishConnection('w'),
- 'SELECT MAX(milestoneNumber) n
- FROM %T
- WHERE parentProjectPHID = %s',
- $object->getTableName(),
- $object->getParentProject()->getPHID());
- if (!$current) {
- $number = 1;
- } else {
- $number = (int)$current['n'] + 1;
- }
- return $number;
}
return parent::getCustomTransactionNewValue($object, $xaction);
@@ -127,7 +114,21 @@
$object->setParentProjectPHID($xaction->getNewValue());
return;
case PhabricatorProjectTransaction::TYPE_MILESTONE:
- $object->setMilestoneNumber($xaction->getNewValue());
+ $current = queryfx_one(
+ $object->establishConnection('w'),
+ 'SELECT MAX(milestoneNumber) n
+ FROM %T
+ WHERE parentProjectPHID = %s',
+ $object->getTableName(),
+ $object->getParentProject()->getPHID());
+ if (!$current) {
+ $number = 1;
+ } else {
+ $number = (int)$current['n'] + 1;
+ }
+
+ $object->setMilestoneNumber($number);
+ $object->setParentProjectPHID($xaction->getNewValue());
return;
}
@@ -239,6 +240,84 @@
return parent::applyBuiltinExternalTransaction($object, $xaction);
}
+ protected function validateAllTransactions(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $errors = array();
+
+ // Prevent creating projects which are both subprojects and milestones,
+ // since this does not make sense, won't work, and will break everything.
+ $parent_xaction = null;
+ foreach ($xactions as $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorProjectTransaction::TYPE_PARENT:
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
+ if ($xaction->getNewValue() === null) {
+ continue;
+ }
+
+ if (!$parent_xaction) {
+ $parent_xaction = $xaction;
+ continue;
+ }
+
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $xaction->getTransactionType(),
+ pht('Invalid'),
+ pht(
+ 'When creating a project, specify a maximum of one parent '.
+ 'project or milestone project. A project can not be both a '.
+ 'subproject and a milestone.'),
+ $xaction);
+ break;
+ break;
+ }
+ }
+
+ $is_milestone = $object->isMilestone();
+ foreach ($xactions as $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
+ if ($xaction->getNewValue() !== null) {
+ $is_milestone = true;
+ }
+ break;
+ }
+ }
+
+ $is_parent = $object->getHasSubprojects();
+
+ foreach ($xactions as $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorProjectTransaction::TYPE_MEMBERS:
+ if ($is_parent) {
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $xaction->getTransactionType(),
+ pht('Invalid'),
+ pht(
+ 'You can not change members of a project with subprojects '.
+ 'directly. Members of any subproject are automatically '.
+ 'members of the parent project.'),
+ $xaction);
+ }
+
+ if ($is_milestone) {
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $xaction->getTransactionType(),
+ pht('Invalid'),
+ pht(
+ 'You can not change members of a milestone. Members of the '.
+ 'parent project are automatically members of the milestone.'),
+ $xaction);
+ }
+ break;
+ }
+ }
+
+ return $errors;
+ }
+
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
@@ -367,25 +446,29 @@
break;
case PhabricatorProjectTransaction::TYPE_PARENT:
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
if (!$xactions) {
break;
}
$xaction = last($xactions);
+ $parent_phid = $xaction->getNewValue();
+ if (!$parent_phid) {
+ continue;
+ }
+
if (!$this->getIsNewObject()) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
- 'You can only set a parent project when creating a project '.
- 'for the first time.'),
+ 'You can only set a parent or milestone project when creating a '.
+ 'project for the first time.'),
$xaction);
break;
}
- $parent_phid = $xaction->getNewValue();
-
$projects = id(new PhabricatorProjectQuery())
->setViewer($this->requireActor())
->withPHIDs(array($parent_phid))
@@ -400,8 +483,8 @@
$type,
pht('Invalid'),
pht(
- 'Parent project PHID ("%s") must be the PHID of a valid, '.
- 'visible project which you have permission to edit.',
+ 'Parent or milestone project PHID ("%s") must be the PHID of a '.
+ 'valid, visible project which you have permission to edit.',
$parent_phid),
$xaction);
break;
@@ -414,8 +497,8 @@
$type,
pht('Invalid'),
pht(
- 'Parent project PHID ("%s") must not be a milestone. '.
- 'Milestones may not have subprojects.',
+ 'Parent or milestone project PHID ("%s") must not be a '.
+ 'milestone. Milestones may not have subprojects or milestones.',
$parent_phid),
$xaction);
break;
@@ -427,9 +510,9 @@
$type,
pht('Invalid'),
pht(
- 'You can not create a subproject under this parent because '.
- 'it would nest projects too deeply. The maximum nesting '.
- 'depth of projects is %s.',
+ 'You can not create a subproject or mielstone under this parent '.
+ 'because it would nest projects too deeply. The maximum '.
+ 'nesting depth of projects is %s.',
new PhutilNumber($limit)),
$xaction);
break;
@@ -611,6 +694,7 @@
array $xactions) {
$materialize = false;
+ $new_parent = null;
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_EDGE:
@@ -622,10 +706,34 @@
break;
case PhabricatorProjectTransaction::TYPE_PARENT:
$materialize = true;
+ $new_parent = $object->getParentProject();
break;
}
}
+ if ($new_parent) {
+ // If we just created the first subproject of this parent, we want to
+ // copy all of the real members to the subproject.
+ if (!$new_parent->getHasSubprojects()) {
+ $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
+
+ $project_members = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $new_parent->getPHID(),
+ $member_type);
+
+ if ($project_members) {
+ $editor = id(new PhabricatorEdgeEditor());
+ foreach ($project_members as $phid) {
+ $editor->addEdge($object->getPHID(), $member_type, $phid);
+ }
+ $editor->save();
+ }
+ }
+ }
+
+ // TODO: We should dump an informational transaction onto the parent
+ // project to show that we created the sub-thing.
+
if ($materialize) {
id(new PhabricatorProjectsMembershipIndexEngineExtension())
->rematerialize($object);
diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php
--- a/src/applications/project/engine/PhabricatorProjectEditEngine.php
+++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php
@@ -5,6 +5,27 @@
const ENGINECONST = 'projects.project';
+ private $parentProject;
+ private $milestoneProject;
+
+ public function setParentProject(PhabricatorProject $parent_project) {
+ $this->parentProject = $parent_project;
+ return $this;
+ }
+
+ public function getParentProject() {
+ return $this->parentProject;
+ }
+
+ public function setMilestoneProject(PhabricatorProject $milestone_project) {
+ $this->milestoneProject = $milestone_project;
+ return $this;
+ }
+
+ public function getMilestoneProject() {
+ return $this->milestoneProject;
+ }
+
public function getEngineName() {
return pht('Projects');
}
@@ -50,6 +71,22 @@
return $object->getURI();
}
+ protected function getObjectCreateCancelURI($object) {
+ $parent = $this->getParentProject();
+ if ($parent) {
+ $id = $parent->getID();
+ return "/project/subprojects/{$id}/";
+ }
+
+ $milestone = $this->getMilestoneProject();
+ if ($milestone) {
+ $id = $milestone->getID();
+ return "/project/milestones/{$id}/";
+ }
+
+ return parent::getObjectCreateCancelURI($object);
+ }
+
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
ProjectCreateProjectsCapability::CAPABILITY);
@@ -65,6 +102,8 @@
$configuration
->setFieldOrder(
array(
+ 'parent',
+ 'milestone',
'name',
'std:project:internal:description',
'icon',
@@ -84,7 +123,52 @@
unset($slugs[$object->getPrimarySlug()]);
$slugs = array_values($slugs);
+ $milestone = $this->getMilestoneProject();
+ $parent = $this->getParentProject();
+
+ if ($parent) {
+ $parent_phid = $parent->getPHID();
+ } else {
+ $parent_phid = null;
+ }
+
+ if ($milestone) {
+ $milestone_phid = $milestone->getPHID();
+ } else {
+ $milestone_phid = null;
+ }
+
return array(
+ id(new PhabricatorHandlesEditField())
+ ->setKey('parent')
+ ->setLabel(pht('Parent'))
+ ->setDescription(pht('Create a subproject of an existing project.'))
+ ->setConduitDescription(
+ pht('Choose a parent project to create a subproject beneath.'))
+ ->setConduitTypeDescription(pht('PHID of the parent project.'))
+ ->setAliases(array('parentPHID'))
+ ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT)
+ ->setHandleParameterType(new AphrontPHIDHTTPParameterType())
+ ->setSingleValue($parent_phid)
+ ->setIsReorderable(false)
+ ->setIsDefaultable(false)
+ ->setIsLockable(false)
+ ->setIsLocked(true),
+ id(new PhabricatorHandlesEditField())
+ ->setKey('milestone')
+ ->setLabel(pht('Milestone Of'))
+ ->setDescription(pht('Parent project to create a milestone for.'))
+ ->setConduitDescription(
+ pht('Choose a parent project to create a new milestone for.'))
+ ->setConduitTypeDescription(pht('PHID of the parent project.'))
+ ->setAliases(array('milestonePHID'))
+ ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE)
+ ->setHandleParameterType(new AphrontPHIDHTTPParameterType())
+ ->setSingleValue($milestone_phid)
+ ->setIsReorderable(false)
+ ->setIsDefaultable(false)
+ ->setIsLockable(false)
+ ->setIsLocked(true),
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php
--- a/src/applications/project/query/PhabricatorProjectSearchEngine.php
+++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php
@@ -164,42 +164,15 @@
array $handles) {
assert_instances_of($projects, 'PhabricatorProject');
$viewer = $this->requireViewer();
- $handles = $viewer->loadHandles(mpull($projects, 'getPHID'));
-
- $list = new PHUIObjectItemListView();
- $list->setUser($viewer);
- $can_edit_projects = id(new PhabricatorPolicyFilter())
- ->setViewer($viewer)
- ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
- ->apply($projects);
-
- foreach ($projects as $key => $project) {
- $id = $project->getID();
-
- $tag_list = id(new PHUIHandleTagListView())
- ->setSlim(true)
- ->setHandles(array($handles[$project->getPHID()]));
-
- $item = id(new PHUIObjectItemView())
- ->setHeader($project->getName())
- ->setHref($this->getApplicationURI("view/{$id}/"))
- ->setImageURI($project->getProfileImageURI())
- ->addAttribute($tag_list);
-
- if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) {
- $item->addIcon('delete-grey', pht('Archived'));
- $item->setDisabled(true);
- }
-
- $list->addItem($item);
- }
-
- $result = new PhabricatorApplicationSearchResultView();
- $result->setObjectList($list);
- $result->setNoDataString(pht('No projects found.'));
- return $result;
+ $list = id(new PhabricatorProjectListView())
+ ->setUser($viewer)
+ ->setProjects($projects)
+ ->renderList();
+ return id(new PhabricatorApplicationSearchResultView())
+ ->setObjectList($list)
+ ->setNoDataString(pht('No projects found.'));
}
protected function getNewUserBody() {
diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php
--- a/src/applications/project/storage/PhabricatorProject.php
+++ b/src/applications/project/storage/PhabricatorProject.php
@@ -88,6 +88,10 @@
}
public function getPolicy($capability) {
+ if ($this->isMilestone()) {
+ return $this->getParentProject()->getPolicy($capability);
+ }
+
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
@@ -99,6 +103,12 @@
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ if ($this->isMilestone()) {
+ return $this->getParentProject()->hasAutomaticCapability(
+ $capability,
+ $viewer);
+ }
+
$can_edit = PhabricatorPolicyCapability::CAN_EDIT;
switch ($capability) {
@@ -437,6 +447,34 @@
return $ancestors;
}
+ public function supportsEditMembers() {
+ if ($this->isMilestone()) {
+ return false;
+ }
+
+ if ($this->getHasSubprojects()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function supportsMilestones() {
+ if ($this->isMilestone()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function supportsSubprojects() {
+ if ($this->isMilestone()) {
+ return false;
+ }
+
+ return true;
+ }
+
/* -( PhabricatorSubscribableInterface )----------------------------------- */
diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/view/PhabricatorProjectListView.php
@@ -0,0 +1,53 @@
+<?php
+
+final class PhabricatorProjectListView extends AphrontView {
+
+ private $projects;
+
+ public function setProjects(array $projects) {
+ $this->projects = $projects;
+ return $this;
+ }
+
+ public function getProjects() {
+ return $this->projects;
+ }
+
+ public function renderList() {
+ $viewer = $this->getUser();
+ $projects = $this->getProjects();
+
+ $handles = $viewer->loadHandles(mpull($projects, 'getPHID'));
+
+ $list = id(new PHUIObjectItemListView())
+ ->setUser($viewer);
+
+ foreach ($projects as $key => $project) {
+ $id = $project->getID();
+
+ $tag_list = id(new PHUIHandleTagListView())
+ ->setSlim(true)
+ ->setHandles(array($handles[$project->getPHID()]));
+
+ $item = id(new PHUIObjectItemView())
+ ->setHeader($project->getName())
+ ->setHref("/project/view/{$id}/")
+ ->setImageURI($project->getProfileImageURI())
+ ->addAttribute($tag_list);
+
+ if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) {
+ $item->addIcon('delete-grey', pht('Archived'));
+ $item->setDisabled(true);
+ }
+
+ $list->addItem($item);
+ }
+
+ return $list;
+ }
+
+ public function render() {
+ return $this->renderList();
+ }
+
+}
diff --git a/webroot/rsrc/css/layout/phabricator-side-menu-view.css b/webroot/rsrc/css/layout/phabricator-side-menu-view.css
--- a/webroot/rsrc/css/layout/phabricator-side-menu-view.css
+++ b/webroot/rsrc/css/layout/phabricator-side-menu-view.css
@@ -89,6 +89,10 @@
color: {$blue};
}
+.phabricator-icon-nav .phabricator-side-menu .phui-list-item-icon.grey {
+ color: {$lightgreyborder};
+}
+
.phabricator-icon-nav .phabricator-side-menu .phui-list-item-selected {
border: none;
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Aug 11, 6:03 PM (22 h, 52 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
16522849
Default Alt Text
D14904.diff (32 KB)
Attached To
Mode
D14904: Add very basic UI for creating milestones and subprojects
Attached
Detach File
Event Timeline
Log In to Comment