1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-19 05:12:41 +01:00

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
This commit is contained in:
epriestley 2016-10-05 16:22:54 -07:00
parent 49448a87c1
commit fa6a5a46ba
11 changed files with 352 additions and 15 deletions

View file

@ -2088,7 +2088,9 @@ phutil_register_library_map(array(
'PhabricatorCalendarExportQueryKeyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportQueryKeyTransaction.php',
'PhabricatorCalendarExportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarExportSearchEngine.php',
'PhabricatorCalendarExportTransaction' => 'applications/calendar/storage/PhabricatorCalendarExportTransaction.php',
'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php',
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php',
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php',
@ -6857,7 +6859,9 @@ phutil_register_library_map(array(
'PhabricatorCalendarExportQueryKeyTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarExportTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
'PhabricatorCalendarIconSet' => 'PhabricatorIconSet',

View file

@ -0,0 +1,154 @@
<?php
final class PhabricatorCalendarExportViewController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$export = id(new PhabricatorCalendarExportQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$export) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Exports'),
'/calendar/export/');
$crumbs->addTextCrumb(pht('Export %d', $export->getID()));
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$export,
new PhabricatorCalendarExportTransactionQuery());
$timeline->setShouldTerminate(true);
$header = $this->buildHeaderView($export);
$curtain = $this->buildCurtain($export);
$details = $this->buildPropertySection($export);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(
array(
$timeline,
))
->setCurtain($curtain)
->addPropertySection(pht('Details'), $details);
$page_title = pht('Export %d %s', $export->getID(), $export->getName());
return $this->newPage()
->setTitle($page_title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($export->getPHID()))
->appendChild($view);
}
private function buildHeaderView(
PhabricatorCalendarExport $export) {
$viewer = $this->getViewer();
$id = $export->getID();
if ($export->getIsDisabled()) {
$icon = 'fa-ban';
$color = 'grey';
$status = pht('Disabled');
} else {
$icon = 'fa-check';
$color = 'bluegrey';
$status = pht('Active');
}
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($export->getName())
->setStatus($icon, $color, $status)
->setPolicyObject($export);
return $header;
}
private function buildCurtain(PhabricatorCalendarExport $export) {
$viewer = $this->getRequest()->getUser();
$id = $export->getID();
$curtain = $this->newCurtainView($export);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$export,
PhabricatorPolicyCapability::CAN_EDIT);
$ics_uri = $export->getICSURI();
$edit_uri = "export/edit/{$id}/";
$edit_uri = $this->getApplicationURI($edit_uri);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Export'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref($edit_uri));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Export as .ics'))
->setIcon('fa-download')
->setHref($ics_uri));
return $curtain;
}
private function buildPropertySection(
PhabricatorCalendarExport $export) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
$mode = $export->getPolicyMode();
$policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode);
$policy_name = PhabricatorCalendarExport::getPolicyModeName($mode);
$policy_desc = PhabricatorCalendarExport::getPolicyModeDescription($mode);
$policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode);
$policy_view = id(new PHUIStatusListView())
->addItem(
id(new PHUIStatusItemView())
->setIcon($policy_icon, $policy_color)
->setTarget($policy_name)
->setNote($policy_desc));
$properties->addProperty(pht('Mode'), $policy_view);
$query_key = $export->getQueryKey();
$query_link = phutil_tag(
'a',
array(
'href' => $this->getApplicationURI("/query/{$query_key}/"),
),
$query_key);
$properties->addProperty(pht('Query'), $query_link);
$ics_uri = $export->getICSURI();
$ics_uri = PhabricatorEnv::getURI($ics_uri);
$properties->addProperty(
pht('ICS URI'),
phutil_tag(
'a',
array(
'href' => $ics_uri,
),
$ics_uri));
return $properties;
}
}

View file

@ -43,7 +43,7 @@ final class PhabricatorCalendarExportEditEngine
}
protected function getObjectEditShortText($object) {
return $object->getMonogram();
return pht('Export %d', $object->getID());
}
protected function getObjectCreateShortText() {
@ -65,6 +65,22 @@ final class PhabricatorCalendarExportEditEngine
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$export_modes = PhabricatorCalendarExport::getAvailablePolicyModes();
$export_modes = array_fuse($export_modes);
$current_mode = $object->getPolicyMode();
if (empty($export_modes[$current_mode])) {
array_shift($export_modes, $current_mode);
}
$mode_options = array();
foreach ($export_modes as $export_mode) {
$mode_name = PhabricatorCalendarExport::getPolicyModeName($export_mode);
$mode_summary = PhabricatorCalendarExport::getPolicyModeSummary(
$export_mode);
$mode_options[$export_mode] = pht('%s: %s', $mode_name, $mode_summary);
}
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
@ -87,6 +103,27 @@ final class PhabricatorCalendarExportEditEngine
->setConduitDescription(pht('Disable or restore the export.'))
->setConduitTypeDescription(pht('True to cancel the export.'))
->setValue($object->getIsDisabled()),
id(new PhabricatorTextEditField())
->setKey('queryKey')
->setLabel(pht('Query Key'))
->setDescription(pht('Query to execute.'))
->setIsRequired(true)
->setTransactionType(
PhabricatorCalendarExportQueryKeyTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Change the export query key.'))
->setConduitTypeDescription(pht('New export query key.'))
->setValue($object->getQueryKey()),
id(new PhabricatorSelectEditField())
->setKey('mode')
->setLabel(pht('Mode'))
->setTransactionType(
PhabricatorCalendarExportModeTransaction::TRANSACTIONTYPE)
->setOptions($mode_options)
->setDescription(pht('Change the policy mode for the export.'))
->setConduitDescription(pht('Adjust export mode.'))
->setConduitTypeDescription(pht('New export mode.'))
->setValue($current_mode),
);
return $fields;

View file

@ -11,4 +11,8 @@ final class PhabricatorCalendarExportEditor
return pht('Calendar Exports');
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this export.', $author);
}
}

View file

@ -255,11 +255,20 @@ final class PhabricatorCalendarEventSearchEngine
array $handles) {
if ($this->isMonthView($query)) {
return $this->buildCalendarMonthView($events, $query);
$result = $this->buildCalendarMonthView($events, $query);
} else if ($this->isDayView($query)) {
return $this->buildCalendarDayView($events, $query);
$result = $this->buildCalendarDayView($events, $query);
} else {
$result = $this->buildCalendarListView($events, $query);
}
return $result;
}
private function buildCalendarListView(
array $events,
PhabricatorSavedQuery $query) {
assert_instances_of($events, 'PhabricatorCalendarEvent');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
@ -562,4 +571,17 @@ final class PhabricatorCalendarEventSearchEngine
return false;
}
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
$viewer = $this->requireViewer();
$can_export = $viewer->isLoggedIn();
return array(
id(new PhabricatorActionView())
->setIcon('fa-download')
->setName(pht('Export Query as .ics'))
->setDisabled(!$can_export)
->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()),
);
}
}

View file

@ -64,6 +64,7 @@ final class PhabricatorCalendarExportSearchEngine
foreach ($exports as $export) {
$item = id(new PHUIObjectItemView())
->setViewer($viewer)
->setObjectName(pht('Export %d', $export->getID()))
->setHeader($export->getName())
->setHref($export->getURI());
@ -71,6 +72,15 @@ final class PhabricatorCalendarExportSearchEngine
$item->setDisabled(true);
}
$mode = $export->getPolicyMode();
$policy_icon = PhabricatorCalendarExport::getPolicyModeIcon($mode);
$policy_name = PhabricatorCalendarExport::getPolicyModeName($mode);
$policy_color = PhabricatorCalendarExport::getPolicyModeColor($mode);
$item->addIcon(
"{$policy_icon} {$policy_color}",
$policy_name);
$list->addItem($item);
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorCalendarExportTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PhabricatorCalendarExportTransaction();
}
}

View file

@ -14,12 +14,12 @@ final class PhabricatorCalendarExport extends PhabricatorCalendarDAO
protected $isDisabled = 0;
const MODE_PUBLIC = 'public';
const MODE_PRIVATE = 'private';
const MODE_PRIVILEGED = 'privileged';
public static function initializeNewCalendarExport(PhabricatorUser $actor) {
return id(new self())
->setAuthorPHID($actor->getPHID())
->setPolicyMode(self::MODE_PRIVATE)
->setPolicyMode(self::MODE_PRIVILEGED)
->setIsDisabled(0);
}
@ -65,10 +65,23 @@ final class PhabricatorCalendarExport extends PhabricatorCalendarDAO
private static function getPolicyModeMap() {
return array(
self::MODE_PUBLIC => array(
'icon' => 'fa-globe',
'name' => pht('Public'),
'color' => 'bluegrey',
'summary' => pht(
'Export only public data.'),
'description' => pht(
'Only publicly available data is exported.'),
),
self::MODE_PRIVATE => array(
'name' => pht('Private'),
self::MODE_PRIVILEGED => array(
'icon' => 'fa-unlock-alt',
'name' => pht('Privileged'),
'color' => 'red',
'summary' => pht(
'Export private data.'),
'description' => pht(
'Anyone who knows the URI for this export can view all event '.
'details as though they were logged in with your account.'),
),
);
}
@ -78,14 +91,55 @@ final class PhabricatorCalendarExport extends PhabricatorCalendarDAO
}
public static function getPolicyModeName($const) {
$map = self::getPolicyModeSpec($const);
return idx($map, 'name', $const);
$spec = self::getPolicyModeSpec($const);
return idx($spec, 'name', $const);
}
public static function getPolicyModeIcon($const) {
$spec = self::getPolicyModeSpec($const);
return idx($spec, 'icon', $const);
}
public static function getPolicyModeColor($const) {
$spec = self::getPolicyModeSpec($const);
return idx($spec, 'color', $const);
}
public static function getPolicyModeSummary($const) {
$spec = self::getPolicyModeSpec($const);
return idx($spec, 'summary', $const);
}
public static function getPolicyModeDescription($const) {
$spec = self::getPolicyModeSpec($const);
return idx($spec, 'description', $const);
}
public static function getPolicyModes() {
return array_keys(self::getPolicyModeMap());
}
public static function getAvailablePolicyModes() {
$modes = array();
if (PhabricatorEnv::getEnvConfig('policy.allow-public')) {
$modes[] = self::MODE_PUBLIC;
}
$modes[] = self::MODE_PRIVILEGED;
return $modes;
}
public function getICSFilename() {
return PhabricatorSlug::normalizeProjectSlug($this->getName()).'.ics';
}
public function getICSURI() {
$secret_key = $this->getSecretKey();
$ics_name = $this->getICSFilename();
return "/calendar/export/ics/{$secret_key}/{$ics_name}";
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -20,12 +20,15 @@ final class PhabricatorCalendarExportQueryKeyTransaction
}
public function validateTransactions($object, array $xactions) {
$actor = $this->getActor();
$errors = array();
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
$query = id(new PhabricatorSavedQueryQuery())
->setViewer($actor)
->withEngineClassNames(array('PhabricatorCalendarEventSearchEngine'))
->withQueryKeys(array($value))
->executeOne();
@ -33,6 +36,13 @@ final class PhabricatorCalendarExportQueryKeyTransaction
continue;
}
$builtin = id(new PhabricatorCalendarEventSearchEngine())
->setViewer($actor)
->getBuiltinQueries($actor);
if (isset($builtin[$value])) {
continue;
}
$errors[] = $this->newInvalidError(
pht(
'Query key "%s" does not identify a valid event query.',

View file

@ -252,12 +252,6 @@ final class PhabricatorApplicationSearchController
get_class($engine)));
}
if ($list->getActions()) {
foreach ($list->getActions() as $action) {
$header->addActionLink($action);
}
}
if ($list->getObjectList()) {
$box->setObjectList($list->getObjectList());
}
@ -274,6 +268,21 @@ final class PhabricatorApplicationSearchController
$result_header = $list->getHeader();
if ($result_header) {
$box->setHeader($result_header);
$header = $result_header;
}
if ($list->getActions()) {
foreach ($list->getActions() as $action) {
$header->addActionLink($action);
}
}
$use_actions = $engine->newUseResultsActions($saved_query);
if ($use_actions) {
$use_dropdown = $this->newUseResultsDropdown(
$saved_query,
$use_actions);
$header->addActionLink($use_dropdown);
}
$more_crumbs = $list->getCrumbs();
@ -496,5 +505,24 @@ final class PhabricatorApplicationSearchController
return $nux_view;
}
private function newUseResultsDropdown(
PhabricatorSavedQuery $query,
array $dropdown_items) {
$viewer = $this->getViewer();
$action_list = id(new PhabricatorActionListView())
->setViewer($viewer);
foreach ($dropdown_items as $dropdown_item) {
$action_list->addAction($dropdown_item);
}
return id(new PHUIButtonView())
->setTag('a')
->setHref('#')
->setText(pht('Use Results...'))
->setIcon('fa-road')
->setDropdownMenu($action_list);
}
}

View file

@ -1390,4 +1390,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
return null;
}
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
return array();
}
}