1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-09 16:32:39 +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:
epriestley 2019-02-13 17:50:10 -08:00
parent 8f8e863613
commit 8810cd2f4d
7 changed files with 300 additions and 42 deletions

View file

@ -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',

View file

@ -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',

View file

@ -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',
),
);
}

View file

@ -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;
}
}

View file

@ -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'))

View file

@ -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);
}
}

View file

@ -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;
}