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)