-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
782 additions
and
1,720 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
## v2.0.0 | ||
Phixator 2 initial release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,29 @@ | ||
Phabricator extensions collection. | ||
# :hourglass: Phixator 2 | ||
|
||
Run arcanist/bin/arc liberate phabricator/src according to manual https://secure.phabricator.com/book/phabcontrib/article/adding_new_classes/ after add new extension. | ||
Task Work Log extension for Phabricator / Phorge | ||
|
||
Updated September 15 2020 | ||
## Screenshots | ||
| <img src="screenshot/phixator-list.png" height="220"> | <img src="screenshot/phixator-menu.png" height="220"> | | ||
| - | - | | ||
|
||
## Features | ||
|
||
- Log hours of work on Maniphest tasks with the spent time, description and date of the work | ||
- Show the list of work logs, filtered by users, projects, spaces, tasks or time periods | ||
- Embed work log queries in Dashboards, with choice of displayed information | ||
- Export your query results in .csv, .json, .txt | ||
- French translation | ||
|
||
## Installation | ||
|
||
1. Clone or unzip this repository in the extensions folder of Phabricator in your server (`phabricator/src/extensions`) | ||
2. Browse the application in Phabricator (`your-url.com/phixator`) | ||
3. Subscribe/Watch this repository for new releases and bug fixes | ||
|
||
## To do | ||
- [ ] Time budgets | ||
|
||
## Credits | ||
Project originated by [4xxi](https://github.com/4xxi), updated by [ssnd292](https://github.com/ssnd292), rewritten by [motla](https://github.com/motla). | ||
|
||
[MIT License](LICENSE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
class PhabricatorPhixatorApplication extends PhabricatorApplication { | ||
|
||
public function getName() { | ||
return pht('Phixator'); | ||
} | ||
|
||
public function getBaseURI() { | ||
return '/phixator/'; | ||
} | ||
|
||
public function getShortDescription() { | ||
return pht('Log work time on tasks'); | ||
} | ||
|
||
public function getIcon() { | ||
return 'fa-hourglass-half'; | ||
} | ||
|
||
public function getTitleGlyph() { | ||
return "\xE2\x8F\xB3"; | ||
} | ||
|
||
public function getApplicationGroup() { | ||
return self::GROUP_UTILITIES; | ||
} | ||
|
||
public function getApplicationOrder() { | ||
return 0.110; | ||
} | ||
|
||
public function getEventListeners() { | ||
return [new PhixatorUIEventListener()]; | ||
} | ||
|
||
public function getRoutes() { | ||
return [ | ||
'/phixator/' => [ | ||
$this->getQueryRoutePattern() => 'PhixatorWorkLogListController', | ||
'log/' => [ | ||
'edit/(?P<transactionPHID>[^/]+)/' => 'PhixatorWorkLogEditController', | ||
'delete/(?P<transactionPHID>[^/]+)/' => 'PhixatorWorkLogDeleteController', | ||
'(?P<taskPHID>[^/]+)/' => 'PhixatorWorkLogEditController', | ||
] | ||
], | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
/** | ||
* Displays and manage time log deletion | ||
*/ | ||
final class PhixatorWorkLogDeleteController extends PhabricatorController { | ||
|
||
public function handleRequest(AphrontRequest $request) { | ||
$viewer = $request->getViewer(); | ||
$transactionPHID = $request->getURIData('transactionPHID'); | ||
$return_uri = $request->getStr('return_to') ?? '/phixator'; | ||
|
||
// Get the transaction to delete, check that the viewer can edit it | ||
$transaction = (new PhixatorWorkLogQuery()) | ||
->setViewer($viewer) | ||
->withPHIDs([$transactionPHID]) | ||
->requireCapabilities([PhabricatorPolicyCapability::CAN_EDIT]) | ||
->executeOne(); | ||
if (!$transaction) return new Aphront404Response(); | ||
|
||
// If the delete confirmation was submitted, "delete" the transaction (just rename its type) | ||
if ($request->isFormPost()) { | ||
$transaction->setTransactionType(ManiphestTaskWorkLogTransaction::TRANSACTIONTYPE.":deleted"); | ||
$transaction->save(); | ||
return (new AphrontRedirectResponse())->setURI($return_uri); | ||
} | ||
|
||
// Else display delete confirmation to the user | ||
$dialog = (new AphrontDialogView()) | ||
->setViewer($viewer) | ||
->setTitle(pht('Delete Time Log?')) | ||
->appendChild(pht('Are you sure to delete this time log?')) | ||
->addSubmitButton(pht('Delete')) | ||
->addCancelButton($return_uri); | ||
return (new AphrontDialogResponse())->setDialog($dialog); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
/** | ||
* Base class to display and manage the forms to create or edit work time logs | ||
*/ | ||
class PhixatorWorkLogEditController extends PhabricatorController { | ||
public function handleRequest(AphrontRequest $request) { | ||
$viewer = $request->getViewer(); | ||
$taskPHID = $request->getURIData('taskPHID'); | ||
$transactionPHID = $request->getURIData('transactionPHID'); | ||
$is_edit = (bool)$transactionPHID; | ||
$return_uri = $request->getStr('return_to'); | ||
if(!$return_uri) $return_uri = '/phixator'; | ||
|
||
// Declare default parameters | ||
$params = array('minutes' => 0, 'description' => '', 'started' => time()); | ||
|
||
// In case of an edit, check if the viewer can edit this time log, then get time log values | ||
if($is_edit) { | ||
$transaction = (new PhixatorWorkLogQuery()) | ||
->setViewer($viewer) | ||
->withPHIDs([$transactionPHID]) | ||
->requireCapabilities([PhabricatorPolicyCapability::CAN_EDIT]) | ||
->executeOne(); | ||
if(!$transaction) return new Aphront404Response(); | ||
$taskPHID = $transaction->getObjectPHID(); | ||
$params = $transaction->getNewValue(); | ||
} | ||
|
||
// Get the task and check if the viewer can edit the task | ||
$task = (new ManiphestTaskQuery())->setViewer($viewer)->withPHIDs([$taskPHID])->needSubscriberPHIDs(true)->executeOne(); | ||
if(!$task) return new Aphront404Response(); | ||
PhabricatorPolicyFilter::requireCapability($viewer, $task, PhabricatorPolicyCapability::CAN_EDIT); | ||
|
||
// If the form was submitted and is correct: validate it and create/edit the transaction | ||
$formErrors = []; | ||
if($request->isDialogFormPost()) { | ||
// Get fields submitted by the user | ||
$work_time = strtolower(trim($request->getStr('work_time'))); | ||
$params['minutes'] = PhixatorUtil::timeStringToMinutes($work_time); | ||
$params['description'] = trim($request->getStr('description')); | ||
$timestamp = AphrontFormDateControlValue::newFromRequest($request, 'started'); | ||
if(!$timestamp->isValid()) $formErrors[] = pht('Please choose a valid date'); | ||
$params['started'] = $timestamp->getEpoch(); | ||
if(!PhixatorUtil::isTimeStringFormatCorrect($work_time)) $formErrors[] = pht('Work time is incorrect. Allowed format is: 1h 1m'); | ||
if(!$formErrors) { | ||
// Edit time log | ||
if($is_edit) { | ||
$transaction->setNewValue($params)->setOldValue($params['started']); // set oldValue to 'started' date for query ordering purpose | ||
$transaction->save(); | ||
return (new AphrontRedirectResponse())->setURI($return_uri); | ||
} | ||
// Or create new time log | ||
else { | ||
$transaction = (new ManiphestTransaction())->setTransactionType(ManiphestTaskWorkLogTransaction::TRANSACTIONTYPE)->setNewValue($params); | ||
$editor = (new ManiphestTransactionEditor())->setActor($viewer)->setContentSource(PhabricatorContentSource::newFromRequest($request))->setContinueOnNoEffect(true); | ||
$editor->applyTransactions($task, [$transaction]); | ||
return (new AphrontRedirectResponse())->setURI('/'.$task->getMonogram()); | ||
} | ||
} | ||
} | ||
|
||
// If no submitted form or the submitted form was not correct, display the new/edit form | ||
$dialog = $this->newDialog() | ||
->setTitle($is_edit ? pht('Edit Work Time Log') : pht('Log Work Time')) | ||
->setWidth(AphrontDialogView::WIDTH_FORM) | ||
->setErrors($formErrors); | ||
$form = new PHUIFormLayoutView(); | ||
$form->appendChild((new AphrontFormStaticControl()) | ||
->setLabel(pht('Task')) | ||
->setValue($task->getTitle())); | ||
$form->appendChild((new AphrontFormTextControl()) | ||
->setViewer($viewer) | ||
->setName('work_time') | ||
->setLabel(pht('Work Time')) | ||
->setPlaceholder('1h 30m') | ||
->setAutofocus(true) | ||
->setValue(PhixatorUtil::minutesToTimeString($params['minutes']))); | ||
$form->appendChild((new AphrontFormTextAreaControl()) | ||
->setViewer($viewer) | ||
->setName('description') | ||
->setLabel(pht('Work Description')) | ||
->setValue($params['description'])); | ||
$form->appendChild((new AphrontFormDateControl()) | ||
->setViewer($viewer) | ||
->setName('started') | ||
->setLabel(pht('Work Date')) | ||
->setIsTimeDisabled(true) | ||
->setValue($params['started'])); | ||
$dialog->appendChild($form); | ||
$dialog->addHiddenInput('return_to', $return_uri); | ||
$dialog->addCancelButton($return_uri, pht('Close')); | ||
$dialog->addSubmitButton($is_edit ? pht('Edit') : pht('Add')); | ||
return $dialog; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
final class PhixatorWorkLogListController extends PhabricatorController { | ||
|
||
private $queryKey; | ||
|
||
public function shouldAllowPublic() { | ||
return true; | ||
} | ||
|
||
public function willProcessRequest(array $data) { | ||
$this->queryKey = idx($data, 'queryKey'); | ||
} | ||
|
||
public function getQueryKey() { | ||
return $this->queryKey; | ||
} | ||
|
||
public function processRequest() { | ||
$controller = (new PhabricatorApplicationSearchController()) | ||
->setQueryKey($this->queryKey) | ||
->setSearchEngine(new PhixatorWorkLogSearchEngine()) | ||
->setNavigation($this->buildSideNavView()); | ||
|
||
return $this->delegateToController($controller); | ||
} | ||
|
||
protected function buildSideNavView() { | ||
$user = $this->getRequest()->getUser(); | ||
$nav = new AphrontSideNavFilterView(); | ||
$nav->setBaseURI(new PhutilURI($this->getApplicationURI())); | ||
(new PhixatorWorkLogSearchEngine())->setViewer($user)->addNavigationItems($nav->getMenu()); | ||
$nav->selectFilter(null); | ||
return $nav; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php | ||
/** | ||
* Displays the total work time summary in Maniphest tasks curtain | ||
*/ | ||
class PhixatorCurtainExtension extends PHUICurtainExtension { | ||
const EXTENSIONKEY = 'phixator.log_history'; | ||
|
||
public function shouldEnableForObject($object) { | ||
return ($object instanceof ManiphestTask); | ||
} | ||
|
||
public function getExtensionApplication() { | ||
return new PhabricatorPhixatorApplication(); | ||
} | ||
|
||
public function buildCurtainPanel($object) { | ||
// Create the curtain panel and its status view | ||
$panel = $this->newPanel()->setHeaderText(pht('Work Time Summary'))->setOrder(40000); | ||
$status_view = new PHUIStatusListView(); | ||
$panel->appendChild($status_view); | ||
|
||
// Get the transactions corresponding to the task currently displaying the curtain block | ||
$transactions = (new ManiphestTransactionQuery()) | ||
->setViewer($this->getViewer()) | ||
->withObjectPHIDs([$object->getPHID()]) | ||
->withTransactionTypes([ManiphestTaskWorkLogTransaction::TRANSACTIONTYPE]) | ||
->needComments(true) | ||
->execute(); | ||
if(!$transactions) return; | ||
$transactions = array_reverse($transactions); | ||
|
||
// Compute the contributing user list with their corresponding work time | ||
$summarySpendByUser = []; | ||
foreach ($transactions as $transaction) { | ||
$authorPHID = $transaction->getAuthorPHID(); | ||
$minutes = intval($transaction->getNewValue()['minutes'] ?? 0); | ||
if (!isset($summarySpendByUser[$authorPHID])) $summarySpendByUser[$authorPHID] = 0; | ||
$summarySpendByUser[$authorPHID] += $minutes; | ||
} | ||
|
||
// Display all contributing users with their cumulated work times | ||
foreach ($summarySpendByUser as $authorPHID => $spendMinutes) { | ||
$author = (new PhabricatorPeopleQuery())->setViewer($this->getViewer())->withPHIDs([$authorPHID])->executeOne(); | ||
if(!$author) continue; | ||
$authorUrl = (new PhabricatorObjectHandle()) | ||
->setType(phid_get_type($authorPHID)) | ||
->setPHID($authorPHID) | ||
->setName($author->getFullName()) | ||
->setURI('/p/'.$author->getUsername().'/') | ||
->renderLink(); | ||
$item = (new PHUIStatusItemView()) | ||
->setIcon('fa-hourglass-half') | ||
->setTarget(pht('<b>%s</b> by %s', PhixatorUtil::minutesToTimeString($spendMinutes), $authorUrl)); | ||
$status_view->addItem($item); | ||
} | ||
|
||
// Display link to get the work logs list for this task | ||
$status_view->addItem((new PHUIStatusItemView())->setTarget( | ||
(new PhabricatorObjectHandle())->setName(pht('Show details...'))->setURI('/phixator/query/advanced/?taskIDs='.$object->getMonogram())->renderLink()) | ||
); | ||
|
||
return $panel; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
class PhixatorUIEventListener extends PhabricatorEventListener { | ||
public function register() { | ||
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS); | ||
} | ||
|
||
public function handleEvent(PhutilEvent $event) { | ||
switch ($event->getType()) { | ||
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: | ||
$this->handleActionEvent($event); | ||
break; | ||
} | ||
} | ||
|
||
private function handleActionEvent(PhabricatorEvent $event) { | ||
$viewer = $event->getUser(); | ||
$object = $event->getValue('object'); | ||
|
||
if (!$object || !$object->getPHID()) return; | ||
if (!($object instanceof ManiphestTask)) return; | ||
if (!$this->canUseApplication($event->getUser())) return; | ||
|
||
$actionView = (new PhabricatorActionView()) | ||
->setName(pht('Log Work Time')) | ||
->setIcon('fa-hourglass-half') | ||
->setWorkflow(true) | ||
->setHref('/phixator/log/' . $object->getPHID() . '/'); | ||
|
||
$can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $object, PhabricatorPolicyCapability::CAN_EDIT); | ||
if (!$viewer->isLoggedIn() || !$can_edit) { | ||
$actionView->setDisabled(true); | ||
} | ||
|
||
$this->addActionMenuItems($event, $actionView); | ||
} | ||
} |
Oops, something went wrong.