1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 11:30:55 +01:00

Roughly implement milestone columns on workboards

Summary:
Ref T10010. These aren't perfect but I think (?) they aren't horribly broken.

  - When a project is a parent project, destroy (as far as the user can tell) any custom columns.
  - When a project has milestones, automatically generate columns on the project's workboard (if it has a workboard).
  - When you move tasks between milestones, add the proper milestone tag.
  - When you move tasks out of milestones back into the backlog, add the proper parent project tag.
  - (Plenty of UI / design stuff to adjust.)

Test Plan:
  - Dragged stuff between milestone columns.
  - Used a normal workboard.
  - Wasn't able to find any egregiously bad cases that did anything terrible.

{F1088224}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10010

Differential Revision: https://secure.phabricator.com/D15171
This commit is contained in:
epriestley 2016-02-02 09:53:18 -08:00
parent 00165424d0
commit 90a0459821
18 changed files with 496 additions and 57 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -413,7 +413,7 @@ return array(
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
'rsrc/js/application/policy/behavior-policy-control.js' => 'ae45872f',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
'rsrc/js/application/projects/behavior-project-boards.js' => 'c05fb42a',
'rsrc/js/application/projects/behavior-project-boards.js' => '48470f95',
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
@ -653,7 +653,7 @@ return array(
'javelin-behavior-phui-profile-menu' => '12884df9',
'javelin-behavior-policy-control' => 'ae45872f',
'javelin-behavior-policy-rule-editor' => '5e9f347c',
'javelin-behavior-project-boards' => 'c05fb42a',
'javelin-behavior-project-boards' => '48470f95',
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
'javelin-behavior-recurring-edit' => '5f1c4d5f',
@ -1151,6 +1151,15 @@ return array(
'javelin-dom',
'javelin-workflow',
),
'48470f95' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'49b73b36' => array(
'javelin-behavior',
'javelin-dom',
@ -1779,15 +1788,6 @@ return array(
'javelin-install',
'javelin-dom',
),
'c05fb42a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'c1700f6f' => array(
'javelin-install',
'javelin-util',

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_project.project_column
ADD proxyPHID VARBINARY(64);

View file

@ -1889,6 +1889,7 @@ phutil_register_library_map(array(
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php',
'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php',
'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php',
'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php',
@ -7262,6 +7263,7 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface',
'PhabricatorConduitResultInterface',
'PhabricatorColumnProxyInterface',
),
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
'PhabricatorProjectApplication' => 'PhabricatorApplication',

View file

@ -280,10 +280,23 @@ final class ManiphestEditEngine
return new Aphront404Response();
}
// If the workboard's project has been removed from the card's project
// list, we are going to remove it from the board completely.
// If the workboard's project and all descendant projects have been removed
// from the card's project list, we are going to remove it from the board
// completely.
// TODO: If the user did something sneaky and changed a subproject, we'll
// currently leave the card where it was but should really move it to the
// proper new column.
$descendant_projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withAncestorProjectPHIDs(array($column->getProjectPHID()))
->execute();
$board_phids = mpull($descendant_projects, 'getPHID', 'getPHID');
$board_phids[$column->getProjectPHID()] = $column->getProjectPHID();
$project_map = array_fuse($task->getProjectPHIDs());
$remove_card = empty($project_map[$column->getProjectPHID()]);
$remove_card = !array_intersect_key($board_phids, $project_map);
$positions = id(new PhabricatorProjectColumnPositionQuery())
->setViewer($viewer)

View file

@ -222,12 +222,22 @@ final class ManiphestTransactionEditor
// can't see.
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
$select_phids = array($board_phid);
$descendants = id(new PhabricatorProjectQuery())
->setViewer($omnipotent_viewer)
->withAncestorProjectPHIDs($select_phids)
->execute();
foreach ($descendants as $descendant) {
$select_phids[] = $descendant->getPHID();
}
$board_tasks = id(new ManiphestTaskQuery())
->setViewer($omnipotent_viewer)
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_AND,
array($board_phid))
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
array($select_phids))
->execute();
$object_phids = mpull($board_tasks, 'getPHID');

View file

@ -972,7 +972,45 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
$task1->getPHID(),
);
$this->assertTasksInColumn($expect, $user, $board, $column);
}
public function testMilestoneMoves() {
$user = $this->createUser();
$user->save();
$board = $this->createProject($user);
$backlog = $this->addColumn($user, $board, 0);
// Create a task into the backlog.
$task = $this->newTask($user, array($board));
$expect = array(
$backlog->getPHID(),
);
$this->assertColumns($expect, $user, $board, $task);
$milestone = $this->createProject($user, $board, true);
$this->addProjectTags($user, $task, array($milestone->getPHID()));
// We just want the side effect of looking at the board: creation of the
// milestone column.
$this->loadColumns($user, $board, $task);
$column = id(new PhabricatorProjectColumnQuery())
->setViewer($user)
->withProjectPHIDs(array($board->getPHID()))
->withProxyPHIDs(array($milestone->getPHID()))
->executeOne();
$this->assertTrue((bool)$column);
// Moving the task to the milestone should have moved it to the milestone
// column.
$expect = array(
$column->getPHID(),
);
$this->assertColumns($expect, $user, $board, $task);
}
private function moveToColumn(
@ -1014,7 +1052,14 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
PhabricatorUser $viewer,
PhabricatorProject $board,
ManiphestTask $task) {
$column_phids = $this->loadColumns($viewer, $board, $task);
$this->assertEqual($expect, $column_phids);
}
private function loadColumns(
PhabricatorUser $viewer,
PhabricatorProject $board,
ManiphestTask $task) {
$engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer)
->setBoardPHIDs(array($board->getPHID()))
@ -1028,7 +1073,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
$column_phids = mpull($columns, 'getPHID');
$column_phids = array_values($column_phids);
$this->assertEqual($expect, $column_phids);
return $column_phids;
}
private function assertTasksInColumn(
@ -1236,6 +1281,16 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
$this->applyTransactions($project, $user, $xactions);
// Force these values immediately; they are normally updated by the
// index engine.
if ($parent) {
if ($is_milestone) {
$parent->setHasMilestones(1)->save();
} else {
$parent->setHasSubprojects(1)->save();
}
}
return $project;
}

View file

@ -95,17 +95,27 @@ final class PhabricatorProjectBoardViewController
$task_query = $search_engine->buildQueryFromSavedQuery($saved);
$select_phids = array($project->getPHID());
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
$descendants = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withAncestorProjectPHIDs($select_phids)
->execute();
foreach ($descendants as $descendant) {
$select_phids[] = $descendant->getPHID();
}
}
$tasks = $task_query
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_AND,
array($project->getPHID()))
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
array($select_phids))
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
->setViewer($viewer)
->execute();
$tasks = mpull($tasks, null, 'getPHID');
$board_phid = $project->getPHID();
$layout_engine = id(new PhabricatorBoardLayoutEngine())
@ -225,6 +235,13 @@ final class PhabricatorProjectBoardViewController
}
}
$proxy = $column->getProxy();
if ($proxy && !$proxy->isMilestone()) {
// TODO: For now, don't show subproject columns because we can't
// handle tasks with multiple positions yet.
continue;
}
$task_phids = $layout_engine->getColumnObjectPHIDs(
$board_phid,
$column->getPHID());
@ -247,6 +264,11 @@ final class PhabricatorProjectBoardViewController
$panel->setHeaderIcon($header_icon);
}
$display_class = $column->getDisplayClass();
if ($display_class) {
$panel->addClass($display_class);
}
if ($column->isHidden()) {
$panel->addClass('project-panel-hidden');
}
@ -582,6 +604,12 @@ final class PhabricatorProjectBoardViewController
$column_items = array();
if ($column->getProxyPHID()) {
$default_phid = $column->getProxyPHID();
} else {
$default_phid = $column->getProjectPHID();
}
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-plus')
->setName(pht('Create Task...'))
@ -590,6 +618,7 @@ final class PhabricatorProjectBoardViewController
->setMetadata(
array(
'columnPHID' => $column->getPHID(),
'projectPHID' => $default_phid,
));
$batch_edit_uri = $request->getRequestURI();
@ -738,6 +767,10 @@ final class PhabricatorProjectBoardViewController
}
}
// TODO: Tailor this UI if the project is already a parent project. We
// should not offer options for creating a parent project workboard, since
// they can't have their own columns.
$new_selector = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Columns'))
->setName('initialize-type')

View file

@ -139,7 +139,33 @@ final class PhabricatorProjectMoveController
->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
->setNewValue($sub);
}
}
}
$proxy = $column->getProxy();
if ($proxy) {
// We're moving the task into a subproject or milestone column, so add
// the subproject or milestone.
$add_projects = array($proxy->getPHID());
} else if ($project->getHasSubprojects() || $project->getHasMilestones()) {
// We're moving the task into the "Backlog" column on the parent project,
// so add the parent explicitly. This gets rid of any subproject or
// milestone tags.
$add_projects = array($project->getPHID());
} else {
$add_projects = array();
}
if ($add_projects) {
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $project_type)
->setNewValue(
array(
'+' => array_fuse($add_projects),
));
}
$editor = id(new ManiphestTransactionEditor())
->setActor($viewer)
@ -157,6 +183,18 @@ final class PhabricatorProjectMoveController
->executeOne();
}
// Reload the object so it reflects edits which have been applied.
$object = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withPHIDs(array($object_phid))
->needProjectPHIDs(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
$card = id(new ProjectBoardTaskCard())
->setViewer($viewer)
->setTask($object)
@ -169,6 +207,6 @@ final class PhabricatorProjectMoveController
return id(new AphrontAjaxResponse())->setContent(
array('task' => $card));
}
}
}

View file

@ -35,7 +35,7 @@ final class PhabricatorProjectSubprojectWarningController
$conversion_help = pht(
"Creating a project's first subproject **moves all ".
"members** and **destroys all workboard columns**.".
"members** to become members of the subproject instead".
"\n\n".
"See [[ %s | Projects User Guide ]] in the documentation for details. ".
"This process can not be undone.",

View file

@ -320,8 +320,63 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
$columns = msort($columns, 'getSequence');
$columns = mpull($columns, null, 'getPHID');
$this->columnMap = $columns;
$need_children = array();
foreach ($boards as $phid => $board) {
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
$need_children[] = $phid;
}
}
if ($need_children) {
$children = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withParentProjectPHIDs($need_children)
->execute();
$children = mpull($children, null, 'getPHID');
$children = mgroup($children, 'getParentProjectPHID');
} else {
$children = array();
}
$columns = mgroup($columns, 'getProjectPHID');
foreach ($boards as $board_phid => $board) {
$board_columns = idx($columns, $board_phid, array());
// If the project has milestones, create any missing columns.
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
$child_projects = idx($children, $board_phid, array());
$next_sequence = last($board_columns)->getSequence() + 1;
$proxy_columns = mpull($board_columns, null, 'getProxyPHID');
foreach ($child_projects as $child_phid => $child) {
if (isset($proxy_columns[$child_phid])) {
continue;
}
$new_column = PhabricatorProjectColumn::initializeNewColumn($viewer)
->attachProject($board)
->attachProxy($child)
->setSequence($next_sequence++)
->setProjectPHID($board_phid)
->setProxyPHID($child_phid);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$new_column->save();
unset($unguarded);
$board_columns[$new_column->getPHID()] = $new_column;
}
}
$columns[$board_phid] = $board_columns;
}
foreach ($columns as $board_phid => $board_columns) {
foreach ($board_columns as $board_column) {
$column_phid = $board_column->getPHID();
$this->columnMap[$column_phid] = $board_column;
}
}
return $columns;
}
@ -350,6 +405,8 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
array $columns,
array $positions) {
$viewer = $this->getViewer();
$board_phid = $board->getPHID();
$position_groups = mgroup($positions, 'getObjectPHID');
@ -363,32 +420,143 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
}
}
// Find all the columns which are proxies for other objects.
$proxy_map = array();
foreach ($columns as $column) {
$proxy_phid = $column->getProxyPHID();
if ($proxy_phid) {
$proxy_map[$proxy_phid] = $column->getPHID();
}
}
$object_phids = $this->getObjectPHIDs();
// If we have proxies, we need to force cards into the correct proxy
// columns.
if ($proxy_map) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($object_phids)
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
$project_phids = $edge_query->getDestinationPHIDs();
$project_phids = array_fuse($project_phids);
} else {
$project_phids = array();
}
if ($project_phids) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withPHIDs($project_phids)
->execute();
$projects = mpull($projects, null, 'getPHID');
} else {
$projects = array();
}
// Build a map from every project that any task is tagged with to the
// ancestor project which has a column on this board, if one exists.
$ancestor_map = array();
foreach ($projects as $phid => $project) {
if (isset($proxy_map[$phid])) {
$ancestor_map[$phid] = $proxy_map[$phid];
} else {
$seen = array($phid);
foreach ($project->getAncestorProjects() as $ancestor) {
$ancestor_phid = $ancestor->getPHID();
$seen[] = $ancestor_phid;
if (isset($proxy_map[$ancestor_phid])) {
foreach ($seen as $project_phid) {
$ancestor_map[$project_phid] = $proxy_map[$ancestor_phid];
}
}
}
}
}
foreach ($object_phids as $object_phid) {
$positions = idx($position_groups, $object_phid, array());
// Remove any positions in columns which no longer exist.
foreach ($positions as $key => $position) {
$column_phid = $position->getColumnPHID();
if (empty($columns[$column_phid])) {
$this->remQueue[] = $position;
unset($positions[$key]);
// First, check for objects that have corresponding proxy columns. We're
// going to overwrite normal column positions if a tag belongs to a proxy
// column, since you can't be in normal columns if you're in proxy
// columns.
$proxy_hits = array();
if ($proxy_map) {
$object_project_phids = $edge_query->getDestinationPHIDs(
array(
$object_phid,
));
foreach ($object_project_phids as $project_phid) {
if (isset($ancestor_map[$project_phid])) {
$proxy_hits[] = $ancestor_map[$project_phid];
}
}
}
// If the object has no position, put it on the default column.
if (!$positions) {
$new_position = id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($default_phid)
->setObjectPHID($object_phid)
->setSequence(0);
if ($proxy_hits) {
// TODO: For now, only one column hit is permissible.
$proxy_hits = array_slice($proxy_hits, 0, 1);
$this->addQueue[] = $new_position;
$proxy_hits = array_fuse($proxy_hits);
$positions = array(
$new_position,
);
// Check the object positions: we hope to find a position in each
// column the object should be part of. We're going to drop any
// invalid positions and create new positions where positions are
// missing.
foreach ($positions as $key => $position) {
$column_phid = $position->getColumnPHID();
if (isset($proxy_hits[$column_phid])) {
// Valid column, mark the position as found.
unset($proxy_hits[$column_phid]);
} else {
// Invalid column, ignore the position.
unset($positions[$key]);
}
}
// Create new positions for anything we haven't found.
foreach ($proxy_hits as $proxy_hit) {
$new_position = id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($proxy_hit)
->setObjectPHID($object_phid)
->setSequence(0);
$this->addQueue[] = $new_position;
$positions[] = $new_position;
}
} else {
// Ignore any positions in columns which no longer exist. We don't
// actively destory them because the rest of the code ignores them and
// there's no real need to destroy the data.
foreach ($positions as $key => $position) {
$column_phid = $position->getColumnPHID();
if (empty($columns[$column_phid])) {
unset($positions[$key]);
}
}
// If the object has no position, put it on the default column.
if (!$positions) {
$new_position = id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($default_phid)
->setObjectPHID($object_phid)
->setSequence(0);
$this->addQueue[] = $new_position;
$positions = array(
$new_position,
);
}
}
foreach ($positions as $position) {

View file

@ -0,0 +1,7 @@
<?php
interface PhabricatorColumnProxyInterface {
public function getProxyColumnName();
}

View file

@ -6,6 +6,7 @@ final class PhabricatorProjectColumnQuery
private $ids;
private $phids;
private $projectPHIDs;
private $proxyPHIDs;
private $statuses;
public function withIDs(array $ids) {
@ -23,6 +24,11 @@ final class PhabricatorProjectColumnQuery
return $this;
}
public function withProxyPHIDs(array $proxy_phids) {
$this->proxyPHIDs = $proxy_phids;
return $this;
}
public function withStatuses(array $status) {
$this->statuses = $status;
return $this;
@ -60,6 +66,55 @@ final class PhabricatorProjectColumnQuery
$column->attachProject($project);
}
$proxy_phids = array_filter(mpull($page, 'getProjectPHID'));
return $page;
}
protected function didFilterPage(array $page) {
$proxy_phids = array();
foreach ($page as $column) {
$proxy_phid = $column->getProxyPHID();
if ($proxy_phid !== null) {
$proxy_phids[$proxy_phid] = $proxy_phid;
}
}
if ($proxy_phids) {
$proxies = id(new PhabricatorObjectQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($proxy_phids)
->execute();
$proxies = mpull($proxies, null, 'getPHID');
} else {
$proxies = array();
}
foreach ($page as $key => $column) {
$proxy_phid = $column->getProxyPHID();
if ($proxy_phid !== null) {
$proxy = idx($proxies, $proxy_phid);
// Only attach valid proxies, so we don't end up getting surprsied if
// an install somehow gets junk into their database.
if (!($proxy instanceof PhabricatorColumnProxyInterface)) {
$proxy = null;
}
if (!$proxy) {
$this->didRejectResult($column);
unset($page[$key]);
continue;
}
} else {
$proxy = null;
}
$column->attachProxy($proxy);
}
return $page;
}
@ -87,6 +142,13 @@ final class PhabricatorProjectColumnQuery
$this->projectPHIDs);
}
if ($this->proxyPHIDs !== null) {
$where[] = qsprintf(
$conn,
'proxyPHID IN (%Ls)',
$this->proxyPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,

View file

@ -9,7 +9,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO
PhabricatorCustomFieldInterface,
PhabricatorDestructibleInterface,
PhabricatorFulltextInterface,
PhabricatorConduitResultInterface {
PhabricatorConduitResultInterface,
PhabricatorColumnProxyInterface {
protected $name;
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
@ -663,4 +664,25 @@ final class PhabricatorProject extends PhabricatorProjectDAO
);
}
/* -( PhabricatorColumnProxyInterface )------------------------------------ */
public function getProxyColumnName() {
return $this->getName();
}
public function getProxyColumnIcon() {
return $this->getDisplayIconIcon();
}
public function getProxyColumnClass() {
if ($this->isMilestone()) {
return 'phui-workboard-column-milestone';
}
return null;
}
}

View file

@ -17,10 +17,12 @@ final class PhabricatorProjectColumn
protected $name;
protected $status;
protected $projectPHID;
protected $proxyPHID;
protected $sequence;
protected $properties = array();
private $project = self::ATTACHABLE;
private $proxy = self::ATTACHABLE;
public static function initializeNewColumn(PhabricatorUser $user) {
return id(new PhabricatorProjectColumn())
@ -38,6 +40,7 @@ final class PhabricatorProjectColumn
'name' => 'text255',
'status' => 'uint32',
'sequence' => 'uint32',
'proxyPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_status' => array(
@ -46,6 +49,10 @@ final class PhabricatorProjectColumn
'key_sequence' => array(
'columns' => array('projectPHID', 'sequence'),
),
'key_proxy' => array(
'columns' => array('projectPHID', 'proxyPHID'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
@ -64,6 +71,15 @@ final class PhabricatorProjectColumn
return $this->assertAttached($this->project);
}
public function attachProxy($proxy) {
$this->proxy = $proxy;
return $this;
}
public function getProxy() {
return $this->assertAttached($this->proxy);
}
public function isDefaultColumn() {
return (bool)$this->getProperty('isDefault');
}
@ -73,6 +89,11 @@ final class PhabricatorProjectColumn
}
public function getDisplayName() {
$proxy = $this->getProxy();
if ($proxy) {
return $proxy->getProxyColumnName();
}
$name = $this->getName();
if (strlen($name)) {
return $name;
@ -96,11 +117,23 @@ final class PhabricatorProjectColumn
return null;
}
public function getDisplayClass() {
$proxy = $this->getProxy();
if ($proxy) {
return $proxy->getProxyColumnClass();
}
return null;
}
public function getHeaderIcon() {
$icon = null;
$proxy = $this->getProxy();
if ($proxy) {
return $proxy->getProxyColumnIcon();
}
if ($this->isHidden()) {
$icon = 'fa-eye-slash';
return 'fa-eye-slash';
}
return null;

View file

@ -162,7 +162,6 @@ subprojects, parent projects, and milestones.
|---|---|---|---|---|
| //Members// | Yes | Union of Subprojects | Yes | Same as Parent |
| //Policies// | Yes | Yes | Affected by Parent | Same as Parent |
| //Workboard// | Yes | No Custom Columns | Yes | Yes |
| //Hashtags// | Yes | Yes | Yes | Special |
@ -257,14 +256,6 @@ parent project is an ancestor of the new subproject.
You can edit the project afterward to change or remove members if you want to
split membership apart in a more granular way across multiple new subprojects.
**No Workboard Columns**: Parent projects can not have their own workboard
columns: instead, the workboard of a parent project shows columns representing
the child projects.
Thus, a project's workboard columns are destroyed when you add the first
subproject. All objects on the workboard will be returned to the project's
backlog. The new board will show columns for subprojects instead.
**Searching**: When you search for a parent project, results for any subproject
are returned. For example, if you search for {nav Engineering}, your query will
match results in {nav Engineering} itself, but also subprojects like

View file

@ -10,8 +10,8 @@ final class PHUIWorkpanelView extends AphrontTagView {
private $headerTag;
private $headerIcon;
public function setHeaderIcon(PHUIIconView $header_icon) {
$this->headerIcon = $header_icon;
public function setHeaderIcon($icon) {
$this->headerIcon = $icon;
return $this;
}

View file

@ -280,13 +280,16 @@ JX.behavior('project-boards', function(config, statics) {
// close the dropdown, but don't want to follow the link.
e.prevent();
var column_phid = e.getNodeData('column-add-task').columnPHID;
var column_data = e.getNodeData('column-add-task');
var column_phid = column_data.columnPHID;
var request_data = {
responseType: 'card',
columnPHID: column_phid,
projects: statics.projectPHID,
projects: column_data.projectPHID,
order: statics.order
};
var cols = getcolumns();
var ii;
var column;