mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-22 21:40:55 +01:00
Add a standalone view for the Maniphest task graph
Summary: See PHI1073. Improve the UX here: - When there are a small number of connected tasks, no changes. - When there are too many total connected tasks, but not too many directly connected tasks, show hint text with a "View Standalone Graph" button to view more of the graph. - When there are too many directly connected tasks, show better hint text with a "View Standalone Graph" button. - Always show a "View Standalone Graph" option in the dropdown menu. - Add a standalone view which works the same way but has a limit of 2,000. - This view doesn't have "View Standalone Graph" links, since they'd just link back to the same page, but is basically the same otherwise. - Increase the main page task limit from 100 to 200. Test Plan: Mobile View: {F6210326} Way too much stuff: {F6210327} New persistent link to the standalone page: {F6210328} Kind of too much stuff: {F6210329} Standalone view: {F6210330} Reviewers: amckinley Reviewed By: amckinley Subscribers: 20after4 Differential Revision: https://secure.phabricator.com/D20164
This commit is contained in:
parent
8f8e863613
commit
8810cd2f4d
7 changed files with 300 additions and 42 deletions
|
@ -9,7 +9,7 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => '3c8a0668',
|
||||
'conpherence.pkg.js' => '020aebcf',
|
||||
'core.pkg.css' => '4ed8ce1f',
|
||||
'core.pkg.css' => '85a1da99',
|
||||
'core.pkg.js' => '5c737607',
|
||||
'differential.pkg.css' => 'b8df73d4',
|
||||
'differential.pkg.js' => '67c9ea4c',
|
||||
|
@ -30,7 +30,7 @@ return array(
|
|||
'rsrc/css/aphront/notification.css' => '30240bd2',
|
||||
'rsrc/css/aphront/panel-view.css' => '46923d46',
|
||||
'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf',
|
||||
'rsrc/css/aphront/table-view.css' => 'daa1f9df',
|
||||
'rsrc/css/aphront/table-view.css' => '205053cd',
|
||||
'rsrc/css/aphront/tokenizer.css' => 'b52d0668',
|
||||
'rsrc/css/aphront/tooltip.css' => 'e3f2412f',
|
||||
'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2',
|
||||
|
@ -520,7 +520,7 @@ return array(
|
|||
'aphront-list-filter-view-css' => 'feb64255',
|
||||
'aphront-multi-column-view-css' => 'fbc00ba3',
|
||||
'aphront-panel-view-css' => '46923d46',
|
||||
'aphront-table-view-css' => 'daa1f9df',
|
||||
'aphront-table-view-css' => '205053cd',
|
||||
'aphront-tokenizer-control-css' => 'b52d0668',
|
||||
'aphront-tooltip-css' => 'e3f2412f',
|
||||
'aphront-typeahead-control-css' => '8779483d',
|
||||
|
|
|
@ -1714,6 +1714,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskFerretEngine' => 'applications/maniphest/search/ManiphestTaskFerretEngine.php',
|
||||
'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
|
||||
'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php',
|
||||
'ManiphestTaskGraphController' => 'applications/maniphest/controller/ManiphestTaskGraphController.php',
|
||||
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
|
||||
'ManiphestTaskHasCommitRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php',
|
||||
'ManiphestTaskHasDuplicateTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php',
|
||||
|
@ -7401,6 +7402,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskFerretEngine' => 'PhabricatorFerretEngine',
|
||||
'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
|
||||
'ManiphestTaskGraph' => 'PhabricatorObjectGraph',
|
||||
'ManiphestTaskGraphController' => 'ManiphestController',
|
||||
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
|
||||
'ManiphestTaskHasCommitRelationship' => 'ManiphestTaskRelationship',
|
||||
'ManiphestTaskHasDuplicateTaskEdgeType' => 'PhabricatorEdgeType',
|
||||
|
|
|
@ -55,6 +55,7 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
|
|||
'subtask/(?P<id>[1-9]\d*)/' => 'ManiphestTaskSubtaskController',
|
||||
),
|
||||
'subpriority/' => 'ManiphestSubpriorityController',
|
||||
'graph/(?P<id>[1-9]\d*)/' => 'ManiphestTaskGraphController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -61,4 +61,102 @@ abstract class ManiphestController extends PhabricatorController {
|
|||
return $view;
|
||||
}
|
||||
|
||||
final protected function newTaskGraphDropdownMenu(
|
||||
ManiphestTask $task,
|
||||
$has_parents,
|
||||
$has_subtasks,
|
||||
$include_standalone) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$parents_uri = urisprintf(
|
||||
'/?subtaskIDs=%d#R',
|
||||
$task->getID());
|
||||
$parents_uri = $this->getApplicationURI($parents_uri);
|
||||
|
||||
$subtasks_uri = urisprintf(
|
||||
'/?parentIDs=%d#R',
|
||||
$task->getID());
|
||||
$subtasks_uri = $this->getApplicationURI($subtasks_uri);
|
||||
|
||||
$dropdown_menu = id(new PhabricatorActionListView())
|
||||
->setViewer($viewer)
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($parents_uri)
|
||||
->setName(pht('Search Parent Tasks'))
|
||||
->setDisabled(!$has_parents)
|
||||
->setIcon('fa-chevron-circle-up'))
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($subtasks_uri)
|
||||
->setName(pht('Search Subtasks'))
|
||||
->setDisabled(!$has_subtasks)
|
||||
->setIcon('fa-chevron-circle-down'));
|
||||
|
||||
if ($include_standalone) {
|
||||
$standalone_uri = urisprintf('/graph/%d/', $task->getID());
|
||||
$standalone_uri = $this->getApplicationURI($standalone_uri);
|
||||
|
||||
$dropdown_menu->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($standalone_uri)
|
||||
->setName(pht('View Standalone Graph'))
|
||||
->setIcon('fa-code-fork'));
|
||||
}
|
||||
|
||||
$graph_menu = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-search')
|
||||
->setText(pht('Search...'))
|
||||
->setDropdownMenu($dropdown_menu);
|
||||
|
||||
return $graph_menu;
|
||||
}
|
||||
|
||||
final protected function newTaskGraphOverflowView(
|
||||
ManiphestTask $task,
|
||||
$overflow_message,
|
||||
$include_standalone) {
|
||||
|
||||
$id = $task->getID();
|
||||
|
||||
if ($include_standalone) {
|
||||
$standalone_uri = $this->getApplicationURI("graph/{$id}/");
|
||||
|
||||
$standalone_link = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($standalone_uri)
|
||||
->setColor(PHUIButtonView::GREY)
|
||||
->setIcon('fa-code-fork')
|
||||
->setText(pht('View Standalone Graph'));
|
||||
} else {
|
||||
$standalone_link = null;
|
||||
}
|
||||
|
||||
$standalone_icon = id(new PHUIIconView())
|
||||
->setIcon('fa-exclamation-triangle', 'yellow')
|
||||
->addClass('object-graph-header-icon');
|
||||
|
||||
$standalone_view = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'object-graph-header',
|
||||
),
|
||||
array(
|
||||
$standalone_link,
|
||||
$standalone_icon,
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'object-graph-header-message',
|
||||
),
|
||||
array(
|
||||
$overflow_message,
|
||||
)),
|
||||
));
|
||||
|
||||
return $standalone_view;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -80,7 +80,8 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
$related_tabs = array();
|
||||
$graph_menu = null;
|
||||
|
||||
$graph_limit = 100;
|
||||
$graph_limit = 200;
|
||||
$overflow_message = null;
|
||||
$task_graph = id(new ManiphestTaskGraph())
|
||||
->setViewer($viewer)
|
||||
->setSeedPHID($task->getPHID())
|
||||
|
@ -96,61 +97,55 @@ final class ManiphestTaskDetailController extends ManiphestController {
|
|||
$has_parents = (bool)$parent_list;
|
||||
$has_subtasks = (bool)$subtask_list;
|
||||
|
||||
$search_text = pht('Search...');
|
||||
|
||||
// First, get a count of direct parent tasks and subtasks. If there
|
||||
// are too many of these, we just don't draw anything. You can use
|
||||
// the search button to browse tasks with the search UI instead.
|
||||
$direct_count = count($parent_list) + count($subtask_list);
|
||||
|
||||
if ($direct_count > $graph_limit) {
|
||||
$message = pht(
|
||||
'Task graph too large to display (this task is directly connected '.
|
||||
'to more than %s other tasks). Use %s to explore connected tasks.',
|
||||
$graph_limit,
|
||||
phutil_tag('strong', array(), $search_text));
|
||||
$message = phutil_tag('em', array(), $message);
|
||||
$graph_table = id(new PHUIPropertyListView())
|
||||
->addTextContent($message);
|
||||
$overflow_message = pht(
|
||||
'This task is directly connected to more than %s other tasks. '.
|
||||
'Use %s to browse parents or subtasks, or %s to show more of the '.
|
||||
'graph.',
|
||||
new PhutilNumber($graph_limit),
|
||||
phutil_tag('strong', array(), pht('Search...')),
|
||||
phutil_tag('strong', array(), pht('View Standalone Graph')));
|
||||
|
||||
$graph_table = null;
|
||||
} else {
|
||||
// If there aren't too many direct tasks, but there are too many total
|
||||
// tasks, we'll only render directly connected tasks.
|
||||
if ($task_graph->isOverLimit()) {
|
||||
$task_graph->setRenderOnlyAdjacentNodes(true);
|
||||
|
||||
$overflow_message = pht(
|
||||
'This task is connected to more than %s other tasks. '.
|
||||
'Only direct parents and subtasks are shown here. Use '.
|
||||
'%s to show more of the graph.',
|
||||
new PhutilNumber($graph_limit),
|
||||
phutil_tag('strong', array(), pht('View Standalone Graph')));
|
||||
}
|
||||
|
||||
$graph_table = $task_graph->newGraphTable();
|
||||
}
|
||||
|
||||
$parents_uri = urisprintf(
|
||||
'/?subtaskIDs=%d#R',
|
||||
$task->getID());
|
||||
$parents_uri = $this->getApplicationURI($parents_uri);
|
||||
if ($overflow_message) {
|
||||
$overflow_view = $this->newTaskGraphOverflowView(
|
||||
$task,
|
||||
$overflow_message,
|
||||
true);
|
||||
|
||||
$subtasks_uri = urisprintf(
|
||||
'/?parentIDs=%d#R',
|
||||
$task->getID());
|
||||
$subtasks_uri = $this->getApplicationURI($subtasks_uri);
|
||||
$graph_table = array(
|
||||
$overflow_view,
|
||||
$graph_table,
|
||||
);
|
||||
}
|
||||
|
||||
$dropdown_menu = id(new PhabricatorActionListView())
|
||||
->setViewer($viewer)
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($parents_uri)
|
||||
->setName(pht('Search Parent Tasks'))
|
||||
->setDisabled(!$has_parents)
|
||||
->setIcon('fa-chevron-circle-up'))
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($subtasks_uri)
|
||||
->setName(pht('Search Subtasks'))
|
||||
->setDisabled(!$has_subtasks)
|
||||
->setIcon('fa-chevron-circle-down'));
|
||||
|
||||
$graph_menu = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setIcon('fa-search')
|
||||
->setText($search_text)
|
||||
->setDropdownMenu($dropdown_menu);
|
||||
$graph_menu = $this->newTaskGraphDropdownMenu(
|
||||
$task,
|
||||
$has_parents,
|
||||
$has_subtasks,
|
||||
true);
|
||||
|
||||
$related_tabs[] = id(new PHUITabView())
|
||||
->setName(pht('Task Graph'))
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskGraphController
|
||||
extends ManiphestController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$task = id(new ManiphestTaskQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$task) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs()
|
||||
->addTextCrumb($task->getMonogram(), $task->getURI())
|
||||
->addTextCrumb(pht('Graph'))
|
||||
->setBorder(true);
|
||||
|
||||
$graph_limit = 2000;
|
||||
$overflow_message = null;
|
||||
$task_graph = id(new ManiphestTaskGraph())
|
||||
->setViewer($viewer)
|
||||
->setSeedPHID($task->getPHID())
|
||||
->setLimit($graph_limit)
|
||||
->loadGraph();
|
||||
if (!$task_graph->isEmpty()) {
|
||||
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
|
||||
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
|
||||
$parent_map = $task_graph->getEdges($parent_type);
|
||||
$subtask_map = $task_graph->getEdges($subtask_type);
|
||||
$parent_list = idx($parent_map, $task->getPHID(), array());
|
||||
$subtask_list = idx($subtask_map, $task->getPHID(), array());
|
||||
$has_parents = (bool)$parent_list;
|
||||
$has_subtasks = (bool)$subtask_list;
|
||||
|
||||
// First, get a count of direct parent tasks and subtasks. If there
|
||||
// are too many of these, we just don't draw anything. You can use
|
||||
// the search button to browse tasks with the search UI instead.
|
||||
$direct_count = count($parent_list) + count($subtask_list);
|
||||
|
||||
if ($direct_count > $graph_limit) {
|
||||
$overflow_message = pht(
|
||||
'This task is directly connected to more than %s other tasks, '.
|
||||
'which is too many tasks to display. Use %s to browse parents '.
|
||||
'or subtasks.',
|
||||
new PhutilNumber($graph_limit),
|
||||
phutil_tag('strong', array(), pht('Search...')));
|
||||
|
||||
$graph_table = null;
|
||||
} else {
|
||||
// If there aren't too many direct tasks, but there are too many total
|
||||
// tasks, we'll only render directly connected tasks.
|
||||
if ($task_graph->isOverLimit()) {
|
||||
$task_graph->setRenderOnlyAdjacentNodes(true);
|
||||
|
||||
$overflow_message = pht(
|
||||
'This task is connected to more than %s other tasks. '.
|
||||
'Only direct parents and subtasks are shown here.',
|
||||
new PhutilNumber($graph_limit));
|
||||
}
|
||||
|
||||
$graph_table = $task_graph->newGraphTable();
|
||||
}
|
||||
|
||||
$graph_menu = $this->newTaskGraphDropdownMenu(
|
||||
$task,
|
||||
$has_parents,
|
||||
$has_subtasks,
|
||||
false);
|
||||
} else {
|
||||
$graph_menu = null;
|
||||
$graph_table = null;
|
||||
|
||||
$overflow_message = pht(
|
||||
'This task has no parent tasks and no subtasks, so there is no '.
|
||||
'graph to draw.');
|
||||
}
|
||||
|
||||
if ($overflow_message) {
|
||||
$overflow_view = $this->newTaskGraphOverflowView(
|
||||
$task,
|
||||
$overflow_message,
|
||||
false);
|
||||
|
||||
$graph_table = array(
|
||||
$overflow_view,
|
||||
$graph_table,
|
||||
);
|
||||
}
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Task Graph'));
|
||||
|
||||
if ($graph_menu) {
|
||||
$header->addActionLink($graph_menu);
|
||||
}
|
||||
|
||||
$tab_view = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($graph_table);
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setFooter($tab_view);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(
|
||||
array(
|
||||
$task->getMonogram(),
|
||||
pht('Graph'),
|
||||
))
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($view);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -327,3 +327,40 @@ span.single-display-line-content {
|
|||
.phui-object-box .aphront-table-view {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.object-graph-header {
|
||||
padding: 8px 12px;
|
||||
overflow: hidden;
|
||||
background: {$lightyellow};
|
||||
border-bottom: 1px solid {$lightblueborder};
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.object-graph-header .object-graph-header-icon {
|
||||
float: left;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.object-graph-header a.button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.object-graph-header-message {
|
||||
margin: 8px 200px 8px 20px;
|
||||
}
|
||||
|
||||
.device .object-graph-header .object-graph-header-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device .object-graph-header-message {
|
||||
clear: both;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.device .object-graph-header a.button {
|
||||
margin: 0 auto 12px;
|
||||
display: block;
|
||||
width: 180px;
|
||||
float: none;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue