@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix errant rules for associating projects when dragging tasks within a milestone column

Summary:
Fixes T10912. When you drag tasks within a milestone, we currently apply an overbroad, API-focused rule and add the parent board's project. This logic was added fairly recently, as part of T6027, to improve the behavior of API-originated moves.

Later on, this causes the task to toggle in and out of the parent project on every alternate drag.

This logic is also partially duplicated in the `MoveController`.

- Add test coverage for this interaction.
- Fix the logic so it accounts for subproject / milestone columns correctly.
- Put all of the logic into the TransactionEditor, so the API gets the exact same rules.

Test Plan:
- Added a failing test and made it pass.
- Dragged tasks around within a milestone column:
- Before patch: they got bogus project swaps on every other move.
- After patch: projects didn't change (correct).
- Dragged tasks around between normal and milestone columns.
- Before patch: worked properly.
- After patch: still works properly.

Here's what the bad changes look like, the task is swapping projects with every other move:

{F1255957}

The "every other" is because the logic was trying to do this:

- Add both the parent and milestone project.
- Whichever one exists already gets dropped from the change list because it would have no effect.
- The other one then applies.
- In applying, it forces removal of the first one.
- Then this process repeats in the other direction the next time through.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10912

Differential Revision: https://secure.phabricator.com/D15834

+119 -43
+86 -17
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 868 868 switch ($type) { 869 869 case PhabricatorTransactions::TYPE_COLUMNS: 870 870 try { 871 - $this->buildMoveTransaction($object, $xaction); 872 - 873 - // Implicilty add the task to any boards that we're moving it 874 - // on, since moves on a board the task isn't part of are not 875 - // meaningful. 876 - $board_phids = ipull($xaction->getNewValue(), 'boardPHID'); 877 - if ($board_phids) { 878 - $results[] = id(new ManiphestTransaction()) 879 - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 880 - ->setMetadataValue( 881 - 'edge:type', 882 - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) 883 - ->setIgnoreOnNoEffect(true) 884 - ->setNewValue( 885 - array( 886 - '+' => array_fuse($board_phids), 887 - )); 871 + $more_xactions = $this->buildMoveTransaction($object, $xaction); 872 + foreach ($more_xactions as $more_xaction) { 873 + $results[] = $more_xaction; 888 874 } 889 875 } catch (Exception $ex) { 890 876 $error = new PhabricatorApplicationTransactionValidationError( ··· 1098 1084 1099 1085 $new = array_values($new); 1100 1086 $xaction->setNewValue($new); 1087 + 1088 + 1089 + $more = array(); 1090 + 1091 + // If we're moving the object into a column and it does not already belong 1092 + // in the column, add the appropriate board. For normal columns, this 1093 + // is the board PHID. For proxy columns, it is the proxy PHID, unless the 1094 + // object is already a member of some descendant of the proxy PHID. 1095 + 1096 + // The major case where this can happen is moves via the API, but it also 1097 + // happens when a user drags a task from the "Backlog" to a milestone 1098 + // column. 1099 + 1100 + if ($object_phid) { 1101 + $current_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 1102 + $object_phid, 1103 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 1104 + $current_phids = array_fuse($current_phids); 1105 + } else { 1106 + $current_phids = array(); 1107 + } 1108 + 1109 + $add_boards = array(); 1110 + foreach ($new as $move) { 1111 + $column_phid = $move['columnPHID']; 1112 + $board_phid = $move['boardPHID']; 1113 + $column = $columns[$column_phid]; 1114 + $proxy_phid = $column->getProxyPHID(); 1115 + 1116 + // If this is a normal column, add the board if the object isn't already 1117 + // associated. 1118 + if (!$proxy_phid) { 1119 + if (!isset($current_phids[$board_phid])) { 1120 + $add_boards[] = $board_phid; 1121 + } 1122 + continue; 1123 + } 1124 + 1125 + // If this is a proxy column but the object is already associated with 1126 + // the proxy board, we don't need to do anything. 1127 + if (isset($current_phids[$proxy_phid])) { 1128 + continue; 1129 + } 1130 + 1131 + // If this a proxy column and the object is already associated with some 1132 + // descendant of the proxy board, we also don't need to do anything. 1133 + $descendants = id(new PhabricatorProjectQuery()) 1134 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 1135 + ->withAncestorProjectPHIDs(array($proxy_phid)) 1136 + ->execute(); 1137 + 1138 + $found_descendant = false; 1139 + foreach ($descendants as $descendant) { 1140 + if (isset($current_phids[$descendant->getPHID()])) { 1141 + $found_descendant = true; 1142 + break; 1143 + } 1144 + } 1145 + 1146 + if ($found_descendant) { 1147 + continue; 1148 + } 1149 + 1150 + // Otherwise, we're moving the object to a proxy column which it is not 1151 + // a member of yet, so add an association to the column's proxy board. 1152 + 1153 + $add_boards[] = $proxy_phid; 1154 + } 1155 + 1156 + if ($add_boards) { 1157 + $more[] = id(new ManiphestTransaction()) 1158 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 1159 + ->setMetadataValue( 1160 + 'edge:type', 1161 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) 1162 + ->setIgnoreOnNoEffect(true) 1163 + ->setNewValue( 1164 + array( 1165 + '+' => array_fuse($add_boards), 1166 + )); 1167 + } 1168 + 1169 + return $more; 1101 1170 } 1102 1171 1103 1172 private function applyBoardMove($object, array $move) {
+33
src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
··· 1011 1011 $column->getPHID(), 1012 1012 ); 1013 1013 $this->assertColumns($expect, $user, $board, $task); 1014 + 1015 + 1016 + // Move the task within the "Milestone" column. This should not affect 1017 + // the projects the task is tagged with. See T10912. 1018 + $task_a = $task; 1019 + 1020 + $task_b = $this->newTask($user, array($backlog)); 1021 + $this->moveToColumn($user, $board, $task_b, $backlog, $column); 1022 + 1023 + $a_options = array( 1024 + 'beforePHID' => $task_b->getPHID(), 1025 + ); 1026 + 1027 + $b_options = array( 1028 + 'beforePHID' => $task_a->getPHID(), 1029 + ); 1030 + 1031 + $old_projects = $this->getTaskProjects($task); 1032 + 1033 + // Move the target task to the top. 1034 + $this->moveToColumn($user, $board, $task_a, $column, $column, $a_options); 1035 + $new_projects = $this->getTaskProjects($task_a); 1036 + $this->assertEqual($old_projects, $new_projects); 1037 + 1038 + // Move the other task. 1039 + $this->moveToColumn($user, $board, $task_b, $column, $column, $b_options); 1040 + $new_projects = $this->getTaskProjects($task_a); 1041 + $this->assertEqual($old_projects, $new_projects); 1042 + 1043 + // Move the target task again. 1044 + $this->moveToColumn($user, $board, $task_a, $column, $column, $a_options); 1045 + $new_projects = $this->getTaskProjects($task_a); 1046 + $this->assertEqual($old_projects, $new_projects); 1014 1047 } 1015 1048 1016 1049 public function testColumnExtendedPolicies() {
-26
src/applications/project/controller/PhabricatorProjectMoveController.php
··· 96 96 } 97 97 } 98 98 99 - $proxy = $column->getProxy(); 100 - if ($proxy) { 101 - // We're moving the task into a subproject or milestone column, so add 102 - // the subproject or milestone. 103 - $add_projects = array($proxy->getPHID()); 104 - } else if ($project->getHasSubprojects() || $project->getHasMilestones()) { 105 - // We're moving the task into the "Backlog" column on the parent project, 106 - // so add the parent explicitly. This gets rid of any subproject or 107 - // milestone tags. 108 - $add_projects = array($project->getPHID()); 109 - } else { 110 - $add_projects = array(); 111 - } 112 - 113 - if ($add_projects) { 114 - $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 115 - 116 - $xactions[] = id(new ManiphestTransaction()) 117 - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 118 - ->setMetadataValue('edge:type', $project_type) 119 - ->setNewValue( 120 - array( 121 - '+' => array_fuse($add_projects), 122 - )); 123 - } 124 - 125 99 $editor = id(new ManiphestTransactionEditor()) 126 100 ->setActor($viewer) 127 101 ->setContinueOnMissingFields(true)