Merge branch 'feature/less-costly-count-queries-for-history-views-8615'

resolves #8615
This commit is contained in:
Johannes Meyer 2015-08-06 13:11:50 +02:00
commit 8c80839cbc
13 changed files with 156 additions and 54 deletions

View File

@ -47,16 +47,6 @@ class DbQuery extends SimpleQuery
*/ */
protected $useSubqueryCount = false; protected $useSubqueryCount = false;
/**
* Set the count maximum
*
* If the count maximum is set, count queries will not count more than that many rows. You should set this
* property only for really heavy queries.
*
* @var int
*/
protected $maxCount;
/** /**
* Count query result * Count query result
* *
@ -343,9 +333,6 @@ class DbQuery extends SimpleQuery
$columns = array('cnt' => 'COUNT(*)'); $columns = array('cnt' => 'COUNT(*)');
return $this->db->select()->from($count, $columns); return $this->db->select()->from($count, $columns);
} }
if ($this->maxCount !== null) {
return $this->db->select()->from($count->limit($this->maxCount));
}
$count->columns(array('cnt' => 'COUNT(*)')); $count->columns(array('cnt' => 'COUNT(*)'));
return $count; return $count;

View File

@ -143,6 +143,16 @@ class SimpleQuery implements QueryInterface, Queryable, Iterator
return $this->ds; return $this->ds;
} }
/**
* Return the current position of this query's iterator
*
* @return int
*/
public function getIteratorPosition()
{
return $this->iteratorPosition;
}
/** /**
* Start or rewind the iteration * Start or rewind the iteration
*/ */

View File

@ -605,6 +605,16 @@ class RepositoryQuery implements QueryInterface, SortRules, Iterator
return $this->query->count(); return $this->query->count();
} }
/**
* Return the current position of this query's iterator
*
* @return int
*/
public function getIteratorPosition()
{
return $this->query->getIteratorPosition();
}
/** /**
* Start or rewind the iteration * Start or rewind the iteration
*/ */

View File

@ -576,7 +576,6 @@ class Monitoring_ListController extends Controller
$this->view->history = $query; $this->view->history = $query;
$this->setupLimitControl(); $this->setupLimitControl();
$this->setupPaginationControl($this->view->history);
$this->setupSortControl(array( $this->setupSortControl(array(
'timestamp' => $this->translate('Occurence') 'timestamp' => $this->translate('Occurence')
), $query); ), $query);

View File

@ -17,6 +17,14 @@ function contactsLink($match, $view) {
$self = $this; $self = $this;
$url = $this->url();
$limit = (int) $url->getParam('limit', 25);
if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) {
$page = 1;
}
$history->limit($limit * $page);
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs; ?> <?= $this->tabs; ?>
@ -24,14 +32,16 @@ if (! $this->compact): ?>
<h1><?= $this->translate('This Host\'s Event History'); ?></h1> <h1><?= $this->translate('This Host\'s Event History'); ?></h1>
<?= $this->sortBox; ?> <?= $this->sortBox; ?>
<?= $this->limiter; ?> <?= $this->limiter; ?>
<?= $this->paginator; ?> <a class="load-more-hint" href="#load-more">
<?= $this->translate('Scroll to the bottom of this page to load additional events'); ?>
</a>
<?= $this->filterEditor; ?> <?= $this->filterEditor; ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<table data-base-target="_next" class="action objecthistory"> <table data-base-target="_next" class="action objecthistory">
<tbody> <tbody>
<?php foreach ($history as $event): ?> <?php foreach ($history->peekAhead() as $event): ?>
<?php <?php
$stateClass = 'invalid'; $stateClass = 'invalid';
$msg = $this->escape($event->output); $msg = $this->escape($event->output);
@ -102,6 +112,9 @@ if (! $this->compact): ?>
?> ?>
<tr class="state <?= $stateClass; ?>"> <tr class="state <?= $stateClass; ?>">
<td class="state"> <td class="state">
<?php if ($history->getIteratorPosition() % $limit === 0): ?>
<a id="page-<?= $history->getIteratorPosition() / $limit + 1; ?>"></a>
<?php endif ?>
<strong><?= $this->escape($title); ?></strong> <strong><?= $this->escape($title); ?></strong>
<br> <br>
<?= date('d.m. H:i', $event->timestamp); ?> <?= date('d.m. H:i', $event->timestamp); ?>
@ -139,5 +152,17 @@ if (! $this->compact): ?>
</table> </table>
<?php if (! $history->hasResult()): ?> <?php if (! $history->hasResult()): ?>
<?= $this->translate('No history events found matching the filter'); ?> <?= $this->translate('No history events found matching the filter'); ?>
<?php elseif ($history->hasMore()): ?>
<div class="load-more-container"><?= $this->qlink(
$this->translate('Load More'),
$url->setAnchor('page-' . ($page + 1)),
array(
'page' => $page + 1,
),
array(
'id' => 'load-more',
'class' => 'pull-right load-more button-like'
)
); ?></div>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -2,21 +2,29 @@
use Icinga\Module\Monitoring\Object\Host; use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\Service; use Icinga\Module\Monitoring\Object\Service;
$history->peekAhead($this->compact); $url = $this->url();
$limit = (int) $url->getParam('limit', 25);
if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) {
$page = 1;
}
$history->limit($limit * $page);
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs; ?> <?= $this->tabs; ?>
<?= $this->sortBox; ?> <?= $this->sortBox; ?>
<?= $this->limiter; ?> <?= $this->limiter; ?>
<?= $this->paginator; ?> <a class="load-more-hint" href="#load-more">
<?= $this->translate('Scroll to the bottom of this page to load additional events'); ?>
</a>
<?= $this->filterEditor; ?> <?= $this->filterEditor; ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<table data-base-target="_next" class="action"> <table data-base-target="_next" class="action">
<tbody> <tbody>
<?php foreach ($history as $event): ?> <?php foreach ($history->peekAhead() as $event): ?>
<?php <?php
$icon = 'help'; $icon = 'help';
$msg = $event->output; $msg = $event->output;
@ -71,6 +79,9 @@ if (! $this->compact): ?>
?> ?>
<tr class="state <?= $stateName; ?>"> <tr class="state <?= $stateName; ?>">
<td class="state"> <td class="state">
<?php if ($history->getIteratorPosition() % $limit === 0): ?>
<a id="page-<?= $history->getIteratorPosition() / $limit + 1; ?>"></a>
<?php endif ?>
<strong><?= $this->escape($title); ?></strong> <strong><?= $this->escape($title); ?></strong>
<br> <br>
<?= $this->timeAgo($event->timestamp, $this->compact); ?> <?= $this->timeAgo($event->timestamp, $this->compact); ?>
@ -95,14 +106,28 @@ if (! $this->compact): ?>
<?php if (! $history->hasResult()): ?> <?php if (! $history->hasResult()): ?>
<?= $this->translate('No history events found matching the filter'); ?> <?= $this->translate('No history events found matching the filter'); ?>
<?php elseif ($history->hasMore()): ?> <?php elseif ($history->hasMore()): ?>
<?php if ($this->compact): ?>
<?= $this->qlink( <?= $this->qlink(
$this->translate('Show More'), $this->translate('Show More'),
$this->url()->without(array('view', 'limit')), $url->without(array('view', 'limit')),
null, null,
array( array(
'data-base-target' => '_next', 'data-base-target' => '_next',
'class' => 'pull-right show-more' 'class' => 'pull-right show-more'
) )
); ?> ); ?>
<?php else: ?>
<div class="load-more-container"><?= $this->qlink(
$this->translate('Load More'),
$url->setAnchor('page-' . ($page + 1)),
array(
'page' => $page + 1,
),
array(
'id' => 'load-more',
'class' => 'pull-right load-more button-like'
)
); ?></div>
<?php endif ?>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -16,6 +16,14 @@ function contactsLink($match, $view) {
$self = $this; $self = $this;
$url = $this->url();
$limit = (int) $url->getParam('limit', 25);
if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) {
$page = 1;
}
$history->limit($limit * $page);
if (! $this->compact): ?> if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs; ?> <?= $this->tabs; ?>
@ -23,14 +31,16 @@ if (! $this->compact): ?>
<h1><?= $this->translate('This Service\'s Event History'); ?></h1> <h1><?= $this->translate('This Service\'s Event History'); ?></h1>
<?= $this->sortBox; ?> <?= $this->sortBox; ?>
<?= $this->limiter; ?> <?= $this->limiter; ?>
<?= $this->paginator; ?> <a class="load-more-hint" href="#load-more">
<?= $this->translate('Scroll to the bottom of this page to load additional events'); ?>
</a>
<?= $this->filterEditor; ?> <?= $this->filterEditor; ?>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="content"> <div class="content">
<table data-base-target="_next" class="action objecthistory"> <table data-base-target="_next" class="action objecthistory">
<tbody> <tbody>
<?php foreach ($history as $event): ?> <?php foreach ($history->peekAhead() as $event): ?>
<?php <?php
$stateClass = 'invalid'; $stateClass = 'invalid';
$msg = $this->escape($event->output); $msg = $this->escape($event->output);
@ -100,6 +110,9 @@ if (! $this->compact): ?>
?> ?>
<tr class="state <?= $stateClass; ?>"> <tr class="state <?= $stateClass; ?>">
<td class="state"> <td class="state">
<?php if ($history->getIteratorPosition() % $limit === 0): ?>
<a id="page-<?= $history->getIteratorPosition() / $limit + 1; ?>"></a>
<?php endif ?>
<strong><?= $this->escape($title); ?></strong> <strong><?= $this->escape($title); ?></strong>
<br> <br>
<?= date('d.m. H:i', $event->timestamp); ?> <?= date('d.m. H:i', $event->timestamp); ?>
@ -121,5 +134,17 @@ if (! $this->compact): ?>
</table> </table>
<?php if (! $history->hasResult()): ?> <?php if (! $history->hasResult()): ?>
<?= $this->translate('No history events found matching the filter'); ?> <?= $this->translate('No history events found matching the filter'); ?>
<?php elseif ($history->hasMore()): ?>
<div class="load-more-container"><?= $this->qlink(
$this->translate('Load More'),
$url->setAnchor('page-' . ($page + 1)),
array(
'page' => $page + 1,
),
array(
'id' => 'load-more',
'class' => 'pull-right load-more button-like'
)
); ?></div>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -115,7 +115,7 @@ class HostgroupsummaryQuery extends IdoQuery
$this->subQueries[] = $services; $this->subQueries[] = $services;
$this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL);
$this->select->from(array('statussummary' => $this->summaryQuery), array()); $this->select->from(array('statussummary' => $this->summaryQuery), array());
$this->group(array('hostgroup_name', 'hostgroup_alias')); $this->group(array('statussummary.hostgroup_name', 'statussummary.hostgroup_alias'));
$this->joinedVirtualTables['hoststatussummary'] = true; $this->joinedVirtualTables['hoststatussummary'] = true;
} }

View File

@ -69,6 +69,16 @@ abstract class DataView implements QueryInterface, SortRules, IteratorAggregate
return $this->getQuery(); return $this->getQuery();
} }
/**
* Return the current position of the result set's iterator
*
* @return int
*/
public function getIteratorPosition()
{
return $this->query->getIteratorPosition();
}
/** /**
* Get the query name this data view relies on * Get the query name this data view relies on
* *

View File

@ -62,7 +62,18 @@ ul.pagination {
cursor: default; cursor: default;
} }
a.show-more { a.show-more, a.load-more {
display: block; display: block;
margin: 0.5em; margin: 0.5em;
}
a.load-more-hint {
display: inline-block;
margin-left: 1em;
}
div.load-more-container {
display: table;
margin: 0 auto;
margin-top: 0.5em;
} }

View File

@ -322,21 +322,6 @@
return false; return false;
}, },
/**
* Handle anchor, i.e. focus the element which is referenced by the anchor
*
* @param {string} query jQuery selector
*/
handleAnchor: function(query) {
var $element = $(query);
if ($element.length > 0) {
if (typeof $element.attr('tabindex') === 'undefined') {
$element.attr('tabindex', -1);
}
$element.focus();
}
},
/** /**
* Someone clicked a link or tr[href] * Someone clicked a link or tr[href]
*/ */
@ -404,7 +389,7 @@
// This is an anchor only // This is an anchor only
if (href.substr(0, 1) === '#' && href.length > 1 if (href.substr(0, 1) === '#' && href.length > 1
&& href.substr(1, 1) !== '!') { && href.substr(1, 1) !== '!') {
self.handleAnchor(href); icinga.ui.focusElement(href.substr(1), $a.closest('.container'));
return; return;
} }
@ -434,7 +419,7 @@
formerUrl = $target.data('icingaUrl'); formerUrl = $target.data('icingaUrl');
if (typeof formerUrl !== 'undefined' && formerUrl.split(/#/)[0] === href.split(/#/)[0]) { if (typeof formerUrl !== 'undefined' && formerUrl.split(/#/)[0] === href.split(/#/)[0]) {
icinga.ui.scrollContainerToAnchor($target, href.split(/#/)[1]); icinga.ui.focusElement(href.split(/#/)[1], $target);
$target.data('icingaUrl', href); $target.data('icingaUrl', href);
if (formerUrl !== href) { if (formerUrl !== href) {
icinga.history.pushCurrentState(); icinga.history.pushCurrentState();

View File

@ -560,7 +560,7 @@
oldNotifications.appendTo($('#notifications')); oldNotifications.appendTo($('#notifications'));
} }
if (url.match(/#/)) { if (url.match(/#/)) {
this.icinga.ui.scrollContainerToAnchor(req.$target, url.split(/#/)[1]); this.icinga.ui.focusElement(url.split(/#/)[1], req.$target);
} }
if (newBody) { if (newBody) {
this.icinga.ui.fixDebugVisibility().triggerWindowResize(); this.icinga.ui.fixDebugVisibility().triggerWindowResize();

View File

@ -122,6 +122,33 @@
return this; return this;
}, },
/**
* Focus the given element and scroll to its position
*
* @param {string} element The name or id of the element to focus
* @param {object} $container The container containing the element
*/
focusElement: function(element, $container) {
var $element = $('#' + element, $container);
if (! $element.length) {
// The name attribute is actually deprecated, on anchor tags,
// but we'll possibly handle links from another source
// (module etc) so that's used as a fallback
$element = $('[name="' + element.replace(/'/, '\\\'') + '"]', $container);
}
if ($element.length) {
if (typeof $element.attr('tabindex') === 'undefined') {
$element.attr('tabindex', -1);
}
$element.focus();
$container.scrollTop(0);
$container.scrollTop($element.first().position().top);
}
},
moveToLeft: function () { moveToLeft: function () {
var col2 = this.cutContainer($('#col2')); var col2 = this.cutContainer($('#col2'));
var kill = this.cutContainer($('#col1')); var kill = this.cutContainer($('#col1'));
@ -159,18 +186,6 @@
$col.data('icingaModule', backup['data']['data-icinga-module']); $col.data('icingaModule', backup['data']['data-icinga-module']);
}, },
scrollContainerToAnchor: function ($container, anchorName) {
// TODO: Generic issue -> we probably should escape attribute value selectors!?
var $anchor = $("a[name='" + anchorName.replace(/'/, '\\\'') + "']", $container);
if ($anchor.length) {
$container.scrollTop(0);
$container.scrollTop($anchor.first().position().top);
this.icinga.logger.debug('Scrolling ', $container, ' to ', anchorName);
} else {
this.icinga.logger.info('Anchor "' + anchorName + '" not found in ', $container);
}
},
triggerWindowResize: function () { triggerWindowResize: function () {
this.onWindowResize({data: {self: this}}); this.onWindowResize({data: {self: this}});
}, },