mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-19 03:50:54 +01:00
Various statistics about revisions at /differential/stats/revisions/
Summary: Show some statistics, like number of revisions, number of revisions per week, lines per revision, etc. for phrivolous amusement. Test Plan: - Went to /differential/stats/revisions/ Numbers seem right - Clicked 'Accepted' Again - Changed to another user with long history Load time was not too long though delay noticeable - Clicked 'Requested changes to' User was preserved, looks good Reviewers: epriestley, btrahan Reviewed By: epriestley CC: aran, epriestley Differential Revision: https://secure.phabricator.com/D1643
This commit is contained in:
parent
fc4e23c50f
commit
583cca0d7c
6 changed files with 370 additions and 0 deletions
|
@ -258,6 +258,8 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionListData' => 'applications/differential/data/revisionlist',
|
||||
'DifferentialRevisionListView' => 'applications/differential/view/revisionlist',
|
||||
'DifferentialRevisionQuery' => 'applications/differential/query/revision',
|
||||
'DifferentialRevisionStatsController' => 'applications/differential/controller/revisionstats',
|
||||
'DifferentialRevisionStatsView' => 'applications/differential/view/revisionstats',
|
||||
'DifferentialRevisionStatusFieldSpecification' => 'applications/differential/field/specification/revisionstatus',
|
||||
'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory',
|
||||
'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview',
|
||||
|
@ -1076,6 +1078,8 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialRevisionListController' => 'DifferentialController',
|
||||
'DifferentialRevisionListView' => 'AphrontView',
|
||||
'DifferentialRevisionStatsController' => 'DifferentialController',
|
||||
'DifferentialRevisionStatsView' => 'AphrontView',
|
||||
'DifferentialRevisionStatusFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialRevisionUpdateHistoryView' => 'AphrontView',
|
||||
'DifferentialRevisionViewController' => 'DifferentialController',
|
||||
|
|
|
@ -96,6 +96,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
'/differential/' => array(
|
||||
'$' => 'DifferentialRevisionListController',
|
||||
'filter/(?P<filter>\w+)/$' => 'DifferentialRevisionListController',
|
||||
'stats/(?P<filter>\w+)/$' => 'DifferentialRevisionStatsController',
|
||||
'diff/' => array(
|
||||
'(?P<id>\d+)/$' => 'DifferentialDiffViewController',
|
||||
'create/$' => 'DifferentialDiffCreateController',
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
class DifferentialRevisionStatsController extends DifferentialController {
|
||||
private $filter;
|
||||
|
||||
public function shouldRequireLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private function loadRevisions($phid) {
|
||||
$table = new DifferentialRevision();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
$rows = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT revisions.* FROM %T revisions ' .
|
||||
'JOIN %T comments ON comments.revisionID = revisions.id ' .
|
||||
'JOIN (' .
|
||||
' SELECT revisionID FROM %T WHERE objectPHID = %s ' .
|
||||
' UNION ALL ' .
|
||||
' SELECT id from differential_revision WHERE authorPHID = %s) rel ' .
|
||||
'ON (comments.revisionID = rel.revisionID)' .
|
||||
'WHERE comments.action = %s' .
|
||||
'AND comments.authorPHID = %s',
|
||||
$table->getTableName(),
|
||||
id(new DifferentialComment())->getTableName(),
|
||||
DifferentialRevision::RELATIONSHIP_TABLE,
|
||||
$phid,
|
||||
$phid,
|
||||
$this->filter,
|
||||
$phid
|
||||
);
|
||||
return $table->loadAllFromArray($rows);
|
||||
}
|
||||
|
||||
private function loadComments($phid) {
|
||||
$table = new DifferentialComment();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
$rows = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT comments.* FROM %T comments ' .
|
||||
'JOIN (' .
|
||||
' SELECT revisionID FROM %T WHERE objectPHID = %s ' .
|
||||
' UNION ALL ' .
|
||||
' SELECT id from differential_revision WHERE authorPHID = %s) rel ' .
|
||||
'ON (comments.revisionID = rel.revisionID)' .
|
||||
'WHERE comments.action = %s' .
|
||||
'AND comments.authorPHID = %s',
|
||||
$table->getTableName(),
|
||||
DifferentialRevision::RELATIONSHIP_TABLE,
|
||||
$phid,
|
||||
$phid,
|
||||
$this->filter,
|
||||
$phid
|
||||
);
|
||||
|
||||
return $table->loadAllFromArray($rows);
|
||||
}
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->filter = idx($data, 'filter');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$phid_arr = $request->getArr('view_user');
|
||||
$view_target = head($phid_arr);
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($request->getRequestURI()->alter('phid', $view_target));
|
||||
}
|
||||
|
||||
$params = array_filter(
|
||||
array(
|
||||
'phid' => $request->getStr('phid'),
|
||||
));
|
||||
|
||||
// Fill in the defaults we'll actually use for calculations if any
|
||||
// parameters are missing.
|
||||
$params += array(
|
||||
'phid' => $user->getPHID(),
|
||||
);
|
||||
|
||||
$side_nav = new AphrontSideNavFilterView();
|
||||
$side_nav->setBaseURI(id(new PhutilURI('/differential/stats/'))
|
||||
->alter('phid', $params['phid']));
|
||||
foreach (array(
|
||||
DifferentialAction::ACTION_COMMIT,
|
||||
DifferentialAction::ACTION_ACCEPT,
|
||||
DifferentialAction::ACTION_REJECT,
|
||||
DifferentialAction::ACTION_UPDATE,
|
||||
DifferentialAction::ACTION_COMMENT,
|
||||
) as $action) {
|
||||
$verb = ucfirst(DifferentialAction::getActionPastTenseVerb($action));
|
||||
$side_nav->addFilter($action, $verb);
|
||||
}
|
||||
$this->filter =
|
||||
$side_nav->selectFilter($this->filter,
|
||||
DifferentialAction::ACTION_COMMIT);
|
||||
|
||||
$panels = array();
|
||||
$handles = id(new PhabricatorObjectHandleData(array($params['phid'])))
|
||||
->loadHandles();
|
||||
|
||||
$filter_form = id(new AphrontFormView())
|
||||
->setAction('/differential/stats/'.$this->filter.'/')
|
||||
->setUser($user);
|
||||
|
||||
$filter_form->appendChild(
|
||||
$this->renderControl($params['phid'], $handles));
|
||||
$filter_form->appendChild(id(new AphrontFormSubmitControl())
|
||||
->setValue('Filter Revisions'));
|
||||
|
||||
$side_nav->appendChild($filter_form);
|
||||
|
||||
$comments = $this->loadComments($params['phid']);
|
||||
$revisions = $this->loadRevisions($params['phid']);
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('Differential rate analysis');
|
||||
$panel->appendChild(
|
||||
id(new DifferentialRevisionStatsView())
|
||||
->setComments($comments)
|
||||
->setRevisions($revisions)
|
||||
->setUser($user));
|
||||
$panels[] = $panel;
|
||||
|
||||
foreach ($panels as $panel) {
|
||||
$side_nav->appendChild($panel);
|
||||
}
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$side_nav,
|
||||
array(
|
||||
'title' => 'Differential statistics',
|
||||
));
|
||||
}
|
||||
|
||||
private function renderControl($view_phid, $handles) {
|
||||
$value = array();
|
||||
if ($view_phid) {
|
||||
$value = array(
|
||||
$view_phid => $handles[$view_phid]->getFullName(),
|
||||
);
|
||||
}
|
||||
return id(new AphrontFormTokenizerControl())
|
||||
->setDatasource('/typeahead/common/users/')
|
||||
->setLabel('View User')
|
||||
->setName('view_user')
|
||||
->setValue($value)
|
||||
->setLimit(1);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/differential/constants/action');
|
||||
phutil_require_module('phabricator', 'applications/differential/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/differential/storage/comment');
|
||||
phutil_require_module('phabricator', 'applications/differential/storage/revision');
|
||||
phutil_require_module('phabricator', 'applications/differential/view/revisionstats');
|
||||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||
phutil_require_module('phabricator', 'storage/queryfx');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||
phutil_require_module('phabricator', 'view/form/control/tokenizer');
|
||||
phutil_require_module('phabricator', 'view/layout/panel');
|
||||
phutil_require_module('phabricator', 'view/layout/sidenavfilter');
|
||||
|
||||
phutil_require_module('phutil', 'parser/uri');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('DifferentialRevisionStatsController.php');
|
|
@ -0,0 +1,152 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render some distracting statistics on revisions
|
||||
*/
|
||||
final class DifferentialRevisionStatsView extends AphrontView {
|
||||
private $comments;
|
||||
private $revisions;
|
||||
private $user;
|
||||
|
||||
public function setRevisions(array $revisions) {
|
||||
$this->revisions = $revisions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setComments(array $comments) {
|
||||
$this->comments = $comments;
|
||||
return $this;
|
||||
}
|
||||
public function setUser($user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$user = $this->user;
|
||||
if (!$user) {
|
||||
throw new Exception("Call setUser() before render()!");
|
||||
}
|
||||
|
||||
$id_to_revision_map = array();
|
||||
foreach ($this->revisions as $rev) {
|
||||
$id_to_revision_map[$rev->getID()] = $rev;
|
||||
}
|
||||
$revisions_seen = array();
|
||||
|
||||
$dates = array();
|
||||
$counts = array();
|
||||
$lines = array();
|
||||
$boosts = array();
|
||||
$days_with_diffs = array();
|
||||
$count_active = array();
|
||||
$now = time();
|
||||
$row_array = array();
|
||||
|
||||
foreach (array(
|
||||
'1 week', '2 weeks', '3 weeks',
|
||||
'1 month', '2 months', '3 months', '6 months', '9 months',
|
||||
'1 year', '18 months',
|
||||
'2 years', '3 years', '4 years', '5 years',
|
||||
) as $age) {
|
||||
$dates[$age] = strtotime($age . ' ago');
|
||||
$counts[$age] = 0;
|
||||
$lines[$age] = 0;
|
||||
$count_active[$age] = 0;
|
||||
}
|
||||
|
||||
foreach ($this->comments as $comment) {
|
||||
$rev_date = $comment->getDateCreated();
|
||||
|
||||
$day = phabricator_date($rev_date, $user);
|
||||
$old_daycount = idx($days_with_diffs, $day, 0);
|
||||
$days_with_diffs[$day] = $old_daycount + 1;
|
||||
|
||||
$rev_id = $comment->getRevisionID();
|
||||
|
||||
if (idx($revisions_seen, $rev_id)) {
|
||||
continue;
|
||||
}
|
||||
$rev = $id_to_revision_map[$rev_id];
|
||||
$revisions_seen[$rev_id] = true;
|
||||
|
||||
foreach ($dates as $age => $cutoff) {
|
||||
if ($cutoff > $rev_date) {
|
||||
continue;
|
||||
}
|
||||
if ($rev) {
|
||||
$lines[$age] += $rev->getLineCount();
|
||||
}
|
||||
$counts[$age]++;
|
||||
if (!$old_daycount) {
|
||||
$count_active[$age]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$old_count = 0;
|
||||
foreach ($dates as $age => $cutoff) {
|
||||
$weeks = ($now - $cutoff + 0.) / (7 * 60 * 60 * 24);
|
||||
if ($old_count == $counts[$age]) {
|
||||
continue;
|
||||
}
|
||||
$old_count = $counts[$age];
|
||||
|
||||
$row_array[$age] = array(
|
||||
'Revisions per week' => number_format($counts[$age] / $weeks, 2),
|
||||
'Lines per week' => number_format($lines[$age] / $weeks, 1),
|
||||
'Active days per week' =>
|
||||
number_format($count_active[$age] / $weeks, 1),
|
||||
'Revisions' => number_format($counts[$age]),
|
||||
'Lines' => number_format($lines[$age]),
|
||||
'Lines per diff' => number_format($lines[$age] /
|
||||
($counts[$age] + 0.0001)),
|
||||
'Active days' => number_format($count_active[$age]),
|
||||
);
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
$row_names = array_keys(head($row_array));
|
||||
foreach ($row_names as $row_name) {
|
||||
$rows[] = array($row_name);
|
||||
}
|
||||
foreach (array_keys($dates) as $age) {
|
||||
$i = 0;
|
||||
foreach ($row_names as $row_name) {
|
||||
$rows[$i][] = idx(idx($row_array, $age), $row_name, '-');
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
|
||||
$table = new AphrontTableView($rows);
|
||||
$table->setColumnClasses(
|
||||
array(
|
||||
'wide pri',
|
||||
));
|
||||
|
||||
$table->setHeaders(
|
||||
array_merge(
|
||||
array(
|
||||
'Metric',
|
||||
),
|
||||
array_keys($dates)));
|
||||
|
||||
return $table->render();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'view/base');
|
||||
phutil_require_module('phabricator', 'view/control/table');
|
||||
phutil_require_module('phabricator', 'view/utils');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
phutil_require_source('DifferentialRevisionStatsView.php');
|
Loading…
Reference in a new issue