Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13993860
D15221.id36744.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
D15221.id36744.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D15221: Add a basic progress bar for milestones
Attached
Detach File
Event Timeline
Log In to Comment