Creating Findings

Motivation

Playbooks should use the Findings API to display output. They should not send output directly to Slack or other destinations.

By using the Findings API, your playbook will support Slack, MSTeams, and other sinks.

Basic Usage

Playbooks can call event.add_enrichment to add to the playbook's output. For example:

@action
def test_playbook(event: ExecutionBaseEvent):
    event.add_enrichment(
        [
            MarkdownBlock(
                "This is a *markdown* message. Here are some movie characters:"
            ),
            TableBlock(
                [["Han Solo", "Star Wars"], ["Paul Atreides", "Dune"]],
                ["name", "movie"],
            ),
        ]
    )

When playbooks finish running, their output is sent to the configured sinks. Here is output for the above example:

Core Concepts

The Findings API has four important concepts:

Finding

An event, like a Prometheus alert or a deployment update.

Enrichment

Details about a Finding, like the labels for a Prometheus alert or a deployment's YAML before and after the update.

Block

A visual element, like a paragraph, an image, or a table.

Sink

A destination Findings are sent to, like Slack, MSTeams, Kafka topics

Here is an example showing the above concepts:

digraph {
  compound=true;
  rankdir=TB
  fixedsize=true;

  node [ fontname="Handlee"
         fixedsize=true
         width=2
         height=1
  ];
  subgraph cluster_finding {

      label=<Finding<BR /><BR /><I><FONT POINT-SIZE="9">HighCPU Alert</FONT></I>>;

      subgraph cluster_enrichment1 {
          label=<Enrichment<BR /><BR /><I><FONT POINT-SIZE="9">Prometheus Labels</FONT></I>>;
          block1 [
              label = <TableBlock<BR /><BR /><I><FONT POINT-SIZE="9">pod=my-pod<BR />namespace=default</FONT></I>>;
          ]
      }

      subgraph cluster_enrichment2 {
          label=<Enrichment<BR /><BR /><I><FONT POINT-SIZE="9">CPU Profile Result</FONT></I>>;
          rank=same;
          block2 [
              label = <MarkdownBlock<BR /><BR /><I><FONT POINT-SIZE="9">Explanation</FONT></I>>;
          ]
          block3 [
              label = <FileBlock<BR /><BR /><I><FONT POINT-SIZE="9">Profiler Result</FONT></I>>;
          ]
      }
  }

      slack_sink [
          label = <Slack Sink>;
      ]
      msteams_sink [
          label = <MSTeams Sink>;
      ]
      more_sinks [
          label = <...>;
      ]

  block2 -> slack_sink, more_sinks, msteams_sink [ltail=cluster_finding minlen=2];
}

Advanced Usage

It is possible to customize a findings name, override the default finding for events, and more.

But we haven't documented it yet. Please consult the source code.

class Finding(title, aggregation_key, severity=FindingSeverity.INFO, source=FindingSource.NONE, description=None, subject=<robusta.core.reporting.base.FindingSubject object>, finding_type=FindingType.ISSUE, failure=True, creation_date=None, fingerprint=None, starts_at=None, ends_at=None, add_silence_url=False, silence_labels=None)[source]

A Finding represents an event that should be sent to sinks.

property attribute_map : dict[str, str | dict[str, str]]
Return type:

Dict[str, Union[str, Dict[str, str]]]

get_investigate_uri(account_id, cluster_name=None)[source]
add_enrichment(enrichment_blocks, annotations=None, suppress_warning=False, enrichment_type=None, title=None)[source]
get_prometheus_silence_url(account_id, cluster_name)[source]
Return type:

str

Block Types

Every Block represents a different type of visual data. Here are the possible Blocks:

MarkdownBlock(text[, dedent])

A Block of Markdown

FileBlock(filename, contents, **kwargs)

A file of any type.

DividerBlock(**data)

A visual separator between other blocks

HeaderBlock(text)

Text formatted as a header

ListBlock(items)

A list of items, nicely formatted

TableBlock(rows[, headers, ...])

Table display of a list of lists.

KubernetesFieldsBlock(k8s_obj, fields[, ...])

A nicely formatted Kubernetes objects, with a subset of the fields shown

KubernetesDiffBlock(interesting_diffs, old, ...)

A diff between two versions of a Kubernetes object

JsonBlock(json_str)

Json data

CallbackBlock(choices)

A set of buttons that allows callbacks from the sink - for example, a button in Slack that will trigger another action when clicked

Note

Not all block types are supported by all sinks. If an unsupported block arrives at a sink, it will be ignored

Reference

class MarkdownBlock(text, dedent=False)[source]

A Block of Markdown

Parameters:
text

one or more paragraphs of Markdown markup

dedent=False

if True, remove common indentation so that you can use multi-line docstrings.

A simple example:

MarkdownBlock("Hi, *I'm bold* and _I'm italic_")

Things can get hairy when using writing content across multiple lines:

MarkdownBlock(
    "# This is a header \n\n"
    "And this is a paragraph. "
    "Same paragraph. A new string on each line prevents Python from adding newlines."
),

For convenience, use strip_whitespace=True and multiline strings:

MarkdownBlock("""
    Due to strip_whitespace=True this is all one
    paragraph despite indentation and newlines.
    """, strip_whitespace=True)
class FileBlock(filename, contents, **kwargs)[source]

A file of any type. Used for images, log files, binary files, and more.

Parameters:
filename

the file's name

contents

the file's contents

Examples:

FileBlock("test.txt", "this is the file's contents")
class DividerBlock(**data)[source]

A visual separator between other blocks

Create a new model by parsing and validating input data from keyword arguments.

Raises ValidationError if the input data cannot be parsed to form a valid model.

class HeaderBlock(text)[source]

Text formatted as a header

Parameters:
text

the header

class ListBlock(items)[source]

A list of items, nicely formatted

Parameters:
items

a list of strings

class TableBlock(rows, headers=(), column_renderers={}, table_name='', column_width=None, metadata={}, table_format=None, **kwargs)[source]

Table display of a list of lists.

Note: Wider tables appears as a file attachment on Slack, because they aren't rendered properly inline

Variables:
column_width=None

Hint to sink for the portion of size each column should use. Not supported by all sinks. example: [1, 1, 1, 2] use twice the size for last column.

Parameters:
rows

a list of rows. each row is a list of columns

headers=()

names of each column

class KubernetesFieldsBlock(k8s_obj, fields, explanations={})[source]

A nicely formatted Kubernetes objects, with a subset of the fields shown

Parameters:
k8s_obj

a kubernetes object

fields

a list of fields to display. for example ["metadata.name", "metadata.namespace"]

explanations={}

an explanation for each field. for example {"metadata.name": "the pods name"}

class KubernetesDiffBlock(interesting_diffs, old, new, name, kind, namespace=None)[source]

A diff between two versions of a Kubernetes object

Parameters:
interesting_diffs

parts of the diff to emphasize - some sinks will only show these to save space

old

the old version of the object

new

the new version of the object

class JsonBlock(json_str)[source]

Json data

Parameters:
json_str

json as a string

class CallbackBlock(choices)[source]

A set of buttons that allows callbacks from the sink - for example, a button in Slack that will trigger another action when clicked

Parameters:
choices

a dict mapping between each the text on each button to the action it triggers