Merge pull request #669 from Icinga/migrate-like-and-unlike-searches

migrate: Notify the user about the equal/like filter change
This commit is contained in:
Johannes Meyer 2023-06-15 15:22:31 +02:00 committed by GitHub
commit 690feba9df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 178 additions and 46 deletions

View file

@ -7,12 +7,15 @@ namespace Icinga\Module\Icingadb\Controllers;
use Exception;
use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Application\Hook;
use Icinga\Application\Icinga;
use Icinga\Exception\IcingaException;
use Icinga\Module\Icingadb\Compat\UrlMigrator;
use Icinga\Module\Icingadb\Forms\SetAsBackendForm;
use Icinga\Module\Icingadb\Hook\IcingadbSupportHook;
use Icinga\Module\Icingadb\Web\Controller;
use ipl\Html\HtmlString;
use ipl\Stdlib\Filter;
use ipl\Web\Filter\QueryString;
use ipl\Web\Url;
class MigrateController extends Controller
@ -65,6 +68,55 @@ class MigrateController extends Controller
$response->sendResponse();
}
public function searchUrlAction()
{
$this->assertHttpMethod('post');
if (! $this->getRequest()->isApiRequest()) {
$this->httpBadRequest('No API request');
}
if (
! preg_match('/([^;]*);?/', $this->getRequest()->getHeader('Content-Type'), $matches)
|| $matches[1] !== 'application/json'
) {
$this->httpBadRequest('No JSON content');
}
$traverseFilter = function ($filter) use (&$traverseFilter) {
if ($filter instanceof Filter\Chain) {
foreach ($filter as $child) {
$newChild = $traverseFilter($child);
if ($newChild !== null) {
$filter->replace($child, $newChild);
}
}
} elseif ($filter instanceof Filter\Equal) {
if (strpos($filter->getValue(), '*') !== false) {
return Filter::like($filter->getColumn(), $filter->getValue());
}
} elseif ($filter instanceof Filter\Unequal) {
if (strpos($filter->getValue(), '*') !== false) {
return Filter::unlike($filter->getColumn(), $filter->getValue());
}
}
};
$urls = $this->getRequest()->getPost();
$result = [];
foreach ($urls as $urlString) {
$url = Url::fromPath($urlString);
$filter = QueryString::parse($url->getQueryString());
$filter = $traverseFilter($filter) ?? $filter;
$result[] = $url->setQueryString(QueryString::render($filter))->getAbsoluteUrl();
}
$response = $this->getResponse()->json();
$response->setSuccessData($result);
$response->sendResponse();
}
public function checkboxStateAction()
{
$this->assertHttpMethod('get');
@ -98,7 +150,10 @@ class MigrateController extends Controller
}
$moduleSupportStates = [];
if ($this->Auth()->hasPermission('module/monitoring')) {
if (
Icinga::app()->getModuleManager()->hasEnabled('monitoring')
&& $this->Auth()->hasPermission('module/monitoring')
) {
$supportList = [];
foreach (Hook::all('Icingadb/IcingadbSupport') as $hook) {
/** @var IcingadbSupportHook $hook */

View file

@ -565,9 +565,5 @@ namespace Icinga\Module\Icingadb {
$this->provideJsFile('action-list.js');
$this->provideJsFile('loadmore.js');
$mg = Icinga::app()->getModuleManager();
if ($mg->hasEnabled('monitoring')) {
$this->provideJsFile('migrate.js');
}
$this->provideJsFile('migrate.js');
}

View file

@ -4,7 +4,6 @@
namespace Icinga\Module\Icingadb\Common;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Icingadb\Widget\StateBadge;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
@ -116,7 +115,9 @@ abstract class StateBadges extends BaseHtmlElement
}
if ($this->hasBaseFilter()) {
$url->addFilter(Filter::fromQueryString(QueryString::render($this->getBaseFilter())));
$urlParams = $url->getParams()->toArray(false);
$url->setQueryString(QueryString::render($this->getBaseFilter()))
->addParams($urlParams);
}
return new Link($content, $url);

View file

@ -5,7 +5,6 @@
namespace Icinga\Module\Icingadb\Widget\Detail;
use Icinga\Chart\Donut;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Icingadb\Common\Links;
use Icinga\Module\Icingadb\Widget\HostStateBadges;
use ipl\Html\ValidHtml;
@ -38,7 +37,7 @@ class HostStatistics extends ObjectStatistics
{
$url = Links::hosts();
if ($this->hasBaseFilter()) {
$url->addFilter(Filter::fromQueryString(QueryString::render($this->getBaseFilter())));
$url->setQueryString(QueryString::render($this->getBaseFilter()));
}
return new Link(

View file

@ -5,7 +5,6 @@
namespace Icinga\Module\Icingadb\Widget\Detail;
use Icinga\Chart\Donut;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Icingadb\Common\Links;
use Icinga\Module\Icingadb\Widget\ServiceStateBadges;
use ipl\Html\ValidHtml;
@ -42,7 +41,7 @@ class ServiceStatistics extends ObjectStatistics
{
$url = Links::services();
if ($this->hasBaseFilter()) {
$url->addFilter(Filter::fromQueryString(QueryString::render($this->getBaseFilter())));
$url->setQueryString(QueryString::render($this->getBaseFilter()));
}
return new Link(

View file

@ -5,7 +5,6 @@
namespace Icinga\Module\Icingadb\Widget;
use Icinga\Chart\Donut;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Icingadb\Common\BaseFilter;
use Icinga\Module\Icingadb\Common\Links;
use Icinga\Module\Icingadb\Model\HoststateSummary;
@ -42,9 +41,7 @@ class HostSummaryDonut extends Card
->addSlice($this->summary->hosts_unreachable_unhandled, ['class' => 'slice-state-unreachable'])
->addSlice($this->summary->hosts_pending, ['class' => 'slice-state-pending'])
->setLabelBig($this->summary->hosts_down_unhandled)
->setLabelBigUrl(Links::hosts()->addFilter(
Filter::fromQueryString(QueryString::render($this->getBaseFilter()))
)->addParams([
->setLabelBigUrl(Links::hosts()->setQueryString(QueryString::render($this->getBaseFilter()))->addParams([
'host.state.soft_state' => 1,
'host.state.is_handled' => 'n',
'sort' => 'host.state.last_state_change'

View file

@ -5,7 +5,6 @@
namespace Icinga\Module\Icingadb\Widget;
use Icinga\Chart\Donut;
use Icinga\Data\Filter\Filter;
use Icinga\Module\Icingadb\Common\BaseFilter;
use Icinga\Module\Icingadb\Common\Links;
use Icinga\Module\Icingadb\Model\ServicestateSummary;
@ -44,9 +43,7 @@ class ServiceSummaryDonut extends Card
->addSlice($this->summary->services_unknown_unhandled, ['class' => 'slice-state-unknown'])
->addSlice($this->summary->services_pending, ['class' => 'slice-state-pending'])
->setLabelBig($this->summary->services_critical_unhandled)
->setLabelBigUrl(Links::services()->addFilter(
Filter::fromQueryString(QueryString::render($this->getBaseFilter()))
)->addParams([
->setLabelBigUrl(Links::services()->setQueryString(QueryString::render($this->getBaseFilter()))->addParams([
'service.state.soft_state' => 2,
'service.state.is_handled' => 'n',
'sort' => 'service.state.last_state_change'

View file

@ -76,6 +76,9 @@
}
.suggestion-area {
display: flex;
flex-direction: column-reverse;
padding: .75em;
flex-grow: 1;
pointer-events: auto;
@ -90,12 +93,19 @@
}
p {
display: none;
margin-bottom: .5em;
color: @text-color-light;
}
form ~ .monitoring-migration-hint,
.search-migration-suggestions:not(:empty) + .search-migration-hint,
.monitoring-migration-suggestions:not(:empty) + .monitoring-migration-hint {
display: block;
}
& > button.close {
float: right;
margin-left: auto;
margin-top: 1em;
&:hover {
@ -105,7 +115,7 @@
ul {
padding: 0;
margin: .5em 0 0;
margin: 0;
list-style-type: none;
}
@ -116,6 +126,10 @@
&:last-of-type {
margin-bottom: 0;
}
&:first-of-type {
margin-top: 0;
}
}
li {
@ -143,7 +157,6 @@
}
form {
margin-top: 0.5em;
width: 100%;
.control-group {
@ -159,5 +172,10 @@
}
}
}
.search-migration-suggestions:not(:empty) ~ form,
.search-migration-suggestions:not(:empty) ~ .monitoring-migration-suggestions:not(:empty) {
margin-bottom: .5em;
}
}
}

View file

@ -11,9 +11,11 @@
const POPUP_HTML = '<div class="icinga-module module-icingadb">\n' +
' <div id="migrate-popup">\n' +
' <div class="suggestion-area">\n' +
' <p>Preview this in Icinga DB</p>\n' +
' <ul></ul>\n' +
' <button type="button" class="close">Don\'t show this again</button>\n' +
' <ul class="search-migration-suggestions"></ul>\n' +
' <p class="search-migration-hint">Miss some results? Try the link(s) below</p>\n' +
' <ul class="monitoring-migration-suggestions"></ul>\n' +
' <p class="monitoring-migration-hint">Preview this in Icinga DB</p>\n' +
' </div>\n' +
' <div class="minimizer"><i class="icon-"></i></div>\n' +
' </div>\n' +
@ -37,6 +39,7 @@
this.knownBackendSupport = {};
this.urlMigrationReadyState = null;
this.backendSupportReadyState = null;
this.searchMigrationReadyState = null;
this.backendSupportRelated = {};
this.$popup = null;
@ -48,6 +51,7 @@
// We don't want to ask the server to migrate non-monitoring urls
this.isMonitoringUrl = new RegExp('^' + icinga.config.baseUrl + '/monitoring/');
this.on('rendered', this.onRendered, this);
this.on('close-column', this.onColumnClose, this);
this.on('click', '#migrate-popup button.close', this.onClose, this);
@ -107,7 +111,8 @@
};
Migrate.prototype.prepareMigration = function($target) {
let urls = {};
let monitoringUrls = {};
let searchUrls = {};
let modules = {}
$target.each((_, container) => {
@ -115,14 +120,18 @@
let href = $container.data('icingaUrl');
let containerId = $container.attr('id');
if (typeof href !== 'undefined' && href.match(this.isMonitoringUrl)) {
if (!! href) {
if (
typeof this.previousMigrations[containerId] !== 'undefined'
&& this.previousMigrations[containerId] === href
) {
delete this.previousMigrations[containerId];
} else {
urls[containerId] = href;
if (href.match(this.isMonitoringUrl)) {
monitoringUrls[containerId] = href;
} else if ($container.find('[data-enrichment-type="search-bar"]').length) {
searchUrls[containerId] = href;
}
}
}
@ -132,13 +141,20 @@
}
});
if (Object.keys(urls).length) {
if (Object.keys(monitoringUrls).length) {
this.setUrlMigrationReadyState(false);
this.migrateMonitoringUrls(urls);
this.migrateUrls(monitoringUrls, 'monitoring');
} else {
this.setUrlMigrationReadyState(null);
}
if (Object.keys(searchUrls).length) {
this.setSearchMigrationReadyState(false);
this.migrateUrls(searchUrls, 'search');
} else {
this.setSearchMigrationReadyState(null);
}
if (Object.keys(modules).length) {
this.setBackendSupportReadyState(false);
this.prepareBackendCheckboxForm(modules);
@ -146,7 +162,11 @@
this.setBackendSupportReadyState(null);
}
if (this.urlMigrationReadyState === null && this.backendSupportReadyState === null) {
if (
this.urlMigrationReadyState === null
&& this.backendSupportReadyState === null
&& this.searchMigrationReadyState === null
) {
this.cleanupPopup();
}
};
@ -221,7 +241,7 @@
_this.knownMigrations[containerUrl] = false;
}
if (_this.Popup().find('li').length === 1) {
if (_this.Popup().find('li').length === 1 && ! _this.Popup().find('#setAsBackendForm').length) {
_this.hidePopup(function () {
// Let the transition finish first, looks cleaner
$suggestion.remove();
@ -246,7 +266,7 @@
}
};
Migrate.prototype.migrateMonitoringUrls = function(urls) {
Migrate.prototype.migrateUrls = function(urls, type) {
var _this = this,
containerIds = [],
containerUrls = [];
@ -258,24 +278,34 @@
}
});
let endpoint, changeCallback;
if (type === 'monitoring') {
endpoint = 'monitoring-url';
changeCallback = this.changeUrlMigrationReadyState.bind(this);
} else {
endpoint = 'search-url';
changeCallback = this.changeSearchMigrationReadyState.bind(this);
}
if (containerUrls.length) {
var req = $.ajax({
context : this,
type : 'post',
url : this.icinga.config.baseUrl + '/icingadb/migrate/monitoring-url',
url : this.icinga.config.baseUrl + '/icingadb/migrate/' + endpoint,
headers : { 'Accept': 'application/json' },
contentType : 'application/json',
data : JSON.stringify(containerUrls)
});
req.urls = urls;
req.suggestionType = type;
req.urlIndexToContainerId = containerIds;
req.done(this.processUrlMigrationResults);
req.always(() => this.changeUrlMigrationReadyState(true));
req.always(() => changeCallback(true));
} else {
// All urls have already been migrated once, show popup immediately
this.addSuggestions(urls);
this.changeUrlMigrationReadyState(true);
this.addSuggestions(urls, type);
changeCallback(true);
}
};
@ -298,7 +328,7 @@
_this.knownMigrations[req.urls[containerId]] = migratedUrl;
});
this.addSuggestions(req.urls);
this.addSuggestions(req.urls, req.suggestionType);
};
Migrate.prototype.prepareBackendCheckboxForm = function(modules) {
@ -373,7 +403,7 @@
$form.attr('data-base-target', 'migrate-popup-backend-submit-blackhole');
$form.append('<div id="migrate-popup-backend-submit-blackhole"></div>');
this.Popup().find('.suggestion-area > ul').after($form);
this.Popup().find('.monitoring-migration-suggestions').before($form);
} else {
let $newForm = $(html);
$form.find('[name=backend]').prop('checked', $newForm.find('[name=backend]').is(':checked'));
@ -382,10 +412,17 @@
this.showPopup();
}
Migrate.prototype.addSuggestions = function(urls) {
Migrate.prototype.addSuggestions = function(urls, type) {
var where;
if (type === 'monitoring') {
where = '.monitoring-migration-suggestions';
} else {
where = '.search-migration-suggestions';
}
var _this = this,
hasSuggestions = false,
$ul = this.Popup().find('.suggestion-area > ul');
$ul = this.Popup().find('.suggestion-area > ul' + where);
$.each(urls, function (containerId, containerUrl) {
// No urls for which the user clicked "No" or an error occurred and only migrated urls please
if (_this.knownMigrations[containerUrl] !== false && _this.knownMigrations[containerUrl] !== containerUrl) {
@ -425,6 +462,9 @@
if (hasSuggestions) {
this.showPopup();
if (type === 'search') {
this.maximizePopup();
}
}
};
@ -442,6 +482,8 @@
|| _this.knownMigrations[containerUrl] === false
// Already migrated or no migration necessary
|| containerUrl === _this.knownMigrations[containerUrl]
// The container URL changed
|| containerUrl !== $suggestion.data('containerUrl')
) {
toBeRemoved.push($suggestion);
}
@ -478,7 +520,7 @@
let hasBackendForm = this.cleanupBackendForm();
if (hasBackendForm !== true && this.Popup().find('li').length === toBeRemoved.length) {
this.hidePopup(function () {
this.hidePopup(() => {
// Let the transition finish first, looks cleaner
$.each(toBeRemoved, function (_, $suggestion) {
$suggestion.remove();
@ -496,13 +538,20 @@
if (typeof hasBackendForm === 'object') {
hasBackendForm.remove();
}
// Let showPopup() handle the automatic minimization in case all search suggestions have been removed
this.showPopup();
}
};
Migrate.prototype.showPopup = function() {
var $popup = this.Popup();
if (this.storage.get('minimized')) {
$popup.addClass('active minimized hidden');
if (this.storage.get('minimized') && ! this.forceFullyMaximized()) {
if (this.isShown()) {
this.minimizePopup();
} else {
$popup.addClass('active minimized hidden');
}
} else {
$popup.addClass('active');
}
@ -532,6 +581,10 @@
this.Popup().removeClass('minimized hidden');
};
Migrate.prototype.forceFullyMaximized = function() {
return this.Popup().find('.search-migration-suggestions:not(:empty)').length > 0;
};
Migrate.prototype.togglePopup = function() {
if (this.Popup().is('.minimized')) {
this.maximizePopup();
@ -549,7 +602,23 @@
Migrate.prototype.changeUrlMigrationReadyState = function (state) {
this.setUrlMigrationReadyState(state);
if (this.backendSupportReadyState !== false) {
if (this.backendSupportReadyState !== false && this.searchMigrationReadyState !== false) {
this.searchMigrationReadyState = null;
this.backendSupportReadyState = null;
this.urlMigrationReadyState = null;
this.cleanupPopup();
}
};
Migrate.prototype.setSearchMigrationReadyState = function (state) {
this.searchMigrationReadyState = state;
};
Migrate.prototype.changeSearchMigrationReadyState = function (state) {
this.setSearchMigrationReadyState(state);
if (this.backendSupportReadyState !== false && this.urlMigrationReadyState !== false) {
this.searchMigrationReadyState = null;
this.backendSupportReadyState = null;
this.urlMigrationReadyState = null;
this.cleanupPopup();
@ -563,7 +632,8 @@
Migrate.prototype.changeBackendSupportReadyState = function (state) {
this.setBackendSupportReadyState(state);
if (this.urlMigrationReadyState !== false) {
if (this.urlMigrationReadyState !== false && this.searchMigrationReadyState !== false) {
this.searchMigrationReadyState = null;
this.backendSupportReadyState = null;
this.urlMigrationReadyState = null;
this.cleanupPopup();