Skip to content

Commit 90a0459

Browse files
author
epriestley
committedFeb 4, 2016
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
1 parent 0016542 commit 90a0459

18 files changed

+496
-57
lines changed
 

‎resources/builtin/image-200x200.png

1.23 KB
Loading

‎resources/celerity/map.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@
413413
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
414414
'rsrc/js/application/policy/behavior-policy-control.js' => 'ae45872f',
415415
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
416-
'rsrc/js/application/projects/behavior-project-boards.js' => 'c05fb42a',
416+
'rsrc/js/application/projects/behavior-project-boards.js' => '48470f95',
417417
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
418418
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
419419
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
@@ -653,7 +653,7 @@
653653
'javelin-behavior-phui-profile-menu' => '12884df9',
654654
'javelin-behavior-policy-control' => 'ae45872f',
655655
'javelin-behavior-policy-rule-editor' => '5e9f347c',
656-
'javelin-behavior-project-boards' => 'c05fb42a',
656+
'javelin-behavior-project-boards' => '48470f95',
657657
'javelin-behavior-project-create' => '065227cc',
658658
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
659659
'javelin-behavior-recurring-edit' => '5f1c4d5f',
@@ -1151,6 +1151,15 @@
11511151
'javelin-dom',
11521152
'javelin-workflow',
11531153
),
1154+
'48470f95' => array(
1155+
'javelin-behavior',
1156+
'javelin-dom',
1157+
'javelin-util',
1158+
'javelin-vector',
1159+
'javelin-stratcom',
1160+
'javelin-workflow',
1161+
'phabricator-draggable-list',
1162+
),
11541163
'49b73b36' => array(
11551164
'javelin-behavior',
11561165
'javelin-dom',
@@ -1779,15 +1788,6 @@
17791788
'javelin-install',
17801789
'javelin-dom',
17811790
),
1782-
'c05fb42a' => array(
1783-
'javelin-behavior',
1784-
'javelin-dom',
1785-
'javelin-util',
1786-
'javelin-vector',
1787-
'javelin-stratcom',
1788-
'javelin-workflow',
1789-
'phabricator-draggable-list',
1790-
),
17911791
'c1700f6f' => array(
17921792
'javelin-install',
17931793
'javelin-util',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE {$NAMESPACE}_project.project_column
2+
ADD proxyPHID VARBINARY(64);

‎src/__phutil_library_map__.php

+2
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,7 @@
18891889
'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php',
18901890
'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php',
18911891
'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php',
1892+
'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php',
18921893
'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php',
18931894
'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php',
18941895
'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php',
@@ -7262,6 +7263,7 @@
72627263
'PhabricatorDestructibleInterface',
72637264
'PhabricatorFulltextInterface',
72647265
'PhabricatorConduitResultInterface',
7266+
'PhabricatorColumnProxyInterface',
72657267
),
72667268
'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction',
72677269
'PhabricatorProjectApplication' => 'PhabricatorApplication',

‎src/applications/maniphest/editor/ManiphestEditEngine.php

+16-3
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,23 @@ private function buildCardResponse(ManiphestTask $task) {
280280
return new Aphront404Response();
281281
}
282282

283-
// If the workboard's project has been removed from the card's project
284-
// list, we are going to remove it from the board completely.
283+
// If the workboard's project and all descendant projects have been removed
284+
// from the card's project list, we are going to remove it from the board
285+
// completely.
286+
287+
// TODO: If the user did something sneaky and changed a subproject, we'll
288+
// currently leave the card where it was but should really move it to the
289+
// proper new column.
290+
291+
$descendant_projects = id(new PhabricatorProjectQuery())
292+
->setViewer($viewer)
293+
->withAncestorProjectPHIDs(array($column->getProjectPHID()))
294+
->execute();
295+
$board_phids = mpull($descendant_projects, 'getPHID', 'getPHID');
296+
$board_phids[$column->getProjectPHID()] = $column->getProjectPHID();
297+
285298
$project_map = array_fuse($task->getProjectPHIDs());
286-
$remove_card = empty($project_map[$column->getProjectPHID()]);
299+
$remove_card = !array_intersect_key($board_phids, $project_map);
287300

288301
$positions = id(new PhabricatorProjectColumnPositionQuery())
289302
->setViewer($viewer)

‎src/applications/maniphest/editor/ManiphestTransactionEditor.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,22 @@ protected function applyCustomExternalTransaction(
222222
// can't see.
223223
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
224224

225+
$select_phids = array($board_phid);
226+
227+
$descendants = id(new PhabricatorProjectQuery())
228+
->setViewer($omnipotent_viewer)
229+
->withAncestorProjectPHIDs($select_phids)
230+
->execute();
231+
foreach ($descendants as $descendant) {
232+
$select_phids[] = $descendant->getPHID();
233+
}
234+
225235
$board_tasks = id(new ManiphestTaskQuery())
226236
->setViewer($omnipotent_viewer)
227237
->withEdgeLogicPHIDs(
228238
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
229-
PhabricatorQueryConstraint::OPERATOR_AND,
230-
array($board_phid))
239+
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
240+
array($select_phids))
231241
->execute();
232242

233243
$object_phids = mpull($board_tasks, 'getPHID');

‎src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php

+56-1
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,45 @@ public function testBoardMoves() {
972972
$task1->getPHID(),
973973
);
974974
$this->assertTasksInColumn($expect, $user, $board, $column);
975+
}
976+
977+
public function testMilestoneMoves() {
978+
$user = $this->createUser();
979+
$user->save();
980+
981+
$board = $this->createProject($user);
982+
983+
$backlog = $this->addColumn($user, $board, 0);
984+
985+
// Create a task into the backlog.
986+
$task = $this->newTask($user, array($board));
987+
$expect = array(
988+
$backlog->getPHID(),
989+
);
990+
$this->assertColumns($expect, $user, $board, $task);
991+
992+
$milestone = $this->createProject($user, $board, true);
993+
994+
$this->addProjectTags($user, $task, array($milestone->getPHID()));
995+
996+
// We just want the side effect of looking at the board: creation of the
997+
// milestone column.
998+
$this->loadColumns($user, $board, $task);
999+
1000+
$column = id(new PhabricatorProjectColumnQuery())
1001+
->setViewer($user)
1002+
->withProjectPHIDs(array($board->getPHID()))
1003+
->withProxyPHIDs(array($milestone->getPHID()))
1004+
->executeOne();
1005+
1006+
$this->assertTrue((bool)$column);
9751007

1008+
// Moving the task to the milestone should have moved it to the milestone
1009+
// column.
1010+
$expect = array(
1011+
$column->getPHID(),
1012+
);
1013+
$this->assertColumns($expect, $user, $board, $task);
9761014
}
9771015

9781016
private function moveToColumn(
@@ -1014,7 +1052,14 @@ private function assertColumns(
10141052
PhabricatorUser $viewer,
10151053
PhabricatorProject $board,
10161054
ManiphestTask $task) {
1055+
$column_phids = $this->loadColumns($viewer, $board, $task);
1056+
$this->assertEqual($expect, $column_phids);
1057+
}
10171058

1059+
private function loadColumns(
1060+
PhabricatorUser $viewer,
1061+
PhabricatorProject $board,
1062+
ManiphestTask $task) {
10181063
$engine = id(new PhabricatorBoardLayoutEngine())
10191064
->setViewer($viewer)
10201065
->setBoardPHIDs(array($board->getPHID()))
@@ -1028,7 +1073,7 @@ private function assertColumns(
10281073
$column_phids = mpull($columns, 'getPHID');
10291074
$column_phids = array_values($column_phids);
10301075

1031-
$this->assertEqual($expect, $column_phids);
1076+
return $column_phids;
10321077
}
10331078

10341079
private function assertTasksInColumn(
@@ -1236,6 +1281,16 @@ private function createProject(
12361281

12371282
$this->applyTransactions($project, $user, $xactions);
12381283

1284+
// Force these values immediately; they are normally updated by the
1285+
// index engine.
1286+
if ($parent) {
1287+
if ($is_milestone) {
1288+
$parent->setHasMilestones(1)->save();
1289+
} else {
1290+
$parent->setHasSubprojects(1)->save();
1291+
}
1292+
}
1293+
12391294
return $project;
12401295
}
12411296

‎src/applications/project/controller/PhabricatorProjectBoardViewController.php

+36-3
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,27 @@ public function handleRequest(AphrontRequest $request) {
9595

9696
$task_query = $search_engine->buildQueryFromSavedQuery($saved);
9797

98+
$select_phids = array($project->getPHID());
99+
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
100+
$descendants = id(new PhabricatorProjectQuery())
101+
->setViewer($viewer)
102+
->withAncestorProjectPHIDs($select_phids)
103+
->execute();
104+
foreach ($descendants as $descendant) {
105+
$select_phids[] = $descendant->getPHID();
106+
}
107+
}
108+
98109
$tasks = $task_query
99110
->withEdgeLogicPHIDs(
100111
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
101-
PhabricatorQueryConstraint::OPERATOR_AND,
102-
array($project->getPHID()))
112+
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
113+
array($select_phids))
103114
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
104115
->setViewer($viewer)
105116
->execute();
106117
$tasks = mpull($tasks, null, 'getPHID');
107118

108-
109119
$board_phid = $project->getPHID();
110120

111121
$layout_engine = id(new PhabricatorBoardLayoutEngine())
@@ -225,6 +235,13 @@ public function handleRequest(AphrontRequest $request) {
225235
}
226236
}
227237

238+
$proxy = $column->getProxy();
239+
if ($proxy && !$proxy->isMilestone()) {
240+
// TODO: For now, don't show subproject columns because we can't
241+
// handle tasks with multiple positions yet.
242+
continue;
243+
}
244+
228245
$task_phids = $layout_engine->getColumnObjectPHIDs(
229246
$board_phid,
230247
$column->getPHID());
@@ -247,6 +264,11 @@ public function handleRequest(AphrontRequest $request) {
247264
$panel->setHeaderIcon($header_icon);
248265
}
249266

267+
$display_class = $column->getDisplayClass();
268+
if ($display_class) {
269+
$panel->addClass($display_class);
270+
}
271+
250272
if ($column->isHidden()) {
251273
$panel->addClass('project-panel-hidden');
252274
}
@@ -582,6 +604,12 @@ private function buildColumnMenu(
582604

583605
$column_items = array();
584606

607+
if ($column->getProxyPHID()) {
608+
$default_phid = $column->getProxyPHID();
609+
} else {
610+
$default_phid = $column->getProjectPHID();
611+
}
612+
585613
$column_items[] = id(new PhabricatorActionView())
586614
->setIcon('fa-plus')
587615
->setName(pht('Create Task...'))
@@ -590,6 +618,7 @@ private function buildColumnMenu(
590618
->setMetadata(
591619
array(
592620
'columnPHID' => $column->getPHID(),
621+
'projectPHID' => $default_phid,
593622
));
594623

595624
$batch_edit_uri = $request->getRequestURI();
@@ -738,6 +767,10 @@ private function buildInitializeContent(PhabricatorProject $project) {
738767
}
739768
}
740769

770+
// TODO: Tailor this UI if the project is already a parent project. We
771+
// should not offer options for creating a parent project workboard, since
772+
// they can't have their own columns.
773+
741774
$new_selector = id(new AphrontFormRadioButtonControl())
742775
->setLabel(pht('Columns'))
743776
->setName('initialize-type')

‎src/applications/project/controller/PhabricatorProjectMoveController.php

+40-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,33 @@ public function handleRequest(AphrontRequest $request) {
139139
->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)
140140
->setNewValue($sub);
141141
}
142-
}
142+
}
143+
144+
$proxy = $column->getProxy();
145+
if ($proxy) {
146+
// We're moving the task into a subproject or milestone column, so add
147+
// the subproject or milestone.
148+
$add_projects = array($proxy->getPHID());
149+
} else if ($project->getHasSubprojects() || $project->getHasMilestones()) {
150+
// We're moving the task into the "Backlog" column on the parent project,
151+
// so add the parent explicitly. This gets rid of any subproject or
152+
// milestone tags.
153+
$add_projects = array($project->getPHID());
154+
} else {
155+
$add_projects = array();
156+
}
157+
158+
if ($add_projects) {
159+
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
160+
161+
$xactions[] = id(new ManiphestTransaction())
162+
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
163+
->setMetadataValue('edge:type', $project_type)
164+
->setNewValue(
165+
array(
166+
'+' => array_fuse($add_projects),
167+
));
168+
}
143169

144170
$editor = id(new ManiphestTransactionEditor())
145171
->setActor($viewer)
@@ -157,6 +183,18 @@ public function handleRequest(AphrontRequest $request) {
157183
->executeOne();
158184
}
159185

186+
// Reload the object so it reflects edits which have been applied.
187+
$object = id(new ManiphestTaskQuery())
188+
->setViewer($viewer)
189+
->withPHIDs(array($object_phid))
190+
->needProjectPHIDs(true)
191+
->requireCapabilities(
192+
array(
193+
PhabricatorPolicyCapability::CAN_VIEW,
194+
PhabricatorPolicyCapability::CAN_EDIT,
195+
))
196+
->executeOne();
197+
160198
$card = id(new ProjectBoardTaskCard())
161199
->setViewer($viewer)
162200
->setTask($object)
@@ -169,6 +207,6 @@ public function handleRequest(AphrontRequest $request) {
169207

170208
return id(new AphrontAjaxResponse())->setContent(
171209
array('task' => $card));
172-
}
210+
}
173211

174212
}

‎src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function handleRequest(AphrontRequest $request) {
3535

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

0 commit comments

Comments
 (0)
Failed to load comments.