Page MenuHomePhabricator

D15221.id36744.diff
No OneTemporary

D15221.id36744.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,7 +7,7 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => 'b4a7e275',
+ 'core.pkg.css' => 'bef9c7cb',
'core.pkg.js' => '17380dd3',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
@@ -146,10 +146,10 @@
'rsrc/css/phui/phui-object-item-list-view.css' => '8f443e8b',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'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-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-status.css' => '888cedb8',
'rsrc/css/phui/phui-tag-view.css' => '9d5d4400',
@@ -823,10 +823,10 @@
'phui-object-item-list-view-css' => '8f443e8b',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
- 'phui-profile-menu-css' => '4a243229',
+ 'phui-profile-menu-css' => '2d5f0c75',
'phui-property-list-view-css' => '27b2849e',
'phui-remarkup-preview-css' => '1a8f2591',
- 'phui-segment-bar-view-css' => '728e4d19',
+ 'phui-segment-bar-view-css' => '52e7e529',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8',
'phui-tag-view-css' => '9d5d4400',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2933,6 +2933,7 @@
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
+ 'PhabricatorProjectPointsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProfilePanelEngine' => 'applications/project/engine/PhabricatorProjectProfilePanelEngine.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
@@ -7364,6 +7365,7 @@
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
+ 'PhabricatorProjectPointsProfilePanel' => 'PhabricatorProfilePanel',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProfilePanelEngine' => 'PhabricatorProfilePanelEngine',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php
--- a/src/applications/maniphest/editor/ManiphestEditEngine.php
+++ b/src/applications/maniphest/editor/ManiphestEditEngine.php
@@ -307,28 +307,45 @@
// currently leave the card where it was but should really move it to the
// proper new column.
+ $board_phid = $column->getProjectPHID();
+
$descendant_projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withAncestorProjectPHIDs(array($column->getProjectPHID()))
->execute();
$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());
$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)
- ->withBoardPHIDs(array($column->getProjectPHID()))
- ->withColumnPHIDs(array($column->getPHID()))
->execute();
- $task_phids = mpull($positions, 'getObjectPHID');
+ $objects = mpull($objects, null, 'getPHID');
- $column_tasks = id(new ManiphestTaskQuery())
+ $layout_engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer)
- ->withPHIDs($task_phids)
- ->needProjectPHIDs(true)
- ->execute();
+ ->setBoardPHIDs(array($board_phid))
+ ->setObjectPHIDs(array_keys($objects))
+ ->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) {
// TODO: This is a little bit awkward, because PHP and JS use
diff --git a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
--- a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
+++ b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
@@ -86,9 +86,14 @@
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());
- $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');
}
diff --git a/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php b/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php
--- a/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php
+++ b/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php
@@ -21,6 +21,10 @@
->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY);
$panels[] = $this->newPanel()
+ ->setBuiltinKey(PhabricatorProject::PANEL_POINTS)
+ ->setPanelKey(PhabricatorProjectPointsProfilePanel::PANELKEY);
+
+ $panels[] = $this->newPanel()
->setBuiltinKey(PhabricatorProject::PANEL_WORKBOARD)
->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY);
diff --git a/src/applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php b/src/applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/profilepanel/PhabricatorProjectPointsProfilePanel.php
@@ -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,
+ );
+ }
+
+}
diff --git a/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php b/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
--- a/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
+++ b/src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
@@ -18,6 +18,18 @@
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(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
@@ -42,14 +54,6 @@
protected function newNavigationMenuItems(
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();
$has_workboard = $project->getHasWorkboard();
diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php
--- a/src/applications/project/storage/PhabricatorProject.php
+++ b/src/applications/project/storage/PhabricatorProject.php
@@ -48,6 +48,7 @@
const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken';
const PANEL_PROFILE = 'project.profile';
+ const PANEL_POINTS = 'project.points';
const PANEL_WORKBOARD = 'project.workboard';
const PANEL_MEMBERS = 'project.members';
const PANEL_MANAGE = 'project.manage';
diff --git a/src/applications/search/engine/PhabricatorProfilePanelEngine.php b/src/applications/search/engine/PhabricatorProfilePanelEngine.php
--- a/src/applications/search/engine/PhabricatorProfilePanelEngine.php
+++ b/src/applications/search/engine/PhabricatorProfilePanelEngine.php
@@ -236,6 +236,11 @@
->withProfilePHIDs(array($object->getPHID()))
->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
// a stored version, replace the defaults with the stored changes.
foreach ($stored_panels as $stored_panel) {
@@ -259,12 +264,6 @@
}
}
- foreach ($panels as $panel) {
- $impl = $panel->getPanel();
-
- $impl->setViewer($viewer);
- }
-
$panels = msort($panels, 'getSortKey');
// Normalize keys since callers shouldn't rely on this array being
@@ -306,6 +305,7 @@
$builtins = $this->getBuiltinProfilePanels($object);
$panels = PhabricatorProfilePanel::getAllPanels();
+ $viewer = $this->getViewer();
$order = 1;
$map = array();
@@ -339,6 +339,9 @@
$panel_key));
}
+ $panel = clone $panel;
+ $panel->setViewer($viewer);
+
$builtin
->setProfilePHID($object->getPHID())
->attachPanel($panel)
diff --git a/src/view/phui/PHUISegmentBarSegmentView.php b/src/view/phui/PHUISegmentBarSegmentView.php
--- a/src/view/phui/PHUISegmentBarSegmentView.php
+++ b/src/view/phui/PHUISegmentBarSegmentView.php
@@ -5,6 +5,7 @@
private $width;
private $color;
private $position;
+ private $tooltip;
public function setWidth($width) {
$this->width = $width;
@@ -25,6 +26,11 @@
return $this;
}
+ public function setTooltip($tooltip) {
+ $this->tooltip = $tooltip;
+ return $this;
+ }
+
protected function canAppendChild() {
return false;
}
@@ -48,9 +54,25 @@
$left = floor(100 * $left) / 100;
$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(
'class' => implode(' ', $classes),
'style' => "left: {$left}; width: {$width};",
+ 'sigil' => $sigil,
+ 'meta' => $meta,
);
}
diff --git a/webroot/rsrc/css/phui/phui-profile-menu.css b/webroot/rsrc/css/phui/phui-profile-menu.css
--- a/webroot/rsrc/css/phui/phui-profile-menu.css
+++ b/webroot/rsrc/css/phui/phui-profile-menu.css
@@ -149,6 +149,18 @@
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 {
box-sizing: border-box;
height: {$menu.profile.item.height};
diff --git a/webroot/rsrc/css/phui/phui-segment-bar-view.css b/webroot/rsrc/css/phui/phui-segment-bar-view.css
--- a/webroot/rsrc/css/phui/phui-segment-bar-view.css
+++ b/webroot/rsrc/css/phui/phui-segment-bar-view.css
@@ -20,7 +20,7 @@
position: absolute;
top: 0;
bottom: 0;
- margin-left: -4px;
+ margin-left: -5px;
border-right: 5px solid;
border-radius: 0 4px 4px 0;
}

File Metadata

Mime Type
text/plain
Expires
Thu, Oct 24, 1:55 AM (3 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6719502
Default Alt Text
D15221.id36744.diff (17 KB)

Event Timeline