Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F18852484
D15171.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
31 KB
Referenced Files
None
Subscribers
None
D15171.diff
View Options
diff --git a/resources/builtin/image-200x200.png b/resources/builtin/image-200x200.png
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
literal 0
Hc$@<O00001
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -413,7 +413,7 @@
'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 @@
'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 @@
'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 @@
'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',
diff --git a/resources/sql/autopatches/20160202.board.1.proxy.sql b/resources/sql/autopatches/20160202.board.1.proxy.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160202.board.1.proxy.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_project.project_column
+ ADD proxyPHID VARBINARY(64);
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
@@ -1889,6 +1889,7 @@
'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 @@
'PhabricatorDestructibleInterface',
'PhabricatorFulltextInterface',
'PhabricatorConduitResultInterface',
+ 'PhabricatorColumnProxyInterface',
),
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
'PhabricatorProjectApplication' => 'PhabricatorApplication',
diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php
--- a/src/applications/maniphest/editor/ManiphestEditEngine.php
+++ b/src/applications/maniphest/editor/ManiphestEditEngine.php
@@ -280,10 +280,23 @@
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)
diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
--- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php
+++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
@@ -222,12 +222,22 @@
// 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');
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
@@ -972,7 +972,45 @@
$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 @@
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 @@
$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 @@
$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;
}
diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
--- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
@@ -95,17 +95,27 @@
$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 @@
}
}
+ $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 @@
$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 @@
$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 @@
->setMetadata(
array(
'columnPHID' => $column->getPHID(),
+ 'projectPHID' => $default_phid,
));
$batch_edit_uri = $request->getRequestURI();
@@ -738,6 +767,10 @@
}
}
+ // 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')
diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php
--- a/src/applications/project/controller/PhabricatorProjectMoveController.php
+++ b/src/applications/project/controller/PhabricatorProjectMoveController.php
@@ -139,7 +139,33 @@
->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 @@
->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 @@
return id(new AphrontAjaxResponse())->setContent(
array('task' => $card));
- }
+ }
}
diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php b/src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php
--- a/src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php
+++ b/src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php
@@ -35,7 +35,7 @@
$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.",
diff --git a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
--- a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
+++ b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
@@ -320,8 +320,63 @@
$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 @@
array $columns,
array $positions) {
+ $viewer = $this->getViewer();
+
$board_phid = $board->getPHID();
$position_groups = mgroup($positions, 'getObjectPHID');
@@ -363,32 +420,143 @@
}
}
+ // 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);
+
+ $proxy_hits = array_fuse($proxy_hits);
+
+ // 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]);
+ }
+ }
- $this->addQueue[] = $new_position;
+ // 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);
- $positions = array(
- $new_position,
- );
+ $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) {
diff --git a/src/applications/project/interface/PhabricatorColumnProxyInterface.php b/src/applications/project/interface/PhabricatorColumnProxyInterface.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/interface/PhabricatorColumnProxyInterface.php
@@ -0,0 +1,7 @@
+<?php
+
+interface PhabricatorColumnProxyInterface {
+
+ public function getProxyColumnName();
+
+}
diff --git a/src/applications/project/query/PhabricatorProjectColumnQuery.php b/src/applications/project/query/PhabricatorProjectColumnQuery.php
--- a/src/applications/project/query/PhabricatorProjectColumnQuery.php
+++ b/src/applications/project/query/PhabricatorProjectColumnQuery.php
@@ -6,6 +6,7 @@
private $ids;
private $phids;
private $projectPHIDs;
+ private $proxyPHIDs;
private $statuses;
public function withIDs(array $ids) {
@@ -23,6 +24,11 @@
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 @@
$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 @@
$this->projectPHIDs);
}
+ if ($this->proxyPHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'proxyPHID IN (%Ls)',
+ $this->proxyPHIDs);
+ }
+
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
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
@@ -9,7 +9,8 @@
PhabricatorCustomFieldInterface,
PhabricatorDestructibleInterface,
PhabricatorFulltextInterface,
- PhabricatorConduitResultInterface {
+ PhabricatorConduitResultInterface,
+ PhabricatorColumnProxyInterface {
protected $name;
protected $status = PhabricatorProjectStatus::STATUS_ACTIVE;
@@ -663,4 +664,25 @@
);
}
+
+/* -( 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;
+ }
+
+
}
diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php
--- a/src/applications/project/storage/PhabricatorProjectColumn.php
+++ b/src/applications/project/storage/PhabricatorProjectColumn.php
@@ -17,10 +17,12 @@
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 @@
'name' => 'text255',
'status' => 'uint32',
'sequence' => 'uint32',
+ 'proxyPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_status' => array(
@@ -46,6 +49,10 @@
'key_sequence' => array(
'columns' => array('projectPHID', 'sequence'),
),
+ 'key_proxy' => array(
+ 'columns' => array('projectPHID', 'proxyPHID'),
+ 'unique' => true,
+ ),
),
) + parent::getConfiguration();
}
@@ -64,6 +71,15 @@
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 @@
}
public function getDisplayName() {
+ $proxy = $this->getProxy();
+ if ($proxy) {
+ return $proxy->getProxyColumnName();
+ }
+
$name = $this->getName();
if (strlen($name)) {
return $name;
@@ -96,11 +117,23 @@
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;
diff --git a/src/docs/user/userguide/projects.diviner b/src/docs/user/userguide/projects.diviner
--- a/src/docs/user/userguide/projects.diviner
+++ b/src/docs/user/userguide/projects.diviner
@@ -162,7 +162,6 @@
|---|---|---|---|---|
| //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 @@
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
diff --git a/src/view/phui/PHUIWorkpanelView.php b/src/view/phui/PHUIWorkpanelView.php
--- a/src/view/phui/PHUIWorkpanelView.php
+++ b/src/view/phui/PHUIWorkpanelView.php
@@ -10,8 +10,8 @@
private $headerTag;
private $headerIcon;
- public function setHeaderIcon(PHUIIconView $header_icon) {
- $this->headerIcon = $header_icon;
+ public function setHeaderIcon($icon) {
+ $this->headerIcon = $icon;
return $this;
}
diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js
--- a/webroot/rsrc/js/application/projects/behavior-project-boards.js
+++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js
@@ -280,13 +280,16 @@
// 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;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Nov 1 2025, 9:26 AM (4 w, 5 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/kd/il/7reat64kvlfmujlb
Default Alt Text
D15171.diff (31 KB)
Attached To
Mode
D15171: Roughly implement milestone columns on workboards
Attached
Detach File
Event Timeline
Log In to Comment