Skip to content
Open
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
12 changes: 10 additions & 2 deletions elementary/monitor/alerts/source_freshness_alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,17 @@ def asset_type(self) -> str:
def concise_name(self) -> str:
return f"{self.source_name}.{self.identifier}"

@property
def is_runtime_error(self) -> bool:
"""Whether the freshness check failed to run, in either engine's casing.

dbt-core writes "runtime error", dbt Fusion writes it capitalized.
"""
return (self.original_status or "").lower() == "runtime error"

@property
def error_message(self) -> str:
if self.original_status == "runtime error":
if self.is_runtime_error:
return f"Failed to calculate the source freshness\n```{self.error}```"
return self.result_description

Expand All @@ -178,7 +186,7 @@ def summary(self) -> str:
if part
]
)
if self.original_status == "runtime error":
if self.is_runtime_error:
return f'Failed to calculate the source freshness of "{source_fqn}"'
return f'Freshness exceeded the acceptable times on source "{source_fqn}"'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{#
dbt-core writes source freshness statuses in lowercase ("pass", "warn", "error",
"runtime error"), while dbt Fusion writes them capitalized ("Pass", "Warn", "Error").
Compare and return them lowercased, so rows from either engine get the same status.
#}
{% macro normalized_source_freshness_status(status_column='status') %}
case
when {{ status_column }} = 'error' then 'fail'
when {{ status_column }} = 'runtime error' then 'error'
else {{ status_column }}
when lower({{ status_column }}) = 'error' then 'fail'
when lower({{ status_column }}) = 'runtime error' then 'error'
else lower({{ status_column }})
end as normalized_status
{% endmacro %}
24 changes: 22 additions & 2 deletions tests/unit/monitor/alerts/test_alert_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ def _make_model_alert(


def _make_source_freshness_alert(
source_name: str = "my_source", identifier: str = "my_table"
source_name: str = "my_source",
identifier: str = "my_table",
original_status: str = "fail",
) -> SourceFreshnessAlertModel:
return SourceFreshnessAlertModel(
id="id",
source_name=source_name,
identifier=identifier,
original_status="fail",
original_status=original_status,
path="models/src.yml",
error=None,
alert_class_id="acid",
Expand Down Expand Up @@ -114,6 +116,24 @@ def test_concise_name_is_source_dot_identifier(self) -> None:
alert = _make_source_freshness_alert(source_name="raw", identifier="orders")
assert alert.concise_name == "raw.orders"

@pytest.mark.parametrize("status", ["runtime error", "Runtime Error"])
def test_runtime_error_status_is_case_insensitive(self, status: str) -> None:
"""dbt-core writes "runtime error", dbt Fusion "Runtime Error": both are runtime errors."""
alert = _make_source_freshness_alert(original_status=status)
assert alert.is_runtime_error
assert (
alert.error_message
== "Failed to calculate the source freshness\n```None```"
)
assert alert.summary.startswith("Failed to calculate the source freshness")

@pytest.mark.parametrize("status", ["error", "Error", "warn", "Pass"])
def test_other_statuses_are_not_runtime_errors(self, status: str) -> None:
"""Other statuses, in either casing, get the freshness-exceeded summary."""
alert = _make_source_freshness_alert(original_status=status)
assert not alert.is_runtime_error
assert alert.summary.startswith("Freshness exceeded the acceptable times")


class TestAlertModelDetectedAtTimezone:
"""detected_at is stored in UTC (naive) in the alerts table and must be
Expand Down
Loading