1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

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
This commit is contained in:
epriestley 2016-11-02 15:15:09 -07:00
parent e9b861ff15
commit e4c6ae5345
8 changed files with 120 additions and 45 deletions

View file

@ -94,7 +94,7 @@ final class PhabricatorCalendarEventEditEngine
// At least for now, just hide "Invitees" when editing all future events. // At least for now, just hide "Invitees" when editing all future events.
// This may eventually deserve a more nuanced approach. // This may eventually deserve a more nuanced approach.
$hide_invitees = ($this->getSeriesEditMode() == self::MODE_FUTURE); $is_future = ($this->getSeriesEditMode() == self::MODE_FUTURE);
$fields = array( $fields = array(
id(new PhabricatorTextEditField()) id(new PhabricatorTextEditField())
@ -162,7 +162,7 @@ final class PhabricatorCalendarEventEditEngine
->setConduitTypeDescription(pht('New event host.')) ->setConduitTypeDescription(pht('New event host.'))
->setSingleValue($object->getHostPHID()), ->setSingleValue($object->getHostPHID()),
id(new PhabricatorDatasourceEditField()) id(new PhabricatorDatasourceEditField())
->setIsHidden($hide_invitees) ->setIsHidden($is_future)
->setKey('inviteePHIDs') ->setKey('inviteePHIDs')
->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID')) ->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID'))
->setLabel(pht('Invitees')) ->setLabel(pht('Invitees'))
@ -193,8 +193,16 @@ final class PhabricatorCalendarEventEditEngine
->setConduitDescription(pht('Change the event icon.')) ->setConduitDescription(pht('Change the event icon.'))
->setConduitTypeDescription(pht('New event icon.')) ->setConduitTypeDescription(pht('New event icon.'))
->setValue($object->getIcon()), ->setValue($object->getIcon()),
// NOTE: We're being a little sneaky here. This field is hidden and
// always has the value "true", so it makes the event recurring when you
// submit a form which contains the field. Then we put the the field on
// the "recurring" page in the "Make Recurring" dialog to simplify the
// workflow. This is still normal, explicit field from the perspective
// of the API.
id(new PhabricatorBoolEditField()) id(new PhabricatorBoolEditField())
->setIsHidden($object->getIsRecurring()) ->setIsHidden(true)
->setKey('isRecurring') ->setKey('isRecurring')
->setLabel(pht('Recurring')) ->setLabel(pht('Recurring'))
->setOptions(pht('One-Time Event'), pht('Recurring Event')) ->setOptions(pht('One-Time Event'), pht('Recurring Event'))
@ -203,7 +211,7 @@ final class PhabricatorCalendarEventEditEngine
->setDescription(pht('One time or recurring event.')) ->setDescription(pht('One time or recurring event.'))
->setConduitDescription(pht('Make the event recurring.')) ->setConduitDescription(pht('Make the event recurring.'))
->setConduitTypeDescription(pht('Mark the event as a recurring event.')) ->setConduitTypeDescription(pht('Mark the event as a recurring event.'))
->setValue($object->getIsRecurring()), ->setValue(true),
id(new PhabricatorSelectEditField()) id(new PhabricatorSelectEditField())
->setKey('frequency') ->setKey('frequency')
->setLabel(pht('Frequency')) ->setLabel(pht('Frequency'))
@ -295,35 +303,35 @@ final class PhabricatorCalendarEventEditEngine
protected function willApplyTransactions($object, array $xactions) { protected function willApplyTransactions($object, array $xactions) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
$this->rawTransactions = $this->cloneTransactions($xactions);
$is_parent = $object->isParentEvent(); $is_parent = $object->isParentEvent();
$is_child = $object->isChildEvent(); $is_child = $object->isChildEvent();
$is_future = ($this->getSeriesEditMode() === self::MODE_FUTURE); $is_future = ($this->getSeriesEditMode() === self::MODE_FUTURE);
// Figure out which transactions we can apply to the whole series of events.
// Some transactions (like comments) can never be bulk applied.
$inherited_xactions = array();
foreach ($xactions as $xaction) {
$modular_type = $xaction->getModularType();
if (!($modular_type instanceof PhabricatorCalendarEventTransactionType)) {
continue;
}
$inherited_edit = $modular_type->isInheritedEdit();
if ($inherited_edit) {
$inherited_xactions[] = $xaction;
}
}
$this->rawTransactions = $this->cloneTransactions($inherited_xactions);
$must_fork = ($is_child && $is_future) || $must_fork = ($is_child && $is_future) ||
($is_parent && !$is_future); ($is_parent && !$is_future);
// We don't need to fork when editing a parent event if none of the edits
// can transfer to child events. For example, commenting on a parent is
// fine.
if ($is_parent && !$is_future) { if ($is_parent && !$is_future) {
// We don't necessarily need to fork if whatever we're editing is not if (!$inherited_xactions) {
// inherited by children. For example, we can add a comment to the parent
// event without needing to fork. Test all the stuff we're doing and see
// if anything is actually inherited.
$inherited_edit = false;
foreach ($xactions as $xaction) {
$modular_type = $xaction->getTransactionImplementation();
if ($modular_type instanceof PhabricatorCalendarEventTransactionType) {
$inherited_edit = $modular_type->isInheritedEdit();
if ($inherited_edit) {
break;
}
}
}
// Nothing the user is trying to do requires us to fork, so we can just
// apply the changes normally.
if (!$inherited_edit) {
$must_fork = false; $must_fork = false;
} }
} }

View file

@ -935,10 +935,14 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
$datetime->newAbsoluteDateTime()->toDictionary()); $datetime->newAbsoluteDateTime()->toDictionary());
} }
public function setUntilDateTime(PhutilCalendarDateTime $datetime) { public function setUntilDateTime(PhutilCalendarDateTime $datetime = null) {
return $this->setParameter( if ($datetime) {
'untilDateTime', $value = $datetime->newAbsoluteDateTime()->toDictionary();
$datetime->newAbsoluteDateTime()->toDictionary()); } else {
$value = null;
}
return $this->setParameter('untilDateTime', $value);
} }
public function setRecurrenceRule(PhutilCalendarRecurrenceRule $rrule) { public function setRecurrenceRule(PhutilCalendarRecurrenceRule $rrule) {

View file

@ -5,8 +5,17 @@ abstract class PhabricatorCalendarEventDateTransaction
abstract protected function getInvalidDateMessage(); abstract protected function getInvalidDateMessage();
public function isInheritedEdit() {
return false;
}
public function generateNewValue($object, $value) { public function generateNewValue($object, $value) {
$editor = $this->getEditor(); $editor = $this->getEditor();
if ($value->isDisabled()) {
return null;
}
return $value->newPhutilDateTime() return $value->newPhutilDateTime()
->setIsAllDay($editor->getNewIsAllDay()) ->setIsAllDay($editor->getNewIsAllDay())
->newAbsoluteDateTime() ->newAbsoluteDateTime()

View file

@ -13,6 +13,14 @@ final class PhabricatorCalendarEventIconTransaction
$object->setIcon($value); $object->setIcon($value);
} }
public function shouldHide() {
if ($this->isCreateTransaction()) {
return true;
}
return false;
}
public function getTitle() { public function getTitle() {
$old = $this->getIconLabel($this->getOldValue()); $old = $this->getIconLabel($this->getOldValue());
$new = $this->getIconLabel($this->getNewValue()); $new = $this->getIconLabel($this->getNewValue());

View file

@ -13,6 +13,17 @@ final class PhabricatorCalendarEventRecurringTransaction
return (int)$value; return (int)$value;
} }
public function isInheritedEdit() {
return false;
}
public function shouldHide() {
// This event isn't interesting on its own, and is accompanied by an
// "alice set this event to repeat weekly." event in normal circumstances
// anyway.
return true;
}
public function applyInternalEffects($object, $value) { public function applyInternalEffects($object, $value) {
$object->setIsRecurring($value); $object->setIsRecurring($value);
} }

View file

@ -23,25 +23,41 @@ final class PhabricatorCalendarEventUntilDateTransaction
$actor = $this->getActor(); $actor = $this->getActor();
$editor = $this->getEditor(); $editor = $this->getEditor();
if ($value) {
$datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($value); $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($value);
$datetime->setIsAllDay($editor->getNewIsAllDay()); $datetime->setIsAllDay($editor->getNewIsAllDay());
$object->setUntilDateTime($datetime); $object->setUntilDateTime($datetime);
} else {
$object->setUntilDateTime(null);
}
} }
public function getTitle() { public function getTitle() {
if ($this->getNewValue()) {
return pht( return pht(
'%s changed this event to repeat until %s.', '%s changed this event to repeat until %s.',
$this->renderAuthor(), $this->renderAuthor(),
$this->renderNewDate()); $this->renderNewDate());
} else {
return pht(
'%s changed this event to repeat forever.',
$this->renderAuthor());
}
} }
public function getTitleForFeed() { public function getTitleForFeed() {
if ($this->getNewValue()) {
return pht( return pht(
'%s changed %s to repeat until %s.', '%s changed %s to repeat until %s.',
$this->renderAuthor(), $this->renderAuthor(),
$this->renderObject(), $this->renderObject(),
$this->renderNewDate()); $this->renderNewDate());
} else {
return pht(
'%s changed %s to repeat forever.',
$this->renderAuthor(),
$this->renderObject());
}
} }
protected function getInvalidDateMessage() { protected function getInvalidDateMessage() {

View file

@ -1073,12 +1073,23 @@ abstract class PhabricatorApplicationTransaction
break; break;
default: default:
// In developer mode, provide a better hint here about which string
// we're missing.
$developer_mode = 'phabricator.developer-mode';
$is_developer = PhabricatorEnv::getEnvConfig($developer_mode);
if ($is_developer) {
return pht(
'%s edited this object (transaction type "%s").',
$this->renderHandleLink($author_phid),
$this->getTransactionType());
} else {
return pht( return pht(
'%s edited this %s.', '%s edited this %s.',
$this->renderHandleLink($author_phid), $this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName()); $this->getApplicationObjectTypeName());
} }
} }
}
public function getTitleForFeed() { public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID(); $author_phid = $this->getAuthorPHID();
@ -1615,6 +1626,10 @@ abstract class PhabricatorApplicationTransaction
'editable by the transaction author.'); 'editable by the transaction author.');
} }
public function getModularType() {
return null;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */ /* -( PhabricatorDestructibleInterface )----------------------------------- */

View file

@ -7,6 +7,10 @@ abstract class PhabricatorModularTransaction
abstract public function getBaseTransactionClass(); abstract public function getBaseTransactionClass();
public function getModularType() {
return $this->getTransactionImplementation();
}
final protected function getTransactionImplementation() { final protected function getTransactionImplementation() {
if (!$this->implementation) { if (!$this->implementation) {
$this->implementation = $this->newTransactionImplementation(); $this->implementation = $this->newTransactionImplementation();