Skip to content

Commit

Permalink
Show root problem list for objects with problem and are part of depen…
Browse files Browse the repository at this point in the history
…dency (#1057)

ref #1050

blocked by: #1055
blocked by: Icinga/ipl-web#231,  Icinga/ipl-web#234
  • Loading branch information
nilmerg authored Oct 31, 2024
2 parents 98fc692 + bfb4c51 commit a7fb07b
Show file tree
Hide file tree
Showing 20 changed files with 672 additions and 60 deletions.
16 changes: 9 additions & 7 deletions application/controllers/ServiceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ public function init()
$name = $this->params->getRequired('name');
$hostName = $this->params->getRequired('host.name');

$query = Service::on($this->getDb())->with([
'state',
'icon_image',
'host',
'host.state',
'timeperiod'
]);
$query = Service::on($this->getDb())
->withColumns(['has_problematic_parent'])
->with([
'state',
'icon_image',
'host',
'host.state',
'timeperiod'
]);
$query
->setResultSetClass(VolatileStateResults::class)
->filter(Filter::all(
Expand Down
100 changes: 63 additions & 37 deletions library/Icingadb/Common/StateBadges.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace Icinga\Module\Icingadb\Common;

use InvalidArgumentException;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
use ipl\Html\HtmlElement;
use ipl\Stdlib\BaseFilter;
use ipl\Stdlib\Filter;
use ipl\Web\Filter\QueryString;
use ipl\Web\Url;
use ipl\Web\Widget\Link;
use ipl\Web\Widget\StateBadge;
Expand All @@ -26,7 +27,7 @@ abstract class StateBadges extends BaseHtmlElement
/** @var string Prefix */
protected $prefix;

/** @var Url Badge link */
/** @var ?Url Badge link */
protected $url;

protected $tag = 'ul';
Expand All @@ -46,13 +47,6 @@ public function __construct($item)
$this->url = $this->getBaseUrl();
}

/**
* Get the badge base URL
*
* @return Url
*/
abstract protected function getBaseUrl(): Url;

/**
* Get the type of the items
*
Expand All @@ -67,21 +61,36 @@ abstract protected function getType(): string;
*/
abstract protected function getPrefix(): string;

/**
* Get the badge base URL
*
* @return ?Url
*/
protected function getBaseUrl(): ?Url
{
return null;
}

/**
* Get the integer of the given state text
*
* @param string $state
*
* @return int
*
* @throws InvalidArgumentException if the given state is not valid
*/
abstract protected function getStateInt(string $state): int;
protected function getStateInt(string $state): int
{
throw new InvalidArgumentException(sprintf('%s is not a valid state', $state));
}

/**
* Get the badge URL
*
* @return Url
* @return ?Url
*/
public function getUrl(): Url
public function getUrl(): ?Url
{
return $this->url;
}
Expand All @@ -108,7 +117,7 @@ public function setUrl(Url $url): self
*
* @return Link
*/
public function createLink($content, Filter\Rule $filter = null): Link
protected function createLink($content, Filter\Rule $filter = null): Link
{
$url = clone $this->getUrl();

Expand All @@ -135,18 +144,23 @@ public function createLink($content, Filter\Rule $filter = null): Link
*
* @return ?BaseHtmlElement
*/
protected function createBadge(string $state)
protected function createBadge(string $state): ?BaseHtmlElement
{
$key = $this->prefix . "_{$state}";
if (empty($this->item->$key)) {
return null;
}

if (isset($this->item->$key) && $this->item->$key) {
return Html::tag('li', $this->createLink(
new StateBadge($this->item->$key, $state),
$stateBadge = new StateBadge($this->item->$key, $state);

if ($this->url !== null) {
$this->createLink(
$stateBadge,
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state))
));
);
}

return null;
return new HtmlElement('li', null, $stateBadge);
}

/**
Expand All @@ -156,34 +170,46 @@ protected function createBadge(string $state)
*
* @return ?BaseHtmlElement
*/
protected function createGroup(string $state)
protected function createGroup(string $state): ?BaseHtmlElement
{
$content = [];
$handledKey = $this->prefix . "_{$state}_handled";
$unhandledKey = $this->prefix . "_{$state}_unhandled";

if (isset($this->item->$unhandledKey) && $this->item->$unhandledKey) {
$content[] = Html::tag('li', $this->createLink(
new StateBadge($this->item->$unhandledKey, $state),
Filter::all(
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
Filter::equal($this->type . '.state.is_handled', 'n'),
Filter::equal($this->type . '.state.is_reachable', 'y')
)
));
$unhandledStateBadge = new StateBadge($this->item->$unhandledKey, $state);

if ($this->url !== null) {
$unhandledStateBadge = $this->createLink(
$unhandledStateBadge,
Filter::all(
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
Filter::equal($this->type . '.state.is_handled', 'n'),
Filter::equal($this->type . '.state.is_reachable', 'y')
)
);
}

$content[] = new HtmlElement('li', null, $unhandledStateBadge);
}

if (isset($this->item->$handledKey) && $this->item->$handledKey) {
$content[] = Html::tag('li', $this->createLink(
new StateBadge($this->item->$handledKey, $state, true),
Filter::all(
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
Filter::any(
Filter::equal($this->type . '.state.is_handled', 'y'),
Filter::equal($this->type . '.state.is_reachable', 'n')
$handledStateBadge = new StateBadge($this->item->$handledKey, $state, true);

if ($this->url !== null) {
$handledStateBadge = $this->createLink(
$handledStateBadge,
Filter::all(
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
Filter::any(
Filter::equal($this->type . '.state.is_handled', 'y'),
Filter::equal($this->type . '.state.is_reachable', 'n')
)
)
)
));
);
}

$content[] = new HtmlElement('li', null, $handledStateBadge);
}

if (empty($content)) {
Expand Down
94 changes: 94 additions & 0 deletions library/Icingadb/Model/Behavior/HasProblematicParent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

/* Icinga DB Web | (c) 2024 Icinga GmbH | GPLv2 */

namespace Icinga\Module\Icingadb\Model\Behavior;

use Icinga\Module\Icingadb\Model\DependencyEdge;
use ipl\Orm\AliasedExpression;
use ipl\Orm\ColumnDefinition;
use ipl\Orm\Exception\InvalidColumnException;
use ipl\Orm\Query;
use ipl\Sql\Expression;
use ipl\Stdlib\Filter;
use ipl\Orm\Contract\RewriteColumnBehavior;
use ipl\Orm\Contract\QueryAwareBehavior;

/**
* Behavior to check if the service has a problematic parent
*/
class HasProblematicParent implements RewriteColumnBehavior, QueryAwareBehavior
{
/** @var Query */
protected $query;

public function setQuery(Query $query): self
{
$this->query = $query;

return $this;
}

public function rewriteColumn($column, ?string $relation = null): ?AliasedExpression
{
if (! $this->isSelectableColumn($column)) {
return null;
}

$resolver = $this->query->getResolver();
if ($relation !== null) {
$serviceTableAlias = $resolver->getAlias($resolver->resolveRelation($relation)->getTarget());
$column = $resolver->qualifyColumnAlias($column, $serviceTableAlias);
} else {
$serviceTableAlias = $resolver->getAlias($this->query->getModel());
}

$subQueryModel = new DependencyEdge();
$subQuery = (new Query())
->setDb($this->query->getDb())
->setModel($subQueryModel)
->columns([new Expression('1')])
->utilize('from')
->limit(1)
->filter(Filter::equal('dependency.state.failed', 'y'));

$subQueryResolver = $subQuery->getResolver()->setAliasPrefix('hpp_');
$subQueryTarget = $subQueryResolver->resolveRelation($subQueryModel->getTableName() . '.from')->getTarget();
$targetForeignKey = $subQueryResolver->qualifyColumn(
'service_id',
$subQueryResolver->getAlias($subQueryTarget)
);

$subQuery->getSelectBase()
->where("$targetForeignKey = {$resolver->qualifyColumn('id', $serviceTableAlias)}");

[$select, $values] = $this->query->getDb()
->getQueryBuilder()
->assembleSelect($subQuery->assembleSelect());

return new AliasedExpression(
$this->query->getDb()->quoteIdentifier([$column]),
"($select)",
null,
...$values
);
}

public function isSelectableColumn(string $name): bool
{
return $name === 'has_problematic_parent';
}

public function rewriteColumnDefinition(ColumnDefinition $def, string $relation): void
{
}

public function rewriteCondition(Filter\Condition $condition, $relation = null)
{
$column = substr($condition->getColumn(), strlen($relation ?? ''));

if ($this->isSelectableColumn($column)) {
throw new InvalidColumnException($column, $this->query->getModel());
}
}
}
3 changes: 2 additions & 1 deletion library/Icingadb/Model/RedundancyGroupState.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public function createRelations(Relations $relations): void

public function getStateText(): string
{
return $this->failed ? 'problem' : 'ok';
// The method should only be called to fake state balls and not to show the group's state
return $this->failed ? 'unreachable' : 'reachable';
}
}
Loading

0 comments on commit a7fb07b

Please sign in to comment.