From 0aee3da19e6a27ae6f0f05f8f2d769535923f7b9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 3 May 2019 11:44:05 -0700 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 4 + .../PhabricatorProjectApplication.php | 2 + .../PhabricatorProjectReportsController.php | 96 +++++++++++++++++++ .../PhabricatorProjectProfileMenuEngine.php | 4 + ...abricatorProjectReportsProfileMenuItem.php | 80 ++++++++++++++++ .../project/storage/PhabricatorProject.php | 5 + 6 files changed, 191 insertions(+) create mode 100644 src/applications/project/controller/PhabricatorProjectReportsController.php create mode 100644 src/applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 867fce8822..2be86fe4e4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4245,6 +4245,8 @@ phutil_register_library_map(array( 'PhabricatorProjectProjectPHIDType' => 'applications/project/phid/PhabricatorProjectProjectPHIDType.php', 'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php', 'PhabricatorProjectRemoveHeraldAction' => 'applications/project/herald/PhabricatorProjectRemoveHeraldAction.php', + 'PhabricatorProjectReportsController' => 'applications/project/controller/PhabricatorProjectReportsController.php', + 'PhabricatorProjectReportsProfileMenuItem' => 'applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php', 'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php', 'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php', 'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php', @@ -10496,6 +10498,8 @@ phutil_register_library_map(array( 'PhabricatorProjectProjectPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorProjectRemoveHeraldAction' => 'PhabricatorProjectHeraldAction', + 'PhabricatorProjectReportsController' => 'PhabricatorProjectController', + 'PhabricatorProjectReportsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField', diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index 46d7558f5b..d8e78c5f7d 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -71,6 +71,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { => 'PhabricatorProjectBoardViewController', 'move/(?P[1-9]\d*)/' => 'PhabricatorProjectMoveController', 'cover/' => 'PhabricatorProjectCoverController', + 'reports/(?P[1-9]\d*)/' => + 'PhabricatorProjectReportsController', 'board/(?P[1-9]\d*)/' => array( 'edit/(?:(?P\d+)/)?' => 'PhabricatorProjectColumnEditController', diff --git a/src/applications/project/controller/PhabricatorProjectReportsController.php b/src/applications/project/controller/PhabricatorProjectReportsController.php new file mode 100644 index 0000000000..4a08dd6103 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectReportsController.php @@ -0,0 +1,96 @@ +getViewer(); + + $response = $this->loadProject(); + if ($response) { + return $response; + } + + $project = $this->getProject(); + $id = $project->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + + $nav = $this->newNavigation( + $project, + PhabricatorProject::ITEM_REPORTS); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Reports')); + $crumbs->setBorder(true); + + $project_phid = $project->getPHID(); + + $argv = array( + 'sum', + array( + 'accumulate', + array('fact', 'tasks.open-count.create.project', $project_phid), + ), + array( + 'accumulate', + array('fact', 'tasks.open-count.status.project', $project_phid), + ), + array( + 'accumulate', + array('fact', 'tasks.open-count.assign.project', $project_phid), + ), + ); + + $function = id(new PhabricatorComposeChartFunction()) + ->setArguments(array($argv)); + + $datasets = array( + id(new PhabricatorChartDataset()) + ->setFunction($function), + ); + + $chart = id(new PhabricatorFactChart()) + ->setDatasets($datasets); + + $engine = id(new PhabricatorChartEngine()) + ->setViewer($viewer) + ->setChart($chart); + + $chart = $engine->getStoredChart(); + + $panel_type = id(new PhabricatorDashboardChartPanelType()) + ->getPanelTypeKey(); + + $chart_panel = id(new PhabricatorDashboardPanel()) + ->setPanelType($panel_type) + ->setName(pht('%s: Burndown', $project->getName())) + ->setProperty('chartKey', $chart->getChartKey()); + + $chart_view = id(new PhabricatorDashboardPanelRenderingEngine()) + ->setViewer($viewer) + ->setPanel($chart_panel) + ->setParentPanelPHIDs(array()) + ->renderPanel(); + + $view = id(new PHUITwoColumnView()) + ->setFooter( + array( + $chart_view, + )); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), pht('Reports'))) + ->appendChild($view); + } + +} diff --git a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php index 813cd01781..5ce4a3001b 100644 --- a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php +++ b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php @@ -37,6 +37,10 @@ final class PhabricatorProjectProfileMenuEngine ->setBuiltinKey(PhabricatorProject::ITEM_WORKBOARD) ->setMenuItemKey(PhabricatorProjectWorkboardProfileMenuItem::MENUITEMKEY); + $items[] = $this->newItem() + ->setBuiltinKey(PhabricatorProject::ITEM_REPORTS) + ->setMenuItemKey(PhabricatorProjectReportsProfileMenuItem::MENUITEMKEY); + $items[] = $this->newItem() ->setBuiltinKey(PhabricatorProject::ITEM_MEMBERS) ->setMenuItemKey(PhabricatorProjectMembersProfileMenuItem::MENUITEMKEY); diff --git a/src/applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php new file mode 100644 index 0000000000..d1350238f5 --- /dev/null +++ b/src/applications/project/menuitem/PhabricatorProjectReportsProfileMenuItem.php @@ -0,0 +1,80 @@ +getViewer(); + + if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + return false; + } + + $class = 'PhabricatorManiphestApplication'; + if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { + return false; + } + + return true; + } + + public function getDisplayName( + PhabricatorProfileMenuItemConfiguration $config) { + $name = $config->getMenuItemProperty('name'); + + if (strlen($name)) { + return $name; + } + + return $this->getDefaultName(); + } + + public function buildEditEngineFields( + PhabricatorProfileMenuItemConfiguration $config) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setPlaceholder($this->getDefaultName()) + ->setValue($config->getMenuItemProperty('name')), + ); + } + + protected function newMenuItemViewList( + PhabricatorProfileMenuItemConfiguration $config) { + $project = $config->getProfileObject(); + + $id = $project->getID(); + $uri = $project->getReportsURI(); + $name = $this->getDisplayName($config); + + $item = $this->newItemView() + ->setURI($uri) + ->setName($name) + ->setIcon('fa-area-chart'); + + return array( + $item, + ); + } + +} diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 67ab05f5fd..54267829d3 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -58,6 +58,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO const ITEM_PROFILE = 'project.profile'; const ITEM_POINTS = 'project.points'; const ITEM_WORKBOARD = 'project.workboard'; + const ITEM_REPORTS = 'project.reports'; const ITEM_MEMBERS = 'project.members'; const ITEM_MANAGE = 'project.manage'; const ITEM_MILESTONES = 'project.milestones'; @@ -396,6 +397,10 @@ final class PhabricatorProject extends PhabricatorProjectDAO return urisprintf('/project/board/%d/', $this->getID()); } + public function getReportsURI() { + return urisprintf('/project/reports/%d/', $this->getID()); + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20));