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

Make more of the Calendar export workflow work

Summary:
Ref T10747.

- Adds a "Use Results..." dropdown to query result pages, with actions you can take with search results (today: create export; in future: bulk edit, export as excel, make dashboard panel, etc).
- Allows you to create an export against a query key.
- I'm just using a text edit field for this for now.
- Fleshes out export modes. I plan to support: public (as though you were logged out), privileged (as though you were logged in) and availability (event times, but not details).

This does not actually export stuff yet.

Test Plan: Created some exports. Viewed and listed exports.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

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

+352 -15
+4
src/__phutil_library_map__.php
··· 2088 2088 'PhabricatorCalendarExportQueryKeyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php', 2089 2089 'PhabricatorCalendarExportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarExportSearchEngine.php', 2090 2090 'PhabricatorCalendarExportTransaction' => 'applications/calendar/storage/PhabricatorCalendarExportTransaction.php', 2091 + 'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php', 2091 2092 'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php', 2093 + 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 2092 2094 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 2093 2095 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 2094 2096 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', ··· 6857 6859 'PhabricatorCalendarExportQueryKeyTransaction' => 'PhabricatorCalendarExportTransactionType', 6858 6860 'PhabricatorCalendarExportSearchEngine' => 'PhabricatorApplicationSearchEngine', 6859 6861 'PhabricatorCalendarExportTransaction' => 'PhabricatorModularTransaction', 6862 + 'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 6860 6863 'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType', 6864 + 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 6861 6865 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 6862 6866 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 6863 6867 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet',
+154
src/applications/calendar/controller/PhabricatorCalendarExportViewController.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarExportViewController 4 + extends PhabricatorCalendarController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $export = id(new PhabricatorCalendarExportQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->executeOne(); 13 + if (!$export) { 14 + return new Aphront404Response(); 15 + } 16 + 17 + $crumbs = $this->buildApplicationCrumbs(); 18 + $crumbs->addTextCrumb( 19 + pht('Exports'), 20 + '/calendar/export/'); 21 + $crumbs->addTextCrumb(pht('Export %d', $export->getID())); 22 + $crumbs->setBorder(true); 23 + 24 + $timeline = $this->buildTransactionTimeline( 25 + $export, 26 + new PhabricatorCalendarExportTransactionQuery()); 27 + $timeline->setShouldTerminate(true); 28 + 29 + $header = $this->buildHeaderView($export); 30 + $curtain = $this->buildCurtain($export); 31 + $details = $this->buildPropertySection($export); 32 + 33 + $view = id(new PHUITwoColumnView()) 34 + ->setHeader($header) 35 + ->setMainColumn( 36 + array( 37 + $timeline, 38 + )) 39 + ->setCurtain($curtain) 40 + ->addPropertySection(pht('Details'), $details); 41 + 42 + $page_title = pht('Export %d %s', $export->getID(), $export->getName()); 43 + 44 + return $this->newPage() 45 + ->setTitle($page_title) 46 + ->setCrumbs($crumbs) 47 + ->setPageObjectPHIDs(array($export->getPHID())) 48 + ->appendChild($view); 49 + } 50 + 51 + private function buildHeaderView( 52 + PhabricatorCalendarExport $export) { 53 + $viewer = $this->getViewer(); 54 + $id = $export->getID(); 55 + 56 + if ($export->getIsDisabled()) { 57 + $icon = 'fa-ban'; 58 + $color = 'grey'; 59 + $status = pht('Disabled'); 60 + } else { 61 + $icon = 'fa-check'; 62 + $color = 'bluegrey'; 63 + $status = pht('Active'); 64 + } 65 + 66 + $header = id(new PHUIHeaderView()) 67 + ->setUser($viewer) 68 + ->setHeader($export->getName()) 69 + ->setStatus($icon, $color, $status) 70 + ->setPolicyObject($export); 71 + 72 + return $header; 73 + } 74 + 75 + private function buildCurtain(PhabricatorCalendarExport $export) { 76 + $viewer = $this->getRequest()->getUser(); 77 + $id = $export->getID(); 78 + 79 + $curtain = $this->newCurtainView($export); 80 + 81 + $can_edit = PhabricatorPolicyFilter::hasCapability( 82 + $viewer, 83 + $export, 84 + PhabricatorPolicyCapability::CAN_EDIT); 85 + 86 + $ics_uri = $export->getICSURI(); 87 + 88 + $edit_uri = "export/edit/{$id}/"; 89 + $edit_uri = $this->getApplicationURI($edit_uri); 90 + 91 + $curtain->addAction( 92 + id(new PhabricatorActionView()) 93 + ->setName(pht('Edit Export')) 94 + ->setIcon('fa-pencil') 95 + ->setDisabled(!$can_edit) 96 + ->setWorkflow(!$can_edit) 97 + ->setHref($edit_uri)); 98 + 99 + $curtain->addAction( 100 + id(new PhabricatorActionView()) 101 + ->setName(pht('Export as .ics')) 102 + ->setIcon('fa-download') 103 + ->setHref($ics_uri)); 104 + 105 + return $curtain; 106 + } 107 + 108 + private function buildPropertySection( 109 + PhabricatorCalendarExport $export) { 110 + $viewer = $this->getViewer(); 111 + 112 + $properties = id(new PHUIPropertyListView()) 113 + ->setUser($viewer); 114 + 115 + $mode = $export->getPolicyMode(); 116 + 117 + $policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode); 118 + $policy_name = PhabricatorCalendarExport::getPolicyModeName($mode); 119 + $policy_desc = PhabricatorCalendarExport::getPolicyModeDescription($mode); 120 + $policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode); 121 + 122 + $policy_view = id(new PHUIStatusListView()) 123 + ->addItem( 124 + id(new PHUIStatusItemView()) 125 + ->setIcon($policy_icon, $policy_color) 126 + ->setTarget($policy_name) 127 + ->setNote($policy_desc)); 128 + 129 + $properties->addProperty(pht('Mode'), $policy_view); 130 + 131 + $query_key = $export->getQueryKey(); 132 + $query_link = phutil_tag( 133 + 'a', 134 + array( 135 + 'href' => $this->getApplicationURI("/query/{$query_key}/"), 136 + ), 137 + $query_key); 138 + $properties->addProperty(pht('Query'), $query_link); 139 + 140 + $ics_uri = $export->getICSURI(); 141 + $ics_uri = PhabricatorEnv::getURI($ics_uri); 142 + 143 + $properties->addProperty( 144 + pht('ICS URI'), 145 + phutil_tag( 146 + 'a', 147 + array( 148 + 'href' => $ics_uri, 149 + ), 150 + $ics_uri)); 151 + 152 + return $properties; 153 + } 154 + }
+38 -1
src/applications/calendar/editor/PhabricatorCalendarExportEditEngine.php
··· 43 43 } 44 44 45 45 protected function getObjectEditShortText($object) { 46 - return $object->getMonogram(); 46 + return pht('Export %d', $object->getID()); 47 47 } 48 48 49 49 protected function getObjectCreateShortText() { ··· 65 65 protected function buildCustomEditFields($object) { 66 66 $viewer = $this->getViewer(); 67 67 68 + $export_modes = PhabricatorCalendarExport::getAvailablePolicyModes(); 69 + $export_modes = array_fuse($export_modes); 70 + 71 + $current_mode = $object->getPolicyMode(); 72 + if (empty($export_modes[$current_mode])) { 73 + array_shift($export_modes, $current_mode); 74 + } 75 + 76 + $mode_options = array(); 77 + foreach ($export_modes as $export_mode) { 78 + $mode_name = PhabricatorCalendarExport::getPolicyModeName($export_mode); 79 + $mode_summary = PhabricatorCalendarExport::getPolicyModeSummary( 80 + $export_mode); 81 + $mode_options[$export_mode] = pht('%s: %s', $mode_name, $mode_summary); 82 + } 83 + 68 84 $fields = array( 69 85 id(new PhabricatorTextEditField()) 70 86 ->setKey('name') ··· 87 103 ->setConduitDescription(pht('Disable or restore the export.')) 88 104 ->setConduitTypeDescription(pht('True to cancel the export.')) 89 105 ->setValue($object->getIsDisabled()), 106 + id(new PhabricatorTextEditField()) 107 + ->setKey('queryKey') 108 + ->setLabel(pht('Query Key')) 109 + ->setDescription(pht('Query to execute.')) 110 + ->setIsRequired(true) 111 + ->setTransactionType( 112 + PhabricatorCalendarExportQueryKeyTransaction::TRANSACTIONTYPE) 113 + ->setConduitDescription(pht('Change the export query key.')) 114 + ->setConduitTypeDescription(pht('New export query key.')) 115 + ->setValue($object->getQueryKey()), 116 + id(new PhabricatorSelectEditField()) 117 + ->setKey('mode') 118 + ->setLabel(pht('Mode')) 119 + ->setTransactionType( 120 + PhabricatorCalendarExportModeTransaction::TRANSACTIONTYPE) 121 + ->setOptions($mode_options) 122 + ->setDescription(pht('Change the policy mode for the export.')) 123 + ->setConduitDescription(pht('Adjust export mode.')) 124 + ->setConduitTypeDescription(pht('New export mode.')) 125 + ->setValue($current_mode), 126 + 90 127 ); 91 128 92 129 return $fields;
+4
src/applications/calendar/editor/PhabricatorCalendarExportEditor.php
··· 11 11 return pht('Calendar Exports'); 12 12 } 13 13 14 + public function getCreateObjectTitle($author, $object) { 15 + return pht('%s created this export.', $author); 16 + } 17 + 14 18 }
+24 -2
src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
··· 255 255 array $handles) { 256 256 257 257 if ($this->isMonthView($query)) { 258 - return $this->buildCalendarMonthView($events, $query); 258 + $result = $this->buildCalendarMonthView($events, $query); 259 259 } else if ($this->isDayView($query)) { 260 - return $this->buildCalendarDayView($events, $query); 260 + $result = $this->buildCalendarDayView($events, $query); 261 + } else { 262 + $result = $this->buildCalendarListView($events, $query); 261 263 } 264 + 265 + return $result; 266 + } 267 + 268 + private function buildCalendarListView( 269 + array $events, 270 + PhabricatorSavedQuery $query) { 262 271 263 272 assert_instances_of($events, 'PhabricatorCalendarEvent'); 264 273 $viewer = $this->requireViewer(); ··· 560 569 } 561 570 562 571 return false; 572 + } 573 + 574 + public function newUseResultsActions(PhabricatorSavedQuery $saved) { 575 + $viewer = $this->requireViewer(); 576 + $can_export = $viewer->isLoggedIn(); 577 + 578 + return array( 579 + id(new PhabricatorActionView()) 580 + ->setIcon('fa-download') 581 + ->setName(pht('Export Query as .ics')) 582 + ->setDisabled(!$can_export) 583 + ->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()), 584 + ); 563 585 } 564 586 565 587 }
+10
src/applications/calendar/query/PhabricatorCalendarExportSearchEngine.php
··· 64 64 foreach ($exports as $export) { 65 65 $item = id(new PHUIObjectItemView()) 66 66 ->setViewer($viewer) 67 + ->setObjectName(pht('Export %d', $export->getID())) 67 68 ->setHeader($export->getName()) 68 69 ->setHref($export->getURI()); 69 70 70 71 if ($export->getIsDisabled()) { 71 72 $item->setDisabled(true); 72 73 } 74 + 75 + $mode = $export->getPolicyMode(); 76 + $policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode); 77 + $policy_name = PhabricatorCalendarExport::getPolicyModeName($mode); 78 + $policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode); 79 + 80 + $item->addIcon( 81 + "{$policy_icon} {$policy_color}", 82 + $policy_name); 73 83 74 84 $list->addItem($item); 75 85 }
+10
src/applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarExportTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhabricatorCalendarExportTransaction(); 8 + } 9 + 10 + }
+60 -6
src/applications/calendar/storage/PhabricatorCalendarExport.php
··· 14 14 protected $isDisabled = 0; 15 15 16 16 const MODE_PUBLIC = 'public'; 17 - const MODE_PRIVATE = 'private'; 17 + const MODE_PRIVILEGED = 'privileged'; 18 18 19 19 public static function initializeNewCalendarExport(PhabricatorUser $actor) { 20 20 return id(new self()) 21 21 ->setAuthorPHID($actor->getPHID()) 22 - ->setPolicyMode(self::MODE_PRIVATE) 22 + ->setPolicyMode(self::MODE_PRIVILEGED) 23 23 ->setIsDisabled(0); 24 24 } 25 25 ··· 65 65 private static function getPolicyModeMap() { 66 66 return array( 67 67 self::MODE_PUBLIC => array( 68 + 'icon' => 'fa-globe', 68 69 'name' => pht('Public'), 70 + 'color' => 'bluegrey', 71 + 'summary' => pht( 72 + 'Export only public data.'), 73 + 'description' => pht( 74 + 'Only publicly available data is exported.'), 69 75 ), 70 - self::MODE_PRIVATE => array( 71 - 'name' => pht('Private'), 76 + self::MODE_PRIVILEGED => array( 77 + 'icon' => 'fa-unlock-alt', 78 + 'name' => pht('Privileged'), 79 + 'color' => 'red', 80 + 'summary' => pht( 81 + 'Export private data.'), 82 + 'description' => pht( 83 + 'Anyone who knows the URI for this export can view all event '. 84 + 'details as though they were logged in with your account.'), 72 85 ), 73 86 ); 74 87 } ··· 78 91 } 79 92 80 93 public static function getPolicyModeName($const) { 81 - $map = self::getPolicyModeSpec($const); 82 - return idx($map, 'name', $const); 94 + $spec = self::getPolicyModeSpec($const); 95 + return idx($spec, 'name', $const); 96 + } 97 + 98 + public static function getPolicyModeIcon($const) { 99 + $spec = self::getPolicyModeSpec($const); 100 + return idx($spec, 'icon', $const); 101 + } 102 + 103 + public static function getPolicyModeColor($const) { 104 + $spec = self::getPolicyModeSpec($const); 105 + return idx($spec, 'color', $const); 106 + } 107 + 108 + public static function getPolicyModeSummary($const) { 109 + $spec = self::getPolicyModeSpec($const); 110 + return idx($spec, 'summary', $const); 111 + } 112 + 113 + public static function getPolicyModeDescription($const) { 114 + $spec = self::getPolicyModeSpec($const); 115 + return idx($spec, 'description', $const); 83 116 } 84 117 85 118 public static function getPolicyModes() { 86 119 return array_keys(self::getPolicyModeMap()); 87 120 } 88 121 122 + public static function getAvailablePolicyModes() { 123 + $modes = array(); 124 + 125 + if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { 126 + $modes[] = self::MODE_PUBLIC; 127 + } 128 + 129 + $modes[] = self::MODE_PRIVILEGED; 130 + 131 + return $modes; 132 + } 133 + 134 + public function getICSFilename() { 135 + return PhabricatorSlug::normalizeProjectSlug($this->getName()).'.ics'; 136 + } 137 + 138 + public function getICSURI() { 139 + $secret_key = $this->getSecretKey(); 140 + $ics_name = $this->getICSFilename(); 141 + return "/calendar/export/ics/{$secret_key}/{$ics_name}"; 142 + } 89 143 90 144 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 91 145
+10
src/applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php
··· 20 20 } 21 21 22 22 public function validateTransactions($object, array $xactions) { 23 + $actor = $this->getActor(); 24 + 23 25 $errors = array(); 24 26 25 27 foreach ($xactions as $xaction) { 26 28 $value = $xaction->getNewValue(); 27 29 28 30 $query = id(new PhabricatorSavedQueryQuery()) 31 + ->setViewer($actor) 29 32 ->withEngineClassNames(array('PhabricatorCalendarEventSearchEngine')) 30 33 ->withQueryKeys(array($value)) 31 34 ->executeOne(); 32 35 if ($query) { 36 + continue; 37 + } 38 + 39 + $builtin = id(new PhabricatorCalendarEventSearchEngine()) 40 + ->setViewer($actor) 41 + ->getBuiltinQueries($actor); 42 + if (isset($builtin[$value])) { 33 43 continue; 34 44 } 35 45
+34 -6
src/applications/search/controller/PhabricatorApplicationSearchController.php
··· 252 252 get_class($engine))); 253 253 } 254 254 255 - if ($list->getActions()) { 256 - foreach ($list->getActions() as $action) { 257 - $header->addActionLink($action); 258 - } 259 - } 260 - 261 255 if ($list->getObjectList()) { 262 256 $box->setObjectList($list->getObjectList()); 263 257 } ··· 274 268 $result_header = $list->getHeader(); 275 269 if ($result_header) { 276 270 $box->setHeader($result_header); 271 + $header = $result_header; 272 + } 273 + 274 + if ($list->getActions()) { 275 + foreach ($list->getActions() as $action) { 276 + $header->addActionLink($action); 277 + } 278 + } 279 + 280 + $use_actions = $engine->newUseResultsActions($saved_query); 281 + if ($use_actions) { 282 + $use_dropdown = $this->newUseResultsDropdown( 283 + $saved_query, 284 + $use_actions); 285 + $header->addActionLink($use_dropdown); 277 286 } 278 287 279 288 $more_crumbs = $list->getCrumbs(); ··· 496 505 return $nux_view; 497 506 } 498 507 508 + private function newUseResultsDropdown( 509 + PhabricatorSavedQuery $query, 510 + array $dropdown_items) { 511 + 512 + $viewer = $this->getViewer(); 513 + 514 + $action_list = id(new PhabricatorActionListView()) 515 + ->setViewer($viewer); 516 + foreach ($dropdown_items as $dropdown_item) { 517 + $action_list->addAction($dropdown_item); 518 + } 519 + 520 + return id(new PHUIButtonView()) 521 + ->setTag('a') 522 + ->setHref('#') 523 + ->setText(pht('Use Results...')) 524 + ->setIcon('fa-road') 525 + ->setDropdownMenu($action_list); 526 + } 499 527 500 528 }
+4
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 1390 1390 return null; 1391 1391 } 1392 1392 1393 + public function newUseResultsActions(PhabricatorSavedQuery $saved) { 1394 + return array(); 1395 + } 1396 + 1393 1397 }