Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 9 additions & 22 deletions docs/guides/share-observability-report/send-report-summary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,51 +11,38 @@ This is useful for you and your team members to understand quickly if there is s
- **Hosting on S3 or GCS**: You need to provide slack token and channel to in order to enable the Slack results summary.

<Frame>
<img
src="/pics/report_slack_summary.png"
alt="Demo"
/>
<img src="/pics/report_slack_summary.png" alt="Demo" />
</Frame>

### Enabling report summary for hosted report

### Enabling report summary for hosted report

After you [set up a Slack app and token](/integrations/slack#slack-integration-setup) you can run the following command:

AWS S3:

```shell
edr send-report --aws-profile-name <AWS_PROFILE> --s3-bucket-name <BUCKET_NAME> --slack-token <SLACK_TOKEN> --slack-channel-name <CHANNEL_NAME>
```

GCS:
```shell
edr send-report --google-service-account-path <SERVICE_ACCOUNT_PATH> --gcs-bucket-name <BUCKET_NAME> --slack-token <SLACK_TOKEN> --slack-channel-name <CHANNEL_NAME>
```

A link to the hosted report is included in the summary, which can be accessed based on the permissions you have set on your hosting platform (AWS/GCS).
```shell
edr send-report --google-service-account-path <SERVICE_ACCOUNT_PATH> --gcs-bucket-name <BUCKET_NAME> --slack-token <SLACK_TOKEN> --slack-channel-name <CHANNEL_NAME>
```

A link to the hosted report is included in the summary, which can be accessed based on the permissions you have set on your hosting platform (AWS/GCS).

### Report summary configuration
### Report summary configuration

#### Filter results summary

You can filter your results summary by a specific tag, owner or model.

To filter by tag:

```shell send-report selectors
edr send-report --select tag:finance
edr send-report --select config.meta.owner:@jeff
edr send-report --select model:customers
edr send-report --select customers
```

#### Include test descriptions

For a quick high level overview and to avoid reaching Slack's row limit, the default test results summary does not include test descriptions.
The test descriptions can be included in your summary, but it is only recommended if you have a small number of failures.

The following `include` configuration will allow you to add descriptions:

```shell
edr send-report --include descriptions
```
Binary file modified docs/pics/report_slack_summary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/quickstart/send-slack-alerts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ Elementary support configuring suppression interval for alerts.
By default, the suppression interval for all of the alerts is set to 0.
Elementary won't send any alert that is generated within suppression interval.

`alert_suppression_interval` can accept values of 0-24 (including unrounded numbers) - this number represents the number of hours for which alerts will be skipped.
`alert_suppression_interval` can accept values greater than 0, including unrounded numbers - this number represents the number of hours for which alerts will be skipped.

To set it up globaly for your project, add the alert suppression interval to your models and tests in the `dbt_project.yml` file:

Expand Down
2 changes: 1 addition & 1 deletion elementary/monitor/api/alerts/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def _get_suppressed_alerts(
else None
)
is_alert_in_suppression = (
(current_time_utc - last_sent_time).seconds / 3600
(current_time_utc - last_sent_time).total_seconds() / 3600
<= suppression_interval
if last_sent_time
else False
Expand Down
Empty file.
Empty file.
44 changes: 44 additions & 0 deletions elementary/monitor/api/test_management/test_management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from elementary.clients.api.api_client import APIClient
from elementary.clients.dbt.dbt_runner import DbtRunner
from elementary.monitor.fetchers.test_management.schema import (
ResourcesModel,
TagsModel,
TestsModel,
UserModel,
UsersModel,
)
from elementary.monitor.fetchers.test_management.test_management import (
TestManagementFetcher,
)
from elementary.utils.log import get_logger

logger = get_logger(__name__)


class TestManagementAPI(APIClient):
def __init__(
self,
dbt_runner: DbtRunner,
exclude_elementary: bool = True,
):
super().__init__(dbt_runner)
self.test_management_fetcher = TestManagementFetcher(dbt_runner=self.dbt_runner)
self.exclude_elementary = exclude_elementary

def get_resources(self) -> ResourcesModel:
return self.test_management_fetcher.get_resources(self.exclude_elementary)

def get_tests(self) -> TestsModel:
tests = self.test_management_fetcher.get_tests()
return TestsModel(tests=tests)

def get_tags(self) -> TagsModel:
return self.test_management_fetcher.get_tags()

def get_project_users(self) -> UsersModel:
project_users = self.test_management_fetcher.get_all_project_users()
project_users = [
UserModel(name=project_user, origin="project")
for project_user in project_users
]
return UsersModel(users=project_users)
2 changes: 1 addition & 1 deletion elementary/monitor/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def get_cli_properties() -> dict:
@click.option(
"--group-by",
type=click.Choice(["alert", "table"]),
default="alert",
default=None,
help="Whether to group alerts by 'alert' or by 'table'",
)
@click.pass_context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ def get_slack_message(
filter: SelectorFilterSchema = SelectorFilterSchema(),
include_description: bool = False,
) -> SlackMessageSchema:
self._add_title_to_slack_alert(
test_results=test_results,
bucket_website_url=bucket_website_url,
self._add_title_to_slack_alert(env=env)
self._add_preview_to_slack_alert(
test_results,
days_back=days_back,
env=env,
bucket_website_url=bucket_website_url,
filter=filter,
)
self._add_preview_to_slack_alert(test_results)
self._add_details_to_slack_alert(
test_results=test_results,
include_description=include_description,
Expand All @@ -38,44 +37,18 @@ def get_slack_message(

def _add_title_to_slack_alert(
self,
test_results: List[TestResultSummarySchema],
env: str,
days_back: int,
bucket_website_url: Optional[str] = None,
filter: SelectorFilterSchema = SelectorFilterSchema(),
):
env_text = (
":construction: Development"
if env == "dev"
else ":large_green_circle: Production"
)
summary_filter_text = self._get_summary_filter_text(days_back, filter)
totals = self._get_test_results_totals(test_results)

title_blocks = [
self.create_header_block(f":mag: Monitoring summary ({env_text})"),
self.create_text_section_block(summary_filter_text),
self.create_divider_block(),
]

if bucket_website_url:
title_blocks.append(
self.create_text_section_block(
f"<{bucket_website_url}|View full report> :arrow_upper_right:"
)
)

title_blocks.append(
self.create_fields_section_block(
[
f":white_check_mark: Passed: {totals.get('passed', 0)}",
f":small_red_triangle: Failed: {totals.get('failed', 0)}",
f":exclamation: Errors: {totals.get('error', 0)}",
f":Warning: Warning: {totals.get('warning', 0)}",
]
)
)

title_blocks.append(self.create_divider_block())
self._add_always_displayed_blocks(title_blocks)

@staticmethod
Expand All @@ -96,37 +69,42 @@ def _get_summary_filter_text(

return f"_This summary was generated with the following filters - {days_back_text}{f', {selector_text}' if selector_text else ''}_"

def _add_preview_to_slack_alert(self, test_results: List[TestResultSummarySchema]):
owners = []
tags = []
subscribers = []
for test in test_results:
if test.status != "pass":
formatted_tags = [
tag if tag.startswith(TAG_PREFIX) else f"{TAG_PREFIX}{tag}"
for tag in test.tags
def _add_preview_to_slack_alert(
self,
test_results: List[TestResultSummarySchema],
days_back: int,
filter: SelectorFilterSchema = SelectorFilterSchema(),
bucket_website_url: Optional[str] = None,
):
preview_blocks = []

summary_filter_text = self._get_summary_filter_text(days_back, filter)
preview_blocks.append(self.create_text_section_block(summary_filter_text))

if bucket_website_url:
preview_blocks.append(
self.create_text_section_block(
f"<{bucket_website_url}|View full report> :arrow_upper_right:"
)
)

totals = self._get_test_results_totals(test_results)
preview_blocks.append(
self.create_fields_section_block(
[
f":white_check_mark: Passed: {totals.get('passed', 0)}",
f":small_red_triangle: Failed: {totals.get('failed', 0)}",
f":exclamation: Errors: {totals.get('error', 0)}",
f":Warning: Warning: {totals.get('warning', 0)}",
]
owners.extend(test.owners)
tags.extend(formatted_tags)
subscribers.extend(test.subscribers)

tags_text = self.prettify_and_dedup_list(tags) if tags else "_No tags_"
owners_text = self.prettify_and_dedup_list(owners) if owners else "_No owners_"
subscribers_text = (
self.prettify_and_dedup_list(subscribers)
if subscribers
else "_No subscribers_"
)
)

preview_blocks = [
self.create_text_section_block(":mega: *Attention required* :mega:"),
self.create_text_section_block(f"*Tags:* {tags_text}"),
self.create_text_section_block(f"*Owners:* {owners_text}"),
self.create_text_section_block(f"*Subscribers:* {subscribers_text}"),
self.create_empty_section_block(),
]
if preview_blocks:
self._add_blocks_as_attachments(preview_blocks)
preview_blocks_filler = [self.create_empty_section_block()] * (
self._MAX_ALERT_PREVIEW_BLOCKS - len(preview_blocks)
)
preview_blocks.extend(preview_blocks_filler)
self._add_blocks_as_attachments(preview_blocks)

def _add_details_to_slack_alert(
self,
Expand Down
29 changes: 29 additions & 0 deletions elementary/monitor/dbt_project/macros/base_queries/owners.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% macro project_owners() %}
{% set project_owners_query %}
with dbt_models as (
select * from {{ ref('elementary', 'dbt_models') }}
),

dbt_sources as (
select * from {{ ref('elementary', 'dbt_sources') }}
),

dbt_seeds as (
select * from {{ ref('elementary', 'dbt_seeds') }}
),

dbt_tests as (
select * from {{ ref('elementary', 'dbt_tests') }}
)

select model_owners as owner from dbt_tests
union
select owner from dbt_models
union
select owner from dbt_sources
union
select owner from dbt_seeds
{% endset %}
{% set owners_agate = run_query(project_owners_query) %}
{% do return(elementary.agate_to_dicts(owners_agate)) %}
{% endmacro %}
86 changes: 86 additions & 0 deletions elementary/monitor/dbt_project/macros/base_queries/resources.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{% macro model_resources(exclude_elementary=true) %}
{% set model_resources_query %}
with dbt_models as (
select * from {{ ref('elementary', 'dbt_models') }}
)

select
name,
schema_name as schema,
tags,
owner as owners
from dbt_models
{% if exclude_elementary %}
where package_name != 'elementary'
{% endif %}
{% endset %}
{% set models_agate = run_query(model_resources_query) %}
{% do return(elementary.agate_to_dicts(models_agate)) %}
{% endmacro %}


{% macro source_resources(exclude_elementary=true) %}
{% set source_resources_query %}
with dbt_sources as (
select * from {{ ref('elementary', 'dbt_sources') }}
)

select
name,
source_name,
schema_name AS schema,
tags,
owner AS owners
from dbt_sources
{% if exclude_elementary %}
where package_name != 'elementary'
{% endif %}
{% endset %}
{% set sources_agate = run_query(source_resources_query) %}
{% do return(elementary.agate_to_dicts(sources_agate)) %}
{% endmacro %}


{% macro all_resources(exclude_elementary=true) %}
{% set models = model_resources(exclude_elementary) %}
{% set sources = source_resources(exclude_elementary) %}

{% set resources = [] %}
{% do resources.extend(sources) %}
{% for model in models %}
{% do model.update({"source_name": none}) %}
{% do resources.append(model) %}
{% endfor %}
{% do return(resources) %}
{% endmacro %}


{% macro resources_meta() %}
{% set resources_meta_query %}
with dbt_models as (
select * from {{ ref('elementary', 'dbt_models') }}
),

dbt_sources as (
select * from {{ ref('elementary', 'dbt_sources') }}
),

dbt_seeds as (
select * from {{ ref('elementary', 'dbt_seeds') }}
),

dbt_tests as (
select * from {{ ref('elementary', 'dbt_tests') }}
)

select meta from dbt_tests
union
select meta from dbt_models
union
select meta from dbt_sources
union
select meta from dbt_seeds
{% endset %}
{% set resources_meta_agate = run_query(resources_meta_query) %}
{% do return(elementary.agate_to_dicts(resources_meta_agate)) %}
{% endmacro %}
Loading