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:
parent
49448a87c1
commit
fa6a5a46ba
11 changed files with 352 additions and 15 deletions
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -11,4 +11,8 @@ final class PhabricatorCalendarExportEditor
|
|||
return pht('Calendar Exports');
|
||||
}
|
||||
|
||||
public function getCreateObjectTitle($author, $object) {
|
||||
return pht('%s created this export.', $author);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExportTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PhabricatorCalendarExportTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1390,4 +1390,8 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue