@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.

Merge TYPE_PROJECT_COLUMNS and TYPE_COLUMN transactions into a more general TYPE_COLUMNS transaction

Summary:
Ref T6027. We currently have two different transaction types:

- `TYPE_PROJECT_COLUMNS` does most of the work, but has a sort of weird structure and isn't really suitable for API use.
- `TYPE_COLUMN` is this weird, junk transaction which mostly just creates the other transaction.

Merge them into a single higher-level `TYPE_COLUMNS` transaction which works properly and has a sensible structure and comprehensive error checking.

Remaining work here:

- I've removed the old rendering logic, but not yet added new logic. I need to migrate the old transaction types and add new rendering logic.
- Although the internal representation is now //suitable// for use in the API, it isn't properly exposed yet.

Test Plan:
- Created tasks into a column.
- Ran unit tests.
- Moved tasks between columns.
- Will perform additional testing in followups.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T6027

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

+344 -273
+7 -5
src/applications/maniphest/editor/ManiphestEditEngine.php
··· 98 98 id(new PhabricatorHandlesEditField()) 99 99 ->setKey('column') 100 100 ->setLabel(pht('Column')) 101 - ->setDescription(pht('Workboard column to create this task into.')) 102 - ->setConduitDescription(pht('Create into a workboard column.')) 103 - ->setConduitTypeDescription(pht('PHID of workboard column.')) 104 - ->setAliases(array('columnPHID')) 105 - ->setTransactionType(ManiphestTransaction::TYPE_COLUMN) 101 + ->setDescription(pht('Create a task in a workboard column.')) 102 + ->setConduitDescription( 103 + pht('Move a task to one or more workboard columns.')) 104 + ->setConduitTypeDescription( 105 + pht('PHID or PHIDs of workboard columns.')) 106 + ->setAliases(array('columnPHID', 'columns', 'columnPHIDs')) 107 + ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) 106 108 ->setSingleValue(null) 107 109 ->setIsInvisible(true) 108 110 ->setIsReorderable(false)
+317 -202
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 3 3 final class ManiphestTransactionEditor 4 4 extends PhabricatorApplicationTransactionEditor { 5 5 6 + private $moreValidationErrors = array(); 7 + 6 8 public function getEditorApplicationClass() { 7 9 return 'PhabricatorManiphestApplication'; 8 10 } ··· 22 24 $types[] = ManiphestTransaction::TYPE_DESCRIPTION; 23 25 $types[] = ManiphestTransaction::TYPE_OWNER; 24 26 $types[] = ManiphestTransaction::TYPE_SUBPRIORITY; 25 - $types[] = ManiphestTransaction::TYPE_PROJECT_COLUMN; 26 27 $types[] = ManiphestTransaction::TYPE_MERGED_INTO; 27 28 $types[] = ManiphestTransaction::TYPE_MERGED_FROM; 28 29 $types[] = ManiphestTransaction::TYPE_UNBLOCK; 29 30 $types[] = ManiphestTransaction::TYPE_PARENT; 30 - $types[] = ManiphestTransaction::TYPE_COLUMN; 31 31 $types[] = ManiphestTransaction::TYPE_COVER_IMAGE; 32 32 $types[] = ManiphestTransaction::TYPE_POINTS; 33 + $types[] = PhabricatorTransactions::TYPE_COLUMNS; 33 34 $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 34 35 $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 35 36 ··· 63 64 return $object->getDescription(); 64 65 case ManiphestTransaction::TYPE_OWNER: 65 66 return nonempty($object->getOwnerPHID(), null); 66 - case ManiphestTransaction::TYPE_PROJECT_COLUMN: 67 - // These are pre-populated. 68 - return $xaction->getOldValue(); 69 67 case ManiphestTransaction::TYPE_SUBPRIORITY: 70 68 return $object->getSubpriority(); 71 69 case ManiphestTransaction::TYPE_COVER_IMAGE: ··· 80 78 case ManiphestTransaction::TYPE_MERGED_FROM: 81 79 return null; 82 80 case ManiphestTransaction::TYPE_PARENT: 83 - case ManiphestTransaction::TYPE_COLUMN: 81 + case PhabricatorTransactions::TYPE_COLUMNS: 84 82 return null; 85 83 } 86 84 } ··· 98 96 case ManiphestTransaction::TYPE_TITLE: 99 97 case ManiphestTransaction::TYPE_DESCRIPTION: 100 98 case ManiphestTransaction::TYPE_SUBPRIORITY: 101 - case ManiphestTransaction::TYPE_PROJECT_COLUMN: 102 99 case ManiphestTransaction::TYPE_MERGED_INTO: 103 100 case ManiphestTransaction::TYPE_MERGED_FROM: 104 101 case ManiphestTransaction::TYPE_UNBLOCK: 105 102 case ManiphestTransaction::TYPE_COVER_IMAGE: 106 103 return $xaction->getNewValue(); 107 104 case ManiphestTransaction::TYPE_PARENT: 108 - case ManiphestTransaction::TYPE_COLUMN: 105 + case PhabricatorTransactions::TYPE_COLUMNS: 109 106 return $xaction->getNewValue(); 110 107 case ManiphestTransaction::TYPE_POINTS: 111 108 $value = $xaction->getNewValue(); ··· 127 124 $new = $xaction->getNewValue(); 128 125 129 126 switch ($xaction->getTransactionType()) { 130 - case ManiphestTransaction::TYPE_PROJECT_COLUMN: 131 - $new_column_phids = $new['columnPHIDs']; 132 - $old_column_phids = $old['columnPHIDs']; 133 - sort($new_column_phids); 134 - sort($old_column_phids); 135 - return ($old !== $new); 127 + case PhabricatorTransactions::TYPE_COLUMNS: 128 + return (bool)$new; 136 129 } 137 130 138 131 return parent::transactionHasEffect($object, $xaction); ··· 175 168 case ManiphestTransaction::TYPE_SUBPRIORITY: 176 169 $object->setSubpriority($xaction->getNewValue()); 177 170 return; 178 - case ManiphestTransaction::TYPE_PROJECT_COLUMN: 179 - // these do external (edge) updates 180 - return; 181 171 case ManiphestTransaction::TYPE_MERGED_INTO: 182 172 $object->setStatus(ManiphestTaskStatus::getDuplicateStatus()); 183 173 return; ··· 212 202 return; 213 203 case ManiphestTransaction::TYPE_MERGED_FROM: 214 204 case ManiphestTransaction::TYPE_PARENT: 215 - case ManiphestTransaction::TYPE_COLUMN: 205 + case PhabricatorTransactions::TYPE_COLUMNS: 216 206 return; 217 207 } 218 208 } ··· 231 221 ->addEdge($parent_phid, $parent_type, $task_phid) 232 222 ->save(); 233 223 break; 234 - case ManiphestTransaction::TYPE_PROJECT_COLUMN: 235 - $board_phid = idx($xaction->getNewValue(), 'projectPHID'); 236 - if (!$board_phid) { 237 - throw new Exception( 238 - pht( 239 - "Expected '%s' in column transaction.", 240 - 'projectPHID')); 241 - } 242 - 243 - $old_phids = idx($xaction->getOldValue(), 'columnPHIDs', array()); 244 - $new_phids = idx($xaction->getNewValue(), 'columnPHIDs', array()); 245 - if (count($new_phids) !== 1) { 246 - throw new Exception( 247 - pht( 248 - "Expected exactly one '%s' in column transaction.", 249 - 'columnPHIDs')); 250 - } 251 - 252 - $before_phid = idx($xaction->getNewValue(), 'beforePHID'); 253 - $after_phid = idx($xaction->getNewValue(), 'afterPHID'); 254 - 255 - if (!$before_phid && !$after_phid && ($old_phids == $new_phids)) { 256 - // If we are not moving the object between columns and also not 257 - // reordering the position, this is a move on some other order 258 - // (like priority). We can leave the positions untouched and just 259 - // bail, there's no work to be done. 260 - return; 261 - } 262 - 263 - // Otherwise, we're either moving between columns or adjusting the 264 - // object's position in the "natural" ordering, so we do need to update 265 - // some rows. 266 - 267 - $object_phid = $object->getPHID(); 268 - 269 - // We're doing layout with the ominpotent viewer to make sure we don't 270 - // remove positions in columns that exist, but which the actual actor 271 - // can't see. 272 - $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); 273 - 274 - $select_phids = array($board_phid); 275 - 276 - $descendants = id(new PhabricatorProjectQuery()) 277 - ->setViewer($omnipotent_viewer) 278 - ->withAncestorProjectPHIDs($select_phids) 279 - ->execute(); 280 - foreach ($descendants as $descendant) { 281 - $select_phids[] = $descendant->getPHID(); 282 - } 283 - 284 - $board_tasks = id(new ManiphestTaskQuery()) 285 - ->setViewer($omnipotent_viewer) 286 - ->withEdgeLogicPHIDs( 287 - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 288 - PhabricatorQueryConstraint::OPERATOR_ANCESTOR, 289 - array($select_phids)) 290 - ->execute(); 291 - 292 - $board_tasks = mpull($board_tasks, null, 'getPHID'); 293 - $board_tasks[$object_phid] = $object; 294 - 295 - // Make sure tasks are sorted by ID, so we lay out new positions in 296 - // a consistent way. 297 - $board_tasks = msort($board_tasks, 'getID'); 298 - 299 - $object_phids = array_keys($board_tasks); 300 - 301 - $engine = id(new PhabricatorBoardLayoutEngine()) 302 - ->setViewer($omnipotent_viewer) 303 - ->setBoardPHIDs(array($board_phid)) 304 - ->setObjectPHIDs($object_phids) 305 - ->executeLayout(); 306 - 307 - // TODO: This logic needs to be revised if we legitimately support 308 - // multiple column positions. 309 - 310 - // NOTE: When a task is newly created, it's implicitly added to the 311 - // backlog but we don't currently record that in the "$old_phids". Just 312 - // clean it up for now. 313 - $columns = $engine->getObjectColumns($board_phid, $object_phid); 314 - foreach ($columns as $column) { 315 - $engine->queueRemovePosition( 316 - $board_phid, 317 - $column->getPHID(), 318 - $object_phid); 319 - } 320 - 321 - // Remove all existing column positions on the board. 322 - foreach ($old_phids as $column_phid) { 323 - $engine->queueRemovePosition( 324 - $board_phid, 325 - $column_phid, 326 - $object_phid); 327 - } 328 - 329 - // Add new positions. 330 - foreach ($new_phids as $column_phid) { 331 - if ($before_phid) { 332 - $engine->queueAddPositionBefore( 333 - $board_phid, 334 - $column_phid, 335 - $object_phid, 336 - $before_phid); 337 - } else if ($after_phid) { 338 - $engine->queueAddPositionAfter( 339 - $board_phid, 340 - $column_phid, 341 - $object_phid, 342 - $after_phid); 343 - } else { 344 - $engine->queueAddPosition( 345 - $board_phid, 346 - $column_phid, 347 - $object_phid); 348 - } 224 + case PhabricatorTransactions::TYPE_COLUMNS: 225 + foreach ($xaction->getNewValue() as $move) { 226 + $this->applyBoardMove($object, $move); 349 227 } 350 - 351 - $engine->applyPositionUpdates(); 352 - 353 228 break; 354 229 default: 355 230 break; ··· 492 367 493 368 494 369 $board_phids = array(); 495 - $type_column = ManiphestTransaction::TYPE_PROJECT_COLUMN; 370 + $type_columns = PhabricatorTransactions::TYPE_COLUMNS; 496 371 foreach ($xactions as $xaction) { 497 - if ($xaction->getTransactionType() == $type_column) { 498 - $new = $xaction->getNewValue(); 499 - $project_phid = idx($new, 'projectPHID'); 500 - if ($project_phid) { 501 - $board_phids[] = $project_phid; 372 + if ($xaction->getTransactionType() == $type_columns) { 373 + $moves = $xaction->getNewValue(); 374 + foreach ($moves as $move) { 375 + $board_phids[] = $move['boardPHID']; 502 376 } 503 377 } 504 378 } ··· 815 689 last($with_effect)); 816 690 } 817 691 break; 818 - case ManiphestTransaction::TYPE_COLUMN: 819 - $with_effect = array(); 820 - foreach ($xactions as $xaction) { 821 - $column_phid = $xaction->getNewValue(); 822 - if (!$column_phid) { 823 - continue; 824 - } 825 - 826 - $with_effect[] = $xaction; 827 - 828 - $column = $this->loadProjectColumn($column_phid); 829 - if (!$column) { 830 - $errors[] = new PhabricatorApplicationTransactionValidationError( 831 - $type, 832 - pht('Invalid'), 833 - pht( 834 - 'Column PHID "%s" does not identify a visible column.', 835 - $column_phid), 836 - $xaction); 837 - } 838 - } 839 - 840 - if ($with_effect && !$this->getIsNewObject()) { 841 - $errors[] = new PhabricatorApplicationTransactionValidationError( 842 - $type, 843 - pht('Invalid'), 844 - pht( 845 - 'You can only put a task into an initial column during task '. 846 - 'creation.'), 847 - last($with_effect)); 848 - } 849 - break; 850 692 case ManiphestTransaction::TYPE_OWNER: 851 693 foreach ($xactions as $xaction) { 852 694 $old = $xaction->getOldValue(); ··· 938 780 return $errors; 939 781 } 940 782 783 + protected function validateAllTransactions( 784 + PhabricatorLiskDAO $object, 785 + array $xactions) { 786 + 787 + $errors = parent::validateAllTransactions($object, $xactions); 788 + 789 + if ($this->moreValidationErrors) { 790 + $errors = array_merge($errors, $this->moreValidationErrors); 791 + } 792 + 793 + return $errors; 794 + } 795 + 941 796 protected function expandTransactions( 942 797 PhabricatorLiskDAO $object, 943 798 array $xactions) { ··· 1009 864 1010 865 $results = parent::expandTransaction($object, $xaction); 1011 866 1012 - switch ($xaction->getTransactionType()) { 1013 - case ManiphestTransaction::TYPE_COLUMN: 1014 - $column_phid = $xaction->getNewValue(); 1015 - if (!$column_phid) { 1016 - break; 867 + $type = $xaction->getTransactionType(); 868 + switch ($type) { 869 + case PhabricatorTransactions::TYPE_COLUMNS: 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 + )); 888 + } 889 + } catch (Exception $ex) { 890 + $error = new PhabricatorApplicationTransactionValidationError( 891 + $type, 892 + pht('Invalid'), 893 + $ex->getMessage(), 894 + $xaction); 895 + $this->moreValidationErrors[] = $error; 1017 896 } 1018 - 1019 - // When a task is created into a column, we also generate a transaction 1020 - // to actually put it in that column. 1021 - $column = $this->loadProjectColumn($column_phid); 1022 - $results[] = id(new ManiphestTransaction()) 1023 - ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN) 1024 - ->setOldValue( 1025 - array( 1026 - 'projectPHID' => $column->getProjectPHID(), 1027 - 'columnPHIDs' => array(), 1028 - )) 1029 - ->setNewValue( 1030 - array( 1031 - 'projectPHID' => $column->getProjectPHID(), 1032 - 'columnPHIDs' => array($column->getPHID()), 1033 - )); 1034 897 break; 1035 898 case ManiphestTransaction::TYPE_OWNER: 1036 899 // If this is a no-op update, don't expand it. ··· 1055 918 } 1056 919 1057 920 return $results; 1058 - } 1059 - 1060 - private function loadProjectColumn($column_phid) { 1061 - return id(new PhabricatorProjectColumnQuery()) 1062 - ->setViewer($this->getActor()) 1063 - ->withPHIDs(array($column_phid)) 1064 - ->executeOne(); 1065 921 } 1066 922 1067 923 protected function extractFilePHIDsFromCustomTransaction( ··· 1077 933 1078 934 return $phids; 1079 935 } 936 + 937 + private function buildMoveTransaction( 938 + PhabricatorLiskDAO $object, 939 + PhabricatorApplicationTransaction $xaction) { 940 + 941 + $new = $xaction->getNewValue(); 942 + if (!is_array($new)) { 943 + $this->validateColumnPHID($new); 944 + $new = array($new); 945 + } 946 + 947 + $nearby_phids = array(); 948 + foreach ($new as $key => $value) { 949 + if (!is_array($value)) { 950 + $this->validateColumnPHID($value); 951 + $value = array( 952 + 'columnPHID' => $value, 953 + ); 954 + } 955 + 956 + PhutilTypeSpec::checkMap( 957 + $value, 958 + array( 959 + 'columnPHID' => 'string', 960 + 'beforePHID' => 'optional string', 961 + 'afterPHID' => 'optional string', 962 + )); 963 + 964 + $new[$key] = $value; 965 + 966 + if (!empty($value['beforePHID'])) { 967 + $nearby_phids[] = $value['beforePHID']; 968 + } 969 + 970 + if (!empty($value['afterPHID'])) { 971 + $nearby_phids[] = $value['afterPHID']; 972 + } 973 + } 974 + 975 + if ($nearby_phids) { 976 + $nearby_objects = id(new PhabricatorObjectQuery()) 977 + ->setViewer($this->getActor()) 978 + ->withPHIDs($nearby_phids) 979 + ->execute(); 980 + $nearby_objects = mpull($nearby_objects, null, 'getPHID'); 981 + } else { 982 + $nearby_objects = array(); 983 + } 984 + 985 + $column_phids = ipull($new, 'columnPHID'); 986 + if ($column_phids) { 987 + $columns = id(new PhabricatorProjectColumnQuery()) 988 + ->setViewer($this->getActor()) 989 + ->withPHIDs($column_phids) 990 + ->execute(); 991 + $columns = mpull($columns, null, 'getPHID'); 992 + } else { 993 + $columns = array(); 994 + } 995 + 996 + $board_phids = mpull($columns, 'getProjectPHID'); 997 + $object_phid = $object->getPHID(); 998 + 999 + $object_phids = $nearby_phids; 1000 + 1001 + // Note that we may not have an object PHID if we're creating a new 1002 + // object. 1003 + if ($object_phid) { 1004 + $object_phids[] = $object_phid; 1005 + } 1006 + 1007 + if ($object_phids) { 1008 + $layout_engine = id(new PhabricatorBoardLayoutEngine()) 1009 + ->setViewer($this->getActor()) 1010 + ->setBoardPHIDs($board_phids) 1011 + ->setObjectPHIDs($object_phids) 1012 + ->setFetchAllBoards(true) 1013 + ->executeLayout(); 1014 + } 1015 + 1016 + foreach ($new as $key => $spec) { 1017 + $column_phid = $spec['columnPHID']; 1018 + $column = idx($columns, $column_phid); 1019 + if (!$column) { 1020 + throw new Exception( 1021 + pht( 1022 + 'Column move transaction specifies column PHID "%s", but there '. 1023 + 'is no corresponding column with this PHID.', 1024 + $column_phid)); 1025 + } 1026 + 1027 + $board_phid = $column->getProjectPHID(); 1028 + 1029 + $nearby = array(); 1030 + 1031 + if (!empty($spec['beforePHID'])) { 1032 + $nearby['beforePHID'] = $spec['beforePHID']; 1033 + } 1034 + 1035 + if (!empty($spec['afterPHID'])) { 1036 + $nearby['afterPHID'] = $spec['afterPHID']; 1037 + } 1038 + 1039 + if (count($nearby) > 1) { 1040 + throw new Exception( 1041 + pht( 1042 + 'Column move transaction moves object to multiple positions. '. 1043 + 'Specify only "beforePHID" or "afterPHID", not both.')); 1044 + } 1045 + 1046 + foreach ($nearby as $where => $nearby_phid) { 1047 + if (empty($nearby_objects[$nearby_phid])) { 1048 + throw new Exception( 1049 + pht( 1050 + 'Column move transaction specifies object "%s" as "%s", but '. 1051 + 'there is no corresponding object with this PHID.', 1052 + $object_phid, 1053 + $where)); 1054 + } 1055 + 1056 + $nearby_columns = $layout_engine->getObjectColumns( 1057 + $board_phid, 1058 + $nearby_phid); 1059 + $nearby_columns = mpull($nearby_columns, null, 'getPHID'); 1060 + 1061 + if (empty($nearby_columns[$column_phid])) { 1062 + throw new Exception( 1063 + pht( 1064 + 'Column move transaction specifies object "%s" as "%s" in '. 1065 + 'column "%s", but this object is not in that column!', 1066 + $nearby_phid, 1067 + $where, 1068 + $column_phid)); 1069 + } 1070 + } 1071 + 1072 + if ($object_phid) { 1073 + $old_columns = $layout_engine->getObjectColumns( 1074 + $board_phid, 1075 + $object_phid); 1076 + $old_column_phids = mpull($old_columns, 'getPHID'); 1077 + } else { 1078 + $old_column_phids = array(); 1079 + } 1080 + 1081 + $spec += array( 1082 + 'boardPHID' => $board_phid, 1083 + 'fromColumnPHIDs' => $old_column_phids, 1084 + ); 1085 + 1086 + // Check if the object is already in this column, and isn't being moved. 1087 + // We can just drop this column change if it has no effect. 1088 + $from_map = array_fuse($spec['fromColumnPHIDs']); 1089 + $already_here = isset($from_map[$column_phid]); 1090 + $is_reordering = (bool)$nearby; 1091 + 1092 + if ($already_here && !$is_reordering) { 1093 + unset($new[$key]); 1094 + } else { 1095 + $new[$key] = $spec; 1096 + } 1097 + } 1098 + 1099 + $new = array_values($new); 1100 + $xaction->setNewValue($new); 1101 + } 1102 + 1103 + private function applyBoardMove($object, array $move) { 1104 + $board_phid = $move['boardPHID']; 1105 + $column_phid = $move['columnPHID']; 1106 + $before_phid = idx($move, 'beforePHID'); 1107 + $after_phid = idx($move, 'afterPHID'); 1108 + 1109 + $object_phid = $object->getPHID(); 1110 + 1111 + // We're doing layout with the ominpotent viewer to make sure we don't 1112 + // remove positions in columns that exist, but which the actual actor 1113 + // can't see. 1114 + $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); 1115 + 1116 + $select_phids = array($board_phid); 1117 + 1118 + $descendants = id(new PhabricatorProjectQuery()) 1119 + ->setViewer($omnipotent_viewer) 1120 + ->withAncestorProjectPHIDs($select_phids) 1121 + ->execute(); 1122 + foreach ($descendants as $descendant) { 1123 + $select_phids[] = $descendant->getPHID(); 1124 + } 1125 + 1126 + $board_tasks = id(new ManiphestTaskQuery()) 1127 + ->setViewer($omnipotent_viewer) 1128 + ->withEdgeLogicPHIDs( 1129 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 1130 + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, 1131 + array($select_phids)) 1132 + ->execute(); 1133 + 1134 + $board_tasks = mpull($board_tasks, null, 'getPHID'); 1135 + $board_tasks[$object_phid] = $object; 1136 + 1137 + // Make sure tasks are sorted by ID, so we lay out new positions in 1138 + // a consistent way. 1139 + $board_tasks = msort($board_tasks, 'getID'); 1140 + 1141 + $object_phids = array_keys($board_tasks); 1142 + 1143 + $engine = id(new PhabricatorBoardLayoutEngine()) 1144 + ->setViewer($omnipotent_viewer) 1145 + ->setBoardPHIDs(array($board_phid)) 1146 + ->setObjectPHIDs($object_phids) 1147 + ->executeLayout(); 1148 + 1149 + // TODO: This logic needs to be revised when we legitimately support 1150 + // multiple column positions. 1151 + $columns = $engine->getObjectColumns($board_phid, $object_phid); 1152 + foreach ($columns as $column) { 1153 + $engine->queueRemovePosition( 1154 + $board_phid, 1155 + $column->getPHID(), 1156 + $object_phid); 1157 + } 1158 + 1159 + if ($before_phid) { 1160 + $engine->queueAddPositionBefore( 1161 + $board_phid, 1162 + $column_phid, 1163 + $object_phid, 1164 + $before_phid); 1165 + } else if ($after_phid) { 1166 + $engine->queueAddPositionAfter( 1167 + $board_phid, 1168 + $column_phid, 1169 + $object_phid, 1170 + $after_phid); 1171 + } else { 1172 + $engine->queueAddPosition( 1173 + $board_phid, 1174 + $column_phid, 1175 + $object_phid); 1176 + } 1177 + 1178 + $engine->applyPositionUpdates(); 1179 + } 1180 + 1181 + 1182 + private function validateColumnPHID($value) { 1183 + if (phid_get_type($value) == PhabricatorProjectColumnPHIDType::TYPECONST) { 1184 + return; 1185 + } 1186 + 1187 + throw new Exception( 1188 + pht( 1189 + 'When moving objects between columns on a board, columns must '. 1190 + 'be identified by PHIDs. This transaction uses "%s" to identify '. 1191 + 'a column, but that is not a valid column PHID.', 1192 + $value)); 1193 + } 1194 + 1080 1195 1081 1196 1082 1197 }
+3 -41
src/applications/maniphest/storage/ManiphestTransaction.php
··· 10 10 const TYPE_PRIORITY = 'priority'; 11 11 const TYPE_EDGE = 'edge'; 12 12 const TYPE_SUBPRIORITY = 'subpriority'; 13 - const TYPE_PROJECT_COLUMN = 'projectcolumn'; 14 13 const TYPE_MERGED_INTO = 'mergedinto'; 15 14 const TYPE_MERGED_FROM = 'mergedfrom'; 16 15 const TYPE_UNBLOCK = 'unblock'; 17 16 const TYPE_PARENT = 'parent'; 18 - const TYPE_COLUMN = 'column'; 19 17 const TYPE_COVER_IMAGE = 'cover-image'; 20 18 const TYPE_POINTS = 'points'; 21 19 ··· 48 46 49 47 public function shouldGenerateOldValue() { 50 48 switch ($this->getTransactionType()) { 51 - case self::TYPE_PROJECT_COLUMN: 52 49 case self::TYPE_EDGE: 53 50 case self::TYPE_UNBLOCK: 54 51 return false; ··· 85 82 $phids[] = $old; 86 83 } 87 84 break; 88 - case self::TYPE_PROJECT_COLUMN: 89 - $phids[] = $new['projectPHID']; 90 - $phids[] = head($new['columnPHIDs']); 91 - break; 92 85 case self::TYPE_MERGED_INTO: 93 86 $phids[] = $new; 94 87 break; ··· 152 145 break; 153 146 case self::TYPE_SUBPRIORITY: 154 147 case self::TYPE_PARENT: 155 - case self::TYPE_COLUMN: 156 148 return true; 157 - case self::TYPE_PROJECT_COLUMN: 158 - $old_cols = idx($this->getOldValue(), 'columnPHIDs'); 159 - $new_cols = idx($this->getNewValue(), 'columnPHIDs'); 160 - 161 - $old_cols = array_values($old_cols); 162 - $new_cols = array_values($new_cols); 163 - sort($old_cols); 164 - sort($new_cols); 165 - 166 - return ($old_cols === $new_cols); 167 149 case self::TYPE_COVER_IMAGE: 168 150 // At least for now, don't show these. 169 151 return true; ··· 308 290 return pht('Reassigned'); 309 291 } 310 292 311 - case self::TYPE_PROJECT_COLUMN: 293 + case PhabricatorTransactions::TYPE_COLUMNS: 312 294 return pht('Changed Project Column'); 313 295 314 296 case self::TYPE_PRIORITY: ··· 378 360 case self::TYPE_DESCRIPTION: 379 361 return 'fa-pencil'; 380 362 381 - case self::TYPE_PROJECT_COLUMN: 363 + case PhabricatorTransactions::TYPE_COLUMNS: 382 364 return 'fa-columns'; 383 365 384 366 case self::TYPE_MERGED_INTO: ··· 616 598 $this->renderHandleList($removed)); 617 599 } 618 600 619 - case self::TYPE_PROJECT_COLUMN: 620 - $project_phid = $new['projectPHID']; 621 - $column_phid = head($new['columnPHIDs']); 622 - return pht( 623 - '%s moved this task to %s on the %s workboard.', 624 - $this->renderHandleLink($author_phid), 625 - $this->renderHandleLink($column_phid), 626 - $this->renderHandleLink($project_phid)); 627 - break; 628 - 629 601 case self::TYPE_MERGED_INTO: 630 602 return pht( 631 603 '%s closed this task as a duplicate of %s.', ··· 887 859 $this->renderHandleList($removed)); 888 860 } 889 861 890 - case self::TYPE_PROJECT_COLUMN: 891 - $project_phid = $new['projectPHID']; 892 - $column_phid = head($new['columnPHIDs']); 893 - return pht( 894 - '%s moved %s to %s on the %s workboard.', 895 - $this->renderHandleLink($author_phid), 896 - $this->renderHandleLink($object_phid), 897 - $this->renderHandleLink($column_phid), 898 - $this->renderHandleLink($project_phid)); 899 - 900 862 case self::TYPE_MERGED_INTO: 901 863 return pht( 902 864 '%s merged task %s into %s.', ··· 961 923 case self::TYPE_UNBLOCK: 962 924 $tags[] = self::MAILTAG_UNBLOCK; 963 925 break; 964 - case self::TYPE_PROJECT_COLUMN: 926 + case PhabricatorTransactions::TYPE_COLUMNS: 965 927 $tags[] = self::MAILTAG_COLUMN; 966 928 break; 967 929 case PhabricatorTransactions::TYPE_COMMENT:
+6 -11
src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
··· 1072 1072 $options = array(); 1073 1073 } 1074 1074 1075 + $value = array( 1076 + 'columnPHID' => $dst->getPHID(), 1077 + ) + $options; 1078 + 1075 1079 $xactions[] = id(new ManiphestTransaction()) 1076 - ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN) 1077 - ->setOldValue( 1078 - array( 1079 - 'projectPHID' => $board->getPHID(), 1080 - 'columnPHIDs' => array($src->getPHID()), 1081 - )) 1082 - ->setNewValue( 1083 - array( 1084 - 'projectPHID' => $board->getPHID(), 1085 - 'columnPHIDs' => array($dst->getPHID()), 1086 - ) + $options); 1080 + ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) 1081 + ->setNewValue(array($value)); 1087 1082 1088 1083 $editor = id(new ManiphestTransactionEditor()) 1089 1084 ->setActor($viewer)
+10 -14
src/applications/project/controller/PhabricatorProjectMoveController.php
··· 68 68 69 69 $xactions = array(); 70 70 71 + $order_params = array(); 71 72 if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { 72 - $order_params = array( 73 - 'afterPHID' => $after_phid, 74 - 'beforePHID' => $before_phid, 75 - ); 76 - } else { 77 - $order_params = array(); 73 + if ($after_phid) { 74 + $order_params['afterPHID'] = $after_phid; 75 + } else if ($before_phid) { 76 + $order_params['beforePHID'] = $before_phid; 77 + } 78 78 } 79 79 80 80 $xactions[] = id(new ManiphestTransaction()) 81 - ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN) 81 + ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) 82 82 ->setNewValue( 83 83 array( 84 - 'columnPHIDs' => array($column->getPHID()), 85 - 'projectPHID' => $column->getProjectPHID(), 86 - ) + $order_params) 87 - ->setOldValue( 88 - array( 89 - 'columnPHIDs' => $old_column_phids, 90 - 'projectPHID' => $column->getProjectPHID(), 84 + array( 85 + 'columnPHID' => $column->getPHID(), 86 + ) + $order_params, 91 87 )); 92 88 93 89 if ($order == PhabricatorProjectColumn::ORDER_PRIORITY) {
+1
src/applications/transactions/constants/PhabricatorTransactions.php
··· 14 14 const TYPE_INLINESTATE = 'core:inlinestate'; 15 15 const TYPE_SPACE = 'core:space'; 16 16 const TYPE_CREATE = 'core:create'; 17 + const TYPE_COLUMNS = 'core:columns'; 17 18 18 19 const COLOR_RED = 'red'; 19 20 const COLOR_ORANGE = 'orange';