diff --git a/application/clicommands/SendCommand.php b/application/clicommands/SendCommand.php index 0b06bcc..114c5ec 100644 --- a/application/clicommands/SendCommand.php +++ b/application/clicommands/SendCommand.php @@ -150,7 +150,7 @@ public function problemAction() if ($ackPipe) { $cmd = new LegacyCommandPipe($ackPipe); } else { - $cmd = (new IcingaCommandPipe())->setMonitoringInfo($info); + $cmd = new IcingaCommandPipe($info); } if ($cmd->acknowledge($ackAuthor, $ackMessage, $host, $service)) { Logger::info("Problem has been acknowledged for $key"); diff --git a/library/Jira/IcingaCommandPipe.php b/library/Jira/IcingaCommandPipe.php index 1e6d626..5ec223c 100644 --- a/library/Jira/IcingaCommandPipe.php +++ b/library/Jira/IcingaCommandPipe.php @@ -2,8 +2,6 @@ namespace Icinga\Module\Jira; -use Icinga\Application\Modules\Module; -use Icinga\Module\Jira\ProvidedHook\Icingadb\IcingadbSupport; use Icinga\Module\Monitoring\Command\Object\AcknowledgeProblemCommand; use Icinga\Module\Monitoring\Command\Transport\CommandTransport; use Icinga\Module\Icingadb\Command\Transport\CommandTransport as IcingadbCommandTransport; @@ -13,20 +11,16 @@ class IcingaCommandPipe { + /** @var MonitoringInfo */ private $monitoringInfo; - public function acknowledge($author, $message, $host, $service = null) + public function __construct(MonitoringInfo $monitoringInfo) { - if ($this->monitoringInfo === null) { - if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { - $backend = new IcingadbBackend(); - } else { - $backend = new IdoBackend(); - } - - $this->setMonitoringInfo($backend->getMonitoringInfo($host, $service)); - } + $this->monitoringInfo = $monitoringInfo; + } + public function acknowledge($author, $message, $host, $service = null) + { if (! $this->monitoringInfo->hasObject()) { if ($service !== null) { throw new IcingaException( @@ -57,13 +51,6 @@ public function acknowledge($author, $message, $host, $service = null) return true; } - public function setMonitoringInfo(MonitoringInfo $info) - { - $this->monitoringInfo = $info; - - return $this; - } - protected function getAcknowledgeProblemCommand() { if ($this->monitoringInfo->getObject() instanceof MonitoredObject) { diff --git a/library/Jira/Web/Form/NewIssueForm.php b/library/Jira/Web/Form/NewIssueForm.php index b85271e..26c22c7 100644 --- a/library/Jira/Web/Form/NewIssueForm.php +++ b/library/Jira/Web/Form/NewIssueForm.php @@ -275,7 +275,7 @@ protected function eventuallyAcknowledge($key, $host, $service) $ackMessage = "Jira issue $key has been created"; try { - $cmd = new IcingaCommandPipe(); + $cmd = new IcingaCommandPipe($this->monitoringInfo); if ($cmd->acknowledge('Jira', $ackMessage, $host, $service)) { Logger::info("Problem has been acknowledged for $key"); } diff --git a/library/Jira/Web/RenderingHelper.php b/library/Jira/Web/RenderingHelper.php index 4af30e6..8ffa1f6 100644 --- a/library/Jira/Web/RenderingHelper.php +++ b/library/Jira/Web/RenderingHelper.php @@ -7,7 +7,10 @@ use Icinga\Date\DateFormatter; use Icinga\Module\Jira\ProvidedHook\Icingadb\IcingadbSupport; use Icinga\Module\Jira\RestApi; +use ipl\Html\Attributes; use ipl\Html\Html; +use ipl\Html\HtmlElement; +use ipl\Html\HtmlString; use ipl\Web\Url; use ipl\Web\Widget\Icon; use ipl\Web\Widget\Link; @@ -17,49 +20,191 @@ class RenderingHelper { protected $api; - public function linkToMonitoring($host, $service) + /** @var ?string Host name from the icingaKey JIRA ticket field */ + protected $hostName; + + /** @var ?string Service name from the icingaKey JIRA ticket field */ + protected $serviceName; + + /** @var ?Link Host link */ + protected $hostLink; + + /** @var ?Link Service link */ + protected $serviceLink; + + /** + * Set the name of monitored host + * + * @param string $hostName + * + * @return $this + */ + public function setHostName(string $hostName): self { - if ($service === null) { - return $this->linkToMonitoringHost($host); - } else { - return $this->linkToMonitoringService($host, $service); - } + $this->hostName = $hostName; + + return $this; } - public function linkToMonitoringHost($host) + + /** + * Set the name of monitored service + * + * @param string $serviceName + * + * @return $this + */ + public function setServiceName(string $serviceName): self { - if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { - return new Link([new Icon('server'), $host], Url::fromPath('icingadb/host', [ - 'name' => $host - ]), [ - 'title' => t('Show Icinga Host State'), - ]); - } + $this->serviceName = $serviceName; - return new Link([new Icon('laptop'), $host], Url::fromPath('monitoring/host/show', [ - 'host' => $host - ]), [ - 'title' => t('Show Icinga Host State'), - ]); + return $this; } - public function linkToMonitoringService($host, $service) + /** + * Set the link of monitored host + * + * @param Link $hostLink + * + * @return $this + */ + public function setHostLink(Link $hostLink): self { - if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) { - return new Link([new Icon('cog'), $service], Url::fromPath('icingadb/service', [ - 'name' => $service, - 'host.name' => $host, - ]), [ - 'title' => t('Show Icinga Service State'), - ]); - } + $this->hostLink = $hostLink; - return new Link([new Icon('cog'), $service], Url::fromPath('monitoring/service/show', [ - 'host' => $host, - 'service' => $service, - ]), [ - 'title' => t('Show Icinga Service State'), - ]); + return $this; + } + + /** + * Set the link of monitored service + * + * @param Link $serviceLink + * + * @return $this + */ + public function setServiceLink(Link $serviceLink): self + { + $this->serviceLink = $serviceLink; + + return $this; + } + + /** + * Get the link of monitored host + * + * @return ?Link + */ + public function getHostLink(): ?Link + { + return $this->hostLink; + } + + /** + * Get the link of monitored service + * + * @return ?Link + */ + public function getServiceLink(): ?Link + { + return $this->serviceLink; + } + + + /** + * Get the formatted issue comment using author, time and description or comment body + * + * @param string|HtmlElement[] $author + * @param string $time + * @param string $body + * + * @return HtmlElement[] + */ + public function getIssueComment($author, string $time, string $body): array + { + return [ + new HtmlElement('h3', null, Html::sprintf('%s: %s', $this->shortTimeSince($time), $author)), + new HtmlElement('pre', new Attributes(['class' => 'comment']), $this->formatBody($body)), + ]; + } + + /** + * Format the given issue description or comment body + * + * @param string $body + * + * @return HtmlString + */ + public function formatBody(string $body): HtmlString + { + // Replace object urls in the given string with link elements + $body = preg_replace_callback('/\[([^|]+)\|([^]]+)]/', function ($match) { + $url = Url::fromPath($match[2]); + $link = new Link($match[1], $url); + $urlPath = $url->getPath(); + + $monitoringObjectLink = in_array($urlPath, ['monitoring/host/show', 'monitoring/service/show']); + $icingadbObjectLink = in_array($urlPath, ['icingadb/host', 'icingadb/service']); + + if ($monitoringObjectLink || $icingadbObjectLink) { + $transformToIcingadbLink = $monitoringObjectLink + && Module::exists('icingadb') + && IcingadbSupport::useIcingaDbAsBackend(); + if (strpos($urlPath, '/service') !== false) { + if ($monitoringObjectLink) { + $urlServiceParam = $url->getParam('service'); + $urlHostParam = $url->getParam('host'); + if ($transformToIcingadbLink) { + $url->setPath('icingadb/service') + ->remove(['service', 'host']) + ->overwriteParams(['name' => $urlServiceParam, 'host.name' => $urlHostParam]); + $link->setUrl($url); + } + } else { + $urlServiceParam = $url->getParam('name'); + $urlHostParam = $url->getParam('host.name'); + } + + if ( + ! $this->serviceLink + && $urlServiceParam === $this->serviceName + && $urlHostParam === $this->hostName + ) { + $serviceLink = clone $link; + $serviceLink->setContent([new Icon('cog'), $match[1]]) + ->addAttributes(['title' => t('Show Icinga Service State')]); + $this->setServiceLink($serviceLink); + } + } else { + $icon = new Icon('server'); + if ($monitoringObjectLink) { + $urlHostParam = $url->getParam('host'); + if ($transformToIcingadbLink) { + $url->setPath('icingadb/host') + ->remove('host') + ->setParam('name', $urlHostParam); + $link->setUrl($url); + } else { + $icon = new Icon('laptop'); + } + } else { + $urlHostParam = $url->getParam('name'); + } + + if (! $this-> hostLink && $urlHostParam === $this->hostName) { + $hostLink = clone $link; + $hostLink->setContent([$icon, $match[1]]) + ->addAttributes(['title' => t('Show Icinga Host State')]); + $this->setHostLink($hostLink); + } + } + } elseif ($url->isExternal()) { + $link->addAttributes(['target' => '_blank']); + } + + return $link->render(); + }, $body); + + return new HtmlString($body); } public function linkToJira($caption, $url, $attributes = []) diff --git a/library/Jira/Web/Table/IssueDetails.php b/library/Jira/Web/Table/IssueDetails.php index 4589ad0..da898c4 100644 --- a/library/Jira/Web/Table/IssueDetails.php +++ b/library/Jira/Web/Table/IssueDetails.php @@ -4,12 +4,11 @@ use Icinga\Module\Jira\Web\RenderingHelper; use ipl\Html\Html; -use ipl\Html\HtmlString; +use ipl\Html\HtmlElement; use Icinga\Application\Config; use ipl\Html\Table; use ipl\I18n\Translation; use ipl\Web\Widget\Icon; -use stdClass; class IssueDetails extends Table { @@ -41,10 +40,12 @@ protected function assemble() $icingaKey = preg_replace('/^BEGIN(.+)END$/', '$1', $fields->$keyField); $parts = explode('!', $icingaKey); $host = array_shift($parts); + $helper->setHostName($host); if (empty($parts)) { $service = null; } else { $service = array_shift($parts); + $helper->setServiceName($service); } if (isset($fields->icingaUser)) { $user = $fields->icingaUser; @@ -74,16 +75,22 @@ protected function assemble() $this->translate('Created') => $helper->shortTimeSince($fields->created, false), ]); + $summary = $helper->getIssueComment( + $fields->summary, + $fields->created, + $fields->description + ); + if ($host !== null) { $this->addNameValueRow( $this->translate('Host'), - $helper->linkToMonitoringHost($host) + $helper->getHostLink() ); } if ($service !== null) { $this->addNameValueRow( $this->translate('Service'), - $helper->linkToMonitoringService($host, $service) + $helper->getServiceLink() ); } if ($user !== null) { @@ -94,11 +101,7 @@ protected function assemble() } $this->addComments(array_reverse($fields->comment->comments)); - $this->addComment( - $fields->summary, - $fields->created, - $fields->description - ); + $this->addComment($summary); } protected function addWideRow($content) @@ -113,9 +116,11 @@ protected function addComments($comments) foreach ($comments as $comment) { if (property_exists($comment, 'author')) { $this->addComment( - $this->formatAuthor($comment->author), - $comment->created, - $comment->body + $this->helper->getIssueComment( + $this->formatAuthor($comment->author), + $comment->created, + $comment->body + ) ); } } @@ -145,35 +150,16 @@ protected function formatAuthor($author) } } - protected function formatBody($body) + /** + * Add comment to the issue detail + * + * @param HtmlElement[] $comment + * + * @return $this + */ + protected function addComment(array $comment) { - $html = Html::wantHtml($body)->render(); - - // This is safe. - return new HtmlString($this->replaceLinks($html)); - } - - protected function replaceLinks($string) - { - return \preg_replace_callback('/\[([^|]+)\|([^]]+)]/', function ($match) { - return Html::tag('a', ['href' => $match[2], 'target' => '_blank'], $match[1]); - }, $string); - } - - protected function addComment($author, $time, $body) - { - return $this->addWideRow([ - Html::tag('h3', null, Html::sprintf( - '%s: %s', - $this->helper->shortTimeSince($time), - $author - )), - Html::tag( - 'pre', - ['class' => 'comment'], - $this->formatBody($body) - ), - ]); + return $this->addWideRow($comment); } protected function createNameValueRow($name, $value) diff --git a/library/Jira/Web/Table/IssuesTable.php b/library/Jira/Web/Table/IssuesTable.php index eb40dc5..7fa7a1f 100644 --- a/library/Jira/Web/Table/IssuesTable.php +++ b/library/Jira/Web/Table/IssuesTable.php @@ -33,6 +33,7 @@ protected function assemble() ], null, 'th')); foreach ($this->issues as $issue) { + $issueDescription = $helper->formatBody($issue->fields->description); $this->add(static::tr([ static::td([ $helper->renderAvatar($issue->fields->project), @@ -46,7 +47,7 @@ protected function assemble() $issue->fields->summary, Html::tag( 'p', - $issue->fields->description + $issueDescription ), ])->setSeparator(' '), static::td($helper->shortTimeSince($issue->fields->created)), diff --git a/phpstan-baseline-standard.neon b/phpstan-baseline-standard.neon index 431da3c..14eb4ee 100644 --- a/phpstan-baseline-standard.neon +++ b/phpstan-baseline-standard.neon @@ -425,16 +425,6 @@ parameters: count: 1 path: library/Jira/IcingaCommandPipe.php - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\IcingaCommandPipe\\:\\:setMonitoringInfo\\(\\) has no return type specified\\.$#" - count: 1 - path: library/Jira/IcingaCommandPipe.php - - - - message: "#^Property Icinga\\\\Module\\\\Jira\\\\IcingaCommandPipe\\:\\:\\$monitoringInfo has no type specified\\.$#" - count: 1 - path: library/Jira/IcingaCommandPipe.php - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\IcingadbBackend\\:\\:getObject\\(\\) should return Icinga\\\\Module\\\\Icingadb\\\\Model\\\\Host\\|Icinga\\\\Module\\\\Icingadb\\\\Model\\\\Service but returns ipl\\\\Orm\\\\Model\\.$#" count: 1 @@ -1615,46 +1605,6 @@ parameters: count: 1 path: library/Jira/Web/RenderingHelper.php - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoring\\(\\) has no return type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoring\\(\\) has parameter \\$host with no type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoring\\(\\) has parameter \\$service with no type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoringHost\\(\\) has no return type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoringHost\\(\\) has parameter \\$host with no type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoringService\\(\\) has no return type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoringService\\(\\) has parameter \\$host with no type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:linkToMonitoringService\\(\\) has parameter \\$service with no type specified\\.$#" - count: 1 - path: library/Jira/Web/RenderingHelper.php - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\RenderingHelper\\:\\:renderAvatar\\(\\) has no return type specified\\.$#" count: 1 @@ -1755,26 +1705,6 @@ parameters: count: 1 path: library/Jira/Web/Table/IssueDetails.php - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:addComment\\(\\) has no return type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:addComment\\(\\) has parameter \\$author with no type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:addComment\\(\\) has parameter \\$body with no type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:addComment\\(\\) has parameter \\$time with no type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:addComments\\(\\) has no return type specified\\.$#" count: 1 @@ -1850,31 +1780,6 @@ parameters: count: 1 path: library/Jira/Web/Table/IssueDetails.php - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:formatBody\\(\\) has no return type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:formatBody\\(\\) has parameter \\$body with no type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:replaceLinks\\(\\) has no return type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - - - message: "#^Method Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:replaceLinks\\(\\) has parameter \\$string with no type specified\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - - - message: "#^Parameter \\#2 \\$callback of function preg_replace_callback expects callable\\(array\\\\)\\: string, Closure\\(mixed\\)\\: ipl\\\\Html\\\\HtmlElement given\\.$#" - count: 1 - path: library/Jira/Web/Table/IssueDetails.php - - message: "#^Property Icinga\\\\Module\\\\Jira\\\\Web\\\\Table\\\\IssueDetails\\:\\:\\$helper has no type specified\\.$#" count: 1 diff --git a/public/css/module.less b/public/css/module.less index 0f93702..d202384 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -1,10 +1,20 @@ .issue-table { - td:first-of-type { - white-space: nowrap; - } - img { - vertical-align: middle; - } + td:first-of-type { + white-space: nowrap; + } + + img { + vertical-align: middle; + } + + td > p > a { + border-bottom: 1px dotted @text-color-light; + + &:hover { + border-bottom: 1px solid @text-color-light; + text-decoration: none; + } + } } .status-badge {