@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a "Reports" menu item to Projects

Summary:
Ref T13279. Since the use cases that have made it upstream are all for relatively complex charts (e.g., requiring aggregation and composition of multiple data series in nontrivial ways) I'm currently looking at an overall approach like this:

- At least for now, Charts provides a low-level internal-only API for composing charts from raw datasets.
- This is exposed to users through pre-built `SearchEngine`-like interfaces that provide a small number of more manageable controls (show chart from date X to date Y, show projects A, B, C), but not the full set of composition features (`compose(scale(2), cos())` and such).
- Eventually, we may put more UI on the raw chart composition stuff and let you build your own fully custom charts by gluing together datasets and functions.
- Or we may add this stuff in piecemeal to the higher-level UI as tools like "add goal line" or "add trend line" or whatever.

This will let the low-level API mature/evolve a bit before users get hold of it directly, if they ever do. Most requests today are likely satisfiable with a small number of chart engines plus raw API data access, so maybe UI access to flexible charting is far away.

Step toward this by adding a "Reports" section to projects. For now, this just renders a basic burnup for the current project. Followups will add an "Engine" layer above this and make the chart it produces more useful.

Test Plan: {F6426984}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13279

Differential Revision: https://secure.phabricator.com/D20495

+191
+4
src/__phutil_library_map__.php
··· 4245 4245 'PhabricatorProjectProjectPHIDType' => 'applications/project/phid/PhabricatorProjectProjectPHIDType.php', 4246 4246 'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php', 4247 4247 'PhabricatorProjectRemoveHeraldAction' => 'applications/project/herald/PhabricatorProjectRemoveHeraldAction.php', 4248 + 'PhabricatorProjectReportsController' => 'applications/project/controller/PhabricatorProjectReportsController.php', 4249 + 'PhabricatorProjectReportsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php', 4248 4250 'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php', 4249 4251 'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php', 4250 4252 'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php', ··· 10496 10498 'PhabricatorProjectProjectPHIDType' => 'PhabricatorPHIDType', 10497 10499 'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 10498 10500 'PhabricatorProjectRemoveHeraldAction' => 'PhabricatorProjectHeraldAction', 10501 + 'PhabricatorProjectReportsController' => 'PhabricatorProjectController', 10502 + 'PhabricatorProjectReportsProfileMenuItem' => 'PhabricatorProfileMenuItem', 10499 10503 'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec', 10500 10504 'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine', 10501 10505 'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField',
+2
src/applications/project/application/PhabricatorProjectApplication.php
··· 71 71 => 'PhabricatorProjectBoardViewController', 72 72 'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController', 73 73 'cover/' => 'PhabricatorProjectCoverController', 74 + 'reports/(?P<projectID>[1-9]\d*)/' => 75 + 'PhabricatorProjectReportsController', 74 76 'board/(?P<projectID>[1-9]\d*)/' => array( 75 77 'edit/(?:(?P<id>\d+)/)?' 76 78 => 'PhabricatorProjectColumnEditController',
+96
src/applications/project/controller/PhabricatorProjectReportsController.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectReportsController 4 + extends PhabricatorProjectController { 5 + 6 + public function shouldAllowPublic() { 7 + return true; 8 + } 9 + 10 + public function handleRequest(AphrontRequest $request) { 11 + $viewer = $request->getViewer(); 12 + 13 + $response = $this->loadProject(); 14 + if ($response) { 15 + return $response; 16 + } 17 + 18 + $project = $this->getProject(); 19 + $id = $project->getID(); 20 + 21 + $can_edit = PhabricatorPolicyFilter::hasCapability( 22 + $viewer, 23 + $project, 24 + PhabricatorPolicyCapability::CAN_EDIT); 25 + 26 + $nav = $this->newNavigation( 27 + $project, 28 + PhabricatorProject::ITEM_REPORTS); 29 + 30 + $crumbs = $this->buildApplicationCrumbs(); 31 + $crumbs->addTextCrumb(pht('Reports')); 32 + $crumbs->setBorder(true); 33 + 34 + $project_phid = $project->getPHID(); 35 + 36 + $argv = array( 37 + 'sum', 38 + array( 39 + 'accumulate', 40 + array('fact', 'tasks.open-count.create.project', $project_phid), 41 + ), 42 + array( 43 + 'accumulate', 44 + array('fact', 'tasks.open-count.status.project', $project_phid), 45 + ), 46 + array( 47 + 'accumulate', 48 + array('fact', 'tasks.open-count.assign.project', $project_phid), 49 + ), 50 + ); 51 + 52 + $function = id(new PhabricatorComposeChartFunction()) 53 + ->setArguments(array($argv)); 54 + 55 + $datasets = array( 56 + id(new PhabricatorChartDataset()) 57 + ->setFunction($function), 58 + ); 59 + 60 + $chart = id(new PhabricatorFactChart()) 61 + ->setDatasets($datasets); 62 + 63 + $engine = id(new PhabricatorChartEngine()) 64 + ->setViewer($viewer) 65 + ->setChart($chart); 66 + 67 + $chart = $engine->getStoredChart(); 68 + 69 + $panel_type = id(new PhabricatorDashboardChartPanelType()) 70 + ->getPanelTypeKey(); 71 + 72 + $chart_panel = id(new PhabricatorDashboardPanel()) 73 + ->setPanelType($panel_type) 74 + ->setName(pht('%s: Burndown', $project->getName())) 75 + ->setProperty('chartKey', $chart->getChartKey()); 76 + 77 + $chart_view = id(new PhabricatorDashboardPanelRenderingEngine()) 78 + ->setViewer($viewer) 79 + ->setPanel($chart_panel) 80 + ->setParentPanelPHIDs(array()) 81 + ->renderPanel(); 82 + 83 + $view = id(new PHUITwoColumnView()) 84 + ->setFooter( 85 + array( 86 + $chart_view, 87 + )); 88 + 89 + return $this->newPage() 90 + ->setNavigation($nav) 91 + ->setCrumbs($crumbs) 92 + ->setTitle(array($project->getName(), pht('Reports'))) 93 + ->appendChild($view); 94 + } 95 + 96 + }
+4
src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php
··· 38 38 ->setMenuItemKey(PhabricatorProjectWorkboardProfileMenuItem::MENUITEMKEY); 39 39 40 40 $items[] = $this->newItem() 41 + ->setBuiltinKey(PhabricatorProject::ITEM_REPORTS) 42 + ->setMenuItemKey(PhabricatorProjectReportsProfileMenuItem::MENUITEMKEY); 43 + 44 + $items[] = $this->newItem() 41 45 ->setBuiltinKey(PhabricatorProject::ITEM_MEMBERS) 42 46 ->setMenuItemKey(PhabricatorProjectMembersProfileMenuItem::MENUITEMKEY); 43 47
+80
src/applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectReportsProfileMenuItem 4 + extends PhabricatorProfileMenuItem { 5 + 6 + const MENUITEMKEY = 'project.reports'; 7 + 8 + public function getMenuItemTypeName() { 9 + return pht('Project Reports'); 10 + } 11 + 12 + private function getDefaultName() { 13 + return pht('Reports (Prototype)'); 14 + } 15 + 16 + public function getMenuItemTypeIcon() { 17 + return 'fa-area-chart'; 18 + } 19 + 20 + public function canMakeDefault( 21 + PhabricatorProfileMenuItemConfiguration $config) { 22 + return true; 23 + } 24 + 25 + public function shouldEnableForObject($object) { 26 + $viewer = $this->getViewer(); 27 + 28 + if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { 29 + return false; 30 + } 31 + 32 + $class = 'PhabricatorManiphestApplication'; 33 + if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { 34 + return false; 35 + } 36 + 37 + return true; 38 + } 39 + 40 + public function getDisplayName( 41 + PhabricatorProfileMenuItemConfiguration $config) { 42 + $name = $config->getMenuItemProperty('name'); 43 + 44 + if (strlen($name)) { 45 + return $name; 46 + } 47 + 48 + return $this->getDefaultName(); 49 + } 50 + 51 + public function buildEditEngineFields( 52 + PhabricatorProfileMenuItemConfiguration $config) { 53 + return array( 54 + id(new PhabricatorTextEditField()) 55 + ->setKey('name') 56 + ->setLabel(pht('Name')) 57 + ->setPlaceholder($this->getDefaultName()) 58 + ->setValue($config->getMenuItemProperty('name')), 59 + ); 60 + } 61 + 62 + protected function newMenuItemViewList( 63 + PhabricatorProfileMenuItemConfiguration $config) { 64 + $project = $config->getProfileObject(); 65 + 66 + $id = $project->getID(); 67 + $uri = $project->getReportsURI(); 68 + $name = $this->getDisplayName($config); 69 + 70 + $item = $this->newItemView() 71 + ->setURI($uri) 72 + ->setName($name) 73 + ->setIcon('fa-area-chart'); 74 + 75 + return array( 76 + $item, 77 + ); 78 + } 79 + 80 + }
+5
src/applications/project/storage/PhabricatorProject.php
··· 58 58 const ITEM_PROFILE = 'project.profile'; 59 59 const ITEM_POINTS = 'project.points'; 60 60 const ITEM_WORKBOARD = 'project.workboard'; 61 + const ITEM_REPORTS = 'project.reports'; 61 62 const ITEM_MEMBERS = 'project.members'; 62 63 const ITEM_MANAGE = 'project.manage'; 63 64 const ITEM_MILESTONES = 'project.milestones'; ··· 394 395 395 396 public function getWorkboardURI() { 396 397 return urisprintf('/project/board/%d/', $this->getID()); 398 + } 399 + 400 + public function getReportsURI() { 401 + return urisprintf('/project/reports/%d/', $this->getID()); 397 402 } 398 403 399 404 public function save() {