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

Smooth out various transaction/editing behaviors for Calendar

Summary:
Ref T11809.

- Allow users to remove the "Until" date from recurring events.
- When removing "Until", show a sensible string ("...set this event to repeat forever.")
- When users go through the "Make Recurring" workflow, don't require them to explicitly select "Recurring: Recurring" from the dropdown. This intent is clear from clicking "Make Recurring".
- When editing "All Future Events", don't literally apply date changes to them, since that doesn't make sense. We update the template, then reschedule any events which haven't been edited already. I think this is what users probably mean if they make this edit.
- When creating an event with a non-default icon, don't show "alice changed the icon from Default to Party.".
- Hide the "recurring mode" transaction, which had no string ("alice edited this Event.") and was redundant anyway.
- Also, add a little piece of developer text to make hunting these things down easier.

Test Plan: Edited various events, parents, children, made events recur, set until, unset until, viewed transactions, rescheduled parents, rescheduled children.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11809

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

+120 -45
+32 -24
src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
··· 94 94 95 95 // At least for now, just hide "Invitees" when editing all future events. 96 96 // This may eventually deserve a more nuanced approach. 97 - $hide_invitees = ($this->getSeriesEditMode() == self::MODE_FUTURE); 97 + $is_future = ($this->getSeriesEditMode() == self::MODE_FUTURE); 98 98 99 99 $fields = array( 100 100 id(new PhabricatorTextEditField()) ··· 162 162 ->setConduitTypeDescription(pht('New event host.')) 163 163 ->setSingleValue($object->getHostPHID()), 164 164 id(new PhabricatorDatasourceEditField()) 165 - ->setIsHidden($hide_invitees) 165 + ->setIsHidden($is_future) 166 166 ->setKey('inviteePHIDs') 167 167 ->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID')) 168 168 ->setLabel(pht('Invitees')) ··· 193 193 ->setConduitDescription(pht('Change the event icon.')) 194 194 ->setConduitTypeDescription(pht('New event icon.')) 195 195 ->setValue($object->getIcon()), 196 + 197 + // NOTE: We're being a little sneaky here. This field is hidden and 198 + // always has the value "true", so it makes the event recurring when you 199 + // submit a form which contains the field. Then we put the the field on 200 + // the "recurring" page in the "Make Recurring" dialog to simplify the 201 + // workflow. This is still normal, explicit field from the perspective 202 + // of the API. 203 + 196 204 id(new PhabricatorBoolEditField()) 197 - ->setIsHidden($object->getIsRecurring()) 205 + ->setIsHidden(true) 198 206 ->setKey('isRecurring') 199 207 ->setLabel(pht('Recurring')) 200 208 ->setOptions(pht('One-Time Event'), pht('Recurring Event')) ··· 203 211 ->setDescription(pht('One time or recurring event.')) 204 212 ->setConduitDescription(pht('Make the event recurring.')) 205 213 ->setConduitTypeDescription(pht('Mark the event as a recurring event.')) 206 - ->setValue($object->getIsRecurring()), 214 + ->setValue(true), 207 215 id(new PhabricatorSelectEditField()) 208 216 ->setKey('frequency') 209 217 ->setLabel(pht('Frequency')) ··· 295 303 296 304 protected function willApplyTransactions($object, array $xactions) { 297 305 $viewer = $this->getViewer(); 298 - $this->rawTransactions = $this->cloneTransactions($xactions); 299 306 300 307 $is_parent = $object->isParentEvent(); 301 308 $is_child = $object->isChildEvent(); 302 309 $is_future = ($this->getSeriesEditMode() === self::MODE_FUTURE); 303 310 311 + // Figure out which transactions we can apply to the whole series of events. 312 + // Some transactions (like comments) can never be bulk applied. 313 + $inherited_xactions = array(); 314 + foreach ($xactions as $xaction) { 315 + $modular_type = $xaction->getModularType(); 316 + if (!($modular_type instanceof PhabricatorCalendarEventTransactionType)) { 317 + continue; 318 + } 319 + 320 + $inherited_edit = $modular_type->isInheritedEdit(); 321 + if ($inherited_edit) { 322 + $inherited_xactions[] = $xaction; 323 + } 324 + } 325 + $this->rawTransactions = $this->cloneTransactions($inherited_xactions); 326 + 304 327 $must_fork = ($is_child && $is_future) || 305 328 ($is_parent && !$is_future); 306 329 330 + // We don't need to fork when editing a parent event if none of the edits 331 + // can transfer to child events. For example, commenting on a parent is 332 + // fine. 307 333 if ($is_parent && !$is_future) { 308 - // We don't necessarily need to fork if whatever we're editing is not 309 - // inherited by children. For example, we can add a comment to the parent 310 - // event without needing to fork. Test all the stuff we're doing and see 311 - // if anything is actually inherited. 312 - 313 - $inherited_edit = false; 314 - foreach ($xactions as $xaction) { 315 - $modular_type = $xaction->getTransactionImplementation(); 316 - if ($modular_type instanceof PhabricatorCalendarEventTransactionType) { 317 - $inherited_edit = $modular_type->isInheritedEdit(); 318 - if ($inherited_edit) { 319 - break; 320 - } 321 - } 322 - } 323 - 324 - // Nothing the user is trying to do requires us to fork, so we can just 325 - // apply the changes normally. 326 - if (!$inherited_edit) { 334 + if (!$inherited_xactions) { 327 335 $must_fork = false; 328 336 } 329 337 }
+8 -4
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 935 935 $datetime->newAbsoluteDateTime()->toDictionary()); 936 936 } 937 937 938 - public function setUntilDateTime(PhutilCalendarDateTime $datetime) { 939 - return $this->setParameter( 940 - 'untilDateTime', 941 - $datetime->newAbsoluteDateTime()->toDictionary()); 938 + public function setUntilDateTime(PhutilCalendarDateTime $datetime = null) { 939 + if ($datetime) { 940 + $value = $datetime->newAbsoluteDateTime()->toDictionary(); 941 + } else { 942 + $value = null; 943 + } 944 + 945 + return $this->setParameter('untilDateTime', $value); 942 946 } 943 947 944 948 public function setRecurrenceRule(PhutilCalendarRecurrenceRule $rrule) {
+9
src/applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php
··· 5 5 6 6 abstract protected function getInvalidDateMessage(); 7 7 8 + public function isInheritedEdit() { 9 + return false; 10 + } 11 + 8 12 public function generateNewValue($object, $value) { 9 13 $editor = $this->getEditor(); 14 + 15 + if ($value->isDisabled()) { 16 + return null; 17 + } 18 + 10 19 return $value->newPhutilDateTime() 11 20 ->setIsAllDay($editor->getNewIsAllDay()) 12 21 ->newAbsoluteDateTime()
+8
src/applications/calendar/xaction/PhabricatorCalendarEventIconTransaction.php
··· 13 13 $object->setIcon($value); 14 14 } 15 15 16 + public function shouldHide() { 17 + if ($this->isCreateTransaction()) { 18 + return true; 19 + } 20 + 21 + return false; 22 + } 23 + 16 24 public function getTitle() { 17 25 $old = $this->getIconLabel($this->getOldValue()); 18 26 $new = $this->getIconLabel($this->getNewValue());
+11
src/applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php
··· 13 13 return (int)$value; 14 14 } 15 15 16 + public function isInheritedEdit() { 17 + return false; 18 + } 19 + 20 + public function shouldHide() { 21 + // This event isn't interesting on its own, and is accompanied by an 22 + // "alice set this event to repeat weekly." event in normal circumstances 23 + // anyway. 24 + return true; 25 + } 26 + 16 27 public function applyInternalEffects($object, $value) { 17 28 $object->setIsRecurring($value); 18 29 }
+29 -13
src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php
··· 23 23 $actor = $this->getActor(); 24 24 $editor = $this->getEditor(); 25 25 26 - $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($value); 27 - $datetime->setIsAllDay($editor->getNewIsAllDay()); 28 - 29 - $object->setUntilDateTime($datetime); 26 + if ($value) { 27 + $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($value); 28 + $datetime->setIsAllDay($editor->getNewIsAllDay()); 29 + $object->setUntilDateTime($datetime); 30 + } else { 31 + $object->setUntilDateTime(null); 32 + } 30 33 } 31 34 32 35 public function getTitle() { 33 - return pht( 34 - '%s changed this event to repeat until %s.', 35 - $this->renderAuthor(), 36 - $this->renderNewDate()); 36 + if ($this->getNewValue()) { 37 + return pht( 38 + '%s changed this event to repeat until %s.', 39 + $this->renderAuthor(), 40 + $this->renderNewDate()); 41 + } else { 42 + return pht( 43 + '%s changed this event to repeat forever.', 44 + $this->renderAuthor()); 45 + } 37 46 } 38 47 39 48 public function getTitleForFeed() { 40 - return pht( 41 - '%s changed %s to repeat until %s.', 42 - $this->renderAuthor(), 43 - $this->renderObject(), 44 - $this->renderNewDate()); 49 + if ($this->getNewValue()) { 50 + return pht( 51 + '%s changed %s to repeat until %s.', 52 + $this->renderAuthor(), 53 + $this->renderObject(), 54 + $this->renderNewDate()); 55 + } else { 56 + return pht( 57 + '%s changed %s to repeat forever.', 58 + $this->renderAuthor(), 59 + $this->renderObject()); 60 + } 45 61 } 46 62 47 63 protected function getInvalidDateMessage() {
+19 -4
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 1073 1073 break; 1074 1074 1075 1075 default: 1076 - return pht( 1077 - '%s edited this %s.', 1078 - $this->renderHandleLink($author_phid), 1079 - $this->getApplicationObjectTypeName()); 1076 + // In developer mode, provide a better hint here about which string 1077 + // we're missing. 1078 + $developer_mode = 'phabricator.developer-mode'; 1079 + $is_developer = PhabricatorEnv::getEnvConfig($developer_mode); 1080 + if ($is_developer) { 1081 + return pht( 1082 + '%s edited this object (transaction type "%s").', 1083 + $this->renderHandleLink($author_phid), 1084 + $this->getTransactionType()); 1085 + } else { 1086 + return pht( 1087 + '%s edited this %s.', 1088 + $this->renderHandleLink($author_phid), 1089 + $this->getApplicationObjectTypeName()); 1090 + } 1080 1091 } 1081 1092 } 1082 1093 ··· 1613 1624 'Transactions are visible to users that can see the object which was '. 1614 1625 'acted upon. Some transactions - in particular, comments - are '. 1615 1626 'editable by the transaction author.'); 1627 + } 1628 + 1629 + public function getModularType() { 1630 + return null; 1616 1631 } 1617 1632 1618 1633
+4
src/applications/transactions/storage/PhabricatorModularTransaction.php
··· 7 7 8 8 abstract public function getBaseTransactionClass(); 9 9 10 + public function getModularType() { 11 + return $this->getTransactionImplementation(); 12 + } 13 + 10 14 final protected function getTransactionImplementation() { 11 15 if (!$this->implementation) { 12 16 $this->implementation = $this->newTransactionImplementation();