diff --git a/playbooks/robusta_playbooks/alerts_integration.py b/playbooks/robusta_playbooks/alerts_integration.py
index 7a5cead75..238c5304d 100644
--- a/playbooks/robusta_playbooks/alerts_integration.py
+++ b/playbooks/robusta_playbooks/alerts_integration.py
@@ -45,6 +45,7 @@
)
from robusta.core.playbooks.oom_killer_utils import logs_enricher, start_log_enrichment
from robusta.core.reporting import FindingSubject
+from robusta.core.reporting.base import Link, LinkType
from robusta.core.reporting.blocks import TableBlockFormat
from robusta.utils.parsing import format_event_templated_string
@@ -214,6 +215,8 @@ def default_enricher(alert: PrometheusKubernetesAlert, params: DefaultEnricherPa
By default, this enricher is last in the processing order, so it will be added to all alerts, that aren't silenced.
"""
+ alert.add_link(Link(url=alert.alert.generatorURL, name="View Graph", type=LinkType.PROMETHEUS_GENERATOR_URL))
+
labels = alert.alert.labels
alert.add_enrichment(
[
@@ -441,7 +444,7 @@ def format_pod_templated_string(pod: RobustaPod, template: Optional[str]) -> Opt
subject_type=FindingSubjectType.from_kind("pod"),
namespace=pod.metadata.namespace,
labels=pod.metadata.labels,
- annotations=pod.metadata.annotations
+ annotations=pod.metadata.annotations,
)
return format_event_templated_string(subject, template)
@@ -469,6 +472,7 @@ def alert_foreign_logs_enricher(event: PrometheusKubernetesAlert, params: Foreig
params.label_selectors = [format_event_templated_string(subject, selector) for selector in params.label_selectors]
return foreign_logs_enricher(event, params)
+
@action
def foreign_logs_enricher(event: ExecutionBaseEvent, params: ForeignLogParams):
"""
diff --git a/playbooks/robusta_playbooks/event_enrichments.py b/playbooks/robusta_playbooks/event_enrichments.py
index 546ee0dc0..f751bfba9 100644
--- a/playbooks/robusta_playbooks/event_enrichments.py
+++ b/playbooks/robusta_playbooks/event_enrichments.py
@@ -31,7 +31,7 @@
SlackAnnotations,
StatefulSet,
VideoEnricherParams,
- VideoLink,
+ Link,
action,
get_event_timestamp,
get_job_all_pods,
@@ -367,7 +367,7 @@ def external_video_enricher(event: ExecutionBaseEvent, params: VideoEnricherPara
"""
Attaches a video links to the finding
"""
- event.add_video_link(VideoLink(url=params.url, name=params.name))
+ event.add_video_link(Link(url=params.url, name=params.name))
@action
diff --git a/src/robusta/api/__init__.py b/src/robusta/api/__init__.py
index 4cb7da867..99bfe2b75 100644
--- a/src/robusta/api/__init__.py
+++ b/src/robusta/api/__init__.py
@@ -170,7 +170,7 @@
ScanReportBlock,
ScanReportRow,
TableBlock,
- VideoLink,
+ Link,
)
from robusta.core.reporting.action_requests import (
ActionRequestBody,
diff --git a/src/robusta/core/model/events.py b/src/robusta/core/model/events.py
index e78739493..9ec060e80 100644
--- a/src/robusta/core/model/events.py
+++ b/src/robusta/core/model/events.py
@@ -17,7 +17,8 @@
FindingSource,
FindingSubject,
FindingSubjectType,
- VideoLink,
+ Link,
+ LinkType,
)
from robusta.core.sinks import SinkBase
from robusta.integrations.scheduled.playbook_scheduler import PlaybooksScheduler
@@ -95,10 +96,15 @@ def __prepare_sinks_findings(self):
sink_finding.id = finding_id # share the same finding id between different sinks
self.sink_findings[sink].append(sink_finding)
- def add_video_link(self, video_link: VideoLink):
+ def add_link(self, link: Link, suppress_warning: bool = False) -> None:
self.__prepare_sinks_findings()
for sink in self.named_sinks:
- self.sink_findings[sink][0].add_video_link(video_link, True)
+ self.sink_findings[sink][0].add_link(link, suppress_warning)
+
+ def add_video_link(self, video_link: Link) -> None:
+ # For backward compatability
+ video_link.type = LinkType.VIDEO
+ self.add_link(video_link, True)
def emit_event(self, event_name: str, **kwargs):
"""Publish an event to the pubsub. It will be processed by the sinks during the execution of the playbook."""
diff --git a/src/robusta/core/reporting/__init__.py b/src/robusta/core/reporting/__init__.py
index 97116de99..8afaedb71 100644
--- a/src/robusta/core/reporting/__init__.py
+++ b/src/robusta/core/reporting/__init__.py
@@ -9,7 +9,7 @@
FindingStatus,
FindingSubject,
FindingSubjectType,
- VideoLink,
+ Link,
)
from robusta.core.reporting.blocks import (
CallbackBlock,
@@ -39,7 +39,7 @@
"Emojis",
"FindingSeverity",
"FindingStatus",
- "VideoLink",
+ "Link",
"FindingSource",
"Enrichment",
"Filterable",
diff --git a/src/robusta/core/reporting/base.py b/src/robusta/core/reporting/base.py
index a406921da..e371a77ad 100644
--- a/src/robusta/core/reporting/base.py
+++ b/src/robusta/core/reporting/base.py
@@ -6,6 +6,7 @@
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
+from strenum import StrEnum
from typing import Any, Dict, List, Optional, Union
from urllib.parse import urlencode
@@ -23,11 +24,13 @@ class BaseBlock(BaseModel):
html_class: str = None
-class Emojis(Enum):
+class Emojis(StrEnum):
Explain = "📘"
Recommend = "🛠"
Alert = "🚨"
K8Notification = "👀"
+ Video = "🎬"
+ Graph = "📈"
class FindingSeverity(Enum):
@@ -88,9 +91,25 @@ def to_emoji(self) -> str:
return "🔥"
-class VideoLink(BaseModel):
+class LinkType(StrEnum):
+ VIDEO = "video"
+ PROMETHEUS_GENERATOR_URL = "prometheus_generator_url"
+
+
+class Link(BaseModel):
url: str
name: str = "See more"
+ type: Optional[LinkType] = None
+
+ @property
+ def link_text(self):
+ if self.type == LinkType.PROMETHEUS_GENERATOR_URL:
+ return f"{Emojis.Graph} {self.name}"
+
+ if self.type == LinkType.VIDEO:
+ return f"{Emojis.Video} {self.name}"
+
+ return self.name
class EnrichmentType(Enum):
@@ -260,7 +279,7 @@ def __init__(
self.category = None # TODO fill real category
self.subject = subject
self.enrichments: List[Enrichment] = []
- self.video_links: List[VideoLink] = []
+ self.links: List[Link] = []
self.service = TopServiceResolver.guess_cached_resource(name=subject.name, namespace=subject.namespace)
self.service_key = self.service.get_resource_key() if self.service else ""
uri_path = f"services/{self.service_key}?tab=grouped" if self.service_key else "graphs"
@@ -341,11 +360,15 @@ def add_enrichment(
Enrichment(blocks=enrichment_blocks, annotations=annotations, enrichment_type=enrichment_type, title=title)
)
- def add_video_link(self, video_link: VideoLink, suppress_warning: bool = False):
+ def add_link(self, link: Link, suppress_warning: bool = False) -> None:
if self.dirty and not suppress_warning:
logging.warning("Updating a finding after it was added to the event is not allowed!")
+ self.links.append(link)
- self.video_links.append(video_link)
+ def add_video_link(self, video_link: Link, suppress_warning: bool = False) -> None:
+ # For backward compatability
+ video_link.type = LinkType.VIDEO
+ self.add_link(video_link, suppress_warning)
def __str__(self):
return f"title: {self.title} desc: {self.description} severity: {self.severity} sub-name: {self.subject.name} sub-type:{self.subject.subject_type.value} enrich: {self.enrichments}"
diff --git a/src/robusta/core/reporting/blocks.py b/src/robusta/core/reporting/blocks.py
index dbca6ea4a..9e6c7cf6a 100644
--- a/src/robusta/core/reporting/blocks.py
+++ b/src/robusta/core/reporting/blocks.py
@@ -528,7 +528,6 @@ class PrometheusBlock(BaseBlock):
class Config:
arbitrary_types_allowed = True
-
def __init__(
self,
data: PrometheusQueryResult,
diff --git a/src/robusta/core/reporting/url_helpers.py b/src/robusta/core/reporting/url_helpers.py
new file mode 100644
index 000000000..a46dae75a
--- /dev/null
+++ b/src/robusta/core/reporting/url_helpers.py
@@ -0,0 +1,31 @@
+import logging
+from typing import Optional
+from urllib.parse import ParseResult, parse_qs, urlencode, urlparse
+
+from robusta.core.model.env_vars import ROBUSTA_UI_DOMAIN
+
+
+PROM_GRAPH_PATH_URL: str = "/graph"
+PROM_GRAPH_URL_EXPR_PARAM: str = "g0.expr"
+
+
+def convert_prom_graph_url_to_robusta_metrics_explorer(prom_url: str, cluster_name: str, account_id: str) -> str:
+ try:
+ parsed_url: ParseResult = urlparse(prom_url)
+ if parsed_url.path != PROM_GRAPH_PATH_URL:
+ logging.warning("Failed to convert to robusta metric explorer url, url: %s not seems to be graph url", prom_url)
+ return prom_url
+
+ query_string = parse_qs(parsed_url.query)
+ expr_params: Optional[list[str]] = query_string.get(PROM_GRAPH_URL_EXPR_PARAM)
+ if not expr_params:
+ logging.warning("Failed to get expr params, url: %s not seems to be graph url", prom_url)
+ return prom_url
+
+ expr = expr_params[0]
+ params: dict[str, str] = {"query": expr, "cluster": cluster_name, "account": account_id}
+ robusta_metrics_url: str = f"{ROBUSTA_UI_DOMAIN}/metrics-explorer?{urlencode(params)}"
+ return robusta_metrics_url
+ except Exception:
+ logging.warning('Failed to convert prom url: %s to robusta url', prom_url, exc_info=True)
+ return prom_url
diff --git a/src/robusta/core/sinks/common/html_tools.py b/src/robusta/core/sinks/common/html_tools.py
index a4cd98540..c4bbe18f8 100644
--- a/src/robusta/core/sinks/common/html_tools.py
+++ b/src/robusta/core/sinks/common/html_tools.py
@@ -2,9 +2,10 @@
Some base code for handling HTML-outputting sinks. Currently used
by the mail and servicenow sinks.
"""
-from typing import List
-from robusta.core.reporting.base import BaseBlock, Finding
+from typing import List, Optional
+
+from robusta.core.reporting.base import BaseBlock, Emojis, Finding, LinkType
from robusta.core.reporting.blocks import LinksBlock, LinkProp
from robusta.core.reporting.blocks import FileBlock
from robusta.core.sinks.transformer import Transformer
@@ -88,21 +89,28 @@ def get_css(self):
}
"""
- def create_links(self, finding: Finding, html_class: str):
- links: List[LinkProp] = [LinkProp(
- text="Investigate 🔎",
- url=finding.get_investigate_uri(self.account_id, self.cluster_name),
- )]
-
- if finding.add_silence_url:
+ def create_links(self, finding: Finding, html_class: str, platform_enabled: bool) -> Optional[LinksBlock]:
+ links: List[LinkProp] = []
+ if platform_enabled:
links.append(
LinkProp(
- text="Configure Silences 🔕",
- url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ text="Investigate 🔎",
+ url=finding.get_investigate_uri(self.account_id, self.cluster_name),
)
)
- for video_link in finding.video_links:
- links.append(LinkProp(text=f"{video_link.name} 🎬", url=video_link.url))
+ if finding.add_silence_url:
+ links.append(
+ LinkProp(
+ text="Configure Silences 🔕",
+ url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ )
+ )
+
+ for link in finding.links:
+ links.append(LinkProp(text=link.link_text, url=link.url))
- return with_attr(LinksBlock(links=links), "html_class", html_class)
+ if links:
+ return with_attr(LinksBlock(links=links), "html_class", html_class)
+
+ return None
diff --git a/src/robusta/core/sinks/msteams/msteams_sink.py b/src/robusta/core/sinks/msteams/msteams_sink.py
index 423691ad4..f96b7f6b8 100644
--- a/src/robusta/core/sinks/msteams/msteams_sink.py
+++ b/src/robusta/core/sinks/msteams/msteams_sink.py
@@ -9,8 +9,15 @@ def __init__(self, sink_config: MsTeamsSinkConfigWrapper, registry):
super().__init__(sink_config.ms_teams_sink, registry)
self.webhook_url = sink_config.ms_teams_sink.webhook_url
self.webhook_override = sink_config.ms_teams_sink.webhook_override
+ self.sink_config = sink_config.ms_teams_sink
def write_finding(self, finding: Finding, platform_enabled: bool):
MsTeamsSender.send_finding_to_ms_teams(
- self.webhook_url, finding, platform_enabled, self.cluster_name, self.account_id, self.webhook_override
+ self.webhook_url,
+ finding,
+ platform_enabled,
+ self.cluster_name,
+ self.account_id,
+ self.webhook_override,
+ self.sink_config.prefer_redirect_to_platform,
)
diff --git a/src/robusta/core/sinks/opsgenie/opsgenie_sink.py b/src/robusta/core/sinks/opsgenie/opsgenie_sink.py
index 20d1cb460..b809cb192 100644
--- a/src/robusta/core/sinks/opsgenie/opsgenie_sink.py
+++ b/src/robusta/core/sinks/opsgenie/opsgenie_sink.py
@@ -78,19 +78,25 @@ def write_finding(self, finding: Finding, platform_enabled: bool):
self.__open_alert(finding, platform_enabled)
def __to_description(self, finding: Finding, platform_enabled: bool) -> str:
- description = ""
+ actions_block: list[str] = []
if platform_enabled:
- description = (
+ actions_block.append(
f'🔎 Investigate'
)
if finding.add_silence_url:
- description = f'{description} 🔕 Silence'
+ actions_block.append(
+ f'🔕 Silence'
+ )
- for video_link in finding.video_links:
- description = f'{description} 🎬 {video_link.name}'
- description = f"{description}\n"
+ for link in finding.links:
+ actions_block.append(f'{link.link_text}')
- return f"{description}{self.__enrichments_as_text(finding.enrichments)}"
+ if actions_block:
+ actions = f"{' '.join(actions_block)}\n"
+ else:
+ actions = ""
+
+ return f"{actions}{self.__enrichments_as_text(finding.enrichments)}"
def __to_details(self, finding: Finding) -> dict:
details = {
diff --git a/src/robusta/core/sinks/pagerduty/pagerduty_sink.py b/src/robusta/core/sinks/pagerduty/pagerduty_sink.py
index 953ff5c49..e93f0f354 100644
--- a/src/robusta/core/sinks/pagerduty/pagerduty_sink.py
+++ b/src/robusta/core/sinks/pagerduty/pagerduty_sink.py
@@ -4,17 +4,19 @@
import requests
from robusta.core.model.k8s_operation_type import K8sOperationType
-from robusta.core.reporting.base import BaseBlock, Finding, FindingSeverity, Enrichment
+from robusta.core.reporting.base import BaseBlock, Finding, FindingSeverity, Enrichment, Link, LinkType
from robusta.core.reporting.blocks import (
HeaderBlock,
JsonBlock,
KubernetesDiffBlock,
+ LinksBlock,
ListBlock,
MarkdownBlock,
TableBlock,
)
from robusta.core.reporting.consts import FindingAggregationKey
-from robusta.core.sinks.pagerduty.pagerduty_sink_params import PagerdutyConfigWrapper
+from robusta.core.reporting.url_helpers import convert_prom_graph_url_to_robusta_metrics_explorer
+from robusta.core.sinks.pagerduty.pagerduty_sink_params import PagerdutyConfigWrapper, PagerdutySinkParams
from robusta.core.sinks.sink_base import SinkBase
@@ -24,6 +26,7 @@ def __init__(self, sink_config: PagerdutyConfigWrapper, registry):
self.events_url = "https://events.pagerduty.com/v2/enqueue/"
self.change_url = "https://events.pagerduty.com/v2/change/enqueue"
self.api_key = sink_config.pagerduty_sink.api_key
+ self.sink_config: PagerdutySinkParams = sink_config.pagerduty_sink
@staticmethod
def __to_pagerduty_severity_type(severity: FindingSeverity):
@@ -57,15 +60,16 @@ def __send_changes_to_pagerduty(self, finding: Finding, platform_enabled: bool):
custom_details: dict = {}
links = []
if platform_enabled:
- links.append({
- "text": "🔂 See change history in Robusta",
- "href": finding.get_investigate_uri(self.account_id, self.cluster_name)
- })
+ links.append(
+ {
+ "text": "🔂 See change history in Robusta",
+ "href": finding.get_investigate_uri(self.account_id, self.cluster_name),
+ }
+ )
else:
- links.append({
- "text": "🔂 Enable Robusta UI to see change history",
- "href": "https://bit.ly/robusta-ui-pager-duty"
- })
+ links.append(
+ {"text": "🔂 Enable Robusta UI to see change history", "href": "https://bit.ly/robusta-ui-pager-duty"}
+ )
source = self.cluster_name
@@ -114,9 +118,9 @@ def __send_changes_to_pagerduty(self, finding: Finding, platform_enabled: bool):
"summary": summary,
"timestamp": timestamp,
"source": source,
- "custom_details": custom_details
+ "custom_details": custom_details,
},
- "links": links
+ "links": links,
}
headers = {"Content-Type": "application/json"}
@@ -126,33 +130,52 @@ def __send_changes_to_pagerduty(self, finding: Finding, platform_enabled: bool):
f"Error sending message to PagerDuty: {response.status_code}, {response.reason}, {response.text}"
)
- @staticmethod
def __send_events_to_pagerduty(self, finding: Finding, platform_enabled: bool):
custom_details: dict = {}
- links = []
+ links: list[dict[str, str]] = []
+
if platform_enabled:
- links.append({
- "text": "🔎 Investigate in Robusta",
- "href": finding.get_investigate_uri(self.account_id, self.cluster_name)
- })
+ links.append(
+ {
+ "text": "🔎 Investigate in Robusta",
+ "href": finding.get_investigate_uri(self.account_id, self.cluster_name),
+ }
+ )
if finding.add_silence_url:
- links.append({
- "text": "🔕 Create Prometheus Silence",
- "href": finding.get_prometheus_silence_url(self.account_id, self.cluster_name)
- })
+ links.append(
+ {
+ "text": "🔕 Create Prometheus Silence",
+ "href": finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ }
+ )
else:
- links.append({
- "text": "🔎 Enable Robusta UI to investigate",
- "href": "https://bit.ly/robusta-ui-pager-duty"
- })
+ links.append(
+ {"text": "🔎 Enable Robusta UI to investigate", "href": "https://bit.ly/robusta-ui-pager-duty"}
+ )
if finding.add_silence_url:
- links.append({
- "text": "🔕 Enable Robusta UI to silence alerts",
- "href": "https://bit.ly/robusta-ui-pager-duty"
- })
+ links.append(
+ {"text": "🔕 Enable Robusta UI to silence alerts", "href": "https://bit.ly/robusta-ui-pager-duty"}
+ )
+
+ prom_generator_link: Optional[Link] = next(
+ filter(lambda link: link.type == LinkType.PROMETHEUS_GENERATOR_URL, finding.links), None
+ )
+ if prom_generator_link:
+ link_url: str = prom_generator_link.url
+ if platform_enabled and self.sink_config.prefer_redirect_to_platform:
+ link_url = convert_prom_graph_url_to_robusta_metrics_explorer(
+ prom_generator_link.url, self.cluster_name, self.account_id
+ )
+
+ links.append(
+ {
+ "text": prom_generator_link.link_text,
+ "href": link_url,
+ }
+ )
# custom fields that don't have an inherent meaning in PagerDuty itself:
custom_details["Resource"] = finding.subject.name
@@ -163,9 +186,9 @@ def __send_events_to_pagerduty(self, finding: Finding, platform_enabled: bool):
custom_details["Severity"] = PagerdutySink.__to_pagerduty_severity_type(finding.severity).upper()
custom_details["Fingerprint ID"] = finding.fingerprint
custom_details["Description"] = finding.description
- custom_details[
- "Caption"
- ] = f"{finding.severity.to_emoji()} {PagerdutySink.__to_pagerduty_severity_type(finding.severity)} - {finding.title}"
+ custom_details["Caption"] = (
+ f"{finding.severity.to_emoji()} {PagerdutySink.__to_pagerduty_severity_type(finding.severity)} - {finding.title}"
+ )
message_lines = ""
if finding.description:
@@ -173,6 +196,16 @@ def __send_events_to_pagerduty(self, finding: Finding, platform_enabled: bool):
for enrichment in finding.enrichments:
for block in enrichment.blocks:
+ if isinstance(block, LinksBlock):
+ for link in block.links:
+ links.append(
+ {
+ "text": link.text,
+ "href": link.url,
+ }
+ )
+ continue
+
text = self.__to_unformatted_text_for_alerts(block)
if not text:
continue
@@ -208,7 +241,7 @@ def write_finding(self, finding: Finding, platform_enabled: bool):
if finding.aggregation_key == FindingAggregationKey.CONFIGURATION_CHANGE_KUBERNETES_RESOURCE_CHANGE.value:
return PagerdutySink.__send_changes_to_pagerduty(self, finding=finding, platform_enabled=platform_enabled)
- return PagerdutySink.__send_events_to_pagerduty(self, finding=finding, platform_enabled=platform_enabled)
+ return self.__send_events_to_pagerduty(finding=finding, platform_enabled=platform_enabled)
@staticmethod
def __to_unformatted_text_for_alerts(block: BaseBlock) -> str:
@@ -234,10 +267,12 @@ def __to_unformatted_text_for_alerts(block: BaseBlock) -> str:
@staticmethod
def __to_unformatted_text_for_changes(block: KubernetesDiffBlock) -> Optional[List[str]]:
- return list(map(
- lambda diff: diff.formatted_path,
- block.diffs,
- ))
+ return list(
+ map(
+ lambda diff: diff.formatted_path,
+ block.diffs,
+ )
+ )
# fetch the changed values from the block
@staticmethod
diff --git a/src/robusta/core/sinks/pushover/pushover_sink.py b/src/robusta/core/sinks/pushover/pushover_sink.py
index f7a67f2f1..92c962bf1 100644
--- a/src/robusta/core/sinks/pushover/pushover_sink.py
+++ b/src/robusta/core/sinks/pushover/pushover_sink.py
@@ -89,8 +89,10 @@ def __get_message_text(self, finding: Finding, platform_enabled: bool):
if finding.add_silence_url:
message_content += f"[{SILENCE_ICON} Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
- for video_link in finding.video_links:
- message_content = f"[{VIDEO_ICON} {video_link.name}]({video_link.url})"
+ for link in finding.links:
+ message_content += f"[{link.link_text}]({link.url})"
+
+ if message_content:
message_content += "\n\n"
blocks = [MarkdownBlock(text=f"Source: {self.cluster_name}\n\n")]
diff --git a/src/robusta/core/sinks/robusta/dal/model_conversion.py b/src/robusta/core/sinks/robusta/dal/model_conversion.py
index d30a8cd3b..e8ba5b91c 100644
--- a/src/robusta/core/sinks/robusta/dal/model_conversion.py
+++ b/src/robusta/core/sinks/robusta/dal/model_conversion.py
@@ -21,7 +21,7 @@
PrometheusBlock,
TableBlock,
)
-from robusta.core.reporting.blocks import EmptyFileBlock, GraphBlock
+from robusta.core.reporting.blocks import EmptyFileBlock, GraphBlock, LinksBlock
from robusta.core.reporting.callbacks import ExternalActionRequestBuilder
from robusta.core.reporting.holmes import HolmesChatResultsBlock, HolmesResultsBlock, ToolCallResult
from robusta.core.sinks.transformer import Transformer
@@ -50,7 +50,7 @@ def to_finding_json(account_id: str, cluster_id: str, finding: Finding):
"service_key": finding.service_key,
"cluster": cluster_id,
"account_id": account_id,
- "video_links": [link.dict() for link in finding.video_links],
+ "video_links": [link.dict() for link in finding.links], # TD: Migrate column in table.
"starts_at": datetime_to_db_str(finding.starts_at),
"updated_at": datetime_to_db_str(datetime.now()),
}
@@ -137,7 +137,7 @@ def to_evidence_json(
finding_id: uuid.UUID,
enrichment: Enrichment,
) -> Dict[Any, Any]:
- structured_data = []
+ structured_data: list[Any] = []
for block in enrichment.blocks:
if isinstance(block, MarkdownBlock):
if not block.text:
@@ -238,6 +238,9 @@ def to_evidence_json(
structured_data.append({"type": "json", "data": block.json_str})
elif isinstance(block, EventsRef):
structured_data.append({"type": "events_ref", "data": block.dict()})
+ elif isinstance(block, LinksBlock):
+ links = [link.dict() for link in block.links]
+ structured_data.append({"type": "list", "data": links})
else:
logging.warning(f"cannot convert block of type {type(block)} to robusta platform format block: {block}")
continue # no reason to crash the entire report
diff --git a/src/robusta/core/sinks/sink_base_params.py b/src/robusta/core/sinks/sink_base_params.py
index 9347ef74f..f9c055ecc 100644
--- a/src/robusta/core/sinks/sink_base_params.py
+++ b/src/robusta/core/sinks/sink_base_params.py
@@ -103,6 +103,7 @@ def validate_notification_mode(cls, values: Dict):
class SinkBaseParams(ABC, BaseModel):
name: str
send_svg: bool = False
+ prefer_redirect_to_platform = True
default: bool = True
match: dict = {}
scope: Optional[ScopeParams]
diff --git a/src/robusta/core/sinks/telegram/telegram_sink.py b/src/robusta/core/sinks/telegram/telegram_sink.py
index 7e26f36bf..0a4358fa6 100644
--- a/src/robusta/core/sinks/telegram/telegram_sink.py
+++ b/src/robusta/core/sinks/telegram/telegram_sink.py
@@ -24,8 +24,9 @@ class TelegramSink(SinkBase):
def __init__(self, sink_config: TelegramSinkConfigWrapper, registry):
super().__init__(sink_config.telegram_sink, registry)
- self.client = TelegramClient(sink_config.telegram_sink.chat_id, sink_config.telegram_sink.thread_id,
- sink_config.telegram_sink.bot_token)
+ self.client = TelegramClient(
+ sink_config.telegram_sink.chat_id, sink_config.telegram_sink.thread_id, sink_config.telegram_sink.bot_token
+ )
self.send_files = sink_config.telegram_sink.send_files
def write_finding(self, finding: Finding, platform_enabled: bool):
@@ -52,16 +53,9 @@ def __get_message_text(self, finding: Finding, platform_enabled: bool):
message_content = self.__build_telegram_title(title, status, finding.severity, finding.add_silence_url)
- if platform_enabled:
- message_content += (
- f"[{INVESTIGATE_ICON} Investigate]({finding.get_investigate_uri(self.account_id, self.cluster_name)}) "
- )
- if finding.add_silence_url:
- message_content += f"[{SILENCE_ICON} Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
-
- for video_link in finding.video_links:
- message_content = f"[{VIDEO_ICON} {video_link.name}]({video_link.url})"
- message_content += "\n\n"
+ actions_content: str = self._get_actions_block(finding, platform_enabled)
+ if actions_content:
+ message_content += actions_content
blocks = [MarkdownBlock(text=f"*Source:* `{self.cluster_name}`\n\n")]
@@ -80,14 +74,32 @@ def __get_message_text(self, finding: Finding, platform_enabled: bool):
return message_content
+ def _get_actions_block(self, finding: Finding, platform_enabled: bool):
+ actions_content = ""
+ if platform_enabled:
+ actions_content += (
+ f"[{INVESTIGATE_ICON} Investigate]({finding.get_investigate_uri(self.account_id, self.cluster_name)}) "
+ )
+ if finding.add_silence_url:
+ actions_content += f"[{SILENCE_ICON} Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
+
+ for link in finding.links:
+ actions_content = f"[{link.link_text}]({link.url})"
+
+ if actions_content:
+ actions_content += "\n\n"
+
+ return actions_content
+
@classmethod
def __is_telegram_text_block(cls, block: BaseBlock) -> bool:
# enrichments text tables are too big for mobile device
return not (isinstance(block, FileBlock) or isinstance(block, TableBlock))
@classmethod
- def __build_telegram_title(cls, title: str, status: FindingStatus, severity: FindingSeverity,
- add_silence_url: bool) -> str:
+ def __build_telegram_title(
+ cls, title: str, status: FindingStatus, severity: FindingSeverity, add_silence_url: bool
+ ) -> str:
icon = SEVERITY_EMOJI_MAP.get(severity, "")
status_str: str = f"{status.to_emoji()} {status.name.lower()} - " if add_silence_url else ""
return f"{status_str}{icon} {severity.name} - *{title}*\n\n"
diff --git a/src/robusta/core/sinks/victorops/victorops_sink.py b/src/robusta/core/sinks/victorops/victorops_sink.py
index 35c9dc06c..c7c3881c3 100644
--- a/src/robusta/core/sinks/victorops/victorops_sink.py
+++ b/src/robusta/core/sinks/victorops/victorops_sink.py
@@ -29,8 +29,8 @@ def write_finding(self, finding: Finding, platform_enabled: bool):
self.account_id, self.cluster_name
)
- for video_link in finding.video_links:
- json_dict[f"vo_annotate.u.🎬 {video_link.name}"] = video_link.url
+ for link in finding.links:
+ json_dict[f"vo_annotate.u.🎬 {link.name}"] = link.url
# custom fields
json_dict["Resource"] = finding.subject.name
diff --git a/src/robusta/core/sinks/webhook/webhook_sink.py b/src/robusta/core/sinks/webhook/webhook_sink.py
index fa7959962..c56127492 100644
--- a/src/robusta/core/sinks/webhook/webhook_sink.py
+++ b/src/robusta/core/sinks/webhook/webhook_sink.py
@@ -44,8 +44,8 @@ def __write_text(self, finding: Finding, platform_enabled: bool):
f"Silence: {finding.get_prometheus_silence_url(self.account_id, self.cluster_name)}"
)
- for video_link in finding.video_links:
- message_lines.append(f"{video_link.name}: {video_link.url}")
+ for link in finding.links:
+ message_lines.append(f"{link.name}: {link.url}")
message_lines.append(f"Source: {self.cluster_name}")
message_lines.append(finding.description)
diff --git a/src/robusta/core/sinks/yamessenger/yamessenger_sink.py b/src/robusta/core/sinks/yamessenger/yamessenger_sink.py
index 72468a885..533fb1c5c 100644
--- a/src/robusta/core/sinks/yamessenger/yamessenger_sink.py
+++ b/src/robusta/core/sinks/yamessenger/yamessenger_sink.py
@@ -18,7 +18,6 @@
}
INVESTIGATE_ICON = "\U0001F50E"
SILENCE_ICON = "\U0001F515"
-VIDEO_ICON = "\U0001F3AC"
class YaMessengerSink(SinkBase):
def __init__(self, sink_config: YaMessengerSinkConfigWrapper, registry):
@@ -64,9 +63,9 @@ def __get_message_text(self, finding: Finding, platform_enabled: bool):
if finding.add_silence_url:
message_content += f"[{SILENCE_ICON} Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
- for video_link in finding.video_links:
- message_content = f"[{VIDEO_ICON} {video_link.name}]({video_link.url})"
- message_content += "\n\n"
+ for link in finding.links:
+ message_content += f"[{link.link_text}]({link.url})"
+ message_content += "\n\n"
blocks = [MarkdownBlock(text=f"*Source:* `{self.cluster_name}`\n\n")]
diff --git a/src/robusta/integrations/discord/sender.py b/src/robusta/integrations/discord/sender.py
index 5f2b746ac..46dc31c2c 100644
--- a/src/robusta/integrations/discord/sender.py
+++ b/src/robusta/integrations/discord/sender.py
@@ -2,7 +2,7 @@
import re
from enum import Enum
from itertools import chain
-from typing import Dict, List, Tuple, Union
+from typing import Dict, List, Optional, Tuple, Union
import requests
@@ -112,8 +112,8 @@ def __extract_markdown_name(block: MarkdownBlock):
regex = re.compile(r"\*.+\*")
match = re.match(regex, block.text)
if match:
- title = text[match.span()[0]: match.span()[1]]
- text = text[match.span()[1]:]
+ title = text[match.span()[0] : match.span()[1]]
+ text = text[match.span()[1] :]
return title, DiscordSender.__transform_markdown_links(text) or BLANK_CHAR
@staticmethod
@@ -193,12 +193,12 @@ def __to_discord(self, block: BaseBlock, sink_name: str) -> List[Union[DiscordBl
return [] # no reason to crash the entire report
def __send_blocks_to_discord(
- self,
- report_blocks: List[BaseBlock],
- title: str,
- status: FindingStatus,
- severity: FindingSeverity,
- msg_color: str,
+ self,
+ report_blocks: List[BaseBlock],
+ title: str,
+ status: FindingStatus,
+ severity: FindingSeverity,
+ msg_color: str,
):
# Process attachment blocks
file_blocks = add_pngs_for_all_svgs([b for b in report_blocks if isinstance(b, FileBlock)])
@@ -250,20 +250,14 @@ def __send_blocks_to_discord(
logging.debug("Message was delivered successfully")
def send_finding_to_discord(
- self,
- finding: Finding,
- platform_enabled: bool,
+ self,
+ finding: Finding,
+ platform_enabled: bool,
):
blocks: List[BaseBlock] = []
- if platform_enabled: # add link to the robusta ui, if it's configured
- actions = f"[:mag_right: Investigate]({finding.get_investigate_uri(self.account_id, self.cluster_name)})"
- if finding.add_silence_url:
- actions = f"{actions} [:no_bell: Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
-
- for video_link in finding.video_links:
- actions = f"{actions} [:clapper: {video_link.name}]({video_link.url})"
- blocks.append(DiscordDescriptionBlock(description=actions))
-
+ actions_block = self._get_actions_block(finding, platform_enabled)
+ if actions_block:
+ blocks.append(actions_block)
blocks.append(DiscordFieldBlock(name="Source", value=f"`{self.cluster_name}`"))
# first add finding description block
@@ -298,3 +292,22 @@ def send_finding_to_discord(
severity=finding.severity,
msg_color=msg_color,
)
+
+ def _get_actions_block(self, finding: Finding, platform_enabled: bool) -> Optional[DiscordDescriptionBlock]:
+ actions: list[str] = []
+ if platform_enabled: # add link to the robusta ui, if it's configured
+ actions.append(
+ "[:mag_right: Investigate]({finding.get_investigate_uri(self.account_id, self.cluster_name)})"
+ )
+ if finding.add_silence_url:
+ actions.append(
+ f"[:no_bell: Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
+ )
+
+ for link in finding.links:
+ actions.append(f"[:clapper: {link.name}]({link.url})")
+
+ if actions:
+ return DiscordDescriptionBlock(description=" ".join(actions))
+
+ return None
diff --git a/src/robusta/integrations/google_chat/sender.py b/src/robusta/integrations/google_chat/sender.py
index 87f0a5125..1543b15ed 100644
--- a/src/robusta/integrations/google_chat/sender.py
+++ b/src/robusta/integrations/google_chat/sender.py
@@ -28,8 +28,9 @@ def send_finding(self, finding: Finding, platform_enabled: bool):
)
blocks.append(self.__create_finding_header(finding, status))
- if platform_enabled:
- blocks.append(self.__create_links(finding))
+ links_block = self.__create_links(finding, platform_enabled)
+ if links_block:
+ blocks.append(links_block)
blocks.append(MarkdownBlock(text=f"*Source:* `{self.cluster_name}`\n\n"))
if finding.description:
@@ -120,24 +121,25 @@ def __create_finding_header(self, finding: Finding, status: FindingStatus) -> Ma
f"<{finding.get_investigate_uri(self.account_id, self.cluster_name)}|*{title}*>\n\n"
)
- def __create_links(self, finding: Finding):
+ def __create_links(self, finding: Finding, platform_enabled: bool):
links: List[LinkProp] = []
- links.append(
- LinkProp(
- text="Investigate 🔎",
- url=finding.get_investigate_uri(self.account_id, self.cluster_name),
- )
- )
-
- if finding.add_silence_url:
+ if platform_enabled:
links.append(
LinkProp(
- text="Configure Silences 🔕",
- url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ text="Investigate 🔎",
+ url=finding.get_investigate_uri(self.account_id, self.cluster_name),
)
)
- for video_link in finding.video_links:
- links.append(LinkProp(text=f"{video_link.name} 🎬", url=video_link.url))
+ if finding.add_silence_url:
+ links.append(
+ LinkProp(
+ text="Configure Silences 🔕",
+ url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ )
+ )
+
+ for link in finding.links:
+ links.append(LinkProp(text=f"{link.link_text}", url=link.url))
return LinksBlock(links=links)
diff --git a/src/robusta/integrations/jira/sender.py b/src/robusta/integrations/jira/sender.py
index c5deeb16f..42d26a808 100644
--- a/src/robusta/integrations/jira/sender.py
+++ b/src/robusta/integrations/jira/sender.py
@@ -204,10 +204,10 @@ def send_finding_to_jira(
)
)
- for video_link in finding.video_links:
- actions.append(
- to_paragraph(f"🎬 {video_link.name}", [{"type": "link", "attrs": {"href": video_link.url}}])
- )
+ for link in finding.links:
+ actions.append(
+ to_paragraph(f"{link.link_text}", [{"type": "link", "attrs": {"href": link.url}}])
+ )
# Add runbook_url to issue markdown if present
if finding.subject.annotations.get("runbook_url", None):
diff --git a/src/robusta/integrations/mail/sender.py b/src/robusta/integrations/mail/sender.py
index 0f42a94d3..85cb0d37d 100644
--- a/src/robusta/integrations/mail/sender.py
+++ b/src/robusta/integrations/mail/sender.py
@@ -31,8 +31,9 @@ def send_finding(self, finding: Finding, platform_enabled: bool, include_headers
if include_headers:
blocks.append(self.__create_finding_header(finding, status))
- if platform_enabled:
- blocks.append(self.create_links(finding, html_class="header_links"))
+ links_block = self.create_links(finding, "header_links", platform_enabled)
+ if links_block:
+ blocks.append(links_block)
blocks.append(MarkdownBlock(text=f"*Source:* `{self.cluster_name}`"))
diff --git a/src/robusta/integrations/mattermost/sender.py b/src/robusta/integrations/mattermost/sender.py
index ce9e0abba..7d6d663c1 100644
--- a/src/robusta/integrations/mattermost/sender.py
+++ b/src/robusta/integrations/mattermost/sender.py
@@ -49,8 +49,9 @@ def __init__(self, cluster_name: str, account_id: str, client: MattermostClient,
self.sink_params = sink_params
@classmethod
- def __add_mattermost_title(cls, title: str, status: FindingStatus, severity: FindingSeverity,
- add_silence_url: bool) -> str:
+ def __add_mattermost_title(
+ cls, title: str, status: FindingStatus, severity: FindingSeverity, add_silence_url: bool
+ ) -> str:
icon = SEVERITY_EMOJI_MAP.get(severity, "")
status_str: str = f"{status.to_emoji()} {status.name.lower()} - " if add_silence_url else ""
return f"{status_str}{icon} {severity.name} - **{title}**"
@@ -89,13 +90,13 @@ def __to_mattermost_diff(self, block: KubernetesDiffBlock, sink_name: str) -> st
return "\n".join(_blocks)
def __send_blocks_to_mattermost(
- self,
- report_blocks: List[BaseBlock],
- title: str,
- status: FindingStatus,
- severity: FindingSeverity,
- msg_color: str,
- add_silence_url: bool,
+ self,
+ report_blocks: List[BaseBlock],
+ title: str,
+ status: FindingStatus,
+ severity: FindingSeverity,
+ msg_color: str,
+ add_silence_url: bool,
):
# Process attachment blocks
@@ -112,8 +113,9 @@ def __send_blocks_to_mattermost(
output_blocks = []
header_block = {}
if title:
- title = self.__add_mattermost_title(title=title, status=status, severity=severity,
- add_silence_url=add_silence_url)
+ title = self.__add_mattermost_title(
+ title=title, status=status, severity=severity, add_silence_url=add_silence_url
+ )
header_block = self.__to_mattermost(HeaderBlock(title), self.sink_params.name)
for block in other_blocks:
output_blocks.append(self.__to_mattermost(block, self.sink_params.name))
@@ -130,16 +132,31 @@ def __send_blocks_to_mattermost(
self.client.post_message(header_block, attachments, file_attachments)
- def send_finding_to_mattermost(self, finding: Finding, platform_enabled: bool):
- blocks: List[BaseBlock] = []
+ def _get_actions_markdown(self, finding: Finding, platform_enabled: bool):
+ actions: list[str] = []
if platform_enabled: # add link to the robusta ui, if it's configured
- actions = f"[:mag_right: Investigate]({finding.get_investigate_uri(self.account_id, self.cluster_name)})"
+ actions.append(
+ f"[:mag_right: Investigate]({finding.get_investigate_uri(self.account_id, self.cluster_name)})"
+ )
if finding.add_silence_url:
- actions = f"{actions} [:no_bell: Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
- for video_link in finding.video_links:
- actions = f"{actions} [:clapper: {video_link.name}]({video_link.url})"
+ actions.append(
+ f"[:no_bell: Silence]({finding.get_prometheus_silence_url(self.account_id, self.cluster_name)})"
+ )
+
+ for link in finding.links:
+ actions.append(f"[:clapper: {link.name}]({link.url})")
+
+ if actions:
+ return MarkdownBlock(" ".join(actions))
+
+ return None
+
+ def send_finding_to_mattermost(self, finding: Finding, platform_enabled: bool):
+ blocks: List[BaseBlock] = []
- blocks.append(MarkdownBlock(actions))
+ actions_block = self._get_actions_markdown(finding, platform_enabled)
+ if actions_block:
+ blocks.append(actions_block)
blocks.append(MarkdownBlock(f"*Source:* `{self.cluster_name}`\n"))
diff --git a/src/robusta/integrations/msteams/msteams_msg.py b/src/robusta/integrations/msteams/msteams_msg.py
index f169922c0..4095c122c 100644
--- a/src/robusta/integrations/msteams/msteams_msg.py
+++ b/src/robusta/integrations/msteams/msteams_msg.py
@@ -14,7 +14,8 @@
MarkdownBlock,
TableBlock,
)
-from robusta.core.reporting.base import FindingStatus
+from robusta.core.reporting.base import FindingStatus, LinkType
+from robusta.core.reporting.url_helpers import convert_prom_graph_url_to_robusta_metrics_explorer
from robusta.integrations.msteams.msteams_adaptive_card_files import MsTeamsAdaptiveCardFiles
from robusta.integrations.msteams.msteams_elements.msteams_base import MsTeamsBase
from robusta.integrations.msteams.msteams_elements.msteams_card import MsTeamsCard
@@ -31,11 +32,12 @@ class MsTeamsMsg:
# a safe zone of less then 28K
MAX_SIZE_IN_BYTES = 1024 * 20
- def __init__(self, webhook_url: str):
+ def __init__(self, webhook_url: str, prefer_redirect_to_platform: bool):
self.entire_msg: List[MsTeamsBase] = []
self.current_section: List[MsTeamsBase] = []
self.text_file_containers = []
self.webhook_url = webhook_url
+ self.prefer_redirect_to_platform = prefer_redirect_to_platform
def write_title_and_desc(self, platform_enabled: bool, finding: Finding, cluster_name: str, account_id: str):
status: FindingStatus = (
@@ -46,14 +48,7 @@ def write_title_and_desc(self, platform_enabled: bool, finding: Finding, cluster
block = MsTeamsTextBlock(text=f"{title}", font_size="extraLarge")
self.__write_to_entire_msg([block])
- if platform_enabled: # add link to the Robusta ui, if it's configured
- silence_url = finding.get_prometheus_silence_url(account_id, cluster_name)
- actions = f"[🔎 Investigate]({finding.get_investigate_uri(account_id, cluster_name)})"
- if finding.add_silence_url:
- actions = f"{actions} [🔕 Silence]({silence_url})"
- for video_link in finding.video_links:
- actions = f"{actions} [🎬 {video_link.name}]({video_link.url})"
- self.__write_to_entire_msg([MsTeamsTextBlock(text=actions)])
+ self._add_actions(platform_enabled, finding, cluster_name, account_id)
self.__write_to_entire_msg([MsTeamsTextBlock(text=f"**Source:** *{cluster_name}*")])
@@ -61,6 +56,25 @@ def write_title_and_desc(self, platform_enabled: bool, finding: Finding, cluster
block = MsTeamsTextBlock(text=finding.description)
self.__write_to_entire_msg([block])
+ def _add_actions(self, platform_enabled: bool, finding: Finding, cluster_name: str, account_id: str):
+ actions: list[str] = []
+ if platform_enabled: # add link to the Robusta ui, if it's configured
+ actions.append(f"[🔎 Investigate]({finding.get_investigate_uri(account_id, cluster_name)})")
+
+ if finding.add_silence_url:
+ silence_url = finding.get_prometheus_silence_url(account_id, cluster_name)
+ actions.append(f"[🔕 Silence]({silence_url})")
+
+ for link in finding.links:
+ link_url = link.url
+ if link.type == LinkType.PROMETHEUS_GENERATOR_URL and self.prefer_redirect_to_platform:
+ link_url = convert_prom_graph_url_to_robusta_metrics_explorer(link.url, cluster_name, account_id)
+ action: str = f"[{link.link_text}]({link_url})"
+ actions.append(action)
+
+ if actions:
+ self.__write_to_entire_msg([MsTeamsTextBlock(text=" ".join(actions))])
+
@classmethod
def __build_msteams_title(
cls, title: str, status: FindingStatus, severity: FindingSeverity, add_silence_url: bool
diff --git a/src/robusta/integrations/msteams/sender.py b/src/robusta/integrations/msteams/sender.py
index 1e2db511c..0b98a239f 100644
--- a/src/robusta/integrations/msteams/sender.py
+++ b/src/robusta/integrations/msteams/sender.py
@@ -58,11 +58,12 @@ def send_finding_to_ms_teams(
cluster_name: str,
account_id: str,
webhook_override: str,
+ prefer_redirect_to_platform: bool,
):
webhook_url = MsTeamsWebhookUrlTransformer.template(
webhook_override=webhook_override, default_webhook_url=webhook_url, annotations=finding.subject.annotations
)
- msg = MsTeamsMsg(webhook_url)
+ msg = MsTeamsMsg(webhook_url, prefer_redirect_to_platform)
msg.write_title_and_desc(platform_enabled, finding, cluster_name, account_id)
for enrichment in finding.enrichments:
diff --git a/src/robusta/integrations/rocketchat/sender.py b/src/robusta/integrations/rocketchat/sender.py
index 32d2b8a0d..0d53dca5d 100644
--- a/src/robusta/integrations/rocketchat/sender.py
+++ b/src/robusta/integrations/rocketchat/sender.py
@@ -296,25 +296,26 @@ def __create_finding_header(self, finding: Finding, status: FindingStatus, platf
return MarkdownBlock(f"{status_str} {sev.to_emoji()} `{sev.name.lower()}` {title}")
- def __create_links(self, finding: Finding):
+ def __create_links(self, finding: Finding, platform_enabled: bool) -> LinksBlock:
links: List[LinkProp] = []
- links.append(
- LinkProp(
- text="Investigate 🔎",
- url=finding.get_investigate_uri(self.account_id, self.cluster_name),
- )
- )
-
- if finding.add_silence_url:
+ if platform_enabled:
links.append(
LinkProp(
- text="Configure Silences 🔕",
- url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ text="Investigate 🔎",
+ url=finding.get_investigate_uri(self.account_id, self.cluster_name),
)
)
- for video_link in finding.video_links:
- links.append(LinkProp(text=f"{video_link.name} 🎬", url=video_link.url))
+ if finding.add_silence_url:
+ links.append(
+ LinkProp(
+ text="Configure Silences 🔕",
+ url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ )
+ )
+
+ for link in finding.links:
+ links.append(LinkProp(text=f"{link.link_text}", url=link.url))
return LinksBlock(links=links)
@@ -335,8 +336,7 @@ def send_finding_to_rocketchat(
if finding.title:
blocks.append(self.__create_finding_header(finding, status, platform_enabled))
- if platform_enabled:
- blocks.append(self.__create_links(finding))
+ blocks.append(self.__create_links(finding, platform_enabled))
blocks.append(MarkdownBlock(text=f"*Source:* `{self.cluster_name}`"))
if finding.description:
diff --git a/src/robusta/integrations/servicenow/sender.py b/src/robusta/integrations/servicenow/sender.py
index 8eb71ded1..4b5933b9d 100644
--- a/src/robusta/integrations/servicenow/sender.py
+++ b/src/robusta/integrations/servicenow/sender.py
@@ -92,8 +92,9 @@ def send_finding(self, finding: Finding, platform_enabled: bool):
def format_message(self, finding: Finding, platform_enabled: bool) -> Tuple[HTMLTransformer, str]:
blocks: List[BaseBlock] = []
- if platform_enabled:
- blocks.append(self.create_links(finding, html_class="header_links"))
+ links_block = self.create_links(finding, "header_links", platform_enabled)
+ if links_block:
+ blocks.append(links_block)
blocks.append(MarkdownBlock(text=f"*Source:* `{self.cluster_name}`"))
if finding.description:
diff --git a/src/robusta/integrations/slack/sender.py b/src/robusta/integrations/slack/sender.py
index 007e0bea5..482ff1afd 100644
--- a/src/robusta/integrations/slack/sender.py
+++ b/src/robusta/integrations/slack/sender.py
@@ -12,9 +12,14 @@
from slack_sdk.errors import SlackApiError
from robusta.core.model.base_params import AIInvestigateParams, ResourceInfo
-from robusta.core.model.env_vars import ADDITIONAL_CERTIFICATE, SLACK_REQUEST_TIMEOUT, HOLMES_ENABLED, SLACK_TABLE_COLUMNS_LIMIT
+from robusta.core.model.env_vars import (
+ ADDITIONAL_CERTIFICATE,
+ SLACK_REQUEST_TIMEOUT,
+ HOLMES_ENABLED,
+ SLACK_TABLE_COLUMNS_LIMIT,
+)
from robusta.core.playbooks.internal.ai_integration import ask_holmes
-from robusta.core.reporting.base import Emojis, EnrichmentType, Finding, FindingStatus
+from robusta.core.reporting.base import Emojis, EnrichmentType, Finding, FindingStatus, LinkType
from robusta.core.reporting.blocks import (
BaseBlock,
CallbackBlock,
@@ -33,6 +38,7 @@
from robusta.core.reporting.callbacks import ExternalActionRequestBuilder
from robusta.core.reporting.consts import EnrichmentAnnotation, FindingSource, FindingType, SlackAnnotations
from robusta.core.reporting.holmes import HolmesResultsBlock, ToolCallResult
+from robusta.core.reporting.url_helpers import convert_prom_graph_url_to_robusta_metrics_explorer
from robusta.core.reporting.utils import add_pngs_for_all_svgs
from robusta.core.sinks.common import ChannelTransformer
from robusta.core.sinks.sink_base import KeyT
@@ -350,26 +356,39 @@ def __create_finding_header(
{title}"""
)
- def __create_links(self, finding: Finding, include_investigate_link: bool):
+ def __create_links(
+ self,
+ finding: Finding,
+ platform_enabled: bool,
+ include_investigate_link: bool,
+ prefer_redirect_to_platform: bool,
+ ):
links: List[LinkProp] = []
- if include_investigate_link:
- links.append(
- LinkProp(
- text="Investigate 🔎",
- url=finding.get_investigate_uri(self.account_id, self.cluster_name),
+ if platform_enabled:
+ if include_investigate_link:
+ links.append(
+ LinkProp(
+ text="Investigate 🔎",
+ url=finding.get_investigate_uri(self.account_id, self.cluster_name),
+ )
)
- )
- if finding.add_silence_url:
- links.append(
- LinkProp(
- text="Configure Silences 🔕",
- url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ if finding.add_silence_url:
+ links.append(
+ LinkProp(
+ text="Configure Silences 🔕",
+ url=finding.get_prometheus_silence_url(self.account_id, self.cluster_name),
+ )
+ )
+
+ for link in finding.links:
+ link_url = link.url
+ if link.type == LinkType.PROMETHEUS_GENERATOR_URL and prefer_redirect_to_platform:
+ link_url = convert_prom_graph_url_to_robusta_metrics_explorer(
+ link.url, self.cluster_name, self.account_id
)
- )
- for video_link in finding.video_links:
- links.append(LinkProp(text=f"{video_link.name} 🎬", url=video_link.url))
+ links.append(LinkProp(text=link.link_text, url=link_url))
return LinksBlock(links=links)
@@ -489,8 +508,10 @@ def send_finding_to_slack(
if finding.title:
blocks.append(self.__create_finding_header(finding, status, platform_enabled, sink_params.investigate_link))
- if platform_enabled:
- blocks.append(self.__create_links(finding, sink_params.investigate_link))
+ links_block: LinksBlock = self.__create_links(
+ finding, platform_enabled, sink_params.investigate_link, sink_params.prefer_redirect_to_platform
+ )
+ blocks.append(links_block)
if HOLMES_ENABLED:
blocks.append(self.__create_holmes_callback(finding))
diff --git a/src/robusta/integrations/zulip/sender.py b/src/robusta/integrations/zulip/sender.py
index f95d014a7..4d6431a20 100644
--- a/src/robusta/integrations/zulip/sender.py
+++ b/src/robusta/integrations/zulip/sender.py
@@ -12,7 +12,7 @@
MarkdownBlock,
TableBlock,
)
-from robusta.core.reporting.base import BaseBlock, Finding, FindingStatus
+from robusta.core.reporting.base import BaseBlock, Finding, FindingStatus, LinkType
from robusta.core.reporting.blocks import FileBlock, LinksBlock
from robusta.core.reporting.consts import FindingSource
from robusta.core.reporting.utils import convert_svg_to_png
@@ -138,8 +138,9 @@ def send_finding_to_zulip(self, finding: Finding, sink_params: ZulipSinkParams,
if finding.add_silence_url:
silence_url = finding.get_prometheus_silence_url(self.account_id, self.cluster_name)
message_lines.append(self.__to_zulip_link("🔕 Silence", silence_url))
- for video_link in finding.video_links:
- message_lines.append(f"🎬 {self.__to_zulip_link(video_link.name, video_link.url)}")
+
+ for link in finding.links:
+ message_lines.append(f"🎬 {self.__to_zulip_link(link.name, link.url)}")
message_lines.append(f"{self.__to_zulip_bold('Source:')} `{self.cluster_name}`")
message_lines.append(finding.description)