2012-02-08 09:47:14 -08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
final class ManiphestReportController extends ManiphestController {
|
|
|
|
|
|
|
|
|
|
private $view;
|
|
|
|
|
|
2015-08-01 17:06:57 -07:00
|
|
|
public function handleRequest(AphrontRequest $request) {
|
|
|
|
|
$viewer = $this->getViewer();
|
|
|
|
|
$this->view = $request->getURIData('view');
|
2012-02-08 09:47:14 -08:00
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
if ($request->isFormPost()) {
|
|
|
|
|
$uri = $request->getRequestURI();
|
|
|
|
|
|
|
|
|
|
$project = head($request->getArr('set_project'));
|
2012-03-21 16:58:52 -07:00
|
|
|
$project = nonempty($project, null);
|
2012-03-01 14:19:11 -08:00
|
|
|
$uri = $uri->alter('project', $project);
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
$window = $request->getStr('set_window');
|
|
|
|
|
$uri = $uri->alter('window', $window);
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-08 09:47:14 -08:00
|
|
|
$nav = new AphrontSideNavFilterView();
|
|
|
|
|
$nav->setBaseURI(new PhutilURI('/maniphest/report/'));
|
2013-03-12 23:30:03 -07:00
|
|
|
$nav->addLabel(pht('Open Tasks'));
|
|
|
|
|
$nav->addFilter('user', pht('By User'));
|
|
|
|
|
$nav->addFilter('project', pht('By Project'));
|
|
|
|
|
$nav->addLabel(pht('Burnup'));
|
|
|
|
|
$nav->addFilter('burn', pht('Burnup Rate'));
|
2012-02-08 09:47:14 -08:00
|
|
|
|
|
|
|
|
$this->view = $nav->selectFilter($this->view, 'user');
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
require_celerity_resource('maniphest-report-css');
|
|
|
|
|
|
|
|
|
|
switch ($this->view) {
|
|
|
|
|
case 'burn':
|
|
|
|
|
$core = $this->renderBurn();
|
|
|
|
|
break;
|
|
|
|
|
case 'user':
|
|
|
|
|
case 'project':
|
|
|
|
|
$core = $this->renderOpenTasks();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return new Aphront404Response();
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-04 20:34:23 -07:00
|
|
|
$crumbs = $this->buildApplicationCrumbs()
|
|
|
|
|
->addTextCrumb(pht('Reports'));
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
$nav->appendChild($core);
|
2016-04-04 20:34:23 -07:00
|
|
|
$title = pht('Maniphest Reports');
|
|
|
|
|
|
|
|
|
|
return $this->newPage()
|
|
|
|
|
->setTitle($title)
|
|
|
|
|
->setCrumbs($crumbs)
|
|
|
|
|
->setNavigation($nav);
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function renderBurn() {
|
|
|
|
|
$request = $this->getRequest();
|
2015-08-01 17:06:57 -07:00
|
|
|
$viewer = $request->getUser();
|
2012-03-01 14:19:11 -08:00
|
|
|
|
|
|
|
|
$handle = null;
|
|
|
|
|
|
|
|
|
|
$project_phid = $request->getStr('project');
|
|
|
|
|
if ($project_phid) {
|
|
|
|
|
$phids = array($project_phid);
|
2012-09-04 19:02:56 -07:00
|
|
|
$handles = $this->loadViewerHandles($phids);
|
2012-03-01 14:19:11 -08:00
|
|
|
$handle = $handles[$project_phid];
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-24 10:49:06 -07:00
|
|
|
$table = new ManiphestTransaction();
|
2012-03-01 14:19:11 -08:00
|
|
|
$conn = $table->establishConnection('r');
|
|
|
|
|
|
|
|
|
|
$joins = '';
|
|
|
|
|
if ($project_phid) {
|
|
|
|
|
$joins = qsprintf(
|
|
|
|
|
$conn,
|
2013-09-23 14:31:58 -07:00
|
|
|
'JOIN %T t ON x.objectPHID = t.phid
|
2014-07-17 15:42:30 -07:00
|
|
|
JOIN %T p ON p.src = t.phid AND p.type = %d AND p.dst = %s',
|
2012-03-01 14:19:11 -08:00
|
|
|
id(new ManiphestTask())->getTableName(),
|
2014-07-17 15:42:30 -07:00
|
|
|
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
|
|
|
|
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
2012-03-01 14:19:11 -08:00
|
|
|
$project_phid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = queryfx_all(
|
|
|
|
|
$conn,
|
Correct an issue where Maniphest's awful legacy "reports" UI was extra broken on merges
Summary:
See PHI66. See that issue for context. This UI is bad broken legacy junk, but was especially broken when reporting merges.
These do not currently generate a "status" transaction, so they were never counted as task closures. Pretend they're normal closures.
This is still wrong, but should be much closer to the real numbers. Specifically, if you merge a closed task into another task, it will incorrectly be counted as an extra close. This could result in negative tasks, but the numbers should be much closer to reality than they are today even so.
The "Facts" application (T1562) is the real pathway forward here in the longer term.
Test Plan:
- Moved my `maniphest_transactions` table aside with `RENAME TABLE ...`.
- Created a new empty table with `CREATE TABLE ... LIKE ...`.
- Reloaded reports UI, saw empty chart.
- Created, closed, and reopened tasks while reloading the chart, saw accurate reporting.
- Merged an open task into another task, saw bad reporting.
- Applied patch, saw the right chart again.
Reviewers: amckinley
Reviewed By: amckinley
Differential Revision: https://secure.phabricator.com/D18601
2017-09-14 04:23:53 -07:00
|
|
|
'SELECT x.transactionType, x.oldValue, x.newValue, x.dateCreated
|
|
|
|
|
FROM %T x %Q
|
|
|
|
|
WHERE transactionType IN (%Ls)
|
2012-03-01 14:19:11 -08:00
|
|
|
ORDER BY x.dateCreated ASC',
|
|
|
|
|
$table->getTableName(),
|
|
|
|
|
$joins,
|
Correct an issue where Maniphest's awful legacy "reports" UI was extra broken on merges
Summary:
See PHI66. See that issue for context. This UI is bad broken legacy junk, but was especially broken when reporting merges.
These do not currently generate a "status" transaction, so they were never counted as task closures. Pretend they're normal closures.
This is still wrong, but should be much closer to the real numbers. Specifically, if you merge a closed task into another task, it will incorrectly be counted as an extra close. This could result in negative tasks, but the numbers should be much closer to reality than they are today even so.
The "Facts" application (T1562) is the real pathway forward here in the longer term.
Test Plan:
- Moved my `maniphest_transactions` table aside with `RENAME TABLE ...`.
- Created a new empty table with `CREATE TABLE ... LIKE ...`.
- Reloaded reports UI, saw empty chart.
- Created, closed, and reopened tasks while reloading the chart, saw accurate reporting.
- Merged an open task into another task, saw bad reporting.
- Applied patch, saw the right chart again.
Reviewers: amckinley
Reviewed By: amckinley
Differential Revision: https://secure.phabricator.com/D18601
2017-09-14 04:23:53 -07:00
|
|
|
array(
|
|
|
|
|
ManiphestTaskStatusTransaction::TRANSACTIONTYPE,
|
|
|
|
|
ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE,
|
|
|
|
|
));
|
2012-03-01 14:19:11 -08:00
|
|
|
|
Pile more atrocities onto the Maniphest burnup report
Summary:
See PHI273. Ref T13020. After D18777, tasks created directly into the default status (which is common) via the web UI no longer write a "status" transaction.
This is consistent with other applications, and consistent with the API/email behavior for tasks since early 2016. It also improves the consistency of //reading// tasks via the API.
However, it impacted the "Burnup Report" which relies on directly reading these rows to detect task creation. Until this is fixed properly (T1562), synthetically generate the "missing" transactions which this page expects by looking at task creation dates instead.
Specifically, we:
- Generate a fake `status: null -> "open"` transaction for every task by looking at the Task table.
- Go through the transaction list and remove all the legacy `status: null -> "any open status"` transactions. These will only exist for older tasks.
- Merge all our new fake transactions into the list of transactions.
- Continue on as though nothing happened, letting the rendering code continue to operate on legacy-looking data.
I think this will slightly miscount tasks which were created directly into a closed status, but this is very rare, and does not significantly impact the accuracy of this report relative to other known issues (notably, merging closed tasks).
This will also get the wrong result if the default status has changed from an "open" status to a "closed" status at any point, but this is exceptionally bizarre/rare.
Ultimately, T1562 will let us delete all this stuff and disavow its existence.
Test Plan:
- Created some tasks, loaded burnup before/after this patch.
- My local chart looks more accurate afterwards, but the data is super weird (I used `bin/lipsum` to create a huge number of tasks a couple months ago). I'll vet this on `secure`, which has more reasonable data.
Here's my local chart:
{F5356499}
That's what it //should// look like, it's just hard to be confident that nothing else is hiding there.
Reviewers: amckinley
Reviewed By: amckinley
Maniphest Tasks: T13020
Differential Revision: https://secure.phabricator.com/D18853
2018-01-04 09:34:40 -08:00
|
|
|
// See PHI273. After the move to EditEngine, we no longer create a
|
|
|
|
|
// "status" transaction if a task is created directly into the default
|
|
|
|
|
// status. This likely impacted API/email tasks after 2016 and all other
|
|
|
|
|
// tasks after late 2017. Until Facts can fix this properly, use the
|
|
|
|
|
// task creation dates to generate synthetic transactions which look like
|
|
|
|
|
// the older transactions that this page expects.
|
|
|
|
|
|
|
|
|
|
$default_status = ManiphestTaskStatus::getDefaultStatus();
|
|
|
|
|
$duplicate_status = ManiphestTaskStatus::getDuplicateStatus();
|
|
|
|
|
|
|
|
|
|
// Build synthetic transactions which take status from `null` to the
|
|
|
|
|
// default value.
|
|
|
|
|
$create_rows = queryfx_all(
|
|
|
|
|
$conn,
|
|
|
|
|
'SELECT dateCreated FROM %T',
|
|
|
|
|
id(new ManiphestTask())->getTableName());
|
|
|
|
|
foreach ($create_rows as $key => $create_row) {
|
|
|
|
|
$create_rows[$key] = array(
|
|
|
|
|
'transactionType' => 'status',
|
|
|
|
|
'oldValue' => null,
|
|
|
|
|
'newValue' => $default_status,
|
|
|
|
|
'dateCreated' => $create_row['dateCreated'],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove any actual legacy status transactions which take status from
|
|
|
|
|
// `null` to any open status.
|
|
|
|
|
foreach ($data as $key => $row) {
|
|
|
|
|
if ($row['transactionType'] != 'status') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$oldv = trim($row['oldValue'], '"');
|
|
|
|
|
$newv = trim($row['oldValue'], '"');
|
|
|
|
|
|
|
|
|
|
// If this is a status change, preserve it.
|
|
|
|
|
if ($oldv != 'null') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this task was created directly into a closed status, preserve
|
|
|
|
|
// the transaction.
|
|
|
|
|
if (!ManiphestTaskStatus::isOpenStatus($newv)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this is a legacy "create" transaction, discard it in favor of the
|
|
|
|
|
// synthetic one.
|
|
|
|
|
unset($data[$key]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Merge the synthetic rows into the real transactions.
|
|
|
|
|
$data = array_merge($create_rows, $data);
|
|
|
|
|
$data = array_values($data);
|
2018-01-04 10:18:45 -08:00
|
|
|
$data = isort($data, 'dateCreated');
|
Pile more atrocities onto the Maniphest burnup report
Summary:
See PHI273. Ref T13020. After D18777, tasks created directly into the default status (which is common) via the web UI no longer write a "status" transaction.
This is consistent with other applications, and consistent with the API/email behavior for tasks since early 2016. It also improves the consistency of //reading// tasks via the API.
However, it impacted the "Burnup Report" which relies on directly reading these rows to detect task creation. Until this is fixed properly (T1562), synthetically generate the "missing" transactions which this page expects by looking at task creation dates instead.
Specifically, we:
- Generate a fake `status: null -> "open"` transaction for every task by looking at the Task table.
- Go through the transaction list and remove all the legacy `status: null -> "any open status"` transactions. These will only exist for older tasks.
- Merge all our new fake transactions into the list of transactions.
- Continue on as though nothing happened, letting the rendering code continue to operate on legacy-looking data.
I think this will slightly miscount tasks which were created directly into a closed status, but this is very rare, and does not significantly impact the accuracy of this report relative to other known issues (notably, merging closed tasks).
This will also get the wrong result if the default status has changed from an "open" status to a "closed" status at any point, but this is exceptionally bizarre/rare.
Ultimately, T1562 will let us delete all this stuff and disavow its existence.
Test Plan:
- Created some tasks, loaded burnup before/after this patch.
- My local chart looks more accurate afterwards, but the data is super weird (I used `bin/lipsum` to create a huge number of tasks a couple months ago). I'll vet this on `secure`, which has more reasonable data.
Here's my local chart:
{F5356499}
That's what it //should// look like, it's just hard to be confident that nothing else is hiding there.
Reviewers: amckinley
Reviewed By: amckinley
Maniphest Tasks: T13020
Differential Revision: https://secure.phabricator.com/D18853
2018-01-04 09:34:40 -08:00
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
$stats = array();
|
|
|
|
|
$day_buckets = array();
|
|
|
|
|
|
2012-03-19 19:19:28 -07:00
|
|
|
$open_tasks = array();
|
|
|
|
|
|
|
|
|
|
foreach ($data as $key => $row) {
|
Correct an issue where Maniphest's awful legacy "reports" UI was extra broken on merges
Summary:
See PHI66. See that issue for context. This UI is bad broken legacy junk, but was especially broken when reporting merges.
These do not currently generate a "status" transaction, so they were never counted as task closures. Pretend they're normal closures.
This is still wrong, but should be much closer to the real numbers. Specifically, if you merge a closed task into another task, it will incorrectly be counted as an extra close. This could result in negative tasks, but the numbers should be much closer to reality than they are today even so.
The "Facts" application (T1562) is the real pathway forward here in the longer term.
Test Plan:
- Moved my `maniphest_transactions` table aside with `RENAME TABLE ...`.
- Created a new empty table with `CREATE TABLE ... LIKE ...`.
- Reloaded reports UI, saw empty chart.
- Created, closed, and reopened tasks while reloading the chart, saw accurate reporting.
- Merged an open task into another task, saw bad reporting.
- Applied patch, saw the right chart again.
Reviewers: amckinley
Reviewed By: amckinley
Differential Revision: https://secure.phabricator.com/D18601
2017-09-14 04:23:53 -07:00
|
|
|
switch ($row['transactionType']) {
|
|
|
|
|
case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
|
|
|
|
|
// NOTE: Hack to avoid json_decode().
|
|
|
|
|
$oldv = trim($row['oldValue'], '"');
|
|
|
|
|
$newv = trim($row['newValue'], '"');
|
|
|
|
|
break;
|
|
|
|
|
case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE:
|
|
|
|
|
// NOTE: Merging a task does not generate a "status" transaction.
|
|
|
|
|
// We pretend it did. Note that this is not always accurate: it is
|
2017-10-09 10:48:01 -07:00
|
|
|
// possible to merge a task which was previously closed, but this
|
Correct an issue where Maniphest's awful legacy "reports" UI was extra broken on merges
Summary:
See PHI66. See that issue for context. This UI is bad broken legacy junk, but was especially broken when reporting merges.
These do not currently generate a "status" transaction, so they were never counted as task closures. Pretend they're normal closures.
This is still wrong, but should be much closer to the real numbers. Specifically, if you merge a closed task into another task, it will incorrectly be counted as an extra close. This could result in negative tasks, but the numbers should be much closer to reality than they are today even so.
The "Facts" application (T1562) is the real pathway forward here in the longer term.
Test Plan:
- Moved my `maniphest_transactions` table aside with `RENAME TABLE ...`.
- Created a new empty table with `CREATE TABLE ... LIKE ...`.
- Reloaded reports UI, saw empty chart.
- Created, closed, and reopened tasks while reloading the chart, saw accurate reporting.
- Merged an open task into another task, saw bad reporting.
- Applied patch, saw the right chart again.
Reviewers: amckinley
Reviewed By: amckinley
Differential Revision: https://secure.phabricator.com/D18601
2017-09-14 04:23:53 -07:00
|
|
|
// fake transaction always counts a merge as a closure.
|
|
|
|
|
$oldv = $default_status;
|
|
|
|
|
$newv = $duplicate_status;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2012-03-19 19:19:28 -07:00
|
|
|
|
2014-03-25 13:49:33 -07:00
|
|
|
if ($oldv == 'null') {
|
|
|
|
|
$old_is_open = false;
|
|
|
|
|
} else {
|
|
|
|
|
$old_is_open = ManiphestTaskStatus::isOpenStatus($oldv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$new_is_open = ManiphestTaskStatus::isOpenStatus($newv);
|
2012-03-19 19:19:28 -07:00
|
|
|
|
|
|
|
|
$is_open = ($new_is_open && !$old_is_open);
|
|
|
|
|
$is_close = ($old_is_open && !$new_is_open);
|
|
|
|
|
|
|
|
|
|
$data[$key]['_is_open'] = $is_open;
|
|
|
|
|
$data[$key]['_is_close'] = $is_close;
|
|
|
|
|
|
|
|
|
|
if (!$is_open && !$is_close) {
|
|
|
|
|
// This is either some kind of bogus event, or a resolution change
|
|
|
|
|
// (e.g., resolved -> invalid). Just skip it.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-02 14:45:07 -07:00
|
|
|
$day_bucket = phabricator_format_local_time(
|
2012-03-01 14:19:11 -08:00
|
|
|
$row['dateCreated'],
|
2015-08-01 17:06:57 -07:00
|
|
|
$viewer,
|
2012-04-08 16:33:51 -07:00
|
|
|
'Yz');
|
2012-03-01 14:19:11 -08:00
|
|
|
$day_buckets[$day_bucket] = $row['dateCreated'];
|
|
|
|
|
if (empty($stats[$day_bucket])) {
|
|
|
|
|
$stats[$day_bucket] = array(
|
|
|
|
|
'open' => 0,
|
|
|
|
|
'close' => 0,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
$stats[$day_bucket][$is_close ? 'close' : 'open']++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$template = array(
|
|
|
|
|
'open' => 0,
|
|
|
|
|
'close' => 0,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$rows = array();
|
|
|
|
|
$rowc = array();
|
|
|
|
|
$last_month = null;
|
|
|
|
|
$last_month_epoch = null;
|
|
|
|
|
$last_week = null;
|
|
|
|
|
$last_week_epoch = null;
|
|
|
|
|
$week = null;
|
|
|
|
|
$month = null;
|
|
|
|
|
|
Use head_key() and last_key() to explicitly communicate intent
Summary:
PHP arrays have an internal "current position" marker. (I think because foreach() wasn't introduced until PHP 4 and there was no way to get rid of it by then?)
A few functions affect the position of the marker, like reset(), end(), each(), next(), and prev(). A few functions read the position of the marker, like each(), next(), prev(), current() and key().
For the most part, no one uses any of this because foreach() is vastly easier and more natural. However, we sometimes want to select the first or last key from an array. Since key() returns the key //at the current position//, and you can't guarantee that no one will introduce some next() calls somewhere, the right way to do this is reset() + key(). This is cumbesome, so we introduced head_key() and last_key() (like head() and last()) in D2161.
Switch all the reset()/end() + key() (or omitted reset() since I was feeling like taking risks + key()) calls to head_key() or last_key().
Test Plan: Verified most of these by visiting the affected pages.
Reviewers: btrahan, vrana, jungejason, Koolvin
Reviewed By: jungejason
CC: aran
Differential Revision: https://secure.phabricator.com/D2169
2012-04-09 11:08:59 -07:00
|
|
|
$last = last_key($stats) - 1;
|
2012-03-01 14:19:11 -08:00
|
|
|
$period = $template;
|
|
|
|
|
|
|
|
|
|
foreach ($stats as $bucket => $info) {
|
|
|
|
|
$epoch = $day_buckets[$bucket];
|
|
|
|
|
|
2012-04-02 14:45:07 -07:00
|
|
|
$week_bucket = phabricator_format_local_time(
|
2012-03-01 14:19:11 -08:00
|
|
|
$epoch,
|
2015-08-01 17:06:57 -07:00
|
|
|
$viewer,
|
Use head_key() and last_key() to explicitly communicate intent
Summary:
PHP arrays have an internal "current position" marker. (I think because foreach() wasn't introduced until PHP 4 and there was no way to get rid of it by then?)
A few functions affect the position of the marker, like reset(), end(), each(), next(), and prev(). A few functions read the position of the marker, like each(), next(), prev(), current() and key().
For the most part, no one uses any of this because foreach() is vastly easier and more natural. However, we sometimes want to select the first or last key from an array. Since key() returns the key //at the current position//, and you can't guarantee that no one will introduce some next() calls somewhere, the right way to do this is reset() + key(). This is cumbesome, so we introduced head_key() and last_key() (like head() and last()) in D2161.
Switch all the reset()/end() + key() (or omitted reset() since I was feeling like taking risks + key()) calls to head_key() or last_key().
Test Plan: Verified most of these by visiting the affected pages.
Reviewers: btrahan, vrana, jungejason, Koolvin
Reviewed By: jungejason
CC: aran
Differential Revision: https://secure.phabricator.com/D2169
2012-04-09 11:08:59 -07:00
|
|
|
'YW');
|
2012-03-01 14:19:11 -08:00
|
|
|
if ($week_bucket != $last_week) {
|
|
|
|
|
if ($week) {
|
|
|
|
|
$rows[] = $this->formatBurnRow(
|
2015-08-01 17:06:57 -07:00
|
|
|
pht('Week of %s', phabricator_date($last_week_epoch, $viewer)),
|
2012-03-01 14:19:11 -08:00
|
|
|
$week);
|
|
|
|
|
$rowc[] = 'week';
|
|
|
|
|
}
|
|
|
|
|
$week = $template;
|
|
|
|
|
$last_week = $week_bucket;
|
|
|
|
|
$last_week_epoch = $epoch;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-02 14:45:07 -07:00
|
|
|
$month_bucket = phabricator_format_local_time(
|
2012-03-01 14:19:11 -08:00
|
|
|
$epoch,
|
2015-08-01 17:06:57 -07:00
|
|
|
$viewer,
|
Use head_key() and last_key() to explicitly communicate intent
Summary:
PHP arrays have an internal "current position" marker. (I think because foreach() wasn't introduced until PHP 4 and there was no way to get rid of it by then?)
A few functions affect the position of the marker, like reset(), end(), each(), next(), and prev(). A few functions read the position of the marker, like each(), next(), prev(), current() and key().
For the most part, no one uses any of this because foreach() is vastly easier and more natural. However, we sometimes want to select the first or last key from an array. Since key() returns the key //at the current position//, and you can't guarantee that no one will introduce some next() calls somewhere, the right way to do this is reset() + key(). This is cumbesome, so we introduced head_key() and last_key() (like head() and last()) in D2161.
Switch all the reset()/end() + key() (or omitted reset() since I was feeling like taking risks + key()) calls to head_key() or last_key().
Test Plan: Verified most of these by visiting the affected pages.
Reviewers: btrahan, vrana, jungejason, Koolvin
Reviewed By: jungejason
CC: aran
Differential Revision: https://secure.phabricator.com/D2169
2012-04-09 11:08:59 -07:00
|
|
|
'Ym');
|
2012-03-01 14:19:11 -08:00
|
|
|
if ($month_bucket != $last_month) {
|
|
|
|
|
if ($month) {
|
|
|
|
|
$rows[] = $this->formatBurnRow(
|
2015-08-01 17:06:57 -07:00
|
|
|
phabricator_format_local_time($last_month_epoch, $viewer, 'F, Y'),
|
2012-03-01 14:19:11 -08:00
|
|
|
$month);
|
|
|
|
|
$rowc[] = 'month';
|
|
|
|
|
}
|
|
|
|
|
$month = $template;
|
|
|
|
|
$last_month = $month_bucket;
|
|
|
|
|
$last_month_epoch = $epoch;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-01 17:06:57 -07:00
|
|
|
$rows[] = $this->formatBurnRow(phabricator_date($epoch, $viewer), $info);
|
2012-03-01 14:19:11 -08:00
|
|
|
$rowc[] = null;
|
|
|
|
|
$week['open'] += $info['open'];
|
|
|
|
|
$week['close'] += $info['close'];
|
|
|
|
|
$month['open'] += $info['open'];
|
|
|
|
|
$month['close'] += $info['close'];
|
|
|
|
|
$period['open'] += $info['open'];
|
|
|
|
|
$period['close'] += $info['close'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($week) {
|
|
|
|
|
$rows[] = $this->formatBurnRow(
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Week To Date'),
|
2012-03-01 14:19:11 -08:00
|
|
|
$week);
|
|
|
|
|
$rowc[] = 'week';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($month) {
|
|
|
|
|
$rows[] = $this->formatBurnRow(
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Month To Date'),
|
2012-03-01 14:19:11 -08:00
|
|
|
$month);
|
|
|
|
|
$rowc[] = 'month';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rows[] = $this->formatBurnRow(
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('All Time'),
|
2012-03-01 14:19:11 -08:00
|
|
|
$period);
|
|
|
|
|
$rowc[] = 'aggregate';
|
|
|
|
|
|
|
|
|
|
$rows = array_reverse($rows);
|
|
|
|
|
$rowc = array_reverse($rowc);
|
|
|
|
|
|
|
|
|
|
$table = new AphrontTableView($rows);
|
|
|
|
|
$table->setRowClasses($rowc);
|
|
|
|
|
$table->setHeaders(
|
|
|
|
|
array(
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Period'),
|
|
|
|
|
pht('Opened'),
|
|
|
|
|
pht('Closed'),
|
|
|
|
|
pht('Change'),
|
2012-03-01 14:19:11 -08:00
|
|
|
));
|
|
|
|
|
$table->setColumnClasses(
|
|
|
|
|
array(
|
|
|
|
|
'right wide',
|
|
|
|
|
'n',
|
|
|
|
|
'n',
|
|
|
|
|
'n',
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
if ($handle) {
|
2013-03-12 23:30:03 -07:00
|
|
|
$inst = pht(
|
2014-06-09 11:36:49 -07:00
|
|
|
'NOTE: This table reflects tasks currently in '.
|
|
|
|
|
'the project. If a task was opened in the past but added to '.
|
|
|
|
|
'the project recently, it is counted on the day it was '.
|
|
|
|
|
'opened, not the day it was categorized. If a task was part '.
|
|
|
|
|
'of this project in the past but no longer is, it is not '.
|
|
|
|
|
'counted at all.');
|
|
|
|
|
$header = pht('Task Burn Rate for Project %s', $handle->renderLink());
|
2013-11-11 09:23:23 -08:00
|
|
|
$caption = phutil_tag('p', array(), $inst);
|
2012-03-01 14:19:11 -08:00
|
|
|
} else {
|
2014-06-09 11:36:49 -07:00
|
|
|
$header = pht('Task Burn Rate for All Tasks');
|
2012-03-01 14:19:11 -08:00
|
|
|
$caption = null;
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-05 11:56:28 -07:00
|
|
|
if ($caption) {
|
2015-03-01 14:45:56 -08:00
|
|
|
$caption = id(new PHUIInfoView())
|
2014-06-05 11:56:28 -07:00
|
|
|
->appendChild($caption)
|
2015-03-01 14:45:56 -08:00
|
|
|
->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
|
2014-06-05 11:56:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$panel = new PHUIObjectBoxView();
|
|
|
|
|
$panel->setHeaderText($header);
|
|
|
|
|
if ($caption) {
|
2015-03-06 17:03:18 -08:00
|
|
|
$panel->setInfoView($caption);
|
2014-06-05 11:56:28 -07:00
|
|
|
}
|
[Redesign] Add Table, Collapse support to ObjectBox
Summary: Converts most all tables to be directly set via `setTable` to an ObjectBox. I think this path is more flexible design wise, as we can change the box based on children, and not just CSS. We also already do this with PropertyList, Forms, ObjectList, and Header. `setCollapsed` is added to ObjectBox to all children objects to bleed to the edges (like diffs).
Test Plan: I did a grep of `appendChild($table)` as well as searches for `PHUIObjectBoxView`, also with manual opening of hundreds of files. I'm sure I missed 5-8 places. If you just appendChild($table) nothing breaks, it just looks a little funny.
Reviewers: epriestley, btrahan
Subscribers: Korvin, epriestley
Differential Revision: https://secure.phabricator.com/D12955
2015-05-20 12:43:34 -07:00
|
|
|
$panel->setTable($table);
|
2012-03-01 14:19:11 -08:00
|
|
|
|
|
|
|
|
$tokens = array();
|
|
|
|
|
if ($handle) {
|
2013-10-07 12:51:24 -07:00
|
|
|
$tokens = array($handle);
|
2012-03-01 14:19:11 -08:00
|
|
|
}
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
$filter = $this->renderReportFilters($tokens, $has_window = false);
|
2012-03-01 14:19:11 -08:00
|
|
|
|
|
|
|
|
$id = celerity_generate_unique_node_id();
|
2013-01-17 18:57:09 -08:00
|
|
|
$chart = phutil_tag(
|
2012-03-01 14:19:11 -08:00
|
|
|
'div',
|
|
|
|
|
array(
|
|
|
|
|
'id' => $id,
|
2014-06-05 11:56:28 -07:00
|
|
|
'style' => 'border: 1px solid #BFCFDA; '.
|
|
|
|
|
'background-color: #fff; '.
|
|
|
|
|
'margin: 8px 16px; '.
|
2012-03-01 14:19:11 -08:00
|
|
|
'height: 400px; ',
|
|
|
|
|
),
|
|
|
|
|
'');
|
|
|
|
|
|
2012-03-19 19:19:28 -07:00
|
|
|
list($burn_x, $burn_y) = $this->buildSeries($data);
|
2012-03-01 14:19:11 -08:00
|
|
|
|
2016-01-28 13:29:27 -08:00
|
|
|
require_celerity_resource('d3');
|
|
|
|
|
require_celerity_resource('phui-chart-css');
|
2012-03-01 14:19:11 -08:00
|
|
|
|
2012-07-30 10:44:08 -07:00
|
|
|
Javelin::initBehavior('line-chart', array(
|
2012-03-01 14:19:11 -08:00
|
|
|
'hardpoint' => $id,
|
|
|
|
|
'x' => array(
|
2012-03-19 19:19:28 -07:00
|
|
|
$burn_x,
|
2012-03-01 14:19:11 -08:00
|
|
|
),
|
|
|
|
|
'y' => array(
|
2012-03-19 19:19:28 -07:00
|
|
|
$burn_y,
|
2012-03-01 14:19:11 -08:00
|
|
|
),
|
2012-07-30 10:44:08 -07:00
|
|
|
'xformat' => 'epoch',
|
2014-01-27 13:48:23 -08:00
|
|
|
'yformat' => 'int',
|
2012-03-01 14:19:11 -08:00
|
|
|
));
|
|
|
|
|
|
2016-01-28 13:29:27 -08:00
|
|
|
$box = id(new PHUIObjectBoxView())
|
|
|
|
|
->setHeaderText(pht('Burnup Rate'))
|
|
|
|
|
->appendChild($chart);
|
|
|
|
|
|
|
|
|
|
return array($filter, $box, $panel);
|
2012-03-01 14:19:11 -08:00
|
|
|
}
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
private function renderReportFilters(array $tokens, $has_window) {
|
|
|
|
|
$request = $this->getRequest();
|
2015-08-01 17:06:57 -07:00
|
|
|
$viewer = $request->getUser();
|
2012-03-21 16:58:52 -07:00
|
|
|
|
|
|
|
|
$form = id(new AphrontFormView())
|
2015-08-01 17:06:57 -07:00
|
|
|
->setUser($viewer)
|
2015-03-31 14:10:55 -07:00
|
|
|
->appendControl(
|
2012-03-21 16:58:52 -07:00
|
|
|
id(new AphrontFormTokenizerControl())
|
2014-07-17 15:48:50 -07:00
|
|
|
->setDatasource(new PhabricatorProjectDatasource())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Project'))
|
2012-03-21 16:58:52 -07:00
|
|
|
->setLimit(1)
|
|
|
|
|
->setName('set_project')
|
2015-03-31 14:10:55 -07:00
|
|
|
// TODO: This is silly, but this is Maniphest reports.
|
|
|
|
|
->setValue(mpull($tokens, 'getPHID')));
|
2012-03-21 16:58:52 -07:00
|
|
|
|
|
|
|
|
if ($has_window) {
|
|
|
|
|
list($window_str, $ignored, $window_error) = $this->getWindow();
|
|
|
|
|
$form
|
|
|
|
|
->appendChild(
|
|
|
|
|
id(new AphrontFormTextControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Recently Means'))
|
2012-03-21 16:58:52 -07:00
|
|
|
->setName('set_window')
|
|
|
|
|
->setCaption(
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Configure the cutoff for the "Recently Closed" column.'))
|
2012-03-21 16:58:52 -07:00
|
|
|
->setValue($window_str)
|
|
|
|
|
->setError($window_error));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$form
|
|
|
|
|
->appendChild(
|
|
|
|
|
id(new AphrontFormSubmitControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setValue(pht('Filter By Project')));
|
2012-03-21 16:58:52 -07:00
|
|
|
|
|
|
|
|
$filter = new AphrontListFilterView();
|
|
|
|
|
$filter->appendChild($form);
|
|
|
|
|
|
|
|
|
|
return $filter;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
private function buildSeries(array $data) {
|
2012-03-19 19:19:28 -07:00
|
|
|
$out = array();
|
2012-03-01 14:19:11 -08:00
|
|
|
|
2012-03-19 19:19:28 -07:00
|
|
|
$counter = 0;
|
2012-03-01 14:19:11 -08:00
|
|
|
foreach ($data as $row) {
|
|
|
|
|
$t = (int)$row['dateCreated'];
|
2012-03-19 19:19:28 -07:00
|
|
|
if ($row['_is_close']) {
|
|
|
|
|
--$counter;
|
|
|
|
|
$out[$t] = $counter;
|
|
|
|
|
} else if ($row['_is_open']) {
|
|
|
|
|
++$counter;
|
|
|
|
|
$out[$t] = $counter;
|
2012-03-01 14:19:11 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-19 19:19:28 -07:00
|
|
|
return array(array_keys($out), array_values($out));
|
2012-03-01 14:19:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function formatBurnRow($label, $info) {
|
|
|
|
|
$delta = $info['open'] - $info['close'];
|
|
|
|
|
$fmt = number_format($delta);
|
|
|
|
|
if ($delta > 0) {
|
|
|
|
|
$fmt = '+'.$fmt;
|
2013-11-11 09:23:23 -08:00
|
|
|
$fmt = phutil_tag('span', array('class' => 'red'), $fmt);
|
2012-03-01 14:19:11 -08:00
|
|
|
} else {
|
2013-11-11 09:23:23 -08:00
|
|
|
$fmt = phutil_tag('span', array('class' => 'green'), $fmt);
|
2012-03-01 14:19:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return array(
|
|
|
|
|
$label,
|
|
|
|
|
number_format($info['open']),
|
|
|
|
|
number_format($info['close']),
|
2014-10-08 00:01:04 +11:00
|
|
|
$fmt,
|
|
|
|
|
);
|
2012-03-01 14:19:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function renderOpenTasks() {
|
|
|
|
|
$request = $this->getRequest();
|
2015-08-01 17:06:57 -07:00
|
|
|
$viewer = $request->getUser();
|
2012-03-01 14:19:11 -08:00
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
$query = id(new ManiphestTaskQuery())
|
2015-08-01 17:06:57 -07:00
|
|
|
->setViewer($viewer)
|
2014-02-17 15:59:31 -08:00
|
|
|
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants());
|
2012-03-01 14:19:11 -08:00
|
|
|
|
2014-12-18 13:53:45 -08:00
|
|
|
switch ($this->view) {
|
|
|
|
|
case 'project':
|
|
|
|
|
$query->needProjectPHIDs(true);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
$project_phid = $request->getStr('project');
|
|
|
|
|
$project_handle = null;
|
|
|
|
|
if ($project_phid) {
|
|
|
|
|
$phids = array($project_phid);
|
2012-09-04 19:02:56 -07:00
|
|
|
$handles = $this->loadViewerHandles($phids);
|
2012-03-21 16:58:52 -07:00
|
|
|
$project_handle = $handles[$project_phid];
|
|
|
|
|
|
2015-04-23 04:10:39 -07:00
|
|
|
$query->withEdgeLogicPHIDs(
|
|
|
|
|
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
|
|
|
|
PhabricatorQueryConstraint::OPERATOR_OR,
|
|
|
|
|
$phids);
|
2012-03-21 16:58:52 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
$tasks = $query->execute();
|
2012-02-08 09:47:14 -08:00
|
|
|
|
2012-03-19 19:46:57 -07:00
|
|
|
$recently_closed = $this->loadRecentlyClosedTasks();
|
|
|
|
|
|
2015-08-01 17:06:57 -07:00
|
|
|
$date = phabricator_date(time(), $viewer);
|
2012-02-08 09:47:14 -08:00
|
|
|
|
|
|
|
|
switch ($this->view) {
|
|
|
|
|
case 'user':
|
|
|
|
|
$result = mgroup($tasks, 'getOwnerPHID');
|
|
|
|
|
$leftover = idx($result, '', array());
|
|
|
|
|
unset($result['']);
|
2012-03-19 19:46:57 -07:00
|
|
|
|
|
|
|
|
$result_closed = mgroup($recently_closed, 'getOwnerPHID');
|
|
|
|
|
$leftover_closed = idx($result_closed, '', array());
|
|
|
|
|
unset($result_closed['']);
|
|
|
|
|
|
2013-09-13 16:56:21 -07:00
|
|
|
$base_link = '/maniphest/?assigned=';
|
|
|
|
|
$leftover_name = phutil_tag('em', array(), pht('(Up For Grabs)'));
|
2013-03-12 23:30:03 -07:00
|
|
|
$col_header = pht('User');
|
|
|
|
|
$header = pht('Open Tasks by User and Priority (%s)', $date);
|
2012-02-08 09:47:14 -08:00
|
|
|
break;
|
|
|
|
|
case 'project':
|
|
|
|
|
$result = array();
|
2012-03-19 19:46:57 -07:00
|
|
|
$leftover = array();
|
2012-02-08 09:47:14 -08:00
|
|
|
foreach ($tasks as $task) {
|
|
|
|
|
$phids = $task->getProjectPHIDs();
|
|
|
|
|
if ($phids) {
|
|
|
|
|
foreach ($phids as $project_phid) {
|
|
|
|
|
$result[$project_phid][] = $task;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$leftover[] = $task;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-03-19 19:46:57 -07:00
|
|
|
|
|
|
|
|
$result_closed = array();
|
|
|
|
|
$leftover_closed = array();
|
|
|
|
|
foreach ($recently_closed as $task) {
|
|
|
|
|
$phids = $task->getProjectPHIDs();
|
|
|
|
|
if ($phids) {
|
|
|
|
|
foreach ($phids as $project_phid) {
|
|
|
|
|
$result_closed[$project_phid][] = $task;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$leftover_closed[] = $task;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-26 07:15:25 -07:00
|
|
|
$base_link = '/maniphest/?projects=';
|
2013-09-13 16:56:21 -07:00
|
|
|
$leftover_name = phutil_tag('em', array(), pht('(No Project)'));
|
2013-03-12 23:30:03 -07:00
|
|
|
$col_header = pht('Project');
|
|
|
|
|
$header = pht('Open Tasks by Project and Priority (%s)', $date);
|
2012-02-08 09:47:14 -08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$phids = array_keys($result);
|
2012-09-04 19:02:56 -07:00
|
|
|
$handles = $this->loadViewerHandles($phids);
|
2012-02-08 09:47:14 -08:00
|
|
|
$handles = msort($handles, 'getName');
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
$order = $request->getStr('order', 'name');
|
2012-03-21 16:58:52 -07:00
|
|
|
list($order, $reverse) = AphrontTableView::parseSort($order);
|
2012-03-01 14:19:11 -08:00
|
|
|
|
2012-03-19 19:46:57 -07:00
|
|
|
require_celerity_resource('aphront-tooltip-css');
|
|
|
|
|
Javelin::initBehavior('phabricator-tooltips', array());
|
|
|
|
|
|
2012-02-08 09:47:14 -08:00
|
|
|
$rows = array();
|
|
|
|
|
$pri_total = array();
|
|
|
|
|
foreach (array_merge($handles, array(null)) as $handle) {
|
|
|
|
|
if ($handle) {
|
2012-03-21 16:58:52 -07:00
|
|
|
if (($project_handle) &&
|
|
|
|
|
($project_handle->getPHID() == $handle->getPHID())) {
|
|
|
|
|
// If filtering by, e.g., "bugs", don't show a "bugs" group.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-08 09:47:14 -08:00
|
|
|
$tasks = idx($result, $handle->getPHID(), array());
|
2013-01-17 18:43:35 -08:00
|
|
|
$name = phutil_tag(
|
2012-02-08 09:47:14 -08:00
|
|
|
'a',
|
|
|
|
|
array(
|
2012-03-23 11:08:10 -07:00
|
|
|
'href' => $base_link.$handle->getPHID(),
|
2012-02-08 09:47:14 -08:00
|
|
|
),
|
2013-01-17 18:43:35 -08:00
|
|
|
$handle->getName());
|
2012-03-19 19:46:57 -07:00
|
|
|
$closed = idx($result_closed, $handle->getPHID(), array());
|
2012-02-08 09:47:14 -08:00
|
|
|
} else {
|
|
|
|
|
$tasks = $leftover;
|
|
|
|
|
$name = $leftover_name;
|
2012-03-19 19:46:57 -07:00
|
|
|
$closed = $leftover_closed;
|
2012-02-08 09:47:14 -08:00
|
|
|
}
|
|
|
|
|
|
2012-03-19 19:46:57 -07:00
|
|
|
$taskv = $tasks;
|
2012-02-08 09:47:14 -08:00
|
|
|
$tasks = mgroup($tasks, 'getPriority');
|
|
|
|
|
|
|
|
|
|
$row = array();
|
|
|
|
|
$row[] = $name;
|
|
|
|
|
$total = 0;
|
|
|
|
|
foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $label) {
|
|
|
|
|
$n = count(idx($tasks, $pri, array()));
|
|
|
|
|
if ($n == 0) {
|
|
|
|
|
$row[] = '-';
|
|
|
|
|
} else {
|
|
|
|
|
$row[] = number_format($n);
|
|
|
|
|
}
|
|
|
|
|
$total += $n;
|
|
|
|
|
}
|
|
|
|
|
$row[] = number_format($total);
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
list($link, $oldest_all) = $this->renderOldest($taskv);
|
|
|
|
|
$row[] = $link;
|
2012-03-19 19:46:57 -07:00
|
|
|
|
|
|
|
|
$normal_or_better = array();
|
|
|
|
|
foreach ($taskv as $id => $task) {
|
2013-09-13 11:11:05 -07:00
|
|
|
// TODO: This is sort of a hard-code for the default "normal" status.
|
|
|
|
|
// When reports are more powerful, this should be made more general.
|
|
|
|
|
if ($task->getPriority() < 50) {
|
2012-03-19 19:46:57 -07:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$normal_or_better[$id] = $task;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
list($link, $oldest_pri) = $this->renderOldest($normal_or_better);
|
|
|
|
|
$row[] = $link;
|
2012-03-19 19:46:57 -07:00
|
|
|
|
|
|
|
|
if ($closed) {
|
|
|
|
|
$task_ids = implode(',', mpull($closed, 'getID'));
|
2013-01-17 18:43:35 -08:00
|
|
|
$row[] = phutil_tag(
|
2012-03-19 19:46:57 -07:00
|
|
|
'a',
|
|
|
|
|
array(
|
2013-09-23 14:31:58 -07:00
|
|
|
'href' => '/maniphest/?ids='.$task_ids,
|
2012-03-19 19:46:57 -07:00
|
|
|
'target' => '_blank',
|
|
|
|
|
),
|
2013-01-17 18:43:35 -08:00
|
|
|
number_format(count($closed)));
|
2012-03-19 19:46:57 -07:00
|
|
|
} else {
|
|
|
|
|
$row[] = '-';
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
switch ($order) {
|
|
|
|
|
case 'total':
|
|
|
|
|
$row['sort'] = $total;
|
|
|
|
|
break;
|
2012-03-21 16:58:52 -07:00
|
|
|
case 'oldest-all':
|
|
|
|
|
$row['sort'] = $oldest_all;
|
|
|
|
|
break;
|
|
|
|
|
case 'oldest-pri':
|
|
|
|
|
$row['sort'] = $oldest_pri;
|
|
|
|
|
break;
|
|
|
|
|
case 'closed':
|
|
|
|
|
$row['sort'] = count($closed);
|
|
|
|
|
break;
|
2012-03-01 14:19:11 -08:00
|
|
|
case 'name':
|
|
|
|
|
default:
|
|
|
|
|
$row['sort'] = $handle ? $handle->getName() : '~';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-08 09:47:14 -08:00
|
|
|
$rows[] = $row;
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
$rows = isort($rows, 'sort');
|
|
|
|
|
foreach ($rows as $k => $row) {
|
|
|
|
|
unset($rows[$k]['sort']);
|
|
|
|
|
}
|
2012-03-21 16:58:52 -07:00
|
|
|
if ($reverse) {
|
|
|
|
|
$rows = array_reverse($rows);
|
|
|
|
|
}
|
2012-03-01 14:19:11 -08:00
|
|
|
|
2012-02-08 09:47:14 -08:00
|
|
|
$cname = array($col_header);
|
|
|
|
|
$cclass = array('pri right wide');
|
2013-09-13 11:11:05 -07:00
|
|
|
$pri_map = ManiphestTaskPriority::getShortNameMap();
|
2012-03-19 19:46:57 -07:00
|
|
|
foreach ($pri_map as $pri => $label) {
|
2012-02-08 09:47:14 -08:00
|
|
|
$cname[] = $label;
|
|
|
|
|
$cclass[] = 'n';
|
|
|
|
|
}
|
2015-06-09 23:06:52 +10:00
|
|
|
$cname[] = pht('Total');
|
2012-02-08 09:47:14 -08:00
|
|
|
$cclass[] = 'n';
|
2013-01-25 12:57:17 -08:00
|
|
|
$cname[] = javelin_tag(
|
2012-03-19 19:46:57 -07:00
|
|
|
'span',
|
|
|
|
|
array(
|
|
|
|
|
'sigil' => 'has-tooltip',
|
|
|
|
|
'meta' => array(
|
2013-03-12 23:30:03 -07:00
|
|
|
'tip' => pht('Oldest open task.'),
|
2012-03-19 19:46:57 -07:00
|
|
|
'size' => 200,
|
|
|
|
|
),
|
|
|
|
|
),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Oldest (All)'));
|
2012-03-19 19:46:57 -07:00
|
|
|
$cclass[] = 'n';
|
2013-01-25 12:57:17 -08:00
|
|
|
$cname[] = javelin_tag(
|
2012-03-19 19:46:57 -07:00
|
|
|
'span',
|
|
|
|
|
array(
|
|
|
|
|
'sigil' => 'has-tooltip',
|
|
|
|
|
'meta' => array(
|
2015-05-22 17:27:56 +10:00
|
|
|
'tip' => pht(
|
|
|
|
|
'Oldest open task, excluding those with Low or Wishlist priority.'),
|
2012-03-19 19:46:57 -07:00
|
|
|
'size' => 200,
|
|
|
|
|
),
|
|
|
|
|
),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Oldest (Pri)'));
|
2012-03-19 19:46:57 -07:00
|
|
|
$cclass[] = 'n';
|
2012-03-21 16:58:52 -07:00
|
|
|
|
|
|
|
|
list($ignored, $window_epoch) = $this->getWindow();
|
2015-08-01 17:06:57 -07:00
|
|
|
$edate = phabricator_datetime($window_epoch, $viewer);
|
2013-01-25 12:57:17 -08:00
|
|
|
$cname[] = javelin_tag(
|
2012-03-21 16:58:52 -07:00
|
|
|
'span',
|
|
|
|
|
array(
|
|
|
|
|
'sigil' => 'has-tooltip',
|
|
|
|
|
'meta' => array(
|
2013-03-12 23:30:03 -07:00
|
|
|
'tip' => pht('Closed after %s', $edate),
|
2014-10-08 00:01:04 +11:00
|
|
|
'size' => 260,
|
2012-03-21 16:58:52 -07:00
|
|
|
),
|
|
|
|
|
),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Recently Closed'));
|
2012-03-19 19:46:57 -07:00
|
|
|
$cclass[] = 'n';
|
2012-02-08 09:47:14 -08:00
|
|
|
|
|
|
|
|
$table = new AphrontTableView($rows);
|
|
|
|
|
$table->setHeaders($cname);
|
|
|
|
|
$table->setColumnClasses($cclass);
|
2012-03-21 16:58:52 -07:00
|
|
|
$table->makeSortable(
|
|
|
|
|
$request->getRequestURI(),
|
|
|
|
|
'order',
|
|
|
|
|
$order,
|
|
|
|
|
$reverse,
|
|
|
|
|
array(
|
|
|
|
|
'name',
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
'total',
|
|
|
|
|
'oldest-all',
|
|
|
|
|
'oldest-pri',
|
|
|
|
|
'closed',
|
|
|
|
|
));
|
2012-02-08 09:47:14 -08:00
|
|
|
|
2014-06-05 11:56:28 -07:00
|
|
|
$panel = new PHUIObjectBoxView();
|
|
|
|
|
$panel->setHeaderText($header);
|
[Redesign] Add Table, Collapse support to ObjectBox
Summary: Converts most all tables to be directly set via `setTable` to an ObjectBox. I think this path is more flexible design wise, as we can change the box based on children, and not just CSS. We also already do this with PropertyList, Forms, ObjectList, and Header. `setCollapsed` is added to ObjectBox to all children objects to bleed to the edges (like diffs).
Test Plan: I did a grep of `appendChild($table)` as well as searches for `PHUIObjectBoxView`, also with manual opening of hundreds of files. I'm sure I missed 5-8 places. If you just appendChild($table) nothing breaks, it just looks a little funny.
Reviewers: epriestley, btrahan
Subscribers: Korvin, epriestley
Differential Revision: https://secure.phabricator.com/D12955
2015-05-20 12:43:34 -07:00
|
|
|
$panel->setTable($table);
|
2012-02-08 09:47:14 -08:00
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
$tokens = array();
|
|
|
|
|
if ($project_handle) {
|
2013-10-07 12:51:24 -07:00
|
|
|
$tokens = array($project_handle);
|
2012-03-21 16:58:52 -07:00
|
|
|
}
|
|
|
|
|
$filter = $this->renderReportFilters($tokens, $has_window = true);
|
2012-03-01 14:19:11 -08:00
|
|
|
|
|
|
|
|
return array($filter, $panel);
|
2012-02-08 09:47:14 -08:00
|
|
|
}
|
|
|
|
|
|
2012-03-19 19:46:57 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load all the tasks that have been recently closed.
|
|
|
|
|
*/
|
|
|
|
|
private function loadRecentlyClosedTasks() {
|
2012-03-21 16:58:52 -07:00
|
|
|
list($ignored, $window_epoch) = $this->getWindow();
|
2012-03-19 19:46:57 -07:00
|
|
|
|
|
|
|
|
$table = new ManiphestTask();
|
2013-09-24 10:49:06 -07:00
|
|
|
$xtable = new ManiphestTransaction();
|
2012-03-19 19:46:57 -07:00
|
|
|
$conn_r = $table->establishConnection('r');
|
|
|
|
|
|
2014-03-25 13:49:33 -07:00
|
|
|
// TODO: Gross. This table is not meant to be queried like this. Build
|
|
|
|
|
// real stats tables.
|
|
|
|
|
|
|
|
|
|
$open_status_list = array();
|
|
|
|
|
foreach (ManiphestTaskStatus::getOpenStatusConstants() as $constant) {
|
|
|
|
|
$open_status_list[] = json_encode((string)$constant);
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-08 16:21:53 -07:00
|
|
|
$rows = queryfx_all(
|
2012-03-19 19:46:57 -07:00
|
|
|
$conn_r,
|
2014-08-08 16:21:53 -07:00
|
|
|
'SELECT t.id FROM %T t JOIN %T x ON x.objectPHID = t.phid
|
2014-05-14 16:49:50 -07:00
|
|
|
WHERE t.status NOT IN (%Ls)
|
2014-03-25 13:49:33 -07:00
|
|
|
AND x.oldValue IN (null, %Ls)
|
|
|
|
|
AND x.newValue NOT IN (%Ls)
|
2012-03-19 19:46:57 -07:00
|
|
|
AND t.dateModified >= %d
|
|
|
|
|
AND x.dateCreated >= %d',
|
|
|
|
|
$table->getTableName(),
|
|
|
|
|
$xtable->getTableName(),
|
2014-05-14 16:49:50 -07:00
|
|
|
ManiphestTaskStatus::getOpenStatusConstants(),
|
2014-03-25 13:49:33 -07:00
|
|
|
$open_status_list,
|
|
|
|
|
$open_status_list,
|
2012-03-21 16:58:52 -07:00
|
|
|
$window_epoch,
|
|
|
|
|
$window_epoch);
|
2012-03-19 19:46:57 -07:00
|
|
|
|
2014-08-08 16:21:53 -07:00
|
|
|
if (!$rows) {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ids = ipull($rows, 'id');
|
|
|
|
|
|
2014-12-30 07:00:52 -08:00
|
|
|
$query = id(new ManiphestTaskQuery())
|
2014-08-08 16:21:53 -07:00
|
|
|
->setViewer($this->getRequest()->getUser())
|
2014-12-30 07:00:52 -08:00
|
|
|
->withIDs($ids);
|
|
|
|
|
|
|
|
|
|
switch ($this->view) {
|
|
|
|
|
case 'project':
|
|
|
|
|
$query->needProjectPHIDs(true);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $query->execute();
|
2012-03-19 19:46:57 -07:00
|
|
|
}
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
/**
|
|
|
|
|
* Parse the "Recently Means" filter into:
|
|
|
|
|
*
|
|
|
|
|
* - A string representation, like "12 AM 7 days ago" (default);
|
|
|
|
|
* - a locale-aware epoch representation; and
|
|
|
|
|
* - a possible error.
|
|
|
|
|
*/
|
|
|
|
|
private function getWindow() {
|
|
|
|
|
$request = $this->getRequest();
|
2015-08-01 17:06:57 -07:00
|
|
|
$viewer = $request->getUser();
|
2012-03-21 16:58:52 -07:00
|
|
|
|
|
|
|
|
$window_str = $this->getRequest()->getStr('window', '12 AM 7 days ago');
|
|
|
|
|
|
|
|
|
|
$error = null;
|
|
|
|
|
$window_epoch = null;
|
|
|
|
|
|
|
|
|
|
// Do locale-aware parsing so that the user's timezone is assumed for
|
|
|
|
|
// time windows like "3 PM", rather than assuming the server timezone.
|
|
|
|
|
|
2015-08-01 17:06:57 -07:00
|
|
|
$window_epoch = PhabricatorTime::parseLocalTime($window_str, $viewer);
|
2013-06-03 12:58:11 -07:00
|
|
|
if (!$window_epoch) {
|
2012-03-21 16:58:52 -07:00
|
|
|
$error = 'Invalid';
|
|
|
|
|
$window_epoch = time() - (60 * 60 * 24 * 7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the time ends up in the future, convert it to the corresponding time
|
|
|
|
|
// and equal distance in the past. This is so users can type "6 days" (which
|
|
|
|
|
// means "6 days from now") and get the behavior of "6 days ago", rather
|
|
|
|
|
// than no results (because the window epoch is in the future). This might
|
2017-10-09 10:48:01 -07:00
|
|
|
// be a little confusing because it causes "tomorrow" to mean "yesterday"
|
2012-03-21 16:58:52 -07:00
|
|
|
// and "2022" (or whatever) to mean "ten years ago", but these inputs are
|
|
|
|
|
// nonsense anyway.
|
|
|
|
|
|
|
|
|
|
if ($window_epoch > time()) {
|
|
|
|
|
$window_epoch = time() - ($window_epoch - time());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return array($window_str, $window_epoch, $error);
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-19 19:46:57 -07:00
|
|
|
private function renderOldest(array $tasks) {
|
2012-04-03 12:10:45 -07:00
|
|
|
assert_instances_of($tasks, 'ManiphestTask');
|
2012-03-19 19:46:57 -07:00
|
|
|
$oldest = null;
|
|
|
|
|
foreach ($tasks as $id => $task) {
|
|
|
|
|
if (($oldest === null) ||
|
|
|
|
|
($task->getDateCreated() < $tasks[$oldest]->getDateCreated())) {
|
|
|
|
|
$oldest = $id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($oldest === null) {
|
2012-03-22 13:55:22 -07:00
|
|
|
return array('-', 0);
|
2012-03-19 19:46:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$oldest = $tasks[$oldest];
|
|
|
|
|
|
2012-03-21 16:58:52 -07:00
|
|
|
$raw_age = (time() - $oldest->getDateCreated());
|
|
|
|
|
$age = number_format($raw_age / (24 * 60 * 60)).' d';
|
2012-03-19 19:46:57 -07:00
|
|
|
|
2013-01-25 12:57:17 -08:00
|
|
|
$link = javelin_tag(
|
2012-03-19 19:46:57 -07:00
|
|
|
'a',
|
|
|
|
|
array(
|
|
|
|
|
'href' => '/T'.$oldest->getID(),
|
|
|
|
|
'sigil' => 'has-tooltip',
|
|
|
|
|
'meta' => array(
|
|
|
|
|
'tip' => 'T'.$oldest->getID().': '.$oldest->getTitle(),
|
|
|
|
|
),
|
|
|
|
|
'target' => '_blank',
|
|
|
|
|
),
|
2013-01-25 12:57:17 -08:00
|
|
|
$age);
|
2012-03-21 16:58:52 -07:00
|
|
|
|
|
|
|
|
return array($link, $raw_age);
|
2012-03-19 19:46:57 -07:00
|
|
|
}
|
|
|
|
|
|
2012-02-08 09:47:14 -08:00
|
|
|
}
|