Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13979454
D16802.id40468.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D16802.id40468.diff
View Options
diff --git a/resources/sql/autopatches/20161104.calendar.01.availability.sql b/resources/sql/autopatches/20161104.calendar.01.availability.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20161104.calendar.01.availability.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_calendar.calendar_eventinvitee
+ ADD availability VARCHAR(64) NOT NULL;
diff --git a/resources/sql/autopatches/20161104.calendar.02.availdefault.sql b/resources/sql/autopatches/20161104.calendar.02.availdefault.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20161104.calendar.02.availdefault.sql
@@ -0,0 +1,3 @@
+UPDATE {$NAMESPACE}_calendar.calendar_eventinvitee
+ SET availability = 'default'
+ WHERE availability = '';
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2035,6 +2035,7 @@
'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
'PhabricatorCalendarEventAcceptTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAcceptTransaction.php',
'PhabricatorCalendarEventAllDayTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php',
+ 'PhabricatorCalendarEventAvailabilityController' => 'applications/calendar/controller/PhabricatorCalendarEventAvailabilityController.php',
'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php',
'PhabricatorCalendarEventCancelTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventCancelTransaction.php',
'PhabricatorCalendarEventDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventDateTransaction.php',
@@ -6881,6 +6882,7 @@
),
'PhabricatorCalendarEventAcceptTransaction' => 'PhabricatorCalendarEventReplyTransaction',
'PhabricatorCalendarEventAllDayTransaction' => 'PhabricatorCalendarEventTransactionType',
+ 'PhabricatorCalendarEventAvailabilityController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
'PhabricatorCalendarEventCancelTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventDateTransaction' => 'PhabricatorCalendarEventTransactionType',
diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php
--- a/src/applications/calendar/application/PhabricatorCalendarApplication.php
+++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php
@@ -61,6 +61,8 @@
=> 'PhabricatorCalendarEventJoinController',
'export/(?P<id>[1-9]\d*)/(?P<filename>[^/]*)'
=> 'PhabricatorCalendarEventExportController',
+ 'availability/(?P<id>[1-9]\d*)/(?P<availability>[^/]+)/'
+ => 'PhabricatorCalendarEventAvailabilityController',
),
'export/' => array(
$this->getQueryRoutePattern()
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventAvailabilityController.php b/src/applications/calendar/controller/PhabricatorCalendarEventAvailabilityController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventAvailabilityController.php
@@ -0,0 +1,56 @@
+<?php
+
+final class PhabricatorCalendarEventAvailabilityController
+ extends PhabricatorCalendarController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+ $id = $request->getURIData('id');
+
+ $event = id(new PhabricatorCalendarEventQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if (!$event) {
+ return new Aphront404Response();
+ }
+
+ $response = $this->newImportedEventResponse($event);
+ if ($response) {
+ return $response;
+ }
+
+ $cancel_uri = $event->getURI();
+
+ if (!$event->getIsUserAttending($viewer->getPHID())) {
+ return $this->newDialog()
+ ->setTitle(pht('Not Attending Event'))
+ ->appendParagraph(
+ pht(
+ 'You can not change your display availability for events you '.
+ 'are not attending.'))
+ ->addCancelButton($cancel_uri);
+ }
+
+ // TODO: This endpoint currently only works via AJAX. It would be vaguely
+ // nice to provide a plain HTML version of the workflow where we return
+ // a dialog with a vanilla <select /> in it for cases where all the JS
+ // breaks.
+ $request->validateCSRF();
+
+ $invitee = $event->getInviteeForPHID($viewer->getPHID());
+
+ $map = PhabricatorCalendarEventInvitee::getAvailabilityMap();
+ $new_availability = $request->getURIData('availability');
+ if (isset($map[$new_availability])) {
+ $invitee
+ ->setAvailability($new_availability)
+ ->save();
+
+ // Invalidate the availability cache.
+ $viewer->writeAvailabilityCache(array(), null);
+ }
+
+ return id(new AphrontRedirectResponse())->setURI($cancel_uri);
+ }
+}
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
--- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
@@ -132,6 +132,43 @@
$header->addActionLink($action);
}
+ $options = PhabricatorCalendarEventInvitee::getAvailabilityMap();
+
+ $is_attending = $event->getIsUserAttending($viewer->getPHID());
+ if ($is_attending) {
+ $invitee = $event->getInviteeForPHID($viewer->getPHID());
+
+ $selected = $invitee->getDisplayAvailability($event);
+ if (!$selected) {
+ $selected = PhabricatorCalendarEventInvitee::AVAILABILITY_AVAILABLE;
+ }
+
+ $selected_option = idx($options, $selected);
+
+ $availability_select = id(new PHUIButtonView())
+ ->setTag('a')
+ ->setIcon('fa-circle '.$selected_option['color'])
+ ->setText(pht('Availability: %s', $selected_option['name']));
+
+ $dropdown = id(new PhabricatorActionListView())
+ ->setUser($viewer);
+
+ foreach ($options as $key => $option) {
+ $uri = "event/availability/{$id}/{$key}/";
+ $uri = $this->getApplicationURI($uri);
+
+ $dropdown->addAction(
+ id(new PhabricatorActionView())
+ ->setName($option['name'])
+ ->setIcon('fa-circle '.$option['color'])
+ ->setHref($uri)
+ ->setWorkflow(true));
+ }
+
+ $availability_select->setDropdownMenu($dropdown);
+ $header->addActionLink($availability_select);
+ }
+
return $header;
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -448,6 +448,12 @@
return $this->assertAttached($this->invitees);
}
+ public function getInviteeForPHID($phid) {
+ $invitees = $this->getInvitees();
+ $invitees = mpull($invitees, null, 'getInviteePHID');
+ return idx($invitees, $phid);
+ }
+
public static function getFrequencyMap() {
return array(
PhutilCalendarRecurrenceRule::FREQUENCY_DAILY => array(
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventInvitee.php b/src/applications/calendar/storage/PhabricatorCalendarEventInvitee.php
--- a/src/applications/calendar/storage/PhabricatorCalendarEventInvitee.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEventInvitee.php
@@ -7,12 +7,18 @@
protected $inviteePHID;
protected $inviterPHID;
protected $status;
+ protected $availability = self::AVAILABILITY_DEFAULT;
const STATUS_INVITED = 'invited';
const STATUS_ATTENDING = 'attending';
const STATUS_DECLINED = 'declined';
const STATUS_UNINVITED = 'uninvited';
+ const AVAILABILITY_DEFAULT = 'default';
+ const AVAILABILITY_AVAILABLE = 'available';
+ const AVAILABILITY_BUSY = 'busy';
+ const AVAILABILITY_AWAY = 'away';
+
public static function initializeNewCalendarEventInvitee(
PhabricatorUser $actor, $event) {
return id(new PhabricatorCalendarEventInvitee())
@@ -25,6 +31,7 @@
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'status' => 'text64',
+ 'availability' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_event' => array(
@@ -50,6 +57,50 @@
}
}
+ public function getDisplayAvailability(PhabricatorCalendarEvent $event) {
+ switch ($this->getAvailability()) {
+ case self::AVAILABILITY_DEFAULT:
+ case self::AVAILABILITY_BUSY:
+ return self::AVAILABILITY_BUSY;
+ case self::AVAILABILITY_AWAY:
+ return self::AVAILABILITY_AWAY;
+ default:
+ return null;
+ }
+ }
+
+ public static function getAvailabilityMap() {
+ return array(
+ self::AVAILABILITY_AVAILABLE => array(
+ 'color' => 'green',
+ 'name' => pht('Available'),
+ ),
+ self::AVAILABILITY_BUSY => array(
+ 'color' => 'yellow',
+ 'name' => pht('Busy'),
+ ),
+ self::AVAILABILITY_AWAY => array(
+ 'color' => 'red',
+ 'name' => pht('Away'),
+ ),
+ );
+ }
+
+ public static function getAvailabilitySpec($const) {
+ return idx(self::getAvailabilityMap(), $const, array());
+ }
+
+ public static function getAvailabilityName($const) {
+ $spec = self::getAvailabilitySpec($const);
+ return idx($spec, 'name', $const);
+ }
+
+ public static function getAvailabilityColor($const) {
+ $spec = self::getAvailabilitySpec($const);
+ return idx($spec, 'color', 'indigo');
+ }
+
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
diff --git a/src/applications/calendar/view/PHUIUserAvailabilityView.php b/src/applications/calendar/view/PHUIUserAvailabilityView.php
--- a/src/applications/calendar/view/PHUIUserAvailabilityView.php
+++ b/src/applications/calendar/view/PHUIUserAvailabilityView.php
@@ -23,16 +23,57 @@
return pht('Available');
}
+ $const = $user->getDisplayAvailability();
+ $name = PhabricatorCalendarEventInvitee::getAvailabilityName($const);
+ $color = PhabricatorCalendarEventInvitee::getAvailabilityColor($const);
+
$away_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
- ->setShade(PHUITagView::COLOR_RED)
- ->setName(pht('Away'))
- ->setDotColor(PHUITagView::COLOR_RED);
+ ->setShade($color)
+ ->setName($name)
+ ->setDotColor($color);
$now = PhabricatorTime::getNow();
- $description = pht(
- 'Away until %s',
- $viewer->formatShortDateTime($until, $now));
+
+ // Try to load the event handle. If it's invalid or the user can't see it,
+ // we'll just render a generic message.
+ $object_phid = $user->getAvailabilityEventPHID();
+ $handle = null;
+ if ($object_phid) {
+ $handles = $viewer->loadHandles(array($object_phid));
+ $handle = $handles[$object_phid];
+ if (!$handle->isComplete() || $handle->getPolicyFiltered()) {
+ $handle = null;
+ }
+ }
+
+ switch ($const) {
+ case PhabricatorCalendarEventInvitee::AVAILABILITY_AWAY:
+ if ($handle) {
+ $description = pht(
+ 'Away at %s until %s.',
+ $handle->renderLink(),
+ $viewer->formatShortDateTime($until, $now));
+ } else {
+ $description = pht(
+ 'Away until %s.',
+ $viewer->formatShortDateTime($until, $now));
+ }
+ break;
+ case PhabricatorCalendarEventInvitee::AVAILABILITY_BUSY:
+ default:
+ if ($handle) {
+ $description = pht(
+ 'Busy at %s until %s.',
+ $handle->renderLink(),
+ $viewer->formatShortDateTime($until, $now));
+ } else {
+ $description = pht(
+ 'Busy until %s.',
+ $viewer->formatShortDateTime($until, $now));
+ }
+ break;
+ }
return array(
$away_tag,
diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php
--- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php
+++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php
@@ -66,7 +66,12 @@
} else {
$until = $user->getAwayUntil();
if ($until) {
- $availability = PhabricatorObjectHandle::AVAILABILITY_NONE;
+ $away = PhabricatorCalendarEventInvitee::AVAILABILITY_AWAY;
+ if ($user->getDisplayAvailability() == $away) {
+ $availability = PhabricatorObjectHandle::AVAILABILITY_NONE;
+ } else {
+ $availability = PhabricatorObjectHandle::AVAILABILITY_PARTIAL;
+ }
}
}
diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php
--- a/src/applications/people/query/PhabricatorPeopleQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleQuery.php
@@ -395,18 +395,28 @@
// Group all the events by invited user. Only examine events that users
// are actually attending.
$map = array();
+ $invitee_map = array();
foreach ($events as $event) {
foreach ($event->getInvitees() as $invitee) {
if (!$invitee->isAttending()) {
continue;
}
+ // If the user is set to "Available" for this event, don't consider it
+ // when computin their away status.
+ if (!$invitee->getDisplayAvailability($event)) {
+ continue;
+ }
+
$invitee_phid = $invitee->getInviteePHID();
if (!isset($rebuild[$invitee_phid])) {
continue;
}
$map[$invitee_phid][] = $event;
+
+ $event_phid = $event->getPHID();
+ $invitee_map[$invitee_phid][$event_phid] = $invitee;
}
}
@@ -426,6 +436,7 @@
}
$cursor = $min_range;
+ $next_event = null;
if ($events) {
// Find the next time when the user has no meetings. If we move forward
// because of an event, we check again for events after that one ends.
@@ -435,6 +446,9 @@
$to = $event->getEndDateTimeEpoch();
if (($from <= $cursor) && ($to > $cursor)) {
$cursor = $to;
+ if (!$next_event) {
+ $next_event = $event;
+ }
continue 2;
}
}
@@ -443,13 +457,29 @@
}
if ($cursor > $min_range) {
+ $invitee = $invitee_map[$phid][$next_event->getPHID()];
+ $availability_type = $invitee->getDisplayAvailability($next_event);
$availability = array(
'until' => $cursor,
+ 'eventPHID' => $event->getPHID(),
+ 'availability' => $availability_type,
);
- $availability_ttl = $cursor;
+
+ // We only cache this availability until the end of the current event,
+ // since the event PHID (and possibly the availability type) are only
+ // valid for that long.
+
+ // NOTE: This doesn't handle overlapping events with the greatest
+ // possible care. In theory, if you're attenting multiple events
+ // simultaneously we should accommodate that. However, it's complex
+ // to compute, rare, and probably not confusing most of the time.
+
+ $availability_ttl = $next_event->getStartDateTimeEpochForCache();
} else {
$availability = array(
'until' => null,
+ 'eventPHID' => null,
+ 'availability' => null,
);
$availability_ttl = $max_range;
}
diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php
--- a/src/applications/people/storage/PhabricatorUser.php
+++ b/src/applications/people/storage/PhabricatorUser.php
@@ -960,6 +960,32 @@
}
+ public function getDisplayAvailability() {
+ $availability = $this->availability;
+
+ $this->assertAttached($availability);
+ if (!$availability) {
+ return null;
+ }
+
+ $busy = PhabricatorCalendarEventInvitee::AVAILABILITY_BUSY;
+
+ return idx($availability, 'availability', $busy);
+ }
+
+
+ public function getAvailabilityEventPHID() {
+ $availability = $this->availability;
+
+ $this->assertAttached($availability);
+ if (!$availability) {
+ return null;
+ }
+
+ return idx($availability, 'eventPHID');
+ }
+
+
/**
* Get cached availability, if present.
*
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Oct 20, 4:39 AM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6728113
Default Alt Text
D16802.id40468.diff (16 KB)
Attached To
Mode
D16802: Allow users to mark themselves as "Available", "Busy" or "Away" while attending an event
Attached
Detach File
Event Timeline
Log In to Comment