From 59d645a75e83dbbc04833aeecfa8c31e163303f0 Mon Sep 17 00:00:00 2001 From: Jared Hancock <gravydish@gmail.com> Date: Sat, 28 Apr 2018 21:22:16 -0500 Subject: [PATCH] queue: Improve queries necessary for rendering This removes a significant number of queries used to render the drop-down menu for the queues. Each queue displayed on the page previously required a database query to determine its children. This patch changes the strategy to fetch all the queues and organize them as a tree. The tree can then be walked as the menu is rendered and does not require any further queries. On my test system, it reduces the number of queries for the ticket listing page from 56 to 46. --- include/class.queue.php | 38 +++++++++++++++++++ .../staff/templates/queue-navigation.tmpl.php | 18 ++++++++- .../templates/queue-subnavigation.tmpl.php | 23 ++++++----- scp/tickets.php | 17 +++------ 4 files changed, 74 insertions(+), 22 deletions(-) diff --git a/include/class.queue.php b/include/class.queue.php index cea1404ed..dfa384642 100644 --- a/include/class.queue.php +++ b/include/class.queue.php @@ -1265,6 +1265,44 @@ class CustomQueue extends VerySimpleModel { && $this->sorts->saveAll(); } + /** + * Fetch a tree-organized listing of the queues. Each queue is listed in + * the tree exactly once, and every visible queue is represented. The + * returned structure is an array where the items are two-item arrays + * where the first item is a CustomQueue object an the second is a list + * of the children using the same pattern (two-item arrays of a CustomQueue + * and its children). Visually: + * + * [ [ $queue, [ [ $child, [] ], [ $child, [] ] ], [ $queue, ... ] ] + * + * Parameters: + * $staff - <Staff> staff object which should be used to determine + * visible queues. + * $pid - <int> parent_id of root queue. Default is zero (top-level) + */ + static function getHierarchicalQueues(Staff $staff, $pid=0) { + $all = static::objects() + ->filter(Q::any(array( + 'flags__hasbit' => self::FLAG_PUBLIC, + 'flags__hasbit' => static::FLAG_QUEUE, + 'staff_id' => $staff->getId(), + ))) + ->exclude(['flags__hasbit' => self::FLAG_DISABLED]) + ->asArray(); + + // Find all the queues with a given parent + $for_parent = function($pid) use ($all, &$for_parent) { + $results = []; + foreach (new \ArrayIterator($all) as $q) { + if ($q->parent_id == $pid) + $results[] = [ $q, $for_parent($q->getId()) ]; + } + return $results; + }; + + return $for_parent($pid); + } + static function getOrmPath($name, $query=null) { // Special case for custom data `__answers!id__value`. Only add the // join and constraint on the query the first pass, when the query diff --git a/include/staff/templates/queue-navigation.tmpl.php b/include/staff/templates/queue-navigation.tmpl.php index b903f29fd..3e40918fd 100644 --- a/include/staff/templates/queue-navigation.tmpl.php +++ b/include/staff/templates/queue-navigation.tmpl.php @@ -4,6 +4,7 @@ // $q - <CustomQueue> object for this navigation entry // $selected - <bool> true if this queue is currently active // $child_selected - <bool> true if the selected queue is a descendent +$childs = $children; $this_queue = $q; $selected = (!isset($_REQUEST['a']) && $_REQUEST['queue'] == $this_queue->getId()); ?> @@ -28,8 +29,21 @@ $selected = (!isset($_REQUEST['a']) && $_REQUEST['queue'] == $this_queue->getId </li> <!-- Start Dropdown and child queues --> - <?php foreach ($this_queue->getPublicChildren() as $q) { - include 'queue-subnavigation.tmpl.php'; + <?php foreach ($childs as $_) { + list($q, $children) = $_; + if (!$q->isPrivate()) + include 'queue-subnavigation.tmpl.php'; + } + $first_child = true; + foreach ($childs as $_) { + list($q, $children) = $_; + if (!$q->isPrivate()) + continue; + if ($first_child) { + $first_child = false; + echo '<li class="personalQ"></li>'; + } + include 'queue-subnavigation.tmpl.php'; } ?> <!-- Personal Queues --> <?php diff --git a/include/staff/templates/queue-subnavigation.tmpl.php b/include/staff/templates/queue-subnavigation.tmpl.php index b0cbeeb75..fa33b7b72 100644 --- a/include/staff/templates/queue-subnavigation.tmpl.php +++ b/include/staff/templates/queue-subnavigation.tmpl.php @@ -1,10 +1,9 @@ <?php // Calling conventions // $q - <CustomQueue> object for this navigation entry +// $children - <Array<CustomQueue>> all direct children of this queue $queue = $q; -$children = !$queue instanceof SavedSearch ? $queue->getPublicChildren() : array(); -$subq_searches = !$queue instanceof SavedSearch ? $queue->getMyChildren() : array(); -$hasChildren = count($children) + count($subq_searches) > 0; +$hasChildren = count($children) > 0; $selected = $_REQUEST['queue'] == $q->getId(); global $thisstaff; ?> @@ -27,24 +26,30 @@ global $thisstaff; </a> <?php - $closure_include = function($q) use ($thisstaff, $ost, $cfg) { + $closure_include = function($q, $children) { global $thisstaff, $ost, $cfg; include __FILE__; }; if ($hasChildren) { ?> <ul class="subMenuQ"> <?php - foreach ($children as $q) - $closure_include($q); + foreach ($children as $_) { + list($q, $childs) = $_; + if (!$q->isPrivate()) + $closure_include($q, $childs); + } // Include personal sub-queues $first_child = true; - foreach ($subq_searches as $q) { - if ($first_child) { + foreach ($children as $_) { + list($q, $childs) = $_; + if ($q->isPrivate()) { + if ($first_child) { $first_child = false; echo '<li class="personalQ"></li>'; + } + $closure_include($q, $childs); } - $closure_include($q); } ?> </ul> <?php diff --git a/scp/tickets.php b/scp/tickets.php index 450403f99..82e9b0703 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -445,19 +445,14 @@ $nav->setTabActive('tickets'); $nav->addSubNavInfo('jb-overflowmenu', 'customQ_nav'); // Fetch ticket queues organized by root and sub-queues -$queues = SavedQueue::queues() - ->filter(Q::any(array( - 'flags__hasbit' => CustomQueue::FLAG_PUBLIC, - 'staff_id' => $thisstaff->getId(), - ))) - ->exclude(['flags__hasbit' => CustomQueue::FLAG_DISABLED]) - ->getIterator(); +$queues = CustomQueue::getHierarchicalQueues($thisstaff); // Start with all the top-level (container) queues -foreach ($queues->findAll(array('parent_id' => 0)) -as $q) { - $nav->addSubMenu(function() use ($q, $queue) { - $selected = false; +foreach ($queues as $_) { + list($q, $children) = $_; + if ($q->isPrivate()) + continue; + $nav->addSubMenu(function() use ($q, $queue, $children) { // A queue is selected if it is the one being displayed. It is // "child" selected if its ID is in the path of the one selected $child_selected = $queue -- GitLab