1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-14 02:42:40 +01:00

Add a basic progress bar for milestones

Summary: Ref T4427. This kind of works.

Test Plan: {F1100578}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4427

Differential Revision: https://secure.phabricator.com/D15221
This commit is contained in:
epriestley 2016-02-08 17:31:21 -08:00
parent f84130f9cd
commit 0782652a80
12 changed files with 293 additions and 31 deletions

View file

@ -7,7 +7,7 @@
*/ */
return array( return array(
'names' => array( 'names' => array(
'core.pkg.css' => 'b4a7e275', 'core.pkg.css' => 'bef9c7cb',
'core.pkg.js' => '17380dd3', 'core.pkg.js' => '17380dd3',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9', 'differential.pkg.css' => '2de124c9',
@ -146,10 +146,10 @@ return array(
'rsrc/css/phui/phui-object-item-list-view.css' => '8f443e8b', 'rsrc/css/phui/phui-object-item-list-view.css' => '8f443e8b',
'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
'rsrc/css/phui/phui-profile-menu.css' => '4a243229', 'rsrc/css/phui/phui-profile-menu.css' => '2d5f0c75',
'rsrc/css/phui/phui-property-list-view.css' => '27b2849e', 'rsrc/css/phui/phui-property-list-view.css' => '27b2849e',
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
'rsrc/css/phui/phui-segment-bar-view.css' => '728e4d19', 'rsrc/css/phui/phui-segment-bar-view.css' => '52e7e529',
'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-status.css' => '888cedb8',
'rsrc/css/phui/phui-tag-view.css' => '9d5d4400', 'rsrc/css/phui/phui-tag-view.css' => '9d5d4400',
@ -823,10 +823,10 @@ return array(
'phui-object-item-list-view-css' => '8f443e8b', 'phui-object-item-list-view-css' => '8f443e8b',
'phui-pager-css' => 'bea33d23', 'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e', 'phui-pinboard-view-css' => '2495140e',
'phui-profile-menu-css' => '4a243229', 'phui-profile-menu-css' => '2d5f0c75',
'phui-property-list-view-css' => '27b2849e', 'phui-property-list-view-css' => '27b2849e',
'phui-remarkup-preview-css' => '1a8f2591', 'phui-remarkup-preview-css' => '1a8f2591',
'phui-segment-bar-view-css' => '728e4d19', 'phui-segment-bar-view-css' => '52e7e529',
'phui-spacing-css' => '042804d6', 'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8', 'phui-status-list-view-css' => '888cedb8',
'phui-tag-view-css' => '9d5d4400', 'phui-tag-view-css' => '9d5d4400',

View file

@ -2933,6 +2933,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php', 'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php', 'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php', 'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
'PhabricatorProjectPointsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProfilePanelEngine' => 'applications/project/engine/PhabricatorProjectProfilePanelEngine.php', 'PhabricatorProjectProfilePanelEngine' => 'applications/project/engine/PhabricatorProjectProfilePanelEngine.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php', 'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
@ -7364,6 +7365,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver', 'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorProjectPanelController' => 'PhabricatorProjectController', 'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
'PhabricatorProjectPointsProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProfilePanelEngine' => 'PhabricatorProfilePanelEngine', 'PhabricatorProjectProfilePanelEngine' => 'PhabricatorProfilePanelEngine',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',

View file

@ -307,28 +307,45 @@ final class ManiphestEditEngine
// currently leave the card where it was but should really move it to the // currently leave the card where it was but should really move it to the
// proper new column. // proper new column.
$board_phid = $column->getProjectPHID();
$descendant_projects = id(new PhabricatorProjectQuery()) $descendant_projects = id(new PhabricatorProjectQuery())
->setViewer($viewer) ->setViewer($viewer)
->withAncestorProjectPHIDs(array($column->getProjectPHID())) ->withAncestorProjectPHIDs(array($column->getProjectPHID()))
->execute(); ->execute();
$board_phids = mpull($descendant_projects, 'getPHID', 'getPHID'); $board_phids = mpull($descendant_projects, 'getPHID', 'getPHID');
$board_phids[$column->getProjectPHID()] = $column->getProjectPHID(); $board_phids[$board_phid] = $board_phid;
$project_map = array_fuse($task->getProjectPHIDs()); $project_map = array_fuse($task->getProjectPHIDs());
$remove_card = !array_intersect_key($board_phids, $project_map); $remove_card = !array_intersect_key($board_phids, $project_map);
$positions = id(new PhabricatorProjectColumnPositionQuery()) // TODO: Maybe the caller should pass a list of visible task PHIDs so we
// know which ones we need to reorder? This is a HUGE overfetch.
$objects = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
array($board_phids))
->setViewer($viewer) ->setViewer($viewer)
->withBoardPHIDs(array($column->getProjectPHID()))
->withColumnPHIDs(array($column->getPHID()))
->execute(); ->execute();
$task_phids = mpull($positions, 'getObjectPHID'); $objects = mpull($objects, null, 'getPHID');
$column_tasks = id(new ManiphestTaskQuery()) $layout_engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer) ->setViewer($viewer)
->withPHIDs($task_phids) ->setBoardPHIDs(array($board_phid))
->needProjectPHIDs(true) ->setObjectPHIDs(array_keys($objects))
->execute(); ->executeLayout();
$positions = $layout_engine->getColumnObjectPositions(
$board_phid,
$column_phid);
$column_phids = $layout_engine->getColumnObjectPHIDs(
$board_phid,
$column_phid);
$column_tasks = array_select_keys($objects, $column_phids);
if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
// TODO: This is a little bit awkward, because PHP and JS use // TODO: This is a little bit awkward, because PHP and JS use

View file

@ -86,9 +86,14 @@ final class PhabricatorBoardLayoutEngine extends Phobject {
return array_select_keys($this->columnMap, array_keys($columns)); return array_select_keys($this->columnMap, array_keys($columns));
} }
public function getColumnObjectPHIDs($board_phid, $column_phid) { public function getColumnObjectPositions($board_phid, $column_phid) {
$columns = idx($this->boardLayout, $board_phid, array()); $columns = idx($this->boardLayout, $board_phid, array());
$positions = idx($columns, $column_phid, array()); return idx($columns, $column_phid, array());
}
public function getColumnObjectPHIDs($board_phid, $column_phid) {
$positions = $this->getColumnObjectPositions($board_phid, $column_phid);
return mpull($positions, 'getObjectPHID'); return mpull($positions, 'getObjectPHID');
} }

View file

@ -20,6 +20,10 @@ final class PhabricatorProjectProfilePanelEngine
->setBuiltinKey(PhabricatorProject::PANEL_PROFILE) ->setBuiltinKey(PhabricatorProject::PANEL_PROFILE)
->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY); ->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY);
$panels[] = $this->newPanel()
->setBuiltinKey(PhabricatorProject::PANEL_POINTS)
->setPanelKey(PhabricatorProjectPointsProfilePanel::PANELKEY);
$panels[] = $this->newPanel() $panels[] = $this->newPanel()
->setBuiltinKey(PhabricatorProject::PANEL_WORKBOARD) ->setBuiltinKey(PhabricatorProject::PANEL_WORKBOARD)
->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY); ->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY);

View file

@ -0,0 +1,192 @@
<?php
final class PhabricatorProjectPointsProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.points';
public function getPanelTypeName() {
return pht('Project Points');
}
private function getDefaultName() {
return pht('Points Bar');
}
public function shouldEnableForObject($object) {
$viewer = $this->getViewer();
// Only render this element for milestones.
if (!$object->isMilestone()) {
return false;
}
// Don't show if points aren't configured.
if (!ManiphestTaskPoints::getIsEnabled()) {
return false;
}
// Points are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return false;
}
return true;
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorInstructionsEditField())
->setValue(
pht(
'This is a progress bar which shows how many points of work '.
'are complete within the milestone. It has no configurable '.
'settings.')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$viewer = $this->getViewer();
$project = $config->getProfileObject();
$limit = 250;
$tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_AND,
array($project->getPHID()))
->setLimit($limit + 1)
->execute();
if (count($tasks) > $limit) {
return $this->renderError(
pht(
'Too many tasks to compute statistics for (more than %s).',
new PhutilNumber($limit)));
}
if (!$tasks) {
return $this->renderError(
pht(
'This milestone has no tasks yet.'));
}
$statuses = array();
$points_done = 0;
$points_total = 0;
$no_points = 0;
foreach ($tasks as $task) {
$points = $task->getPoints();
if ($points === null) {
$no_points++;
continue;
}
if (!$points) {
continue;
}
$status = $task->getStatus();
if (empty($statuses[$status])) {
$statuses[$status] = 0;
}
$statuses[$status] += $points;
if (ManiphestTaskStatus::isClosedStatus($status)) {
$points_done += $points;
}
$points_total += $points;
}
if ($no_points == count($tasks)) {
return $this->renderError(
pht('No tasks have assigned point values.'));
}
if (!$points_total) {
return $this->renderError(
pht('All tasks with assigned point values are worth zero points.'));
}
$label = pht(
'%s of %s %s',
new PhutilNumber($points_done),
new PhutilNumber($points_total),
ManiphestTaskPoints::getPointsLabel());
$bar = id(new PHUISegmentBarView())
->setLabel($label);
$map = ManiphestTaskStatus::getTaskStatusMap();
$statuses = array_select_keys($statuses, array_keys($map));
foreach ($statuses as $status => $points) {
if (!$points) {
continue;
}
if (!ManiphestTaskStatus::isClosedStatus($status)) {
continue;
}
$color = ManiphestTaskStatus::getStatusColor($status);
if (!$color) {
$color = 'sky';
}
$tooltip = pht(
'%s %s',
new PhutilNumber($points),
ManiphestTaskStatus::getTaskStatusName($status));
$bar->newSegment()
->setWidth($points / $points_total)
->setColor($color)
->setTooltip($tooltip);
}
$bar = phutil_tag(
'div',
array(
'class' => 'phui-profile-segment-bar',
),
$bar);
$item = $this->newItem()
->appendChild($bar);
return array(
$item,
);
}
private function renderError($message) {
$message = phutil_tag(
'div',
array(
'class' => 'phui-profile-menu-error',
),
$message);
$item = $this->newItem()
->appendChild($message);
return array(
$item,
);
}
}

View file

@ -18,6 +18,18 @@ final class PhabricatorProjectWorkboardProfilePanel
return true; return true;
} }
public function shouldEnableForObject($object) {
$viewer = $this->getViewer();
// Workboards are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return false;
}
return true;
}
public function getDisplayName( public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) { PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name'); $name = $config->getPanelProperty('name');
@ -42,14 +54,6 @@ final class PhabricatorProjectWorkboardProfilePanel
protected function newNavigationMenuItems( protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) { PhabricatorProfilePanelConfiguration $config) {
$viewer = $this->getViewer();
// Workboards are only available if Maniphest is installed.
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return array();
}
$project = $config->getProfileObject(); $project = $config->getProfileObject();
$has_workboard = $project->getHasWorkboard(); $has_workboard = $project->getHasWorkboard();

View file

@ -48,6 +48,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO
const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken'; const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
const PANEL_PROFILE = 'project.profile'; const PANEL_PROFILE = 'project.profile';
const PANEL_POINTS = 'project.points';
const PANEL_WORKBOARD = 'project.workboard'; const PANEL_WORKBOARD = 'project.workboard';
const PANEL_MEMBERS = 'project.members'; const PANEL_MEMBERS = 'project.members';
const PANEL_MANAGE = 'project.manage'; const PANEL_MANAGE = 'project.manage';

View file

@ -236,6 +236,11 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
->withProfilePHIDs(array($object->getPHID())) ->withProfilePHIDs(array($object->getPHID()))
->execute(); ->execute();
foreach ($stored_panels as $stored_panel) {
$impl = $stored_panel->getPanel();
$impl->setViewer($viewer);
}
// Merge the stored panels into the builtin panels. If a builtin panel has // Merge the stored panels into the builtin panels. If a builtin panel has
// a stored version, replace the defaults with the stored changes. // a stored version, replace the defaults with the stored changes.
foreach ($stored_panels as $stored_panel) { foreach ($stored_panels as $stored_panel) {
@ -259,12 +264,6 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
} }
} }
foreach ($panels as $panel) {
$impl = $panel->getPanel();
$impl->setViewer($viewer);
}
$panels = msort($panels, 'getSortKey'); $panels = msort($panels, 'getSortKey');
// Normalize keys since callers shouldn't rely on this array being // Normalize keys since callers shouldn't rely on this array being
@ -306,6 +305,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
$builtins = $this->getBuiltinProfilePanels($object); $builtins = $this->getBuiltinProfilePanels($object);
$panels = PhabricatorProfilePanel::getAllPanels(); $panels = PhabricatorProfilePanel::getAllPanels();
$viewer = $this->getViewer();
$order = 1; $order = 1;
$map = array(); $map = array();
@ -339,6 +339,9 @@ abstract class PhabricatorProfilePanelEngine extends Phobject {
$panel_key)); $panel_key));
} }
$panel = clone $panel;
$panel->setViewer($viewer);
$builtin $builtin
->setProfilePHID($object->getPHID()) ->setProfilePHID($object->getPHID())
->attachPanel($panel) ->attachPanel($panel)

View file

@ -5,6 +5,7 @@ final class PHUISegmentBarSegmentView extends AphrontTagView {
private $width; private $width;
private $color; private $color;
private $position; private $position;
private $tooltip;
public function setWidth($width) { public function setWidth($width) {
$this->width = $width; $this->width = $width;
@ -25,6 +26,11 @@ final class PHUISegmentBarSegmentView extends AphrontTagView {
return $this; return $this;
} }
public function setTooltip($tooltip) {
$this->tooltip = $tooltip;
return $this;
}
protected function canAppendChild() { protected function canAppendChild() {
return false; return false;
} }
@ -48,9 +54,25 @@ final class PHUISegmentBarSegmentView extends AphrontTagView {
$left = floor(100 * $left) / 100; $left = floor(100 * $left) / 100;
$left = sprintf('%.2f%%', $left); $left = sprintf('%.2f%%', $left);
$tooltip = $this->tooltip;
if (strlen($tooltip)) {
Javelin::initBehavior('phabricator-tooltips');
$sigil = 'has-tooltip';
$meta = array(
'tip' => $tooltip,
'align' => 'E',
);
} else {
$sigil = null;
$meta = null;
}
return array( return array(
'class' => implode(' ', $classes), 'class' => implode(' ', $classes),
'style' => "left: {$left}; width: {$width};", 'style' => "left: {$left}; width: {$width};",
'sigil' => $sigil,
'meta' => $meta,
); );
} }

View file

@ -149,6 +149,18 @@
color: {$menu.profile.text}; color: {$menu.profile.text};
} }
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-error {
color: {$greytext};
font-size: {$smallerfontsize};
padding: 18px 15px;
}
.phui-profile-menu .phabricator-side-menu .phui-profile-segment-bar {
color: {$menu.profile.text};
padding: 12px 15px 18px;
}
.phui-profile-menu .phabricator-side-menu .phui-profile-menu-spacer { .phui-profile-menu .phabricator-side-menu .phui-profile-menu-spacer {
box-sizing: border-box; box-sizing: border-box;
height: {$menu.profile.item.height}; height: {$menu.profile.item.height};

View file

@ -20,7 +20,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
margin-left: -4px; margin-left: -5px;
border-right: 5px solid; border-right: 5px solid;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
} }