From 57adb73114f8cbee76e1db7569a732d524705e8b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 4 Jul 2024 13:44:27 +0200 Subject: [PATCH 1/3] IdTagAggregator: Properly handle optional joins --- .../Model/Behavior/IdTagAggregator.php | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/library/Notifications/Model/Behavior/IdTagAggregator.php b/library/Notifications/Model/Behavior/IdTagAggregator.php index 60ae0b06..eb8e81ac 100644 --- a/library/Notifications/Model/Behavior/IdTagAggregator.php +++ b/library/Notifications/Model/Behavior/IdTagAggregator.php @@ -15,6 +15,8 @@ use ipl\Sql\Adapter\Pgsql; use ipl\Stdlib\Filter; +use function ipl\Stdlib\iterable_key_first; + class IdTagAggregator extends PropertyBehavior implements RewriteColumnBehavior, QueryAwareBehavior { /** @var Query */ @@ -37,10 +39,14 @@ public function rewriteColumn($column, ?string $relation = null) $path = ($relation ?? $this->query->getModel()->getTableAlias()) . '.object_id_tag'; $this->query->utilize($path); - $pathAlias = $this->query->getResolver()->getAlias( - $this->query->getResolver()->resolveRelation($path)->getTarget() - ); + $pathRelation = $this->query->getResolver()->resolveRelation($path); + if ($relation !== null) { + // TODO: This is really another case where ipl-orm could automatically adjust the join type... + $pathRelation->setJoinType($this->query->getResolver()->resolveRelation($relation)->getJoinType()); + } + + $pathAlias = $this->query->getResolver()->getAlias($pathRelation->getTarget()); $myAlias = $this->query->getResolver()->getAlias( $relation ? $this->query->getResolver()->resolveRelation($relation)->getTarget() @@ -49,8 +55,8 @@ public function rewriteColumn($column, ?string $relation = null) return new AliasedExpression("{$myAlias}_id_tags", sprintf( $this->query->getDb()->getAdapter() instanceof Pgsql - ? 'json_object_agg(%s, %s)' - : 'json_objectagg(%s, %s)', + ? 'json_object_agg(COALESCE(%s, \'\'), %s)' + : 'json_objectagg(COALESCE(%s, \'\'), %s)', $this->query->getResolver()->qualifyColumn('tag', $pathAlias), $this->query->getResolver()->qualifyColumn('value', $pathAlias) )); @@ -68,7 +74,12 @@ public function fromDb($value, $key, $context) return []; } - return json_decode($value, true) ?? []; + $tags = json_decode($value, true) ?? []; + if (iterable_key_first($tags) === '') { + return []; + } + + return $tags; } public function toDb($value, $key, $context) From afb1db5be84c21996e69316fce9c3b2887fc4cb1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 4 Jul 2024 13:57:34 +0200 Subject: [PATCH 2/3] events: Support mute and unmute events --- library/Notifications/Common/Icons.php | 4 +++ library/Notifications/Model/Event.php | 21 +++++++++++- .../Widget/Detail/EventDetail.php | 34 ++++++++++++++++--- .../Widget/ItemList/EventListItem.php | 23 ++++++++++--- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/library/Notifications/Common/Icons.php b/library/Notifications/Common/Icons.php index 9d9fea63..eefc90f3 100644 --- a/library/Notifications/Common/Icons.php +++ b/library/Notifications/Common/Icons.php @@ -54,6 +54,10 @@ private function __construct() public const CUSTOM = 'message'; + public const MUTE = 'volume-xmark'; + + public const UNMUTE = 'volume-high'; + public const SEVERITY_OK = 'heart'; public const SEVERITY_CRIT = 'circle-exclamation'; diff --git a/library/Notifications/Model/Event.php b/library/Notifications/Model/Event.php index 758aac9c..56aeacb4 100644 --- a/library/Notifications/Model/Event.php +++ b/library/Notifications/Model/Event.php @@ -8,6 +8,7 @@ use Icinga\Module\Notifications\Common\Database; use Icinga\Module\Notifications\Common\Icons; use ipl\Orm\Behavior\Binary; +use ipl\Orm\Behavior\BoolCast; use ipl\Orm\Behavior\MillisecondTimestamp; use ipl\Orm\Behaviors; use ipl\Orm\Model; @@ -27,6 +28,8 @@ * @property ?string $severity * @property ?string $message * @property ?string $username + * @property ?bool $mute + * @property ?string $mute_reason * * @property Query | Objects $object * @property Query | IncidentHistory $incident_history @@ -53,6 +56,8 @@ public function getColumns() 'severity', 'message', 'username', + 'mute', + 'mute_reason' ]; } @@ -64,7 +69,9 @@ public function getColumnDefinitions() 'type' => t('Type'), 'severity' => t('Severity'), 'message' => t('Message'), - 'username' => t('Username') + 'username' => t('Username'), + 'mute' => t('Mute'), + 'mute_reason' => t('Mute Reason') ]; } @@ -95,6 +102,7 @@ public function createBehaviors(Behaviors $behaviors) { $behaviors->add(new MillisecondTimestamp(['time'])); $behaviors->add(new Binary(['object_id'])); + $behaviors->add(new BoolCast(['mute'])); } public function createRelations(Relations $relations) @@ -188,6 +196,10 @@ public function getTypeText(): string return t('left a flapping period', 'notifications.type'); case 'incident-age': return t('exceeded a time constraint', 'notifications.type'); + case 'mute': + return t('was muted', 'notifications.type'); + case 'unmute': + return t('was unmuted', 'notifications.type'); default: // custom return ''; } @@ -259,6 +271,13 @@ public function getIcon(): ?Icon break; case 'custom': $icon = new Icon(Icons::CUSTOM); + break; + case 'mute': + $icon = new Icon(Icons::MUTE); + break; + case 'unmute': + $icon = new Icon(Icons::UNMUTE); + break; } return $icon; diff --git a/library/Notifications/Widget/Detail/EventDetail.php b/library/Notifications/Widget/Detail/EventDetail.php index 5f1024b7..3bb13058 100644 --- a/library/Notifications/Widget/Detail/EventDetail.php +++ b/library/Notifications/Widget/Detail/EventDetail.php @@ -57,15 +57,37 @@ protected function createInfo(): array $info[] = new HorizontalKeyValue(t('Severity'), $severity); } + if ($this->event->mute !== null) { + $info[] = new HorizontalKeyValue( + t('Muted'), + $this->event->mute ? t('Yes') : t('No') + ); + } + return $info; } /** @return ValidHtml[] */ protected function createMessage(): array { - return [ - Html::tag('h2', t('Message')), - Html::tag( + $messages = []; + + if ($this->event->mute_reason !== null) { + $messages[] = Html::tag('h2', t('Mute Reason')); + $messages[] = Html::tag( + 'div', + [ + 'id' => 'mute-reason-' . $this->event->id, + 'class' => 'collapsible', + 'data-visible-height' => 100 + ], + $this->event->mute_reason + ); + } + + if ($this->event->message !== null) { + $messages[] = Html::tag('h2', t('Message')); + $messages[] = Html::tag( 'div', [ 'id' => 'message-' . $this->event->id, @@ -73,8 +95,10 @@ protected function createMessage(): array 'data-visible-height' => 100 ], $this->event->message - ) - ]; + ); + } + + return $messages; } /** @return ValidHtml[] */ diff --git a/library/Notifications/Widget/ItemList/EventListItem.php b/library/Notifications/Widget/ItemList/EventListItem.php index e56b7b2e..7071cb07 100644 --- a/library/Notifications/Widget/ItemList/EventListItem.php +++ b/library/Notifications/Widget/ItemList/EventListItem.php @@ -9,15 +9,12 @@ use Icinga\Module\Notifications\Model\Incident; use Icinga\Module\Notifications\Model\Objects; use Icinga\Module\Notifications\Model\Source; -use Icinga\Module\Notifications\Widget\IconBall; use Icinga\Module\Notifications\Widget\SourceIcon; use InvalidArgumentException; use ipl\Html\BaseHtmlElement; use ipl\Html\Html; use ipl\Html\HtmlElement; -use ipl\Stdlib\Str; use ipl\Web\Common\BaseListItem; -use ipl\Web\Widget\Icon; use ipl\Web\Widget\Link; use ipl\Web\Widget\TimeAgo; @@ -82,7 +79,25 @@ protected function assembleTitle(BaseHtmlElement $title): void protected function assembleCaption(BaseHtmlElement $caption): void { - $caption->add($this->item->message); + switch ($this->item->type) { + case 'mute': + case 'unmute': + case 'flapping-start': + case 'flapping-end': + case 'downtime-start': + case 'downtime-end': + case 'downtime-removed': + case 'acknowledgement-set': + case 'acknowledgement-cleared': + if ($this->item->mute_reason !== null) { + $caption->add($this->item->mute_reason); + break; + } + + // Sometimes these events have no mute reason, but a message + default: + $caption->add($this->item->message); + } } protected function assembleHeader(BaseHtmlElement $header): void From 223ec05ab6aaf779e1042fa582e6dafcb6cbbc9b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 4 Jul 2024 16:16:18 +0200 Subject: [PATCH 3/3] incidents: Support notification suppression --- library/Notifications/Model/IncidentHistory.php | 2 ++ library/Notifications/Model/Objects.php | 4 +++- .../Widget/ItemList/IncidentHistoryListItem.php | 16 ++++++++++++++++ .../Widget/ItemList/IncidentListItem.php | 4 ++++ public/css/common.less | 5 ++--- public/css/detail/incident-detail.less | 4 ++++ 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/library/Notifications/Model/IncidentHistory.php b/library/Notifications/Model/IncidentHistory.php index 8cb4e0d6..320eb7cd 100644 --- a/library/Notifications/Model/IncidentHistory.php +++ b/library/Notifications/Model/IncidentHistory.php @@ -150,6 +150,8 @@ public static function translateNotificationState(string $state): string return t('failed', 'notifications.transmission.state'); case 'pending': return t('pending', 'notifications.transmission.state'); + case 'suppressed': + return t('suppressed', 'notifications.transmission.state'); default: return t('unknown', 'notifications.transmission.state'); } diff --git a/library/Notifications/Model/Objects.php b/library/Notifications/Model/Objects.php index 006a82e1..68d43e3b 100644 --- a/library/Notifications/Model/Objects.php +++ b/library/Notifications/Model/Objects.php @@ -22,6 +22,7 @@ * @property string $host * @property ?string $service * @property ?string $url + * @property ?string $mute_reason * * @property Query | Event $event * @property Query | Incident $incident @@ -48,7 +49,8 @@ public function getColumns() return [ 'source_id', 'name', - 'url' + 'url', + 'mute_reason' ]; } diff --git a/library/Notifications/Widget/ItemList/IncidentHistoryListItem.php b/library/Notifications/Widget/ItemList/IncidentHistoryListItem.php index 344cb373..ad98a90c 100644 --- a/library/Notifications/Widget/ItemList/IncidentHistoryListItem.php +++ b/library/Notifications/Widget/ItemList/IncidentHistoryListItem.php @@ -58,6 +58,10 @@ protected function getIncidentEventIcon(): string switch ($this->item->type) { case 'opened': return Icons::OPENED; + case 'muted': + return Icons::MUTE; + case 'unmuted': + return Icons::UNMUTE; case 'incident_severity_changed': return $this->getSeverityIcon(); case 'recipient_role_changed': @@ -180,6 +184,10 @@ protected function buildMessage(): string ); } + if ($this->item->notification_state === 'suppressed') { + $this->getAttributes()->add('class', 'notification-suppressed'); + } + break; case 'incident_severity_changed': $message = sprintf( @@ -251,6 +259,14 @@ protected function buildMessage(): string $this->item->rule_escalation->name ); + break; + case 'muted': + $message = t('Notifications for this incident have been muted'); + + break; + case 'unmuted': + $message = t('Notifications for this incident have been unmuted'); + break; default: $message = ''; diff --git a/library/Notifications/Widget/ItemList/IncidentListItem.php b/library/Notifications/Widget/ItemList/IncidentListItem.php index acf1bc9a..d4f010b2 100644 --- a/library/Notifications/Widget/ItemList/IncidentListItem.php +++ b/library/Notifications/Widget/ItemList/IncidentListItem.php @@ -79,6 +79,10 @@ protected function assembleHeader(BaseHtmlElement $header): void $header->add($this->createTitle()); $meta = new HtmlElement('span', Attributes::create(['class' => 'meta'])); + if ($this->item->severity !== 'ok' && $this->item->object->mute_reason !== null) { + $meta->addHtml(new Icon(Icons::MUTE, ['title' => $this->item->object->mute_reason])); + } + /** @var Source $source */ $source = $this->item->object->source; $meta->addHtml((new SourceIcon(SourceIcon::SIZE_BIG))->addHtml($source->getIcon())); diff --git a/public/css/common.less b/public/css/common.less index 10d6302c..db1959c8 100644 --- a/public/css/common.less +++ b/public/css/common.less @@ -99,8 +99,7 @@ .event-list .list-item, .incident-list .list-item { - .meta .source-icon { - margin-left: auto; + .meta > :not(:last-child) { margin-right: 0.5em; } -} \ No newline at end of file +} diff --git a/public/css/detail/incident-detail.less b/public/css/detail/incident-detail.less index db48aa8e..653911db 100644 --- a/public/css/detail/incident-detail.less +++ b/public/css/detail/incident-detail.less @@ -26,4 +26,8 @@ } } } + + .list-item.notification-suppressed { + opacity: .75; + } }