mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-23 15:22:41 +01:00
Add excel export to Maniphest
Summary: Allow Maniphest result sets to be exported to Excel. Spreadsheet_Excel_Writer is awful but comparatively easy to get working. There's also a "PHPExcel" package but it has some autoload conflicts right now and this seems good-enough. Test Plan: Exported a bunch of tasks to Excel. Reviewers: btrahan Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T923 Differential Revision: https://secure.phabricator.com/D1721
This commit is contained in:
parent
8a0a00f118
commit
280d7cd294
6 changed files with 273 additions and 23 deletions
|
@ -410,6 +410,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestController' => 'applications/maniphest/controller/base',
|
||||
'ManiphestDAO' => 'applications/maniphest/storage/base',
|
||||
'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/task',
|
||||
'ManiphestExportController' => 'applications/maniphest/controller/export',
|
||||
'ManiphestReplyHandler' => 'applications/maniphest/replyhandler',
|
||||
'ManiphestReportController' => 'applications/maniphest/controller/report',
|
||||
'ManiphestTask' => 'applications/maniphest/storage/task',
|
||||
|
@ -1210,6 +1211,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestController' => 'PhabricatorController',
|
||||
'ManiphestDAO' => 'PhabricatorLiskDAO',
|
||||
'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions',
|
||||
'ManiphestExportController' => 'ManiphestController',
|
||||
'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
|
||||
'ManiphestReportController' => 'ManiphestController',
|
||||
'ManiphestTask' => 'ManiphestDAO',
|
||||
|
|
|
@ -207,6 +207,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
'save/' => 'ManiphestTransactionSaveController',
|
||||
'preview/(?P<id>\d+)/$' => 'ManiphestTransactionPreviewController',
|
||||
),
|
||||
'export/(?P<key>[^/]+)/$' => 'ManiphestExportController',
|
||||
),
|
||||
|
||||
'/T(?P<id>\d+)$' => 'ManiphestTaskDetailController',
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group maniphest
|
||||
*/
|
||||
final class ManiphestExportController extends ManiphestController {
|
||||
|
||||
private $key;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->key = $data['key'];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class Spreadsheet_Excel_Writer
|
||||
*/
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$ok = @include_once 'Spreadsheet/Excel/Writer.php';
|
||||
if (!$ok) {
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setUser($user);
|
||||
|
||||
$dialog->setTitle('Excel Export Not Configured');
|
||||
$dialog->appendChild(
|
||||
'<p>This system does not have Spreadsheet_Excel_Writer installed. '.
|
||||
'This software component is required to export tasks to Excel. Have '.
|
||||
'your system administrator install it with:</p>'.
|
||||
'<br />'.
|
||||
'<p><code>$ sudo pear install Spreadsheet_Excel_Writer</code></p>');
|
||||
|
||||
$dialog->addCancelButton('/maniphest/');
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
$query = id(new PhabricatorSearchQuery())->loadOneWhere(
|
||||
'queryKey = %s',
|
||||
$this->key);
|
||||
if (!$query) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if (!$request->isDialogFormPost()) {
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setUser($user);
|
||||
|
||||
$dialog->setTitle('Export Tasks to Excel');
|
||||
$dialog->appendChild(
|
||||
'<p>Do you want to export the query results to Excel?</p>');
|
||||
|
||||
$dialog->addCancelButton('/maniphest/');
|
||||
$dialog->addSubmitButton('Export to Excel');
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
|
||||
}
|
||||
|
||||
$query->setParameter('limit', null);
|
||||
$query->setParameter('offset', null);
|
||||
$query->setParameter('order', 'p');
|
||||
$query->setParameter('group', 'n');
|
||||
|
||||
list($tasks, $handles) = ManiphestTaskListController::loadTasks($query);
|
||||
// Ungroup tasks.
|
||||
$tasks = array_mergev($tasks);
|
||||
|
||||
$all_projects = array_mergev(mpull($tasks, 'getProjectPHIDs'));
|
||||
$project_handles = id(new PhabricatorObjectHandleData($all_projects))
|
||||
->loadHandles();
|
||||
$handles += $project_handles;
|
||||
|
||||
$workbook = new Spreadsheet_Excel_Writer();
|
||||
$sheet = $workbook->addWorksheet('Exported Maniphest Tasks');
|
||||
|
||||
$date_format = $workbook->addFormat();
|
||||
$date_format->setNumFormat('M/D/YYYY h:mm AM/PM');
|
||||
|
||||
$widths = array(
|
||||
null,
|
||||
20,
|
||||
null,
|
||||
15,
|
||||
20,
|
||||
20,
|
||||
75,
|
||||
40,
|
||||
30,
|
||||
400,
|
||||
);
|
||||
|
||||
foreach ($widths as $col => $width) {
|
||||
if ($width !== null) {
|
||||
$sheet->setColumn($col, $col, $width);
|
||||
}
|
||||
}
|
||||
|
||||
$status_map = ManiphestTaskStatus::getTaskStatusMap();
|
||||
$pri_map = ManiphestTaskPriority::getTaskPriorityMap();
|
||||
|
||||
$rows = array();
|
||||
$rows[] = array(
|
||||
'ID',
|
||||
'Owner',
|
||||
'Status',
|
||||
'Priority',
|
||||
'Date Created',
|
||||
'Date Updated',
|
||||
'Title',
|
||||
'Projects',
|
||||
'URI',
|
||||
'Description',
|
||||
);
|
||||
$formats = array(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$date_format,
|
||||
$date_format,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
$header_format = $workbook->addFormat();
|
||||
$header_format->setBold();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$task_owner = null;
|
||||
if ($task->getOwnerPHID()) {
|
||||
$task_owner = $handles[$task->getOwnerPHID()]->getName();
|
||||
}
|
||||
|
||||
$projects = array();
|
||||
foreach ($task->getProjectPHIDs() as $phid) {
|
||||
$projects[] = $handles[$phid]->getName();
|
||||
}
|
||||
$projects = implode(', ', $projects);
|
||||
|
||||
$rows[] = array(
|
||||
'T'.$task->getID(),
|
||||
$task_owner,
|
||||
idx($status_map, $task->getStatus(), '?'),
|
||||
idx($pri_map, $task->getPriority(), '?'),
|
||||
$this->computeExcelDate($task->getDateCreated()),
|
||||
$this->computeExcelDate($task->getDateModified()),
|
||||
$task->getTitle(),
|
||||
$projects,
|
||||
PhabricatorEnv::getProductionURI('/T'.$task->getID()),
|
||||
$task->getDescription(),
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($rows as $row => $cols) {
|
||||
foreach ($cols as $col => $spec) {
|
||||
if ($row == 0) {
|
||||
$fmt = $header_format;
|
||||
} else {
|
||||
$fmt = $formats[$col];
|
||||
}
|
||||
$sheet->write($row, $col, $spec, $fmt);
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$workbook->close();
|
||||
$data = ob_get_clean();
|
||||
|
||||
return id(new AphrontFileResponse())
|
||||
->setMimeType('application/vnd.ms-excel')
|
||||
->setDownload('maniphest_tasks_'.date('Ymd').'.xls')
|
||||
->setContent($data);
|
||||
}
|
||||
|
||||
private function computeExcelDate($epoch) {
|
||||
$seconds_per_day = (60 * 60 * 24);
|
||||
$offset = ($seconds_per_day * 25569);
|
||||
|
||||
return ($epoch + $offset) / $seconds_per_day;
|
||||
}
|
||||
|
||||
}
|
24
src/applications/maniphest/controller/export/__init__.php
Normal file
24
src/applications/maniphest/controller/export/__init__.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'aphront/response/404');
|
||||
phutil_require_module('phabricator', 'aphront/response/dialog');
|
||||
phutil_require_module('phabricator', 'aphront/response/file');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/controller/tasklist');
|
||||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||
phutil_require_module('phabricator', 'applications/search/storage/query');
|
||||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
phutil_require_module('phabricator', 'view/dialog');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('ManiphestExportController.php');
|
|
@ -100,18 +100,27 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
$page = $request->getInt('page');
|
||||
$page_size = self::DEFAULT_PAGE_SIZE;
|
||||
|
||||
list($tasks, $handles, $total_count) = $this->loadTasks(
|
||||
$user_phids,
|
||||
$project_phids,
|
||||
$task_ids,
|
||||
$query = new PhabricatorSearchQuery();
|
||||
$query->setQuery('<<maniphest>>');
|
||||
$query->setParameters(
|
||||
array(
|
||||
'status' => $status_map,
|
||||
'group' => $grouping,
|
||||
'order' => $order,
|
||||
'offset' => $page,
|
||||
'limit' => $page_size,
|
||||
'view' => $this->view,
|
||||
'userPHIDs' => $user_phids,
|
||||
'projectPHIDs' => $project_phids,
|
||||
'taskIDs' => $task_ids,
|
||||
'group' => $grouping,
|
||||
'order' => $order,
|
||||
'offset' => $page,
|
||||
'limit' => $page_size,
|
||||
'status' => $status_map,
|
||||
));
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$query->save();
|
||||
unset($unguarded);
|
||||
|
||||
list($tasks, $handles, $total_count) = self::loadTasks($query);
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($user)
|
||||
->setAction($request->getRequestURI());
|
||||
|
@ -231,7 +240,7 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
}
|
||||
|
||||
|
||||
$selector->appendChild($this->renderBatchEditor());
|
||||
$selector->appendChild($this->renderBatchEditor($query));
|
||||
|
||||
$selector = phabricator_render_form(
|
||||
$user,
|
||||
|
@ -252,17 +261,17 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
));
|
||||
}
|
||||
|
||||
private function loadTasks(
|
||||
array $user_phids,
|
||||
array $project_phids,
|
||||
array $task_ids,
|
||||
array $dict) {
|
||||
public static function loadTasks(PhabricatorSearchQuery $search_query) {
|
||||
|
||||
$user_phids = $search_query->getParameter('userPHIDs', array());
|
||||
$project_phids = $search_query->getParameter('projectPHIDs', array());
|
||||
$task_ids = $search_query->getParameter('taskIDs', array());
|
||||
|
||||
$query = new ManiphestTaskQuery();
|
||||
$query->withProjects($project_phids);
|
||||
$query->withTaskIDs($task_ids);
|
||||
|
||||
$status = $dict['status'];
|
||||
$status = $search_query->getParameter('status', 'all');
|
||||
if (!empty($status['open']) && !empty($status['closed'])) {
|
||||
$query->withStatus(ManiphestTaskQuery::STATUS_ANY);
|
||||
} else if (!empty($status['open'])) {
|
||||
|
@ -271,7 +280,7 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
$query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
|
||||
}
|
||||
|
||||
switch ($this->view) {
|
||||
switch ($search_query->getParameter('view')) {
|
||||
case 'action':
|
||||
$query->withOwners($user_phids);
|
||||
break;
|
||||
|
@ -299,7 +308,7 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
$query->setOrderBy(
|
||||
idx(
|
||||
$order_map,
|
||||
$dict['order'],
|
||||
$search_query->getParameter('order'),
|
||||
ManiphestTaskQuery::ORDER_MODIFIED));
|
||||
|
||||
$group_map = array(
|
||||
|
@ -310,12 +319,12 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
$query->setGroupBy(
|
||||
idx(
|
||||
$group_map,
|
||||
$dict['group'],
|
||||
$search_query->getParameter('group'),
|
||||
ManiphestTaskQuery::GROUP_NONE));
|
||||
|
||||
$query->setCalculateRows(true);
|
||||
$query->setLimit($dict['limit']);
|
||||
$query->setOffset($dict['offset']);
|
||||
$query->setLimit($search_query->getParameter('limit'));
|
||||
$query->setOffset($search_query->getParameter('offset'));
|
||||
|
||||
$data = $query->execute();
|
||||
$total_row_count = $query->getRowCount();
|
||||
|
@ -325,7 +334,7 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
$handles = id(new PhabricatorObjectHandleData($handle_phids))
|
||||
->loadHandles();
|
||||
|
||||
switch ($dict['group']) {
|
||||
switch ($search_query->getParameter('group')) {
|
||||
case 'priority':
|
||||
$data = mgroup($data, 'getPriority');
|
||||
krsort($data);
|
||||
|
@ -471,7 +480,7 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
return array($group_by, $group_control);
|
||||
}
|
||||
|
||||
private function renderBatchEditor() {
|
||||
private function renderBatchEditor(PhabricatorSearchQuery $search_query) {
|
||||
Javelin::initBehavior(
|
||||
'maniphest-batch-selector',
|
||||
array(
|
||||
|
@ -510,6 +519,14 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
),
|
||||
'Batch Edit Selected Tasks »');
|
||||
|
||||
$export = javelin_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
|
||||
'class' => 'grey button',
|
||||
),
|
||||
'Export Tasks to Excel...');
|
||||
|
||||
return
|
||||
'<div class="maniphest-batch-editor">'.
|
||||
'<div class="batch-editor-header">Batch Task Editor</div>'.
|
||||
|
@ -519,6 +536,9 @@ class ManiphestTaskListController extends ManiphestController {
|
|||
$select_all.
|
||||
$select_none.
|
||||
'</td>'.
|
||||
'<td>'.
|
||||
$export.
|
||||
'</td>'.
|
||||
'<td id="batch-select-status-cell">'.
|
||||
'0 Selected Tasks'.
|
||||
'</td>'.
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
|
||||
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'aphront/writeguard');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/query');
|
||||
phutil_require_module('phabricator', 'applications/maniphest/view/tasklist');
|
||||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||
phutil_require_module('phabricator', 'applications/search/storage/query');
|
||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||
|
|
Loading…
Reference in a new issue