Skip to content

Commit ec828b9

Browse files
authored
Merge branch 'master' into quiet-logs
2 parents 9b7c1fd + 885bc7c commit ec828b9

28 files changed

Lines changed: 811 additions & 41 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<a href="https://join.slack.com/t/elementary-community/shared_invite/zt-uehfrq2f-zXeVTtXrjYRbdE_V6xq4Rg"><img src="https://img.shields.io/badge/join-Slack-ff69b4"/></a>
66
<a href="https://docs.elementary-data.com/oss/quickstart/quickstart-cli-package"><img src="https://img.shields.io/badge/docs-quickstart-orange"/></a>
77
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-ff69b4"/>
8-
<img alt="Downloads" src="https://static.pepy.tech/personalized-badge/elementary-lineage?period=total&units=international_system&left_color=grey&right_color=orange"&left_text=Downloads/>
8+
<img alt="Downloads" src="https://static.pepy.tech/personalized-badge/elementary-data?period=month&units=international_system&left_color=grey&right_color=orange"&left_text=Downloads/>
99
</p>
1010

1111
<h2 align="center">
@@ -198,7 +198,10 @@ For additional information and help:
198198
<a href="https://github.com/Lawiss"><img src="https://avatars.githubusercontent.com/u/30115537?v=4" width="50" height="50" alt=""/></a>
199199
<a href="https://github.com/abhipalsingh"><img src="https://avatars.githubusercontent.com/u/57302403?v=4" width="50" height="50" alt=""/></a>
200200
<a href="https://github.com/pushrbx"><img src="https://avatars.githubusercontent.com/u/6832715?v=4" width="50" height="50" alt=""/></a>
201-
201+
<a href="https://github.com/Ryosuke839"><img src="https://avatars.githubusercontent.com/u/389513?v=4" width="50" height="50" alt=""/></a>
202+
<a href="https://github.com/tlangton3"><img src="https://avatars.githubusercontent.com/u/155970791?v=4" width="50" height="50" alt=""/></a>
203+
<a href="https://github.com/SoloJ"><img src="https://avatars.githubusercontent.com/u/7585392?v=4" width="50" height="50" alt=""/></a>
204+
<a href="https://github.com/StevenReitsma"><img src="https://avatars.githubusercontent.com/u/4895139?v=4" width="50" height="50" alt=""/></a>
202205

203206

204207
<!-- markdownlint-restore -->

elementary/messages/blocks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class Icon(Enum):
2020
BELL = "bell"
2121
GEM = "gem"
2222
SPARKLES = "sparkles"
23+
LINK = "link"
2324

2425

2526
class TextStyle(Enum):

elementary/messages/formats/unicode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Icon.BELL: "🔔",
1616
Icon.GEM: "💎",
1717
Icon.SPARKLES: "✨",
18+
Icon.LINK: "🔗",
1819
}
1920

2021
for icon in Icon:

elementary/monitor/alerts/alert.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,23 @@
77
ReportLinkData,
88
)
99
from elementary.utils.log import get_logger
10+
from elementary.utils.pydantic_shim import BaseModel
1011
from elementary.utils.time import DATETIME_WITH_TIMEZONE_FORMAT
1112

1213
logger = get_logger(__name__)
1314

1415

16+
class OrchestratorInfo(BaseModel):
17+
"""Structured orchestrator metadata for alerts."""
18+
19+
job_id: Optional[str] = None
20+
job_name: Optional[str] = None
21+
run_id: Optional[str] = None
22+
orchestrator: Optional[str] = None
23+
job_url: Optional[str] = None
24+
run_url: Optional[str] = None
25+
26+
1527
class AlertModel:
1628
def __init__(
1729
self,
@@ -32,6 +44,12 @@ def __init__(
3244
alert_fields: Optional[List[str]] = None,
3345
elementary_database_and_schema: Optional[str] = None,
3446
env: Optional[str] = None,
47+
job_id: Optional[str] = None,
48+
job_name: Optional[str] = None,
49+
job_run_id: Optional[str] = None,
50+
job_url: Optional[str] = None,
51+
job_run_url: Optional[str] = None,
52+
orchestrator: Optional[str] = None,
3553
**kwargs,
3654
):
3755
self.id = id
@@ -65,6 +83,12 @@ def __init__(
6583
self.alert_fields = alert_fields
6684
self.elementary_database_and_schema = elementary_database_and_schema
6785
self.env = env
86+
self.job_id = job_id
87+
self.job_name = job_name
88+
self.job_run_id = job_run_id
89+
self.job_url = job_url
90+
self.job_run_url = job_run_url
91+
self.orchestrator = orchestrator
6892

6993
@property
7094
def unified_meta(self) -> Dict:
@@ -84,3 +108,26 @@ def summary(self) -> str:
84108

85109
def get_report_link(self) -> Optional[ReportLinkData]:
86110
raise NotImplementedError
111+
112+
@property
113+
def orchestrator_info(self) -> Optional[OrchestratorInfo]:
114+
"""Returns structured orchestrator metadata if available."""
115+
if not any(
116+
[
117+
self.job_name,
118+
self.job_run_id,
119+
self.orchestrator,
120+
self.job_url,
121+
self.job_run_url,
122+
]
123+
):
124+
return None
125+
126+
return OrchestratorInfo(
127+
job_id=self.job_id or None,
128+
job_name=self.job_name or None,
129+
run_id=self.job_run_id or None,
130+
orchestrator=self.orchestrator or None,
131+
job_url=self.job_url or None,
132+
run_url=self.job_run_url or None,
133+
)

elementary/monitor/alerts/alert_messages/builder.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
from pydantic import BaseModel
55

66
from elementary.messages.block_builders import (
7+
BoldTextBlock,
78
BoldTextLineBlock,
89
BulletListBlock,
910
FactsBlock,
1011
ItalicTextLineBlock,
1112
JsonCodeBlock,
13+
LinkInlineBlocks,
1214
LinksLineBlock,
1315
MentionLineBlock,
1416
NonPrimaryFactBlock,
@@ -33,6 +35,7 @@
3335
TextStyle,
3436
)
3537
from elementary.messages.message_body import Color, MessageBlock, MessageBody
38+
from elementary.monitor.alerts.alert import OrchestratorInfo
3639
from elementary.monitor.alerts.alert_messages.alert_fields import AlertField
3740
from elementary.monitor.alerts.alerts_groups.alerts_group import AlertsGroup
3841
from elementary.monitor.alerts.alerts_groups.base_alerts_group import BaseAlertsGroup
@@ -42,6 +45,9 @@
4245
from elementary.monitor.alerts.model_alert import ModelAlertModel
4346
from elementary.monitor.alerts.source_freshness_alert import SourceFreshnessAlertModel
4447
from elementary.monitor.alerts.test_alert import TestAlertModel
48+
from elementary.monitor.data_monitoring.alerts.integrations.utils.orchestrator_link import (
49+
create_orchestrator_link,
50+
)
4551
from elementary.monitor.data_monitoring.alerts.integrations.utils.report_link import (
4652
ReportLinkData,
4753
)
@@ -106,6 +112,7 @@ def _get_run_alert_subtitle_block(
106112
suppression_interval: Optional[int] = None,
107113
env: Optional[str] = None,
108114
links: list[ReportLinkData] = [],
115+
orchestrator_info: Optional[OrchestratorInfo] = None,
109116
) -> LinesBlock:
110117
summary = []
111118
summary.append((type.capitalize() + ":", name))
@@ -114,16 +121,54 @@ def _get_run_alert_subtitle_block(
114121
summary.append(("Status:", status or "Unknown"))
115122
if detected_at_str:
116123
summary.append(("Time:", detected_at_str))
124+
125+
subtitle_lines = []
126+
127+
if orchestrator_info and orchestrator_info.job_name:
128+
orchestrator_name = orchestrator_info.orchestrator or "orchestrator"
129+
job_info_text = f"{orchestrator_info.job_name} (via {orchestrator_name})"
130+
131+
orchestrator_link = create_orchestrator_link(orchestrator_info)
132+
if orchestrator_link:
133+
job_inlines: List[InlineBlock] = [
134+
BoldTextBlock(text="Job:"),
135+
TextBlock(text=job_info_text + " | "),
136+
]
137+
job_inlines.extend(
138+
LinkInlineBlocks(
139+
text=orchestrator_link.text,
140+
url=orchestrator_link.url,
141+
icon=orchestrator_link.icon,
142+
)
143+
)
144+
145+
subtitle_lines.append(LineBlock(inlines=job_inlines))
146+
else:
147+
summary.append(("Job:", job_info_text))
117148
if suppression_interval:
118149
summary.append(("Suppression interval:", str(suppression_interval)))
119-
subtitle_lines = [SummaryLineBlock(summary=summary)]
120150

121-
if links:
122-
subtitle_lines.append(
123-
LinksLineBlock(
124-
links=[(link.text, link.url, link.icon) for link in links]
151+
subtitle_lines.append(SummaryLineBlock(summary=summary))
152+
153+
all_links = []
154+
155+
for link in links:
156+
all_links.append((link.text, link.url, link.icon))
157+
158+
if orchestrator_info and not orchestrator_info.job_name:
159+
orchestrator_link = create_orchestrator_link(orchestrator_info)
160+
if orchestrator_link:
161+
all_links.append(
162+
(
163+
orchestrator_link.text,
164+
orchestrator_link.url,
165+
orchestrator_link.icon,
166+
)
125167
)
126-
)
168+
169+
if all_links:
170+
subtitle_lines.append(LinksLineBlock(links=all_links))
171+
127172
return LinesBlock(lines=subtitle_lines)
128173

129174
def _get_run_alert_subtitle_links(
@@ -151,6 +196,7 @@ def _get_run_alert_subtitle_blocks(
151196
asset_type = "snapshot" if alert.materialization == "snapshot" else "model"
152197
asset_name = alert.alias
153198
links = self._get_run_alert_subtitle_links(alert)
199+
orchestrator_info = alert.orchestrator_info
154200
return [
155201
self._get_run_alert_subtitle_block(
156202
type=asset_type,
@@ -160,6 +206,7 @@ def _get_run_alert_subtitle_blocks(
160206
suppression_interval=alert.suppression_interval,
161207
env=alert.env,
162208
links=links,
209+
orchestrator_info=orchestrator_info,
163210
)
164211
]
165212

elementary/monitor/alerts/model_alert.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ def __init__(
3737
alert_fields: Optional[List[str]] = None,
3838
elementary_database_and_schema: Optional[str] = None,
3939
env: Optional[str] = None,
40+
job_id: Optional[str] = None,
41+
job_name: Optional[str] = None,
42+
job_run_id: Optional[str] = None,
43+
job_url: Optional[str] = None,
44+
job_run_url: Optional[str] = None,
45+
orchestrator: Optional[str] = None,
4046
**kwargs,
4147
):
4248
super().__init__(
@@ -57,6 +63,13 @@ def __init__(
5763
alert_fields,
5864
elementary_database_and_schema,
5965
env=env,
66+
job_id=job_id,
67+
job_name=job_name,
68+
job_run_id=job_run_id,
69+
job_url=job_url,
70+
job_run_url=job_run_url,
71+
orchestrator=orchestrator,
72+
**kwargs,
6073
)
6174
self.alias = alias
6275
self.path = path

elementary/monitor/alerts/source_freshness_alert.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ def __init__(
4646
alert_fields: Optional[List[str]] = None,
4747
elementary_database_and_schema: Optional[str] = None,
4848
env: Optional[str] = None,
49+
job_id: Optional[str] = None,
50+
job_name: Optional[str] = None,
51+
job_run_id: Optional[str] = None,
52+
job_url: Optional[str] = None,
53+
job_run_url: Optional[str] = None,
54+
orchestrator: Optional[str] = None,
4955
**kwargs,
5056
):
5157
super().__init__(
@@ -66,6 +72,13 @@ def __init__(
6672
alert_fields,
6773
elementary_database_and_schema,
6874
env=env,
75+
job_id=job_id,
76+
job_name=job_name,
77+
job_run_id=job_run_id,
78+
job_url=job_url,
79+
job_run_url=job_run_url,
80+
orchestrator=orchestrator,
81+
**kwargs,
6982
)
7083
self.snapshotted_at_str = (
7184
convert_datetime_utc_str_to_timezone_str(

elementary/monitor/alerts/test_alert.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ def __init__(
4848
alert_fields: Optional[List[str]] = None,
4949
elementary_database_and_schema: Optional[str] = None,
5050
env: Optional[str] = None,
51+
job_id: Optional[str] = None,
52+
job_name: Optional[str] = None,
53+
job_run_id: Optional[str] = None,
54+
job_url: Optional[str] = None,
55+
job_run_url: Optional[str] = None,
56+
orchestrator: Optional[str] = None,
5157
**kwargs,
5258
):
5359
super().__init__(
@@ -68,6 +74,13 @@ def __init__(
6874
alert_fields,
6975
elementary_database_and_schema,
7076
env=env,
77+
job_id=job_id,
78+
job_name=job_name,
79+
job_run_id=job_run_id,
80+
job_url=job_url,
81+
job_run_url=job_run_url,
82+
orchestrator=orchestrator,
83+
**kwargs,
7184
)
7285
self.table_name = table_name
7386
self.test_type = test_type

elementary/monitor/api/filters/filters.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from elementary.monitor.api.models.schema import (
66
ModelRunsSchema,
77
NormalizedModelSchema,
8+
NormalizedSeedSchema,
9+
NormalizedSnapshotSchema,
810
NormalizedSourceSchema,
911
)
1012
from elementary.monitor.api.totals_schema import TotalsSchema
@@ -25,11 +27,15 @@ def get_filters(
2527
models: Dict[str, NormalizedModelSchema],
2628
sources: Dict[str, NormalizedSourceSchema],
2729
models_runs: List[ModelRunsSchema],
30+
seeds: Dict[str, NormalizedSeedSchema],
31+
snapshots: Dict[str, NormalizedSnapshotSchema],
2832
) -> FiltersSchema:
2933
test_results_filters = self._get_test_filters(
30-
test_results_totals, models, sources
34+
test_results_totals, models, sources, seeds, snapshots
35+
)
36+
test_runs_filters = self._get_test_filters(
37+
test_runs_totals, models, sources, seeds, snapshots
3138
)
32-
test_runs_filters = self._get_test_filters(test_runs_totals, models, sources)
3339
model_runs_filters = self._get_model_runs_filters(models_runs)
3440
return FiltersSchema(
3541
test_results=test_results_filters,
@@ -42,6 +48,8 @@ def _get_test_filters(
4248
totals: Dict[str, TotalsSchema],
4349
models: Dict[str, NormalizedModelSchema],
4450
sources: Dict[str, NormalizedSourceSchema],
51+
seeds: Dict[str, NormalizedSeedSchema],
52+
snapshots: Dict[str, NormalizedSnapshotSchema],
4553
) -> List[FilterSchema]:
4654
failures_filter = FilterSchema(name="failures", display_name="Failures")
4755
warnings_filter = FilterSchema(name="warnings", display_name="Warnings")
@@ -50,7 +58,12 @@ def _get_test_filters(
5058
no_tests_filter = FilterSchema(name="no_test", display_name="No Tests")
5159

5260
totals_models_ids = totals.keys()
53-
artifacts: List[ArtifactSchema] = [*models.values(), *sources.values()]
61+
artifacts: List[ArtifactSchema] = [
62+
*models.values(),
63+
*sources.values(),
64+
*seeds.values(),
65+
*snapshots.values(),
66+
]
5467
for artifact in artifacts:
5568
if artifact.unique_id and artifact.unique_id not in totals_models_ids:
5669
no_tests_filter.add_model_unique_id(artifact.unique_id)

elementary/monitor/api/report/report.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,13 @@ def get_report_data(
161161
lineage_node_ids, exclude_elementary_models
162162
)
163163
filters = filters_api.get_filters(
164-
test_results_totals, test_runs_totals, models, sources, models_runs.runs
164+
test_results_totals,
165+
test_runs_totals,
166+
models,
167+
sources,
168+
models_runs.runs,
169+
seeds,
170+
snapshots,
165171
)
166172

167173
serializable_groups = groups.dict()

0 commit comments

Comments
 (0)