From 05cadc6a01db16bf4490566c9ee2103736c4d1b4 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Wed, 20 Mar 2024 12:25:57 +0100 Subject: [PATCH 1/7] Implement object hook to get event object information from respective sources --- .../Hook/ObjectsRendererHook.php | 211 ++++++++++++++++++ library/Notifications/Model/Objects.php | 11 +- .../Widget/Detail/EventDetail.php | 34 +-- .../Widget/Detail/IncidentDetail.php | 36 +-- .../Widget/ItemList/EventList.php | 10 + .../Widget/ItemList/IncidentList.php | 13 ++ 6 files changed, 240 insertions(+), 75 deletions(-) create mode 100644 library/Notifications/Hook/ObjectsRendererHook.php diff --git a/library/Notifications/Hook/ObjectsRendererHook.php b/library/Notifications/Hook/ObjectsRendererHook.php new file mode 100644 index 00000000..54dc28a9 --- /dev/null +++ b/library/Notifications/Hook/ObjectsRendererHook.php @@ -0,0 +1,211 @@ + ['object ID' => [object ID tags]]]. + * + * @var array>> + */ + private static $objectIdTags = []; + + /** + * Array of HTMLs for objects with their corresponding object IDs as keys + * + * It has the following structure : ['object ID' => registered HTML for the object name]. + * + * @var array + */ + private static $objectNameHtmls = []; + + /** + * Get the HTML for the object names for the objects using the object ID tags + * + * @param array> $objectIdTags Array of object ID tags of the objects belonging to the source + * + * @return Generator, ValidHtml> Generator for object name HTMLs with their object ID tags + * as keys + */ + abstract public function getHtmlForObjectNames(array $objectIdTags): Generator; + + /** + * Get the source type of the objects + * + * @return string + */ + abstract public function getSourceType(): string; + + /** + * Create the object link for the given object ID tag + * + * @param array $objectIdTag + * + * @return ValidHtml + */ + abstract public function createObjectLink(array $objectIdTag): ValidHtml; + + /** + * Register object ID tags to the cache + * + * @param Objects $obj + * + * @return void + */ + final public static function register(Objects $obj): void + { + self::$objectIdTags[$obj->source->type][$obj->id] = $obj->id_tags; + } + + /** + * Load HTMLs to be rendered for the object names to the cache using the cached objects + * + * @return void + */ + final public static function load(): void + { + self::prepare(self::$objectIdTags); + + self::$objectIdTags = []; + } + + /** + * Prepare the objects to be rendered using the given object ID tags for each source + * + * The supplied object ID tags must have the following structure: + * ['object source type' => ['object ID' => [object ID tags]]]. + * + * @param array>> $objectIdTags Array of object ID tags for each source + * + * @return void + */ + private static function prepare(array $objectIdTags): void + { + $idTagToObjectIdMap = []; + foreach ($objectIdTags as $sourceType => $objects) { + foreach ($objects as $objectId => $tags) { + if (! isset(self::$objectNameHtmls[$objectId])) { + $idTagToObjectIdMap[$sourceType][] = [$objectId, $tags]; + } + } + } + + $objectDisplayNames = []; + + /** @var self $hook */ + foreach (Hook::all('Notifications\\ObjectsRenderer') as $hook) { + $source = $hook->getSourceType(); + + if (isset($idTagToObjectIdMap[$source])) { + try { + $objectIDTagsForSource = array_map( + function ($object) { + return $object[1]; + }, + $idTagToObjectIdMap[$source] + ); + + /** @var array $objectIdTag */ + foreach ($hook->getHtmlForObjectNames($objectIDTagsForSource) as $objectIdTag => $validHtml) { + foreach ($idTagToObjectIdMap[$source] as $key => $val) { + $diff = array_intersect_assoc($val[1], $objectIdTag); + if (count($diff) === count($val[1])) { + unset($idTagToObjectIdMap[$key]); + $objectDisplayNames[$val[0]] = $validHtml; + + continue 2; + } + } + } + } catch (Exception $e) { + Logger::error('Failed to load hook %s:', get_class($hook), $e); + } + } + } + + self::$objectNameHtmls += $objectDisplayNames; + } + + /** + * Get the object name of the given object + * + * If an HTML for the object name is not loaded, it is prepared using object ID tags and the same is returned. + * + * @param Objects $obj + * + * @return ValidHtml + */ + final public static function getObjectName(Objects $obj): ValidHtml + { + $objId = $obj->id; + if (! isset(self::$objectNameHtmls[$objId])) { + self::prepare([$obj->source->type => [$objId => $obj->id_tags]]); + } + + if (isset(self::$objectNameHtmls[$objId])) { + return self::$objectNameHtmls[$objId]; + } + + $objectTags = []; + + foreach ($obj->id_tags as $tag => $value) { + $objectTags[] = sprintf('%s=%s', $tag, $value); + } + + self::$objectNameHtmls[$objId] = new HtmlString(implode(', ', $objectTags)); + + return self::$objectNameHtmls[$objId]; + } + + /** + * Render object link for the given object + * + * @param Objects $object + * + * @return ?ValidHtml + */ + final public static function renderObjectLink(Objects $object): ?ValidHtml + { + /** @var self $hook */ + foreach (Hook::all('Notifications\\ObjectsRenderer') as $hook) { + try { + if ($object->source->type === $hook->getSourceType()) { + return $hook->createObjectLink($object->id_tags); + } + } catch (Exception $e) { + Logger::error('Failed to load hook %s:', get_class($hook), $e); + } + } + + // Fallback, if the hook is not implemented + if (! $object->url) { + return null; + } + + $objUrl = Url::fromPath($object->url); + + return new Link( + self::getObjectName($object), + $objUrl->isExternal() ? $objUrl->getAbsoluteUrl() : $objUrl->getRelativeUrl(), + ['class' => 'subject', 'data-base-target' => '_next'] + ); + } +} diff --git a/library/Notifications/Model/Objects.php b/library/Notifications/Model/Objects.php index 8a2b08a4..006a82e1 100644 --- a/library/Notifications/Model/Objects.php +++ b/library/Notifications/Model/Objects.php @@ -4,8 +4,8 @@ namespace Icinga\Module\Notifications\Model; +use Icinga\Module\Notifications\Hook\ObjectsRendererHook; use Icinga\Module\Notifications\Model\Behavior\IdTagAggregator; -use ipl\Html\HtmlString; use ipl\Html\ValidHtml; use ipl\Orm\Behavior\Binary; use ipl\Orm\Behaviors; @@ -91,13 +91,6 @@ public function createRelations(Relations $relations) public function getName(): ValidHtml { - //TODO: Once hooks are available, they should render the tags accordingly - $objectTags = []; - - foreach ($this->id_tags as $tag => $value) { - $objectTags[] = sprintf('%s=%s', $tag, $value); - } - - return new HtmlString(implode(', ', $objectTags)); + return ObjectsRendererHook::getObjectName($this); } } diff --git a/library/Notifications/Widget/Detail/EventDetail.php b/library/Notifications/Widget/Detail/EventDetail.php index 9f16a697..33d98486 100644 --- a/library/Notifications/Widget/Detail/EventDetail.php +++ b/library/Notifications/Widget/Detail/EventDetail.php @@ -5,9 +5,9 @@ namespace Icinga\Module\Notifications\Widget\Detail; use Icinga\Date\DateFormatter; +use Icinga\Module\Notifications\Hook\ObjectsRendererHook; use Icinga\Module\Notifications\Model\Event; use Icinga\Module\Notifications\Model\Incident; -use Icinga\Module\Notifications\Model\Objects; use Icinga\Module\Notifications\Widget\EventSourceBadge; use Icinga\Module\Notifications\Widget\ItemList\IncidentList; use InvalidArgumentException; @@ -15,8 +15,6 @@ use ipl\Html\Html; use ipl\Html\ValidHtml; use ipl\Web\Widget\HorizontalKeyValue; -use ipl\Web\Widget\Link; -use ipl\Web\Widget\StateBall; class EventDetail extends BaseHtmlElement { @@ -80,37 +78,9 @@ protected function createMessage(): array /** @return ValidHtml[] */ protected function createRelatedObject(): array { - //TODO(sd): This is just placeholder. Add hook implementation instead - $relatedObj = Html::tag('ul', ['class' => ['item-list', 'action-list'], 'data-base-target' => '_next']); - - /** @var Objects $obj */ - $obj = $this->event->object; - - /** @var string $objUrl */ - $objUrl = $obj->url; - $relatedObj->add( - Html::tag( - 'li', - ['class' => 'list-item', 'data-action-item' => true], - [ //TODO(sd): fix stateball - Html::tag('div', ['class' => 'visual'], new StateBall('down', StateBall::SIZE_LARGE)), - Html::tag( - 'div', - ['class' => 'main'], - Html::tag('header') - ->add(Html::tag( - 'div', - ['class' => 'title'], - new Link($obj->getName(), $objUrl, ['class' => 'subject']) - )) - ) - ] - ) - ); - return [ Html::tag('h2', t('Related Object')), - $relatedObj + ObjectsRendererHook::renderObjectLink($this->event->object) ]; } diff --git a/library/Notifications/Widget/Detail/IncidentDetail.php b/library/Notifications/Widget/Detail/IncidentDetail.php index 21965067..f5bfddce 100644 --- a/library/Notifications/Widget/Detail/IncidentDetail.php +++ b/library/Notifications/Widget/Detail/IncidentDetail.php @@ -4,8 +4,8 @@ namespace Icinga\Module\Notifications\Widget\Detail; +use Icinga\Module\Notifications\Hook\ObjectsRendererHook; use Icinga\Module\Notifications\Model\Incident; -use Icinga\Module\Notifications\Model\Objects; use Icinga\Module\Notifications\Widget\EventSourceBadge; use Icinga\Module\Notifications\Widget\ItemList\IncidentContactList; use Icinga\Module\Notifications\Widget\ItemList\IncidentHistoryList; @@ -14,8 +14,6 @@ use ipl\Html\Html; use ipl\Html\HtmlElement; use ipl\Html\Table; -use ipl\Web\Widget\Link; -use ipl\Web\Widget\StateBall; class IncidentDetail extends BaseHtmlElement { @@ -52,39 +50,9 @@ protected function createContacts() protected function createRelatedObject() { - //TODO(sd): Add hook implementation - $list = Html::tag('ul', ['class' => ['item-list', 'minimal', 'action-list'], 'data-base-target' => '_next']); - - /** @var Objects $obj */ - $obj = $this->incident->object; - - /** @var string $objUrl */ - $objUrl = $obj->url; - $list->add(Html::tag( - 'li', - ['class' => 'list-item', 'data-action-item' => true], - [ //TODO(sd): fix stateball - Html::tag( - 'div', - ['class' => 'visual'], - new StateBall('down', StateBall::SIZE_LARGE) - ), - Html::tag( - 'div', - ['class' => 'main'], - Html::tag('header') - ->add(Html::tag( - 'div', - ['class' => 'title'], - new Link($obj->getName(), $objUrl, ['class' => 'subject']) - )) - ) - ] - )); - return [ Html::tag('h2', t('Object')), - $list + ObjectsRendererHook::renderObjectLink($this->incident->object) ]; } diff --git a/library/Notifications/Widget/ItemList/EventList.php b/library/Notifications/Widget/ItemList/EventList.php index 2ef47006..96d3b9a6 100644 --- a/library/Notifications/Widget/ItemList/EventList.php +++ b/library/Notifications/Widget/ItemList/EventList.php @@ -6,6 +6,8 @@ use Icinga\Module\Notifications\Common\LoadMore; use Icinga\Module\Notifications\Common\NoSubjectLink; +use Icinga\Module\Notifications\Hook\ObjectsRendererHook; +use Icinga\Module\Notifications\Model\Event; use ipl\Orm\ResultSet; use ipl\Web\Common\BaseItemList; @@ -27,6 +29,14 @@ public function __construct(ResultSet $data) protected function init(): void { $this->data = $this->getIterator($this->data); + + $this->on(self::ON_ITEM_ADD, function (EventListItem $item, Event $data) { + ObjectsRendererHook::register($data->object); + }); + + $this->on(self::ON_ASSEMBLED, function () { + ObjectsRendererHook::load(); + }); } protected function getItemClass(): string diff --git a/library/Notifications/Widget/ItemList/IncidentList.php b/library/Notifications/Widget/ItemList/IncidentList.php index 9992f1d5..ba66e37a 100644 --- a/library/Notifications/Widget/ItemList/IncidentList.php +++ b/library/Notifications/Widget/ItemList/IncidentList.php @@ -4,6 +4,8 @@ namespace Icinga\Module\Notifications\Widget\ItemList; +use Icinga\Module\Notifications\Hook\ObjectsRendererHook; +use Icinga\Module\Notifications\Model\Incident; use ipl\Web\Common\BaseItemList; use Icinga\Module\Notifications\Common\NoSubjectLink; @@ -13,6 +15,17 @@ class IncidentList extends BaseItemList protected $defaultAttributes = ['class' => ['action-list', 'incident-list']]; + protected function init(): void + { + $this->on(self::ON_ITEM_ADD, function (IncidentListItem $item, Incident $data) { + ObjectsRendererHook::register($data->object); + }); + + $this->on(self::ON_ASSEMBLED, function () { + ObjectsRendererHook::load(); + }); + } + protected function getItemClass(): string { return IncidentListItem::class; From 8324e5dd74aa323ff26dc70c2f7d337d2b1a5394 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Fri, 24 May 2024 16:36:18 +0200 Subject: [PATCH 2/7] Incident/Event Detail: Do not render object section if the object url is not present --- library/Notifications/Widget/Detail/EventDetail.php | 12 ++++++++++-- .../Notifications/Widget/Detail/IncidentDetail.php | 11 +++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/library/Notifications/Widget/Detail/EventDetail.php b/library/Notifications/Widget/Detail/EventDetail.php index 33d98486..c3827e81 100644 --- a/library/Notifications/Widget/Detail/EventDetail.php +++ b/library/Notifications/Widget/Detail/EventDetail.php @@ -13,6 +13,8 @@ use InvalidArgumentException; use ipl\Html\BaseHtmlElement; use ipl\Html\Html; +use ipl\Html\HtmlElement; +use ipl\Html\Text; use ipl\Html\ValidHtml; use ipl\Web\Widget\HorizontalKeyValue; @@ -78,9 +80,15 @@ protected function createMessage(): array /** @return ValidHtml[] */ protected function createRelatedObject(): array { + $objectUrl = ObjectsRendererHook::renderObjectLink($this->event->object); + + if (! $objectUrl) { + return []; + } + return [ - Html::tag('h2', t('Related Object')), - ObjectsRendererHook::renderObjectLink($this->event->object) + new HtmlElement('h2', null, Text::create(t('Related Object'))), + $objectUrl ]; } diff --git a/library/Notifications/Widget/Detail/IncidentDetail.php b/library/Notifications/Widget/Detail/IncidentDetail.php index f5bfddce..a88036d8 100644 --- a/library/Notifications/Widget/Detail/IncidentDetail.php +++ b/library/Notifications/Widget/Detail/IncidentDetail.php @@ -14,6 +14,7 @@ use ipl\Html\Html; use ipl\Html\HtmlElement; use ipl\Html\Table; +use ipl\Html\Text; class IncidentDetail extends BaseHtmlElement { @@ -50,9 +51,15 @@ protected function createContacts() protected function createRelatedObject() { + $objectUrl = ObjectsRendererHook::renderObjectLink($this->incident->object); + + if (! $objectUrl) { + return []; + } + return [ - Html::tag('h2', t('Object')), - ObjectsRendererHook::renderObjectLink($this->incident->object) + new HtmlElement('h2', null, Text::create(t('Related Object'))), + $objectUrl ]; } From 94291216ff055c41a77b517cf61e89d2a66eec08 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Fri, 17 May 2024 15:31:30 +0200 Subject: [PATCH 3/7] PHPStan: Update baseline --- phpstan-baseline-standard.neon | 60 ---------------------------------- 1 file changed, 60 deletions(-) diff --git a/phpstan-baseline-standard.neon b/phpstan-baseline-standard.neon index 1a449e55..0a87a97e 100644 --- a/phpstan-baseline-standard.neon +++ b/phpstan-baseline-standard.neon @@ -740,11 +740,6 @@ parameters: count: 1 path: library/Notifications/Model/Event.php - - - message: "#^Parameter \\#1 \\$severity of static method Icinga\\\\Module\\\\Notifications\\\\Model\\\\Event\\:\\:mapSeverity\\(\\) expects string\\|null, mixed given\\.$#" - count: 1 - path: library/Notifications/Model/Event.php - - message: "#^Method Icinga\\\\Module\\\\Notifications\\\\Model\\\\Incident\\:\\:createBehaviors\\(\\) has no return type specified\\.$#" count: 1 @@ -1250,36 +1245,11 @@ parameters: count: 1 path: library/Notifications/Widget/CheckboxIcon.php - - - message: "#^Cannot access property \\$source on mixed\\.$#" - count: 1 - path: library/Notifications/Widget/Detail/EventDetail.php - - - - message: "#^Cannot call method getTimestamp\\(\\) on mixed\\.$#" - count: 1 - path: library/Notifications/Widget/Detail/EventDetail.php - - message: "#^Method Icinga\\\\Module\\\\Notifications\\\\Widget\\\\Detail\\\\EventDetail\\:\\:assemble\\(\\) has no return type specified\\.$#" count: 1 path: library/Notifications/Widget/Detail/EventDetail.php - - - message: "#^Cannot access property \\$object_extra_tag on mixed\\.$#" - count: 1 - path: library/Notifications/Widget/Detail/IncidentDetail.php - - - - message: "#^Cannot access property \\$source on mixed\\.$#" - count: 1 - path: library/Notifications/Widget/Detail/IncidentDetail.php - - - - message: "#^Cannot call method with\\(\\) on mixed\\.$#" - count: 2 - path: library/Notifications/Widget/Detail/IncidentDetail.php - - message: "#^Method Icinga\\\\Module\\\\Notifications\\\\Widget\\\\Detail\\\\IncidentDetail\\:\\:assemble\\(\\) has no return type specified\\.$#" count: 1 @@ -1450,21 +1420,6 @@ parameters: count: 1 path: library/Notifications/Widget/ItemList/EventList.php - - - message: "#^Cannot access property \\$source on mixed\\.$#" - count: 1 - path: library/Notifications/Widget/ItemList/EventListItem.php - - - - message: "#^Cannot call method getTimestamp\\(\\) on mixed\\.$#" - count: 1 - path: library/Notifications/Widget/ItemList/EventListItem.php - - - - message: "#^Parameter \\#1 \\$id of static method Icinga\\\\Module\\\\Notifications\\\\Common\\\\Links\\:\\:event\\(\\) expects int, mixed given\\.$#" - count: 1 - path: library/Notifications/Widget/ItemList/EventListItem.php - - message: "#^Parameter \\#2 \\$value of method Icinga\\\\Web\\\\Url\\:\\:setParam\\(\\) expects array\\|bool\\|string, int given\\.$#" count: 1 @@ -1525,16 +1480,6 @@ parameters: count: 1 path: library/Notifications/Widget/ItemList/IncidentHistoryListItem.php - - - message: "#^Parameter \\#1 \\$severity of static method Icinga\\\\Module\\\\Notifications\\\\Model\\\\Event\\:\\:mapSeverity\\(\\) expects string\\|null, mixed given\\.$#" - count: 3 - path: library/Notifications/Widget/ItemList/IncidentHistoryListItem.php - - - - message: "#^Cannot call method getTimestamp\\(\\) on mixed\\.$#" - count: 2 - path: library/Notifications/Widget/ItemList/IncidentListItem.php - - message: "#^Method Icinga\\\\Module\\\\Notifications\\\\Widget\\\\ItemList\\\\PageSeparatorItem\\:\\:assemble\\(\\) has no return type specified\\.$#" count: 1 @@ -1685,11 +1630,6 @@ parameters: count: 1 path: library/Notifications/Widget/Schedule.php - - - message: "#^Parameter \\#2 \\$value of static method ipl\\\\Stdlib\\\\Filter\\:\\:equal\\(\\) expects array\\|bool\\|float\\|int\\|string, mixed given\\.$#" - count: 1 - path: library/Notifications/Widget/Schedule.php - - message: "#^Property Icinga\\\\Module\\\\Notifications\\\\Widget\\\\Schedule\\:\\:\\$schedule \\(Icinga\\\\Module\\\\Notifications\\\\Model\\\\Schedule\\) does not accept Icinga\\\\Module\\\\Notifications\\\\Model\\\\Schedule\\|null\\.$#" count: 1 From 7c4b7c68c23ca6c1661ec84b269b6fbec939ac75 Mon Sep 17 00:00:00 2001 From: raviks789 Date: Mon, 27 May 2024 18:10:43 +0200 Subject: [PATCH 4/7] ObjectsRendererHook: Add implementation to fetch object name as string --- .../Hook/ObjectsRendererHook.php | 114 ++++++++++++++++-- 1 file changed, 102 insertions(+), 12 deletions(-) diff --git a/library/Notifications/Hook/ObjectsRendererHook.php b/library/Notifications/Hook/ObjectsRendererHook.php index 54dc28a9..403462ec 100644 --- a/library/Notifications/Hook/ObjectsRendererHook.php +++ b/library/Notifications/Hook/ObjectsRendererHook.php @@ -9,6 +9,8 @@ use Icinga\Application\Hook; use Icinga\Application\Logger; use Icinga\Module\Notifications\Model\Objects; +use ipl\Html\Attributes; +use ipl\Html\HtmlElement; use ipl\Html\HtmlString; use ipl\Html\ValidHtml; use ipl\Web\Url; @@ -37,6 +39,24 @@ abstract class ObjectsRendererHook */ private static $objectNameHtmls = []; + /** + * Array of object names with their corresponding object IDs as keys + * + * It has the following structure : ['object ID' => 'object name']. + * + * @var array + */ + private static $objectNames = []; + + /** + * Get the object names for the objects using the object ID tags + * + * @param array> $objectIdTags Array of object ID tags of objects belonging to the source + * + * @return Generator, string> Generator for object names with their object ID tags as keys + */ + abstract public function getObjectNames(array $objectIdTags): Generator; + /** * Get the HTML for the object names for the objects using the object ID tags * @@ -78,11 +98,13 @@ final public static function register(Objects $obj): void /** * Load HTMLs to be rendered for the object names to the cache using the cached objects * + * @param bool $asHtml If true loads object names as HTMLs otherwise as string + * * @return void */ - final public static function load(): void + final public static function load(bool $asHtml = true): void { - self::prepare(self::$objectIdTags); + self::prepare(self::$objectIdTags, $asHtml); // Prepare object names as HTML or string self::$objectIdTags = []; } @@ -94,15 +116,17 @@ final public static function load(): void * ['object source type' => ['object ID' => [object ID tags]]]. * * @param array>> $objectIdTags Array of object ID tags for each source + * @param bool $asHtml When true, object names are prepared as HTML otherwise as string * * @return void */ - private static function prepare(array $objectIdTags): void + private static function prepare(array $objectIdTags, bool $asHtml = true): void { $idTagToObjectIdMap = []; + $objectNames = $asHtml ? self::$objectNameHtmls : self::$objectNames; foreach ($objectIdTags as $sourceType => $objects) { foreach ($objects as $objectId => $tags) { - if (! isset(self::$objectNameHtmls[$objectId])) { + if (! isset($objectNames[$objectId])) { $idTagToObjectIdMap[$sourceType][] = [$objectId, $tags]; } } @@ -123,13 +147,31 @@ function ($object) { $idTagToObjectIdMap[$source] ); + $objectNamesFromSource = $asHtml + ? $hook->getHtmlForObjectNames($objectIDTagsForSource) + : $hook->getObjectNames($objectIDTagsForSource); + /** @var array $objectIdTag */ - foreach ($hook->getHtmlForObjectNames($objectIDTagsForSource) as $objectIdTag => $validHtml) { + foreach ($objectNamesFromSource as $objectIdTag => $objectName) { foreach ($idTagToObjectIdMap[$source] as $key => $val) { $diff = array_intersect_assoc($val[1], $objectIdTag); if (count($diff) === count($val[1])) { unset($idTagToObjectIdMap[$key]); - $objectDisplayNames[$val[0]] = $validHtml; + + if ($asHtml) { + $objectName = HtmlElement::create( + 'div', + Attributes::create([ + 'class' => [ + 'icinga-module', + 'module-' . ($source === 'icinga2' ? 'icingadb' : $source) + ] + ]), + $objectName + ); + } + + $objectDisplayNames[$val[0]] = $objectName; continue 2; } @@ -141,7 +183,11 @@ function ($object) { } } - self::$objectNameHtmls += $objectDisplayNames; + if ($asHtml) { + self::$objectNameHtmls += $objectDisplayNames; + } else { + self::$objectNames += $objectDisplayNames; + } } /** @@ -164,15 +210,52 @@ final public static function getObjectName(Objects $obj): ValidHtml return self::$objectNameHtmls[$objId]; } + self::$objectNameHtmls[$objId] = new HtmlString(self::createObjectNameAsString($obj)); + + return self::$objectNameHtmls[$objId]; + } + + /** + * Get the object name of the given object as string + * + * If the object name is not loaded, it is prepared using object ID tags and the same is returned. + * + * @param Objects $obj + * @param bool $prepare If true prepares the object name string from the hook implementation if it is not + * already present in the cache + * + * @return string + */ + final public static function getObjectNameAsString(Objects $obj): string + { + $objId = $obj->id; + if (! isset(self::$objectNames[$objId])) { + self::prepare([$obj->source->type => [$objId => $obj->id_tags]], false); + } + + if (isset(self::$objectNames[$objId])) { + return self::$objectNames[$objId]; + } + + return self::createObjectNameAsString($obj); + } + + /** + * Create object name string for the given object + * + * @param Objects $obj + * + * @return string + */ + private static function createObjectNameAsString(Objects $obj): string + { $objectTags = []; foreach ($obj->id_tags as $tag => $value) { $objectTags[] = sprintf('%s=%s', $tag, $value); } - self::$objectNameHtmls[$objId] = new HtmlString(implode(', ', $objectTags)); - - return self::$objectNameHtmls[$objId]; + return implode(', ', $objectTags); } /** @@ -187,8 +270,15 @@ final public static function renderObjectLink(Objects $object): ?ValidHtml /** @var self $hook */ foreach (Hook::all('Notifications\\ObjectsRenderer') as $hook) { try { - if ($object->source->type === $hook->getSourceType()) { - return $hook->createObjectLink($object->id_tags); + $sourceType = $hook->getSourceType(); + if ($object->source->type === $sourceType) { + return $hook->createObjectLink($object->id_tags) + ->addAttributes([ + 'class' => [ + 'icinga-module', + 'module-' . ($sourceType === 'icinga2' ? 'icingadb' : $sourceType) + ] + ]); } } catch (Exception $e) { Logger::error('Failed to load hook %s:', get_class($hook), $e); From 1252985daef0b53aed8509165771d19a2a932faa Mon Sep 17 00:00:00 2001 From: raviks789 Date: Tue, 28 May 2024 17:48:54 +0200 Subject: [PATCH 5/7] Do not prepare object name HTML to render the link --- .../Notifications/Hook/ObjectsRendererHook.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/library/Notifications/Hook/ObjectsRendererHook.php b/library/Notifications/Hook/ObjectsRendererHook.php index 403462ec..9ff5abe0 100644 --- a/library/Notifications/Hook/ObjectsRendererHook.php +++ b/library/Notifications/Hook/ObjectsRendererHook.php @@ -10,8 +10,9 @@ use Icinga\Application\Logger; use Icinga\Module\Notifications\Model\Objects; use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; use ipl\Html\HtmlElement; -use ipl\Html\HtmlString; +use ipl\Html\Text; use ipl\Html\ValidHtml; use ipl\Web\Url; use ipl\Web\Widget\Link; @@ -197,9 +198,9 @@ function ($object) { * * @param Objects $obj * - * @return ValidHtml + * @return BaseHtmlElement */ - final public static function getObjectName(Objects $obj): ValidHtml + final public static function getObjectName(Objects $obj): BaseHtmlElement { $objId = $obj->id; if (! isset(self::$objectNameHtmls[$objId])) { @@ -210,7 +211,11 @@ final public static function getObjectName(Objects $obj): ValidHtml return self::$objectNameHtmls[$objId]; } - self::$objectNameHtmls[$objId] = new HtmlString(self::createObjectNameAsString($obj)); + self::$objectNameHtmls[$objId] = new HtmlElement( + 'div', + null, + Text::create(self::createObjectNameAsString($obj)) + ); return self::$objectNameHtmls[$objId]; } @@ -293,7 +298,7 @@ final public static function renderObjectLink(Objects $object): ?ValidHtml $objUrl = Url::fromPath($object->url); return new Link( - self::getObjectName($object), + Text::create(self::createObjectNameAsString($object)), $objUrl->isExternal() ? $objUrl->getAbsoluteUrl() : $objUrl->getRelativeUrl(), ['class' => 'subject', 'data-base-target' => '_next'] ); From b366cd63f9645b2f61c0669373426b599d79cdab Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 29 May 2024 14:46:55 +0200 Subject: [PATCH 6/7] ObjectsRendererHook: Allow method `createObjectLink()` to return null Because even if the hook(source) implementation is present, it is possible that the it cannot find a result for the given object (object is deleted/changed). --- .../Hook/ObjectsRendererHook.php | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/library/Notifications/Hook/ObjectsRendererHook.php b/library/Notifications/Hook/ObjectsRendererHook.php index 9ff5abe0..c091c14c 100644 --- a/library/Notifications/Hook/ObjectsRendererHook.php +++ b/library/Notifications/Hook/ObjectsRendererHook.php @@ -80,9 +80,9 @@ abstract public function getSourceType(): string; * * @param array $objectIdTag * - * @return ValidHtml + * @return ?ValidHtml Returns null if no object with given tag found */ - abstract public function createObjectLink(array $objectIdTag): ValidHtml; + abstract public function createObjectLink(array $objectIdTag): ?ValidHtml; /** * Register object ID tags to the cache @@ -277,13 +277,17 @@ final public static function renderObjectLink(Objects $object): ?ValidHtml try { $sourceType = $hook->getSourceType(); if ($object->source->type === $sourceType) { - return $hook->createObjectLink($object->id_tags) - ->addAttributes([ - 'class' => [ - 'icinga-module', - 'module-' . ($sourceType === 'icinga2' ? 'icingadb' : $sourceType) - ] - ]); + $objectLink = $hook->createObjectLink($object->id_tags); + if ($objectLink === null) { + break; + } + + return $objectLink->addAttributes([ + 'class' => [ + 'icinga-module', + 'module-' . ($sourceType === 'icinga2' ? 'icingadb' : $sourceType) + ] + ]); } } catch (Exception $e) { Logger::error('Failed to load hook %s:', get_class($hook), $e); From 67fee8bc1f0724b7f5519b135ae65efbe4760001 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 29 May 2024 14:55:45 +0200 Subject: [PATCH 7/7] ObjectsRendererHook: Fix false positive condition --- library/Notifications/Hook/ObjectsRendererHook.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Notifications/Hook/ObjectsRendererHook.php b/library/Notifications/Hook/ObjectsRendererHook.php index c091c14c..5d0af3ff 100644 --- a/library/Notifications/Hook/ObjectsRendererHook.php +++ b/library/Notifications/Hook/ObjectsRendererHook.php @@ -156,8 +156,8 @@ function ($object) { foreach ($objectNamesFromSource as $objectIdTag => $objectName) { foreach ($idTagToObjectIdMap[$source] as $key => $val) { $diff = array_intersect_assoc($val[1], $objectIdTag); - if (count($diff) === count($val[1])) { - unset($idTagToObjectIdMap[$key]); + if (count($diff) === count($val[1]) && count($diff) === count($objectIdTag)) { + unset($idTagToObjectIdMap[$source][$key]); if ($asHtml) { $objectName = HtmlElement::create(