Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -1267,6 +1267,7 @@ 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', 'PhabricatorCalendarEventOverlapException' => 'applications/calendar/exception/PhabricatorCalendarEventOverlapException.php', 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', + 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php', @@ -3907,9 +3908,14 @@ 'PhabricatorCalendarEventDeleteController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventInvalidEpochException' => 'Exception', - 'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventListController' => + array( + 0 => 'PhabricatorCalendarController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), 'PhabricatorCalendarEventOverlapException' => 'Exception', 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCampfireProtocolAdapter' => 'PhabricatorBotBaseStreamingProtocolAdapter', Index: src/applications/calendar/application/PhabricatorApplicationCalendar.php =================================================================== --- src/applications/calendar/application/PhabricatorApplicationCalendar.php +++ src/applications/calendar/application/PhabricatorApplicationCalendar.php @@ -37,7 +37,8 @@ '/calendar/' => array( '' => 'PhabricatorCalendarBrowseController', 'event/' => array( - '' => 'PhabricatorCalendarEventListController', + '(?:query/(?P[^/]+)/)?' => + 'PhabricatorCalendarEventListController', 'create/' => 'PhabricatorCalendarEventEditController', 'edit/(?P[1-9]\d*)/' => Index: src/applications/calendar/controller/PhabricatorCalendarEventListController.php =================================================================== --- src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -1,62 +1,66 @@ getRequest()->getUser(); - $this->phid = idx($data, 'phid', $user->getPHID()); - $this->loadHandles(array($this->phid)); + $this->queryKey = idx($data, 'queryKey'); } public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - $handle = $this->getHandle($this->phid); + $request = $this->getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new PhabricatorCalendarEventSearchEngine()) + ->setNavigation($this->buildSideNav()); + return $this->delegateToController($controller); + } + + public function buildSideNav() { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - $statuses = id(new PhabricatorCalendarEventQuery()) + id(new PhabricatorCalendarEventSearchEngine()) ->setViewer($user) - ->withInvitedPHIDs(array($this->phid)) - ->withDateRange(time(), strtotime('2037-01-01 12:00:00')) - ->execute(); - - $nav = $this->buildSideNavView(); - $nav->selectFilter($this->getFilter()); - - $page_title = $this->getPageTitle(); - - $status_list = $this->buildStatusList($statuses); - $status_list->setNoDataString($this->getNoDataString()); - - $nav->appendChild( - array( - id(new PHUIHeaderView())->setHeader($page_title), - $status_list, - )); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $page_title, - 'device' => true - )); + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; } - private function buildStatusList(array $statuses) { - assert_instances_of($statuses, 'PhabricatorCalendarEvent'); + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); - $user = $this->getRequest()->getUser(); + $crumbs->addAction( + id(new PHUIListItemView()) + ->setIcon('create') + ->setName(pht('Create Event')) + ->setHref($this->getApplicationURI().'create/')); + + return $crumbs; + } + + public function renderResultsList( + array $events, + PhabricatorSavedQuery $query) { + assert_instances_of($events, 'PhabricatorCalendarEvent'); + + $viewer = $this->getRequest()->getUser(); $list = new PHUIObjectItemListView(); - foreach ($statuses as $status) { - if ($status->getUserPHID() == $user->getPHID()) { - $href = $this->getApplicationURI('/event/edit/'.$status->getID().'/'); + foreach ($events as $event) { + if ($event->getUserPHID() == $viewer->getPHID()) { + $href = $this->getApplicationURI('/event/edit/'.$event->getID().'/'); } else { - $from = $status->getDateFrom(); - $month = phabricator_format_local_time($from, $user, 'm'); - $year = phabricator_format_local_time($from, $user, 'Y'); + $from = $event->getDateFrom(); + $month = phabricator_format_local_time($from, $viewer, 'm'); + $year = phabricator_format_local_time($from, $viewer, 'Y'); $uri = new PhutilURI($this->getApplicationURI()); $uri->setQueryParams( array( @@ -65,20 +69,20 @@ )); $href = (string) $uri; } - $from = phabricator_datetime($status->getDateFrom(), $user); - $to = phabricator_datetime($status->getDateTo(), $user); + $from = phabricator_datetime($event->getDateFrom(), $viewer); + $to = phabricator_datetime($event->getDateTo(), $viewer); - $color = ($status->getStatus() == PhabricatorCalendarEvent::STATUS_AWAY) + $color = ($event->getStatus() == PhabricatorCalendarEvent::STATUS_AWAY) ? 'red' : 'yellow'; $item = id(new PHUIObjectItemView()) - ->setHeader($status->getTerseSummary($user)) + ->setHeader($event->getTerseSummary($viewer)) ->setHref($href) ->setBarColor($color) ->addAttribute(pht('From %s to %s', $from, $to)) ->addAttribute( - phutil_utf8_shorten($status->getDescription(), 64)); + phutil_utf8_shorten($event->getDescription(), 64)); $list->addItem($item); } @@ -86,38 +90,4 @@ return $list; } - private function getNoDataString() { - if ($this->isUserRequest()) { - $no_data = - pht('You do not have any upcoming status events.'); - } else { - $no_data = - pht('%s does not have any upcoming status events.', - $this->getHandle($this->phid)->getName()); - } - return $no_data; - } - - private function getFilter() { - $filter = 'event/'; - - return $filter; - } - - private function getPageTitle() { - if ($this->isUserRequest()) { - $page_title = pht('Upcoming Statuses'); - } else { - $page_title = pht( - 'Upcoming Statuses for %s', - $this->getHandle($this->phid)->getName()); - } - return $page_title; - } - - private function isUserRequest() { - $user = $this->getRequest()->getUser(); - return $this->phid == $user->getPHID(); - } - } Index: src/applications/calendar/query/PhabricatorCalendarEventQuery.php =================================================================== --- src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -7,6 +7,7 @@ private $rangeBegin; private $rangeEnd; private $invitedPHIDs; + private $creatorPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -24,6 +25,11 @@ return $this; } + public function withCreatorPHIDs(array $phids) { + $this->creatorPHIDs = $phids; + return $this; + } + protected function loadPage() { $table = new PhabricatorCalendarEvent(); $conn_r = $table->establishConnection('r'); @@ -49,14 +55,23 @@ $this->ids); } - if ($this->rangeBegin || $this->rangeEnd) { + if ($this->rangeBegin) { + $where[] = qsprintf( + $conn_r, + 'dateTo >= %d', + $this->rangeBegin); + } + + if ($this->rangeEnd) { $where[] = qsprintf( $conn_r, - 'dateTo >= %d AND dateFrom <= %d', - $this->rangeBegin, + 'dateFrom <= %d', $this->rangeEnd); } + // TODO: Currently, the creator is always the only invitee, but you can + // query them separately since this won't always be true. + if ($this->invitedPHIDs) { $where[] = qsprintf( $conn_r, @@ -64,6 +79,13 @@ $this->invitedPHIDs); } + if ($this->creatorPHIDs) { + $where[] = qsprintf( + $conn_r, + 'userPHID IN (%Ls)', + $this->creatorPHIDs); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); Index: src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php =================================================================== --- /dev/null +++ src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -0,0 +1,163 @@ +setParameter( + 'rangeStart', + $this->readDateFromRequest($request, 'rangeStart')); + + $saved->setParameter( + 'rangeEnd', + $this->readDateFromRequest($request, 'rangeEnd')); + + $saved->setParameter( + 'upcoming', + $this->readBoolFromRequest($request, 'upcoming')); + + $saved->setParameter( + 'invitedPHIDs', + $this->readUsersFromRequest($request, 'invited')); + + $saved->setParameter( + 'creatorPHIDs', + $this->readUsersFromRequest($request, 'creators')); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = id(new PhabricatorCalendarEventQuery()); + + $min_range = null; + $max_range = null; + + if ($saved->getParameter('rangeStart')) { + $min_range = $saved->getParameter('rangeStart'); + } + + if ($saved->getParameter('rangeEnd')) { + $max_range = $saved->getParameter('rangeEnd'); + } + + if ($saved->getParameter('upcoming')) { + if ($min_range) { + $min_range = max(time(), $min_range); + } else { + $min_range = time(); + } + } + + if ($min_range || $max_range) { + $query->withDateRange($min_range, $max_range); + } + + $invited_phids = $saved->getParameter('invitedPHIDs'); + if ($invited_phids) { + $query->withInvitedPHIDs($invited_phids); + } + + $creator_phids = $saved->getParameter('creatorPHIDs'); + if ($creator_phids) { + $query->withCreatorPHIDs($creator_phids); + } + + return $query; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved) { + + $range_start = $saved->getParameter('rangeStart'); + $range_end = $saved->getParameter('rangeEnd'); + $upcoming = $saved->getParameter('upcoming'); + + $invited_phids = $saved->getParameter('invitedPHIDs', array()); + $creator_phids = $saved->getParameter('creatorPHIDs', array()); + + $all_phids = array_merge( + $invited_phids, + $creator_phids); + + if ($all_phids) { + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->requireViewer()) + ->withPHIDs($all_phids) + ->execute(); + } else { + $handles = array(); + } + + $invited_handles = array_select_keys($handles, $invited_phids); + $creator_handles = array_select_keys($handles, $creator_phids); + + $form + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/accounts/') + ->setName('creators') + ->setLabel(pht('Created By')) + ->setValue($creator_handles)) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/accounts/') + ->setName('invited') + ->setLabel(pht('Invited')) + ->setValue($invited_handles)) + ->appendChild( + id(new AphrontFormDateControl()) + ->setLabel(pht('Occurs After')) + ->setUser($this->requireViewer()) + ->setName('rangeStart') + ->setAllowNull(true) + ->setValue($range_start)) + ->appendChild( + id(new AphrontFormDateControl()) + ->setLabel(pht('Occurs Before')) + ->setUser($this->requireViewer()) + ->setName('rangeEnd') + ->setAllowNull(true) + ->setValue($range_end)) + ->appendChild( + id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'upcoming', + 1, + pht('Show only upcoming events.'), + $upcoming)); + + } + + protected function getURI($path) { + return '/calendar/event/'.$path; + } + + public function getBuiltinQueryNames() { + $names = array( + 'upcoming' => pht('Upcoming Events'), + 'all' => pht('All Events'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'upcoming': + return $query->setParameter('upcoming', true); + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + +} Index: src/applications/search/engine/PhabricatorApplicationSearchEngine.php =================================================================== --- src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -368,6 +368,17 @@ return $list; } + protected function readDateFromRequest( + AphrontRequest $request, + $key) { + + return id(new AphrontFormDateControl()) + ->setUser($this->requireViewer()) + ->setName($key) + ->setAllowNull(true) + ->readValueFromRequest($request); + } + protected function readBoolFromRequest( AphrontRequest $request, $key) { Index: src/view/form/control/AphrontFormDateControl.php =================================================================== --- src/view/form/control/AphrontFormDateControl.php +++ src/view/form/control/AphrontFormDateControl.php @@ -87,7 +87,7 @@ $result = parent::setValue($epoch); if ($epoch === null) { - return; + return $result; } $readable = $this->formatTime($epoch, 'Y!m!d!g:i A');